Skip to content

Commit

Permalink
feat: add null move pruning (8.7s -> 3.4s) (2.5x speedup)
Browse files Browse the repository at this point in the history
  • Loading branch information
xjcl committed Dec 27, 2022
1 parent f873e18 commit 80d9a32
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 7 deletions.
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,12 @@ Build:
g++ -Ofast jangine.cpp -o jangine
Build on Linux for Windows:
sudo apt install mingw-w64
x86_64-w64-mingw32-g++ -Wall -c -g -Ofast jangine.cpp -o jangine.o
x86_64-w64-mingw32-g++ -static -static-libgcc -static-libstdc++ -o jangine.exe jangine.o
Run the included test suites:
# Validate move generation using move path enumeration ("perft")
Expand All @@ -90,3 +96,15 @@ Run as an interactive (stdin/stdout) UCI engine:
To host bot for lichess, clone the lichess-bot project https://github.com/ShailChoksi/lichess-bot and place the
engine binary in the `engines/` folder.
## Versions
| Version | vs previous | est. CCRL | <improve | CCRL |
|------------|-------------|-----------|----------|------|
| 2022-12-01 | - | 1900 | - | - |
| 2022-12-27 | 342.0/500 | 2033 | +133 | - |
CCRL rating estimated by self-play against other versions of Jangine:
1 / (1 + 10^((ratingA - ratingB)/400)) = scorePercentage
46 changes: 39 additions & 7 deletions jangine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -505,7 +505,7 @@ num initial_eval() {
}


inline PiecePlusCatling make_move(Move mv) // apparenly 7% of time spent in make and unmake
inline PiecePlusCatling make_move(Move mv) // apparently 7% of time spent in make and unmake
{
if (mv == NULLMOVE)
printf("XXX DANGEROUS! NULL MOVE\n");
Expand Down Expand Up @@ -569,6 +569,15 @@ inline PiecePlusCatling make_move(Move mv) // apparenly 7% of time spent in mak
return {hit_piece, old_cr_w, old_cr_b};
}

// TODO XXX TODO: move LASTMOVE setting to make_move/unmake_move, and maybe return it instead of setting global
inline PiecePlusCatling make_null_move()
{
// TODO: cannot null move if in check
zobrint_hash ^= zobrint_random_table[1][0]; // switch side to move

return {0, CASTLINGWHITE, CASTLINGBLACK};
}

inline void unmake_move(Move mv, num hit_piece, CASTLINGRIGHTS c_rights_w, CASTLINGRIGHTS c_rights_b)
{
num piece = board[mv.to];
Expand Down Expand Up @@ -618,6 +627,14 @@ inline void unmake_move(Move mv, num hit_piece, CASTLINGRIGHTS c_rights_w, CASTL
zobrint_hash ^= zobrint_random_table[1][0]; // switch side to move
}

inline void unmake_null_move(CASTLINGRIGHTS c_rights_w, CASTLINGRIGHTS c_rights_b)
{
CASTLINGWHITE = c_rights_w;
CASTLINGBLACK = c_rights_b;

zobrint_hash ^= zobrint_random_table[1][0]; // switch side to move
}

void make_move_str(const char* mv)
{
num a = 8 - (mv[1] - '0');
Expand Down Expand Up @@ -861,7 +878,7 @@ inline void store_hash_entry(Move move, num value, int8_t tte_flag, num depth, n

// non-negamax quiescent alpha-beta minimax search
// https://www.chessprogramming.org/Quiescence_Search
ValuePlusMove negamax(num COLOR, num alpha, num beta, num adaptive, bool is_quies, num depth, bool lines, bool lines_accurate) // 22% of time spent here
ValuePlusMove negamax(num COLOR, num alpha, num beta, num adaptive, bool is_quies, bool can_null_move, num depth, bool lines, bool lines_accurate) // 22% of time spent here
{
NODES_NORMAL += !is_quies;
NODES_QUIES += is_quies;
Expand Down Expand Up @@ -890,7 +907,7 @@ ValuePlusMove negamax(num COLOR, num alpha, num beta, num adaptive, bool is_quie

// Main search is done, do quiescence search at the leaf nodes
if (adaptive <= 0)
return negamax(COLOR, alpha, beta, adaptive, true, depth, lines, lines_accurate);
return negamax(COLOR, alpha, beta, adaptive, true, false, depth, lines, lines_accurate);
}

ValuePlusMove best = {-inf, {0}};
Expand All @@ -905,6 +922,21 @@ ValuePlusMove negamax(num COLOR, num alpha, num beta, num adaptive, bool is_quie
alpha = best.value;
}

// TODO: when switching to bitboards, also check if a piece other than a pawn is still on the board
// TODO: Only one null move in entire search or just disallow 2 in a row?
// TODO: set LASTMOVE?
// Null Move Pruning
bool in_check = king_in_check(COLOR); // maybe okay to do without as no duplicate moves possible and king will be captured instead
if (can_null_move && !in_check && depth >= 1 && adaptive >= 3)
{
PiecePlusCatling ppc = make_null_move();
ValuePlusMove rec = negamax(COLOR == WHITE ? BLACK : WHITE, -beta, -beta + 1, adaptive - 3, is_quies, false, depth + 1, lines, lines_accurate);
unmake_null_move(ppc.c_rights_w, ppc.c_rights_b);

if (-rec.value >= beta)
return {beta, {0}};
}

num alpha_orig = alpha;
Move mv = {0};
PiecePlusCatling ppc;
Expand Down Expand Up @@ -1027,7 +1059,7 @@ ValuePlusMove negamax(num COLOR, num alpha, num beta, num adaptive, bool is_quie
rec = negamax(
COLOR == WHITE ? BLACK : WHITE,
-beta, -alpha,
adaptive_new, is_quies, depth + 1, lines, lines_accurate
adaptive_new, is_quies, can_null_move, depth + 1, lines, lines_accurate
);
else {
// https://www.chessprogramming.org/Principal_Variation_Search
Expand All @@ -1036,13 +1068,13 @@ ValuePlusMove negamax(num COLOR, num alpha, num beta, num adaptive, bool is_quie
rec = negamax( // try a null-window search that saves time if everything is below alpha
COLOR == WHITE ? BLACK : WHITE,
-(alpha+1), -alpha,
adaptive_new, is_quies, depth + 1, lines, lines_accurate
adaptive_new, is_quies, can_null_move, depth + 1, lines, lines_accurate
);
if ((-rec.value) > alpha) // costly re-search if above search fails
rec = negamax(
COLOR == WHITE ? BLACK : WHITE,
-beta, -alpha,
adaptive_new, is_quies, depth + 1, lines, lines_accurate
adaptive_new, is_quies, can_null_move, depth + 1, lines, lines_accurate
);
}

Expand Down Expand Up @@ -1172,7 +1204,7 @@ std::string calc_move(bool lines = false)
SEARCH_ADAPTIVE_DEPTH = search_depth;
LASTMOVE = LASTMOVE_GAME;

ValuePlusMove best_at_depth = negamax(my_color, -inf/2, inf/2, SEARCH_ADAPTIVE_DEPTH, false, 0, (DEBUG >= 2), (DEBUG >= 2));
ValuePlusMove best_at_depth = negamax(my_color, -inf/2, inf/2, SEARCH_ADAPTIVE_DEPTH, false, true, 0, (DEBUG >= 2), (DEBUG >= 2));

printf_move(best_at_depth.move);
printf_move_eval(best_at_depth, true);
Expand Down

0 comments on commit 80d9a32

Please sign in to comment.