Skip to content
Merged
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
71 changes: 53 additions & 18 deletions src/Game.effekt
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,36 @@ def playGame(): Unit = {
}


/*
* Returns the chosen difficulty
* Input : None
* Output : Int
*/
def chooseDifficulty(): Int / {WrongInput, ExitGame, PrintHelp} = {
val validOptions = [
("1", "Easy"),
("2", "Medium"),
("3", "Hard"),
("h", "Read rules"),
("q", "Exit game")
]
val options: String = "[" ++ validOptions.map { e => e.first ++ " - " ++ e.second }
.join(", ") ++ "]"

println("Choose the Difficulty:\n" ++ options)
val side = consoleInput()
side match {
case '1' => 0
case '2' => 1
case '3' => 2
case 'h' => do PrintHelp(GameRules()); chooseDifficulty()
case 'q' => do ExitGame()
case _ =>
do WrongInput("Invalid Input!\nAvailable options: " ++ options ++ "\n")
chooseDifficulty()
}
}

/*
* Returns the Cell Type of the player
* Input : None
Expand Down Expand Up @@ -87,7 +117,7 @@ def playGame(): Unit = {
case 'q' => do ExitGame()
case 'h' => do PrintHelp(Board()); chooseSmallBoard(gameBoard)
case _ =>
if (isDigit(input)) {
if (isDigit(input) && input != '0') {
with on[WrongFormat].panic
val smallBoardNumber: Int = digitValue(input).getOrElse{ 20 } - 1
if (checkAvailableCell(gameBoard.smallCopy, smallBoardNumber)){
Expand Down Expand Up @@ -115,17 +145,16 @@ def playGame(): Unit = {

println("Choose the cell inside the chosen Small Board:\n[h - help, q - exit]")
val input: Char = consoleInput()

input match {
case 'q' => do ExitGame()
case 'h' => do PrintHelp(Board()); chooseCell(gameBoard, smallBoardNumber)
case _ =>
if (isDigit(input)) {
if (isDigit(input) && input != '0') {
with on[WrongFormat].panic
val cellNumber: Int = digitValue(input).getOrElse {20} - 1
if(checkAvailableCell(gameBoard.bigBoard.get(smallBoardNumber), cellNumber)){
cellNumber
}else{
}else{
do WrongInput("This Cell is Already occupied. Please type a number for a valid cell.")
chooseCell(gameBoard, smallBoardNumber)
}
Expand All @@ -143,7 +172,7 @@ def playGame(): Unit = {
* Input : player: Cell, gameMode: GameMode
* Output : Unit
*/
def gameLoop(player: Cell, gameMode: GameMode): Unit / {WrongInput, PrintHelp, ExitGame} = {
def gameLoop(player: Cell, gameMode: GameMode, difficulty: Int): Unit / {WrongInput, PrintHelp, ExitGame} = {
var endGame: Bool = false

var gameBoard: GameBoard = generateNewBigBoard()
Expand Down Expand Up @@ -174,13 +203,12 @@ def playGame(): Unit = {
// The Move Phase
printGameScreen(gameBoard, currentPlayer)
if (gameMode is Computer()) {
if (isComputerTurn(currentPlayer, computer)) currentCell = computerPlay(gameBoard.bigBoard.get(currentSmallBoard), computer)
if (isComputerTurn(currentPlayer, computer)) currentCell = advancedComputerPlay(gameBoard, currentSmallBoard, computer, difficulty)
else currentCell = chooseCell(gameBoard, currentSmallBoard)
}else currentCell = chooseCell(gameBoard, currentSmallBoard)

gameBoard = checkNewCell(gameBoard, currentSmallBoard, currentCell, currentPlayer)

// val finish = checkWinSituation(gameBoard.bigBoard.get(currentSmallBoard), currentPlayer) match {
val finish: Finish = checkWinSituation(gameBoard.bigBoard.get(currentSmallBoard), currentPlayer)
if (finish.isFinished) {
finish.state match {
Expand All @@ -199,6 +227,17 @@ def playGame(): Unit = {

// After The Player's Move

// Verify if the game has been won
val finishGame: Finish = checkWinSituation(gameBoard.smallCopy, currentPlayer)
if (finishGame.isFinished) {
finishGame.state match {
case Win() => println(playerString(currentPlayer) ++ ", You won the game!")
case Lose() => println(playerString(currentPlayer) ++ ", You lost the game!")
}
consoleInput()
endGame = true
}

// Change the Current Small Board
if (checkAvailableCell(gameBoard.smallCopy, currentCell)){
gameBoard = deactivateSmallBoard(gameBoard, currentSmallBoard)
Expand All @@ -210,21 +249,13 @@ def playGame(): Unit = {
}
currentCell = 10

// Verify if the game has been won
val finishGame: Finish = checkWinSituation(gameBoard.smallCopy, currentPlayer)
if (finishGame.isFinished) {
finishGame.state match {
case Win() => println(playerString(currentPlayer) ++ ", You won the game!")
case Lose() => println(playerString(currentPlayer) ++ ", You lost the game!")
}
consoleInput()
endGame = true
}
// Change The Current Player
if(currentPlayer is Cross()) currentPlayer = Nought()
else currentPlayer = Cross()

printGameScreen(gameBoard, currentPlayer)


}
}

Expand All @@ -238,7 +269,11 @@ def playGame(): Unit = {
while (play) {
try{
// Starting the game
gameLoop(chooseSide(), chooseGameMode())
val gm = chooseGameMode()
gm match {
case Local() => gameLoop(chooseSide(), gm, 0)
case Computer() => gameLoop(chooseSide(), gm, chooseDifficulty())
}
} with WrongInput { msg =>
println(msg)
resume(())
Expand Down
2 changes: 1 addition & 1 deletion src/Generate.effekt
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def deactivateSmallBoard(gameBoard: GameBoard, smallBoardNumber: Int): GameBoard

var newSmallBoard: SmallBoard = newBigBoard.get(smallBoardNumber)
var counter: Int = 0
// newGameBoardSmallCopy = newGameBoardSmallCopy.replace(smallBoardNumber, Empty())
if (newGameBoardSmallCopy.get(smallBoardNumber) is Active()) newGameBoardSmallCopy = newGameBoardSmallCopy.replace(smallBoardNumber, Empty())

newSmallBoard.foreach {cell =>
cell match {
Expand Down
140 changes: 140 additions & 0 deletions src/Opponent.effekt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,142 @@ module src/Opponent
import src/lib
import src/Generate

def WIN_SCORE(): Int = 1000
def LOSE_SCORE(): Int = -1000
def DRAW_SCORE(): Int = 0


/*
* Advanced Computer Play Logic with difficulty levels
* Input: gameBoard: GameBoard, smallBoardNumber: Int, computer: Cell, difficulty: Int
* Output: Int
*/
def advancedComputerPlay(gameBoard: GameBoard, smallBoardNumber: Int, computer: Cell, difficulty: Int): Int = {
with on[OutOfBounds].panic
val currentBoard: List[Cell] = gameBoard.bigBoard.get(smallBoardNumber)

// Use existing strategy for easy mode (difficulty = 0)
if (difficulty == 0) {
computerPlay(currentBoard, computer)
} else {
var bestMove = -1
var bestScore = LOSE_SCORE()
val maxDepth = if (difficulty == 1) 3 else 5 // Medium uses depth 3, Hard uses depth 5

// try each possible move
build(9){ i => i }.foreach { move =>
if (checkAvailableCell(currentBoard, move)) {
var simulatedGame = gameBoard
simulatedGame = checkNewCell(simulatedGame, smallBoardNumber, move, computer)
val score = minimax(simulatedGame, move, maxDepth, false, computer)

if (score > bestScore) {
bestScore = score
bestMove = move
}
}
}
// Fallback to basic strategy if no good move found
if (bestMove == -1) {
computerPlay(currentBoard, computer)
} else {
bestMove
}
}
}


/*
* Minimax algorithm implementation
* Input: gameBoard: GameBoard, nextBoard: Int, depth: Int, isMaximizing: Bool, computer: Cell
* Output: Int (score)
*/
def minimax(gameBoard: GameBoard, nextBoard: Int, depth: Int, isMaximizing: Bool, computer: Cell): Int = {
with on[OutOfBounds].panic

val opponent = if (computer is Cross()) Nought() else Cross()
val currentPlayer = if (isMaximizing) computer else opponent
var retValue: Int = -1

// Check terminal states
val gameState = checkWinSituation(gameBoard.smallCopy, currentPlayer)

if (gameState.isFinished) {
retValue = evaluateGameState(gameState, computer)
}


if (depth == 0 && retValue == -1) {
retValue = evaluatePosition(gameBoard, computer)
}
val currentBigBoard: BigBoard = gameBoard.bigBoard
val currentBoard = currentBigBoard.get(nextBoard)

if (retValue == -1) {
if (isMaximizing) {
var maxScore = LOSE_SCORE()
build(9){ i => i}.foreach { move =>
if (checkAvailableCell(currentBoard, move)) {
var newGame = gameBoard
newGame = checkNewCell(newGame, nextBoard, move, computer)
val score = minimax(newGame, move, depth - 1, false, computer)
maxScore = if (score > maxScore) score else maxScore
}
}
retValue = maxScore
} else {
var minScore = WIN_SCORE()
build(9){ i => i}.foreach { move =>
if (checkAvailableCell(currentBoard, move)) {
var newGame = gameBoard
newGame = checkNewCell(newGame, nextBoard, move, opponent)
val score = minimax(newGame, move, depth - 1, true, computer)
minScore = if (score < minScore) score else minScore
}
}
retValue = minScore
}
}
retValue
}


/*
* Evaluates non-terminal positions
*/
def evaluatePosition(gameBoard: GameBoard, computer: Cell): Int = {
with on[OutOfBounds].panic
var score = 0
// Score small boards
build(9){ i => i }.foreach { sm =>
val currentSmallBoard = gameBoard.bigBoard.get(sm)
val boardState = checkWinSituation(currentSmallBoard, computer)
score = score + evaluateGameState(boardState, computer) / 10
}

// Bonus for controlling center board
if (gameBoard.smallCopy.get(4) is computer) {
score = score + 50
}
score
}

/*
* Evaluates final game states
*/
def evaluateGameState(state: Finish, computer: Cell): Int = {
if (state.isFinished) {
state.state match {
case Win() =>
if (state.player is computer) WIN_SCORE()
else LOSE_SCORE()
case _ => DRAW_SCORE()
}
} else {
0
}
}


/*
* Checks if it's a computer turn right now
Expand All @@ -17,6 +153,10 @@ def isComputerTurn(currentPlayer: Cell, computer: Cell): Bool =
}


/// ################
/// SIMPLE ALGORITHM
/// ################

/*
* Computer Play Logic
* Strategy
Expand Down
Loading
Loading