Skip to content

Instantly share code, notes, and snippets.

@xseignard
Created January 15, 2019 15:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save xseignard/bc9126abe9e2467ef928deb6069f3343 to your computer and use it in GitHub Desktop.
Save xseignard/bc9126abe9e2467ef928deb6069f3343 to your computer and use it in GitHub Desktop.
const { Machine, actions } = require('xstate');
const { interpret } = require('xstate/lib/interpreter');
const { send } = actions;
const myActions = {
resetRoom(ctx, e) {
// console.log('Reset room', ctx);
},
openIn(ctx, e) {
// console.log('Open in', ctx);
},
activateMechanism(ctx, e) {
// console.log('Activate', ctx);
},
deactivateMechanism(ctx, e) {
// console.log('Deactivate', ctx);
},
openOut(ctx, e) {
// console.log('Open out', ctx);
},
};
const mechanisms = {
initial: 'enter',
states: {
enter: {
on: { ENTER: 'e1' },
onEntry: ['resetRoom'],
onExit: ['openIn'],
after: [{ delay: 1000, target: 'e1' }],
},
e1: {
on: { E1: 'e2' },
onEntry: ['deactivateMechanism'],
onExit: ['activateMechanism'],
after: [{ delay: 1000, target: 'e2' }],
},
e2: {
on: { E2: 'e3' },
onEntry: ['deactivateMechanism'],
onExit: ['activateMechanism'],
after: [{ delay: 1000, target: 'e3' }],
},
e3: {
on: { E3: 'leave' },
onEntry: ['deactivateMechanism'],
onExit: ['activateMechanism'],
after: [{ delay: 1000, target: 'leave' }],
},
leave: {
type: 'final',
onEntry: ['deactivateMechanism'],
onExit: ['openOut'],
},
},
};
const game = Machine(
{
key: 'haunted',
initial: 'wait',
states: {
wait: {
// try to start directly
onEntry: () => gotToNextRoomIfEmpty('wait', 'r1', 'NEXT'),
on: { NEXT: 'r1' },
},
r1: {
onDone: {
actions: () => updateGames('wait', 'r1', 'r2', 'NEXT'),
},
on: { NEXT: 'r2' },
...mechanisms,
},
r2: {
onDone: {
actions: () => updateGames('r1', 'r2', 'r3', 'NEXT'),
},
on: { NEXT: 'r3' },
...mechanisms,
},
r3: {
onDone: {
actions: () => updateGames('r2', 'r3', 'r4', 'NEXT'),
},
on: { NEXT: 'r4' },
...mechanisms,
},
r4: {
onDone: {
actions: () => updateGames('r3', 'r4', 'r5', 'NEXT'),
},
on: { NEXT: 'r5' },
...mechanisms,
},
r5: {
onDone: {
actions: () => updateGames('r4', 'r5', 'r6', 'NEXT'),
},
on: { NEXT: 'r6' },
...mechanisms,
},
r6: {
onDone: {
actions: [
// finish the game
send('END'),
// notify other games they can go from r5 to r6
() => notifyGames('r5', 'NEXT'),
],
},
on: { END: 'end' },
...mechanisms,
},
end: { type: 'final' },
},
on: { QUIT: 'end' },
onDone: {
actions: () => cleanup(),
},
},
{ actions: myActions }
);
// find a game by state key (in which room it is) name
const findGame = room => {
return games.find(g => Object.keys(g.state.value)[0] === room || g.state.value === room);
};
// check if the next room is empty on every game
// if so, go to it
const gotToNextRoomIfEmpty = (currentRoom, nextRoom, event) => {
// find the game being on the currentRoom
const currentGame = findGame(currentRoom);
// find the game being on the nextRoom
const nextGame = findGame(nextRoom);
// if there is no game being on the nextRoom, we can put the currentGame on this room
if (!nextGame) {
currentGame.send(event);
return true;
} else return false;
};
// notify other games they can go from roomToUpdate to roomToUpdate + 1
const notifyGames = (roomToUpdate, event) => {
// find the game being on the roomToUpdate
const gameToUpdate = findGame(roomToUpdate);
// update it if existing
if (gameToUpdate) gameToUpdate.send(event);
};
// combine the two above
const updateGames = (prevRoom, currentRoom, nextRoom, event) => {
if (gotToNextRoomIfEmpty(currentRoom, nextRoom, event)) notifyGames(prevRoom, event);
};
// remove ended games
const cleanup = () => {
games = games.filter(g => {
const ended = g.state.value === 'end';
if (ended) g.stop();
return !ended;
});
};
// keeping trace of the started services
let games = [];
// init a new service, setting its context and store it in the stack of started services
const init = team => {
const currentGame = game.withContext({ team });
const service = interpret(currentGame).onTransition(state =>
console.log(`${team}: ${JSON.stringify(state.value)}`)
);
games.push(service);
service.start();
};
// start 2 services
// the second one should leave the wait state only when the first one enters the r2 state
init('Team1');
setTimeout(() => {
init('Team2');
}, 1000);
// after some time, start another one
setTimeout(() => {
init('Team3');
}, 10000);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment