Merge pull request #26 from 2OOP/Michiel

Merge michiel branche with ServerManager for updated, working UI.
This commit is contained in:
Bas Antonius de Jong
2025-09-18 13:01:49 +02:00
committed by GitHub
3 changed files with 132 additions and 69 deletions

23
.idea/workspace.xml generated
View File

@@ -4,7 +4,8 @@
<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/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/Main.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/org/toop/Main.java" afterDir="false" /> <change beforePath="$PROJECT_DIR$/src/main/java/org/toop/Main.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/org/toop/Main.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/org/toop/UI/GameSelectorWindow.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/org/toop/UI/GameSelectorWindow.java" afterDir="false" /> <change beforePath="$PROJECT_DIR$/src/main/java/org/toop/UI/GameSelectorWindow.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/org/toop/UI/GameSelectorWindow.java" afterDir="false" />
@@ -29,7 +30,7 @@
<component name="Git.Settings"> <component name="Git.Settings">
<option name="RECENT_BRANCH_BY_REPOSITORY"> <option name="RECENT_BRANCH_BY_REPOSITORY">
<map> <map>
<entry key="$PROJECT_DIR$" value="Ticho" /> <entry key="$PROJECT_DIR$" value="ServerManager" />
</map> </map>
</option> </option>
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" /> <option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
@@ -70,6 +71,7 @@
<component name="PropertiesComponent"><![CDATA[{ <component name="PropertiesComponent"><![CDATA[{
"keyToString": { "keyToString": {
"Application.Main.executor": "Run", "Application.Main.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",
@@ -83,7 +85,10 @@
"node.js.selected.package.eslint": "(autodetect)", "node.js.selected.package.eslint": "(autodetect)",
"node.js.selected.package.tslint": "(autodetect)", "node.js.selected.package.tslint": "(autodetect)",
"nodejs_package_manager_path": "npm", "nodejs_package_manager_path": "npm",
"settings.editor.selected.configurable": "preferences.editor", "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" "vue.rearranger.settings.migration": "true"
} }
}]]></component> }]]></component>
@@ -102,14 +107,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="" />
@@ -144,6 +141,10 @@
</map> </map>
</option> </option>
</component> </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 />

View File

@@ -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, 8, 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) {
@@ -59,7 +82,7 @@ public class MinMaxTicTacToe {
else { else {
boolean empty = false; boolean empty = false;
for (char cell : game.grid) { // else, look at draw conditions. we check per cell if it's empty or not for (char cell : game.grid) { // else, look at draw conditions. we check per cell if it's empty or not
if (cell == ' ') { if (cell == GameBase.EMPTY) {
empty = true; // if a thing is empty, set to true empty = true; // if a thing is empty, set to true
break; // break the loop break; // break the loop
} }

View File

@@ -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!");
} }
} }