Skip to content

Instantly share code, notes, and snippets.

@Spiralis
Last active April 10, 2019 20:39
Show Gist options
  • Save Spiralis/c442d6fcd34b42dfd535003bea7b2190 to your computer and use it in GitHub Desktop.
Save Spiralis/c442d6fcd34b42dfd535003bea7b2190 to your computer and use it in GitHub Desktop.
A simplified XState statecharts state-machine for running a handball game. Typically used with a control app for the match officials. Note: There are still parts missing in this sample, like disciplinary actions (2 min, yellow and red cards), as well as who made the goals, and adjusting the match-clock (adjusting the data on the fly). But, it is…
// Available variables:
// Machine (machine factory function)
// XState (all XState exports)
function pad(n, width) {
var n = n + "";
return n.length >= width ? n : new Array(width - n.length + 1).join("0") + n;
}
function displayTime(ticksInSecs) {
var ticks = ticksInSecs / 1000;
var hh = Math.floor(ticks / 3600);
var mm = Math.floor((ticks % 3600) / 60);
var ss = ticks % 60;
return pad(hh, 2) + ":" + pad(mm, 2) + ":" + pad(ss, 2);
}
const startClock = XState.actions.assign({
start_ticks: (ctx, event) => Date.now()
});
const stopClock = XState.actions.assign({
match_ticks: (ctx, event) => ctx.match_ticks + Date.now() - ctx.start_ticks
});
const setClockDisplay = XState.actions.assign({
match_clock: (ctx, event) => displayTime(ctx.match_ticks)
});
const incPeriod = XState.actions.assign({
period: (ctx, event) => ctx.period + 1,
team: (ctx, event) => ({
...ctx.team,
a: {
...ctx.team.a,
timeouts_in_period: 0
},
b: {
...ctx.team.b,
timeouts_in_period: 0
}
})
});
const matchPlaying = (ctx, event) => {
return ctx.period > 0 && ctx.period <= ctx.setup.num_periods;
};
const matchFinished = (ctx, event) => {
return ctx.period > ctx.setup.num_periods;
};
const canTimeoutA = (ctx, event) => {
return (
ctx.team.a.timeouts_in_period < ctx.setup.max_timeouts_per_period &&
ctx.team.a.timeouts_total < ctx.setup.max_timeouts
);
};
const canTimeoutB = (ctx, event) => {
return (
ctx.team.b.timeouts_in_period < ctx.setup.max_timeouts_per_period &&
ctx.team.b.timeouts_total < ctx.setup.max_timeouts
);
};
const incTimeoutA = XState.actions.assign({
team: (ctx, event) => ({
...ctx.team,
a: {
...ctx.team.a,
timeouts_total: ctx.team.a.timeouts_total + 1,
timeouts_in_period: ctx.team.a.timeouts_in_period + 1
}
})
});
const incTimeoutB = XState.actions.assign({
team: (ctx, event) => ({
...ctx.team,
b: {
...ctx.team.b,
timeouts_total: ctx.team.b.timeouts_total + 1,
timeouts_in_period: ctx.team.b.timeouts_in_period + 1
}
})
});
const incGoalA = XState.actions.assign({
team: (ctx, event) => ({
...ctx.team,
a: {
...ctx.team.a,
goals: ctx.team.a.goals + 1
}
})
});
const incGoalB = XState.actions.assign({
team: (ctx, event) => ({
...ctx.team,
b: {
...ctx.team.b,
goals: ctx.team.b.goals + 1
}
})
});
const playingStates = {
intial: "running",
states: {
running: {
onEntry: "startClock",
onExit: ["stopClock", "setClockDisplay"],
on: {
STOP_TIME: "paused",
TIMEOUT_A: {
target: "in_timeout",
actions: "incTimeoutA",
cond: "canTimeoutA"
},
TIMEOUT_B: {
target: "in_timeout",
actions: "incTimeoutB",
cond: "canTimeoutB"
},
TIMER_PERIOD_BREAK: {
target: "in_period_break",
actions: "incPeriod"
}
}
},
paused: {
on: {
START_TIME: "running"
}
},
in_timeout: {
on: {
TIMER_TO_DONE: "running"
}
},
in_period_break: {
on: {
"": [
{
target: "done",
cond: "matchFinished"
}
],
TIMER_PAUSE_DONE: "running"
}
},
done: {
type: "final"
}
}
};
const matchMachine = Machine(
{
id: "match",
initial: "pending",
context: {
setup: {
num_periods: 2,
max_timeouts: 3,
max_timeouts_per_period: 2,
timeout_length: 60000
},
start_ticks: 0, // Time for last event, making it possible for stopClock to calculate what time to add on exit
match_ticks: 0, // Runtime for the match, increased on every stop of time in the match.
match_clock: "00:00:00",
period: 0,
team: {
a: {
timeouts_total: 0,
timeouts_in_period: 0,
goals: 0
},
b: {
timeouts_total: 0,
timeouts_in_period: 0,
goals: 0
}
}
},
states: {
pending: {
on: {
START: {
target: "playing",
actions: "incPeriod"
}
}
},
playing: {
...playingStates,
onDone: {
target: "finished"
}
},
finished: {
type: "final"
}
},
on: {
HOME_GOAL: {
actions: "incGoalA",
cond: "matchPlaying"
},
AWAY_GOAL: {
actions: "incGoalB",
cond: "matchPlaying"
}
}
},
{
actions: {
startClock,
stopClock,
setClockDisplay,
incPeriod,
incTimeoutA,
incTimeoutB,
incGoalA,
incGoalB
},
guards: { matchPlaying, matchFinished, canTimeoutA, canTimeoutB }
}
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment