Added multithreading to AIPlayer for better performance
+ MoveProcessor and ProcessingResult classes for handling multithreaded move calculation + Separate package for AIPlayer and its components
This commit is contained in:
parent
666a7ea25c
commit
e19d91da8f
@ -17,7 +17,7 @@ public class Pawn extends Piece {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isValidMove(Move move) {
|
public boolean isValidMove(Move move) {
|
||||||
// TODO: en passant
|
// TODO: en passant, pawn promotion
|
||||||
boolean step = move.isVertical() && move.yDist == 1;
|
boolean step = move.isVertical() && move.yDist == 1;
|
||||||
boolean doubleStep = move.isVertical() && move.yDist == 2;
|
boolean doubleStep = move.isVertical() && move.yDist == 2;
|
||||||
boolean strafe = move.isDiagonal() && move.xDist == 1;
|
boolean strafe = move.isDiagonal() && move.xDist == 1;
|
||||||
@ -43,6 +43,8 @@ public class Pawn extends Piece {
|
|||||||
|
|
||||||
int sign = getColor() == Color.WHITE ? -1 : 1;
|
int sign = getColor() == Color.WHITE ? -1 : 1;
|
||||||
|
|
||||||
|
if (sign == -1 && pos.y == 1 || sign == 1 && pos.y == 7) return moves;
|
||||||
|
|
||||||
// Strafe left
|
// Strafe left
|
||||||
if (pos.x > 0) {
|
if (pos.x > 0) {
|
||||||
Move move = new Move(pos, new Position(pos.x - 1, pos.y + sign));
|
Move move = new Move(pos, new Position(pos.x - 1, pos.y + sign));
|
||||||
|
@ -1,55 +0,0 @@
|
|||||||
package dev.kske.chess.game;
|
|
||||||
|
|
||||||
import javax.swing.SwingUtilities;
|
|
||||||
|
|
||||||
import dev.kske.chess.board.Board;
|
|
||||||
import dev.kske.chess.board.Move;
|
|
||||||
import dev.kske.chess.board.Piece;
|
|
||||||
import dev.kske.chess.board.Piece.Color;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 Move bestMove;
|
|
||||||
private int count;
|
|
||||||
|
|
||||||
public AIPlayer(Board board, Color color) {
|
|
||||||
super(board, color);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void requestMove() {
|
|
||||||
new Thread(() -> {
|
|
||||||
count = 0;
|
|
||||||
findBestMove((Board) board.clone(), color, 0);
|
|
||||||
System.out.println("Moves processed: " + count);
|
|
||||||
SwingUtilities.invokeLater(() -> game.onMove(this, bestMove));
|
|
||||||
}).start();
|
|
||||||
}
|
|
||||||
|
|
||||||
private int findBestMove(Board board, Color color, int depth) {
|
|
||||||
int bestValue = Integer.MIN_VALUE;
|
|
||||||
for (Move move : board.getMoves(color)) {
|
|
||||||
++count;
|
|
||||||
Piece capturePiece = board.move(move);
|
|
||||||
int teamValue = board.evaluate(color);
|
|
||||||
int enemyValue = board.evaluate(color.opposite());
|
|
||||||
int valueChange = teamValue - enemyValue;
|
|
||||||
|
|
||||||
if (depth < 4) valueChange -= findBestMove(board, color.opposite(), depth + 1);
|
|
||||||
|
|
||||||
if (valueChange > bestValue) {
|
|
||||||
bestValue = valueChange;
|
|
||||||
if (depth == 0) bestMove = move;
|
|
||||||
}
|
|
||||||
|
|
||||||
board.revert(move, capturePiece);
|
|
||||||
}
|
|
||||||
return bestValue;
|
|
||||||
}
|
|
||||||
}
|
|
80
src/dev/kske/chess/game/ai/AIPlayer.java
Normal file
80
src/dev/kske/chess/game/ai/AIPlayer.java
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
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 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;
|
||||||
|
|
||||||
|
public AIPlayer(Board board, Color color, int maxDepth) {
|
||||||
|
super(board, color);
|
||||||
|
availableProcessors = Runtime.getRuntime().availableProcessors();
|
||||||
|
this.maxDepth = maxDepth;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void requestMove() {
|
||||||
|
/*
|
||||||
|
* 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));
|
||||||
|
beginIndex = endIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Execute processors, get the best result and pass it back to the Game class
|
||||||
|
*/
|
||||||
|
ExecutorService 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));
|
||||||
|
SwingUtilities.invokeLater(() -> game.onMove(this, results.get(0).move));
|
||||||
|
}, "AIPlayer calculation setup").start();
|
||||||
|
}
|
||||||
|
}
|
59
src/dev/kske/chess/game/ai/MoveProcessor.java
Normal file
59
src/dev/kske/chess/game/ai/MoveProcessor.java
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
package dev.kske.chess.game.ai;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
|
||||||
|
import dev.kske.chess.board.Board;
|
||||||
|
import dev.kske.chess.board.Move;
|
||||||
|
import dev.kske.chess.board.Piece;
|
||||||
|
import dev.kske.chess.board.Piece.Color;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Project: <strong>Chess</strong><br>
|
||||||
|
* File: <strong>MoveProcessor.java</strong><br>
|
||||||
|
* Created: <strong>08.07.2019</strong><br>
|
||||||
|
* Author: <strong>Kai S. K. Engelbart</strong>
|
||||||
|
*/
|
||||||
|
public class MoveProcessor implements Callable<ProcessingResult> {
|
||||||
|
|
||||||
|
private final Board board;
|
||||||
|
private final List<Move> rootMoves;;
|
||||||
|
private final Color color;
|
||||||
|
private final int maxDepth;
|
||||||
|
|
||||||
|
private Move bestMove;
|
||||||
|
|
||||||
|
public MoveProcessor(Board board, List<Move> rootMoves, Color color, int maxDepth) {
|
||||||
|
this.board = board;
|
||||||
|
this.rootMoves = rootMoves;
|
||||||
|
this.color = color;
|
||||||
|
this.maxDepth = maxDepth;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ProcessingResult call() throws Exception {
|
||||||
|
int score = miniMax(board, rootMoves, color, 0);
|
||||||
|
return new ProcessingResult(bestMove, score);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int miniMax(Board board, List<Move> moves, Color color, int depth) {
|
||||||
|
int bestValue = Integer.MIN_VALUE;
|
||||||
|
for (Move move : moves) {
|
||||||
|
Piece capturePiece = board.move(move);
|
||||||
|
int teamValue = board.evaluate(color);
|
||||||
|
int enemyValue = board.evaluate(color.opposite());
|
||||||
|
int valueChange = teamValue - enemyValue;
|
||||||
|
|
||||||
|
if (depth < maxDepth && valueChange >= 0)
|
||||||
|
valueChange -= miniMax(board, board.getMoves(color.opposite()), color.opposite(), depth + 1);
|
||||||
|
|
||||||
|
if (valueChange > bestValue) {
|
||||||
|
bestValue = valueChange;
|
||||||
|
if (depth == 0) bestMove = move;
|
||||||
|
}
|
||||||
|
|
||||||
|
board.revert(move, capturePiece);
|
||||||
|
}
|
||||||
|
return bestValue;
|
||||||
|
}
|
||||||
|
}
|
25
src/dev/kske/chess/game/ai/ProcessingResult.java
Normal file
25
src/dev/kske/chess/game/ai/ProcessingResult.java
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
package dev.kske.chess.game.ai;
|
||||||
|
|
||||||
|
import dev.kske.chess.board.Move;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Project: <strong>Chess</strong><br>
|
||||||
|
* File: <strong>ProcessingResult.java</strong><br>
|
||||||
|
* Created: <strong>08.07.2019</strong><br>
|
||||||
|
* Author: <strong>Kai S. K. Engelbart</strong>
|
||||||
|
*/
|
||||||
|
public class ProcessingResult {
|
||||||
|
|
||||||
|
public final Move move;
|
||||||
|
public final int score;
|
||||||
|
|
||||||
|
public ProcessingResult(Move move, int score) {
|
||||||
|
this.move = move;
|
||||||
|
this.score = score;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.format("ProcessingResult[Move = %s, Score = %d]", move, score);
|
||||||
|
}
|
||||||
|
}
|
@ -8,10 +8,10 @@ import javax.swing.JButton;
|
|||||||
import javax.swing.JDialog;
|
import javax.swing.JDialog;
|
||||||
|
|
||||||
import dev.kske.chess.board.Piece.Color;
|
import dev.kske.chess.board.Piece.Color;
|
||||||
import dev.kske.chess.game.AIPlayer;
|
|
||||||
import dev.kske.chess.game.Game;
|
import dev.kske.chess.game.Game;
|
||||||
import dev.kske.chess.game.NaturalPlayer;
|
import dev.kske.chess.game.NaturalPlayer;
|
||||||
import dev.kske.chess.game.Player;
|
import dev.kske.chess.game.Player;
|
||||||
|
import dev.kske.chess.game.ai.AIPlayer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Project: <strong>Chess</strong><br>
|
* Project: <strong>Chess</strong><br>
|
||||||
@ -48,7 +48,7 @@ public class GameModeDialog extends JDialog {
|
|||||||
btnAI.addActionListener((evt) -> {
|
btnAI.addActionListener((evt) -> {
|
||||||
Map<Color, Player> players = new HashMap<>();
|
Map<Color, Player> players = new HashMap<>();
|
||||||
players.put(Color.WHITE, new NaturalPlayer(boardPanel.getBoard(), Color.WHITE, boardPanel));
|
players.put(Color.WHITE, new NaturalPlayer(boardPanel.getBoard(), Color.WHITE, boardPanel));
|
||||||
players.put(Color.BLACK, new AIPlayer(boardPanel.getBoard(), Color.BLACK));
|
players.put(Color.BLACK, new AIPlayer(boardPanel.getBoard(), Color.BLACK, 4));
|
||||||
startGame(players, boardPanel);
|
startGame(players, boardPanel);
|
||||||
});
|
});
|
||||||
getContentPane().add(btnAI);
|
getContentPane().add(btnAI);
|
||||||
@ -56,8 +56,8 @@ public class GameModeDialog extends JDialog {
|
|||||||
JButton btnAI2 = new JButton("AI against AI");
|
JButton btnAI2 = new JButton("AI against AI");
|
||||||
btnAI2.addActionListener((evt) -> {
|
btnAI2.addActionListener((evt) -> {
|
||||||
Map<Color, Player> players = new HashMap<>();
|
Map<Color, Player> players = new HashMap<>();
|
||||||
players.put(Color.WHITE, new AIPlayer(boardPanel.getBoard(), Color.WHITE));
|
players.put(Color.WHITE, new AIPlayer(boardPanel.getBoard(), Color.WHITE, 4));
|
||||||
players.put(Color.BLACK, new AIPlayer(boardPanel.getBoard(), Color.BLACK));
|
players.put(Color.BLACK, new AIPlayer(boardPanel.getBoard(), Color.BLACK, 3));
|
||||||
startGame(players, boardPanel);
|
startGame(players, boardPanel);
|
||||||
});
|
});
|
||||||
getContentPane().add(btnAI2);
|
getContentPane().add(btnAI2);
|
||||||
|
Reference in New Issue
Block a user