| Engine | LOVE 11.x |
|---|---|
| Language | Lua |
| Pattern | State tables + push/pop stack |
| Time to run | ~3 minutes |
| Level | Intermediate |
| Output | One module (~70 lines) |
| Dependencies | None. Pure LOVE. |
The prompt
Open a fresh chat at claude.ai/new, paste, and send.
$ Build me a LOVE2D (LOVE 11.x) game state manager as a single Lua module. -- state shape - A state is a plain table that may implement: enter(...), exit(), update(dt), draw(), keypressed(key), mousepressed(x,y,button). - All callbacks are optional; the manager calls them only if present. -- manager api - A stack of states. Expose: - switch(state, ...) : exit current top, clear stack, push new (this is the common case). - push(state, ...) : keep current, push a new one on top (overlays like pause). - pop() : exit top, resume the one beneath. - switch/push call enter(...) with the extra args; the leaving state's exit() is called. -- routing - update(dt) and the input callbacks go to the TOP state only. - draw() draws the top state; add an option to also draw the state beneath when the top is flagged as an overlay (e.g. state.overlay == true), so a pause menu shows the game behind it. -- constraints - Pure Lua, no libraries. - Nil-safe: never crash if a state omits a callback. - Return the module table. -- return format - The module, plus a short comment showing main.lua forwarding love.update/draw/keypressed/mousepressed to it. - No prose before or after.
What this gets you
The backbone that keeps a LOVE2D game from turning into one monster main.lua. Each screen is its own tidy table, transitions are one call, and pause-over-gameplay works because the stack keeps the game alive underneath. Add a screen without touching the others.
It is nil-safe, so a minimal state that only implements draw is fine, and it routes every love callback to the right place for you.
Why it matters
The stack is what makes pause work
A single current-state variable cannot show a pause menu over a frozen game, the game is gone the moment you switch. A stack keeps gameplay on the stack, paused, while the menu sits on top, and pop() returns you exactly where you were.
States as tables keep screens isolated
Each screen owning its own enter/update/draw means no cross-talk and no giant conditional in love.update. Adding game-over is a new file, not a new branch in every callback.
Nil-safe routing
Not every state needs mousepressed. Calling callbacks only if they exist means minimal states stay minimal and nothing crashes because a screen did not implement everything.
What good output looks like
- push and pop both exist and call enter/exit. push keeps the stack and adds on top; pop removes the top and resumes beneath. Both should fire the right lifecycle callbacks. If only switch exists, overlays are impossible.
- Callbacks are called only if present. Look for an
if top.update then top.update(dt) endstyle guard, not a bare call that assumes every state implements everything. - Input goes to the top state only. keypressed/mousepressed route to the top of the stack, not to every state.
- Overlay draw is optional and opt-in. Drawing the state beneath should happen only when the top is flagged as an overlay, otherwise every push double-draws.
How to wire it up
- Save the output as
state.lua. - In
main.lua,local State = require 'state'and callState.switch(menu)inlove.load. - Forward the callbacks:
function love.update(dt) State.update(dt) end, and the same for draw, keypressed, mousepressed. - Write each screen as a table with the callbacks it needs (menu, game, gameover).
- For pause, set
pause.overlay = trueand callState.push(pause)from the game, thenState.pop()to resume.
Common gotchas
Pause menu shows a black screen behind it
The manager is not drawing the state beneath the overlay. Flag the pause state as an overlay and have draw render the one below it first.
Crash when a state omits a callback
Callbacks are being called unconditionally. Guard each with a presence check so optional callbacks stay optional.
Old state keeps updating after a switch
switch is not clearing the stack or not calling exit. Ensure switch tears down the current top and routes update only to the new top.
Where to go next
- Transitions. Add a fade between switches by pushing a transition state that draws over both.
- Shared data. Pass args through switch/push (enter receives them) to hand the score to the game-over screen.
- Resize/quit. Route more love callbacks (resize, quit) through the same top-state pattern.
Use it however you want
The prompt is CC0, paste it, modify it, ship a game with it. The output Claude gives you is yours. A link back to pixeldex.dev in your project's README helps the next solo dev find this stuff.