Recently, I took up a programming challenge to build an unbeatable game of Tic-Tac-Toe using Ruby.
At first glance this seemed like it would be relatively simple. I mean, I learned how to play Tic-Tac-Toe in grade school, so how hard could it be to teach a computer to do the same? Much harder as it turns out.
Through a short series of posts, I’m going to describe my thought process while solving this problem and also how I implemented the solution. If you’d like to see the finished code, it’s available on my Github.
Understanding The Problem
Before trying to solve any problem, technical or otherwise, you first need to understand what is being asked of you and what the solution ‘looks like’.
The requirements for the Tic-Tac-Toe problem were the following:
- The game should be implemented in pure Ruby
- The game should be playable via the command line or other UI means
- The opponent should be unbeatable (meaning it should always win or tie)
- The board and results should be clearly displayed
OK, so this helps clarify a few things. At the very least, I knew that I would need to implement the following:
- A board object that can be displayed and perhaps keep track of some state.
- Two players: one human that would input moves via the keyboard, one computer (AI) that would somehow always determine the best move to make.
- A game ‘engine’ that actually runs the game, keeps track of turns and can determine when the game is over (either by a win or a tie).
As you can probably guess, these highlighted nouns became my Ruby classes.
Something To Look At
After breaking down the problem into a few smaller problems as described above, I got to work.
I find that when working on a new project, I can easily begin to feel overwhelmed with how much I have left to do. The best way that I’ve found to combat this is to get something ‘presentable’ as soon as possible. The sooner I can actually see something on the screen, the more motivated I become.
For my Tic-Tac-Toe game, the obvious first step was to be able to print the game board, so I started with the ‘Board’ class.
Since a Tic-Tac-Toe game board is a 3x3 grid, my first instinct was to create 2 arrays, one for the rows and one for the columns. After a little tinkering, I realized that while this models the way that we think about a game board, it didn’t translate very naturally to code and that there was actually a simpler way.
Create a single empty Array of size 9.
The next step was to be able to get the board to print to the screen and actually look like a real Tic-Tac-Toe board.
Again, after some trial and error, I came to this solution:
This ‘overrides’ Board’s to_string method and does the following:
- Creates an empty string
- Iterates through each position from 0 to 8 (since array’s are 0-indexed)
- For each ‘position’:
- If the space is occupied, it appends the contents of that space (the player’s piece)
- Otherwise, it just appends the position number itself
- If the position is in the first or second column, then it appends a vertical line representing a column
- Otherwise, if the position is the last in it’s row, it appends the row separator (unless it is the final row)
- Finally, returns the built string
If you ran this code by creating a new Board object and called to_s on it (which ‘puts’ will do implicitly), you would get the following:
Success! We’ve got something on the screen that actually looks like a Tic-Tac-Toe board.
Next time we’ll implement the Human player and the Game engine. Thanks for reading and let me know what you think in the comments!
Update: Part 2 is available here