Building An Unbeatable Tic-Tac-Toe Game In Ruby Pt. 2

February 23, 2015

This is the second part of a short series describing how I designed and built a unbeatable game of Tic-Tac-Toe using Ruby. Check out part 1 here.

Getting the Game Going

Last time we left off with a working game board able to display itself as well as any ‘players’ that occupy any of it’s spaces. Now it’s time to put that board into action and build something that resembles an actual game.

In order to figure out how our game ‘engine’ should work, lets first come up with an algorithm describing the normal flow of a Tic-Tac-Toe game.

Tic-Tac-Toe Gameplay Algorithm:

Player ‘X’ goes first

  1. Player ‘X’ places their marker on an empty board piece
  2. Next, check to see if player ‘X’ won by getting three ‘X’s in a row
  3. If not, check to see if there is a tie (a full board with no three of a kind in a row)
  4. If neither of these conditions are true, repeat these steps with player ‘O’ instead of player ‘X’

Because our brains are so good at noticing patterns, we normally don’t explicitly perform steps 2 & 3 after each turn. Instead, we can usually determine before we even make our move whether or not it’s possible for us to win, lose or tie (more on this in the next post).

Now that we have an algorithm, lets turn that into ‘psuedocode’.

player, opponent = 'X', 'O' # Player 'X' goes first

while true # Repeat until game is over
  turn player # Signify it's the current player's turn

  player.move board # Have that player make their move on the board

  display_board board

  quit && alert player.won if winner? # Quit and alert if the player just won
  quit && alert tie if tie? # Else, if it's a tie, end the game and alert tie

  player, opponent = opponent, player # Otherwise, swap the players and repeat
end

Now here is the cool part: If you check out the actual code on Github, you’ll notice that this ‘psuedocode’ is almost exactly the same as the actual Ruby code!

Delegators, Mount Up

Looking back at the code, you’ll notice that there are a lot of calls to a UI object such as ‘turn’, ‘won’, ‘tie’, ‘thinking’, ‘banner’, etc. in order to print and receive information from the user. Initially, I inlined these methods right there in the game engine. This worked, however it made the code look very cluttered and also seemed to violate the separation of concerns principle to have the game engine also deal with UI. For that reason, I extracted everything having to deal with input or output into a UI class, which is where the above methods live.

Well, that last part is only half true. If you look at the UI code, you’ll notice that UI does not actually implement any logic in these methods. Instead, it passes every call to a ‘delegate’ object along with the calling parameters. The reason I took this route is that the UI is really an ‘abstract’ concept since there are multiple ways that the game could communicate with a user.

For example: Lets say that after seeing our console game, our boss asked us to make a Tic-Tac-Toe Service for our enterprise business clients to enjoy. The only thing that would need to change for this implementation would be the ‘user interface’. Instead of printing and receiving input directly to and from the console, our service might communicate via HTTP using JSON as our messaging format. All we would need to do would be to create a HTTP delegate that could accept and respond with JSON and wire up our UI class to use this new delegate. None of the underlying calling code would have to change.

Another benefit of having all UI methods in a single class or set of classes is that it helps keep our dependencies isolated to only those classes in which they are actually required. For example, in the IO::Console class, I make use of the awesome ‘colorize’ gem which adds the ability to ‘colorize’ console output using ANSI escape sequences. This makes our game a little more exciting and also easier to read on the normally ‘black and white’ console.

Here’s what the final result looks like:

TicTac console

That’s about it for this post. Next time I’ll go over implementing the actual players, both human and ‘AI’.

As always, any feedback in the comments or on Twitter is greatly appreciated!

Update: Part 3 is available here

Did you find this content helpful?


Let me send you more stuff like this! Unsubscribe at any time. No spam ever. Period.


Subscribe to MarkPhelps.me

* indicates required

Discussion, links, and tweets

Mark Phelps

I'm a Software Engineer in Durham, NC. I mostly write about Go, Ruby, and some Java from time to time.