Created
January 15, 2019 15:34
-
-
Save xseignard/bc9126abe9e2467ef928deb6069f3343 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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