For the last few days, I’ve spent my spare development time tinkering with the game UI layout to prepare for adding the events log element. There are a few elements that were originally set with absolute positioning which was always a temporary solution until a more obvious design emerges as more UI elements are added. I update the existing elements to be more responsive in a grid layout while retaining the aspect ratio of the game viewport.
When players move on the hex grid, events are created in the game state’s events log and each player has their own list of the events they can see. I next want to add a UI element to show these events as text in the game view. Before adding the new element, I need to reorganize the existing layout to accommodate this feature.
Styling the LiveView element One item on my backlog is to fix the height of the game live view page so it doesn’t scroll.
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.
Here is the last test I need to make pass for the scenario of a player moving from an occupied hex to another occupied hex.
defmodule Minotaur.GameEngine.Session.EndRoundPlayerMoveTest do # … describe "player moves from occupied hex to occupied hex" do setup [:new_game] setup %{game: game, p1: p1} do {:ok, game} = Session.register_player_move(game, p1.user_id, %Vector{q: 1, r: 0}) {:ok, game} = Session.end_round(game, nil) [game: game] end # … test "only player in destination hex and moved player can see PCEnteredHexEvent", ctx do %{game: game, p1: p1, p2: p2, p3: p3, p4: p4} = ctx %{id: p1_id} = p1 events = game.
Creating PCLeftHexEvent events I last left the tests failing for the event creation behavior I want to build. Before making these tests pass, I do some refactoring to remove some duplicate logic for calculating the origin and destination hex coordinates from the move actions. This logic is handled in the apply_move/2 function to update the world state and repeated in the function that creates move events. I move the event creation logic into apply_move and have it return a tuple of the updated world and the associated PCEnteredHexEvent event which is created for every PC move action.
Upgrading game versions automatically Now that I have created the means to upgrade an old game state version to the latest version, I will implement this behavior during process initialization whenever an existing game session is resumed from a stored state. I create a new test case for this desired behavior and update the game session server code until the test passes.
defmodule Minotaur.GameEngine.ResumeActiveSessionsTest do # … describe "a game session is saved with an old version of game state" do setup [:create_game, :save_old_game_version_session] test "game is resumed with upgraded state version", ctx do :ok = GameEngine.
Testing game state encoding/decoding The latest test is now passing, but some others are failing due to encoding issues with the game state. I need to implement the encoding logic for the new EventsLog struct to resolve these failures. Breaking encoding changes will likely be a common occurrence as I build out more detailed game features so I will create an new test module focused on encoding and decoding game state from the database.
The next in-game feature I want to build is a text log that shows a player all resolved events to which they have visibility. This will involve keeping a list of events on the backend and adding a new UI element to the frontend to display events as text.
I’ll need to think about how events will be tracked for each game in a way that can be filtered by each player that has visibility to the event.
Applying my newfound knowledge A few days ago, I left a test case in a failing state which made it very clear where I need to jump in next for this project. The test case is checking that the game session process is no longer alive once the game reaches a game over state. After a deep dive yesterday into how exit signals work for supervised GenServer processes, I decide to simply have the game session process tell its supervisor to shut the process down.
Side Quest: Docker signals not handled When a game session reaches a game over condition, I need to shutdown game session processes so they aren’t restarted by the Dynamic Supervisor that spawned them. I experiment with the restart strategies for child processes to see how the Docker termination signal is handled during a rolling deploy. I notice that the exit signals are not being properly handled in the game session processes despite explicitly trapping exits and defining the termination callback.