From 0a33acbf0dd5ca40bbf18089b20885fa832acfc5 Mon Sep 17 00:00:00 2001 From: Robby Zambito Date: Sat, 30 Nov 2019 21:20:27 -0500 Subject: Made moveScore overloadable for AIPlayer. Correctly assign currentOpponent. Show final board after last move. Handle ties with Tie object. Added multiple AI implementations. --- src/main/scala/me/robbyzambito/othello/Main.scala | 6 ++- .../me/robbyzambito/othello/game/AIPlayer.scala | 9 ++-- .../scala/me/robbyzambito/othello/game/Game.scala | 51 ++++++++++++++++------ .../me/robbyzambito/othello/game/Player.scala | 2 +- .../me/robbyzambito/othello/game/package.scala | 7 +++ 5 files changed, 55 insertions(+), 20 deletions(-) diff --git a/src/main/scala/me/robbyzambito/othello/Main.scala b/src/main/scala/me/robbyzambito/othello/Main.scala index 3158f10..d0d4b6f 100644 --- a/src/main/scala/me/robbyzambito/othello/Main.scala +++ b/src/main/scala/me/robbyzambito/othello/Main.scala @@ -1,9 +1,13 @@ package me.robbyzambito.othello -import me.robbyzambito.othello.game.Game +import me.robbyzambito.othello.game._ object Main extends App { Game().loop() +// Game.easy.loop() +// Game.random.loop() +// Game(List(AIPlayer(Position.WHITE), AIPlayer(Position.BLACK, -_.takenPositions.length))).loop() + //Game(List(AIPlayer(Position.WHITE, -_.takenPositions.length), AIPlayer(Position.BLACK, -_.takenPositions.length))).loop() } diff --git a/src/main/scala/me/robbyzambito/othello/game/AIPlayer.scala b/src/main/scala/me/robbyzambito/othello/game/AIPlayer.scala index b2e8a68..8704db9 100644 --- a/src/main/scala/me/robbyzambito/othello/game/AIPlayer.scala +++ b/src/main/scala/me/robbyzambito/othello/game/AIPlayer.scala @@ -9,17 +9,18 @@ package me.robbyzambito.othello.game * * See [[Player]] */ -case class AIPlayer(override val color: Position) extends Player(color) { +case class AIPlayer(override val color: Position, + moveScore: (Move) => Int = _.takenPositions.length) extends Player(color) { /** - * Create an implicit method score used for decision making. + * Create an implicit value `score` used for decision making. * This is the method can be as simple or as involved * as need be. * * @param move */ implicit class MoveScore(move: Move) { - def score: Int = move.takenPositions.length + val score: Int = moveScore(move) // By default this maps a move to the # of } override def nextMove(board: Board): Move = { @@ -27,7 +28,7 @@ case class AIPlayer(override val color: Position) extends Player(color) { implicit val moveOrdering: Ordering[Move] = (x, y) => x.score.compareTo(y.score) println(s"$this moving...") - Thread.sleep(1000L) + Thread.sleep(1000L) // It is jarring if the computer immediately plays its turn. println() // Select the move with the highest score. diff --git a/src/main/scala/me/robbyzambito/othello/game/Game.scala b/src/main/scala/me/robbyzambito/othello/game/Game.scala index 6fc278f..d3f76e1 100644 --- a/src/main/scala/me/robbyzambito/othello/game/Game.scala +++ b/src/main/scala/me/robbyzambito/othello/game/Game.scala @@ -15,10 +15,16 @@ case class Game(board: Board, turnCount: Int = 0) { val currentPlayer: Player = players(turnCount % players.length) - val currentOpponent: Player = players(turnCount % players.length) + val currentOpponent: Player = players((turnCount + 1) % players.length) + + // The winner is decided by seeing who has the most positions on the board. + private lazy val whiteCount = board.positions.flatten.count(_ == Position.WHITE) + private lazy val blackCount = board.positions.flatten.count(_ == Position.BLACK) /** - * Plays the game. + * Progresses the state of the game. When the game is finished, + * the final board is printed out. + * * @param game Current state of the game. */ @tailrec @@ -26,7 +32,14 @@ case class Game(board: Board, if (game.winner.isEmpty) { loop(game.takeTurn) } else { - println(game.winnerMessage) + println(game.winnerMessage + "\n\nFinal board:") + println(game.board) + + println( + s""" + |Final score: + |\t${game.players.find(_.color == Position.WHITE).get}\t:\t${game.whiteCount} + |\t${game.players.find(_.color == Position.BLACK).get}\t:\t${game.blackCount}""".stripMargin) } } @@ -38,7 +51,7 @@ case class Game(board: Board, def save(): Boolean = ??? /** - * The winner of the game. Ties are not yet handled + * The winner of the game. Ties are represented by the [[Tie]] object. * * [[None]] if there has not been a winner yet. Otherwise return the [[Player]] which has won. */ @@ -46,22 +59,19 @@ case class Game(board: Board, if (currentPlayer.canMove(board) || currentOpponent.canMove(board)) None else { // No one can move => Game is finished - val whiteCount = board.positions.flatten.count(_ == Position.WHITE) - val blackCount = board.positions.flatten.count(_ == Position.BLACK) - if (whiteCount > blackCount) - players.find(p => p.color == Position.WHITE) + players.find(_.color == Position.WHITE) else if (whiteCount < blackCount) - players.find(p => p.color == Position.BLACK) - else None - // throw new Error("Game tied") + players.find(_.color == Position.BLACK) + else + Some(Tie) } } - lazy val winnerMessage: String = s"${winner.map(_.toString).getOrElse("Nobody")} has won!" + lazy val winnerMessage: String = s"${winner.get} has won!" /** - * Take a turn + * Take a turn. It is assumed that the game is not over. * * @return the game with the next turn state */ @@ -88,5 +98,18 @@ case class Game(board: Board, object Game { def apply(players: List[Player]): Game = new Game(Board.init(), players) - def apply(): Game = Game( List(UserPlayer(Position.WHITE), AIPlayer(Position.BLACK)) ) + // Default AI will choose the moves which flip the most on the current turn. + def apply(): Game = Game(List( + UserPlayer(Position.WHITE), + AIPlayer(Position.BLACK))) + + // The AI will always flip the least number of positions per turn. + def easy: Game = Game(List( + UserPlayer(Position.WHITE), + AIPlayer(Position.BLACK, -_.takenPositions.length))) + + // Select a random available move. + def random: Game = Game(List( + UserPlayer(Position.WHITE), + AIPlayer(Position.BLACK, _ => (math.random() * 100).toInt))) } \ No newline at end of file diff --git a/src/main/scala/me/robbyzambito/othello/game/Player.scala b/src/main/scala/me/robbyzambito/othello/game/Player.scala index c55f6db..262e4e3 100644 --- a/src/main/scala/me/robbyzambito/othello/game/Player.scala +++ b/src/main/scala/me/robbyzambito/othello/game/Player.scala @@ -106,7 +106,7 @@ abstract class Player(val color: Position) { override def toString: String = color match { case Position.WHITE => "White" case Position.BLACK => "Black" - case _ => "Default" + case _ => "UNKNOWN" } } diff --git a/src/main/scala/me/robbyzambito/othello/game/package.scala b/src/main/scala/me/robbyzambito/othello/game/package.scala index 250da56..61d1154 100644 --- a/src/main/scala/me/robbyzambito/othello/game/package.scala +++ b/src/main/scala/me/robbyzambito/othello/game/package.scala @@ -8,4 +8,11 @@ package object game { } type Position = Position.Position + + object Tie extends Player(Position.EMPTY) { + override def nextMove(board: Board): Move = ??? + + override def toString: String = "Nobody" + } + } -- cgit v1.2.3