Browse Source

Made moveScore overloadable for AIPlayer.

Correctly assign currentOpponent.
Show final board after last move.
Handle ties with Tie object.
Added multiple AI implementations.
master
Robby Zambito 7 months ago
parent
commit
0a33acbf0d
5 changed files with 55 additions and 20 deletions
  1. +5
    -1
      src/main/scala/me/robbyzambito/othello/Main.scala
  2. +5
    -4
      src/main/scala/me/robbyzambito/othello/game/AIPlayer.scala
  3. +37
    -14
      src/main/scala/me/robbyzambito/othello/game/Game.scala
  4. +1
    -1
      src/main/scala/me/robbyzambito/othello/game/Player.scala
  5. +7
    -0
      src/main/scala/me/robbyzambito/othello/game/package.scala

+ 5
- 1
src/main/scala/me/robbyzambito/othello/Main.scala View File

@@ -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()

}

+ 5
- 4
src/main/scala/me/robbyzambito/othello/game/AIPlayer.scala View File

@@ -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.


+ 37
- 14
src/main/scala/me/robbyzambito/othello/game/Game.scala View File

@@ -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)))
}

+ 1
- 1
src/main/scala/me/robbyzambito/othello/game/Player.scala View File

@@ -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"
}

}

+ 7
- 0
src/main/scala/me/robbyzambito/othello/game/package.scala View File

@@ -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"
}

}

Loading…
Cancel
Save