Game State Version 3
When I attempt to run the application in the dev
environment, an existing game session stored in my local dev database is crashing after it resumes and attempts to process move actions.
After some debugging, I find that this is due to the events log being in a bad state caused by the recent change of adding the :events_visible_by_player
field.
I never added a new test case for upgrading the game state version after making this change and now I’m paying for it.
The map value of :events_visible_by_player
is expected to always be populated with a list for each player id in the game.
When a new game state is initialized, these lists are created automatically.
However, when upgrading from version 1 game state which does not have an events log, the game state is updated with EventsLog.new()
which sets an empty map for :events_visible_by_player
.
defmodule Minotaur.GameEngine.Game do
# …
def upgrade_latest_version(%{version: 1} = game) do
v2 = %{game | events_log: EventsLog.new(), version: 2}
upgrade_latest_version(v2)
end
end
To fix this issue, I’ll need to define a new upgrade path to the latest version which will be bumped to 3.
I start by writing a new test case for the desired behavior for the scenario of calling upgrade_latest_version
with a version 2 game state.
defmodule Minotaur.GameEngine.GameTest do
# …
describe "upgrade_latest_version/1 from version 2" do
setup [:create_game, :rollback_v3]
test "returns latest version state", %{game: game} do
game = Game.upgrade_latest_version(game)
assert Game.latest_version() == game.version
assert %EventsLog{events_visible_by_player: player_events} = game.events_log
Enum.each(game.players, fn {player_id, _} ->
assert [] == player_events[player_id]
end)
end
end
# v3 sets default player events as empty lists
def rollback_v3(%{game: game}) do
game =
game
|> put_in([:events_log, :events_visible_by_player], %{})
|> Map.put(:version, 2)
[game: game]
end
defp create_game(_ctx) do
[game: game_fixture()]
end
end
I then increment the current version module attribute and add a new function definition to handle upgrading from version 2 game state.
defmodule Minotaur.GameEngine.Game do
# …
@current_version 3
# …
def upgrade_latest_version(%{version: @current_version} = game) do
game
end
def upgrade_latest_version(%{version: 2} = game) do
events_log = EventsLog.new(events: game.events_log.events, players: game.players)
v3 =
game
|> Map.put(:events_log, events_log)
|> Map.put(:version, 3)
upgrade_latest_version(v3)
end
def upgrade_latest_version(%{version: 1} = game) do
v2 = %{game | events_log: EventsLog.new(), version: 2}
upgrade_latest_version(v2)
end
def upgrade_latest_version(_) do
{:error, :invalid_game_version}
end
end
The test passes so I launch the application again in the dev environment. The game session resumes and is able to process moves correctly after the game state is automatically upgraded.
The next feature I’ll focus on is to render the events log for each player in their game UI.