Will It Game?: Rails Forms

This past week, we’ve been learning to make Rails applications using forms at Flatiron School. It didn’t take long for me to grow sick of looking at forms after forms and I wanted to see if I could make it more fun. So to reinforce what I’ve learned, I decided to make a simple tic-tac-toe game using Rails forms.

Models
This simple game just needed two models; Game and Turn. A game has many turns, and a turn belongs to a game. The game would keep track of all of the turns (up to 9) and the winner, if any. The turn would keep track of which mark it is (X or O) as well as where it is placed on the tic-tac-toe board.
Views
The views would follow the RESTful pattern of the actions :index for listing all games, :show for the current state/result of a game, :new for starting a new game with some parameters (single or two players), :create for creating the new game, :edit to POST the user’s move, and :update for updating the game state.
Controller
The controller (with methods defined in the game model and game helper) handles the flow to view different games and to start or resume a game. For the game, it serves the :edit view as long as the game is in progress (game loop), then the :show view for the conclusion of the game.

Less Form-like, More Game-like

Routes
I wanted to customize the route urls to mask the fact that the user is interacting with forms while still keeping it as RESTful.
# routes.rb
get ‘/games/:id/play’, to: ‘games#edit’, as: ‘game_play’
patch ‘/games/:id/play’, to: ‘games#update’, as: ‘update_game_play’

using aliases to make you forget you’re in an edit form
The routes and corresponding views are set up as follows:
‘/’ → Main Menu
‘/games/’ → List of all played games
‘/games/new’ → Form to create new game (single player or two players)
‘/games/:id/play’ → Form to update game with user move (re-renders)
‘/games/:id’ → Current state/result of game with turn history

Forms
Now, for the forms. I would have to POST the move a user makes and re-render with an updated game board, and I’ve learned to do so with submit buttons with Rails forms. So I made each of the 9 spots on the board submit buttons and hidden fields for params that would POST its board_index, and with CSS made it so that they don’t look like the generic form buttons.
Another cool feature I was able to add to the game was that since I had access to the array of turns from the game in the order they were created, I could display a “turn history" using a cumulative array of arrays representing the game board state at each turn.

Game Logic

For a simple game like tic-tac-toe, the core components of the game are the check to see if win conditions are met and the AI logic. This was done by comparing an array of all winning combos (by board index) and comparing that array to the game state array for X’s and O’s respectively. This is what the AI would use to place a mark for a winning move when there are two out of three for a winning combo, or to block a potential player’s winning combo. Otherwise, the AI will try to prioritize taking corners of the board.
# Returns an array with ‘X’ or ‘O’ and winning combo string if there is a winner
# or ‘TIE’ and an empty string if game ended with no winners
# or nil if game has not yet ended
def check_for_win(game)
return nil if game.turns.size < 5 xs = game.turns.where(mark: 'X').map {|turn| turn.board_index } os = game.turns.where(mark: 'O').map {|turn| turn.board_index } @@win_conditions.each do |win| # Check if xs or os contain indices combo that meet win condition if (win-xs).empty? return ["X", win.join(' ')] elsif (win-os).empty? return ["O",win.join(' ')] end end if game.turns.size > 8
return ["TIE",""]
else
return nil
end
end

method to check for wins
This method utilizes some really neat Ruby array operations that allows you to subtract two arrays to get just the elements that are exclusive, so the difference between an array with a winning combo (for example, [0, 1, 2] for the top row) and a game board state array that results in an empty array of exclusive elements would mean that the player has atleast indices 0, 1, 2 on the board. The string of the winning combo in the return array is used to dynamically add class names to show the winning move.
Overall, this was a fun side project that gave me an escape from the mundane world of forms using form_for and allowed me to experiment with customization of routes and to test the boundaries of the RESTful pattern.
Following the MVC pattern made working on the project a lot smoother as it made structuring the project very easy and made the roles for different parts of the application very clear, and I’ve definitely grown to appreciate this pattern.
This project is available at https://github.com/bbpak/rails-forms-tic-tac-toe

Link: https://dev.to/bouhm/will-it-game-rails-forms-35ic