From 4a58136afe5c355ac83571a823dbe59a83afb042 Mon Sep 17 00:00:00 2001 From: Persson-dev Date: Fri, 18 Apr 2025 18:42:28 +0200 Subject: [PATCH 01/15] almost working --- app/src/main/java/chess/SwingMain.java | 8 +- app/src/main/java/chess/ai/AI.java | 19 ++- app/src/main/java/chess/ai/AlphaBetaAI.java | 141 ++++++++++++++++++ .../chess/controller/CommandExecutor.java | 1 - .../controller/commands/CastlingCommand.java | 2 + .../controller/commands/MoveCommand.java | 4 +- .../controller/commands/PromoteCommand.java | 2 + .../chess/controller/event/GameAdaptator.java | 7 + .../controller/event/GameDispatcher.java | 11 ++ .../chess/controller/event/GameListener.java | 40 +++-- .../chess/view/consolerender/Console.java | 7 + .../java/chess/view/simplerender/Window.java | 6 + 12 files changed, 232 insertions(+), 16 deletions(-) create mode 100644 app/src/main/java/chess/ai/AlphaBetaAI.java diff --git a/app/src/main/java/chess/SwingMain.java b/app/src/main/java/chess/SwingMain.java index ab5ce8b..531997c 100644 --- a/app/src/main/java/chess/SwingMain.java +++ b/app/src/main/java/chess/SwingMain.java @@ -1,5 +1,6 @@ package chess; +import chess.ai.AlphaBetaAI; import chess.ai.DumbAI; import chess.ai.HungryAI; import chess.controller.CommandExecutor; @@ -18,12 +19,15 @@ public class SwingMain { Window window = new Window(commandExecutor, false); commandExecutor.addListener(window); - DumbAI ai = new DumbAI(commandExecutor, Color.Black); + DumbAI ai = new DumbAI(commandExecutor, Color.White); commandExecutor.addListener(ai); - HungryAI ai2 = new HungryAI(commandExecutor, Color.White); + AlphaBetaAI ai2 = new AlphaBetaAI(commandExecutor, Color.Black, 3); commandExecutor.addListener(ai2); + // Window window2 = new Window(ai2.getSimulation(), false); + // ai2.getSimulation().addListener(window2); + commandExecutor.addListener(new GameAdaptator(){ @Override public void onGameEnd() { diff --git a/app/src/main/java/chess/ai/AI.java b/app/src/main/java/chess/ai/AI.java index 677b340..c53709a 100644 --- a/app/src/main/java/chess/ai/AI.java +++ b/app/src/main/java/chess/ai/AI.java @@ -4,6 +4,7 @@ import java.util.List; import chess.controller.Command; import chess.controller.CommandExecutor; +import chess.controller.Command.CommandResult; import chess.controller.commands.GetPieceAtCommand; import chess.controller.commands.GetPlayerMovesCommand; import chess.controller.commands.GetAllowedCastlingsCommand; @@ -50,8 +51,12 @@ public abstract class AI extends GameAdaptator{ } protected List getAllowedMoves() { + return getAllowedMoves(this.commandExecutor); + } + + protected List getAllowedMoves(CommandExecutor commandExecutor) { GetPlayerMovesCommand cmd = new GetPlayerMovesCommand(); - sendCommand(cmd); + sendCommand(cmd, commandExecutor); return cmd.getMoves(); } @@ -61,8 +66,16 @@ public abstract class AI extends GameAdaptator{ return cmd2.getCastlingResult(); } - protected void sendCommand(Command command) { - this.commandExecutor.executeCommand(command); + protected CommandResult sendCommand(Command command) { + return sendCommand(command, this.commandExecutor); + } + + protected CommandResult sendCommand(Command command, CommandExecutor commandExecutor) { + CommandResult result = commandExecutor.executeCommand(command); + if(result == CommandResult.NotAllowed){ + System.out.println("eeeeee"); + } + return result; } } diff --git a/app/src/main/java/chess/ai/AlphaBetaAI.java b/app/src/main/java/chess/ai/AlphaBetaAI.java new file mode 100644 index 0000000..6b9313d --- /dev/null +++ b/app/src/main/java/chess/ai/AlphaBetaAI.java @@ -0,0 +1,141 @@ +package chess.ai; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +import chess.controller.CommandExecutor; +import chess.controller.commands.CastlingCommand; +import chess.controller.commands.MoveCommand; +import chess.controller.commands.NewGameCommand; +import chess.controller.commands.PromoteCommand; +import chess.controller.commands.PromoteCommand.PromoteType; +import chess.controller.commands.UndoCommand; +import chess.model.ChessBoard; +import chess.model.Color; +import chess.model.Coordinate; +import chess.model.Game; +import chess.model.Move; + +public class AlphaBetaAI extends AI { + + private final int searchDepth; + private final PieceCost pieceCost; + private final CommandExecutor simulation; + private final Game gameSimulation; + + public AlphaBetaAI(CommandExecutor commandExecutor, Color color, int searchDepth) { + super(commandExecutor, color); + this.searchDepth = 3; + this.pieceCost = new PieceCost(color); + this.gameSimulation = new Game(); + this.simulation = new CommandExecutor(this.gameSimulation); + } + + public CommandExecutor getSimulation() { + return simulation; + } + + private int getBoardEvaluation() { + final ChessBoard board = this.gameSimulation.getBoard(); + int result = 0; + for (int i = 0; i < Coordinate.VALUE_MAX; i++) { + for (int j = 0; j < Coordinate.VALUE_MAX; j++) { + result += pieceCost.getCost(board.pieceAt(new Coordinate(i, j))); + } + } + return result; + } + + private int negaMax(int depth, int alpha, int beta) { + if (depth == 0) + return getBoardEvaluation(); + + int value = Integer.MIN_VALUE; + + List moves = getAllowedMoves(this.simulation); + + if (moves.isEmpty()) { + System.out.println("oaaaaaaaaa"); + int board = getBoardEvaluation(); + int result = board + (this.gameSimulation.getPlayerTurn() == this.color ? -100 : 100); + return result; + } + + for (Move move : moves) { + sendCommand(new MoveCommand(move), this.simulation); + if (this.gameSimulation.getBoard().pawnShouldBePromoted()) + sendCommand(new PromoteCommand(PromoteType.Queen), this.simulation); + int leaveValue = -negaMax(depth - 1, -beta, -alpha); + value = Integer.max(value, leaveValue); + sendCommand(new UndoCommand(), this.simulation); + alpha = Integer.max(alpha, value); + if (alpha >= beta) + return value; + } + + return value; + } + + private Move getBestMove() { + List moves = getAllowedMoves(this.simulation); + int bestMove = Integer.MIN_VALUE; + List bestMoves = new ArrayList<>(20); + + for (Move move : moves) { + sendCommand(new MoveCommand(move), this.simulation); + if (this.gameSimulation.getBoard().pawnShouldBePromoted()) + sendCommand(new PromoteCommand(PromoteType.Queen), this.simulation); + int value = -negaMax(this.searchDepth - 1, Integer.MIN_VALUE, Integer.MAX_VALUE); + sendCommand(new UndoCommand(), this.simulation); + if (value > bestMove) { + bestMove = value; + bestMoves.clear(); + bestMoves.add(move); + } else if (value == bestMove) { + bestMoves.add(move); + } + } + + System.out.println("Best move : " + bestMove + " count : " + bestMoves.size()); + + return bestMoves.get(new Random().nextInt(bestMoves.size())); + } + + @Override + public void onGameStart() { + sendCommand(new NewGameCommand(), this.simulation); + } + + @Override + public void onPawnPromoted(PromoteType promotion, Color player) { + // if (player == this.color) + // return; + sendCommand(new PromoteCommand(promotion), this.simulation); + } + + @Override + public void onCastling(boolean bigCastling) { + sendCommand(new CastlingCommand(bigCastling), this.simulation); + } + + @Override + public void onMove(Move move) { + sendCommand(new MoveCommand(move), this.simulation); + } + + @Override + protected void play() { + long current = System.currentTimeMillis(); + Move move = getBestMove(); + long elapsed = System.currentTimeMillis() - current; + System.out.println("Took " + elapsed + "ms"); + sendCommand(new MoveCommand(move)); + } + + @Override + protected void promote(Coordinate pawnCoords) { + sendCommand(new PromoteCommand(PromoteType.Queen)); + } + +} diff --git a/app/src/main/java/chess/controller/CommandExecutor.java b/app/src/main/java/chess/controller/CommandExecutor.java index 76eae78..607a5ca 100644 --- a/app/src/main/java/chess/controller/CommandExecutor.java +++ b/app/src/main/java/chess/controller/CommandExecutor.java @@ -55,7 +55,6 @@ public class CommandExecutor { this.dispatcher.onBoardUpdate(); if (checkGameStatus()) { this.dispatcher.onGameEnd(); - return; } switchPlayerTurn(command instanceof UndoCommand); return; diff --git a/app/src/main/java/chess/controller/commands/CastlingCommand.java b/app/src/main/java/chess/controller/commands/CastlingCommand.java index 36af108..04a02ca 100644 --- a/app/src/main/java/chess/controller/commands/CastlingCommand.java +++ b/app/src/main/java/chess/controller/commands/CastlingCommand.java @@ -49,6 +49,8 @@ public class CastlingCommand extends PlayerCommand { board.applyMove(this.kingMove); board.applyMove(this.rookMove); + outputSystem.onCastling(this.bigCastling); + return CommandResult.Moved; } diff --git a/app/src/main/java/chess/controller/commands/MoveCommand.java b/app/src/main/java/chess/controller/commands/MoveCommand.java index 894fbec..7116690 100644 --- a/app/src/main/java/chess/controller/commands/MoveCommand.java +++ b/app/src/main/java/chess/controller/commands/MoveCommand.java @@ -37,7 +37,6 @@ public class MoveCommand extends PlayerCommand { return result; case Moved: - outputSystem.onMove(this.move); game.saveTraitPiecesPos(); return result; @@ -76,11 +75,14 @@ public class MoveCommand extends PlayerCommand { } if (tryPromote(game, outputSystem)) { + outputSystem.onMove(this.move); return CommandResult.ActionNeeded; } board.setLastMove(this.move); + outputSystem.onMove(this.move); + return CommandResult.Moved; } diff --git a/app/src/main/java/chess/controller/commands/PromoteCommand.java b/app/src/main/java/chess/controller/commands/PromoteCommand.java index c012e8d..a735ab8 100644 --- a/app/src/main/java/chess/controller/commands/PromoteCommand.java +++ b/app/src/main/java/chess/controller/commands/PromoteCommand.java @@ -55,6 +55,8 @@ public class PromoteCommand extends PlayerCommand { this.oldPawn = pawn; board.pieceComes(createPiece(this.promoteType, pawn.getColor()), this.pieceCoords); + outputSystem.onPawnPromoted(this.promoteType, game.getPlayerTurn()); + return CommandResult.Moved; } diff --git a/app/src/main/java/chess/controller/event/GameAdaptator.java b/app/src/main/java/chess/controller/event/GameAdaptator.java index 9ef52c3..33ed535 100644 --- a/app/src/main/java/chess/controller/event/GameAdaptator.java +++ b/app/src/main/java/chess/controller/event/GameAdaptator.java @@ -1,5 +1,6 @@ package chess.controller.event; +import chess.controller.commands.PromoteCommand.PromoteType; import chess.model.Color; import chess.model.Coordinate; import chess.model.Move; @@ -45,4 +46,10 @@ public abstract class GameAdaptator implements GameListener { @Override public void onDraw() {} + @Override + public void onCastling(boolean bigCastling) {} + + @Override + public void onPawnPromoted(PromoteType promotion, Color player) {} + } diff --git a/app/src/main/java/chess/controller/event/GameDispatcher.java b/app/src/main/java/chess/controller/event/GameDispatcher.java index e0a6fe0..c311f9f 100644 --- a/app/src/main/java/chess/controller/event/GameDispatcher.java +++ b/app/src/main/java/chess/controller/event/GameDispatcher.java @@ -6,6 +6,7 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.function.Consumer; +import chess.controller.commands.PromoteCommand.PromoteType; import chess.model.Color; import chess.model.Coordinate; import chess.model.Move; @@ -93,6 +94,16 @@ public class GameDispatcher implements GameListener { asyncForEachCall((l) -> l.onDraw()); } + @Override + public void onCastling(boolean bigCastling) { + asyncForEachCall((l) -> l.onCastling(bigCastling)); + } + + @Override + public void onPawnPromoted(PromoteType promotion, Color player) { + asyncForEachCall((l) -> l.onPawnPromoted(promotion, player)); + } + public void stopService() { this.executor.shutdown(); } diff --git a/app/src/main/java/chess/controller/event/GameListener.java b/app/src/main/java/chess/controller/event/GameListener.java index a530d9a..74e4cfe 100644 --- a/app/src/main/java/chess/controller/event/GameListener.java +++ b/app/src/main/java/chess/controller/event/GameListener.java @@ -1,5 +1,6 @@ package chess.controller.event; +import chess.controller.commands.PromoteCommand.PromoteType; import chess.model.Color; import chess.model.Coordinate; import chess.model.Move; @@ -15,7 +16,7 @@ public interface GameListener { * Invoked when a draw occurs (same position is repeated three times) */ void onDraw(); - + /** * Invoked when the game has ended (by a win or a draw) */ @@ -25,7 +26,7 @@ public interface GameListener { * Invoked when the game has started */ void onGameStart(); - + /** * Invoked when a king is in check */ @@ -35,47 +36,68 @@ public interface GameListener { * Invoked when a checkmate occurs */ void onKingInMat(); - + /** * Invoked when a valid move on the board occurs + * * @param move the move to be processed */ void onMove(Move move); /** * Invoked when a sent move is not allowed + * * @param move the move to be processed */ void onMoveNotAllowed(Move move); - + /** * Invoked when a pat situation occurs */ void onPatSituation(); - + /** * Invoked when it's the player turn - * @param color the color of the player who should play + * + * @param color the color of the player who should play * @param undone true if it's a result of an undo command */ void onPlayerTurn(Color color, boolean undone); - + /** * Invoked when a pawn should be promoted + * * @param pieceCoords the coordinates of the pawn */ void onPromotePawn(Coordinate pieceCoords); - + /** * Invoked when a players surrenders + * * @param coward the player who gave up */ void onSurrender(Color coward); - + /** * Invoked when a player wins (by checkmate or if the other one surrenders) + * * @param winner */ void onWin(Color winner); + /** + * Invoked when a castling is done + * + * @param bigCastling if it's queen side castling + */ + void onCastling(boolean bigCastling); + + /** + * Invoked when a pawn is promoted + * + * @param promotion the type of promotion + * @param player the player who promoted the pawns + */ + void onPawnPromoted(PromoteType promotion, Color player); + } diff --git a/app/src/main/java/chess/view/consolerender/Console.java b/app/src/main/java/chess/view/consolerender/Console.java index 3d2406b..1479d1e 100644 --- a/app/src/main/java/chess/view/consolerender/Console.java +++ b/app/src/main/java/chess/view/consolerender/Console.java @@ -333,4 +333,11 @@ public class Console implements GameListener { this.captureInput = captureInput; } + @Override + public void onCastling(boolean bigCastling) {} + + @Override + public void onPawnPromoted(PromoteType promotion, Color player) {} + + } diff --git a/app/src/main/java/chess/view/simplerender/Window.java b/app/src/main/java/chess/view/simplerender/Window.java index d09d4a3..275dd8a 100644 --- a/app/src/main/java/chess/view/simplerender/Window.java +++ b/app/src/main/java/chess/view/simplerender/Window.java @@ -318,4 +318,10 @@ public class Window extends JFrame implements GameListener { JOptionPane.showMessageDialog(this, "Same position was repeated three times. It's a draw!"); } + @Override + public void onCastling(boolean bigCastling) {} + + @Override + public void onPawnPromoted(PromoteType promotion, chess.model.Color player) {} + } -- 2.49.1 From dd6e03352829befe3d280bc933511f0a77faa250 Mon Sep 17 00:00:00 2001 From: Persson-dev Date: Fri, 18 Apr 2025 22:19:47 +0200 Subject: [PATCH 02/15] working --- app/src/main/java/chess/ai/AlphaBetaAI.java | 63 ++++++++++++------- app/src/main/java/chess/ai/PieceCost.java | 21 ++++--- .../chess/controller/CommandExecutor.java | 11 ++-- .../controller/commands/PromoteCommand.java | 2 +- .../controller/event/EmptyGameDispatcher.java | 11 +++- .../chess/controller/event/GameAdaptator.java | 2 +- .../controller/event/GameDispatcher.java | 4 +- .../chess/controller/event/GameListener.java | 2 +- app/src/main/java/chess/model/Game.java | 14 +++-- app/src/main/java/chess/pgn/PgnExport.java | 2 +- .../chess/view/consolerender/Console.java | 2 +- .../java/chess/view/simplerender/Window.java | 2 +- 12 files changed, 89 insertions(+), 47 deletions(-) diff --git a/app/src/main/java/chess/ai/AlphaBetaAI.java b/app/src/main/java/chess/ai/AlphaBetaAI.java index 6b9313d..37e8022 100644 --- a/app/src/main/java/chess/ai/AlphaBetaAI.java +++ b/app/src/main/java/chess/ai/AlphaBetaAI.java @@ -10,6 +10,7 @@ import chess.controller.commands.MoveCommand; import chess.controller.commands.NewGameCommand; import chess.controller.commands.PromoteCommand; import chess.controller.commands.PromoteCommand.PromoteType; +import chess.controller.event.EmptyGameDispatcher; import chess.controller.commands.UndoCommand; import chess.model.ChessBoard; import chess.model.Color; @@ -24,12 +25,15 @@ public class AlphaBetaAI extends AI { private final CommandExecutor simulation; private final Game gameSimulation; + private final int GREAT_MOVE = -9999; + private final int HORRIBLE_MOVE = -GREAT_MOVE; + public AlphaBetaAI(CommandExecutor commandExecutor, Color color, int searchDepth) { super(commandExecutor, color); - this.searchDepth = 3; + this.searchDepth = searchDepth; this.pieceCost = new PieceCost(color); this.gameSimulation = new Game(); - this.simulation = new CommandExecutor(this.gameSimulation); + this.simulation = new CommandExecutor(this.gameSimulation, new EmptyGameDispatcher()); } public CommandExecutor getSimulation() { @@ -44,9 +48,32 @@ public class AlphaBetaAI extends AI { result += pieceCost.getCost(board.pieceAt(new Coordinate(i, j))); } } + if (this.gameSimulation.getPlayerTurn() != color) + return -result; return result; } + private int getEndGameEvaluation() { + Color currentTurn = this.gameSimulation.getPlayerTurn(); + if (currentTurn == this.color) { + return HORRIBLE_MOVE; + } else { + if (this.gameSimulation.getBoard().isKingInCheck(currentTurn)) + return GREAT_MOVE; + return getBoardEvaluation() - PieceCost.PAWN; + } + } + + private void simulateMove(Move move) { + sendCommand(new MoveCommand(move), this.simulation); + if (this.gameSimulation.getBoard().pawnShouldBePromoted()) + sendCommand(new PromoteCommand(PromoteType.Queen), this.simulation); + } + + private void simulateUndo() { + sendCommand(new UndoCommand(), this.simulation); + } + private int negaMax(int depth, int alpha, int beta) { if (depth == 0) return getBoardEvaluation(); @@ -55,20 +82,13 @@ public class AlphaBetaAI extends AI { List moves = getAllowedMoves(this.simulation); - if (moves.isEmpty()) { - System.out.println("oaaaaaaaaa"); - int board = getBoardEvaluation(); - int result = board + (this.gameSimulation.getPlayerTurn() == this.color ? -100 : 100); - return result; - } + if (moves.isEmpty()) + return getEndGameEvaluation(); for (Move move : moves) { - sendCommand(new MoveCommand(move), this.simulation); - if (this.gameSimulation.getBoard().pawnShouldBePromoted()) - sendCommand(new PromoteCommand(PromoteType.Queen), this.simulation); - int leaveValue = -negaMax(depth - 1, -beta, -alpha); - value = Integer.max(value, leaveValue); - sendCommand(new UndoCommand(), this.simulation); + simulateMove(move); + value = Integer.max(value, -negaMax(depth - 1, -beta, -alpha)); + simulateUndo(); alpha = Integer.max(alpha, value); if (alpha >= beta) return value; @@ -83,11 +103,9 @@ public class AlphaBetaAI extends AI { List bestMoves = new ArrayList<>(20); for (Move move : moves) { - sendCommand(new MoveCommand(move), this.simulation); - if (this.gameSimulation.getBoard().pawnShouldBePromoted()) - sendCommand(new PromoteCommand(PromoteType.Queen), this.simulation); + simulateMove(move); int value = -negaMax(this.searchDepth - 1, Integer.MIN_VALUE, Integer.MAX_VALUE); - sendCommand(new UndoCommand(), this.simulation); + simulateUndo(); if (value > bestMove) { bestMove = value; bestMoves.clear(); @@ -108,9 +126,12 @@ public class AlphaBetaAI extends AI { } @Override - public void onPawnPromoted(PromoteType promotion, Color player) { - // if (player == this.color) - // return; + public void onGameEnd() { + this.simulation.close(); + } + + @Override + public void onPawnPromoted(PromoteType promotion) { sendCommand(new PromoteCommand(promotion), this.simulation); } diff --git a/app/src/main/java/chess/ai/PieceCost.java b/app/src/main/java/chess/ai/PieceCost.java index 61a9c4f..d94e4e0 100644 --- a/app/src/main/java/chess/ai/PieceCost.java +++ b/app/src/main/java/chess/ai/PieceCost.java @@ -14,6 +14,13 @@ public class PieceCost implements PieceVisitor { private final Color player; + public static final int BISHOP = 3; + public static final int KING = 90; + public static final int KNIGHT = 3; + public static final int PAWN = 1; + public static final int QUEEN = 9; + public static final int ROOK = 5; + public PieceCost(Color color) { this.player = color; } @@ -22,39 +29,39 @@ public class PieceCost implements PieceVisitor { if (piece == null) return 0; int cost = visit(piece); - if (piece.getColor() == player) + if (piece.getColor() != player) cost = -cost; return cost; } @Override public Integer visitPiece(Bishop bishop) { - return 3; + return BISHOP; } @Override public Integer visitPiece(King king) { - return 90; + return KING; } @Override public Integer visitPiece(Knight knight) { - return 3; + return KNIGHT; } @Override public Integer visitPiece(Pawn pawn) { - return 1; + return PAWN; } @Override public Integer visitPiece(Queen queen) { - return 9; + return QUEEN; } @Override public Integer visitPiece(Rook rook) { - return 5; + return ROOK; } } diff --git a/app/src/main/java/chess/controller/CommandExecutor.java b/app/src/main/java/chess/controller/CommandExecutor.java index 607a5ca..5724f26 100644 --- a/app/src/main/java/chess/controller/CommandExecutor.java +++ b/app/src/main/java/chess/controller/CommandExecutor.java @@ -19,7 +19,7 @@ public class CommandExecutor { public CommandExecutor(Game game) { this(game, new GameDispatcher()); } - + public CommandExecutor(Game game, GameDispatcher dispatcher) { this.game = game; this.dispatcher = dispatcher; @@ -52,18 +52,21 @@ public class CommandExecutor { return; case Moved: + boolean notifyPlayerTurn = true; this.dispatcher.onBoardUpdate(); if (checkGameStatus()) { this.dispatcher.onGameEnd(); + notifyPlayerTurn = false; } - switchPlayerTurn(command instanceof UndoCommand); + switchPlayerTurn(command instanceof UndoCommand, notifyPlayerTurn); return; } } - private void switchPlayerTurn(boolean undone) { + private void switchPlayerTurn(boolean undone, boolean notifyPlayerTurn) { this.game.switchPlayerTurn(); - this.dispatcher.onPlayerTurn(this.game.getPlayerTurn(), undone); + if (notifyPlayerTurn) + this.dispatcher.onPlayerTurn(this.game.getPlayerTurn(), undone); } /** diff --git a/app/src/main/java/chess/controller/commands/PromoteCommand.java b/app/src/main/java/chess/controller/commands/PromoteCommand.java index a735ab8..3adb4c4 100644 --- a/app/src/main/java/chess/controller/commands/PromoteCommand.java +++ b/app/src/main/java/chess/controller/commands/PromoteCommand.java @@ -55,7 +55,7 @@ public class PromoteCommand extends PlayerCommand { this.oldPawn = pawn; board.pieceComes(createPiece(this.promoteType, pawn.getColor()), this.pieceCoords); - outputSystem.onPawnPromoted(this.promoteType, game.getPlayerTurn()); + outputSystem.onPawnPromoted(this.promoteType); return CommandResult.Moved; } diff --git a/app/src/main/java/chess/controller/event/EmptyGameDispatcher.java b/app/src/main/java/chess/controller/event/EmptyGameDispatcher.java index 1e1ac2e..5e1b9ab 100644 --- a/app/src/main/java/chess/controller/event/EmptyGameDispatcher.java +++ b/app/src/main/java/chess/controller/event/EmptyGameDispatcher.java @@ -1,5 +1,6 @@ package chess.controller.event; +import chess.controller.commands.PromoteCommand.PromoteType; import chess.model.Color; import chess.model.Coordinate; import chess.model.Move; @@ -57,5 +58,13 @@ public class EmptyGameDispatcher extends GameDispatcher { @Override public void onWin(Color winner) { } - + + @Override + public void onCastling(boolean bigCastling) { + } + + @Override + public void onPawnPromoted(PromoteType promotion) { + } + } diff --git a/app/src/main/java/chess/controller/event/GameAdaptator.java b/app/src/main/java/chess/controller/event/GameAdaptator.java index 33ed535..03a3957 100644 --- a/app/src/main/java/chess/controller/event/GameAdaptator.java +++ b/app/src/main/java/chess/controller/event/GameAdaptator.java @@ -50,6 +50,6 @@ public abstract class GameAdaptator implements GameListener { public void onCastling(boolean bigCastling) {} @Override - public void onPawnPromoted(PromoteType promotion, Color player) {} + public void onPawnPromoted(PromoteType promotion) {} } diff --git a/app/src/main/java/chess/controller/event/GameDispatcher.java b/app/src/main/java/chess/controller/event/GameDispatcher.java index c311f9f..06023fe 100644 --- a/app/src/main/java/chess/controller/event/GameDispatcher.java +++ b/app/src/main/java/chess/controller/event/GameDispatcher.java @@ -100,8 +100,8 @@ public class GameDispatcher implements GameListener { } @Override - public void onPawnPromoted(PromoteType promotion, Color player) { - asyncForEachCall((l) -> l.onPawnPromoted(promotion, player)); + public void onPawnPromoted(PromoteType promotion) { + asyncForEachCall((l) -> l.onPawnPromoted(promotion)); } public void stopService() { diff --git a/app/src/main/java/chess/controller/event/GameListener.java b/app/src/main/java/chess/controller/event/GameListener.java index 74e4cfe..7086e93 100644 --- a/app/src/main/java/chess/controller/event/GameListener.java +++ b/app/src/main/java/chess/controller/event/GameListener.java @@ -98,6 +98,6 @@ public interface GameListener { * @param promotion the type of promotion * @param player the player who promoted the pawns */ - void onPawnPromoted(PromoteType promotion, Color player); + void onPawnPromoted(PromoteType promotion); } diff --git a/app/src/main/java/chess/model/Game.java b/app/src/main/java/chess/model/Game.java index 8982632..4b7be53 100644 --- a/app/src/main/java/chess/model/Game.java +++ b/app/src/main/java/chess/model/Game.java @@ -70,24 +70,26 @@ public class Game { playerTurn = Color.getEnemy(playerTurn); } - public GameStatus checkGameStatus() { - final Color enemy = Color.getEnemy(getPlayerTurn()); - + public GameStatus checkGameStatus(Color color) { if (checkDraw()) return GameStatus.Draw; - if (this.board.isKingInCheck(enemy)) - if (this.board.hasAllowedMoves(enemy)) + if (this.board.isKingInCheck(color)) + if (this.board.hasAllowedMoves(color)) return GameStatus.Check; else return GameStatus.CheckMate; - if (!board.hasAllowedMoves(enemy)) + if (!board.hasAllowedMoves(color)) return GameStatus.Pat; return GameStatus.OnGoing; } + public GameStatus checkGameStatus() { + return checkGameStatus(Color.getEnemy(getPlayerTurn())); + } + public void addAction(PlayerCommand command) { this.movesHistory.add(command); } diff --git a/app/src/main/java/chess/pgn/PgnExport.java b/app/src/main/java/chess/pgn/PgnExport.java index 3ad34f5..22e0cf7 100644 --- a/app/src/main/java/chess/pgn/PgnExport.java +++ b/app/src/main/java/chess/pgn/PgnExport.java @@ -137,7 +137,7 @@ public class PgnExport { } private static String checkCheckMate(Game game) { - switch (game.checkGameStatus()) { + switch (game.checkGameStatus(game.getPlayerTurn())) { case CheckMate: return "#"; diff --git a/app/src/main/java/chess/view/consolerender/Console.java b/app/src/main/java/chess/view/consolerender/Console.java index 1479d1e..559ff54 100644 --- a/app/src/main/java/chess/view/consolerender/Console.java +++ b/app/src/main/java/chess/view/consolerender/Console.java @@ -337,7 +337,7 @@ public class Console implements GameListener { public void onCastling(boolean bigCastling) {} @Override - public void onPawnPromoted(PromoteType promotion, Color player) {} + public void onPawnPromoted(PromoteType promotion) {} } diff --git a/app/src/main/java/chess/view/simplerender/Window.java b/app/src/main/java/chess/view/simplerender/Window.java index 275dd8a..10f6fab 100644 --- a/app/src/main/java/chess/view/simplerender/Window.java +++ b/app/src/main/java/chess/view/simplerender/Window.java @@ -322,6 +322,6 @@ public class Window extends JFrame implements GameListener { public void onCastling(boolean bigCastling) {} @Override - public void onPawnPromoted(PromoteType promotion, chess.model.Color player) {} + public void onPawnPromoted(PromoteType promotion) {} } -- 2.49.1 From 031d3d94ec45bad69ba1c65645e223d65e2fea66 Mon Sep 17 00:00:00 2001 From: Persson-dev Date: Fri, 18 Apr 2025 23:51:42 +0200 Subject: [PATCH 03/15] pretty convincing --- app/src/main/java/chess/SwingMain.java | 5 +- app/src/main/java/chess/ai/AlphaBetaAI.java | 9 +- app/src/main/java/chess/ai/HungryAI.java | 2 +- app/src/main/java/chess/ai/PieceCost.java | 12 +- app/src/main/java/chess/ai/PiecePosCost.java | 128 +++++++++++++++++++ app/src/main/java/chess/pgn/PgnExport.java | 2 +- 6 files changed, 146 insertions(+), 12 deletions(-) create mode 100644 app/src/main/java/chess/ai/PiecePosCost.java diff --git a/app/src/main/java/chess/SwingMain.java b/app/src/main/java/chess/SwingMain.java index 531997c..51d5065 100644 --- a/app/src/main/java/chess/SwingMain.java +++ b/app/src/main/java/chess/SwingMain.java @@ -1,5 +1,6 @@ package chess; +import chess.ai.AI; import chess.ai.AlphaBetaAI; import chess.ai.DumbAI; import chess.ai.HungryAI; @@ -19,10 +20,10 @@ public class SwingMain { Window window = new Window(commandExecutor, false); commandExecutor.addListener(window); - DumbAI ai = new DumbAI(commandExecutor, Color.White); + AI ai = new AlphaBetaAI(commandExecutor, Color.White, 3); commandExecutor.addListener(ai); - AlphaBetaAI ai2 = new AlphaBetaAI(commandExecutor, Color.Black, 3); + AI ai2 = new AlphaBetaAI(commandExecutor, Color.Black, 3); commandExecutor.addListener(ai2); // Window window2 = new Window(ai2.getSimulation(), false); diff --git a/app/src/main/java/chess/ai/AlphaBetaAI.java b/app/src/main/java/chess/ai/AlphaBetaAI.java index 37e8022..e8ce957 100644 --- a/app/src/main/java/chess/ai/AlphaBetaAI.java +++ b/app/src/main/java/chess/ai/AlphaBetaAI.java @@ -17,11 +17,13 @@ import chess.model.Color; import chess.model.Coordinate; import chess.model.Game; import chess.model.Move; +import chess.model.Piece; public class AlphaBetaAI extends AI { private final int searchDepth; private final PieceCost pieceCost; + private final PiecePosCost piecePosCost; private final CommandExecutor simulation; private final Game gameSimulation; @@ -32,6 +34,7 @@ public class AlphaBetaAI extends AI { super(commandExecutor, color); this.searchDepth = searchDepth; this.pieceCost = new PieceCost(color); + this.piecePosCost = new PiecePosCost(color); this.gameSimulation = new Game(); this.simulation = new CommandExecutor(this.gameSimulation, new EmptyGameDispatcher()); } @@ -45,7 +48,9 @@ public class AlphaBetaAI extends AI { int result = 0; for (int i = 0; i < Coordinate.VALUE_MAX; i++) { for (int j = 0; j < Coordinate.VALUE_MAX; j++) { - result += pieceCost.getCost(board.pieceAt(new Coordinate(i, j))); + Coordinate coordinate = new Coordinate(i, j); + Piece piece = board.pieceAt(coordinate); + result += pieceCost.getCost(piece) + piecePosCost.getEvaluation(piece, coordinate); } } if (this.gameSimulation.getPlayerTurn() != color) @@ -60,7 +65,7 @@ public class AlphaBetaAI extends AI { } else { if (this.gameSimulation.getBoard().isKingInCheck(currentTurn)) return GREAT_MOVE; - return getBoardEvaluation() - PieceCost.PAWN; + return getBoardEvaluation() + PieceCost.PAWN; } } diff --git a/app/src/main/java/chess/ai/HungryAI.java b/app/src/main/java/chess/ai/HungryAI.java index 7c1cb54..ed2303c 100644 --- a/app/src/main/java/chess/ai/HungryAI.java +++ b/app/src/main/java/chess/ai/HungryAI.java @@ -26,7 +26,7 @@ public class HungryAI extends AI { private int getMoveCost(Move move) { Piece piece = pieceAt(move.getDeadPieceCoords()); - return pieceCost.getCost(piece); + return -pieceCost.getCost(piece); } private List getBestMoves() { diff --git a/app/src/main/java/chess/ai/PieceCost.java b/app/src/main/java/chess/ai/PieceCost.java index d94e4e0..385be77 100644 --- a/app/src/main/java/chess/ai/PieceCost.java +++ b/app/src/main/java/chess/ai/PieceCost.java @@ -14,12 +14,12 @@ public class PieceCost implements PieceVisitor { private final Color player; - public static final int BISHOP = 3; - public static final int KING = 90; - public static final int KNIGHT = 3; - public static final int PAWN = 1; - public static final int QUEEN = 9; - public static final int ROOK = 5; + public static final int BISHOP = 30; + public static final int KING = 900; + public static final int KNIGHT = 30; + public static final int PAWN = 10; + public static final int QUEEN = 90; + public static final int ROOK = 50; public PieceCost(Color color) { this.player = color; diff --git a/app/src/main/java/chess/ai/PiecePosCost.java b/app/src/main/java/chess/ai/PiecePosCost.java new file mode 100644 index 0000000..be1478e --- /dev/null +++ b/app/src/main/java/chess/ai/PiecePosCost.java @@ -0,0 +1,128 @@ +package chess.ai; + +import java.util.Arrays; +import java.util.List; + +import chess.model.Color; +import chess.model.Coordinate; +import chess.model.Piece; +import chess.model.PieceVisitor; +import chess.model.pieces.Bishop; +import chess.model.pieces.King; +import chess.model.pieces.Knight; +import chess.model.pieces.Pawn; +import chess.model.pieces.Queen; +import chess.model.pieces.Rook; + +public class PiecePosCost implements PieceVisitor> { + + private final Color color; + + private static final List BISHOP = Arrays.asList( + -2, -1, -1, -1, -1, -1, -1, -2, -1, + 0, 0, 0, 0, 0, 0, -1, + -1, 0, 0, 1, 1, 0, 0, -1, + -1, 0, 0, 1, 1, 0, 0, -1, + -1, 0, 0, 1, 1, 0, 0, -1, + -1, 1, 1, 1, 1, 1, 1, -1, + -1, 0, 0, 0, 0, 0, 0, -1, + -2, -1, -1, -1, -1, -1, -1, -2); + + private static final List KING = Arrays.asList( + -3, -4, -4, -5, -5, -4, -4, -3, + -3, -4, -4, -5, -5, -4, -4, -3, + -3, -4, -4, -5, -5, -4, -4, -3, + -3, -4, -4, -5, -5, -4, -4, -3, + -2, -3, -3, -4, -4, -3, -3, -2, + -1, -2, -2, -2, -2, -2, -2, -1, + 2, 2, 0, 0, 0, 0, 2, 2, + 2, 3, 1, 0, 0, 1, 3, 2); + + private static final List KNIGHT = Arrays.asList( + -5, -4, -3, -3, -3, -3, -4, -5, -4, + -2, 0, 0, 0, 0, -2, -4, + -3, 0, 1, 1, 1, 1, 0, -3, + -3, 0, 1, 2, 2, 1, 0, -3, + -3, 0, 1, 2, 2, 1, 0, -3, + -3, 0, 1, 1, 1, 1, 0, -3, + -4, -2, 0, 0, 0, 0, -2, -4, + -5, -4, -3, -3, -3, -3, -4, -5); + + private static final List PAWN = Arrays.asList( + 0, 0, 0, 0, 0, 0, 0, 0, + 5, 5, 5, 5, 5, 5, 5, 5, + 1, 1, 2, 3, 3, 2, 1, 1, + 0, 0, 1, 2, 2, 1, 0, 0, + 0, 0, 1, 2, 2, 1, 0, 0, + 0, 0, -1, 0, 0, -1, 0, 0, + 0, 1, 1, -2, -2, 1, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0); + + private static final List QUEEN = Arrays.asList( + -2, -1, -1, 0, 0, -1, -1, -2, + -1, 0, 0, 0, 0, 0, 0, -1, + -1, 0, 0, 0, 0, 0, 0, -1, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + -1, 0, 0, 0, 0, 0, 0, -1, + -1, 0, 0, 0, 0, 0, 0, -1, + -2, -1, -1, 0, 0, -1, -1, -2); + + private static final List ROOK = Arrays.asList( + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 1, 1, 1, 0, + -1, 0, 0, 0, 0, 0, 0, 0, -1, + -1, 0, 0, 0, 0, 0, 0, 0, -1, + -1, 0, 0, 0, 0, 0, 0, 0, -1, + -1, 0, 0, 0, 0, 0, 0, 0, -1, + -1, 0, 0, 0, 0, 0, 0, 0, -1, + 0, 0, 0, 0, 1, 1, 0, 0, 0); + + public PiecePosCost(Color color) { + this.color = color; + } + + public int getEvaluation(Piece piece, Coordinate coordinate) { + if (piece == null) + return 0; + List positions = visit(piece); + int y = piece.getColor() == Color.Black ? (Coordinate.VALUE_MAX - 1 - coordinate.getY()) : coordinate.getY(); + Coordinate newCoords = new Coordinate(coordinate.getX(), y); + assert newCoords.isValid(); + int result = positions.get(newCoords.toIndex()); + if (piece.getColor() != color) + return -result; + return result; + } + + @Override + public List visitPiece(Bishop bishop) { + return BISHOP; + } + + @Override + public List visitPiece(King king) { + return KING; + } + + @Override + public List visitPiece(Knight knight) { + return KNIGHT; + } + + @Override + public List visitPiece(Pawn pawn) { + return PAWN; + } + + @Override + public List visitPiece(Queen queen) { + return QUEEN; + } + + @Override + public List visitPiece(Rook rook) { + return ROOK; + } + +} diff --git a/app/src/main/java/chess/pgn/PgnExport.java b/app/src/main/java/chess/pgn/PgnExport.java index 22e0cf7..31e57bd 100644 --- a/app/src/main/java/chess/pgn/PgnExport.java +++ b/app/src/main/java/chess/pgn/PgnExport.java @@ -56,7 +56,7 @@ public class PgnExport { } private static String gameEnd(Game game) { - switch (game.checkGameStatus()) { + switch (game.checkGameStatus(game.getPlayerTurn())) { case Draw: case Pat: return "1/2-1/2"; -- 2.49.1 From 13b61ad71c85407c4ca75f78bf190b57a300e088 Mon Sep 17 00:00:00 2001 From: Persson-dev Date: Sat, 19 Apr 2025 12:56:08 +0200 Subject: [PATCH 04/15] big refactor --- app/src/main/java/chess/SwingMain.java | 4 +- app/src/main/java/chess/ai/AlphaBetaAI.java | 131 ++++++++---------- .../main/java/chess/ai/GameSimulation.java | 96 +++++++++++++ app/src/main/java/chess/ai/HungryAI.java | 2 +- app/src/main/java/chess/ai/PieceCost.java | 30 ++-- app/src/main/java/chess/ai/PiecePosCost.java | 131 +++++++++--------- .../chess/controller/CommandExecutor.java | 5 +- .../controller/event/AsyncGameDispatcher.java | 113 +++++++++++++++ .../controller/event/EmptyGameDispatcher.java | 8 ++ .../controller/event/GameDispatcher.java | 110 +-------------- 10 files changed, 366 insertions(+), 264 deletions(-) create mode 100644 app/src/main/java/chess/ai/GameSimulation.java create mode 100644 app/src/main/java/chess/controller/event/AsyncGameDispatcher.java diff --git a/app/src/main/java/chess/SwingMain.java b/app/src/main/java/chess/SwingMain.java index 51d5065..bbaafcc 100644 --- a/app/src/main/java/chess/SwingMain.java +++ b/app/src/main/java/chess/SwingMain.java @@ -20,10 +20,10 @@ public class SwingMain { Window window = new Window(commandExecutor, false); commandExecutor.addListener(window); - AI ai = new AlphaBetaAI(commandExecutor, Color.White, 3); + AI ai = new HungryAI(commandExecutor, Color.White); commandExecutor.addListener(ai); - AI ai2 = new AlphaBetaAI(commandExecutor, Color.Black, 3); + AI ai2 = new AlphaBetaAI(commandExecutor, Color.Black, 1); commandExecutor.addListener(ai2); // Window window2 = new Window(ai2.getSimulation(), false); diff --git a/app/src/main/java/chess/ai/AlphaBetaAI.java b/app/src/main/java/chess/ai/AlphaBetaAI.java index e8ce957..7a9042c 100644 --- a/app/src/main/java/chess/ai/AlphaBetaAI.java +++ b/app/src/main/java/chess/ai/AlphaBetaAI.java @@ -2,20 +2,18 @@ package chess.ai; import java.util.ArrayList; import java.util.List; -import java.util.Random; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; import chess.controller.CommandExecutor; -import chess.controller.commands.CastlingCommand; import chess.controller.commands.MoveCommand; -import chess.controller.commands.NewGameCommand; import chess.controller.commands.PromoteCommand; import chess.controller.commands.PromoteCommand.PromoteType; -import chess.controller.event.EmptyGameDispatcher; -import chess.controller.commands.UndoCommand; import chess.model.ChessBoard; import chess.model.Color; import chess.model.Coordinate; -import chess.model.Game; import chess.model.Move; import chess.model.Piece; @@ -24,28 +22,30 @@ public class AlphaBetaAI extends AI { private final int searchDepth; private final PieceCost pieceCost; private final PiecePosCost piecePosCost; - private final CommandExecutor simulation; - private final Game gameSimulation; + private final GameSimulation simulation; - private final int GREAT_MOVE = -9999; - private final int HORRIBLE_MOVE = -GREAT_MOVE; + private static final float MAX_FLOAT = Float.MAX_VALUE; + private static final float MIN_FLOAT = -MAX_FLOAT; + + private static final int GREAT_MOVE = -9999; + private static final int HORRIBLE_MOVE = -GREAT_MOVE; + + private final ExecutorService threadPool; public AlphaBetaAI(CommandExecutor commandExecutor, Color color, int searchDepth) { super(commandExecutor, color); this.searchDepth = searchDepth; this.pieceCost = new PieceCost(color); this.piecePosCost = new PiecePosCost(color); - this.gameSimulation = new Game(); - this.simulation = new CommandExecutor(this.gameSimulation, new EmptyGameDispatcher()); + this.simulation = new GameSimulation(); + commandExecutor.addListener(simulation); + this.threadPool = Executors.newSingleThreadExecutor(); + System.out.println(Runtime.getRuntime().availableProcessors()); } - public CommandExecutor getSimulation() { - return simulation; - } - - private int getBoardEvaluation() { - final ChessBoard board = this.gameSimulation.getBoard(); - int result = 0; + private float getBoardEvaluation() { + final ChessBoard board = this.simulation.getBoard(); + float result = 0; for (int i = 0; i < Coordinate.VALUE_MAX; i++) { for (int j = 0; j < Coordinate.VALUE_MAX; j++) { Coordinate coordinate = new Coordinate(i, j); @@ -53,48 +53,45 @@ public class AlphaBetaAI extends AI { result += pieceCost.getCost(piece) + piecePosCost.getEvaluation(piece, coordinate); } } - if (this.gameSimulation.getPlayerTurn() != color) + if (this.simulation.getPlayerTurn() != color) return -result; return result; } - private int getEndGameEvaluation() { - Color currentTurn = this.gameSimulation.getPlayerTurn(); + private float getEndGameEvaluation() { + Color currentTurn = this.simulation.getPlayerTurn(); if (currentTurn == this.color) { return HORRIBLE_MOVE; } else { - if (this.gameSimulation.getBoard().isKingInCheck(currentTurn)) + if (this.simulation.getBoard().isKingInCheck(currentTurn)) return GREAT_MOVE; return getBoardEvaluation() + PieceCost.PAWN; } } - private void simulateMove(Move move) { - sendCommand(new MoveCommand(move), this.simulation); - if (this.gameSimulation.getBoard().pawnShouldBePromoted()) - sendCommand(new PromoteCommand(PromoteType.Queen), this.simulation); + private float getMoveValue(Move move) { + this.simulation.tryMove(move); + float value = -negaMax(this.searchDepth - 1, MIN_FLOAT, MAX_FLOAT); + this.simulation.undoMove(); + return value; } - private void simulateUndo() { - sendCommand(new UndoCommand(), this.simulation); - } - - private int negaMax(int depth, int alpha, int beta) { + private float negaMax(int depth, float alpha, float beta) { if (depth == 0) return getBoardEvaluation(); - int value = Integer.MIN_VALUE; + float value = MIN_FLOAT; - List moves = getAllowedMoves(this.simulation); + List moves = this.simulation.getAllowedMoves(); if (moves.isEmpty()) return getEndGameEvaluation(); for (Move move : moves) { - simulateMove(move); - value = Integer.max(value, -negaMax(depth - 1, -beta, -alpha)); - simulateUndo(); - alpha = Integer.max(alpha, value); + this.simulation.tryMove(move); + value = Float.max(value, -negaMax(depth - 1, -beta, -alpha)); + this.simulation.undoMove(); + alpha = Float.max(alpha, value); if (alpha >= beta) return value; } @@ -103,51 +100,39 @@ public class AlphaBetaAI extends AI { } private Move getBestMove() { - List moves = getAllowedMoves(this.simulation); - int bestMove = Integer.MIN_VALUE; - List bestMoves = new ArrayList<>(20); + List moves = this.simulation.getAllowedMoves(); + List> moveEvaluations = new ArrayList<>(50); + float bestMoveValue = MIN_FLOAT; + Move bestMove = null; for (Move move : moves) { - simulateMove(move); - int value = -negaMax(this.searchDepth - 1, Integer.MIN_VALUE, Integer.MAX_VALUE); - simulateUndo(); - if (value > bestMove) { - bestMove = value; - bestMoves.clear(); - bestMoves.add(move); - } else if (value == bestMove) { - bestMoves.add(move); + moveEvaluations.add(this.threadPool.submit(() -> { + return getMoveValue(move); + })); + } + + for (int i = 0; i < moves.size(); i++) { + Move move = moves.get(i); + float value = MIN_FLOAT; + try { + value = moveEvaluations.get(i).get(); + } catch (InterruptedException | ExecutionException e) { + e.printStackTrace(); + } + if (value > bestMoveValue) { + bestMoveValue = value; + bestMove = move; } } - System.out.println("Best move : " + bestMove + " count : " + bestMoves.size()); + System.out.println("Best move : " + bestMoveValue); - return bestMoves.get(new Random().nextInt(bestMoves.size())); - } - - @Override - public void onGameStart() { - sendCommand(new NewGameCommand(), this.simulation); + return bestMove; } @Override public void onGameEnd() { - this.simulation.close(); - } - - @Override - public void onPawnPromoted(PromoteType promotion) { - sendCommand(new PromoteCommand(promotion), this.simulation); - } - - @Override - public void onCastling(boolean bigCastling) { - sendCommand(new CastlingCommand(bigCastling), this.simulation); - } - - @Override - public void onMove(Move move) { - sendCommand(new MoveCommand(move), this.simulation); + this.threadPool.close(); } @Override diff --git a/app/src/main/java/chess/ai/GameSimulation.java b/app/src/main/java/chess/ai/GameSimulation.java new file mode 100644 index 0000000..c1525b9 --- /dev/null +++ b/app/src/main/java/chess/ai/GameSimulation.java @@ -0,0 +1,96 @@ +package chess.ai; + +import java.util.List; + +import chess.controller.Command; +import chess.controller.Command.CommandResult; +import chess.controller.CommandExecutor; +import chess.controller.commands.CastlingCommand; +import chess.controller.commands.GetPlayerMovesCommand; +import chess.controller.commands.MoveCommand; +import chess.controller.commands.NewGameCommand; +import chess.controller.commands.PromoteCommand; +import chess.controller.commands.UndoCommand; +import chess.controller.commands.PromoteCommand.PromoteType; +import chess.controller.event.EmptyGameDispatcher; +import chess.controller.event.GameAdaptator; +import chess.model.ChessBoard; +import chess.model.Color; +import chess.model.Game; +import chess.model.Move; + +public class GameSimulation extends GameAdaptator { + + private final CommandExecutor simulation; + private final Game gameSimulation; + + public GameSimulation() { + this.gameSimulation = new Game(); + this.simulation = new CommandExecutor(gameSimulation, new EmptyGameDispatcher()); + } + + protected CommandResult sendCommand(Command command) { + CommandResult result = this.simulation.executeCommand(command); + if (result == CommandResult.NotAllowed) { + System.out.println("eeeeee"); + } + return result; + } + + public void tryMove(Move move) { + sendCommand(new MoveCommand(move)); + if (this.gameSimulation.getBoard().pawnShouldBePromoted()) + sendCommand(new PromoteCommand(PromoteType.Queen)); + } + + public void undoMove() { + sendCommand(new UndoCommand()); + } + + @Override + public void onPawnPromoted(PromoteType promotion) { + sendCommand(new PromoteCommand(promotion)); + } + + @Override + public void onCastling(boolean bigCastling) { + sendCommand(new CastlingCommand(bigCastling)); + } + + @Override + public void onMove(Move move) { + sendCommand(new MoveCommand(move)); + } + + @Override + public void onGameStart() { + sendCommand(new NewGameCommand()); + } + + public CommandExecutor getCommandExecutor() { + return simulation; + } + + public Game getGame() { + return gameSimulation; + } + + public ChessBoard getBoard() { + return this.gameSimulation.getBoard(); + } + + public Color getPlayerTurn() { + return this.gameSimulation.getPlayerTurn(); + } + + public List getAllowedMoves() { + GetPlayerMovesCommand cmd = new GetPlayerMovesCommand(); + sendCommand(cmd); + return cmd.getMoves(); + } + + public void close() { + this.simulation.close(); + } + +} diff --git a/app/src/main/java/chess/ai/HungryAI.java b/app/src/main/java/chess/ai/HungryAI.java index ed2303c..435c465 100644 --- a/app/src/main/java/chess/ai/HungryAI.java +++ b/app/src/main/java/chess/ai/HungryAI.java @@ -26,7 +26,7 @@ public class HungryAI extends AI { private int getMoveCost(Move move) { Piece piece = pieceAt(move.getDeadPieceCoords()); - return -pieceCost.getCost(piece); + return - (int) pieceCost.getCost(piece); } private List getBestMoves() { diff --git a/app/src/main/java/chess/ai/PieceCost.java b/app/src/main/java/chess/ai/PieceCost.java index 385be77..8654688 100644 --- a/app/src/main/java/chess/ai/PieceCost.java +++ b/app/src/main/java/chess/ai/PieceCost.java @@ -10,57 +10,57 @@ import chess.model.pieces.Pawn; import chess.model.pieces.Queen; import chess.model.pieces.Rook; -public class PieceCost implements PieceVisitor { +public class PieceCost implements PieceVisitor { private final Color player; - public static final int BISHOP = 30; - public static final int KING = 900; - public static final int KNIGHT = 30; - public static final int PAWN = 10; - public static final int QUEEN = 90; - public static final int ROOK = 50; + public static final float BISHOP = 30; + public static final float KING = 900; + public static final float KNIGHT = 30; + public static final float PAWN = 10; + public static final float QUEEN = 90; + public static final float ROOK = 50; public PieceCost(Color color) { this.player = color; } - public int getCost(Piece piece) { + public float getCost(Piece piece) { if (piece == null) return 0; - int cost = visit(piece); + float cost = visit(piece); if (piece.getColor() != player) cost = -cost; return cost; } @Override - public Integer visitPiece(Bishop bishop) { + public Float visitPiece(Bishop bishop) { return BISHOP; } @Override - public Integer visitPiece(King king) { + public Float visitPiece(King king) { return KING; } @Override - public Integer visitPiece(Knight knight) { + public Float visitPiece(Knight knight) { return KNIGHT; } @Override - public Integer visitPiece(Pawn pawn) { + public Float visitPiece(Pawn pawn) { return PAWN; } @Override - public Integer visitPiece(Queen queen) { + public Float visitPiece(Queen queen) { return QUEEN; } @Override - public Integer visitPiece(Rook rook) { + public Float visitPiece(Rook rook) { return ROOK; } diff --git a/app/src/main/java/chess/ai/PiecePosCost.java b/app/src/main/java/chess/ai/PiecePosCost.java index be1478e..68af25e 100644 --- a/app/src/main/java/chess/ai/PiecePosCost.java +++ b/app/src/main/java/chess/ai/PiecePosCost.java @@ -14,114 +14,115 @@ import chess.model.pieces.Pawn; import chess.model.pieces.Queen; import chess.model.pieces.Rook; -public class PiecePosCost implements PieceVisitor> { +public class PiecePosCost implements PieceVisitor> { private final Color color; - private static final List BISHOP = Arrays.asList( - -2, -1, -1, -1, -1, -1, -1, -2, -1, - 0, 0, 0, 0, 0, 0, -1, - -1, 0, 0, 1, 1, 0, 0, -1, - -1, 0, 0, 1, 1, 0, 0, -1, - -1, 0, 0, 1, 1, 0, 0, -1, - -1, 1, 1, 1, 1, 1, 1, -1, - -1, 0, 0, 0, 0, 0, 0, -1, - -2, -1, -1, -1, -1, -1, -1, -2); + private static final List BISHOP = Arrays.asList( + -2.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -2.0f, + -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, + -1.0f, 0.0f, 0.5f, 1.0f, 1.0f, 0.5f, 0.0f, -1.0f, + -1.0f, 0.5f, 0.5f, 1.0f, 1.0f, 0.5f, 0.5f, -1.0f, + -1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, -1.0f, + -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, -1.0f, + -1.0f, 0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 0.5f, -1.0f, + -2.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -2.0f); - private static final List KING = Arrays.asList( - -3, -4, -4, -5, -5, -4, -4, -3, - -3, -4, -4, -5, -5, -4, -4, -3, - -3, -4, -4, -5, -5, -4, -4, -3, - -3, -4, -4, -5, -5, -4, -4, -3, - -2, -3, -3, -4, -4, -3, -3, -2, - -1, -2, -2, -2, -2, -2, -2, -1, - 2, 2, 0, 0, 0, 0, 2, 2, - 2, 3, 1, 0, 0, 1, 3, 2); + private static final List KING = Arrays.asList( + -3.0f, -4.0f, -4.0f, -5.0f, -5.0f, -4.0f, -4.0f, -3.0f, + -3.0f, -4.0f, -4.0f, -5.0f, -5.0f, -4.0f, -4.0f, -3.0f, + -3.0f, -4.0f, -4.0f, -5.0f, -5.0f, -4.0f, -4.0f, -3.0f, + -3.0f, -4.0f, -4.0f, -5.0f, -5.0f, -4.0f, -4.0f, -3.0f, + -2.0f, -3.0f, -3.0f, -4.0f, -4.0f, -3.0f, -3.0f, -2.0f, + -1.0f, -2.0f, -2.0f, -2.0f, -2.0f, -2.0f, -2.0f, -1.0f, + 2.0f, 2.0f, 0.0f, 0.0f, 0.0f, 0.0f, 2.0f, 2.0f, + 2.0f, 3.0f, 1.0f, 0.0f, 0.0f, 1.0f, 3.0f, 2.0f); - private static final List KNIGHT = Arrays.asList( - -5, -4, -3, -3, -3, -3, -4, -5, -4, - -2, 0, 0, 0, 0, -2, -4, - -3, 0, 1, 1, 1, 1, 0, -3, - -3, 0, 1, 2, 2, 1, 0, -3, - -3, 0, 1, 2, 2, 1, 0, -3, - -3, 0, 1, 1, 1, 1, 0, -3, - -4, -2, 0, 0, 0, 0, -2, -4, - -5, -4, -3, -3, -3, -3, -4, -5); + private static final List KNIGHT = Arrays.asList( + -5.0f, -4.0f, -3.0f, -3.0f, -3.0f, -3.0f, -4.0f, -5.0f, + -4.0f, -2.0f, 0.0f, 0.0f, 0.0f, 0.0f, -2.0f, -4.0f, + -3.0f, 0.0f, 1.0f, 1.5f, 1.5f, 1.0f, 0.0f, -3.0f, + -3.0f, 0.5f, 1.5f, 2.0f, 2.0f, 1.5f, 0.5f, -3.0f, + -3.0f, 0.0f, 1.5f, 2.0f, 2.0f, 1.5f, 0.0f, -3.0f, + -3.0f, 0.5f, 1.0f, 1.5f, 1.5f, 1.0f, 0.5f, -3.0f, + -4.0f, -2.0f, 0.0f, 0.5f, 0.5f, 0.0f, -2.0f, -4.0f, + -5.0f, -4.0f, -3.0f, -3.0f, -3.0f, -3.0f, -4.0f, -5.0f); - private static final List PAWN = Arrays.asList( - 0, 0, 0, 0, 0, 0, 0, 0, - 5, 5, 5, 5, 5, 5, 5, 5, - 1, 1, 2, 3, 3, 2, 1, 1, - 0, 0, 1, 2, 2, 1, 0, 0, - 0, 0, 1, 2, 2, 1, 0, 0, - 0, 0, -1, 0, 0, -1, 0, 0, - 0, 1, 1, -2, -2, 1, 1, 0, - 0, 0, 0, 0, 0, 0, 0, 0); + private static final List PAWN = Arrays.asList( + 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, + 5.0f, 5.0f, 5.0f, 5.0f, 5.0f, 5.0f, 5.0f, 5.0f, + 1.0f, 1.0f, 2.0f, 3.0f, 3.0f, 2.0f, 1.0f, 1.0f, + 0.5f, 0.5f, 1.0f, 2.5f, 2.5f, 1.0f, 0.5f, 0.5f, + 0.0f, 0.0f, 1.0f, 2.0f, 2.0f, 1.0f, 0.0f, 0.0f, + 0.5f, -0.5f, -1.0f, 0.0f, 0.0f, -1.0f, -0.5f, 0.5f, + 0.5f, 1.0f, 1.0f, -2.0f, -2.0f, 1.0f, 1.0f, 0.5f, + 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f); - private static final List QUEEN = Arrays.asList( - -2, -1, -1, 0, 0, -1, -1, -2, - -1, 0, 0, 0, 0, 0, 0, -1, - -1, 0, 0, 0, 0, 0, 0, -1, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - -1, 0, 0, 0, 0, 0, 0, -1, - -1, 0, 0, 0, 0, 0, 0, -1, - -2, -1, -1, 0, 0, -1, -1, -2); + private static final List QUEEN = Arrays.asList( + -2.0f, -1.0f, -1.0f, -0.5f, -0.5f, -1.0f, -1.0f, -2.0f, + -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, + -1.0f, 0.0f, 0.5f, 0.5f, 0.5f, 0.5f, 0.0f, -1.0f, + -0.5f, 0.0f, 0.5f, 0.5f, 0.5f, 0.5f, 0.0f, -0.5f, + 0.0f, 0.0f, 0.5f, 0.5f, 0.5f, 0.5f, 0.0f, -0.5f, + -1.0f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f, 0.0f, -1.0f, + -1.0f, 0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, + -2.0f, -1.0f, -1.0f, -0.5f, -0.5f, -1.0f, -1.0f, -2.0f); - private static final List ROOK = Arrays.asList( - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 1, 1, 1, 1, 1, 1, 0, - -1, 0, 0, 0, 0, 0, 0, 0, -1, - -1, 0, 0, 0, 0, 0, 0, 0, -1, - -1, 0, 0, 0, 0, 0, 0, 0, -1, - -1, 0, 0, 0, 0, 0, 0, 0, -1, - -1, 0, 0, 0, 0, 0, 0, 0, -1, - 0, 0, 0, 0, 1, 1, 0, 0, 0); + private static final List ROOK = Arrays.asList( + 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, + 0.5f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.5f, + -0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -0.5f, + -0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -0.5f, + -0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -0.5f, + -0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -0.5f, + -0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -0.5f, + 0.0f, 0.0f, 0.0f, 0.5f, 0.5f, 0.0f, 0.0f, 0.0f); public PiecePosCost(Color color) { this.color = color; } - public int getEvaluation(Piece piece, Coordinate coordinate) { + public float getEvaluation(Piece piece, Coordinate coordinate) { if (piece == null) return 0; - List positions = visit(piece); + List positions = visit(piece); + int x = piece.getColor() == Color.Black ? (Coordinate.VALUE_MAX - 1 - coordinate.getX()) : coordinate.getX(); int y = piece.getColor() == Color.Black ? (Coordinate.VALUE_MAX - 1 - coordinate.getY()) : coordinate.getY(); - Coordinate newCoords = new Coordinate(coordinate.getX(), y); + Coordinate newCoords = new Coordinate(x, y); assert newCoords.isValid(); - int result = positions.get(newCoords.toIndex()); + float result = positions.get(newCoords.toIndex()); if (piece.getColor() != color) return -result; return result; } @Override - public List visitPiece(Bishop bishop) { + public List visitPiece(Bishop bishop) { return BISHOP; } @Override - public List visitPiece(King king) { + public List visitPiece(King king) { return KING; } @Override - public List visitPiece(Knight knight) { + public List visitPiece(Knight knight) { return KNIGHT; } @Override - public List visitPiece(Pawn pawn) { + public List visitPiece(Pawn pawn) { return PAWN; } @Override - public List visitPiece(Queen queen) { + public List visitPiece(Queen queen) { return QUEEN; } @Override - public List visitPiece(Rook rook) { + public List visitPiece(Rook rook) { return ROOK; } diff --git a/app/src/main/java/chess/controller/CommandExecutor.java b/app/src/main/java/chess/controller/CommandExecutor.java index 5724f26..f33040f 100644 --- a/app/src/main/java/chess/controller/CommandExecutor.java +++ b/app/src/main/java/chess/controller/CommandExecutor.java @@ -2,6 +2,7 @@ package chess.controller; import chess.controller.Command.CommandResult; import chess.controller.commands.UndoCommand; +import chess.controller.event.AsyncGameDispatcher; import chess.controller.event.GameDispatcher; import chess.controller.event.GameListener; import chess.model.Game; @@ -17,7 +18,7 @@ public class CommandExecutor { } public CommandExecutor(Game game) { - this(game, new GameDispatcher()); + this(game, new AsyncGameDispatcher()); } public CommandExecutor(Game game, GameDispatcher dispatcher) { @@ -109,6 +110,6 @@ public class CommandExecutor { } public void close() { - this.dispatcher.stopService(); + this.dispatcher.close(); } } diff --git a/app/src/main/java/chess/controller/event/AsyncGameDispatcher.java b/app/src/main/java/chess/controller/event/AsyncGameDispatcher.java new file mode 100644 index 0000000..49c9343 --- /dev/null +++ b/app/src/main/java/chess/controller/event/AsyncGameDispatcher.java @@ -0,0 +1,113 @@ +package chess.controller.event; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.function.Consumer; + +import chess.controller.commands.PromoteCommand.PromoteType; +import chess.model.Color; +import chess.model.Coordinate; +import chess.model.Move; + +public class AsyncGameDispatcher extends GameDispatcher { + + private final List listeners; + private final ExecutorService executor; + + public AsyncGameDispatcher() { + this.listeners = new ArrayList<>(); + this.executor = Executors.newSingleThreadExecutor(); + } + + @Override + public void addListener(GameListener listener) { + this.listeners.add(listener); + } + + private void asyncForEachCall(Consumer func) { + this.executor.execute(() -> this.listeners.forEach(func)); + } + + @Override + public void onPlayerTurn(Color color, boolean undone) { + asyncForEachCall((l) -> l.onPlayerTurn(color, undone)); + } + + @Override + public void onWin(Color color) { + asyncForEachCall((l) -> l.onWin(color)); + } + + @Override + public void onKingInCheck() { + asyncForEachCall((l) -> l.onKingInCheck()); + } + + @Override + public void onKingInMat() { + asyncForEachCall((l) -> l.onKingInMat()); + } + + @Override + public void onPatSituation() { + asyncForEachCall((l) -> l.onPatSituation()); + } + + @Override + public void onSurrender(Color color) { + asyncForEachCall((l) -> l.onSurrender(color)); + } + + @Override + public void onGameStart() { + asyncForEachCall((l) -> l.onGameStart()); + } + + @Override + public void onPromotePawn(Coordinate pieceCoords) { + asyncForEachCall((l) -> l.onPromotePawn(pieceCoords)); + } + + @Override + public void onBoardUpdate() { + asyncForEachCall((l) -> l.onBoardUpdate()); + } + + @Override + public void onGameEnd() { + asyncForEachCall((l) -> l.onGameEnd()); + } + + @Override + public void onMove(Move move) { + asyncForEachCall((l) -> l.onMove(move)); + } + + @Override + public void onMoveNotAllowed(Move move) { + asyncForEachCall((l) -> l.onMoveNotAllowed(move)); + } + + @Override + public void onDraw() { + asyncForEachCall((l) -> l.onDraw()); + } + + @Override + public void onCastling(boolean bigCastling) { + asyncForEachCall((l) -> l.onCastling(bigCastling)); + } + + @Override + public void onPawnPromoted(PromoteType promotion) { + asyncForEachCall((l) -> l.onPawnPromoted(promotion)); + } + + @Override + public void close() { + this.executor.shutdown(); + } + +} diff --git a/app/src/main/java/chess/controller/event/EmptyGameDispatcher.java b/app/src/main/java/chess/controller/event/EmptyGameDispatcher.java index 5e1b9ab..8ad5084 100644 --- a/app/src/main/java/chess/controller/event/EmptyGameDispatcher.java +++ b/app/src/main/java/chess/controller/event/EmptyGameDispatcher.java @@ -67,4 +67,12 @@ public class EmptyGameDispatcher extends GameDispatcher { public void onPawnPromoted(PromoteType promotion) { } + @Override + public void addListener(GameListener listener) { + } + + @Override + public void close() { + } + } diff --git a/app/src/main/java/chess/controller/event/GameDispatcher.java b/app/src/main/java/chess/controller/event/GameDispatcher.java index 06023fe..87c3f4d 100644 --- a/app/src/main/java/chess/controller/event/GameDispatcher.java +++ b/app/src/main/java/chess/controller/event/GameDispatcher.java @@ -1,111 +1,9 @@ package chess.controller.event; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.function.Consumer; +public abstract class GameDispatcher extends GameAdaptator { -import chess.controller.commands.PromoteCommand.PromoteType; -import chess.model.Color; -import chess.model.Coordinate; -import chess.model.Move; - -public class GameDispatcher implements GameListener { - - private final List listeners; - private final ExecutorService executor; - - public GameDispatcher() { - this.listeners = new ArrayList<>(); - this.executor = Executors.newSingleThreadExecutor(); - } - - public void addListener(GameListener listener) { - this.listeners.add(listener); - } - - private void asyncForEachCall(Consumer func) { - this.executor.execute(() -> this.listeners.forEach(func)); - } - - @Override - public void onPlayerTurn(Color color, boolean undone) { - asyncForEachCall((l) -> l.onPlayerTurn(color, undone)); - } - - @Override - public void onWin(Color color) { - asyncForEachCall((l) -> l.onWin(color)); - } - - @Override - public void onKingInCheck() { - asyncForEachCall((l) -> l.onKingInCheck()); - } - - @Override - public void onKingInMat() { - asyncForEachCall((l) -> l.onKingInMat()); - } - - @Override - public void onPatSituation() { - asyncForEachCall((l) -> l.onPatSituation()); - } - - @Override - public void onSurrender(Color color) { - asyncForEachCall((l) -> l.onSurrender(color)); - } - - @Override - public void onGameStart() { - asyncForEachCall((l) -> l.onGameStart()); - } - - @Override - public void onPromotePawn(Coordinate pieceCoords) { - asyncForEachCall((l) -> l.onPromotePawn(pieceCoords)); - } - - @Override - public void onBoardUpdate() { - asyncForEachCall((l) -> l.onBoardUpdate()); - } - - @Override - public void onGameEnd() { - asyncForEachCall((l) -> l.onGameEnd()); - } - - @Override - public void onMove(Move move) { - asyncForEachCall((l) -> l.onMove(move)); - } - - @Override - public void onMoveNotAllowed(Move move) { - asyncForEachCall((l) -> l.onMoveNotAllowed(move)); - } - - @Override - public void onDraw() { - asyncForEachCall((l) -> l.onDraw()); - } - - @Override - public void onCastling(boolean bigCastling) { - asyncForEachCall((l) -> l.onCastling(bigCastling)); - } - - @Override - public void onPawnPromoted(PromoteType promotion) { - asyncForEachCall((l) -> l.onPawnPromoted(promotion)); - } - - public void stopService() { - this.executor.shutdown(); - } + public abstract void addListener(GameListener listener); + public abstract void close(); + } -- 2.49.1 From c289546914ad453aef987a8e4affb9c7a7a74d91 Mon Sep 17 00:00:00 2001 From: Persson-dev Date: Sat, 19 Apr 2025 14:15:53 +0200 Subject: [PATCH 05/15] multithreading --- app/src/main/java/chess/SwingMain.java | 6 +- app/src/main/java/chess/ai/AlphaBetaAI.java | 152 ------------------ .../java/chess/ai/minimax/AlphaBetaAI.java | 88 ++++++++++ .../chess/ai/minimax/AlphaBetaThread.java | 101 ++++++++++++ .../ai/minimax/AlphaBetaThreadCreator.java | 37 +++++ .../ai/{ => minimax}/GameSimulation.java | 2 +- 6 files changed, 230 insertions(+), 156 deletions(-) delete mode 100644 app/src/main/java/chess/ai/AlphaBetaAI.java create mode 100644 app/src/main/java/chess/ai/minimax/AlphaBetaAI.java create mode 100644 app/src/main/java/chess/ai/minimax/AlphaBetaThread.java create mode 100644 app/src/main/java/chess/ai/minimax/AlphaBetaThreadCreator.java rename app/src/main/java/chess/ai/{ => minimax}/GameSimulation.java (98%) diff --git a/app/src/main/java/chess/SwingMain.java b/app/src/main/java/chess/SwingMain.java index bbaafcc..e96ec87 100644 --- a/app/src/main/java/chess/SwingMain.java +++ b/app/src/main/java/chess/SwingMain.java @@ -1,9 +1,9 @@ package chess; import chess.ai.AI; -import chess.ai.AlphaBetaAI; import chess.ai.DumbAI; import chess.ai.HungryAI; +import chess.ai.minimax.AlphaBetaAI; import chess.controller.CommandExecutor; import chess.controller.commands.NewGameCommand; import chess.controller.event.GameAdaptator; @@ -20,10 +20,10 @@ public class SwingMain { Window window = new Window(commandExecutor, false); commandExecutor.addListener(window); - AI ai = new HungryAI(commandExecutor, Color.White); + AI ai = new AlphaBetaAI(commandExecutor, Color.White, 3); commandExecutor.addListener(ai); - AI ai2 = new AlphaBetaAI(commandExecutor, Color.Black, 1); + AI ai2 = new AlphaBetaAI(commandExecutor, Color.Black, 3); commandExecutor.addListener(ai2); // Window window2 = new Window(ai2.getSimulation(), false); diff --git a/app/src/main/java/chess/ai/AlphaBetaAI.java b/app/src/main/java/chess/ai/AlphaBetaAI.java deleted file mode 100644 index 7a9042c..0000000 --- a/app/src/main/java/chess/ai/AlphaBetaAI.java +++ /dev/null @@ -1,152 +0,0 @@ -package chess.ai; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; - -import chess.controller.CommandExecutor; -import chess.controller.commands.MoveCommand; -import chess.controller.commands.PromoteCommand; -import chess.controller.commands.PromoteCommand.PromoteType; -import chess.model.ChessBoard; -import chess.model.Color; -import chess.model.Coordinate; -import chess.model.Move; -import chess.model.Piece; - -public class AlphaBetaAI extends AI { - - private final int searchDepth; - private final PieceCost pieceCost; - private final PiecePosCost piecePosCost; - private final GameSimulation simulation; - - private static final float MAX_FLOAT = Float.MAX_VALUE; - private static final float MIN_FLOAT = -MAX_FLOAT; - - private static final int GREAT_MOVE = -9999; - private static final int HORRIBLE_MOVE = -GREAT_MOVE; - - private final ExecutorService threadPool; - - public AlphaBetaAI(CommandExecutor commandExecutor, Color color, int searchDepth) { - super(commandExecutor, color); - this.searchDepth = searchDepth; - this.pieceCost = new PieceCost(color); - this.piecePosCost = new PiecePosCost(color); - this.simulation = new GameSimulation(); - commandExecutor.addListener(simulation); - this.threadPool = Executors.newSingleThreadExecutor(); - System.out.println(Runtime.getRuntime().availableProcessors()); - } - - private float getBoardEvaluation() { - final ChessBoard board = this.simulation.getBoard(); - float result = 0; - for (int i = 0; i < Coordinate.VALUE_MAX; i++) { - for (int j = 0; j < Coordinate.VALUE_MAX; j++) { - Coordinate coordinate = new Coordinate(i, j); - Piece piece = board.pieceAt(coordinate); - result += pieceCost.getCost(piece) + piecePosCost.getEvaluation(piece, coordinate); - } - } - if (this.simulation.getPlayerTurn() != color) - return -result; - return result; - } - - private float getEndGameEvaluation() { - Color currentTurn = this.simulation.getPlayerTurn(); - if (currentTurn == this.color) { - return HORRIBLE_MOVE; - } else { - if (this.simulation.getBoard().isKingInCheck(currentTurn)) - return GREAT_MOVE; - return getBoardEvaluation() + PieceCost.PAWN; - } - } - - private float getMoveValue(Move move) { - this.simulation.tryMove(move); - float value = -negaMax(this.searchDepth - 1, MIN_FLOAT, MAX_FLOAT); - this.simulation.undoMove(); - return value; - } - - private float negaMax(int depth, float alpha, float beta) { - if (depth == 0) - return getBoardEvaluation(); - - float value = MIN_FLOAT; - - List moves = this.simulation.getAllowedMoves(); - - if (moves.isEmpty()) - return getEndGameEvaluation(); - - for (Move move : moves) { - this.simulation.tryMove(move); - value = Float.max(value, -negaMax(depth - 1, -beta, -alpha)); - this.simulation.undoMove(); - alpha = Float.max(alpha, value); - if (alpha >= beta) - return value; - } - - return value; - } - - private Move getBestMove() { - List moves = this.simulation.getAllowedMoves(); - List> moveEvaluations = new ArrayList<>(50); - float bestMoveValue = MIN_FLOAT; - Move bestMove = null; - - for (Move move : moves) { - moveEvaluations.add(this.threadPool.submit(() -> { - return getMoveValue(move); - })); - } - - for (int i = 0; i < moves.size(); i++) { - Move move = moves.get(i); - float value = MIN_FLOAT; - try { - value = moveEvaluations.get(i).get(); - } catch (InterruptedException | ExecutionException e) { - e.printStackTrace(); - } - if (value > bestMoveValue) { - bestMoveValue = value; - bestMove = move; - } - } - - System.out.println("Best move : " + bestMoveValue); - - return bestMove; - } - - @Override - public void onGameEnd() { - this.threadPool.close(); - } - - @Override - protected void play() { - long current = System.currentTimeMillis(); - Move move = getBestMove(); - long elapsed = System.currentTimeMillis() - current; - System.out.println("Took " + elapsed + "ms"); - sendCommand(new MoveCommand(move)); - } - - @Override - protected void promote(Coordinate pawnCoords) { - sendCommand(new PromoteCommand(PromoteType.Queen)); - } - -} diff --git a/app/src/main/java/chess/ai/minimax/AlphaBetaAI.java b/app/src/main/java/chess/ai/minimax/AlphaBetaAI.java new file mode 100644 index 0000000..092c111 --- /dev/null +++ b/app/src/main/java/chess/ai/minimax/AlphaBetaAI.java @@ -0,0 +1,88 @@ +package chess.ai.minimax; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +import chess.ai.AI; +import chess.controller.CommandExecutor; +import chess.controller.commands.MoveCommand; +import chess.controller.commands.PromoteCommand; +import chess.controller.commands.PromoteCommand.PromoteType; +import chess.model.Color; +import chess.model.Coordinate; +import chess.model.Move; + +public class AlphaBetaAI extends AI { + + private final int searchDepth; + + private static final float MAX_FLOAT = Float.MAX_VALUE; + private static final float MIN_FLOAT = -MAX_FLOAT; + + private final ExecutorService threadPool; + + public AlphaBetaAI(CommandExecutor commandExecutor, Color color, int searchDepth) { + super(commandExecutor, color); + this.searchDepth = searchDepth; + int threadCount = Runtime.getRuntime().availableProcessors() - 1; + this.threadPool = Executors.newFixedThreadPool(threadCount, new AlphaBetaThreadCreator(commandExecutor, color, threadCount)); + System.out.println(); + } + + private Move getBestMove() { + List moves = getAllowedMoves(); + List> moveEvaluations = new ArrayList<>(50); + float bestMoveValue = MIN_FLOAT; + Move bestMove = null; + + System.out.println("Evaluating " + moves.size() + " moves ..."); + + for (Move move : moves) { + moveEvaluations.add(this.threadPool.submit(() -> { + return AlphaBetaThreadCreator.getMoveValue(move, this.searchDepth); + })); + } + + for (int i = 0; i < moves.size(); i++) { + Move move = moves.get(i); + float value = MIN_FLOAT; + try { + value = moveEvaluations.get(i).get(); + } catch (InterruptedException | ExecutionException e) { + e.printStackTrace(); + } + if (value > bestMoveValue) { + bestMoveValue = value; + bestMove = move; + } + } + + System.out.println("Best move : " + bestMoveValue); + + return bestMove; + } + + @Override + public void onGameEnd() { + this.threadPool.close(); + } + + @Override + protected void play() { + long current = System.currentTimeMillis(); + Move move = getBestMove(); + long elapsed = System.currentTimeMillis() - current; + System.out.println("Took " + elapsed + "ms"); + sendCommand(new MoveCommand(move)); + } + + @Override + protected void promote(Coordinate pawnCoords) { + sendCommand(new PromoteCommand(PromoteType.Queen)); + } + +} diff --git a/app/src/main/java/chess/ai/minimax/AlphaBetaThread.java b/app/src/main/java/chess/ai/minimax/AlphaBetaThread.java new file mode 100644 index 0000000..612d9d4 --- /dev/null +++ b/app/src/main/java/chess/ai/minimax/AlphaBetaThread.java @@ -0,0 +1,101 @@ +package chess.ai.minimax; + +import java.util.List; + +import chess.ai.PieceCost; +import chess.ai.PiecePosCost; +import chess.controller.CommandExecutor; +import chess.model.ChessBoard; +import chess.model.Color; +import chess.model.Coordinate; +import chess.model.Move; +import chess.model.Piece; + +public class AlphaBetaThread extends Thread { + + private final GameSimulation simulation; + private final PieceCost pieceCost; + private final PiecePosCost piecePosCost; + + private final Color color; + + private static final int GREAT_MOVE = -9999; + private static final int HORRIBLE_MOVE = -GREAT_MOVE; + + private static final float MAX_FLOAT = Float.MAX_VALUE; + private static final float MIN_FLOAT = -MAX_FLOAT; + + public AlphaBetaThread(Runnable task, GameSimulation simulation, Color color) { + super(task); + this.simulation = simulation; + this.pieceCost = new PieceCost(color); + this.piecePosCost = new PiecePosCost(color); + this.color = color; + } + + private float getEndGameEvaluation() { + Color currentTurn = this.simulation.getPlayerTurn(); + if (currentTurn == this.color) { + return HORRIBLE_MOVE; + } else { + if (this.simulation.getBoard().isKingInCheck(currentTurn)) + return GREAT_MOVE; + return getBoardEvaluation() + PieceCost.PAWN; + } + } + + private float getBoardEvaluation() { + final ChessBoard board = this.simulation.getBoard(); + float result = 0; + for (int i = 0; i < Coordinate.VALUE_MAX; i++) { + for (int j = 0; j < Coordinate.VALUE_MAX; j++) { + Coordinate coordinate = new Coordinate(i, j); + Piece piece = board.pieceAt(coordinate); + result += pieceCost.getCost(piece) + piecePosCost.getEvaluation(piece, coordinate); + } + } + if (this.simulation.getPlayerTurn() != color) + return -result; + return result; + } + + public float getMoveValue(Move move, int searchDepth) { + this.simulation.tryMove(move); + float value = -negaMax(searchDepth - 1, MIN_FLOAT, MAX_FLOAT); + this.simulation.undoMove(); + return value; + } + + private float negaMax(int depth, float alpha, float beta) { + if (depth == 0) + return getBoardEvaluation(); + + float value = MIN_FLOAT; + + List moves = this.simulation.getAllowedMoves(); + + if (moves.isEmpty()) + return getEndGameEvaluation(); + + for (Move move : moves) { + this.simulation.tryMove(move); + value = Float.max(value, -negaMax(depth - 1, -beta, -alpha)); + this.simulation.undoMove(); + alpha = Float.max(alpha, value); + if (alpha >= beta) + return value; + } + + return value; + } + + public GameSimulation getSimulation() { + return simulation; + } + + @Override + public void run() { + super.run(); + } + +} diff --git a/app/src/main/java/chess/ai/minimax/AlphaBetaThreadCreator.java b/app/src/main/java/chess/ai/minimax/AlphaBetaThreadCreator.java new file mode 100644 index 0000000..2013e87 --- /dev/null +++ b/app/src/main/java/chess/ai/minimax/AlphaBetaThreadCreator.java @@ -0,0 +1,37 @@ +package chess.ai.minimax; + +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; + +import chess.controller.CommandExecutor; +import chess.model.Color; +import chess.model.Move; + +public class AlphaBetaThreadCreator implements ThreadFactory{ + + private final Color color; + private final GameSimulation simulations[]; + private int currentThread = 0; + + public AlphaBetaThreadCreator(CommandExecutor commandExecutor, Color color, int threadCount) { + this.color = color; + simulations = new GameSimulation[threadCount]; + for (int i = 0; i < threadCount; i++) { + simulations[i] = new GameSimulation(); + commandExecutor.addListener(simulations[i]); + } + } + + public static float getMoveValue(Move move, int searchDepth) { + AlphaBetaThread t = (AlphaBetaThread) Thread.currentThread(); + return t.getMoveValue(move, searchDepth); + } + + @Override + public Thread newThread(Runnable r) { + AlphaBetaThread t = new AlphaBetaThread(r, simulations[currentThread], color); + currentThread++; + return t; + } + +} diff --git a/app/src/main/java/chess/ai/GameSimulation.java b/app/src/main/java/chess/ai/minimax/GameSimulation.java similarity index 98% rename from app/src/main/java/chess/ai/GameSimulation.java rename to app/src/main/java/chess/ai/minimax/GameSimulation.java index c1525b9..1fb42c6 100644 --- a/app/src/main/java/chess/ai/GameSimulation.java +++ b/app/src/main/java/chess/ai/minimax/GameSimulation.java @@ -1,4 +1,4 @@ -package chess.ai; +package chess.ai.minimax; import java.util.List; -- 2.49.1 From 52d043b888ecf24a466655c17653df1bc1ffa585 Mon Sep 17 00:00:00 2001 From: Persson-dev Date: Sat, 19 Apr 2025 14:15:59 +0200 Subject: [PATCH 06/15] fix pgn winner --- app/src/main/java/chess/pgn/PgnExport.java | 32 +++++++++++----------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/chess/pgn/PgnExport.java b/app/src/main/java/chess/pgn/PgnExport.java index 31e57bd..e4fb6d4 100644 --- a/app/src/main/java/chess/pgn/PgnExport.java +++ b/app/src/main/java/chess/pgn/PgnExport.java @@ -21,24 +21,24 @@ import chess.model.pieces.Pawn; public class PgnExport { // public static void main(String[] args) { - // final Game game = new Game(); - // final CommandExecutor commandExecutor = new CommandExecutor(game); + // final Game game = new Game(); + // final CommandExecutor commandExecutor = new CommandExecutor(game); - // DumbAI ai1 = new DumbAI(commandExecutor, Color.White); - // commandExecutor.addListener(ai1); + // DumbAI ai1 = new DumbAI(commandExecutor, Color.White); + // commandExecutor.addListener(ai1); - // DumbAI ai2 = new DumbAI(commandExecutor, Color.Black); - // commandExecutor.addListener(ai2); + // DumbAI ai2 = new DumbAI(commandExecutor, Color.Black); + // commandExecutor.addListener(ai2); - // commandExecutor.addListener(new GameAdaptator() { - // @Override - // public void onGameEnd() { - // System.out.println(exportGame(game)); - // commandExecutor.close(); - // } - // }); + // commandExecutor.addListener(new GameAdaptator() { + // @Override + // public void onGameEnd() { + // System.out.println(exportGame(game)); + // commandExecutor.close(); + // } + // }); - // commandExecutor.executeCommand(new NewGameCommand()); + // commandExecutor.executeCommand(new NewGameCommand()); // } private static final PiecePgnName piecePgnName = new PiecePgnName(); @@ -63,8 +63,8 @@ public class PgnExport { case CheckMate: if (game.getPlayerTurn() == Color.White) - return "1-0"; - return "0-1"; + return "0-1"; + return "1-0"; default: return ""; -- 2.49.1 From 1022be4ecd9141e8aad31db2afd69f7d2a476972 Mon Sep 17 00:00:00 2001 From: Persson-dev Date: Sat, 19 Apr 2025 14:17:39 +0200 Subject: [PATCH 07/15] yes --- app/src/main/java/chess/SwingMain.java | 2 -- app/src/main/java/chess/ai/minimax/AlphaBetaThread.java | 7 ------- .../main/java/chess/ai/minimax/AlphaBetaThreadCreator.java | 1 - 3 files changed, 10 deletions(-) diff --git a/app/src/main/java/chess/SwingMain.java b/app/src/main/java/chess/SwingMain.java index e96ec87..b52dc41 100644 --- a/app/src/main/java/chess/SwingMain.java +++ b/app/src/main/java/chess/SwingMain.java @@ -1,8 +1,6 @@ package chess; import chess.ai.AI; -import chess.ai.DumbAI; -import chess.ai.HungryAI; import chess.ai.minimax.AlphaBetaAI; import chess.controller.CommandExecutor; import chess.controller.commands.NewGameCommand; diff --git a/app/src/main/java/chess/ai/minimax/AlphaBetaThread.java b/app/src/main/java/chess/ai/minimax/AlphaBetaThread.java index 612d9d4..c5e919c 100644 --- a/app/src/main/java/chess/ai/minimax/AlphaBetaThread.java +++ b/app/src/main/java/chess/ai/minimax/AlphaBetaThread.java @@ -4,7 +4,6 @@ import java.util.List; import chess.ai.PieceCost; import chess.ai.PiecePosCost; -import chess.controller.CommandExecutor; import chess.model.ChessBoard; import chess.model.Color; import chess.model.Coordinate; @@ -92,10 +91,4 @@ public class AlphaBetaThread extends Thread { public GameSimulation getSimulation() { return simulation; } - - @Override - public void run() { - super.run(); - } - } diff --git a/app/src/main/java/chess/ai/minimax/AlphaBetaThreadCreator.java b/app/src/main/java/chess/ai/minimax/AlphaBetaThreadCreator.java index 2013e87..8bee1d9 100644 --- a/app/src/main/java/chess/ai/minimax/AlphaBetaThreadCreator.java +++ b/app/src/main/java/chess/ai/minimax/AlphaBetaThreadCreator.java @@ -1,7 +1,6 @@ package chess.ai.minimax; import java.util.concurrent.ThreadFactory; -import java.util.concurrent.ThreadPoolExecutor; import chess.controller.CommandExecutor; import chess.model.Color; -- 2.49.1 From 7b487ef82d3af1677144931527655639da71d638 Mon Sep 17 00:00:00 2001 From: Persson-dev Date: Sat, 19 Apr 2025 21:34:52 +0200 Subject: [PATCH 08/15] optimize execution speed --- app/src/main/java/chess/controller/CommandExecutor.java | 2 +- app/src/main/java/chess/model/Game.java | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/chess/controller/CommandExecutor.java b/app/src/main/java/chess/controller/CommandExecutor.java index f33040f..2b11eaa 100644 --- a/app/src/main/java/chess/controller/CommandExecutor.java +++ b/app/src/main/java/chess/controller/CommandExecutor.java @@ -55,7 +55,7 @@ public class CommandExecutor { case Moved: boolean notifyPlayerTurn = true; this.dispatcher.onBoardUpdate(); - if (checkGameStatus()) { + if (!(command instanceof UndoCommand) && checkGameStatus()) { this.dispatcher.onGameEnd(); notifyPlayerTurn = false; } diff --git a/app/src/main/java/chess/model/Game.java b/app/src/main/java/chess/model/Game.java index 4b7be53..360a5d2 100644 --- a/app/src/main/java/chess/model/Game.java +++ b/app/src/main/java/chess/model/Game.java @@ -70,6 +70,7 @@ public class Game { playerTurn = Color.getEnemy(playerTurn); } + // this is the bottleneck of algorithms using this chess engine public GameStatus checkGameStatus(Color color) { if (checkDraw()) return GameStatus.Draw; -- 2.49.1 From 8a853bdc3a5ddfe6e07a06f367727e6b922b4c5f Mon Sep 17 00:00:00 2001 From: Persson-dev Date: Sun, 20 Apr 2025 10:45:03 +0200 Subject: [PATCH 09/15] little more optimisation --- app/src/main/java/chess/model/ChessBoard.java | 49 +++++++++++++------ 1 file changed, 34 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/chess/model/ChessBoard.java b/app/src/main/java/chess/model/ChessBoard.java index ebc15e5..adaf385 100644 --- a/app/src/main/java/chess/model/ChessBoard.java +++ b/app/src/main/java/chess/model/ChessBoard.java @@ -4,7 +4,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; -import chess.model.visitor.KingIdentifier; +import chess.model.pieces.King; import chess.model.visitor.PawnIdentifier; import chess.model.visitor.PiecePathChecker; @@ -31,6 +31,10 @@ public class ChessBoard { private Move lastMove; private Piece lastEjectedPiece; + private List cachedAllowedMoves = null; + + private final Coordinate kingPos[]; + public ChessBoard() { this.cells = new Cell[Coordinate.VALUE_MAX][Coordinate.VALUE_MAX]; for (int i = 0; i < Coordinate.VALUE_MAX; i++) { @@ -41,6 +45,7 @@ public class ChessBoard { this.lastVirtualMove = null; this.lastMove = null; this.lastEjectedPiece = null; + this.kingPos = new Coordinate[Color.values().length]; } public void applyMove(Move move) { @@ -98,6 +103,8 @@ public class ChessBoard { public void pieceComes(Piece piece, Coordinate coordinate) { cellAt(coordinate).setPiece(piece); + if (piece instanceof King) + this.kingPos[piece.getColor().ordinal()] = coordinate; } public void pieceLeaves(Coordinate coordinate) { @@ -105,18 +112,19 @@ public class ChessBoard { } public Coordinate findKing(Color color) { - KingIdentifier kingIdentifier = new KingIdentifier(color); - for (int i = 0; i < Coordinate.VALUE_MAX; i++) { - for (int j = 0; j < Coordinate.VALUE_MAX; j++) { - Coordinate coordinate = new Coordinate(i, j); - Piece piece = pieceAt(coordinate); - if (kingIdentifier.isKing(piece)) { - return coordinate; - } - } - } - assert false : "No king found ?!"; - return null; + // KingIdentifier kingIdentifier = new KingIdentifier(color); + // for (int i = 0; i < Coordinate.VALUE_MAX; i++) { + // for (int j = 0; j < Coordinate.VALUE_MAX; j++) { + // Coordinate coordinate = new Coordinate(i, j); + // Piece piece = pieceAt(coordinate); + // if (kingIdentifier.isKing(piece)) { + // return coordinate; + // } + // } + // } + // assert false : "No king found ?!"; + // return null; + return kingPos[color.ordinal()]; } public boolean isKingInCheck(Color color) { @@ -127,7 +135,7 @@ public class ChessBoard { for (int j = 0; j < Coordinate.VALUE_MAX; j++) { Coordinate attackCoords = new Coordinate(i, j); Piece attackPiece = pieceAt(attackCoords); - if (attackPiece == null) + if (attackPiece == null || attackPiece.getColor() == color) continue; PiecePathChecker checker = new PiecePathChecker(this, new Move(attackCoords, kingPos)); @@ -143,6 +151,10 @@ public class ChessBoard { } public List getAllowedMoves(Color player) { + if (this.cachedAllowedMoves != null) { + return this.cachedAllowedMoves; + } + List result = new ArrayList<>(); for (int x = 0; x < Coordinate.VALUE_MAX; x++) { @@ -159,6 +171,11 @@ public class ChessBoard { Coordinate destination = new Coordinate(i, j); Move move = new Move(start, destination); + Piece destPiece = pieceAt(destination); + + if (destPiece != null && destPiece.getColor() == player) + continue; + PiecePathChecker piecePathChecker = new PiecePathChecker(this, move); if (!piecePathChecker.isValid()) @@ -172,6 +189,7 @@ public class ChessBoard { } } } + this.cachedAllowedMoves = result; return result; } @@ -300,10 +318,11 @@ public class ChessBoard { public void setLastMove(Move lastMove) { this.lastMove = lastMove; + this.cachedAllowedMoves = null; } @Override - public boolean equals(Object obj){ + public boolean equals(Object obj) { if (obj instanceof ChessBoard board) return board.hashCode() == this.hashCode(); return false; -- 2.49.1 From 7bdd9fdc836cb918a73eab9b84a56fb0f32c6943 Mon Sep 17 00:00:00 2001 From: Persson-dev Date: Mon, 21 Apr 2025 14:31:58 +0200 Subject: [PATCH 10/15] improve ai (sort moves) --- .../java/chess/ai/minimax/AlphaBetaAI.java | 7 ++++--- .../chess/ai/minimax/AlphaBetaThread.java | 20 +++++++++++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/chess/ai/minimax/AlphaBetaAI.java b/app/src/main/java/chess/ai/minimax/AlphaBetaAI.java index 092c111..56e987a 100644 --- a/app/src/main/java/chess/ai/minimax/AlphaBetaAI.java +++ b/app/src/main/java/chess/ai/minimax/AlphaBetaAI.java @@ -28,9 +28,8 @@ public class AlphaBetaAI extends AI { public AlphaBetaAI(CommandExecutor commandExecutor, Color color, int searchDepth) { super(commandExecutor, color); this.searchDepth = searchDepth; - int threadCount = Runtime.getRuntime().availableProcessors() - 1; + int threadCount = Runtime.getRuntime().availableProcessors() / 2; this.threadPool = Executors.newFixedThreadPool(threadCount, new AlphaBetaThreadCreator(commandExecutor, color, threadCount)); - System.out.println(); } private Move getBestMove() { @@ -48,7 +47,9 @@ public class AlphaBetaAI extends AI { } for (int i = 0; i < moves.size(); i++) { + System.out.printf("Progress : %.2f %% ", (float) (i) / moves.size() * 100.0f); Move move = moves.get(i); + System.out.print("\r"); float value = MIN_FLOAT; try { value = moveEvaluations.get(i).get(); @@ -61,7 +62,7 @@ public class AlphaBetaAI extends AI { } } - System.out.println("Best move : " + bestMoveValue); + System.out.println("Best move : " + bestMoveValue + " "); return bestMove; } diff --git a/app/src/main/java/chess/ai/minimax/AlphaBetaThread.java b/app/src/main/java/chess/ai/minimax/AlphaBetaThread.java index c5e919c..9f32337 100644 --- a/app/src/main/java/chess/ai/minimax/AlphaBetaThread.java +++ b/app/src/main/java/chess/ai/minimax/AlphaBetaThread.java @@ -1,6 +1,10 @@ package chess.ai.minimax; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import java.util.Map; +import java.util.Map.Entry; import chess.ai.PieceCost; import chess.ai.PiecePosCost; @@ -76,7 +80,23 @@ public class AlphaBetaThread extends Thread { if (moves.isEmpty()) return getEndGameEvaluation(); + List> movesCost = new ArrayList<>(moves.size()); + for (Move move : moves) { + this.simulation.tryMove(move); + movesCost.add(Map.entry(move, getBoardEvaluation())); + this.simulation.undoMove(); + } + + Collections.sort(movesCost, (first, second) -> { + return Float.compare(first.getValue(), second.getValue()); + }); + + if (depth == 1) + return -movesCost.getFirst().getValue(); + + for (var moveEntry : movesCost) { + Move move = moveEntry.getKey(); this.simulation.tryMove(move); value = Float.max(value, -negaMax(depth - 1, -beta, -alpha)); this.simulation.undoMove(); -- 2.49.1 From 8354df5add397c916b570a5d198e379eb2b35a6d Mon Sep 17 00:00:00 2001 From: Persson-dev Date: Mon, 21 Apr 2025 15:04:18 +0200 Subject: [PATCH 11/15] smarter --- .../java/chess/ai/minimax/AlphaBetaThread.java | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/chess/ai/minimax/AlphaBetaThread.java b/app/src/main/java/chess/ai/minimax/AlphaBetaThread.java index 9f32337..6e3e515 100644 --- a/app/src/main/java/chess/ai/minimax/AlphaBetaThread.java +++ b/app/src/main/java/chess/ai/minimax/AlphaBetaThread.java @@ -38,13 +38,9 @@ public class AlphaBetaThread extends Thread { private float getEndGameEvaluation() { Color currentTurn = this.simulation.getPlayerTurn(); - if (currentTurn == this.color) { - return HORRIBLE_MOVE; - } else { - if (this.simulation.getBoard().isKingInCheck(currentTurn)) - return GREAT_MOVE; - return getBoardEvaluation() + PieceCost.PAWN; - } + if (this.simulation.getBoard().isKingInCheck(currentTurn)) + return GREAT_MOVE; + return getBoardEvaluation() - PieceCost.PAWN; } private float getBoardEvaluation() { @@ -57,8 +53,6 @@ public class AlphaBetaThread extends Thread { result += pieceCost.getCost(piece) + piecePosCost.getEvaluation(piece, coordinate); } } - if (this.simulation.getPlayerTurn() != color) - return -result; return result; } @@ -70,9 +64,6 @@ public class AlphaBetaThread extends Thread { } private float negaMax(int depth, float alpha, float beta) { - if (depth == 0) - return getBoardEvaluation(); - float value = MIN_FLOAT; List moves = this.simulation.getAllowedMoves(); @@ -84,7 +75,7 @@ public class AlphaBetaThread extends Thread { for (Move move : moves) { this.simulation.tryMove(move); - movesCost.add(Map.entry(move, getBoardEvaluation())); + movesCost.add(Map.entry(move, -getBoardEvaluation())); this.simulation.undoMove(); } -- 2.49.1 From cddadc927961b29332320078f07a11a15cfd2aa2 Mon Sep 17 00:00:00 2001 From: Persson-dev Date: Mon, 21 Apr 2025 18:38:55 +0200 Subject: [PATCH 12/15] 1500 let's go --- app/src/main/java/chess/SwingMain.java | 6 +++--- .../java/chess/ai/minimax/AlphaBetaAI.java | 2 +- .../chess/ai/minimax/AlphaBetaThread.java | 8 ++------ .../java/chess/ai/minimax/GameSimulation.java | 3 ++- .../controller/commands/MoveCommand.java | 2 +- app/src/main/java/chess/model/Game.java | 4 ++++ .../main/java/chess/model/PermissiveGame.java | 20 +++++++++++++++++++ app/src/main/resources/games/wendy.pgn | 12 +++++++++++ 8 files changed, 45 insertions(+), 12 deletions(-) create mode 100644 app/src/main/java/chess/model/PermissiveGame.java create mode 100644 app/src/main/resources/games/wendy.pgn diff --git a/app/src/main/java/chess/SwingMain.java b/app/src/main/java/chess/SwingMain.java index b52dc41..52f087c 100644 --- a/app/src/main/java/chess/SwingMain.java +++ b/app/src/main/java/chess/SwingMain.java @@ -18,11 +18,11 @@ public class SwingMain { Window window = new Window(commandExecutor, false); commandExecutor.addListener(window); - AI ai = new AlphaBetaAI(commandExecutor, Color.White, 3); + AI ai = new AlphaBetaAI(commandExecutor, Color.Black, 5); commandExecutor.addListener(ai); - AI ai2 = new AlphaBetaAI(commandExecutor, Color.Black, 3); - commandExecutor.addListener(ai2); + // AI ai2 = new AlphaBetaAI(commandExecutor, Color.White, 5); + // commandExecutor.addListener(ai2); // Window window2 = new Window(ai2.getSimulation(), false); // ai2.getSimulation().addListener(window2); diff --git a/app/src/main/java/chess/ai/minimax/AlphaBetaAI.java b/app/src/main/java/chess/ai/minimax/AlphaBetaAI.java index 56e987a..98cef39 100644 --- a/app/src/main/java/chess/ai/minimax/AlphaBetaAI.java +++ b/app/src/main/java/chess/ai/minimax/AlphaBetaAI.java @@ -28,7 +28,7 @@ public class AlphaBetaAI extends AI { public AlphaBetaAI(CommandExecutor commandExecutor, Color color, int searchDepth) { super(commandExecutor, color); this.searchDepth = searchDepth; - int threadCount = Runtime.getRuntime().availableProcessors() / 2; + int threadCount = Runtime.getRuntime().availableProcessors(); this.threadPool = Executors.newFixedThreadPool(threadCount, new AlphaBetaThreadCreator(commandExecutor, color, threadCount)); } diff --git a/app/src/main/java/chess/ai/minimax/AlphaBetaThread.java b/app/src/main/java/chess/ai/minimax/AlphaBetaThread.java index 6e3e515..7657037 100644 --- a/app/src/main/java/chess/ai/minimax/AlphaBetaThread.java +++ b/app/src/main/java/chess/ai/minimax/AlphaBetaThread.java @@ -20,10 +20,7 @@ public class AlphaBetaThread extends Thread { private final PieceCost pieceCost; private final PiecePosCost piecePosCost; - private final Color color; - - private static final int GREAT_MOVE = -9999; - private static final int HORRIBLE_MOVE = -GREAT_MOVE; + private static final int GREAT_MOVE = 9999; private static final float MAX_FLOAT = Float.MAX_VALUE; private static final float MIN_FLOAT = -MAX_FLOAT; @@ -33,7 +30,6 @@ public class AlphaBetaThread extends Thread { this.simulation = simulation; this.pieceCost = new PieceCost(color); this.piecePosCost = new PiecePosCost(color); - this.color = color; } private float getEndGameEvaluation() { @@ -69,7 +65,7 @@ public class AlphaBetaThread extends Thread { List moves = this.simulation.getAllowedMoves(); if (moves.isEmpty()) - return getEndGameEvaluation(); + return -getEndGameEvaluation(); List> movesCost = new ArrayList<>(moves.size()); diff --git a/app/src/main/java/chess/ai/minimax/GameSimulation.java b/app/src/main/java/chess/ai/minimax/GameSimulation.java index 1fb42c6..9520b05 100644 --- a/app/src/main/java/chess/ai/minimax/GameSimulation.java +++ b/app/src/main/java/chess/ai/minimax/GameSimulation.java @@ -18,6 +18,7 @@ import chess.model.ChessBoard; import chess.model.Color; import chess.model.Game; import chess.model.Move; +import chess.model.PermissiveGame; public class GameSimulation extends GameAdaptator { @@ -25,7 +26,7 @@ public class GameSimulation extends GameAdaptator { private final Game gameSimulation; public GameSimulation() { - this.gameSimulation = new Game(); + this.gameSimulation = new PermissiveGame(); this.simulation = new CommandExecutor(gameSimulation, new EmptyGameDispatcher()); } diff --git a/app/src/main/java/chess/controller/commands/MoveCommand.java b/app/src/main/java/chess/controller/commands/MoveCommand.java index 7116690..08a957b 100644 --- a/app/src/main/java/chess/controller/commands/MoveCommand.java +++ b/app/src/main/java/chess/controller/commands/MoveCommand.java @@ -52,7 +52,7 @@ public class MoveCommand extends PlayerCommand { final ChessBoard board = game.getBoard(); // we must promote the pending pawn before - if (board.pawnShouldBePromoted()) + if (game.pawnShouldBePromoted()) return CommandResult.NotAllowed; Piece piece = board.pieceAt(move.getStart()); diff --git a/app/src/main/java/chess/model/Game.java b/app/src/main/java/chess/model/Game.java index 360a5d2..28be020 100644 --- a/app/src/main/java/chess/model/Game.java +++ b/app/src/main/java/chess/model/Game.java @@ -122,4 +122,8 @@ public class Game { return this.movesHistory; } + public boolean pawnShouldBePromoted() { + return this.board.pawnShouldBePromoted(); + } + } diff --git a/app/src/main/java/chess/model/PermissiveGame.java b/app/src/main/java/chess/model/PermissiveGame.java new file mode 100644 index 0000000..3554ee4 --- /dev/null +++ b/app/src/main/java/chess/model/PermissiveGame.java @@ -0,0 +1,20 @@ +package chess.model; + +public class PermissiveGame extends Game { + + @Override + public GameStatus checkGameStatus(Color color) { + return GameStatus.OnGoing; + } + + @Override + public void undoTraitPiecesPos() { + + } + + @Override + public void saveTraitPiecesPos() { + + } + +} diff --git a/app/src/main/resources/games/wendy.pgn b/app/src/main/resources/games/wendy.pgn new file mode 100644 index 0000000..50a9e72 --- /dev/null +++ b/app/src/main/resources/games/wendy.pgn @@ -0,0 +1,12 @@ +1. e4 {1.e4 $1 A fiery start $1} 1... e5 {I like how this game is starting $1} 2. Nc3 +Bc5 {J'aime un feu chaud.} 3. Nf3 {It's too cold in here for my liking.} 3... +Qf6 4. Nd5 Qd6 5. d3 c6 {A fiery position is what I seek $1} 6. Nc3 h6 7. a3 Qg6 +8. Nxe5 {Things are beginning to heat up, non $2} 8... Qd6 9. Nc4 Qe6 10. d4 Be7 +11. Ne3 b5 12. Nf5 d5 13. Nxg7+ {That's not very nice.} 13... Kd7 14. Nxe6 +{Brrrrrr. It is getting cold in here.} 14... fxe6 15. exd5 cxd5 16. Bf4 Nf6 17. +Bxb5+ Kd8 18. Qf3 Bd7 19. Be5 {My attack is getting cold, I need to go get some +more firewood $1} 19... a6 20. Bxf6 Re8 21. Bxe7+ Kxe7 22. Nxd5+ exd5 23. Qxd5 +Kf8+ {It's getting toasty in here $1} 24. Be2 Bc6 25. Qd6+ Re7 26. Kf1 Ba4 27. b3 +Nc6 28. bxa4 a5 29. Qxc6 Rd8 30. Qxh6+ Kg8 31. Bc4+ Rf7 32. Qg5+ Kh8 33. Bxf7 +{C'est très, très mauvais $1} 33... Kh7 34. Qg6+ Kh8 35. Ra2 Rxd4 36. Qg8# {Good +play $1 I'll have to throw another log on the fire and try again.} 1-0 \ No newline at end of file -- 2.49.1 From 91428543c222fd11f7739cc2e6cfb5a476b8f245 Mon Sep 17 00:00:00 2001 From: Persson-dev Date: Mon, 21 Apr 2025 18:40:50 +0200 Subject: [PATCH 13/15] fixing gradle input --- app/build.gradle | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/build.gradle b/app/build.gradle index 2fd5ad3..2b4b1c3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -39,6 +39,10 @@ application { applicationName = "3DChess" } +run { + standardInput = System.in +} + jar { manifest { attributes 'Main-Class': application.mainClass -- 2.49.1 From 3a344155470b344480388db868ac73f07fb76056 Mon Sep 17 00:00:00 2001 From: Persson-dev Date: Tue, 22 Apr 2025 10:46:12 +0200 Subject: [PATCH 14/15] fix castling --- .../main/java/chess/controller/commands/CastlingCommand.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/java/chess/controller/commands/CastlingCommand.java b/app/src/main/java/chess/controller/commands/CastlingCommand.java index 04a02ca..b53fd2e 100644 --- a/app/src/main/java/chess/controller/commands/CastlingCommand.java +++ b/app/src/main/java/chess/controller/commands/CastlingCommand.java @@ -49,6 +49,8 @@ public class CastlingCommand extends PlayerCommand { board.applyMove(this.kingMove); board.applyMove(this.rookMove); + board.setLastMove(this.kingMove); + outputSystem.onCastling(this.bigCastling); return CommandResult.Moved; -- 2.49.1 From 5c4797c4513511e9d938b3bfe167f32a88299537 Mon Sep 17 00:00:00 2001 From: Persson-dev Date: Tue, 22 Apr 2025 14:16:38 +0200 Subject: [PATCH 15/15] fix ai undo --- app/src/main/java/chess/ai/minimax/GameSimulation.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/src/main/java/chess/ai/minimax/GameSimulation.java b/app/src/main/java/chess/ai/minimax/GameSimulation.java index 9520b05..772e463 100644 --- a/app/src/main/java/chess/ai/minimax/GameSimulation.java +++ b/app/src/main/java/chess/ai/minimax/GameSimulation.java @@ -68,6 +68,12 @@ public class GameSimulation extends GameAdaptator { sendCommand(new NewGameCommand()); } + @Override + public void onPlayerTurn(Color color, boolean undone) { + if (undone) + sendCommand(new UndoCommand()); + } + public CommandExecutor getCommandExecutor() { return simulation; } -- 2.49.1