diff --git a/.idea/misc.xml b/.idea/misc.xml
index 1c92083..1572df4 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -8,7 +8,7 @@
-
+
\ No newline at end of file
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
index 2d7ff3e..1b770d6 100644
--- a/.idea/workspace.xml
+++ b/.idea/workspace.xml
@@ -4,10 +4,11 @@
-
-
+
+
+
@@ -35,12 +36,12 @@
"assignee": "BAFGdeJong"
}
}
- {
- "selectedUrlAndAccountId": {
- "url": "git@github.com:2OOP/pism_ttt.git",
- "accountId": "7694f583-f911-4763-8185-8ea3ed608804"
+
+}]]>
@@ -56,27 +57,28 @@
- {
- "keyToString": {
- "Application.Main.executor": "Run",
- "JUnit.MinMaxTicTacToeTest.executor": "Run",
- "ModuleVcsDetector.initialDetectionPerformed": "true",
- "RunOnceActivity.ShowReadmeOnStart": "true",
- "RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager": "true",
- "RunOnceActivity.git.unshallow": "true",
- "git-widget-placeholder": "Michiel",
- "node.js.detected.package.eslint": "true",
- "node.js.detected.package.tslint": "true",
- "node.js.selected.package.eslint": "(autodetect)",
- "node.js.selected.package.tslint": "(autodetect)",
- "nodejs_package_manager_path": "npm",
- "project.structure.last.edited": "Modules",
- "project.structure.proportion": "0.0",
- "project.structure.side.proportion": "0.0",
- "settings.editor.selected.configurable": "reference.settings.project.statistic.project.settings",
- "vue.rearranger.settings.migration": "true"
+
+}]]>
@@ -92,14 +94,6 @@
-
-
-
-
-
-
-
-
@@ -114,6 +108,21 @@
+
+
+
+
+
+
+
diff --git a/src/main/java/org/toop/game/tictactoe/MinMaxTicTacToe.java b/src/main/java/org/toop/game/tictactoe/MinMaxTicTacToe.java
index 03ae6f0..1fd2383 100644
--- a/src/main/java/org/toop/game/tictactoe/MinMaxTicTacToe.java
+++ b/src/main/java/org/toop/game/tictactoe/MinMaxTicTacToe.java
@@ -4,19 +4,32 @@ import org.toop.game.*;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
-import org.toop.Main;
public class MinMaxTicTacToe {
private static final Logger logger = LogManager.getLogger(MinMaxTicTacToe.class);
+ /**
+ * This method tries to find the best move by seeing if it can set a winning move, if not, it will do a minimax.
+ */
+
public int findBestMove(TicTacToe game) {
- /**
- * This method tries to find the best move by seeing if it can set a winning move, if not, it will do a minimax.
- */
int bestVal = -100; // set bestval to something impossible
int bestMove = 10; // set bestmove to something impossible
+
+ boolean empty = true;
+ for (char cell : game.grid) {
+ if(!(cell == GameBase.EMPTY)) {
+ empty = false;
+ break;
+ }
+ }
+
+ if (empty && game.validateMove(4)) {
+ return 4;
+ }
+
// simulate all possible moves on the field
for (int i = 0; i < game.grid.length; i++) {
if (game.validateMove(i)) { // check if the move is legal here
@@ -28,9 +41,18 @@ public class MinMaxTicTacToe {
if (result == GameBase.State.WIN) {
return i; // just return right away if you can win on the next move
}
- else {
- thisMoveValue = doMinimax(copyGame, game.movesLeft, false); // else look at other moves
+
+ for (int index = 0; index < game.grid.length; index++ ) {
+ if (game.validateMove(index)) {
+ TicTacToe opponentCopy = copyGame.copyBoard();
+ GameBase.State opponentResult = opponentCopy.play(index);
+ if (opponentResult == GameBase.State.WIN) {
+ return index;
+ }
+ }
}
+
+ thisMoveValue = doMinimax(copyGame, game.movesLeft, false); // else look at other moves
if (thisMoveValue > bestVal) { // if better move than the current best, change the move
bestVal = thisMoveValue;
bestMove = i;
@@ -40,10 +62,11 @@ public class MinMaxTicTacToe {
return bestMove; // return the best move when we've done everything
}
+ /**
+ * This method simulates all the possible future moves in the game through a copy in search of the best move.
+ */
+
public int doMinimax(TicTacToe game, int depth, boolean maximizing) {
- /**
- * This method simulates all the possible future moves in the game through a copy in search of the best move.
- */
boolean state = game.checkWin(); // check for a win (base case stuff)
if (state) {
diff --git a/src/test/java/MinMaxTicTacToeTest.java b/src/test/java/MinMaxTicTacToeTest.java
index 7da0adc..19ebb25 100644
--- a/src/test/java/MinMaxTicTacToeTest.java
+++ b/src/test/java/MinMaxTicTacToeTest.java
@@ -1,76 +1,115 @@
+
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
-import org.toop.game.tictactoe.*;
+import org.toop.game.GameBase;
+import org.toop.game.tictactoe.MinMaxTicTacToe;
+import org.toop.game.tictactoe.TicTacToe;
import static org.junit.jupiter.api.Assertions.*;
-class MinMaxTicTacToeTest {
+/**
+ * Unit tests for MinMaxTicTacToe AI.
+ */
+public class MinMaxTicTacToeTest {
- // makegame makes a board situation so we can test the AI. that's it really, the rest is easy to follow id say
- private TicTacToe makeGame(String board, int currentPlayer) {
- TicTacToe game = new TicTacToe("AI", "Human");
- // Fill the board
- for (int i = 0; i < board.length(); i++) {
- char c = board.charAt(i);
- game.grid[i] = c;
- if (c != '-') game.movesLeft--;
- }
- game.currentPlayer = currentPlayer;
- return game;
+ private MinMaxTicTacToe ai;
+ private TicTacToe game;
+
+ @BeforeEach // called before every test is done to make it work
+ void setUp() {
+ ai = new MinMaxTicTacToe();
+ game = new TicTacToe("AI", "Human");
}
@Test
- void testFindBestMove_AIImmediateWin() {
- TicTacToe game = makeGame("XX-OO----", 0);
- MinMaxTicTacToe ai = new MinMaxTicTacToe();
+ void testBestMoveWinningMoveAvailable() {
+ // Setup board where AI can win immediately
+ // X = AI, O = player
+ // X | X | .
+ // O | O | .
+ // . | . | .
+ game.grid = new char[]{
+ 'X', 'X', GameBase.EMPTY,
+ 'O', 'O', GameBase.EMPTY,
+ GameBase.EMPTY, GameBase.EMPTY, GameBase.EMPTY
+ };
+ game.movesLeft = 4;
+
int bestMove = ai.findBestMove(game);
- assertEquals(2, bestMove, "AI has to take winning move at 2");
+
+ // Ai is expected to place at index 2 to win
+ assertEquals(2, bestMove);
}
@Test
- void testFindBestMove_BlockOpponentWin() {
- TicTacToe game = makeGame("OO-X----", 0); // 0 = AI's turn
- MinMaxTicTacToe ai = new MinMaxTicTacToe();
+ void testBestMoveBlocksOpponentWin() {
+ // Setup board where player could win next turn
+ // O | O | .
+ // X | . | .
+ // . | . | .
+ game.grid = new char[]{
+ 'O', 'O', GameBase.EMPTY,
+ 'X', GameBase.EMPTY, GameBase.EMPTY,
+ GameBase.EMPTY, GameBase.EMPTY, GameBase.EMPTY
+ };
+ game.movesLeft = 4;
+
int bestMove = ai.findBestMove(game);
- assertEquals(2, bestMove, "AI should block opponent win at 2");
+
+ // AI block at index 2 to continue the game
+ assertEquals(2, bestMove);
}
@Test
- void testFindBestMove_ChooseDrawIfNoWin() {
- TicTacToe game = makeGame("XOXOX-O--", 0);
- MinMaxTicTacToe ai = new MinMaxTicTacToe();
+ void testBestMoveCenterPreferredOnEmptyBoard() {
+ // On empty board, center (index 4) is strongest
int bestMove = ai.findBestMove(game);
- assertTrue(bestMove == 6 || bestMove == 8, "AI should draw");
+
+ assertEquals(4, bestMove);
}
@Test
- void testMinimax_ScoreWin() {
- TicTacToe game = makeGame("XXX------", 0);
- MinMaxTicTacToe ai = new MinMaxTicTacToe();
- int score = ai.doMinimax(game, 5, false);
- assertTrue(score > 0, "AI win scored positively");
+ void testDoMinimaxScoresWinPositive() {
+ // Simulate a game state where AI has already won
+ TicTacToe copy = game.copyBoard();
+ copy.grid = new char[]{
+ 'X', 'X', 'X',
+ 'O', 'O', GameBase.EMPTY,
+ GameBase.EMPTY, GameBase.EMPTY, GameBase.EMPTY
+ };
+
+ int score = ai.doMinimax(copy, 5, false);
+
+ assertTrue(score > 0, "AI win should yield positive score");
}
@Test
- void testMinimax_ScoreLoss() {
- TicTacToe game = makeGame("OOO------", 1);
- MinMaxTicTacToe ai = new MinMaxTicTacToe();
- int score = ai.doMinimax(game, 5, true);
- assertTrue(score < 0, "AI loss is negative");
+ void testDoMinimaxScoresLossNegative() {
+ // Simulate a game state where human has already won
+ TicTacToe copy = game.copyBoard();
+ copy.grid = new char[]{
+ 'O', 'O', 'O',
+ 'X', 'X', GameBase.EMPTY,
+ GameBase.EMPTY, GameBase.EMPTY, GameBase.EMPTY
+ };
+
+ int score = ai.doMinimax(copy, 5, true);
+
+ assertTrue(score < 0, "Human win should yield negative score");
}
@Test
- void testMinimax_ScoreDraw() {
- TicTacToe game = makeGame("XOXOXOOXO", 0);
- MinMaxTicTacToe ai = new MinMaxTicTacToe();
- int score = ai.doMinimax(game, 5, true);
- assertEquals(0, score, "Draw should be zero!");
- }
+ void testDoMinimaxDrawReturnsZero() {
+ // Simulate a draw position
+ TicTacToe copy = game.copyBoard();
+ copy.grid = new char[]{
+ 'X', 'O', 'X',
+ 'X', 'O', 'O',
+ 'O', 'X', 'X'
+ };
- @Test
- void testMiniMax_MultipleMoves() {
- TicTacToe game = makeGame("-X-OX--O-", 0);
- MinMaxTicTacToe ai = new MinMaxTicTacToe();
- int bestMove = ai.findBestMove(game);
- assertTrue(bestMove == 0 || bestMove == 2, "Can look at multiple moves!");
+ int score = ai.doMinimax(copy, 0, true);
+
+ assertEquals(0, score, "Draw should return 0 score");
}
-}
+}
\ No newline at end of file