DEV Community

Dwayne Crooks
Dwayne Crooks

Posted on

Tic-tac-toe in Haskell

Introduction

In this post I will present my Haskell implementation of the classic Tic-tac-toe game.

I call it xo-haskell.

I won't go too much into the details but instead I'll try to give a 10,000 foot view of the game's overall design.

10,000 foot view

The game is decomposed into a library and an executable.

The library

The star of the library is the Game module. It encapsulates and enforces Tic-tac-toe's game logic.

For e.g. Let's say we want to start a game where O plays first. We can do the following:

>>> import XO.Mark
>>> import XO.Game as Game

>>> let game0 = Game.new O
Enter fullscreen mode Exit fullscreen mode

game0 is a Game where O plays first.

To actually play you call the play function with the position you want to mark.

>>> let Right game1 = Game.play (0,0) game0
>>> game1
o........; x
Enter fullscreen mode Exit fullscreen mode

You don't pass X or O to the function since whose turn it is is managed behind the scenes. Doing it that way ensures that play always alternates between X and O.

You can't play out of bounds or in a marked position.

>>> let Left error = Game.play (3,3) game1
>>> error
OutOfBounds
Enter fullscreen mode Exit fullscreen mode

That's enforced here.

>>> let Left error = Game.play (0,0) game1
>>> error
Unavailable
Enter fullscreen mode Exit fullscreen mode

And that's enforced here.

The game over conditions are checked by the Referee module. It exports a partial function called unsafeDecide that can determine when a player wins or when a game is squashed.

There is an AI module that implements a traditional minimax algorithm. The best way to see how it works is to read its tests.

Finally, the library is exhaustively unit tested using hspec. Check the tests out here.

The executable

All interactions with the outside world, IO, happens in the executable.

It parses the command-line options using optparse-applicative and then decides which version of the orchestrator to run.

The non-interactive orchestrator manages "computer vs computer" games. The interactive orchestrator manages "human vs computer" or "human vs human" games.

The key thing to notice is how straightforward the implementation of each orchestrator becomes when the game logic is handled as a separate concern.

Why Read The Code

  1. If you've read LYAH or Haskell Book but you're still having problems designing and implementing complete Haskell applications.
  2. To learn how to unit test Haskell code.
  3. To learn how to document Haskell code.
  4. To learn how to design a library for a turn-based game.
  5. To learn how to parse command-line options.

Conclusion

Programming a well designed Tic-tac-toe game in any language (much less Haskell) seems to be a non-trivial problem. I hope this post and the source code for xo-haskell shows you a way that it may be done.

What do you think about the design and the implementation? I'd love to get your thoughts on how it could be improved.

Top comments (0)