From de5c52fc4b41b2939b7b7b0ec14d054bc8364bcd Mon Sep 17 00:00:00 2001 From: Szymek Date: Fri, 7 Jan 2022 20:42:13 +0100 Subject: [PATCH 01/16] Add Kalaha game --- Kalaha.scala | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 Kalaha.scala diff --git a/Kalaha.scala b/Kalaha.scala new file mode 100644 index 00000000..bccf8292 --- /dev/null +++ b/Kalaha.scala @@ -0,0 +1,59 @@ +class Kalaha() { + + private val field1:Array[Int] = Array(6,6,6,6,6,6,0) + private val field2:Array[Int] = Array(6,6,6,6,6,6,0) + + def move(id: Int, field: Int): Int ={ + + var level = id == 1 + var tmpField = newField(level) + var points = tmpField(field) + tmpField(field) = 0 + var index = field + while(points > 0 && index <= 6){ + index += 1 + if index == 7 then { + index = 0 + level = !level + tmpField = newField(level) + } + tmpField(index) += 1 + points -= 1 + } + + if (id==1) == level && index <= 5 then + val otherField = newField(!level) + if tmpField(index) == 1 then + tmpField(6) += otherField(5 - index) + otherField(5-index) = 0 + + if (id==1) == level && index == 6 then + if level then 1 else 2 + else + if level then 2 else 1 + } + + def isOver:Boolean = { + + var isOver1 = false + var isOver2 = false + field1.foreach(x => isOver1 = x==0) + field2.foreach(x => isOver2 = x==0) + isOver1 || isOver2 + } + + def whoWon():Int = { + + var res = 0 + val res1 = field1.foldLeft(0)((x, sum)=> sum + x) + val res2 = field2.foldLeft(0)((x, sum)=> sum + x) + if res1 > res2 then res = 1 + else if res1 < res2 then res = 2 + res + } + + val newField1: Int => Array[Int] = id => if id == 1 then field1 else field2 + + val newField: Boolean => Array[Int] = value =>if value then field1 else field2 + +} From 0796f6d12e5b553c84d9076ceb8a25e54b3663c2 Mon Sep 17 00:00:00 2001 From: Szymek Date: Fri, 14 Jan 2022 13:41:02 +0100 Subject: [PATCH 02/16] Add Player --- Player.scala | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 Player.scala diff --git a/Player.scala b/Player.scala new file mode 100644 index 00000000..f759f015 --- /dev/null +++ b/Player.scala @@ -0,0 +1,14 @@ +import scala.io.StdIn.* + +class Player(id: Int) { + + def makeMove(): Int ={ + + print("\nSelect field number: ") + val field = readInt() + field - 1 + + } + + def getId: Int = id +} From 84bdba4ef9120fea69cfa081e67c34a95619d69e Mon Sep 17 00:00:00 2001 From: Szymek Date: Fri, 14 Jan 2022 13:41:45 +0100 Subject: [PATCH 03/16] Add server --- Server.scala | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 Server.scala diff --git a/Server.scala b/Server.scala new file mode 100644 index 00000000..b55e93ca --- /dev/null +++ b/Server.scala @@ -0,0 +1,25 @@ +import scala.util.Random + +class Server(val player1: Player, val player2: Player): + + val game = new Kalaha() + + def play(): Unit = + + var movingPlayer:Player = player1 + var next = 0 + val rng = Random.nextInt() + if rng % 2 == 1 then movingPlayer = player2 + + while(!game.isOver){ + println("Player "+ movingPlayer.getId + " moving") + game.printFields(movingPlayer.getId) + next = game.move(movingPlayer.getId, movingPlayer.makeMove()) + if next == 1 then movingPlayer = player1 + else movingPlayer = player2 + + } + val winner = game.whoWon() + println("GAME IS OVER!") + if winner == 0 then println("It is a draw!!!\nCONGRATULATION") + else println("\nand the winner is...\nPlayer " + winner + "!!!") From 2410213c820111597b61284ca0a6691c7c1e559b Mon Sep 17 00:00:00 2001 From: Szymek Date: Fri, 14 Jan 2022 13:42:38 +0100 Subject: [PATCH 04/16] create ai classes --- AIPlayer.scala | 14 ++++++++++++++ LogicEngine.scala | 21 +++++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 AIPlayer.scala create mode 100644 LogicEngine.scala diff --git a/AIPlayer.scala b/AIPlayer.scala new file mode 100644 index 00000000..1395b3c7 --- /dev/null +++ b/AIPlayer.scala @@ -0,0 +1,14 @@ +class AIPlayer(id: Int, game: Kalaha) extends Player(id) { + + private val logicEngine = LogicEngine(game, id) + + override def getId: Int = id + + override def makeMove(): Int = { + + logicEngine.makeMove() + + } + + def addEnemyMove(move: Int): Unit = logicEngine.addEnemyMove(move) +} diff --git a/LogicEngine.scala b/LogicEngine.scala new file mode 100644 index 00000000..f62db60c --- /dev/null +++ b/LogicEngine.scala @@ -0,0 +1,21 @@ +class LogicEngine(val game: Kalaha, val playerId: Int) { + + var moves = List[Int]() + val decisions =new Tree(6) + val simulations = new Array[Kalaha](6) + + def makeMove(): Int = + + + + + 0 + + def simulateMove(fieldNumber: Int): Unit = + simulations(fieldNumber - 1) = game.copy() + simulations(fieldNumber - 1).move(fieldNumber-1, playerId) + decisions.getNode(moves).addNewKidAt(simulations(fieldNumber - 1).getScore(playerId), fieldNumber - 1) + + def addEnemyMove(move: Int): Unit = moves = moves :+ move + +} From b888b4bac6bb4cbe7c622f50dbb2d806377bc056 Mon Sep 17 00:00:00 2001 From: Szymek Date: Fri, 14 Jan 2022 13:43:24 +0100 Subject: [PATCH 05/16] create tree structure --- Tree.scala | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 Tree.scala diff --git a/Tree.scala b/Tree.scala new file mode 100644 index 00000000..3b2b51c9 --- /dev/null +++ b/Tree.scala @@ -0,0 +1,42 @@ +import scala.annotation.tailrec + +class Node[T](var value: T, val size: Int) { + + private val kids = new Array[Node[T]](size) + private var index = 0 + + def getValue:T = value + + def setValue(newValue: T): Unit = value = newValue + + def getKids: Array[Node[T]] = kids + + def addKid(newKid: Node[T]): Unit = + kids(index) = newKid + index += 1 + + def addNewKid(value: T): Unit = + kids(index) = new Node[T](value, kids.length) + + def addKidAt(newKid: Node[T], index: Int): Unit = + kids(index) = newKid + + def addNewKidAt(value: T, index: Int): Unit = + kids(index) = new Node[T](value, kids.length) +} + +class Tree(val kidsLimit: Int){ + + var root: Node[Int] = Node[Int](0, kidsLimit) + + def getNode(path: List[Int]): Node[Int] = + @tailrec + def iterate(curr: Node[Int], tail: List[Int]): Node[Int] = + tail match + case Nil => curr + case h::t => iterate(curr.getKids(h-1), t) + iterate(root, path) + + +} + From 64b7ee53c269e4e81dd7cd3a8afae204e1b42096 Mon Sep 17 00:00:00 2001 From: Szymek Date: Fri, 14 Jan 2022 13:45:06 +0100 Subject: [PATCH 06/16] Add game communication --- Kalaha.scala | 65 +++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 51 insertions(+), 14 deletions(-) diff --git a/Kalaha.scala b/Kalaha.scala index bccf8292..13739afa 100644 --- a/Kalaha.scala +++ b/Kalaha.scala @@ -1,12 +1,17 @@ -class Kalaha() { +import scala.util.control.Breaks.break - private val field1:Array[Int] = Array(6,6,6,6,6,6,0) - private val field2:Array[Int] = Array(6,6,6,6,6,6,0) +class Kalaha(private val field1: Array[Int], private val field2: Array[Int]) { + + def this() = { + + this(Array(6,6,6,6,6,6,0),Array(6,6,6,6,6,6,0)) + + } def move(id: Int, field: Int): Int ={ var level = id == 1 - var tmpField = newField(level) + var tmpField = nextField(level) var points = tmpField(field) tmpField(field) = 0 var index = field @@ -15,14 +20,14 @@ class Kalaha() { if index == 7 then { index = 0 level = !level - tmpField = newField(level) + tmpField = nextField(level) } tmpField(index) += 1 points -= 1 } if (id==1) == level && index <= 5 then - val otherField = newField(!level) + val otherField = nextField(!level) if tmpField(index) == 1 then tmpField(6) += otherField(5 - index) otherField(5-index) = 0 @@ -33,27 +38,59 @@ class Kalaha() { if level then 2 else 1 } + def getScore(id: Int): Int = + if id == 1 then field1(5) - field2(5) + else field2(5) - field1(5) + def isOver:Boolean = { - var isOver1 = false - var isOver2 = false - field1.foreach(x => isOver1 = x==0) - field2.foreach(x => isOver2 = x==0) + var isOver1 = true + var isOver2 = true + field1.foreach(x => if x!=0 then { + isOver1 = false + break + }) + field2.foreach(x => if x!=0 then { + isOver2 = false + break + }) isOver1 || isOver2 } def whoWon():Int = { var res = 0 - val res1 = field1.foldLeft(0)((x, sum)=> sum + x) - val res2 = field2.foldLeft(0)((x, sum)=> sum + x) + val res1 = field1.foldLeft(0)((x, sum) => sum + x) + val res2 = field2.foldLeft(0)((x, sum) => sum + x) if res1 > res2 then res = 1 else if res1 < res2 then res = 2 res } - val newField1: Int => Array[Int] = id => if id == 1 then field1 else field2 + def printFields(id: Int): Unit = { + + var playerField = field1 + var enemyField = field2.reverse + + if id == 2 then + playerField = field2 + enemyField = field1.reverse + + println("|---------------Enemy Field---------------|") + println("|Base |--6--|--5--|--4--|--3--|--2--|--1--|") + enemyField.foreach(x => printf("|%5d",x)) + println("|") + println("|-----------------------------------------|") + playerField.foreach(x => printf("|%5d",x)) + println("|") + println("|--1--|--2--|--3--|--4--|--5--|--6--| Base|") + println("|--------------Player Field---------------|") + } + + def copy(): Kalaha = new Kalaha(field1.clone(), field2.clone()) + + val nextField1: Int => Array[Int] = id => if id == 1 then field1 else field2 - val newField: Boolean => Array[Int] = value =>if value then field1 else field2 + val nextField: Boolean => Array[Int] = value =>if value then field1 else field2 } From 0fd06a09f309eae59e82654c4e2f649b21a39cc8 Mon Sep 17 00:00:00 2001 From: Szymek Date: Fri, 14 Jan 2022 21:12:58 +0100 Subject: [PATCH 07/16] Add basic menu --- Menu.scala | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 Menu.scala diff --git a/Menu.scala b/Menu.scala new file mode 100644 index 00000000..8af3542d --- /dev/null +++ b/Menu.scala @@ -0,0 +1,32 @@ +import scala.io.StdIn.* + +class Menu() { + + def run(): Unit = + val game = new Kalaha() + println("KALAHA") + println("1.Singleplayer") + println("2.Multiplayer") + println("3.Simulation") + println("4.Exit") + print("Choice: ") + + val choice = readInt() + + if choice == 1 then + val p1 = new Player(1) + val p2 = new AIPlayer(2, game) + val server = new Server(p1, p2, game) + server.play() + if choice == 2 then + val p1 = new Player(1) + val p2 = new Player(2) + val server = new Server(p1, p2, game) + server.play() + if choice == 3 then + val p1 = new AIPlayer(1, game) + val p2 = new AIPlayer(2, game) + val server = new Server(p1, p2, game) + server.play() + if choice != 4 then run() +} From bd86ccb615c9f946e4b10891d0dbbcb1c8322fad Mon Sep 17 00:00:00 2001 From: Szymek Date: Fri, 14 Jan 2022 21:14:45 +0100 Subject: [PATCH 08/16] Repair next move bug --- Kalaha.scala | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/Kalaha.scala b/Kalaha.scala index 13739afa..efeababf 100644 --- a/Kalaha.scala +++ b/Kalaha.scala @@ -26,34 +26,39 @@ class Kalaha(private val field1: Array[Int], private val field2: Array[Int]) { points -= 1 } - if (id==1) == level && index <= 5 then + if (id == 1) == level && index <= 5 then val otherField = nextField(!level) if tmpField(index) == 1 then tmpField(6) += otherField(5 - index) otherField(5-index) = 0 - if (id==1) == level && index == 6 then - if level then 1 else 2 + if index == 6 then + if (id == 1) == level then + if level then 1 else 2 + else + if level then 1 else 2 else - if level then 2 else 1 + if id == 1 then 2 else 1 } def getScore(id: Int): Int = - if id == 1 then field1(5) - field2(5) - else field2(5) - field1(5) + if id == 1 then field1(6) - field2(6) + else field2(6) - field1(6) - def isOver:Boolean = { + def isOver():Boolean = { var isOver1 = true var isOver2 = true - field1.foreach(x => if x!=0 then { - isOver1 = false - break - }) - field2.foreach(x => if x!=0 then { - isOver2 = false - break - }) + var i = 0 + while(i < 6){ + if field1(i) != 0 then isOver1 = false + i += 1 + } + i = 0 + while(i < 6){ + if field2(i) != 0 then isOver2 = false + i += 1 + } isOver1 || isOver2 } @@ -89,7 +94,7 @@ class Kalaha(private val field1: Array[Int], private val field2: Array[Int]) { def copy(): Kalaha = new Kalaha(field1.clone(), field2.clone()) - val nextField1: Int => Array[Int] = id => if id == 1 then field1 else field2 + //val nextField1: Int => Array[Int] = id => if id == 1 then field1 else field2 val nextField: Boolean => Array[Int] = value =>if value then field1 else field2 From 8ec5db50e5490646c08f7c5619250583df4f07aa Mon Sep 17 00:00:00 2001 From: Szymek Date: Fri, 14 Jan 2022 21:15:48 +0100 Subject: [PATCH 09/16] Upgrade messages --- Server.scala | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Server.scala b/Server.scala index b55e93ca..07de6f70 100644 --- a/Server.scala +++ b/Server.scala @@ -1,8 +1,6 @@ import scala.util.Random -class Server(val player1: Player, val player2: Player): - - val game = new Kalaha() +class Server(private val player1: Player,private val player2: Player,private val game: Kalaha): def play(): Unit = @@ -11,14 +9,17 @@ class Server(val player1: Player, val player2: Player): val rng = Random.nextInt() if rng % 2 == 1 then movingPlayer = player2 - while(!game.isOver){ + while(!game.isOver()){ println("Player "+ movingPlayer.getId + " moving") game.printFields(movingPlayer.getId) - next = game.move(movingPlayer.getId, movingPlayer.makeMove()) + val choice = movingPlayer.makeMove() + next = game.move(movingPlayer.getId, choice) + println("Field " + (choice + 1) + " chosen") if next == 1 then movingPlayer = player1 else movingPlayer = player2 } + val winner = game.whoWon() println("GAME IS OVER!") if winner == 0 then println("It is a draw!!!\nCONGRATULATION") From 084b3c0d2f8a3c83be2eca81ee4260085b328500 Mon Sep 17 00:00:00 2001 From: Szymek Date: Fri, 14 Jan 2022 21:17:12 +0100 Subject: [PATCH 10/16] Add Main --- Main.scala | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 Main.scala diff --git a/Main.scala b/Main.scala new file mode 100644 index 00000000..4e6b895d --- /dev/null +++ b/Main.scala @@ -0,0 +1,9 @@ +object Main { + + def main(args: Array[String]): Unit = { + + val menu = new Menu() + menu.run() + } + +} From 1f73cae0a93f3d4354e8875eb93dcbaa74b95c9f Mon Sep 17 00:00:00 2001 From: Szymek Date: Fri, 14 Jan 2022 21:18:38 +0100 Subject: [PATCH 11/16] Clean AIPlayer --- AIPlayer.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/AIPlayer.scala b/AIPlayer.scala index 1395b3c7..e6af8ff5 100644 --- a/AIPlayer.scala +++ b/AIPlayer.scala @@ -10,5 +10,4 @@ class AIPlayer(id: Int, game: Kalaha) extends Player(id) { } - def addEnemyMove(move: Int): Unit = logicEngine.addEnemyMove(move) } From 515259e8283e6f762666c17d2123c009decfe377 Mon Sep 17 00:00:00 2001 From: Szymek Date: Sat, 15 Jan 2022 12:34:34 +0100 Subject: [PATCH 12/16] Repair AI bug --- LogicEngine.scala | 61 ++++++++++++++++++++++++++++++++++++++++------- Tree.scala | 41 ++++++++++++++++++++++++------- 2 files changed, 84 insertions(+), 18 deletions(-) diff --git a/LogicEngine.scala b/LogicEngine.scala index f62db60c..60bfe170 100644 --- a/LogicEngine.scala +++ b/LogicEngine.scala @@ -1,21 +1,64 @@ +import scala.concurrent.{Await, Future} +import scala.concurrent.duration._ +import scala.concurrent.ExecutionContext.Implicits.global + class LogicEngine(val game: Kalaha, val playerId: Int) { - var moves = List[Int]() - val decisions =new Tree(6) - val simulations = new Array[Kalaha](6) + var moves: List[Int] = List[Int]() + val decisions = new MyTree(6, game) def makeMove(): Int = + if !decisions.getNode(moves).hasKids then { + decisions.getNode(moves).setValue(game.getScore(playerId), game) + + simulate() + } + var bestMove = -1 + var bestScore = -100 + val simResult = decisions.getNode(moves).findPaths() + simResult.foreach(elem => + if elem._1._1 > bestScore && game.getField(playerId)(elem._2.head) != 0 then { + bestScore = elem._1._1 + bestMove = elem._2.head + }) + moves = moves :+ bestMove + bestMove + + + def simulate(): Unit = + def simulateMove(path: List[Int]): Unit = + /* + val move0 = simulateIteration(0, moves:::path) + val move1 = simulateIteration(1, moves:::path) + val move2 = simulateIteration(2, moves:::path) + val move3 = simulateIteration(3, moves:::path) + val move4 = simulateIteration(4, moves:::path) + val move5 = simulateIteration(5, moves:::path) + */ + val move0 = Await.result(Future(simulateIteration(0, moves:::path)), Duration.Inf) + val move1 = Await.result(Future(simulateIteration(1, moves:::path)), Duration.Inf) + val move2 = Await.result(Future(simulateIteration(2, moves:::path)), Duration.Inf) + val move3 = Await.result(Future(simulateIteration(3, moves:::path)), Duration.Inf) + val move4 = Await.result(Future(simulateIteration(4, moves:::path)), Duration.Inf) + val move5 = Await.result(Future(simulateIteration(5, moves:::path)), Duration.Inf) + if move0 == playerId then simulateMove(path:+0) + if move1 == playerId then simulateMove(path:+1) + if move2 == playerId then simulateMove(path:+2) + if move3 == playerId then simulateMove(path:+3) + if move4 == playerId then simulateMove(path:+4) + if move5 == playerId then simulateMove(path:+5) + simulateMove(List()) - 0 - def simulateMove(fieldNumber: Int): Unit = - simulations(fieldNumber - 1) = game.copy() - simulations(fieldNumber - 1).move(fieldNumber-1, playerId) - decisions.getNode(moves).addNewKidAt(simulations(fieldNumber - 1).getScore(playerId), fieldNumber - 1) + def simulateIteration(fieldNumber: Int, way: List[Int]): Int = + val curr = decisions.getNode(way) + val simulationGame = curr.getValue._2.copy() + val whoseNext = simulationGame.move(playerId, fieldNumber) + curr.addNewKidAt((simulationGame.getScore(playerId), simulationGame),fieldNumber) + whoseNext - def addEnemyMove(move: Int): Unit = moves = moves :+ move } diff --git a/Tree.scala b/Tree.scala index 3b2b51c9..9a93742c 100644 --- a/Tree.scala +++ b/Tree.scala @@ -9,34 +9,57 @@ class Node[T](var value: T, val size: Int) { def setValue(newValue: T): Unit = value = newValue + def hasKids: Boolean = index != 0 + def getKids: Array[Node[T]] = kids + def getIndex: Int = index + def addKid(newKid: Node[T]): Unit = kids(index) = newKid - index += 1 + if index == -1 then index = 1 + else index += 1 def addNewKid(value: T): Unit = kids(index) = new Node[T](value, kids.length) + if index == -1 then index = 1 + else index += 1 def addKidAt(newKid: Node[T], index: Int): Unit = kids(index) = newKid + if this.index == 0 then this.index = -1 def addNewKidAt(value: T, index: Int): Unit = kids(index) = new Node[T](value, kids.length) + if this.index == 0 then this.index = -1 + + def findPaths(): Array[(T, List[Int])] = + var res = new Array[(T, List[Int])](0) + def search(curr: Node[T], path: List[Int]): Unit = + if curr.hasKids then + var i = 0 + while(i < size){ + res = res:+(curr.getKids(i).getValue, path:+i) + search(curr.getKids(i), path:+i) + i += 1 + } + search(this,List()) + res } -class Tree(val kidsLimit: Int){ +class MyTree(val kidsLimit: Int, game: Kalaha){ - var root: Node[Int] = Node[Int](0, kidsLimit) + var root: Node[(Int, Kalaha)] = Node[(Int, Kalaha)]((0, game), kidsLimit) - def getNode(path: List[Int]): Node[Int] = + def getNode(path: List[Int]): Node[(Int, Kalaha)] = @tailrec - def iterate(curr: Node[Int], tail: List[Int]): Node[Int] = - tail match - case Nil => curr - case h::t => iterate(curr.getKids(h-1), t) + def iterate(curr: Node[(Int, Kalaha)], tail: List[Int]): Node[(Int, Kalaha)] = + if !curr.hasKids then curr + else tail match + case Nil => curr + case h::t => iterate(curr.getKids(h), t) iterate(root, path) - + } From bb7a9ff77997fc12e6959b23d170a5069cc56d49 Mon Sep 17 00:00:00 2001 From: Szymek Date: Sat, 15 Jan 2022 12:36:44 +0100 Subject: [PATCH 13/16] Polish menu --- Server.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Server.scala b/Server.scala index 07de6f70..ba1e6e83 100644 --- a/Server.scala +++ b/Server.scala @@ -19,7 +19,7 @@ class Server(private val player1: Player,private val player2: Player,private val else movingPlayer = player2 } - + game.printFields(movingPlayer.getId) val winner = game.whoWon() println("GAME IS OVER!") if winner == 0 then println("It is a draw!!!\nCONGRATULATION") From 78eff27c007ad94b7e79c8870eb2ca470519a7fb Mon Sep 17 00:00:00 2001 From: Szymek Date: Sat, 15 Jan 2022 12:37:59 +0100 Subject: [PATCH 14/16] Add field getter --- Kalaha.scala | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Kalaha.scala b/Kalaha.scala index efeababf..3ce235d6 100644 --- a/Kalaha.scala +++ b/Kalaha.scala @@ -94,6 +94,10 @@ class Kalaha(private val field1: Array[Int], private val field2: Array[Int]) { def copy(): Kalaha = new Kalaha(field1.clone(), field2.clone()) + def getField(id: Int): Array[Int] = + if id == 1 then field1 + else field2 + //val nextField1: Int => Array[Int] = id => if id == 1 then field1 else field2 val nextField: Boolean => Array[Int] = value =>if value then field1 else field2 From 5f50521bd809a7b207d8511af8c5a3bf2470c786 Mon Sep 17 00:00:00 2001 From: Szymek Date: Sun, 23 Jan 2022 14:37:57 +0100 Subject: [PATCH 15/16] Add timer and repeir everything --- AIPlayer.scala | 12 +-- Kalaha.scala | 34 ++++---- LogicEngine.scala | 129 +++++++++++++++++++----------- Main.scala | 4 +- Menu.scala | 40 +++++++--- Player.scala | 7 +- RandomPlayer.scala | 22 ++++++ Server.scala | 36 +++++++-- Tree.scala | 191 +++++++++++++++++++++++++++++++++++++++++++-- 9 files changed, 380 insertions(+), 95 deletions(-) create mode 100644 RandomPlayer.scala diff --git a/AIPlayer.scala b/AIPlayer.scala index e6af8ff5..c8202134 100644 --- a/AIPlayer.scala +++ b/AIPlayer.scala @@ -1,13 +1,15 @@ -class AIPlayer(id: Int, game: Kalaha) extends Player(id) { +class AIPlayer(private val id: Int, private val game: Kalaha) extends Player(id) { private val logicEngine = LogicEngine(game, id) override def getId: Int = id - - override def makeMove(): Int = { - logicEngine.makeMove() - + override def makeMove(time: Int): Int = { + + logicEngine.makeMove(time: Int) + } + + override def getEnemyMove(move: Int): Unit = logicEngine.updateEnemyMove(move) } diff --git a/Kalaha.scala b/Kalaha.scala index 3ce235d6..a3796c38 100644 --- a/Kalaha.scala +++ b/Kalaha.scala @@ -74,22 +74,22 @@ class Kalaha(private val field1: Array[Int], private val field2: Array[Int]) { def printFields(id: Int): Unit = { - var playerField = field1 - var enemyField = field2.reverse - - if id == 2 then - playerField = field2 - enemyField = field1.reverse - - println("|---------------Enemy Field---------------|") - println("|Base |--6--|--5--|--4--|--3--|--2--|--1--|") - enemyField.foreach(x => printf("|%5d",x)) - println("|") - println("|-----------------------------------------|") - playerField.foreach(x => printf("|%5d",x)) - println("|") - println("|--1--|--2--|--3--|--4--|--5--|--6--| Base|") - println("|--------------Player Field---------------|") + var playerField = field1 + var enemyField = field2.reverse + + if id == 2 then + playerField = field2 + enemyField = field1.reverse + + println("|---------------Enemy Field---------------|") + println("|Base |--6--|--5--|--4--|--3--|--2--|--1--|") + enemyField.foreach(x => printf("|%5d",x)) + println("|") + println("|-----------------------------------------|") + playerField.foreach(x => printf("|%5d",x)) + println("|") + println("|--1--|--2--|--3--|--4--|--5--|--6--| Base|") + println("|--------------Player Field---------------|") } def copy(): Kalaha = new Kalaha(field1.clone(), field2.clone()) @@ -97,7 +97,7 @@ class Kalaha(private val field1: Array[Int], private val field2: Array[Int]) { def getField(id: Int): Array[Int] = if id == 1 then field1 else field2 - + //val nextField1: Int => Array[Int] = id => if id == 1 then field1 else field2 val nextField: Boolean => Array[Int] = value =>if value then field1 else field2 diff --git a/LogicEngine.scala b/LogicEngine.scala index 60bfe170..2b68deaa 100644 --- a/LogicEngine.scala +++ b/LogicEngine.scala @@ -1,64 +1,105 @@ +import scala.annotation.tailrec import scala.concurrent.{Await, Future} -import scala.concurrent.duration._ +import scala.concurrent.duration.* import scala.concurrent.ExecutionContext.Implicits.global class LogicEngine(val game: Kalaha, val playerId: Int) { var moves: List[Int] = List[Int]() val decisions = new MyTree(6, game) + var bestMove: Int = 0 - def makeMove(): Int = + def updateEnemyMove(move: Int): Unit = moves = moves :+ move - if !decisions.getNode(moves).hasKids then { - decisions.getNode(moves).setValue(game.getScore(playerId), game) + def makeMove(time: Int): Int = { + val start = System.currentTimeMillis() + if decisions.getRoot.getValue._3 == 0 then + if moves.isEmpty then decisions.getRoot.setValue(decisions.getRoot.getValue._1, decisions.getRoot.getValue._2, playerId) + else { + decisions.getRoot.setValue(game.getScore(playerId), game, playerId) + moves = List() + } + var root = decisions.getRoot + moves.foreach(move =>{ + if !root.hasKids then { + fillChildren() + root = root.getKids(move) + } + else root = root.getKids(move) - simulate() - } - var bestMove = -1 - var bestScore = -100 - val simResult = decisions.getNode(moves).findPaths() - simResult.foreach(elem => - if elem._1._1 > bestScore && game.getField(playerId)(elem._2.head) != 0 then { - bestScore = elem._1._1 - bestMove = elem._2.head - }) - moves = moves :+ bestMove + }) + moves = List() + simulate(time*1000, start) + decisions.setRoot(decisions.getRoot.getKids(bestMove)) bestMove + } + + def fillChildren(): Unit ={ + @tailrec + def fillKid(curr: Node, restOfMoves: List[Int]):Unit = + restOfMoves match { + case List() => () + case h::t => + simulateIteration(h, curr, curr.getValue._3) + fillKid(curr.getKids(h),t) + } - def simulate(): Unit = - def simulateMove(path: List[Int]): Unit = - /* - val move0 = simulateIteration(0, moves:::path) - val move1 = simulateIteration(1, moves:::path) - val move2 = simulateIteration(2, moves:::path) - val move3 = simulateIteration(3, moves:::path) - val move4 = simulateIteration(4, moves:::path) - val move5 = simulateIteration(5, moves:::path) - */ + } - val move0 = Await.result(Future(simulateIteration(0, moves:::path)), Duration.Inf) - val move1 = Await.result(Future(simulateIteration(1, moves:::path)), Duration.Inf) - val move2 = Await.result(Future(simulateIteration(2, moves:::path)), Duration.Inf) - val move3 = Await.result(Future(simulateIteration(3, moves:::path)), Duration.Inf) - val move4 = Await.result(Future(simulateIteration(4, moves:::path)), Duration.Inf) - val move5 = Await.result(Future(simulateIteration(5, moves:::path)), Duration.Inf) + def updateBestMove(): Unit = + var choiceMove = (0,0) + var bestScore = -100 + var i = 0 + decisions.getRoot.bestChoice(playerId).foreach((res, next) =>{ + if game.getField(playerId)(i) != 0 then { + if res > bestScore then + bestScore = res + choiceMove = (i, next) + else if choiceMove._2 != playerId && res == bestScore && next == playerId then + bestScore = res + choiceMove = (i, next) + } + i += 1 + }) + bestMove = choiceMove._1 - if move0 == playerId then simulateMove(path:+0) - if move1 == playerId then simulateMove(path:+1) - if move2 == playerId then simulateMove(path:+2) - if move3 == playerId then simulateMove(path:+3) - if move4 == playerId then simulateMove(path:+4) - if move5 == playerId then simulateMove(path:+5) - simulateMove(List()) + def simulate(time: Int, start: Long): Unit = + def simulateMove(curr: Node): Unit = + if (System.currentTimeMillis() - start) < (time* 3 / 4) then { + if curr.hasKids then + curr.getKids.foreach(kid =>{ + simulateMove(kid) + }) + else + val f1 = Future(simulateIteration(0,curr, curr.getValue._3)) + val f2 = Future(simulateIteration(1,curr, curr.getValue._3)) + val f3 = Future(simulateIteration(2,curr, curr.getValue._3)) + val f4 = Future(simulateIteration(3,curr, curr.getValue._3)) + val f5 = Future(simulateIteration(4,curr, curr.getValue._3)) + val f6 = Future(simulateIteration(5,curr, curr.getValue._3)) + if ((System.currentTimeMillis() - start) < (time * 4 / 5)) { + Await.result(f1, Duration.Inf) + Await.result(f2, Duration.Inf) + Await.result(f3, Duration.Inf) + Await.result(f4, Duration.Inf) + Await.result(f5, Duration.Inf) + Await.result(f6, Duration.Inf) + } + else () + } + while((System.currentTimeMillis() - start) < (time* 4 / 5)){ + simulateMove(decisions.getRoot) + decisions.getRoot.countSubtree() + updateBestMove() + } - def simulateIteration(fieldNumber: Int, way: List[Int]): Int = - val curr = decisions.getNode(way) + def simulateIteration(fieldNumber: Int, curr: Node, currPlayerId: Int): Unit = { val simulationGame = curr.getValue._2.copy() - val whoseNext = simulationGame.move(playerId, fieldNumber) - curr.addNewKidAt((simulationGame.getScore(playerId), simulationGame),fieldNumber) - whoseNext - + val whoseNext = simulationGame.move(currPlayerId, fieldNumber) + curr.addNewKidAt((simulationGame.getScore(playerId), simulationGame, whoseNext), fieldNumber) + } + def showTree(): Unit = decisions.print() } diff --git a/Main.scala b/Main.scala index 4e6b895d..b22f16f3 100644 --- a/Main.scala +++ b/Main.scala @@ -1,9 +1,9 @@ object Main { - def main(args: Array[String]): Unit = { val menu = new Menu() menu.run() - } + + } } diff --git a/Menu.scala b/Menu.scala index 8af3542d..23d7c96f 100644 --- a/Menu.scala +++ b/Menu.scala @@ -2,6 +2,8 @@ import scala.io.StdIn.* class Menu() { + val time = 30 + def run(): Unit = val game = new Kalaha() println("KALAHA") @@ -11,22 +13,42 @@ class Menu() { println("4.Exit") print("Choice: ") - val choice = readInt() + var choice = readInt() if choice == 1 then - val p1 = new Player(1) - val p2 = new AIPlayer(2, game) - val server = new Server(p1, p2, game) - server.play() + choice = difficulty(game) if choice == 2 then val p1 = new Player(1) val p2 = new Player(2) - val server = new Server(p1, p2, game) - server.play() + val server = new Server(p1, p2, game, time) + choice = server.play() if choice == 3 then val p1 = new AIPlayer(1, game) val p2 = new AIPlayer(2, game) - val server = new Server(p1, p2, game) - server.play() + val server = new Server(p1, p2, game, time) + choice = server.play() if choice != 4 then run() + + def difficulty(game: Kalaha): Int = + println("Select difficulty level:") + println("1.Easy") + println("2.Hard") + print("Choice: ") + + var choice = readInt() + + if choice == 1 then + val p1 = new Player(1) + val p2 = new RandomPlayer(2, game) + val server = new Server(p1, p2, game, time) + println("You are Player 1") + choice = server.play() + if choice == 2 then + val p1 = new Player(1) + val p2 = new AIPlayer(2, game) + val server = new Server(p1, p2, game, time) + println("You are Player 1") + choice = server.play() + + choice } diff --git a/Player.scala b/Player.scala index f759f015..8cfb5d3a 100644 --- a/Player.scala +++ b/Player.scala @@ -1,14 +1,15 @@ import scala.io.StdIn.* -class Player(id: Int) { +class Player(private val id: Int) { - def makeMove(): Int ={ + def makeMove(time: Int): Int ={ print("\nSelect field number: ") val field = readInt() field - 1 - } def getId: Int = id + + def getEnemyMove(move: Int): Unit = () } diff --git a/RandomPlayer.scala b/RandomPlayer.scala new file mode 100644 index 00000000..c28d4a8e --- /dev/null +++ b/RandomPlayer.scala @@ -0,0 +1,22 @@ +import scala.io.StdIn.readInt +import scala.util.Random + +class RandomPlayer(private val id: Int, private val game: Kalaha) extends Player(id) { + + override def makeMove(time: Int): Int ={ + + var isOk = false + var choice = 0 + while(!isOk){ + + choice = Random.nextInt(6) + if game.getField(id)(choice) != 0 then isOk = true + + } + choice + } + + override def getId: Int = id + + override def getEnemyMove(move: Int): Unit = () +} diff --git a/Server.scala b/Server.scala index ba1e6e83..d92fc219 100644 --- a/Server.scala +++ b/Server.scala @@ -1,20 +1,41 @@ import scala.util.Random +import scala.concurrent.{Await, Future} +import scala.concurrent.duration._ +import scala.concurrent.ExecutionContext.Implicits.global +import java.util.concurrent.TimeUnit -class Server(private val player1: Player,private val player2: Player,private val game: Kalaha): +class Server(private val player1: Player,private val player2: Player,private val game: Kalaha,private val time: Int): - def play(): Unit = + def play(): Int = var movingPlayer:Player = player1 - var next = 0 + var next = 1 val rng = Random.nextInt() if rng % 2 == 1 then movingPlayer = player2 + next = 2 - while(!game.isOver()){ + while(!game.isOver() ){ println("Player "+ movingPlayer.getId + " moving") game.printFields(movingPlayer.getId) - val choice = movingPlayer.makeMove() - next = game.move(movingPlayer.getId, choice) - println("Field " + (choice + 1) + " chosen") + try{ + val FutureChoice = Future(movingPlayer.makeMove(time)) + val choice = Await.result(FutureChoice,Duration(time, TimeUnit.SECONDS)) + + if choice < 0 || choice > 5 then + println(choice + " is a wrong move! You loose your turn") + if next == 1 then next = 2 + else next = 1 + else + if next == 1 then player2.getEnemyMove(choice) + else player1.getEnemyMove(choice) + next = game.move(movingPlayer.getId, choice) + println("Field " + (choice + 1) + " chosen") + } + catch{ + case e:java.util.concurrent.TimeoutException => println("Czas minal!") + return 4 + + } if next == 1 then movingPlayer = player1 else movingPlayer = player2 @@ -24,3 +45,4 @@ class Server(private val player1: Player,private val player2: Player,private val println("GAME IS OVER!") if winner == 0 then println("It is a draw!!!\nCONGRATULATION") else println("\nand the winner is...\nPlayer " + winner + "!!!") + 0 diff --git a/Tree.scala b/Tree.scala index 9a93742c..03685446 100644 --- a/Tree.scala +++ b/Tree.scala @@ -1,5 +1,5 @@ import scala.annotation.tailrec - +/* class Node[T](var value: T, val size: Int) { private val kids = new Array[Node[T]](size) @@ -44,22 +44,197 @@ class Node[T](var value: T, val size: Int) { i += 1 } search(this,List()) - res + res +} +*/ + +def aToPowerb(a: Int, b:Int): Int = + @tailrec + def iterator(result: Int, rest: Int): Int = + rest match + case 1 => result + case 0 => 1 + case _ => iterator(result*a, rest - 1) + iterator(a, b) + +class Node(var value: (Int, Kalaha, Int), val size: Int) { + + private val kids = new Array[Node](size) + private var index = 0 + + def getValue:(Int, Kalaha, Int) = value + + def setValue(newValue: (Int, Kalaha, Int)): Unit = value = newValue + + def hasKids: Boolean = index != 0 + + def getKids: Array[Node] = kids + + def getIndex: Int = index + + def hasAllKids: Boolean = index == size + + def addKid(newKid: Node): Unit = + kids(index) = newKid + if index == -1 then index = 1 + else index += 1 + + def addNewKid(value: (Int, Kalaha, Int)): Unit = + kids(index) = new Node(value, kids.length) + if index == -1 then index = 1 + else index += 1 + + def addKidAt(newKid: Node, index: Int): Unit = + kids(index) = newKid + if this.index == 0 then this.index = -1 + + def addNewKidAt(value: (Int, Kalaha, Int), index: Int): Unit = + kids(index) = new Node(value, kids.length) + if this.index == 0 then this.index = -1 + + def findPaths(): Array[((Int, Kalaha, Int), List[Int])] = + var res = new Array[((Int, Kalaha, Int), List[Int])](0) + def search(curr: Node, path: List[Int]): Unit = + if curr.hasKids then + var i = 0 + while(i < size){ + res = res:+(curr.getKids(i).getValue, path:+i) + search(curr.getKids(i), path:+i) + i += 1 + } + search(this,List()) + res + + def maxKid(): Int = + var index = 0 + var i = 0 + var maxValue = kids(0).value._1 + while (i < kids.length){ + + if kids(i).value._1 > maxValue then { + index = i + maxValue = kids(i).value._1 + } + i += 1 + } + index + + def minKid(): Int = + var index = 0 + var i = 0 + var minValue = kids(0).value._1 + while (i < kids.length){ + + if kids(i).value._1 < minValue then { + index = i + minValue = kids(i).value._1 + } + i += 1 + + } + index + + def chooseBest(mine: Boolean): Int = + if mine then maxKid() + else minKid() + + def countNodes(): Int = + @tailrec + def count(stack: List[Node], res: Int): Int = + stack match { + case List() => res + case h::t => + if h.hasKids then count(h.kids.toList:::t, res+1) + else count(t, res+1) + } + count(List(this), 0) + + def fullHeight(): Int = + val nodesAmount = countNodes() + @tailrec + def counter(rest: Int, power: Int): Int = + if rest < 0 then power - 1 + else counter(rest - aToPowerb(size, power), power + 1) + counter(nodesAmount,0) + + def bestChoice(playerId: Int): Array[(Int, Int)] = + val res = Array.fill(6)((0, 0)) + + def iterator(curr: Node, index: Int, depth: Int): Unit = + if curr.hasKids && depth >= 0 then { + res(index) = (curr.chooseBest(curr.value._3 == playerId), curr.value._3) + curr.kids.foreach(kid => { + iterator(kid, index, depth - 1) + }) + } + + var i = 0 + val depth: Int = fullHeight() + kids.foreach(kid => { + iterator(kid, i, depth - 2) + i += 1 + }) + res + + /* + def bestChoice2(playerId: Int): Array[(Int, Int)] = + val res = Array((kids(0).value._1, kids(0).value._3), (kids(1).value._1, kids(1).value._3), (kids(2).value._1, kids(2).value._3), (kids().value._1, kids(3).value._3), (kids(4).value._1, kids(4).value._3), (kids(5).value._1, kids(5).value._3)) + def iterator(curr: Node, index: Int): Unit = + if curr.hasKids then { + res(index) = (curr.chooseBest(curr.value._3 == playerId), curr.value._3) + curr.kids.foreach(kid =>{ + iterator(kid, index) + }) + } + var i = 0 + kids.foreach(kid =>{ + iterator(kid, i) + i += 1 + }) + res +*/ + + def countSubtree(): Unit = + def kidCount(): Int = + var res = 0 + if hasAllKids then + kids.foreach(kid => { + kid.countSubtree() + res = res + kid.value._1 + }) + res / kids.length + value = (value._1 + kidCount(), value._2, value._3) + + def print(): Unit = printf("%d",value._1) } class MyTree(val kidsLimit: Int, game: Kalaha){ - var root: Node[(Int, Kalaha)] = Node[(Int, Kalaha)]((0, game), kidsLimit) + var root: Node = Node((0, game, 0), kidsLimit) - def getNode(path: List[Int]): Node[(Int, Kalaha)] = + def getRoot: Node = root + + def setRoot(newRoot: Node): Unit = root = newRoot + + def getNode(path: List[Int]): Node = @tailrec - def iterate(curr: Node[(Int, Kalaha)], tail: List[Int]): Node[(Int, Kalaha)] = + def iterate(curr: Node, tail: List[Int]): Node = if !curr.hasKids then curr else tail match - case Nil => curr - case h::t => iterate(curr.getKids(h), t) + case Nil => curr + case h::t => iterate(curr.getKids(h), t) iterate(root, path) - + def print(): Unit = + @tailrec + def iterator(queue: List[Node]): Unit ={ + queue match { + case List() => () + case h::t => + printf("%d ",h.getValue._1) + iterator(t:::h.getKids.toList) + } + } + iterator(List(root)) } From 81bfeeb7129f48904d39a60321214b643b16f19f Mon Sep 17 00:00:00 2001 From: Szymek Date: Sun, 23 Jan 2022 16:49:42 +0100 Subject: [PATCH 16/16] Clean tree --- Tree.scala | 82 +++++------------------------------------------------- 1 file changed, 7 insertions(+), 75 deletions(-) diff --git a/Tree.scala b/Tree.scala index 03685446..8a1988d3 100644 --- a/Tree.scala +++ b/Tree.scala @@ -1,52 +1,4 @@ import scala.annotation.tailrec -/* -class Node[T](var value: T, val size: Int) { - - private val kids = new Array[Node[T]](size) - private var index = 0 - - def getValue:T = value - - def setValue(newValue: T): Unit = value = newValue - - def hasKids: Boolean = index != 0 - - def getKids: Array[Node[T]] = kids - - def getIndex: Int = index - - def addKid(newKid: Node[T]): Unit = - kids(index) = newKid - if index == -1 then index = 1 - else index += 1 - - def addNewKid(value: T): Unit = - kids(index) = new Node[T](value, kids.length) - if index == -1 then index = 1 - else index += 1 - - def addKidAt(newKid: Node[T], index: Int): Unit = - kids(index) = newKid - if this.index == 0 then this.index = -1 - - def addNewKidAt(value: T, index: Int): Unit = - kids(index) = new Node[T](value, kids.length) - if this.index == 0 then this.index = -1 - - def findPaths(): Array[(T, List[Int])] = - var res = new Array[(T, List[Int])](0) - def search(curr: Node[T], path: List[Int]): Unit = - if curr.hasKids then - var i = 0 - while(i < size){ - res = res:+(curr.getKids(i).getValue, path:+i) - search(curr.getKids(i), path:+i) - i += 1 - } - search(this,List()) - res -} -*/ def aToPowerb(a: Int, b:Int): Int = @tailrec @@ -157,42 +109,22 @@ class Node(var value: (Int, Kalaha, Int), val size: Int) { else counter(rest - aToPowerb(size, power), power + 1) counter(nodesAmount,0) - def bestChoice(playerId: Int): Array[(Int, Int)] = - val res = Array.fill(6)((0, 0)) - - def iterator(curr: Node, index: Int, depth: Int): Unit = - if curr.hasKids && depth >= 0 then { + def bestChoice(playerId: Int): Array[(Int, Int)] = + val res = Array.fill(6)((0, 0)) + def iterator(curr: Node, index: Int, depth: Int): Unit = + if curr.hasKids && depth >= 0 then { res(index) = (curr.chooseBest(curr.value._3 == playerId), curr.value._3) curr.kids.foreach(kid => { iterator(kid, index, depth - 1) }) } - - var i = 0 - val depth: Int = fullHeight() - kids.foreach(kid => { - iterator(kid, i, depth - 2) - i += 1 - }) - res - - /* - def bestChoice2(playerId: Int): Array[(Int, Int)] = - val res = Array((kids(0).value._1, kids(0).value._3), (kids(1).value._1, kids(1).value._3), (kids(2).value._1, kids(2).value._3), (kids().value._1, kids(3).value._3), (kids(4).value._1, kids(4).value._3), (kids(5).value._1, kids(5).value._3)) - def iterator(curr: Node, index: Int): Unit = - if curr.hasKids then { - res(index) = (curr.chooseBest(curr.value._3 == playerId), curr.value._3) - curr.kids.foreach(kid =>{ - iterator(kid, index) - }) - } var i = 0 - kids.foreach(kid =>{ - iterator(kid, i) + val depth: Int = fullHeight() + kids.foreach(kid => { + iterator(kid, i, depth - 2) i += 1 }) res -*/ def countSubtree(): Unit = def kidCount(): Int =