Skip to content

Commit

Permalink
feat: state machines
Browse files Browse the repository at this point in the history
  • Loading branch information
neolectron committed Jul 28, 2023
1 parent 32643dd commit 4a0c432
Show file tree
Hide file tree
Showing 6 changed files with 197 additions and 186 deletions.
216 changes: 117 additions & 99 deletions src/machines/game.machine.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,39 @@
import { type ActorRefFrom, assign, createMachine, spawn, send } from 'xstate';
import { personMachine } from './person.machine';
import {
type ActorRefFrom,
assign,
createMachine,
spawn,
sendTo,
} from 'xstate';
import { MathUtils } from 'three';
import { hotspotMachine } from './hotspot.machine';

import { personMachine } from './person.machine';
import { barMachine } from './bar.machine';
import { toiletMachine } from './toilet.machine';

const generateRandomDisasters = (night: number) => {
const getRandomDisasterName = () =>
disasterNames[MathUtils.randInt(1, disasterNames.length)];

const disasters = Array.from({ length: night + 1 }, () => ({
time: MathUtils.randInt(3, length),
disasterName: getRandomDisasterName(),
}));

disasters.sort((a, b) => a.time - b.time);
return disasters;
};

const METERS_CONFIG = {
clock: {
initialValue: 0,
incrementValue: 2,
incrementValue: 5,
maxValue: 100,
clamp: (v: number) => MathUtils.clamp(v, 0, METERS_CONFIG.clock.maxValue),
},
};

const HOTSPOTS = {
bar: barMachine,
toilet: toiletMachine,
};
const disasterNames = ['onBlackout', 'onPolice', 'onFire'];

export const gameMachine = createMachine(
{
Expand All @@ -26,36 +42,25 @@ export const gameMachine = createMachine(
description:
'The game machine is the root machine of the game, it handles the game state.',
context: {
persons: [],
hotspots: [],
persons: [spawn(personMachine, MathUtils.generateUUID())],
hotspots: {
bar: spawn(barMachine, MathUtils.generateUUID()),
toilet: spawn(toiletMachine, MathUtils.generateUUID()),
},
clock: METERS_CONFIG.clock.initialValue,
currentNight: 0,
meters: {
hype: 0,
},
disasterForTheNight: [],
},
on: {
onIncrementHype: {
actions: ['incrementHype'],
},
onDecrementHype: {
actions: ['decrementHype'],
},
onAddPerson: {
actions: ['addPerson'],
},
onRemovePerson: {
actions: ['removePerson'],
},
onAddHotspot: {
actions: ['addHotspot'],
},
onRemoveHotspot: {
actions: ['removeHotspot'],
},
onGameOver: {
target: 'finished',
},
},
entry: assign((context) => {
const disasterForTheNight = generateRandomDisasters(context.currentNight);
return {
...context,
disasterForTheNight,
};
}),
initial: 'notStarted',
states: {
notStarted: {
Expand All @@ -82,10 +87,6 @@ export const gameMachine = createMachine(
onPause: {
target: 'paused',
},
onEndNight: {
actions: 'endNight',
target: 'paused',
},
},
},
paused: {
Expand All @@ -97,97 +98,114 @@ export const gameMachine = createMachine(
},
finished: {},
},
on: {
onIncrementHype: {
actions: assign((context, event) => {
return {
...context,
meters: {
...context.meters,
hype: context.meters.hype + event.hype,
},
};
}),
},
onDecrementHype: {
actions: assign((context, event) => {
return {
...context,
meters: {
...context.meters,
hype: context.meters.hype - event.hype,
},
};
}),
},
onAddPerson: {
actions: assign((context) => {
return {
...context,
persons: [
...context.persons,
spawn(personMachine, MathUtils.generateUUID()),
],
};
}),
},
onRemovePerson: {
actions: assign((context, event) => {
return {
...context,
persons: [
...context.persons.filter((machine) => machine.id !== event.id),
],
};
}),
},
onRemovePersonFromAllHotspots: {
actions: (context, event) => {
const { bar, toilet } = context.hotspots;
bar.send({
type: 'onUnregisterPerson',
person: event.person,
});
toilet.send({
type: 'onUnregisterPerson',
person: event.person,
});
return context;
},
},
onGameOver: {
target: 'finished',
},
},
// ------------------------------------------
schema: {
context: {} as {
persons: ActorRefFrom<typeof personMachine>[];
hotspots: ActorRefFrom<typeof hotspotMachine>[];
hotspots: {
bar: ActorRefFrom<typeof barMachine>;
toilet: ActorRefFrom<typeof toiletMachine>;
};
clock: number;
currentNight: number;
meters: {
hype: number;
};
disasterForTheNight: Record<number, (typeof disasterNames)[number]>[];
},
events: {} as
| { type: 'onIncrementHype'; hype: number }
| { type: 'onDecrementHype'; hype: number }
| { type: 'onAddPerson' }
| { type: 'onRemovePerson'; id: string }
| { type: 'onAddHotspot'; hotspotType: 'bar' | 'toilet' }
| { type: 'onRemoveHotspot'; id: string }
| {
type: 'onRemovePersonFromAllHotspots';
person: ActorRefFrom<typeof personMachine>;
}
| { type: 'onStart' }
| { type: 'onPause' }
| { type: 'onEndNight' }
| { type: 'onGameOver' },
| { type: 'onGameOver' }
| { type: 'onBlackout' }
| { type: 'onPolice' }
| { type: 'onFire' },
},
predictableActionArguments: true,
preserveActionOrder: true,
tsTypes: {} as import('./game.machine.typegen').Typegen0,
},
{
actions: {
incrementHype: assign((context, event) => {
return {
...context,
meters: {
...context.meters,
hype: context.meters.hype + event.hype,
},
};
}),
decrementHype: assign((context, event) => {
return {
...context,
meters: {
...context.meters,
hype: context.meters.hype - event.hype,
},
};
}),
addPerson: assign((context) => {
return {
...context,
persons: [
...context.persons,
spawn(personMachine, MathUtils.generateUUID()),
],
};
}),
removePerson: assign((context, event) => {
return {
...context,
persons: [
...context.persons.filter((machine) => machine.id !== event.id),
],
};
}),
addHotspot: assign((context, event) => ({
...context,
hotspots: [
...context.hotspots,
spawn(HOTSPOTS[event.hotspotType], MathUtils.generateUUID()),
],
})),
removeHotspot: assign((context, event) => {
return {
...context,
hotspots: [
...context.hotspots.filter((machine) => machine.id !== event.id),
],
};
}),
tick: assign((context) => {
const clock = context.clock + METERS_CONFIG.clock.incrementValue;
if (clock >= METERS_CONFIG.clock.maxValue) send('EndNight');
if (clock >= METERS_CONFIG.clock.maxValue) sendTo('Game', 'EndNight');

return {
...context,
clock: METERS_CONFIG.clock.clamp(clock),
};
}),
endNight: assign((context) => {
return {
...context,
clock: METERS_CONFIG.clock.initialValue,
};
}),
},
},
);
10 changes: 2 additions & 8 deletions src/machines/game.machine.typegen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,13 @@ export interface Typegen0 {
};
invokeSrcNameMap: {};
missingImplementations: {
actions: never;
actions: 'endNight';
delays: never;
guards: never;
services: never;
};
eventsCausingActions: {
addHotspot: 'onAddHotspot';
addPerson: 'onAddPerson';
decrementHype: 'onDecrementHype';
endNight: 'onEndNight' | 'xstate.after(500)#Game.playing';
incrementHype: 'onIncrementHype';
removeHotspot: 'onRemoveHotspot';
removePerson: 'onRemovePerson';
endNight: 'xstate.after(500)#Game.playing';
tick: 'xstate.after(500)#Game.playing';
};
eventsCausingDelays: {};
Expand Down
Loading

0 comments on commit 4a0c432

Please sign in to comment.