summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRobby Zambito <Zambito101@gmail.com>2019-11-23 22:25:13 -0500
committerRobby Zambito <Zambito101@gmail.com>2019-11-23 22:25:13 -0500
commiteece346656a652e54829b7199973a278bee9a6e1 (patch)
tree8caae97478adf722c78bae6ca498832735b45936
parentdf8385cab91926226e8a134bf074d4f5de79ea62 (diff)
Two player Othello
-rw-r--r--src/main/scala/me/robbyzambito/othello/Main.scala2
-rw-r--r--src/main/scala/me/robbyzambito/othello/game/Game.scala33
-rw-r--r--src/main/scala/me/robbyzambito/othello/game/Player.scala79
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