Basic

State (State Machines)

The same key means different things depending on the mode: Enter starts on the menu but restarts on game over. Learn how a state machine organizes a game into menu, play, and game-over.

What you will see

menu playing gameOver menu

About 5 min of reading

Quick check

Before this new lesson, one small question about the lesson you just finished. Recall it, do not look it up.

Try It

The same key means different things depending on the mode, Enter starts the game on the menu but restarts it on game over. A state machine organizes a game into a fixed set of states (menu, play, game over) and runs only the current state's behavior. I want to build the real StateMachine the game ships, so switching modes feels automatic rather than a tangle of if statements.

1

State is just which mode we are in, held in one variable. I set state to 'menu' and use an if chain, the exact same structure as the grade example in Conditionals, to decide what to show. When state is 'playing', the same chain picks different behavior. One string drives everything.

let state = "menu" function act(s) { if (s === "menu") { return "show title" } else if (s === "playing") { return "run physics" } else if (s === "gameOver") { return "show final score" } } console.log(act(state)) console.log(act("playing"))

Before you run it, say out loud what it will print. Then open the answer.

What it prints
show title run physics
The if chain is identical in shape to the grade example in Conditionals, except now it decides whole behaviors instead of a letter grade. Change the state string and the same chain returns different behavior.
2

The same input means different things depending on the state. I write an input handler that checks the key AND the current state: Enter on the menu starts the game, Space while playing makes the player jump, Enter on game over restarts it. Notice Enter does two different things.

function handle(key, state) { if (state === "menu") { if (key === "enter") { return "start" } } else if (state === "playing") { if (key === "space") { return "jump" } } else if (state === "gameOver") { if (key === "enter") { return "restart" } } } console.log(handle("enter", "menu")) console.log(handle("enter", "gameOver")) console.log(handle("space", "playing"))

Before you run it, say out loud what it will print. Then open the answer.

What it prints
start restart jump
Enter on the menu STARTS the game; Enter on game over RESTARTS it. They look identical but the state decides what they do. That double meaning is the whole point of state, the game reacts to "the state I'm in, and this key".
3

The game's real StateMachine holds every state in a Map, tracks which one is current, and on setState calls onExit on the old state and onEnter on the new one. I build the exact class and switch states to watch the setup and cleanup run automatically.

class StateMachine { constructor() { this.states = new Map(); this.currentName = null } add(name, state) { this.states.set(name, state); return this } setState(name) { if (this.currentName === name) { return } const prev = this.states.get(this.currentName) if (prev && prev.onExit) { prev.onExit() } this.currentName = name const next = this.states.get(name) if (next.onEnter) { next.onEnter() } } } const sm = new StateMachine() sm.add('menu', { onEnter: () => console.log('menu onEnter'), onExit: () => console.log('menu onExit') }) sm.add('playing', { onEnter: () => console.log('playing onEnter'), onExit: () => console.log('playing onExit') }) sm.setState('menu') sm.setState('playing') sm.setState('playing')

Before you run it, say out loud what it will print. Then open the answer.

What it prints
menu onEnter menu onExit playing onEnter
The first setState('playing') calls menu.onExit then playing.onEnter. The second setState('playing') is skipped because currentName already matches, that guard prevents redundant setup and cleanup. Each state owns its own onEnter and onExit.
4

The game's main loop is four lines: tick the timer, forward to the current state's update, draw the current state, then request the next frame. I simulate one loop cycle by calling update on a PlayState object that owns all its own logic, setup, work, and draw in one self-contained object.

const PlayState = { onEnter() { console.log('setup new level') }, update() { console.log('move player, spawn world, count score') }, draw() { console.log('draw everything') }, } function loop() { PlayState.update() PlayState.draw() } loop() loop()

Before you run it, say out loud what it will print. Then open the answer.

What it prints
setup new level move player, spawn world, count score draw everything move player, spawn world, count score draw everything
onEnter runs once when we arrive; update and draw run every frame the state is current. All of PlayState's code lives in one object, which is why the loop can stay four lines long without turning into if-else spaghetti.
5

A giant if chain could write the whole game, but as states grow it becomes impossible to read and each transition must be handled in one place while its setup lives in another. The state machine fixes both: each state is self-contained and transitions are centralized. I switch through menu -> playing -> gameOver and back, watching only the current state act.

const sm = { states: new Map(), currentName: null, add(n, s) { this.states.set(n, s); return this }, setState(n) { if (this.currentName === n) return; const p = this.states.get(this.currentName); if (p && p.onExit) p.onExit(); this.currentName = n; const x = this.states.get(n); if (x && x.onEnter) x.onEnter() } } sm.add('menu', { onEnter: () => console.log('menu') }) sm.add('playing', { onEnter: () => console.log('playing') }) sm.add('gameOver', { onEnter: () => console.log('gameOver') }) sm.setState('menu') sm.setState('playing') sm.setState('gameOver') sm.setState('menu')

Before you run it, say out loud what it will print. Then open the answer.

What it prints
menu playing gameOver menu
Each switch prints only the state we just entered, the old state's exit and the new state's setup run automatically, so I never have to remember to clean up. That separation is what keeps a multi-state game from becoming tangled conditionals.

Put it in order

Before you write anything, put these lines back in the right order. Click a line to move it up one step. This support step is easier than writing code from scratch, and it is here to make the last example stick.

Data Flow

A single state variable decides which mode's code runs; the state machine runs the current state's update and draw each frame.
1 Pick the current state, e.g. "menu"
↓
2 setState switches modes, calling onExit then onEnter
↓
3 update(dt, input) runs only the current state's logic
↓
4 draw(ctx) renders only the current state
↓
5 input in that context means something specific to the state
Why this works
Each state is an object with behavior

A "state" isn't just a name, it's an object (we met objects in the Objects group) with behavior attached. It typically has:

  • an onEnter(): run when you arrive,
  • an onExit(): run when you leave,
  • an update(dt, input): do the work every frame while in this state,
  • a draw(ctx): draw the screen while in this state.

Here's the shape of the game's PlayState:

const PlayState = {
  onEnter() { newLevel(); score.reset(); },
  update(dt, input) { movePlayer(dt); spawn.update(dt, input); score.update(); },
  draw(ctx) { drawEverything(ctx); },
};

The state machine just asks the current state to update and draw every frame. Everything else stays inert until it becomes current. That separation is what keeps a multi-state game from turning into tangled if spaghetti.

Why a state machine beats if/else

You could write the whole game as one giant if / else if chain. You could. But as states grow (menu → play → pause → game over → ...), the chain becomes impossible to read, and a transition has to be handled in one place while its setup lives in another.

A state machine fixes both problems:

  1. Each state is self-contained. All of PlayState's code, setup, update, draw, lives in one object.
  2. Transitions are centralized. setState calls onExit on the old state and onEnter on the new one, so cleanup and setup are guaranteed to run.

The game's main loop becomes a beautiful four lines:

function loop() {
  const dt = timer.tick();
  sm.update(dt, input);   // forwards to the current state
  sm.draw(ctx);
  requestAnimationFrame(loop);
}

We build that loop in the Advanced tier. For now, the idea to carry forward: a program's behavior is "which state are we in."

Examples

State is just "which one are we in?"

Think of a TV remote. Pressing one button does something different than another, because the remote is in a different state. A game is the same. Model it with one variable:

let state = "menu";   // the game is currently on the menu

That single string drives every other decision. Here it becomes the heart of the game:

if (state === "menu") {
  // show title, wait for Enter
} else if (state === "playing") {
  // run physics, spawn world, count score
} else if (state === "gameOver") {
  // show final score, wait for Enter to restart
}

This is the exact same structure as the grade example in the Conditionals group, but now it decides what whole behavior runs.

State changes the meaning of input

The same input means different things depending on the state. Here's the idea as real code:

if (state === "menu") {
  if (enterPressed) state = "playing";   // Enter STARTS the game
} else if (state === "playing") {
  if (spacePressed) player.jump();        // Space makes the player JUMP
} else if (state === "gameOver") {
  if (enterPressed) startGame();          // Enter RESTARTS the game
}

Notice the double meaning of Enter: on the menu it starts, on game over it restarts. They look identical but do different things. That's the whole point of state, the game isn't reacting to raw keys, it's reacting to "the state I'm in, and this key."

A real state machine

The game's state switching is a **finite state machine (FSM)*: a clean, named structure with a fixed set of states and rules for moving between them. Here's the real StateMachine from app1/js/state/StateMachine.js:

export class StateMachine {
  constructor() {
    this.states = new Map();   // holds every state, keyed by name
    this.current = null;       // which state is running now
    this.currentName = null;
  }

  add(name, state) {
    this.states.set(name, state);
    return this;
  }

  setState(name) {
    if (this.currentName === name) return;   // already here -> nothing
    const prev = this.current;
    const next = this.states.get(name);
    if (prev && prev.onExit) prev.onExit();   // tidy up the old state
    this.current = next;
    this.currentName = name;
    if (next.onEnter) next.onEnter();         // set up the new state
  }
}

That onEnter / onExit pattern is elegant: each state owns its own setup and cleanup. Switch states and the right code runs automatically.

Words this lesson uses

Each one is defined here on this site. Open the entry for the full definition and the other words it points at.

← Data Patterns (map, filter, find)
Previous Lesson
Data Patterns (map, filter, find)
Next Lesson
Drawing with Canvas
Drawing with Canvas →