Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions lista10-kalahaGame/build.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
name := "kalahaGame"

version := "0.1"

scalaVersion := "2.13.1"

libraryDependencies += "com.typesafe.akka" %% "akka-actor" % "2.6.1"
libraryDependencies += "org.scalatest" % "scalatest_2.13" % "3.2.7" % "test"
libraryDependencies += "org.scalatestplus" %% "mockito-3-4" % "3.2.7.0" % "test"

50 changes: 50 additions & 0 deletions lista10-kalahaGame/src/main/scala/Actors/GameManager.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package Actors

import GameObjects.AI.minimax.{FixedDepth, MinMaxAlgorithm}
import GameObjects.AI.{HumanPlayer, MoveDecider}
import GameObjects.Outputs.ConsoleOutput
import GameObjects.Utilities._
import akka.actor.{Actor, Props}

import scala.io.StdIn
import scala.io.StdIn.readLine
import scala.util.Random

class GameManager extends Actor{
setUp()
private def setUp(): Unit = {
val board = new Board(4)
println("playerA: (h/ai) or end to finish")
val playerAIn = scala.io.StdIn.readLine()
if (playerAIn == "end") context.system.terminate()
else {
val playerA = if (playerAIn == "h") new HumanPlayer(board) else getAi(PlayerUpper(), board)
println("playerB: (h/ai)")
val playerBIn = scala.io.StdIn.readLine()
val playerB = if (playerBIn == "h") new HumanPlayer(board) else getAi(PlayerLower(), board)
firstRandomMove(board)
val output = new ConsoleOutput(board)
output.printGame()
context.actorOf(Props(classOf[Server], playerA, playerB, board, Long.MaxValue: Long, output))
}
}

private def firstRandomMove(board: Board) = {
println("First random? (y/n)")
val firstRandom = readLine()
if (firstRandom == "y") {
board.move(Random.nextInt(6), PlayerUpper())
}
}

def getAi(position: PlayerPosition, board: Board): MoveDecider = {
println("Ai depth:")
val depth = StdIn.readInt()
new MinMaxAlgorithm(board, position, new FixedDepth(depth))
}

override def receive: Receive = {
case GameFinished(_) =>
setUp()
}
}
34 changes: 34 additions & 0 deletions lista10-kalahaGame/src/main/scala/Actors/Player.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package Actors

import Actors.Player.{MakeMove, Move}
import Actors.Server.BadMove
import GameObjects.AI.MoveDecider
import akka.actor.Actor

class Player(val playerDecider: MoveDecider) extends Actor {
override def receive: Receive = {
case MakeMove() =>
makeMove()
case BadMove() =>
makeMove()
}

private def makeMove(): Unit = {
try {
val move = Move(playerDecider.getMove)
context.parent ! move
}
catch {
case e: IllegalAccessException =>
playerDecider.badMoveInform(e.getMessage)
throw e
}
}
}

object Player {

case class MakeMove()

case class Move(house: Int) //to consider making houses enum
}
65 changes: 65 additions & 0 deletions lista10-kalahaGame/src/main/scala/Actors/Server.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package Actors

import akka.actor.{Actor, ActorRef, Props}
import Actors.Player.{MakeMove, Move}
import Actors.Server.BadMove
import GameObjects.AI.MoveDecider
import GameObjects.Outputs.NoOutput
import GameObjects.Outputs.Output
import GameObjects.Utilities._

class Server(playerA: MoveDecider, playerB: MoveDecider, private val board: Board, private val timeForTurn: Long = 10, private val serverOutput: Output = NoOutput()) extends Actor {
val timer = new Timer()
val upperChild: ActorRef = context.actorOf(Props(classOf[Player], playerA))
val lowerChild: ActorRef = context.actorOf(Props(classOf[Player], playerB))

(if (board.toMove == PlayerUpper()) upperChild else lowerChild) ! MakeMove()

override def receive: Receive = {
case turn: Move =>
try {
if (timer.getTimeSeconds < timeForTurn) {
val player =
if (sender() == lowerChild) PlayerLower()
else if (sender() == upperChild) PlayerUpper()
else throw new InvalidSenderException("Unknown sender")
val destination = board.move(turn.house, player)
if (destination.isInstanceOf[GameFinished])
finishGame()
else {
serverOutput.printGame()
findDestination(destination) ! MakeMove()
}
timer.restart()
}
else context.parent ! GameFinished("time out")
}
catch {
case e: IllegalArgumentException =>
println(e.getMessage)
serverOutput.printGame()
sender() ! BadMove()
}
case GameFinished(_) =>
finishGame()
}

private def finishGame(): Unit = {
val message = s"Game finished. Result: ${board.playerUpperScore} - ${board.playerLowerScore}"
serverOutput.putMessage(message)
context.parent ! GameFinished()
}

private def findDestination(sendTo: PlayerPosition): ActorRef = {
sendTo match {
case PlayerUpper() => upperChild
case PlayerLower() => lowerChild
}
}
}

object Server {

case class BadMove()

}
70 changes: 70 additions & 0 deletions lista10-kalahaGame/src/main/scala/GameObjects/AI/AiAlgorithm.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package GameObjects.AI

import GameObjects.Utilities.{Board, GameFinished, PlayerPosition}

class AiAlgorithm(private val gameBoard: Board, private val aisPosition: PlayerPosition, private val algorithmDepth: Int) extends MoveDecider {
override def getMove: Int = {
val scores = new Array[Int](6)
for (i <- scores.indices) {
if (gameBoard.playerPit(aisPosition, i) != 0)
scores(i) = countPlayersMoveScore(gameBoard.clone(), algorithmDepth, i)
else scores(i) = Int.MinValue
}
scores.indexOf(scores.max)
}

private def countPlayersMoveScore(board: Board, depth: Int, houseToMove: Int): Int = {
val playerToMakeNextMove = board.move(houseToMove, aisPosition)
if (playerToMakeNextMove == GameFinished()) Int.MaxValue
else {
val nextFunction = menageNextMove(playerToMakeNextMove)
val scores = new Array[Int](6)
for (i <- scores.indices) {
if (board.playerPit(playerToMakeNextMove, i) != 0)
scores(i) = nextFunction(board.clone(), depth, i)
else scores(i) = Int.MinValue
}
scores.max
}
}

private def countOpponentsMoveScore(board: Board, depth: Int, houseToMove: Int): Int = {
val playerToMakeNextMove = board.move(houseToMove, aisPosition.opponent)
if (depth == 0) {
board.playerScore(aisPosition) - board.playerScore(aisPosition.opponent)
}
else {
if (playerToMakeNextMove == GameFinished()) Int.MinValue + 1
else {
val nextFunction = menageNextMove(playerToMakeNextMove)
val newDepth = if (playerToMakeNextMove == aisPosition) depth - 1 else depth
val scores = new Array[Int](6)
for (i <- scores.indices) {
if (board.playerPit(playerToMakeNextMove, i) != 0)
scores(i) = nextFunction(board.clone(), newDepth, i)
else scores(i) = Int.MaxValue
}
scores.min
}
}
}

private def menageNextMove(playerPosition: PlayerPosition): (Board, Int, Int) => Int = {
if (playerPosition == aisPosition) countPlayersMoveScore
else countOpponentsMoveScore
}

/*private def prepareMovesValue(function: (Board, Int, Int) => Int, depth: Int, player : PlayerPosition, board : Board): Seq[Int] = {
counter += 1
println(counter)
val scores = new Array[Int](6)
for (i <- scores.indices) {
if (board.playerPit(player, i) != 0)
scores(i) = function(board.clone(), depth, i)
}
scores
}*/


override def badMoveInform(message: String): Unit = println("ai missed: " + message)
}
13 changes: 13 additions & 0 deletions lista10-kalahaGame/src/main/scala/GameObjects/AI/HumanPlayer.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package GameObjects.AI

import GameObjects.Outputs.ConsoleOutput
import GameObjects.Utilities.Board

class HumanPlayer(private val board: Board) extends MoveDecider {
private val view = new ConsoleOutput(board)
override def getMove: Int = {
scala.io.StdIn.readInt()
}

override def badMoveInform(message: String): Unit = view.putMessage(message)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package GameObjects.AI

trait MoveDecider {
def getMove : Int
def badMoveInform(message : String) : Unit
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package GameObjects.AI.evaluation
import GameObjects.Utilities.{Board, PlayerPosition}

class EvaluationByResult extends EvaluationStrategy {
override def evaluate(player: PlayerPosition, board: Board): Int = {
board.playerScore(player) - board.playerScore(player.opponent)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package GameObjects.AI.evaluation

import GameObjects.Utilities.{Board, PlayerPosition}

trait EvaluationStrategy {
def evaluate(player: PlayerPosition, board: Board): Int
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package GameObjects.AI.evaluation
import GameObjects.Utilities.{Board, PlayerPosition}

class FakeEvaluationStrategy extends EvaluationStrategy {
var x = 0
override def evaluate(player: PlayerPosition, board: Board): Int = {
x += 1
x
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package GameObjects.AI.minimax

trait DepthDetermination {
def determineDepth: Int
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package GameObjects.AI.minimax

class FixedDepth(private val depth: Int) extends DepthDetermination {
override def determineDepth: Int = depth
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package GameObjects.AI.minimax

import GameObjects.AI._
import GameObjects.AI.evaluation.EvaluationByResult
import GameObjects.Utilities.{Board, PlayerPosition}

class MinMaxAlgorithm(private val gameBoard: Board, private val aisPosition: PlayerPosition,
private val depthDetermination: DepthDetermination, private val aiAlgorithm: MiniMaxVisitor = new MiniMaxVisitor(new EvaluationByResult))
extends MoveDecider {

override def getMove: Int = {
val move = aiAlgorithm.minimax(depthDetermination.determineDepth, aisPosition, gameBoard)
println(s"Ai choose $move")
move
}

override def badMoveInform(message: String): Unit = println("ai missed: " + message)
}
Loading