Skip to content

State Machine Loop

Organise game flow with distinct states (title, playing, game over). Clean separation of game phases.

Taught in Game 1, Unit 7 state-machinegame-flowarchitecture

Overview

Instead of one monolithic game loop, use a state machine to separate different phases of your game. Each state has its own update logic, and transitions happen when conditions are met. This makes code cleaner, easier to debug, and simpler to extend.

Code

; =============================================================================
; STATE MACHINE LOOP - ZX SPECTRUM
; Game flow with distinct states
; Taught: Game 1 (Ink War), Unit 7
; CPU: Variable | Memory: ~50 bytes
; =============================================================================

; Game states
GS_TITLE    equ     0           ; Title screen
GS_PLAYING  equ     1           ; Main gameplay
GS_GAMEOVER equ     2           ; Game over screen
GS_PAUSED   equ     3           ; Paused (optional)

main_loop:
    halt                        ; Wait for frame

    ; Dispatch based on current state
    ld      a, (game_state)

    or      a                   ; State 0? (title)
    jr      z, state_title

    cp      GS_PLAYING
    jr      z, state_playing

    cp      GS_GAMEOVER
    jr      z, state_gameover

    cp      GS_PAUSED
    jr      z, state_paused

    jp      main_loop           ; Unknown state, skip

; === TITLE STATE ===
state_title:
    call    read_keyboard
    ld      a, (key_pressed)
    cp      5                   ; Space pressed?
    jr      nz, main_loop

    ; Start game
    call    init_game
    ld      a, GS_PLAYING
    ld      (game_state), a
    jp      main_loop

; === PLAYING STATE ===
state_playing:
    call    read_keyboard
    call    handle_input
    call    update_game
    call    check_game_over

    ld      a, (is_game_over)
    or      a
    jr      z, main_loop

    ; Transition to game over
    ld      a, GS_GAMEOVER
    ld      (game_state), a
    call    show_game_over
    jp      main_loop

; === GAME OVER STATE ===
state_gameover:
    call    read_keyboard
    ld      a, (key_pressed)
    cp      5                   ; Space to restart?
    jr      nz, main_loop

    ; Return to title
    ld      a, GS_TITLE
    ld      (game_state), a
    call    show_title
    jp      main_loop

; === PAUSED STATE ===
state_paused:
    call    read_keyboard
    ld      a, (key_pressed)
    cp      5                   ; Space to unpause?
    jr      nz, main_loop

    ld      a, GS_PLAYING
    ld      (game_state), a
    jp      main_loop

; Placeholder routines
init_game:      ret
update_game:    ret
check_game_over: ret
show_game_over: ret
show_title:     ret
handle_input:   ret
read_keyboard:  ret

; Variables
game_state:     defb    GS_TITLE
is_game_over:   defb    0
key_pressed:    defb    0

Trade-offs

AspectCost
CPUSmall overhead for state dispatch
Memory~50 bytes for framework
LimitationNone - this is good practice

When to use: Any game with multiple screens or phases.

When to avoid: Very simple single-screen games where it’s overkill.

State Transition Diagram

         ┌─────────────┐
         │             │
    ┌────▶   TITLE     │
    │    │             │
    │    └──────┬──────┘
    │           │ Space
    │           ▼
    │    ┌─────────────┐
    │    │             │◀────┐
    │    │   PLAYING   │     │ Space
    │    │             │─────▶ PAUSED
    │    └──────┬──────┘◀────┘
    │           │ Game Over
    │           ▼
    │    ┌─────────────┐
    │    │             │
    └────│  GAMEOVER   │
  Space  │             │
         └─────────────┘

Benefits

  1. Separation of concerns: Each state is self-contained
  2. Easy to add states: New screens just need new state code
  3. Clear transitions: State changes are explicit
  4. Debuggable: Easy to trace which state caused issues

Patterns: Game Loop (HALT), Keyboard Reading

Vault: ZX Spectrum