Skip to content

Instantly share code, notes, and snippets.

@chirlo
Last active December 23, 2015 01:59
Show Gist options
  • Save chirlo/6564276 to your computer and use it in GitHub Desktop.
Save chirlo/6564276 to your computer and use it in GitHub Desktop.
TicTacToe with Either
sealed trait Result
case object X extends Result
case object O extends Result
case object Tie extends Result
object Game {
type Player = (Result, Set[Int])
private val emptySet: Set[Int] = Set()
private val availableSquares = (1 to 9).toSet
private val newGame = Game((X -> emptySet, O -> emptySet), availableSquares)
private val winningCombinations =
Seq(
(1, 2, 3), (4, 5, 6), (7, 8, 9), //rows
(1, 4, 7), (2, 5, 8), (3, 6, 9), //columns
(1, 5, 9), (3, 5, 7) //diagonals
).map { case (x, y, z) => Set(x, y, z) }
def apply(moves: Int*): Either[Game, Result] = {
def rec(e: Game, moves: List[Int]): Either[Game, Result] = moves match {
case Nil => Left(e)
case x :: xs => e mark (x) fold (rec(_, xs), Right(_))
}
rec(newGame, moves.toList)
}
//Entry point: just call Game.start(). The REPL doesn't echo back the input you give however
def start(g: Game = newGame) {
print(s"${g.players._2._1}'s turn, choose a square:")
val square = readInt
g mark (square) fold (start, printResult)
}
private def printResult(r: Result) {
val message = r match {
case Tie => "We have a tie"
case p => s"$p wins"
}
print(message)
}
}
import Game._
case class Game(players: (Player, Player), freeSquares: Set[Int]) {
def mark(i: Int): Either[Game, Result] = {
val ((p1Sign, p1Squares), p2) = players swap //swap to alternate players on each round
val updatedFree = freeSquares - i
if (updatedFree isEmpty)
Right(Tie)
else {
val updatedP1Squares = p1Squares + i
val hasP1Won = winningCombinations exists (_ subsetOf (updatedP1Squares))
if (hasP1Won)
Right(p1Sign)
else
Left(Game(((p1Sign, updatedP1Squares), p2), updatedFree))
}
}
}
import org.scalatest.FunSuite
class GameTest extends FunSuite {
test(s"a game should not be over before 4 moves") {
for {
moves <- (1 to 9).toList.combinations(4).toList
} assert(Game(moves: _*).isLeft)
}
val cases = List(O -> Seq(1, 4, 2, 5, 3), X -> Seq(1, 4, 2, 5, 9, 6))
for ((sign, seq) <- cases) {
test(s"$sign wins when he puts 3 in a row first") {
val result = Game(seq: _*)
assert(result.right.get == sign)
}
}
test("Tie when all squares are full and nobody has 3 in a row") {
val result = Game(1, 4, 7, 5, 2, 8, 6, 3, 9)
assert(result.right.get == Tie)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment