diff --git a/src/dev/kske/chess/board/Pawn.java b/src/dev/kske/chess/board/Pawn.java
index e43da04..0406409 100644
--- a/src/dev/kske/chess/board/Pawn.java
+++ b/src/dev/kske/chess/board/Pawn.java
@@ -17,7 +17,7 @@ public class Pawn extends Piece {
@Override
public boolean isValidMove(Move move) {
- // TODO: en passant
+ // TODO: en passant, pawn promotion
boolean step = move.isVertical() && move.yDist == 1;
boolean doubleStep = move.isVertical() && move.yDist == 2;
boolean strafe = move.isDiagonal() && move.xDist == 1;
@@ -43,6 +43,8 @@ public class Pawn extends Piece {
int sign = getColor() == Color.WHITE ? -1 : 1;
+ if (sign == -1 && pos.y == 1 || sign == 1 && pos.y == 7) return moves;
+
// Strafe left
if (pos.x > 0) {
Move move = new Move(pos, new Position(pos.x - 1, pos.y + sign));
diff --git a/src/dev/kske/chess/game/AIPlayer.java b/src/dev/kske/chess/game/AIPlayer.java
deleted file mode 100644
index fa15216..0000000
--- a/src/dev/kske/chess/game/AIPlayer.java
+++ /dev/null
@@ -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: Chess
- * File: AIPlayer.java
- * Created: 06.07.2019
- * Author: Kai S. K. Engelbart
- */
-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;
- }
-}
diff --git a/src/dev/kske/chess/game/ai/AIPlayer.java b/src/dev/kske/chess/game/ai/AIPlayer.java
new file mode 100644
index 0000000..21a1155
--- /dev/null
+++ b/src/dev/kske/chess/game/ai/AIPlayer.java
@@ -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: Chess
+ * File: AIPlayer.java
+ * Created: 06.07.2019
+ * Author: Kai S. K. Engelbart
+ */
+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 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((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 results = new ArrayList<>(numThreads);
+ try {
+ List> futures = executor.invokeAll(processors);
+ for (Future 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();
+ }
+}
diff --git a/src/dev/kske/chess/game/ai/MoveProcessor.java b/src/dev/kske/chess/game/ai/MoveProcessor.java
new file mode 100644
index 0000000..e7c5741
--- /dev/null
+++ b/src/dev/kske/chess/game/ai/MoveProcessor.java
@@ -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: Chess
+ * File: MoveProcessor.java
+ * Created: 08.07.2019
+ * Author: Kai S. K. Engelbart
+ */
+public class MoveProcessor implements Callable {
+
+ private final Board board;
+ private final List rootMoves;;
+ private final Color color;
+ private final int maxDepth;
+
+ private Move bestMove;
+
+ public MoveProcessor(Board board, List 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 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;
+ }
+}
diff --git a/src/dev/kske/chess/game/ai/ProcessingResult.java b/src/dev/kske/chess/game/ai/ProcessingResult.java
new file mode 100644
index 0000000..0740f87
--- /dev/null
+++ b/src/dev/kske/chess/game/ai/ProcessingResult.java
@@ -0,0 +1,25 @@
+package dev.kske.chess.game.ai;
+
+import dev.kske.chess.board.Move;
+
+/**
+ * Project: Chess
+ * File: ProcessingResult.java
+ * Created: 08.07.2019
+ * Author: Kai S. K. Engelbart
+ */
+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);
+ }
+}
diff --git a/src/dev/kske/chess/ui/GameModeDialog.java b/src/dev/kske/chess/ui/GameModeDialog.java
index 0db92ee..aee5471 100644
--- a/src/dev/kske/chess/ui/GameModeDialog.java
+++ b/src/dev/kske/chess/ui/GameModeDialog.java
@@ -8,10 +8,10 @@ import javax.swing.JButton;
import javax.swing.JDialog;
import dev.kske.chess.board.Piece.Color;
-import dev.kske.chess.game.AIPlayer;
import dev.kske.chess.game.Game;
import dev.kske.chess.game.NaturalPlayer;
import dev.kske.chess.game.Player;
+import dev.kske.chess.game.ai.AIPlayer;
/**
* Project: Chess
@@ -48,7 +48,7 @@ public class GameModeDialog extends JDialog {
btnAI.addActionListener((evt) -> {
Map players = new HashMap<>();
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);
});
getContentPane().add(btnAI);
@@ -56,8 +56,8 @@ public class GameModeDialog extends JDialog {
JButton btnAI2 = new JButton("AI against AI");
btnAI2.addActionListener((evt) -> {
Map players = new HashMap<>();
- players.put(Color.WHITE, new AIPlayer(boardPanel.getBoard(), Color.WHITE));
- players.put(Color.BLACK, new AIPlayer(boardPanel.getBoard(), Color.BLACK));
+ players.put(Color.WHITE, new AIPlayer(boardPanel.getBoard(), Color.WHITE, 4));
+ players.put(Color.BLACK, new AIPlayer(boardPanel.getBoard(), Color.BLACK, 3));
startGame(players, boardPanel);
});
getContentPane().add(btnAI2);