2019-07-08 16:44:21 +02:00
|
|
|
package dev.kske.chess.game.ai;
|
|
|
|
|
2019-10-30 17:11:57 +01:00
|
|
|
import java.util.HashMap;
|
2019-07-08 16:44:21 +02:00
|
|
|
import java.util.List;
|
2019-10-30 17:11:57 +01:00
|
|
|
import java.util.Map;
|
2019-07-08 16:44:21 +02:00
|
|
|
import java.util.concurrent.Callable;
|
|
|
|
|
2020-01-19 22:12:33 +01:00
|
|
|
import dev.kske.chess.board.*;
|
2019-07-08 16:44:21 +02:00
|
|
|
import dev.kske.chess.board.Piece.Color;
|
|
|
|
|
|
|
|
/**
|
2020-01-19 22:12:33 +01:00
|
|
|
* Implements a basic minimax move search algorithm for testing purposes.<br>
|
|
|
|
* <br>
|
2019-07-08 16:44:21 +02:00
|
|
|
* Project: <strong>Chess</strong><br>
|
|
|
|
* File: <strong>MoveProcessor.java</strong><br>
|
|
|
|
* Created: <strong>08.07.2019</strong><br>
|
2020-01-19 22:12:33 +01:00
|
|
|
*
|
2019-10-26 07:55:21 +02:00
|
|
|
* @since Chess v0.1-alpha
|
|
|
|
* @author Kai S. K. Engelbart
|
2019-07-08 16:44:21 +02:00
|
|
|
*/
|
|
|
|
public class MoveProcessor implements Callable<ProcessingResult> {
|
|
|
|
|
|
|
|
private final Board board;
|
2019-07-16 14:42:10 +02:00
|
|
|
private final List<Move> rootMoves;
|
2019-07-08 16:44:21 +02:00
|
|
|
private final Color color;
|
|
|
|
private final int maxDepth;
|
2019-07-16 14:42:10 +02:00
|
|
|
private final int alphaBetaThreshold;
|
2019-07-08 16:44:21 +02:00
|
|
|
|
|
|
|
private Move bestMove;
|
|
|
|
|
2019-11-04 05:51:11 +01:00
|
|
|
private static final Map<Class<? extends Piece>, int[][]> positionScores;
|
2019-10-30 17:11:57 +01:00
|
|
|
|
|
|
|
static {
|
|
|
|
positionScores = new HashMap<>();
|
2019-11-04 05:51:11 +01:00
|
|
|
positionScores.put(King.class,
|
2019-10-30 17:11:57 +01:00
|
|
|
new int[][] { new int[] { -3, -4, -4, -5, -5, -4, -4, -3 }, new int[] { -3, -4, -4, -5, -4, -4, -4, -3 },
|
|
|
|
new int[] { -3, -4, -4, -5, -4, -4, -4, -3 }, new int[] { -3, -4, -4, -5, -4, -4, -4, -3 },
|
|
|
|
new int[] { -2, -3, -3, -2, -2, -2, -2, -1 }, new int[] { -1, -2, -2, -2, -2, -2, -2, -1 },
|
|
|
|
new int[] { 2, 2, 0, 0, 0, 0, 2, 2 }, new int[] { 2, 3, 1, 0, 0, 1, 3, 2 } });
|
2019-11-04 05:51:11 +01:00
|
|
|
positionScores.put(Queen.class,
|
2019-10-30 17:11:57 +01:00
|
|
|
new int[][] { new int[] { -2, -1, -1, -1, -1, -1, -1, -2 }, new int[] { -1, 0, 0, 0, 0, 0, 0, -1 },
|
|
|
|
new int[] { -1, 0, 1, 1, 1, 1, 0, -1 }, new int[] { -1, 0, 1, 1, 1, 1, 0, -1 }, new int[] { 0, 0, 1, 1, 1, 1, 0, -1 },
|
|
|
|
new int[] { -1, 1, 1, 1, 1, 1, 0, -1 }, new int[] { -1, 0, 1, 0, 0, 0, 0, -1 },
|
|
|
|
new int[] { -2, -1, -1, -1, -1, -1, -1, -2 } });
|
2019-11-04 05:51:11 +01:00
|
|
|
positionScores.put(Rook.class,
|
2019-10-30 17:11:57 +01:00
|
|
|
new int[][] { new int[] { 0, 0, 0, 0, 0, 0, 0, 0 }, new int[] { 1, 1, 1, 1, 1, 1, 1, 1 }, new int[] { -1, 0, 0, 0, 0, 0, 0, -1 },
|
|
|
|
new int[] { -1, 0, 0, 0, 0, 0, 0, -1 }, new int[] { -1, 0, 0, 0, 0, 0, 0, -1 }, new int[] { -1, 0, 0, 0, 0, 0, 0, -1 },
|
|
|
|
new int[] { -1, 0, 0, 0, 0, 0, 0, -1 }, new int[] { 0, 0, 0, 1, 1, 0, 0, 0 } });
|
2019-11-04 05:51:11 +01:00
|
|
|
positionScores.put(Knight.class,
|
2019-10-30 17:11:57 +01:00
|
|
|
new int[][] { new int[] { -5, -4, -3, -3, -3, -3, -4, -5 }, new int[] { -4, -2, 0, 0, 0, 0, -2, -4 },
|
|
|
|
new int[] { -3, 0, 1, 2, 2, 1, 0, -3 }, new int[] { -3, 1, 2, 2, 2, 2, 1, -3 }, new int[] { -3, 0, 2, 2, 2, 2, 0, -1 },
|
|
|
|
new int[] { -3, 1, 1, 2, 2, 1, 1, -3 }, new int[] { -4, -2, 0, 1, 1, 0, -2, -4 },
|
|
|
|
new int[] { -5, -4, -3, -3, -3, -3, -4, -5 } });
|
2019-11-04 05:51:11 +01:00
|
|
|
positionScores.put(Bishop.class,
|
2019-10-30 17:11:57 +01:00
|
|
|
new int[][] { new int[] { -2, -1, -1, -1, -1, -1, -1, 2 }, new int[] { -1, 0, 0, 0, 0, 0, 0, -1 },
|
|
|
|
new int[] { -1, 0, 1, 1, 1, 1, 0, -1 }, new int[] { -1, 1, 1, 1, 1, 1, 1, -1 }, new int[] { -1, 0, 1, 1, 1, 1, 0, -1 },
|
|
|
|
new int[] { -1, 1, 1, 1, 1, 1, 1, -1 }, new int[] { -1, 1, 0, 0, 0, 0, 1, -1 },
|
|
|
|
new int[] { -2, -1, -1, -1, -1, -1, -1, -2 } });
|
2019-11-04 05:51:11 +01:00
|
|
|
positionScores.put(Pawn.class,
|
2019-10-30 17:11:57 +01:00
|
|
|
new int[][] { new int[] { 0, 0, 0, 0, 0, 0, 0, 0 }, new int[] { 5, 5, 5, 5, 5, 5, 5, 5 }, new int[] { 1, 1, 2, 3, 3, 2, 1, 1 },
|
|
|
|
new int[] { 0, 0, 1, 3, 3, 1, 0, 0 }, new int[] { 0, 0, 0, 2, 2, 0, 0, 0 }, new int[] { 0, 0, -1, 0, 0, -1, 0, 0 },
|
|
|
|
new int[] { 0, 1, 1, -2, -2, 1, 1, 0 }, new int[] { 0, 0, 0, 0, 0, 0, 0, 0 } });
|
|
|
|
}
|
|
|
|
|
2020-01-19 22:12:33 +01:00
|
|
|
/**
|
|
|
|
* Creates an instance of {@link MoveProcessor}.
|
|
|
|
*
|
|
|
|
* @param board the board to search
|
|
|
|
* @param rootMoves the moves on which the search is based
|
|
|
|
* @param color the color for which to search
|
|
|
|
* @param maxDepth the maximal recursion depth to search to
|
|
|
|
* @param alphaBetaThreshold the threshold necessary to continue a search for a
|
|
|
|
* specific move
|
|
|
|
*/
|
2019-07-16 14:42:10 +02:00
|
|
|
public MoveProcessor(Board board, List<Move> rootMoves, Color color, int maxDepth, int alphaBetaThreshold) {
|
2019-07-25 21:38:49 +02:00
|
|
|
this.board = board;
|
|
|
|
this.rootMoves = rootMoves;
|
|
|
|
this.color = color;
|
|
|
|
this.maxDepth = maxDepth;
|
2019-07-16 14:42:10 +02:00
|
|
|
this.alphaBetaThreshold = alphaBetaThreshold;
|
2019-07-08 16:44:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@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) {
|
2019-07-10 18:54:53 +02:00
|
|
|
board.move(move);
|
2019-10-30 17:11:57 +01:00
|
|
|
int teamValue = evaluate(board, color);
|
|
|
|
int enemyValue = evaluate(board, color.opposite());
|
2019-07-25 21:38:49 +02:00
|
|
|
int valueChange = teamValue - enemyValue;
|
2019-07-08 16:44:21 +02:00
|
|
|
|
2019-07-16 14:42:10 +02:00
|
|
|
if (depth < maxDepth && valueChange >= alphaBetaThreshold)
|
2019-07-08 16:44:21 +02:00
|
|
|
valueChange -= miniMax(board, board.getMoves(color.opposite()), color.opposite(), depth + 1);
|
|
|
|
|
|
|
|
if (valueChange > bestValue) {
|
|
|
|
bestValue = valueChange;
|
|
|
|
if (depth == 0) bestMove = move;
|
|
|
|
}
|
|
|
|
|
2019-07-10 18:54:53 +02:00
|
|
|
board.revert();
|
2019-07-08 16:44:21 +02:00
|
|
|
}
|
|
|
|
return bestValue;
|
|
|
|
}
|
2019-10-30 17:11:57 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Evaluated a board.
|
2020-01-19 22:12:33 +01:00
|
|
|
*
|
|
|
|
* @param board the board to evaluate
|
2019-10-30 17:11:57 +01:00
|
|
|
* @param color The color to evaluate for
|
2020-01-19 22:12:33 +01:00
|
|
|
* @return a positive number representing how good the position is
|
2019-10-30 17:11:57 +01:00
|
|
|
*/
|
|
|
|
private int evaluate(Board board, Color color) {
|
|
|
|
int score = 0;
|
|
|
|
for (int i = 0; i < 8; i++)
|
|
|
|
for (int j = 0; j < 8; j++)
|
|
|
|
if (board.getBoardArr()[i][j] != null && board.getBoardArr()[i][j].getColor() == color) {
|
2019-11-04 05:51:11 +01:00
|
|
|
score += board.getBoardArr()[i][j].getValue();
|
|
|
|
if (positionScores.containsKey(board.getBoardArr()[i][j].getClass()))
|
|
|
|
score += positionScores.get(board.getBoardArr()[i][j].getClass())[i][color == Color.WHITE ? j : 7 - j];
|
2019-10-30 17:11:57 +01:00
|
|
|
}
|
|
|
|
return score;
|
|
|
|
}
|
2019-07-08 16:44:21 +02:00
|
|
|
}
|