package dev.kske.chess.game.ai; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.Callable; import dev.kske.chess.board.*; import dev.kske.chess.board.Piece.Color; /** * Implements a basic minimax move search algorithm for testing purposes.
*
* Project: Chess
* File: MoveProcessor.java
* Created: 08.07.2019
* * @since Chess v0.1-alpha * @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 final int alphaBetaThreshold; private Move bestMove; private static final Map, int[][]> positionScores; static { positionScores = new HashMap<>(); positionScores.put( King.class, 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 } } ); positionScores.put( Queen.class, 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 } } ); positionScores.put( Rook.class, 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 } } ); positionScores.put( Knight.class, 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 } } ); positionScores.put( Bishop.class, 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 } } ); positionScores.put( Pawn.class, 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 } } ); } /** * 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 */ public MoveProcessor( Board board, List rootMoves, Color color, int maxDepth, int alphaBetaThreshold ) { this.board = board; this.rootMoves = rootMoves; this.color = color; this.maxDepth = maxDepth; this.alphaBetaThreshold = alphaBetaThreshold; } @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) { board.move(move); int teamValue = evaluate(board, color); int enemyValue = evaluate(board, color.opposite()); int valueChange = teamValue - enemyValue; if (depth < maxDepth && valueChange >= alphaBetaThreshold) valueChange -= miniMax( board, board.getMoves(color.opposite()), color.opposite(), depth + 1 ); if (valueChange > bestValue) { bestValue = valueChange; if (depth == 0) bestMove = move; } board.revert(); } return bestValue; } /** * Evaluated a board. * * @param board the board to evaluate * @param color The color to evaluate for * @return a positive number representing how good the position is */ 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 ) { 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]; } return score; } }