Tic Tac Toe in Haskell
11 Jan 2013Introduction
Still trying to flex my skills in the Haskell arena and am still chasing a real project to do. Until I find said project, silly little games it is. A little while ago I watched the Haskell Live videos and in these videos a good start is made on a chess framework. Very interesting stuff to see how something like this is put together and if you haven’t seen them, I suggest you go and check out episode 1 and episode 2. Anyway, the videos don’t yet take you through to a completed implementation and while I’d love to say that I possess the skills to take the chess game through, I’d be punching above my weight. In compromise, I’ve been able to use some of the concepts that Rein has demonstrated in his videos - in my own implementation of Tic Tac Toe. Today’s post will take you through my implementation.
Functional Specification
Well, if you’re really wanting the functional in’s-and-out’s of the game Tic-tac-toe, I suggest you step our of your bubble and read this. This implementation will involve two rather random computer players battling it out, so no user interactivity (yet). Well, thanks to a cheeky link to a wikipedia article, that’s the smallest functional specification I’ve ever seen. On to the code.
Pieces
First up, we’ll deal with pieces. To this program, a piece can be a Naught or a Cross. When the piece is a Naught, we want to represent it with a ‘o’, when the piece is a Cross, we want to represent it with an ‘x’. Here’s the type declaration and functions we’ll use to work with pieces.
A piece can only be either a Naught or a Cross, nothing else. The functions are all very straight forward and meeting the requirements of our specification. Naughts will look like “o’s” and Crosses will look like “x’s”. Just to be sure, we can setup a couple of QuickCheck
tests to assure us that it’s ok.
We’re ready to move on.
Cells
A cell is a representation of one of the nine squares that you find on a tic-tac-toe board. A cell differs from a piece as a cell can have a piece value or it can be empty. In our implementation, empty cells will be represented by a period .
. Let’s take a look at the types and the functions we’ll use to interact with cells.
We use Maybe
to allow either a Nothing
or a Piece
value for a cell. With the help of those functions we’ve already written for pieces, you can see that these cell functions are very simple. We’ve only had to cater for one extra case, the case of when there’s no value in a cell. We’ll setup a few more QuickCheck tests just to make sure that our Cell code is working ok.
Great. With this code written we’re ready to wrap up a List of these cells into a field that we will play on.
The Field
A 3x3 grid is normally what’s drawn prior to playing a game of tic-tac-toe. We will define the field as an array of an array of cells. Here’s the type and supporting functions for a field.
Much as before, you can see that we’re leaning on our already-built set of functions to implement all of our field-based functions. At this point, we can define the starting field that all of our games will originate with.
You can see that the field is in string format. It’s a little more human to read and it’s all ready for our readField
function to use as is demonstrated by the following QuickCheck
test.
It’s at this point where any similarities between the Haskell Live videos and my implementation end. It’s probably natural that this is the point as we’re going to start dealing with some game logic.
Winners and Losers
There are a few tests that we have to be constantly running in order to simulate a real-life game of tic-tac-toe. One of which is “do we have a winner yet? Testing if a winner has been established is testing if the same symbol appears on a horizontal, vertical or diagonal line. In each of these configurations, there are only going to be three cells to test, so I made a general purpose array tester. I’ve use pattern matching in this function to be as clear as possible. It’s not as DRY as it could be, but we can improve it later - let’s just get it working. Here’s the function to test three list elements to see if there’s a winner.
Easy. This function looks like it will be very effective in testing winners on the horizontal lines. But what about the vertical lines that occur in different arrays? and the diagonal lines that exist in different arrays and in different indexes across the horizontal axis? My answer here is to create a function that make all of these testable vectors into horizontal rows so that we can use the above function on all of the different win configurations. Here’s the code to build an array of these 3 element arrays.
Walking through this one piece-by-piece, we can see a number of items in the where clause. Sure, these could be consolidated into a big-fat-one-liner, but I wanted to make clear exactly what was being concatenated. So, horiz
is a list comprehension giving you each horizontal line, col1
, col2
and col3
are the vertical columns. diag
is a little less clearer through its use of head and drop, but it does form the diagonals into testable rows.
Playing the game
Weather you’re a computer or human player, you’ll need to know where are the valid spots to move. It’ll be intuitive enough from the user interface presented for a human to realise where their next move will be, but our computer player counterparts will need to be told explicitly where they can move. This next function just finds all of the available cells that you can move into.
We assign an index (from 0 to 8) to each cell and only return the indexes that are paired with a Nothing
value. This function will help us later when we go to build a user interface for this application. We’ll be able to prompt the user intuitively.
Flattening the array gives us the [0..8] range, which over the 3x3 grid (which is really our tic-tac-toe representation), the range looks like this.
This is timely information as we’re just about to present the function that will place a piece on the field for us. Placing a piece will require us to provide the current game field, the piece we want to apply and the index that we want to apply it at. This function cuts the list on the index that we want to apply and then re-concatenates the broken list around the new piece (obviously dropping the cell that we’re putting a piece into). Here’s the code.
Trying to be as clear as possible with my names here. prior
being the list before the break, after
is what comes after. Now it’s time to introduce our “artificial intelligence” which really - there isn’t any intelligence here. It’s based purely off of a random number generator. Here’s how our computer players will play a round of tic-tac-toe.
We’re taking in a random number generator, the piece type that the computer will place and the field that it will be placed in. The output to which will be a new random number generator and the changed field. This way, we can repeatedly call this function and not worry about losing our sense of entropy (or field state!). Finally we have the function that will play our game until we have a result (win naught, win cross or tie). I needed to implement a “not” function for my piece type. This is just so I can offer each “player” a go interchangeably. Here’s the code.
This reads fairly humanly. We assume that if execution breaks into this function, a result wasn’t established on the execution prior, so we’re comfortable in the first check that if there are no spaces left to put pieces, we must declare the game as a tie. The next test is if the previous call to this function caused the other player to win the game. In the event that this check is confirmed, a message is written out and we do not continue any further. If there still isn’t a winner, we get this player to place a piece into a cell and then recurse on this function as the other player. Here is how a game runs when using 1 into mkStdGen
.
That’s tic-tac-toe.
Next time I’ll add some user interactivity so that the computer doesn’t have all the fun!