Last active
August 31, 2017 03:35
-
-
Save countingtoten/ce6fed843326ecf4ecf606b5ca10f040 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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