diff options
author | Robby Zambito <Zambito101@gmail.com> | 2019-11-23 22:25:13 -0500 |
---|---|---|
committer | Robby Zambito <Zambito101@gmail.com> | 2019-11-23 22:25:13 -0500 |
commit | eece346656a652e54829b7199973a278bee9a6e1 (patch) | |
tree | 8caae97478adf722c78bae6ca498832735b45936 | |
parent | df8385cab91926226e8a134bf074d4f5de79ea62 (diff) |
Two player Othello
-rw-r--r-- | src/main/scala/me/robbyzambito/othello/Main.scala | 2 | ||||
-rw-r--r-- | src/main/scala/me/robbyzambito/othello/game/Game.scala | 33 | ||||
-rw-r--r-- | src/main/scala/me/robbyzambito/othello/game/Player.scala | 79 |
3 files changed, 73 insertions, 41 deletions
diff --git a/src/main/scala/me/robbyzambito/othello/Main.scala b/src/main/scala/me/robbyzambito/othello/Main.scala index 3ad491c..6ee0103 100644 --- a/src/main/scala/me/robbyzambito/othello/Main.scala +++ b/src/main/scala/me/robbyzambito/othello/Main.scala @@ -12,6 +12,8 @@ object Main extends App { def gameLoop(game: Game): Unit = { if (game.winner.isEmpty) { gameLoop(game.takeTurn) + } else { + println(game.winnerMessage) } } diff --git a/src/main/scala/me/robbyzambito/othello/game/Game.scala b/src/main/scala/me/robbyzambito/othello/game/Game.scala index f3a2738..8072bf1 100644 --- a/src/main/scala/me/robbyzambito/othello/game/Game.scala +++ b/src/main/scala/me/robbyzambito/othello/game/Game.scala @@ -1,6 +1,7 @@ package me.robbyzambito.othello.game import scala.io.StdIn +import scala.util.Try /** * Represents the state of the game. @@ -41,17 +42,11 @@ case class Game(board: Board, else if (whiteCount < blackCount) players.find(p => p.color == Position.BLACK) else None - // throw new Error("Game tied") + // throw new Error("Game tied") } } - override def toString: String = { - s""" - |${board} - | - |${if (winner.isDefined) s"${winner.get} has won!" else ""} - |""".stripMargin - } + lazy val winnerMessage: String = s"${winner.map(_.toString).getOrElse("Nobody")} has won!" /** * Take a turn @@ -59,22 +54,32 @@ case class Game(board: Board, * @return the game with the next turn state */ def takeTurn: Game = { - println(board) - println() + println(s"${this}\n") def getPos: (Int, Int) = { - val rowCount = StdIn.readLine(s"Enter the row to move for ${currentPlayer}: ").toInt - val colCount = StdIn.readLine(s"Enter the col to move for ${currentPlayer}: ").toInt + val rowCount = Iterator.continually( + Try(StdIn.readLine(s"Enter the row to move for ${currentPlayer}: ").toInt) + ).dropWhile(_.isFailure).next().get + val colCount = Iterator.continually( + Try(StdIn.readLine(s"Enter the col to move for ${currentPlayer}: ").toInt) + ).dropWhile(_.isFailure).next().get + (rowCount, colCount) } + val possibleMoves = currentPlayer.possibleMoves(board) val pos = Iterator.continually(getPos) - .dropWhile(!currentPlayer.possibleMoves(board).contains(_)) + .dropWhile(!possibleMoves.map(m => (m.rowCount, m.colCount)).contains(_)) .next() - this.copy(board = board.updatePosition(pos._1, pos._2, currentPlayer.color), turnCount = turnCount + 1) + val move = possibleMoves.find(m => m.rowCount == pos._1 && m.colCount == pos._2).get + this.copy(board = move(board, currentPlayer), turnCount = turnCount + 1) } + override def toString: String = + s""" ${0 to 7 mkString " "} + |${board.toString.split("\n").zipWithIndex.map { case (s, i) => s"$i $s" }.mkString("\n")}""".stripMargin + } object Game { diff --git a/src/main/scala/me/robbyzambito/othello/game/Player.scala b/src/main/scala/me/robbyzambito/othello/game/Player.scala index e67d1b0..581d2df 100644 --- a/src/main/scala/me/robbyzambito/othello/game/Player.scala +++ b/src/main/scala/me/robbyzambito/othello/game/Player.scala @@ -2,7 +2,6 @@ package me.robbyzambito.othello.game import me.robbyzambito.othello.game.Position.Position -import scala.annotation.tailrec import scala.util.Try /** @@ -17,51 +16,77 @@ case class Player(color: Position) { def canMove(board: Board): Boolean = possibleMoves(board).nonEmpty - def possibleMoves(board: Board): List[(Int, Int)] = { + def possibleMoves(board: Board): List[Move] = { val enemyPosition = if (color == Position.WHITE) Position.BLACK else Position.WHITE - def possibleAcross(rowCount: Int, colCount: Int): Boolean = { + def possibleAcross(rowCount: Int, colCount: Int): Option[Move] = { val row = board.positions(rowCount) - @tailrec - def checkInDirection(pos: Int, step: Int, isPossible: Boolean = false): Boolean = { - if (pos == colCount && row(pos) != Position.EMPTY) - false - else if (isPossible && row(pos + step) == color) - true - else if (!isPossible && Try(row(pos + step)).getOrElse(Position.EMPTY) == enemyPosition) - checkInDirection(pos + step, step, isPossible = true) - else false + /** + * Return the list of positions which will be flipped if a move is made at row(rowCount, pos) + * + * @param pos Offset in the current row + * @param step Direction which to check for the next piece. + * @param isPossible + * @return List of positions which will change if one is placed. Empty list means move is invalid. + */ + def checkInDirection(pos: Int, step: Int, isPossible: Boolean = false): List[(Int, Int)] = { + if (pos == colCount && row(pos) != Position.EMPTY) { + List.empty + } else if (isPossible && Try(row(pos + step)).getOrElse(Position.EMPTY) == color) { + List((rowCount, pos)) + } else if (Try(row(pos + step)).getOrElse(Position.EMPTY) == enemyPosition) { + val nextDirections = checkInDirection(pos + step, step, isPossible = true) + if (nextDirections.nonEmpty) + (rowCount, pos) :: nextDirections + else List.empty + } else List.empty } // Check can move left or right - checkInDirection(colCount, 1) || checkInDirection(colCount, -1) + val changes = checkInDirection(colCount, 1) ::: checkInDirection(colCount, -1) + if (changes.nonEmpty) + Some(Move(rowCount, colCount, changes)) + else None } - def possibleVertical(rowCount: Int, colCount: Int): Boolean = { + def possibleVertical(rowCount: Int, colCount: Int): Option[Move] = { val col = board.positions.indices.map(i => board.positions(i)(colCount)) - @tailrec - def checkInDirection(pos: Int, step: Int, isPossible: Boolean = false): Boolean = { - if (pos == rowCount && col(pos) != Position.EMPTY) - false - else if (isPossible && col(pos + step) == color) - true - else if (!isPossible && Try(col(pos + step)).getOrElse(Position.EMPTY) == enemyPosition) - checkInDirection(pos + step, step, isPossible = true) - else false + + def checkInDirection(pos: Int, step: Int, isPossible: Boolean = false): List[(Int, Int)] = { + if (pos == rowCount && col(pos) != Position.EMPTY) { + List.empty + } else if (isPossible && Try(col(pos + step)).getOrElse(Position.EMPTY) == color) { + List((pos, colCount)) + } else if (Try(col(pos + step)).getOrElse(Position.EMPTY) == enemyPosition) { + val nextDiections = checkInDirection(pos + step, step, isPossible = true) + if (nextDiections.nonEmpty) + (pos, colCount) :: nextDiections + else List.empty + } else List.empty } // Check can move left or right - checkInDirection(rowCount, 1) || checkInDirection(rowCount, -1) + val changes = checkInDirection(rowCount, 1) ::: checkInDirection(rowCount, -1) + if (changes.nonEmpty) + Some(Move(rowCount, colCount, changes)) + else None } (for (rowCount <- board.positions.indices) yield { (for (colCount <- board.positions(rowCount).indices) yield { - if (possibleAcross(rowCount, colCount) || possibleVertical(rowCount, colCount)) - List((rowCount, colCount)) - else List() + val (acc, vert) = (possibleAcross(rowCount, colCount), possibleVertical(rowCount, colCount)) + + if (acc.isDefined || vert.isDefined) + Some( + Move(rowCount, + colCount, + acc.map(_.takenPositions).getOrElse(List.empty) ::: vert.map(_.takenPositions).getOrElse(List.empty) + ) + ) + else None }).flatten }).flatten .toList |