From c289546914ad453aef987a8e4affb9c7a7a74d91 Mon Sep 17 00:00:00 2001 From: Persson-dev Date: Sat, 19 Apr 2025 14:15:53 +0200 Subject: [PATCH] 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;