Skip to content

Instantly share code, notes, and snippets.

@countingtoten
Last active August 31, 2017 03:35
Show Gist options
  • Select an option

  • Save countingtoten/ce6fed843326ecf4ecf606b5ca10f040 to your computer and use it in GitHub Desktop.

Select an option

Save countingtoten/ce6fed843326ecf4ecf606b5ca10f040 to your computer and use it in GitHub Desktop.
import scala.annotation.tailrec
import scala.util.{Try, Success, Failure}
sealed trait Player
case object X extends Player {
override def toString: String = {
"X"
}
}
case object O extends Player {
override def toString: String = {
"O"
}
}
sealed trait Status
case class Winner(player: Player) extends Status
case object InProgress extends Status
case object Draw extends Status
case class Tile(loc: Int, owner: Option[Player]) {
override def toString: String = owner.map(_.toString).getOrElse(s"$loc")
}
val boardWidth = 3
val blankState = (0 until boardWidth * boardWidth).map(loc => new Tile(loc, None))
class Board(state: IndexedSeq[Tile], val currentPlayer: Player, val gameStatus: Status) {
def move(loc: Int) : Board = {
val (front, back) = state.splitAt(loc)
val newBack = if (!back.isEmpty && back.head.owner.isEmpty) {
val newTile = back.head.copy(owner = Some(currentPlayer))
newTile +: back.tail
} else {
throw new Exception(s"Invalid location $loc")
}
val newState = front ++ newBack
val newStatus = getNewStatus(newState, loc)
new Board(newState, otherPlayer, newStatus)
}
private def otherPlayer : Player = {
currentPlayer match {
case X => O
case O => X
}
}
private def getNewStatus(newState: IndexedSeq[Tile], loc: Int) : Status = {
// val (x, y) = (v % 3, v / 3)
// 0,0 | 1,0 | 2,0
// 0,1 | 1,1 | 2,1
// 0,2 | 1,2 | 2,2
val (x, y) = (loc % boardWidth, loc / boardWidth)
val countUp = (0 until boardWidth)
val countDown = (boardWidth - 1 to 0 by -1)
def rowVictory : Boolean = countUp.forall { idx =>
val owner = newState(idx + boardWidth * y).owner
owner == Some(currentPlayer)
}
def columnVictory : Boolean = countUp.forall { idx =>
val owner = newState(x + boardWidth * idx).owner
owner == Some(currentPlayer)
}
def diagonalVictory : Boolean = {
val isDiagonal = x == y || x + y == (boardWidth - 1)
lazy val upDiagonal = countUp.forall { idx =>
val owner = newState(idx + boardWidth * idx).owner
owner == Some(currentPlayer)
}
lazy val downDiagonal = countUp.zip(countDown).forall { case (idx, idy) =>
val owner = newState(idx + boardWidth * idy).owner
owner == Some(currentPlayer)
}
isDiagonal && (upDiagonal || downDiagonal)
}
if (rowVictory || columnVictory || diagonalVictory) {
Winner(currentPlayer)
} else if (newState.exists(_.owner.isEmpty)) {
InProgress
} else {
Draw
}
}
override def toString : String = {
state.grouped(boardWidth).map(_.mkString("|")).mkString("\n")
}
}
@tailrec
def playGame(game: Board) : Unit = {
game.gameStatus match {
case Winner(player) => println(s"$player is victorious!\n$game")
case Draw => println(s"There are no moves left. It's a stupid tie.\n$game")
case InProgress => {
println(s"Player ${game.currentPlayer}, your move")
println(s"$game\n")
val nextMove = scala.io.StdIn.readInt()
Try(game.move(nextMove)) match {
case Success(newBoard) => playGame(newBoard)
case Failure(ex) => {
println(s"Error making a move\n$ex\n")
playGame(game)
}
}
}
}
}
playGame(new Board(blankState, X, InProgress))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment