117 lines
3.5 KiB
Java
117 lines
3.5 KiB
Java
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: <strong>Chess</strong><br>
|
|
* File: <strong>AIPlayer.java</strong><br>
|
|
* Created: <strong>06.07.2019</strong><br>
|
|
*
|
|
* @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<Move> moves = board.getMoves(color);
|
|
|
|
// Define move processors and split the available moves between
|
|
// them.
|
|
int numThreads = Math.min(moves.size(), availableProcessors);
|
|
List<MoveProcessor> 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<ProcessingResult> results = new ArrayList<>(numThreads);
|
|
try {
|
|
List<Future<ProcessingResult>> futures
|
|
= executor.invokeAll(processors);
|
|
for (Future<ProcessingResult> 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() {}
|
|
}
|