Tic-tac-toe in Haskell
Tags: tictactoe, haskell
Published: 2019-06-20 22:10:12

---
title: Tic-tac-toe in Haskell
published: true
description: 
tags: tictactoe, haskell
---

# Introduction

In this post I will present my Haskell implementation of the classic [Tic-tac-toe](https://en.wikipedia.org/wiki/Tic-tac-toe) game.

I call it [xo-haskell](https://github.com/dwayne/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](https://github.com/dwayne/xo-haskell/tree/139ce51c2a11c55e40bd059468d142edf7f07a1d/src/XO) and an [executable](https://github.com/dwayne/xo-haskell/tree/139ce51c2a11c55e40bd059468d142edf7f07a1d/cli).

## The library

The star of the library is the [Game](https://github.com/dwayne/xo-haskell/blob/139ce51c2a11c55e40bd059468d142edf7f07a1d/src/XO/Game.hs) 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:

```haskell
>>> import XO.Mark
>>> import XO.Game as Game

>>> let game0 = Game.new O
```

`game0` is a [Game](https://github.com/dwayne/xo-haskell/blob/139ce51c2a11c55e40bd059468d142edf7f07a1d/src/XO/Game.hs#L80) where O plays first.

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

```haskell
>>> let Right game1 = Game.play (0,0) game0
>>> game1
o........; x
```

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.

```haskell
>>> let Left error = Game.play (3,3) game1
>>> error
OutOfBounds
```

That's enforced [here](https://github.com/dwayne/xo-haskell/blob/139ce51c2a11c55e40bd059468d142edf7f07a1d/src/XO/Game.hs#L130).


```haskell
>>> let Left error = Game.play (0,0) game1
>>> error
Unavailable
```

And that's enforced [here](https://github.com/dwayne/xo-haskell/blob/139ce51c2a11c55e40bd059468d142edf7f07a1d/src/XO/Game.hs#L128).

The game over conditions are checked by the [Referee](https://github.com/dwayne/xo-haskell/blob/139ce51c2a11c55e40bd059468d142edf7f07a1d/src/XO/Referee.hs) module. It exports a partial function called [unsafeDecide](https://github.com/dwayne/xo-haskell/blob/139ce51c2a11c55e40bd059468d142edf7f07a1d/src/XO/Referee.hs#L40) that can determine when a player wins or when a game is squashed.

There is an [AI](https://github.com/dwayne/xo-haskell/blob/139ce51c2a11c55e40bd059468d142edf7f07a1d/src/XO/AI.hs) module that implements a traditional [minimax algorithm](https://en.wikipedia.org/wiki/Minimax#Minimax_algorithm_with_alternate_moves). The best way to see how it works is to read its [tests](https://github.com/dwayne/xo-haskell/blob/139ce51c2a11c55e40bd059468d142edf7f07a1d/tests/Test/XO/AI.hs).

Finally, the library is exhaustively unit tested using [hspec](http://hspec.github.io/). Check the tests out [here](https://github.com/dwayne/xo-haskell/tree/139ce51c2a11c55e40bd059468d142edf7f07a1d/tests/Test/XO).

## The executable

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

It parses the [command-line options](https://github.com/dwayne/xo-haskell/blob/139ce51c2a11c55e40bd059468d142edf7f07a1d/cli/XO/CLI/Options.hs) using [optparse-applicative](https://hackage.haskell.org/package/optparse-applicative) and then decides which version of the [orchestrator](https://github.com/dwayne/xo-haskell/blob/139ce51c2a11c55e40bd059468d142edf7f07a1d/cli/XO/CLI/Orchestrator.hs) to run.

The [non-interactive orchestrator](https://github.com/dwayne/xo-haskell/blob/139ce51c2a11c55e40bd059468d142edf7f07a1d/cli/XO/CLI/Orchestrator/Noninteractive.hs) manages "computer vs computer" games. The [interactive orchestrator](https://github.com/dwayne/xo-haskell/blob/139ce51c2a11c55e40bd059468d142edf7f07a1d/cli/XO/CLI/Orchestrator/Interactive.hs) 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](http://learnyouahaskell.com/) or [Haskell Book](http://haskellbook.com/) 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](https://codereview.stackexchange.com/search?q=tictactoe). I hope this post and the source code for [xo-haskell](https://github.com/dwayne/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.