Skip to content

Commit

Permalink
Improved Move Ordering
Browse files Browse the repository at this point in the history
Quiescence search now also accesses transposition table and has the hash move prioritised.
Silent moves with no scores assigned are placed at a lower priority and ordered based on PSQT change
Fixed a UCI bug where knight promotion was processed wrongly (i.e. e1e2k instead of e1e2n).
  • Loading branch information
RyanLauQF committed Nov 29, 2021
1 parent 9c7eea5 commit b286e9a
Show file tree
Hide file tree
Showing 3 changed files with 144 additions and 20 deletions.
135 changes: 121 additions & 14 deletions BLANKChess/src/engine/MoveOrdering.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,18 @@ public class MoveOrdering {
private static final int PAWN_INDEX = 5;
private static final int NONE_INDEX = 6;

// Move ordering scores
private static final int PV_MOVE_SCORE = 30000;
private static final int HASH_MOVE_SCORE = 20000;
private static final int CAPTURE_BONUS = 10000;
private static final int QUEEN_PROMOTION_BONUS = 9000;
private static final int FIRST_KILLER = 8000;
private static final int SECOND_KILLER = 7000;
private static final int CASTLING_BONUS = 3000;
private static final int KNIGHT_PROMOTION_BONUS = 1000;
private static final int UNINTERESTING_PROMOTION = 300;
private static final int SILENT_MOVE_PENALTY = -1000;

private static final int[][] MVV_LVA_SCORES = {
{0, 0, 0, 0, 0, 0, 0}, // victim K, attacker K, Q, R, B, N, P, None
{50, 51, 52, 53, 54, 55, 0}, // victim Q, attacker K, Q, R, B, N, P, None
Expand All @@ -20,39 +32,50 @@ public class MoveOrdering {
{0, 0, 0, 0, 0, 0, 0}, // victim None, attacker K, Q, R, B, N, P, None
};

public static ArrayList<Short> orderMoves(ArrayList<Short> moves, Search searcher, int searchPly) {
public static ArrayList<Short> orderMoves(ArrayList<Short> moves, Search searcher, int searchPly, short ttMove) {
moves.sort((move1, move2) -> {
int moveScore1 = getMoveScore(move1, searcher, searchPly);
int moveScore2 = getMoveScore(move2, searcher, searchPly);
int moveScore1 = getMoveScore(move1, searcher, searchPly, ttMove);
int moveScore2 = getMoveScore(move2, searcher, searchPly, ttMove);

return Integer.compare(moveScore2, moveScore1);
});
return moves;
}

private static int getMoveScore(Short move, Search searcher, int ply){
private static int getMoveScore(Short move, Search searcher, int ply, short ttMove){
Board board = searcher.board;

// evaluate the move scores
int score = 0;
int start = MoveGenerator.getStart(move);
int end = MoveGenerator.getEnd(move);

// PV Move
if(searcher.pvMoveScoring){
if (searcher.PVMoves[0][ply] == move)
{
// disable score PV flag
searcher.pvMoveScoring = false;

// give PV move the highest score to search it first
return 20000;
return PV_MOVE_SCORE;
}
}

// Hash Move
if(move == ttMove){
return HASH_MOVE_SCORE;
}

if(MoveGenerator.isCastling(move)){
return CASTLING_BONUS;
}

Piece startPiece = board.getTile(start).getPiece();

// Captures sorted by MVV-LVA
if(MoveGenerator.isCapture(move)){
// Sort by Most-Valuable Victim / Least-Valuable Aggressor
// Most-Valuable Victim / Least-Valuable Aggressor
if (MoveGenerator.getMoveType(move) == 4) {
// normal capture
score += MVV_LVA(board.getTile(end).getPiece(), startPiece);
Expand All @@ -61,35 +84,119 @@ private static int getMoveScore(Short move, Search searcher, int ply){
// enpassant capture
score += MVV_LVA_SCORES[PAWN_INDEX][PAWN_INDEX]; // pawn (victim) - pawn (attacker) capture
}
score += 10000; // prioritise captures
score += CAPTURE_BONUS; // prioritise captures
}
// quiet moves positions
else{
// killer move
if(move == searcher.killerMoves[0][ply]){
score += 8000;
score += FIRST_KILLER;
}
else if(move == searcher.killerMoves[1][ply]){
score += 7000;
score += SECOND_KILLER;
}
else{
// history move score
score += searcher.historyMoves[start][end];
}
}

// Promotion bonus score (Queen promotion is prioritised to search)
if(MoveGenerator.isPromotion(move)){
int moveType = MoveGenerator.getMoveType(move);
if(moveType == 8 || moveType == 12){
// knight promotion
score += KNIGHT_PROMOTION_BONUS;
}
else if(moveType == 11 || moveType == 15){
// queen promotion
score += QUEEN_PROMOTION_BONUS;
}
else{
score += UNINTERESTING_PROMOTION; // rook and bishop not as useful as Queen / Knight promotion (knight discovered checks)
}
}

// silent move
// score using change of Mid-game PSQT values
if(score == 0){
int startPos = (startPiece.isWhite()) ? start : EvalUtilities.blackFlippedPosition[start];
int endPos = (startPiece.isWhite()) ? end : EvalUtilities.blackFlippedPosition[end];

if(startPiece.isPawn()){
score += EvalUtilities.pawnMidGamePST[endPos] - EvalUtilities.pawnMidGamePST[startPos];
}
else if(startPiece.isBishop()){
score += EvalUtilities.bishopMidGamePST[endPos] - EvalUtilities.bishopMidGamePST[startPos];
}
else if(startPiece.isKnight()){
score += EvalUtilities.knightMidGamePST[endPos] - EvalUtilities.knightMidGamePST[startPos];
}
else if(startPiece.isRook()){
score += EvalUtilities.rookMidGamePST[endPos] - EvalUtilities.rookMidGamePST[startPos];
}
else if(startPiece.isQueen()){
score += EvalUtilities.queenMidGamePST[endPos] - EvalUtilities.queenMidGamePST[startPos];
}
else if(startPiece.isKing()){
score += EvalUtilities.kingMidGamePST[endPos] - EvalUtilities.kingMidGamePST[startPos];
}

score += SILENT_MOVE_PENALTY;
}

return score;
}

public static ArrayList<Short> orderQuiescence(ArrayList<Short> moves, Search searcher, short bestMove) {
moves.sort((move1, move2) -> {
int moveScore1 = getQuiescenceScore(move1, searcher, bestMove);
int moveScore2 = getQuiescenceScore(move2, searcher, bestMove);

return Integer.compare(moveScore2, moveScore1);
});
return moves;
}

private static int getQuiescenceScore(Short move, Search searcher, short bestMove){
Board board = searcher.board;

// evaluate the move scores
int score = 0;
int start = MoveGenerator.getStart(move);
int end = MoveGenerator.getEnd(move);

if(move == bestMove){
return HASH_MOVE_SCORE;
}

Piece startPiece = board.getTile(start).getPiece();

if(MoveGenerator.isCapture(move)){
// Sort by Most-Valuable Victim / Least-Valuable Aggressor
if (MoveGenerator.getMoveType(move) == 4) {
// normal capture
score += MVV_LVA(board.getTile(end).getPiece(), startPiece);
}
else{
// enpassant capture
score += MVV_LVA_SCORES[PAWN_INDEX][PAWN_INDEX]; // pawn (victim) - pawn (attacker) capture
}
score += CAPTURE_BONUS; // prioritise captures
}

if(MoveGenerator.isPromotion(move)){
int moveType = MoveGenerator.getMoveType(move);
if(moveType == 8 || moveType == 12){
// knight promotion
score += Knight.KNIGHT_MG_VALUE;
score += KNIGHT_PROMOTION_BONUS;
}
else if(moveType == 11 || moveType == 15){
// queen promotion
score += Queen.QUEEN_MG_VALUE;
score += QUEEN_PROMOTION_BONUS;
}
else{
score += 300; // rook and bishop not as useful as Queen / Knight promotion (knight discovered checks)
score += UNINTERESTING_PROMOTION; // rook and bishop not as useful as Queen / Knight promotion (knight discovered checks)
}
}

Expand Down Expand Up @@ -133,10 +240,10 @@ public static void main(String[] args) throws IOException {
Search searcher = new Search(board, new TranspositionTable());
searcher.depthSearch(8);

ArrayList<Short> allMoves = orderMoves(board.getAllLegalMoves(), searcher, 1);
ArrayList<Short> allMoves = orderMoves(board.getAllLegalMoves(), searcher, 1, (short) 0);
for(Short moves : allMoves){
System.out.print(FENUtilities.convertIndexToRankAndFile(MoveGenerator.getStart(moves)) + "-" + FENUtilities.convertIndexToRankAndFile(MoveGenerator.getEnd(moves)) + " ");
System.out.println("Score: " + getMoveScore(moves, searcher, 1) + " ");
System.out.println("Score: " + getMoveScore(moves, searcher, 1, (short) 0) + " ");
}
}
}
25 changes: 21 additions & 4 deletions BLANKChess/src/engine/Search.java
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,7 @@ public int negamax(int depth, int searchPly, int alpha, int beta){

// Probe transposition table if current position has already been evaluated before
long zobrist = board.getZobristHash();
short prevBestMove = -1;
if(searchPly != 0 && !isPV && TT.containsKey(zobrist)){
TranspositionTable.TTEntry entry = TT.getEntry(zobrist);

Expand Down Expand Up @@ -387,6 +388,7 @@ else if(entry.entry_TYPE == TranspositionTable.UPPERBOUND_TYPE){
cutOffCount++;
return entryScore;
}
prevBestMove = entry.bestMove;
}
}

Expand Down Expand Up @@ -495,7 +497,7 @@ else if(entry.entry_TYPE == TranspositionTable.UPPERBOUND_TYPE){
// set to check for fail-low node
byte moveFlag = TranspositionTable.UPPERBOUND_TYPE;

for (Short encodedMove : MoveOrdering.orderMoves(encodedMoves, this, searchPly)) {
for (Short encodedMove : MoveOrdering.orderMoves(encodedMoves, this, searchPly, prevBestMove)) {
moveCount++;
Move move = new Move(board, encodedMove);
move.makeMove();
Expand Down Expand Up @@ -598,6 +600,8 @@ else if(entry.entry_TYPE == TranspositionTable.UPPERBOUND_TYPE){
* i.e. Prevents the AI from blundering a piece due to search being cut at a certain depth causing it to not "see" opponent attacks
*/
private int quiescenceSearch(int alpha, int beta){
//info depth 11 seldepth 26 score cp -86 nodes 1386950 nps 124916 ttCut 28247 time 11103 pv e2a6 e6d5 c3d5 f6d5 e4d5 e7e5 e1f1 e8g8 a6b7 e5b2 a1d1 h3g2 f1g2

// every 32767 (in binary: 0b111111111111111) nodes, check for UCI commands
if((nodeCount & 32767) == 0){
listen();
Expand Down Expand Up @@ -626,14 +630,19 @@ private int quiescenceSearch(int alpha, int beta){
}
nodeCount++;

short prevBestMove = -1;
if(TT.containsKey(board.getZobristHash())){
TranspositionTable.TTEntry entry = TT.getEntry(board.getZobristHash());
prevBestMove = entry.bestMove;
}

if(alpha < stand_pat){
alpha = stand_pat;
}

ArrayList<Short> captureMoves = board.getAllCaptures();

for (Short encodedMove : MoveOrdering.orderMoves(captureMoves, this, ply)) {

for (Short encodedMove : MoveOrdering.orderQuiescence(captureMoves, this, prevBestMove)) {
Move move = new Move(board, encodedMove);

ply++;
Expand All @@ -651,10 +660,17 @@ private int quiescenceSearch(int alpha, int beta){

if(searchedScore > alpha){
alpha = searchedScore;
stand_pat = searchedScore;
// cut-off has occurred
if(searchedScore >= beta) {
TT.recordEntry(board.getZobristHash(), encodedMove, (byte) 0, beta, TranspositionTable.LOWERBOUND_TYPE);
return beta;
}
TT.recordEntry(board.getZobristHash(), encodedMove, (byte) 0, alpha, TranspositionTable.UPPERBOUND_TYPE);
}
else if(searchedScore > stand_pat){
stand_pat = searchedScore;
TT.recordEntry(board.getZobristHash(), encodedMove, (byte) 0, stand_pat, TranspositionTable.UPPERBOUND_TYPE);
}
}
return alpha;
Expand Down Expand Up @@ -775,7 +791,8 @@ public static void main(String[] args) throws IOException {
//board.init("r1bq1rk1/2p1bppp/p1np1n2/1p2p3/3PP3/1B3N2/PPP2PPP/RNBQR1K1 w - - 0 9");
//board.init("8/7k/5Q2/8/8/8/8/1K6 b - - 0 1");
Search search = new Search(board, new TranspositionTable());
search.depthSearch(11);
search.depthSearch(13);
search.depthSearch(13);
System.exit(0);

/*
Expand Down
4 changes: 2 additions & 2 deletions BLANKChess/src/main/UCI.java
Original file line number Diff line number Diff line change
Expand Up @@ -474,7 +474,7 @@ else if (Piece.getRow(end) == 0 || Piece.getRow(end) == 7) {
moveType = 14;
} else if (promotionType == 'b') {
moveType = 13;
} else if (promotionType == 'k') {
} else if (promotionType == 'n') {
moveType = 12;
}
} else {
Expand All @@ -485,7 +485,7 @@ else if (Piece.getRow(end) == 0 || Piece.getRow(end) == 7) {
moveType = 10;
} else if (promotionType == 'b') {
moveType = 9;
} else if (promotionType == 'k') {
} else if (promotionType == 'n') {
moveType = 8;
}
}
Expand Down

0 comments on commit b286e9a

Please sign in to comment.