mirror of
https://github.com/2OOP/pism.git
synced 2026-02-04 10:54:51 +00:00
new unit tests + a small improvement to the ai where it didnt instantly block during unit tests
This commit is contained in:
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
@@ -8,7 +8,7 @@
|
|||||||
</list>
|
</list>
|
||||||
</option>
|
</option>
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_25" default="true" project-jdk-name="openjdk-ea-25" project-jdk-type="JavaSDK">
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_24" project-jdk-name="openjdk-ea-25" project-jdk-type="JavaSDK">
|
||||||
<output url="file://$PROJECT_DIR$/out" />
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
79
.idea/workspace.xml
generated
79
.idea/workspace.xml
generated
@@ -4,10 +4,11 @@
|
|||||||
<option name="autoReloadType" value="SELECTIVE" />
|
<option name="autoReloadType" value="SELECTIVE" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ChangeListManager">
|
<component name="ChangeListManager">
|
||||||
<list default="true" id="997b32da-b4d4-48ac-ab51-52d65f364f81" name="Changes" comment="">
|
<list default="true" id="997b32da-b4d4-48ac-ab51-52d65f364f81" name="Changes" comment="nog een kleine fix waarbij die depth van 8 gebruikte in plaats van game.movesleft">
|
||||||
<change beforePath="$PROJECT_DIR$/.idea/compiler.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/compiler.xml" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/.idea/misc.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/misc.xml" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/src/main/java/org/toop/game/tictactoe/MinMaxTicTacToe.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/org/toop/game/tictactoe/MinMaxTicTacToe.java" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/src/main/java/org/toop/game/tictactoe/MinMaxTicTacToe.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/org/toop/game/tictactoe/MinMaxTicTacToe.java" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/test/java/MinMaxTicTacToeTest.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/test/java/MinMaxTicTacToeTest.java" afterDir="false" />
|
||||||
</list>
|
</list>
|
||||||
<option name="SHOW_DIALOG" value="false" />
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
@@ -35,12 +36,12 @@
|
|||||||
"assignee": "BAFGdeJong"
|
"assignee": "BAFGdeJong"
|
||||||
}
|
}
|
||||||
}</component>
|
}</component>
|
||||||
<component name="GithubPullRequestsUISettings">{
|
<component name="GithubPullRequestsUISettings"><![CDATA[{
|
||||||
"selectedUrlAndAccountId": {
|
"selectedUrlAndAccountId": {
|
||||||
"url": "git@github.com:2OOP/pism_ttt.git",
|
"url": "https://github.com/2OOP/pism_ttt.git",
|
||||||
"accountId": "7694f583-f911-4763-8185-8ea3ed608804"
|
"accountId": "a9609b60-3227-4092-a6d9-591cac75179d"
|
||||||
}
|
}
|
||||||
}</component>
|
}]]></component>
|
||||||
<component name="MavenImportPreferences">
|
<component name="MavenImportPreferences">
|
||||||
<option name="explicitlyEnabledProfiles" value="lwjgl-natives-macos-aarch64,lwjgl-natives-linux-amd64" />
|
<option name="explicitlyEnabledProfiles" value="lwjgl-natives-macos-aarch64,lwjgl-natives-linux-amd64" />
|
||||||
</component>
|
</component>
|
||||||
@@ -56,27 +57,28 @@
|
|||||||
<option name="hideEmptyMiddlePackages" value="true" />
|
<option name="hideEmptyMiddlePackages" value="true" />
|
||||||
<option name="showLibraryContents" value="true" />
|
<option name="showLibraryContents" value="true" />
|
||||||
</component>
|
</component>
|
||||||
<component name="PropertiesComponent">{
|
<component name="PropertiesComponent"><![CDATA[{
|
||||||
"keyToString": {
|
"keyToString": {
|
||||||
"Application.Main.executor": "Run",
|
"Application.Main.executor": "Run",
|
||||||
"JUnit.MinMaxTicTacToeTest.executor": "Run",
|
"JUnit.MinMaxTicTacToeTest.executor": "Run",
|
||||||
"ModuleVcsDetector.initialDetectionPerformed": "true",
|
"ModuleVcsDetector.initialDetectionPerformed": "true",
|
||||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||||
"RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager": "true",
|
"RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager": "true",
|
||||||
"RunOnceActivity.git.unshallow": "true",
|
"RunOnceActivity.git.unshallow": "true",
|
||||||
"git-widget-placeholder": "Michiel",
|
"git-widget-placeholder": "Michiel",
|
||||||
"node.js.detected.package.eslint": "true",
|
"kotlin-language-version-configured": "true",
|
||||||
"node.js.detected.package.tslint": "true",
|
"node.js.detected.package.eslint": "true",
|
||||||
"node.js.selected.package.eslint": "(autodetect)",
|
"node.js.detected.package.tslint": "true",
|
||||||
"node.js.selected.package.tslint": "(autodetect)",
|
"node.js.selected.package.eslint": "(autodetect)",
|
||||||
"nodejs_package_manager_path": "npm",
|
"node.js.selected.package.tslint": "(autodetect)",
|
||||||
"project.structure.last.edited": "Modules",
|
"nodejs_package_manager_path": "npm",
|
||||||
"project.structure.proportion": "0.0",
|
"project.structure.last.edited": "Modules",
|
||||||
"project.structure.side.proportion": "0.0",
|
"project.structure.proportion": "0.0",
|
||||||
"settings.editor.selected.configurable": "reference.settings.project.statistic.project.settings",
|
"project.structure.side.proportion": "0.0",
|
||||||
"vue.rearranger.settings.migration": "true"
|
"settings.editor.selected.configurable": "reference.settings.project.statistic.project.settings",
|
||||||
|
"vue.rearranger.settings.migration": "true"
|
||||||
}
|
}
|
||||||
}</component>
|
}]]></component>
|
||||||
<component name="RunManager">
|
<component name="RunManager">
|
||||||
<configuration name="Main" type="Application" factoryName="Application" nameIsGenerated="true">
|
<configuration name="Main" type="Application" factoryName="Application" nameIsGenerated="true">
|
||||||
<option name="MAIN_CLASS_NAME" value="org.toop.Main" />
|
<option name="MAIN_CLASS_NAME" value="org.toop.Main" />
|
||||||
@@ -92,14 +94,6 @@
|
|||||||
</method>
|
</method>
|
||||||
</configuration>
|
</configuration>
|
||||||
</component>
|
</component>
|
||||||
<component name="SharedIndexes">
|
|
||||||
<attachedChunks>
|
|
||||||
<set>
|
|
||||||
<option value="bundled-jdk-9823dce3aa75-fbdcb00ec9e3-intellij.indexing.shared.core-IU-251.26927.53" />
|
|
||||||
<option value="bundled-js-predefined-d6986cc7102b-09060db00ec0-JavaScript-IU-251.26927.53" />
|
|
||||||
</set>
|
|
||||||
</attachedChunks>
|
|
||||||
</component>
|
|
||||||
<component name="TaskManager">
|
<component name="TaskManager">
|
||||||
<task active="true" id="Default" summary="Default task">
|
<task active="true" id="Default" summary="Default task">
|
||||||
<changelist id="997b32da-b4d4-48ac-ab51-52d65f364f81" name="Changes" comment="" />
|
<changelist id="997b32da-b4d4-48ac-ab51-52d65f364f81" name="Changes" comment="" />
|
||||||
@@ -114,6 +108,21 @@
|
|||||||
<component name="TypeScriptGeneratedFilesManager">
|
<component name="TypeScriptGeneratedFilesManager">
|
||||||
<option name="version" value="3" />
|
<option name="version" value="3" />
|
||||||
</component>
|
</component>
|
||||||
|
<component name="Vcs.Log.Tabs.Properties">
|
||||||
|
<option name="TAB_STATES">
|
||||||
|
<map>
|
||||||
|
<entry key="MAIN">
|
||||||
|
<value>
|
||||||
|
<State />
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
<component name="VcsManagerConfiguration">
|
||||||
|
<MESSAGE value="nog een kleine fix waarbij die depth van 8 gebruikte in plaats van game.movesleft" />
|
||||||
|
<option name="LAST_COMMIT_MESSAGE" value="nog een kleine fix waarbij die depth van 8 gebruikte in plaats van game.movesleft" />
|
||||||
|
</component>
|
||||||
<component name="XSLT-Support.FileAssociations.UIState">
|
<component name="XSLT-Support.FileAssociations.UIState">
|
||||||
<expand />
|
<expand />
|
||||||
<select />
|
<select />
|
||||||
|
|||||||
@@ -4,19 +4,32 @@ import org.toop.game.*;
|
|||||||
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
import org.toop.Main;
|
|
||||||
|
|
||||||
public class MinMaxTicTacToe {
|
public class MinMaxTicTacToe {
|
||||||
|
|
||||||
private static final Logger logger = LogManager.getLogger(MinMaxTicTacToe.class);
|
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) {
|
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 bestVal = -100; // set bestval to something impossible
|
||||||
int bestMove = 10; // set bestmove 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
|
// simulate all possible moves on the field
|
||||||
for (int i = 0; i < game.grid.length; i++) {
|
for (int i = 0; i < game.grid.length; i++) {
|
||||||
if (game.validateMove(i)) { // check if the move is legal here
|
if (game.validateMove(i)) { // check if the move is legal here
|
||||||
@@ -28,9 +41,18 @@ public class MinMaxTicTacToe {
|
|||||||
if (result == GameBase.State.WIN) {
|
if (result == GameBase.State.WIN) {
|
||||||
return i; // just return right away if you can win on the next move
|
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
|
if (thisMoveValue > bestVal) { // if better move than the current best, change the move
|
||||||
bestVal = thisMoveValue;
|
bestVal = thisMoveValue;
|
||||||
bestMove = i;
|
bestMove = i;
|
||||||
@@ -40,10 +62,11 @@ public class MinMaxTicTacToe {
|
|||||||
return bestMove; // return the best move when we've done everything
|
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) {
|
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)
|
boolean state = game.checkWin(); // check for a win (base case stuff)
|
||||||
|
|
||||||
if (state) {
|
if (state) {
|
||||||
|
|||||||
@@ -1,76 +1,115 @@
|
|||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
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.*;
|
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 MinMaxTicTacToe ai;
|
||||||
private TicTacToe makeGame(String board, int currentPlayer) {
|
private TicTacToe game;
|
||||||
TicTacToe game = new TicTacToe("AI", "Human");
|
|
||||||
// Fill the board
|
@BeforeEach // called before every test is done to make it work
|
||||||
for (int i = 0; i < board.length(); i++) {
|
void setUp() {
|
||||||
char c = board.charAt(i);
|
ai = new MinMaxTicTacToe();
|
||||||
game.grid[i] = c;
|
game = new TicTacToe("AI", "Human");
|
||||||
if (c != '-') game.movesLeft--;
|
|
||||||
}
|
|
||||||
game.currentPlayer = currentPlayer;
|
|
||||||
return game;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testFindBestMove_AIImmediateWin() {
|
void testBestMoveWinningMoveAvailable() {
|
||||||
TicTacToe game = makeGame("XX-OO----", 0);
|
// Setup board where AI can win immediately
|
||||||
MinMaxTicTacToe ai = new MinMaxTicTacToe();
|
// 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);
|
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
|
@Test
|
||||||
void testFindBestMove_BlockOpponentWin() {
|
void testBestMoveBlocksOpponentWin() {
|
||||||
TicTacToe game = makeGame("OO-X----", 0); // 0 = AI's turn
|
// Setup board where player could win next turn
|
||||||
MinMaxTicTacToe ai = new MinMaxTicTacToe();
|
// 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);
|
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
|
@Test
|
||||||
void testFindBestMove_ChooseDrawIfNoWin() {
|
void testBestMoveCenterPreferredOnEmptyBoard() {
|
||||||
TicTacToe game = makeGame("XOXOX-O--", 0);
|
// On empty board, center (index 4) is strongest
|
||||||
MinMaxTicTacToe ai = new MinMaxTicTacToe();
|
|
||||||
int bestMove = ai.findBestMove(game);
|
int bestMove = ai.findBestMove(game);
|
||||||
assertTrue(bestMove == 6 || bestMove == 8, "AI should draw");
|
|
||||||
|
assertEquals(4, bestMove);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testMinimax_ScoreWin() {
|
void testDoMinimaxScoresWinPositive() {
|
||||||
TicTacToe game = makeGame("XXX------", 0);
|
// Simulate a game state where AI has already won
|
||||||
MinMaxTicTacToe ai = new MinMaxTicTacToe();
|
TicTacToe copy = game.copyBoard();
|
||||||
int score = ai.doMinimax(game, 5, false);
|
copy.grid = new char[]{
|
||||||
assertTrue(score > 0, "AI win scored positively");
|
'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
|
@Test
|
||||||
void testMinimax_ScoreLoss() {
|
void testDoMinimaxScoresLossNegative() {
|
||||||
TicTacToe game = makeGame("OOO------", 1);
|
// Simulate a game state where human has already won
|
||||||
MinMaxTicTacToe ai = new MinMaxTicTacToe();
|
TicTacToe copy = game.copyBoard();
|
||||||
int score = ai.doMinimax(game, 5, true);
|
copy.grid = new char[]{
|
||||||
assertTrue(score < 0, "AI loss is negative");
|
'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
|
@Test
|
||||||
void testMinimax_ScoreDraw() {
|
void testDoMinimaxDrawReturnsZero() {
|
||||||
TicTacToe game = makeGame("XOXOXOOXO", 0);
|
// Simulate a draw position
|
||||||
MinMaxTicTacToe ai = new MinMaxTicTacToe();
|
TicTacToe copy = game.copyBoard();
|
||||||
int score = ai.doMinimax(game, 5, true);
|
copy.grid = new char[]{
|
||||||
assertEquals(0, score, "Draw should be zero!");
|
'X', 'O', 'X',
|
||||||
}
|
'X', 'O', 'O',
|
||||||
|
'O', 'X', 'X'
|
||||||
|
};
|
||||||
|
|
||||||
@Test
|
int score = ai.doMinimax(copy, 0, true);
|
||||||
void testMiniMax_MultipleMoves() {
|
|
||||||
TicTacToe game = makeGame("-X-OX--O-", 0);
|
assertEquals(0, score, "Draw should return 0 score");
|
||||||
MinMaxTicTacToe ai = new MinMaxTicTacToe();
|
|
||||||
int bestMove = ai.findBestMove(game);
|
|
||||||
assertTrue(bestMove == 0 || bestMove == 2, "Can look at multiple moves!");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user