type State = 'menu' | 'playing' | 'paused' | 'gameover';
type Event = 'start' | 'pause' | 'resume' | 'die' | 'restart';
const fsm = createMachine<State, Event>({
initial: 'menu',
on: {
menu: { start: 'playing' },
playing: { pause: 'paused', die: 'gameover' },
paused: { resume: 'playing' },
gameover: { restart: 'menu' },
},
onEnter: { gameover: () => audio.playGameOver() },
});
fsm.send('start'); // → 'playing'
fsm.send('pause'); // → 'paused'
fsm.matches('paused'); // → true
fsm.send('start'); // ignored (no transition from 'paused' on 'start')
A statically-typed finite-state machine for scenes, menus, modes, or any lifecycle where "which state am I in" matters as much as the data.
Define your states and events as string-literal unions; transitions are checked for exhaustiveness — you can never
send('jump')if the FSM doesn't accept'jump', and you can never declare a transition to a state you didn't list.