package dev.kske.chess.game.ai; import java.util.ArrayList; import java.util.List; import java.util.concurrent.*; import javax.swing.SwingUtilities; import dev.kske.chess.board.Board; import dev.kske.chess.board.Move; import dev.kske.chess.board.Piece.Color; import dev.kske.chess.game.Game; import dev.kske.chess.game.Player; /** * Project: Chess
* File: AIPlayer.java
* Created: 06.07.2019
* * @since Chess v0.1-alpha * @author Kai S. K. Engelbart */ public class AIPlayer extends Player { private int availableProcessors; private int maxDepth; private int alphaBetaThreshold; private volatile boolean exitRequested; private volatile ExecutorService executor; /** * Creates an instance of {@link AIPlayer}. * * @param game the game in which this player will be used * @param color the piece color this player will control * @param maxDepth the maximum search depth * @param alphaBetaThreshold the board evaluation threshold that has to be * reached to continue searching the children of a * move */ public AIPlayer( Game game, Color color, int maxDepth, int alphaBetaThreshold ) { super(game, color); name = "AIPlayer"; availableProcessors = Runtime.getRuntime().availableProcessors(); this.maxDepth = maxDepth; this.alphaBetaThreshold = alphaBetaThreshold; } @Override public void requestMove() { exitRequested = false; // Define some processing threads, split the available moves between // them and // retrieve the result after their execution. new Thread(() -> { // Get a copy of the board and the available moves. Board board = new Board(this.board, false); List moves = board.getMoves(color); // Define move processors and split the available moves between // them. int numThreads = Math.min(moves.size(), availableProcessors); List processors = new ArrayList<>(numThreads); final int step = moves.size() / numThreads; int rem = moves.size() % numThreads; int beginIndex = 0, endIndex = 0; for (int i = 0; i < numThreads; i++) { if (rem-- > 0) ++endIndex; endIndex += step; processors .add(new MoveProcessor(new Board(board, false), moves.subList(beginIndex, endIndex), color, maxDepth, alphaBetaThreshold)); beginIndex = endIndex; } // Execute processors, get the best result and pass it back to the // Game class executor = Executors.newFixedThreadPool(numThreads); List results = new ArrayList<>(numThreads); try { List> futures = executor.invokeAll(processors); for (Future f : futures) results.add(f.get()); } catch (InterruptedException | ExecutionException ex) { ex.printStackTrace(); } finally { executor.shutdown(); } results.sort((r1, r2) -> Integer.compare(r2.score, r1.score)); if (!exitRequested) SwingUtilities .invokeLater(() -> game.onMove(this, results.get(0).move)); }, "AIPlayer calculation setup").start(); } @Override public void cancelMove() { exitRequested = true; if (executor != null) { executor.shutdownNow(); try { executor.awaitTermination(500, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { e.printStackTrace(); } } } @Override public void disconnect() {} }