Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

L8 #442

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open

L8 #442

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
60 changes: 60 additions & 0 deletions ComputerPlayer.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import scala.concurrent.{Await, Future}
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration.Duration

class ComputerPlayer extends Player:
var id: Int = _

def messageColor = if id == 1 then Console.GREEN else if id == 2 then Console.BLUE else Console.WHITE

def log(message: String) = println(s"$messageColor ComPlayer$id: $message ${Console.RESET}")

def opponentId = if id == 1 then 2 else 1

override def notifyOfIllegalMovement(): Unit = ???

override def notifyOfGameInterrupted(playerWonId: Int): Unit = log(":)")

override def sendResult(scores: (Int, Int)): Unit =
val (player1Scores, player2Scores) = scores
println(s"Player1: $player1Scores, Player2: $player2Scores")

def boardStaticEvaluation(board: KalahaBoard) =
val (player1Scores, player2Scores) = board.scores
val scoreDiff = player1Scores - player2Scores
if id == 1 then scoreDiff else -scoreDiff

def minimax(board: KalahaBoard, depth: Int, playerId: Int): Int =
if depth == 0 || board.endGame() then
boardStaticEvaluation(board)
else
if playerId == id then
var maxEval = Integer.MIN_VALUE
for(i <- 0 until board.housesPerSide if board.isMoveLegal(playerId, i))
val nextBoard = board.copy()
val nextPlayer = nextBoard.makeAMove(playerId, i)
val eval = minimax(nextBoard, depth - 1, nextPlayer)
if eval > maxEval then maxEval = eval

maxEval
else
var minEval = Integer.MAX_VALUE
for(i <- 0 until board.housesPerSide if board.isMoveLegal(playerId, i))
val nextBoard = board.copy()
val nextPlayer = nextBoard.makeAMove(playerId, i)
val eval = minimax(nextBoard, depth - 1, nextPlayer)
if eval < minEval then minEval = eval

minEval

override def requestMove(board: KalahaBoard): Int =
val futures = for(i <- 0 until board.housesPerSide if board.isMoveLegal(id, i)) yield Future {
val nextBoard = board.copy()
val nextPlayer = nextBoard.makeAMove(id, i)
(i, minimax(nextBoard, 10, nextPlayer))
}
val scores = Await.result(Future.sequence(futures), Duration.Inf)
// val scores = futures
val move = scores.maxBy(_._2)._1
log(s"moving $move")
move
37 changes: 37 additions & 0 deletions HumanConsolePlayer.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import scala.io.StdIn

class HumanConsolePlayer extends Player:
var id: Int = _

def messageColor = if id == 1 then Console.GREEN else if id == 2 then Console.BLUE else Console.WHITE

def log(message: String) = println(s"$messageColor Player$id: $message ${Console.RESET}")

override def notifyOfIllegalMovement(): Unit =
log("This move is illegal, try again")

override def notifyOfGameInterrupted(playerWonId: Int): Unit =
if playerWonId != id then
log("You have ran out of time, you lost!")
else
log("your opponent has ran out of time, you won!")

override def sendResult(scores: (Int, Int)): Unit =
val (player1Score, player2Score) = scores
val scoreDiffrence = player1Score - player2Score
if scoreDiffrence == 0 then
log("Draw!")
else
val playerWon = if scoreDiffrence > 0 then 1 else 2
if playerWon == this.id then
log("You won! c:")
else
log("You lost! :c")

log(s"player1: $player1Score - player2: $player2Score")

override def requestMove(board: KalahaBoard): Int =
board.printBoard()
log(s"Choose a house: (0-${board.housesPerSide - 1})")
StdIn.readInt()

111 changes: 111 additions & 0 deletions KalahaBoard.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import scala.annotation.tailrec

class IllegalMoveException(message: String) extends Exception(message)
class KalahaBoard(
val housesPerSide: Int,
val initialSeedAmount: Int,
val player1Houses: Array[House],
val player2Houses: Array[House],
val player1Store: Store,
val player2Store: Store
):

def this(housesPerSide: Int, initialSeedAmount: Int) =
this(
housesPerSide,
initialSeedAmount,
Array.tabulate(housesPerSide)(i => House(initialSeedAmount, 1, i)),
Array.tabulate(housesPerSide)(i => House(initialSeedAmount, 2, i)),
Store(0, 1),
Store(0, 2)
)

def copy() =
new KalahaBoard(
housesPerSide,
initialSeedAmount,
player1Houses.clone().map(house => house.copy()),
player2Houses.clone().map(house => house.copy()),
player1Store.copy(),
player2Store.copy()
)

@tailrec
private def nextPitToSow(playerId: Int)(currentPit: Pit): Pit =
val maxHouseIndex = housesPerSide - 1
currentPit match
case Store(_, 1) => player2Houses(0)
case Store(_, 2) => player1Houses(0)
case House(_, 1, `maxHouseIndex`) => if playerId == 1 then player1Store else nextPitToSow(playerId)(player1Store)
case House(_, 1, i) => player1Houses(i + 1)
case House(_, 2, `maxHouseIndex`) => if playerId == 2 then player2Store else nextPitToSow(playerId)(player2Store)
case House(_, 2, i) => player2Houses(i + 1)

def houseByIds(playerId: Int, houseIndex: Int) = (if playerId == 1 then player1Houses else player2Houses)(houseIndex)

def getOppositeHouse(house: House) =
val House(_, playerId, index) = house
(if playerId == 1 then player2Houses else player1Houses)(housesPerSide - index - 1)

def getOppositePlayerId(playerId: Int) = if playerId == 1 then 2 else 1

def scores = (player1Store.seeds, player2Store.seeds)

def isMoveLegal(playerId: Int, houseIndex: Int) =
if houseIndex < 0 || houseIndex > housesPerSide - 1 then
false
else
val House(seeds, _, _) = houseByIds(playerId, houseIndex)
seeds > 0

//returns id of player that will be moving next
def makeAMove(currentPlayer: Int, chosenMove: Int) =
if !isMoveLegal(currentPlayer, chosenMove) then throw new IllegalMoveException(s"house $chosenMove is empty")
val chosenHouse = houseByIds(currentPlayer, chosenMove)
val seedsToSow = chosenHouse.clear()
@tailrec
def sow(currentPit: Pit, seedsLeft: Int): Int =
currentPit.increment()
if seedsLeft > 1 then
sow(nextPitToSow(currentPlayer)(currentPit), seedsLeft - 1)
else
currentPit match
case Store(_, _) => currentPlayer
case House(1, `currentPlayer`, _) =>
val oppositeHouse = getOppositeHouse(currentPit.asInstanceOf[House])
val playersStore = if currentPlayer == 1 then player1Store else player2Store
playersStore.add(oppositeHouse.clear() + currentPit.clear())
getOppositePlayerId(currentPlayer)
case _ => getOppositePlayerId(currentPlayer)


sow(nextPitToSow(currentPlayer)(chosenHouse), seedsToSow)

def collectAllSeeds(playerId: Int) =
val playerHouses = if playerId == 1 then player1Houses else player2Houses
val allSeeds = (
for(
i <- playerHouses
) yield i.clear()
).sum
(if playerId == 1 then player1Store else player2Store).add(allSeeds)

def endGame(): Boolean =
val canPlayer1Move = !player1Houses.forall(house => house.seeds == 0)
if !canPlayer1Move then collectAllSeeds(2)
val canPlayer2Move = !player2Houses.forall(house => house.seeds == 0)
if !canPlayer2Move then collectAllSeeds(1)
!canPlayer1Move || !canPlayer2Move

def printBoard() =
val stringBuilder = new StringBuilder()
stringBuilder ++= Console.WHITE
stringBuilder += ' ' ++= ((housesPerSide - 1) to 0 by -1).mkString(" ") += '\n'
stringBuilder ++= Console.BLUE
stringBuilder += '(' ++= player2Houses.map(_.seeds).reverse.mkString(")(") += ')' += '\n'
stringBuilder += ' '++= player2Store.seeds.toString ++= "\t\t\t\t" ++= Console.GREEN ++= player1Store.seeds.toString += '\n'
stringBuilder += '(' ++= player1Houses.map(_.seeds).mkString(")(") += ')' += '\n'
stringBuilder ++= Console.WHITE
stringBuilder += ' ' ++= (0 until housesPerSide).mkString(" ")
stringBuilder ++= Console.RESET
println(stringBuilder.result())
31 changes: 31 additions & 0 deletions Main.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import scala.annotation.tailrec
import scala.io.StdIn

object Main:
def main(args: Array[String]): Unit =
userInterfaceLoop()


@tailrec
def userInterfaceLoop(): Unit =
print("Choose mode (1 - comp vs comp, 2 - comp vs player, 3 - player vs player): ")
val mode = StdIn.readInt()
if mode < 1 || mode > 3 then
println("there is no mode like that")
userInterfaceLoop()
else
val players =
if mode == 1 then
(new ComputerPlayer(), new ComputerPlayer())
else if mode == 2 then
(new HumanConsolePlayer(), new ComputerPlayer())
else
(new HumanConsolePlayer(), new HumanConsolePlayer())

val server = new Server(players._1, players._2)
server.play(6, 4)
print("Do you want to play again? (y/n) : ")
if StdIn.readBoolean() then
userInterfaceLoop()
else
()
10 changes: 10 additions & 0 deletions Pit.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
sealed trait Pit:
var seeds: Int
def increment(): Unit = this.seeds += 1
def add(amount: Int): Unit = this.seeds += amount
def clear(): Int =
val t = this.seeds
this.seeds = 0
t
case class House(var seeds: Int, ownerId: Int, index: Int) extends Pit
case class Store(var seeds: Int, ownerId: Int) extends Pit
7 changes: 7 additions & 0 deletions Player.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
trait Player:
var id: Int
def requestMove(board: KalahaBoard): Int
def notifyOfIllegalMovement(): Unit
def notifyOfGameInterrupted(playerWonId: Int): Unit
def sendResult(scores: (Int, Int)): Unit

34 changes: 34 additions & 0 deletions Server.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import java.util.concurrent.TimeoutException
import scala.concurrent.{Await, Future}
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration.DurationInt
class Server(val player1: Player, val player2: Player):
player1.id = 1
player2.id = 2

def oppositePlayer(player: Player) = if player.id == 1 then player2 else player1

def play(housesPerSide: Int, initialSeedAmount: Int) =
val gameBoard = new KalahaBoard(housesPerSide, initialSeedAmount)
var currentPlayer = player1
try
while !gameBoard.endGame() do
val moveIndex = Await.result(Future {
var move = currentPlayer.requestMove(gameBoard.copy())
while !gameBoard.isMoveLegal(currentPlayer.id, move) do
println(s"ILLEGAL MOVE $move by player ${currentPlayer.id}")
gameBoard.printBoard()
currentPlayer.notifyOfIllegalMovement()
move = currentPlayer.requestMove(gameBoard.copy())

move
}, 120.seconds)
val nextPlayerId = gameBoard.makeAMove(currentPlayer.id, moveIndex)
currentPlayer = if nextPlayerId == 1 then player1 else player2

player1.sendResult(gameBoard.scores)
player2.sendResult(gameBoard.scores)
catch
case e: TimeoutException =>
player1.notifyOfGameInterrupted(oppositePlayer(currentPlayer).id)
player2.notifyOfGameInterrupted(oppositePlayer(currentPlayer).id)