284 lines
6.2 KiB
Java
284 lines
6.2 KiB
Java
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.<br>
|
|
* <br>
|
|
* Project: <strong>Chess</strong><br>
|
|
* File: <strong>MoveProcessor.java</strong><br>
|
|
* Created: <strong>08.07.2019</strong><br>
|
|
*
|
|
* @since Chess v0.1-alpha
|
|
* @author Kai S. K. Engelbart
|
|
*/
|
|
public class MoveProcessor implements Callable<ProcessingResult> {
|
|
|
|
private final Board board;
|
|
private final List<Move> rootMoves;
|
|
private final Color color;
|
|
private final int maxDepth;
|
|
private final int alphaBetaThreshold;
|
|
|
|
private Move bestMove;
|
|
|
|
private static final Map<Class<? extends Piece>, 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<Move> 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<Move> 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;
|
|
}
|
|
}
|