105 lines
3.2 KiB
Java
105 lines
3.2 KiB
Java
package dev.kske.chess.game.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 java.util.concurrent.TimeUnit;
|
|
|
|
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.Player;
|
|
|
|
/**
|
|
* Project: <strong>Chess</strong><br>
|
|
* File: <strong>AIPlayer.java</strong><br>
|
|
* Created: <strong>06.07.2019</strong><br>
|
|
* Author: <strong>Kai S. K. Engelbart</strong>
|
|
*/
|
|
public class AIPlayer extends Player {
|
|
|
|
private int availableProcessors;
|
|
private int maxDepth;
|
|
private int alphaBetaThreshold;
|
|
|
|
private volatile boolean exitRequested;
|
|
private volatile ExecutorService executor;
|
|
|
|
public AIPlayer(Color color, int maxDepth, int alphaBetaThreshold) {
|
|
super(color);
|
|
availableProcessors = Runtime.getRuntime().availableProcessors();
|
|
this.maxDepth = maxDepth;
|
|
this.alphaBetaThreshold = alphaBetaThreshold;
|
|
exitRequested = false;
|
|
}
|
|
|
|
@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 = (Board) AIPlayer.this.board.clone();
|
|
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((Board) board.clone(), 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());
|
|
executor.shutdown();
|
|
} catch (InterruptedException | ExecutionException ex) {
|
|
ex.printStackTrace();
|
|
}
|
|
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() {}
|
|
}
|