Store and Retrieve the Game State

Learn how to keep track of the game state.

We'll cover the following

We want to store the full state for each game in the :game_state table whenever it changes. We register each game process by the name of the first player, so the name makes a great key to store the state under.

Changes in the Game module

For game processes, some states will change whenever there is a successful reply, either in the game itself or in the state machine data. Luckily, we wrote a single function in the Game module to handle successful replies— reply_success/2. A call to :ets.insert/2 with the name :player1 as the key and the full game state as the value is all we need:

defp reply_success(state_data, reply) do
  :ets.insert(:game_state, {state_data.player1.name, state_data})
  {:reply, reply, state_data, @timeout}
end

Any time we start or restart a process, GenServer triggers the init/1 callback. That makes init/1 a good place to check the :game_state table for any state stored under the first player’s name.

If :ets.lookup/2 returns an empty list, we generate fresh state the way init/1 had done before. If :ets.lookup/2 returns some state, we use that instead.

We add a new private function that returns the state of a new game given the first player’s name:

defp fresh_state(name) do
  player1 = %{name: name, board: Board.new(), guesses: Guesses.new()}
  player2 = %{name: nil, board: Board.new(), guesses: Guesses.new()}
  %{player1: player1, player2: player2, rules: %Rules{}}
end

Then, we can use that new function in init/1:

def init(name) do
  state_data =
  case :ets.lookup(:game_state, name) do
    [] -> fresh_state(name)
    [{_key, state}] -> state
  end
  :ets.insert(:game_state, {name, state_data})
  {:ok, state_data, @timeout}
end

Get hands-on with 1200+ tech skills courses.