Clean game state engine using closures

This trick allows you to separate the different states in your game so that their individual state variables do not bleed into each other. Most games will still have global variables, but those side effects are predictable and expected. Separating things into closures allows you to encapsulate state with behavior.

It's fairly simple; all you need is a single global variable and some glue in the PICO-8 API:

function my_game_state()
  local msg = "hello, world!"
  return {
    update = function(self)
      if btnp(5) then
        msg = "goodbye, world!"
      end
      return self
    end,
    draw = function()
      cls()
      print(msg)
    end
  }
end
 
function _init()
  state = my_game_state()
end
 
function _update()
  -- this will update itself over time
  state = state:update()
end
 
function _draw()
  state:draw()
end

With the above, you'll get a self-contained function that has *everything* you need relating to that game state, in one place. A gotcha to watch out for is the update member you're returning needs to accept a 'self' argument and MUST return either itself or the result of another game state function.

Switching states

Switching between your game states is as simple as returning another function's result. So if we added a title_screen game state, but wanted to get there from another state, we'd do something like:

-- let's pretend there's a cool title_screen function defined here
 
function my_game_state()
  return {
    update = function(self)
      if btnp(6) then
        -- here's the 'magic'
        return title_screen()
      else
        return self
      end
    end,
    draw = function()
      -- draw stuff
    end
  }
end

To get a better idea of how it's employed, check out ZLG's closure game-state demo.