Interlude: Tic Tac Toe
At this point if you have been reading sequentially through these chapters you know enough to put together a simple tic-tac-toe game.
The Game Board
The board is represented by an array of 9 cells. Cell 0 is the top left square, and cell 8 is the bottom right.
Each cell will contain nil, an :X
or an :O
.
Displaying the Board
The DisplayBoard
component displays a board. DisplayBoard
accepts a board
param, and will fire back a clicked_at
event when the user clicks one of the squares.
A small helper function draw_squares
draws an individual square which is displayed as a BUTTON
. A click handler is attached which will fire the clicked_at
event with the appropriate cell id.
Notice that DisplayBoard
has no internal state of its own. That is handled by the DisplayGame
component.
The Game State
The DisplayGame
component has two state variables:
@history
which is an array of boards, each board being the array of cells.@step
which is the current step in the history (we begin at zero)
@step
and @history
allows the player to move backwards or forwards and replay parts of the game.
These are initialized in the before_mount
callback. Because Ruby will adjust the array size as needed and return nil if an array value is not initialized, we can simply initialize the board to an empty array.
There are three reader methods that read the state:
player
returns the current player's token. The first player is always:X
so even stepsare
:X
, and odd steps are:O
.current
returns the board at the current step.history
uses state_reader to encapsulate the history state.
Encapsulated access to state in reader methods like this is not necessary but is good practice
Calculating the Winner Based on the Game State
We also have a current_winner?
method that will return the winning player or nil based on the value of the current board:
Mutating the Game State
There are two mutator methods that change state:
handle_click!
is called with the id of the square when a user clicks on a square.jump_to!
moves the user back and forth through the history.
The handle_click!
mutator first checks to make sure that no one has already won at the current step, and that no one has played in the cell that the user clicked on. If either of these conditions is true handle_click!
returns, no mutation is signaled and nothing changes.
If we had wanted to return AND signal a state mutation we would use the Ruby
next
keyword instead ofreturn
.s
To update the board handle_click!
duplicates the squares; adds the player's token to the cell; makes a new history with the new squares on the end, and finally updates the value of @step
.
We like to use the convention where practical of ending mutator methods with a bang (!) so that readers of the code are aware that these will change state.
The Game Display
Now we have a couple of helper methods to build parts of the game display.
moves
creates the list items that allow the user to move back and forth through the history.status
provides the play state
And finally our render method which displays the Board and the game info:
&method
turns an instance method into a Proc rather than having to say{ |id| handle_click(id) }
Summary
This small game uses everything covered in the previous sections: HTML Tags, Component Classes, Params, Events and Callbacks, and State. The project was derived from this React tutorial: https://reactjs.org/tutorial/tutorial.html. You may want to compare our Ruby code with the React original.
The following sections cover reference materials, and some advanced information. You may want to skip to the HyperState section which will use this example to show how state can be encapsulated, extracted and shared resulting in easier to understand and maintain code.
Last updated