Skip to content

Commit

Permalink
12 bit eval range with 20 bit move (#514)
Browse files Browse the repository at this point in the history
Bench: 3680773

This patch does three things:

    Removes all endgame related code. It didn't really do anything anyways.
    Clamps the static eval range to [-2048, 2047] (12 bits)
    Re-scheme the moves to fit into 20 bits. This change had no impact on the engine.

Combining these three patches, I'm able to store the move and static eval in the TT in a singular int32_t, allowing a 10 byte TT entry. This can be used with a 2 byte padding with 3 entries to create a 32 byte bucket, increasing the # of TT entries by 50%.

High Hash Pressure Tests

ELO | 4.88 +- 3.30 (95%)
SPRT | 6.0+0.06s Threads=1 Hash=2MB
LLR | 2.95 (-2.94, 2.94) [0.00, 3.00]
GAMES | N: 20928 W: 5314 L: 5020 D: 10594
http://chess.grantnet.us/test/33940/

ELO | 6.47 +- 3.88 (95%)
SPRT | 30.0+0.30s Threads=1 Hash=8MB
LLR | 2.97 (-2.94, 2.94) [0.00, 3.00]
GAMES | N: 14552 W: 3580 L: 3309 D: 7663
http://chess.grantnet.us/test/33943/

ELO | 10.21 +- 5.11 (95%)
SPRT | 4.0+0.04s Threads=8 Hash=8MB
LLR | 2.96 (-2.94, 2.94) [0.00, 3.00]
GAMES | N: 8650 W: 2241 L: 1987 D: 4422
http://chess.grantnet.us/test/33945/

No-Adjudication Verification

Elo | 1.66 +- 2.82 (95%)
SPRT | 8.0+0.08s Threads=1 Hash=8MB
LLR | 2.94 (-2.25, 2.89) [-2.50, 0.50]
Games | N: 27912 W: 6751 L: 6618 D: 14543
Penta | [106, 3140, 7336, 3263, 111]
  • Loading branch information
jhonnold authored Oct 6, 2023
1 parent 05769c3 commit c0646c5
Show file tree
Hide file tree
Showing 18 changed files with 176 additions and 301 deletions.
Binary file removed src/bitbases/KBPvK.bb
Binary file not shown.
Binary file removed src/bitbases/KPvK.bb
Binary file not shown.
14 changes: 7 additions & 7 deletions src/board.c
Original file line number Diff line number Diff line change
Expand Up @@ -407,15 +407,15 @@ void MakeMoveUpdate(Move move, Board* board, int update) {
}

if (PieceType(piece) == PAWN) {
if (IsDP(move)) {
if ((from ^ to) == 16) {
int epSquare = to - PawnDir(board->stm);

if (GetPawnAttacks(epSquare, board->stm) & PieceBB(PAWN, board->xstm)) {
board->epSquare = epSquare;
board->zobrist ^= ZOBRIST_EP_KEYS[board->epSquare];
}
} else if (Promo(move)) {
int promoted = Promo(move);
} else if (IsPromo(move)) {
int promoted = PromoPiece(move, board->stm);
FlipBit(board->pieces[piece], to);
FlipBit(board->pieces[promoted], to);

Expand Down Expand Up @@ -471,8 +471,8 @@ void UndoMove(Move move, Board* board) {
board->threatened = board->history[board->histPly].threatened;
board->easyCapture = board->history[board->histPly].easyCapture;

if (Promo(move)) {
int promoted = Promo(move);
if (IsPromo(move)) {
int promoted = PromoPiece(move, board->stm);
FlipBit(board->pieces[piece], to);
FlipBit(board->pieces[promoted], to);
board->squares[to] = piece;
Expand Down Expand Up @@ -647,7 +647,7 @@ int IsPseudoLegal(Move move, Board* board) {
return 1;
}

if (Promo(move)) {
if (IsPromo(move)) {
if (BitCount(board->checkers) > 1)
return 0;

Expand Down Expand Up @@ -746,7 +746,7 @@ void InitCuckoo() {
if (!GetBit(GetPieceAttacks(s1, 0, pcType), s2))
continue;

Move move = BuildMove(s1, s2, pc, NO_PROMO, QUIET);
Move move = BuildMove(s1, s2, pc, QUIET_FLAG);
uint64_t hash = ZOBRIST_PIECES[pc][s1] ^ ZOBRIST_PIECES[pc][s2] ^ ZOBRIST_SIDE_KEY;

uint32_t i = Hash1(hash);
Expand Down
194 changes: 1 addition & 193 deletions src/endgame.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,165 +16,10 @@

#include "endgame.h"

#include <stdlib.h>

#include "bits.h"
#include "board.h"
#include "search.h"
#include "see.h"
#include "types.h"
#include "util.h"

#define INCBIN_PREFIX
#define INCBIN_STYLE INCBIN_STYLE_CAMEL
#include "incbin.h"

INCBIN(KPK, "bitbases/KPvK.bb");
INCBIN(KBPK, "bitbases/KBPvK.bb");

INLINE int PushTogether(int sq1, int sq2) {
return 70 - Distance(sq1, sq2);
}

INLINE int PushToEdge(int sq) {
int rankDistance = Min(Rank(sq), 7 - Rank(sq));
int fileDistance = Min(File(sq), 7 - File(sq));

return 20 - (rankDistance * rankDistance) - (fileDistance * fileDistance);
}

INLINE int MaterialValue(Board* board, const int side) {
int staticScore = 0;
for (int piece = PAWN; piece <= QUEEN; piece++)
staticScore += BitCount(PieceBB(piece, side)) * SEE_VALUE[piece];

return staticScore;
}

// The following KPK code is modified for my use from Cheng (as is the dataset)
INLINE uint32_t KPKIndex(int winningKing, int losingKing, int pawn, int stm) {
int file = File(pawn);
int x = file > 3 ? 7 : 0;

winningKing ^= x;
losingKing ^= x;
pawn ^= x;
file ^= x;

uint32_t p = (((pawn & 0x38) - 8) >> 1) | file;

return (uint32_t) winningKing | ((uint32_t) losingKing << 6) | ((uint32_t) stm << 12) | ((uint32_t) p << 13);
}

INLINE uint8_t KPKDraw(int winningSide, int winningKing, int losingKing, int pawn, int stm) {
uint32_t x = (winningSide == WHITE) ? 0 : 56;
uint32_t idx = KPKIndex(winningKing ^ x, losingKing ^ x, pawn ^ x, winningSide ^ stm);

return (uint8_t) (KPKData[idx >> 3] & (1U << (idx & 7)));
}

INLINE int EvaluateKPK(Board* board, const int winningSide) {
const int losingSide = winningSide ^ 1;
int score = WINNING_ENDGAME + MaterialValue(board, winningSide);

int winningKing = LSB(PieceBB(KING, winningSide));
int losingKing = LSB(PieceBB(KING, losingSide));
int pawn = winningSide == WHITE ? LSB(PieceBB(PAWN, WHITE)) : MSB(PieceBB(PAWN, BLACK));

if (KPKDraw(winningSide, winningKing, losingKing, pawn, board->stm))
return 0;

score += winningSide == WHITE ? (7 - Rank(pawn)) : Rank(pawn);

return winningSide == board->stm ? score : -score;
}

INLINE int EvaluateKXK(Board* board, const int winningSide) {
const int losingSide = winningSide ^ 1;
int score = WINNING_ENDGAME + MaterialValue(board, winningSide);

int winningKing = LSB(PieceBB(KING, winningSide));
int losingKing = LSB(PieceBB(KING, losingSide));

score += PushTogether(winningKing, losingKing) + PushToEdge(losingKing);

score = winningSide == board->stm ? score : -score;
return Min(TB_WIN_BOUND - 1, Max(-TB_WIN_BOUND + 1, score));
}

INLINE int EvaluateKBNK(Board* board, const int winningSide) {
const int losingSide = winningSide ^ 1;
int score = WINNING_ENDGAME + MaterialValue(board, winningSide);

int winningKing = LSB(PieceBB(KING, winningSide));
int losingKing = LSB(PieceBB(KING, losingSide));

score += PushTogether(winningKing, losingKing);

if (DARK_SQS & PieceBB(BISHOP, winningSide)) {
score += 50 * (7 - Min(MDistance(losingKing, A1), MDistance(losingKing, H8)));
} else {
score += 50 * (7 - Min(MDistance(losingKing, A8), MDistance(losingKing, H1)));
}

score = winningSide == board->stm ? score : -score;
return Min(TB_WIN_BOUND - 1, Max(-TB_WIN_BOUND + 1, score));
}

INLINE uint32_t KBPKIndex(int wking, int lking, int bishop, int pawn, int stm) {
int file = File(pawn);
int x = file > 3 ? 7 : 0;

wking ^= x, lking ^= x, bishop ^= x, pawn ^= x;

uint32_t p = (pawn >> 3) - 1;
uint32_t b = bishop >> 1;

return (uint32_t) wking | ((uint32_t) lking << 6) | (b << 12) | ((uint32_t) stm << 17) | (p << 18);
}

INLINE uint8_t KBPKDraw(int winningSide, int winningKing, int losingKing, int bishop, int pawn, int stm) {
int x = winningSide == WHITE ? 0 : 56;
uint32_t idx = KBPKIndex(winningKing ^ x, losingKing ^ x, bishop ^ x, pawn ^ x, winningSide ^ stm);

return !(uint8_t) (KBPKData[idx >> 3] & (1U << (idx & 7)));
}

INLINE int EvaluateKBPK(Board* board, const int winningSide) {
const int losingSide = winningSide ^ 1;

int pawn = winningSide == WHITE ? LSB(PieceBB(PAWN, WHITE)) : MSB(PieceBB(PAWN, BLACK));
int promotionSq = winningSide == WHITE ? File(pawn) : A1 + File(pawn);
int darkPromoSq = !!GetBit(DARK_SQS, promotionSq);
int darkBishop = !!(DARK_SQS & PieceBB(BISHOP, winningSide));

uint8_t files = PawnFiles(PieceBB(PAWN, winningSide));

// "Winning" if not a rook pawn or have right bishop
if ((files != 0x01 && files != 0x80) || darkPromoSq == darkBishop) {
int score = WINNING_ENDGAME + MaterialValue(board, winningSide);

score += winningSide == WHITE ? (7 - Rank(pawn)) : Rank(pawn);
score = winningSide == board->stm ? score : -score;

return Min(TB_WIN_BOUND - 1, Max(-TB_WIN_BOUND + 1, score));
}

// Utilize bitbase for everything else
int winningKing = LSB(PieceBB(KING, winningSide));
int losingKing = LSB(PieceBB(KING, losingSide));
int bishop = LSB(PieceBB(BISHOP, winningSide));

if (KBPKDraw(winningSide, winningKing, losingKing, bishop, pawn, board->stm))
return 0;

int score = WINNING_ENDGAME + MaterialValue(board, winningSide);

score += winningSide == WHITE ? (7 - Rank(pawn)) : Rank(pawn);
score = winningSide == board->stm ? score : -score;

return Min(TB_WIN_BOUND - 1, Max(-TB_WIN_BOUND + 1, score));
}

int EvaluateKnownPositions(Board* board) {
switch (board->piecesCounts) {
// See IsMaterialDraw
Expand All @@ -190,43 +35,6 @@ int EvaluateKnownPositions(Board* board) {
case 0x100100: // KNkb
case 0x110000: // KBkb
return 0;
case 0x1: // KPk
return EvaluateKPK(board, WHITE);
case 0x10: // Kkp
return EvaluateKPK(board, BLACK);
case 0x10100: // KBNk
return EvaluateKBNK(board, WHITE);
case 0x101000: // Kkbn
return EvaluateKBNK(board, BLACK);
default: break;
}

if (BitCount(OccBB(BLACK)) == 1) {
// KBPK
if ((board->piecesCounts ^ 0x10000) <= 0xF)
return EvaluateKBPK(board, WHITE);

// stacked rook pawns
if (board->piecesCounts <= 0xF) {
uint8_t files = PawnFiles(PieceBB(PAWN, WHITE));
if (files == 0x1 || files == 0x80)
return EvaluateKPK(board, WHITE);
}

return EvaluateKXK(board, WHITE);
} else if (BitCount(OccBB(WHITE)) == 1) {
// Kkbp
if ((board->piecesCounts ^ 0x100000) <= 0xF0)
return EvaluateKBPK(board, BLACK);

// stacked rook pawns
if (board->piecesCounts <= 0xF0) {
uint8_t files = PawnFiles(PieceBB(PAWN, BLACK));
if (files == 0x1 || files == 0x80)
return EvaluateKPK(board, BLACK);
}

return EvaluateKXK(board, BLACK);
}

return UNKNOWN;
Expand Down
2 changes: 0 additions & 2 deletions src/endgame.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@
#include "types.h"
#include "util.h"

#define WINNING_ENDGAME 25000

int EvaluateKnownPositions(Board* board);

#endif
2 changes: 1 addition & 1 deletion src/eval.c
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ Score Evaluate(Board* board, ThreadData* thread) {
// scaled based on phase [1, 1.5]
score = (128 + board->phase) * score / 128;

return Min(TB_WIN_BOUND - 1, Max(-TB_WIN_BOUND + 1, score));
return Min(2047, Max(-2048, score));
}

void EvaluateTrace(Board* board) {
Expand Down
2 changes: 1 addition & 1 deletion src/history.c
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ void UpdateHistories(SearchStack* ss,
AddHistoryHeuristic(&HH(stm, bestMove, board->threatened), inc);
UpdateCH(ss, bestMove, inc);

if (Promo(bestMove) < WHITE_QUEEN) {
if (PromoPT(bestMove) != QUEEN) {
AddKillerMove(ss, bestMove);

if ((ss - 1)->move)
Expand Down
2 changes: 1 addition & 1 deletion src/makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
EXE = berserk
SRC = *.c nn/*.c pyrrhic/tbprobe.c
CC = gcc
VERSION = 20230927
VERSION = 20231006
MAIN_NETWORK = networks/berserk-d1b801c65262.nn
EVALFILE = $(MAIN_NETWORK)
DEFS = -DVERSION=\"$(VERSION)\" -DEVALFILE=\"$(EVALFILE)\" -DNDEBUG
Expand Down
6 changes: 3 additions & 3 deletions src/move.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@

const char* PIECE_TO_CHAR = "PpNnBbRrQqKk";

const char* PROMOTION_TO_CHAR = "--nnbbrrqq--";
const char* PROMOTION_TO_CHAR = "-nbrq-";

const int CHAR_TO_PIECE[] = {
['P'] = WHITE_PAWN, //
Expand Down Expand Up @@ -95,8 +95,8 @@ char* MoveToStr(Move move, Board* board) {
if (CHESS_960 && IsCas(move))
to = board->cr[CASTLING_ROOK[to]];

if (Promo(move)) {
sprintf(buffer, "%s%s%c", SQ_TO_COORD[from], SQ_TO_COORD[to], PROMOTION_TO_CHAR[Promo(move)]);
if (IsPromo(move)) {
sprintf(buffer, "%s%s%c", SQ_TO_COORD[from], SQ_TO_COORD[to], PROMOTION_TO_CHAR[PromoPT(move)]);
} else {
sprintf(buffer, "%s%s", SQ_TO_COORD[from], SQ_TO_COORD[to]);
}
Expand Down
36 changes: 24 additions & 12 deletions src/move.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,25 +21,37 @@

#define NULL_MOVE 0

#define QUIET_FLAG 0b0000
#define CASTLE_FLAG 0b0001
#define CAPTURE_FLAG 0b0100
#define EP_FLAG 0b0110
#define PROMO_FLAG 0b1000
#define KNIGHT_PROMO_FLAG 0b1000
#define BISHOP_PROMO_FLAG 0b1001
#define ROOK_PROMO_FLAG 0b1010
#define QUEEN_PROMO_FLAG 0b1011

extern const int CHAR_TO_PIECE[];
extern const char* PIECE_TO_CHAR;
extern const char* PROMOTION_TO_CHAR;
extern const char* SQ_TO_COORD[64];
extern const int CASTLE_ROOK_DEST[64];
extern const int CASTLING_ROOK[64];

#define BuildMove(from, to, piece, promo, flags) \
(from) | ((to) << 6) | ((piece) << 12) | ((promo) << 16) | ((flags) << 20)
#define From(move) ((int) (move) &0x3f)
#define To(move) (((int) (move) &0xfc0) >> 6)
#define Moving(move) (((int) (move) &0xf000) >> 12)
#define Promo(move) (((int) (move) &0xf0000) >> 16)
#define IsCap(move) (((int) (move) &0x100000) >> 20)
#define IsDP(move) (((int) (move) &0x200000) >> 21)
#define IsEP(move) (((int) (move) &0x400000) >> 22)
#define IsCas(move) (((int) (move) &0x800000) >> 23)
// just mask the from/to bits into a single int for indexing butterfly tables
#define FromTo(move) ((int) (move) &0xfff)
#define BuildMove(from, to, piece, flags) (from) | ((to) << 6) | ((piece) << 12) | ((flags) << 16)
#define FromTo(move) (((int) (move) &0x00fff) >> 0)
#define From(move) (((int) (move) &0x0003f) >> 0)
#define To(move) (((int) (move) &0x00fc0) >> 6)
#define Moving(move) (((int) (move) &0x0f000) >> 12)
#define Flags(move) (((int) (move) &0xf0000) >> 16)

#define IsCap(move) (!!(Flags(move) & CAPTURE_FLAG))
#define IsEP(move) (Flags(move) == EP_FLAG)
#define IsCas(move) (Flags(move) == CASTLE_FLAG)

#define IsPromo(move) (!!(Flags(move) & PROMO_FLAG))
#define PromoPT(move) ((Flags(move) & 0x3) + KNIGHT)
#define PromoPiece(move, stm) (Piece(PromoPT(move), stm))

Move ParseMove(char* moveStr, Board* board);
char* MoveToStr(Move move, Board* board);
Expand Down
Loading

0 comments on commit c0646c5

Please sign in to comment.