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
| Aspect | Cost |
|---|---|
| CPU | Small overhead for state dispatch |
| Memory | ~50 bytes for framework |
| Limitation | None - 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
- Separation of concerns: Each state is self-contained
- Easy to add states: New screens just need new state code
- Clear transitions: State changes are explicit
- Debuggable: Easy to trace which state caused issues
Related
Patterns: Game Loop (HALT), Keyboard Reading
Vault: ZX Spectrum