Skip to content

Commit 172ac19

Browse files
committed
Merge remote-tracking branch 'upstream/codex/implement-battle-of-the-kings-variant-7bhqbn'
1 parent 94ce9a5 commit 172ac19

File tree

15 files changed

+540
-71
lines changed

15 files changed

+540
-71
lines changed
File renamed without changes.

setup.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
long_description = fh.read().strip()
2929

3030
sources = glob("src/*.cpp") + glob("src/syzygy/*.cpp") + glob("src/nnue/*.cpp") + glob("src/nnue/features/*.cpp")
31+
headers = glob("src/*.h") + glob("src/syzygy/*.h") + glob("src/nnue/*.h") + glob("src/nnue/features/*.h")
3132
ffish_source_file = os.path.normcase("src/ffishjs.cpp")
3233
try:
3334
sources.remove(ffish_source_file)
@@ -37,6 +38,7 @@
3738
pyffish_module = Extension(
3839
"pyffish",
3940
sources=sources,
41+
depends=headers,
4042
extra_compile_args=args)
4143

4244
setup(name="pyffish", version="0.0.88",

src/apiutil.h

Lines changed: 39 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -372,31 +372,53 @@ inline bool has_insufficient_material(Color c, const Position& pos) {
372372
|| (pos.flag_region(c) && pos.count(c, pos.flag_piece(c))))
373373
return false;
374374

375-
// Restricted pieces
376-
Bitboard restricted = pos.pieces(~c, KING);
377-
// Atomic kings can not help checkmating
378-
if (pos.extinction_pseudo_royal() && pos.blast_on_capture() && (pos.extinction_piece_types() & COMMONER))
379-
restricted |= pos.pieces(c, COMMONER);
375+
// Precalculate if any promotion pawn types have pieces
376+
bool hasPromotingPawn = false;
377+
for (PieceSet pawnTypes = pos.promotion_pawn_types(c); pawnTypes; )
378+
{
379+
PieceType pawnType = pop_lsb(pawnTypes);
380+
if (pos.count(c, pawnType) > 0)
381+
{
382+
hasPromotingPawn = true;
383+
break;
384+
}
385+
}
386+
387+
// Determine checkmating potential of present pieces
388+
constexpr PieceSet MAJOR_PIECES = piece_set(ROOK) | QUEEN | ARCHBISHOP | CHANCELLOR
389+
| SILVER | GOLD | COMMONER | CENTAUR | AMAZON | BERS;
390+
constexpr PieceSet COLORBOUND_PIECES = piece_set(BISHOP) | FERS | FERS_ALFIL | ALFIL | ELEPHANT;
391+
Bitboard restricted = pos.pieces(KING);
392+
Bitboard colorbound = 0;
380393
for (PieceSet ps = pos.piece_types(); ps;)
381394
{
382395
PieceType pt = pop_lsb(ps);
383-
if (pt == KING || !(pos.board_bb(c, pt) & pos.board_bb(~c, KING)))
396+
397+
// Constrained pieces
398+
if (pt == KING || !(pos.board_bb(c, pt) & pos.board_bb(~c, KING)) || (pos.extinction_pseudo_royal() && pos.blast_on_capture() && (pos.extinction_piece_types() & pt)))
384399
restricted |= pos.pieces(c, pt);
385-
else if (is_custom(pt) && pos.count(c, pt) > 0)
386-
// to be conservative, assume any custom piece has mating potential
387-
return false;
400+
401+
// If piece is a major piece or a custom piece we consider it sufficient for mate.
402+
// To avoid false positives, we assume any custom piece has mating potential.
403+
else if ((MAJOR_PIECES & pt) || is_custom(pt))
404+
{
405+
// Check if piece is already on the board
406+
if (pos.count(c, pt) > 0)
407+
return false;
408+
409+
// Check if any pawn can promote to this piece type
410+
if (hasPromotingPawn && (pos.promotion_piece_types(c) & pt))
411+
return false;
412+
}
413+
414+
// Collect color-bound pieces
415+
else if (COLORBOUND_PIECES & pt)
416+
colorbound |= pos.pieces(pt);
388417
}
389418

390-
// Mating pieces
391-
for (PieceType pt : { ROOK, QUEEN, ARCHBISHOP, CHANCELLOR, SILVER, GOLD, COMMONER, CENTAUR, AMAZON, BERS })
392-
if ((pos.pieces(c, pt) & ~restricted) || (pos.count(c, pos.main_promotion_pawn_type(c)) && (pos.promotion_piece_types(c) & pt)))
393-
return false;
419+
Bitboard unbound = pos.pieces() ^ restricted ^ colorbound;
394420

395421
// Color-bound pieces
396-
Bitboard colorbound = 0, unbound;
397-
for (PieceType pt : { BISHOP, FERS, FERS_ALFIL, ALFIL, ELEPHANT })
398-
colorbound |= pos.pieces(pt) & ~restricted;
399-
unbound = pos.pieces() ^ restricted ^ colorbound;
400422
if ((colorbound & pos.pieces(c)) && (((DarkSquares & colorbound) && (~DarkSquares & colorbound)) || unbound || pos.stalemate_value() != VALUE_DRAW || pos.check_counting() || pos.makpong()))
401423
return false;
402424

src/endgame.cpp

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1033,24 +1033,34 @@ Value Endgame<RK, EG_EVAL_ANTI>::operator()(const Position& pos) const {
10331033
}
10341034

10351035

1036-
/// K vs N. The king usally wins, but there are a few exceptions.
1036+
/// K vs N. The king usually wins, but there are a few exceptions.
10371037
template<>
10381038
Value Endgame<KN, EG_EVAL_ANTI>::operator()(const Position& pos) const {
10391039

10401040
assert(pos.endgame_eval() == EG_EVAL_ANTI);
10411041

10421042
Square KSq = pos.square<COMMONER>(strongSide);
10431043
Square NSq = pos.square<KNIGHT>(weakSide);
1044+
Bitboard kingAttacks = attacks_bb<KING>(KSq) & pos.board_bb();
1045+
Bitboard knightAttacks = attacks_bb<KNIGHT>(NSq) & pos.board_bb();
1046+
bool strongSideToMove = pos.side_to_move() == strongSide;
1047+
1048+
// Loss in 1 play
1049+
if (strongSideToMove ? kingAttacks & NSq : knightAttacks & KSq)
1050+
return VALUE_TB_LOSS_IN_MAX_PLY + 1;
1051+
// Win in 2 ply
1052+
if (kingAttacks & knightAttacks)
1053+
return VALUE_TB_WIN_IN_MAX_PLY - 2;
1054+
// Loss in 3 ply
1055+
if (strongSideToMove ? knightAttacks & KSq : kingAttacks & NSq)
1056+
return VALUE_TB_LOSS_IN_MAX_PLY + 3;
1057+
1058+
Value result = Value(push_to_edge(NSq, pos)) - push_to_edge(KSq, pos);
1059+
// The king usually wins, but scenarios with king on the edge are more complicated
1060+
if (!(KSq & (FileABB | FileHBB | Rank1BB | Rank8BB)))
1061+
result += VALUE_KNOWN_WIN;
10441062

1045-
// wins for knight
1046-
if (pos.side_to_move() == strongSide && (attacks_bb<KNIGHT>(NSq) & KSq))
1047-
return -VALUE_KNOWN_WIN;
1048-
if (pos.side_to_move() == weakSide && (attacks_bb<KNIGHT>(NSq) & attacks_bb<KING>(KSq)))
1049-
return VALUE_KNOWN_WIN;
1050-
1051-
Value result = VALUE_KNOWN_WIN + push_to_edge(NSq, pos) - push_to_edge(KSq, pos);
1052-
1053-
return strongSide == pos.side_to_move() ? result : -result;
1063+
return strongSideToMove ? result : -result;
10541064
}
10551065

10561066
/// N vs N. The side to move always wins/loses if the knights are on

src/movegen.cpp

Lines changed: 51 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,23 @@ namespace {
6464
return moveList;
6565
}
6666

67-
*moveList++ = make<T>(from, to, pt);
67+
PieceType forcedGate = NO_PIECE_TYPE;
68+
Square forcedGateSquare = SQ_NONE;
69+
if (from != to)
70+
{
71+
Piece pcFrom = pos.piece_on(from);
72+
if (pcFrom != NO_PIECE)
73+
{
74+
forcedGate = pos.forced_gating_type(us, type_of(pcFrom));
75+
if (forcedGate != NO_PIECE_TYPE)
76+
forcedGateSquare = from;
77+
}
78+
}
79+
80+
if (forcedGate != NO_PIECE_TYPE)
81+
*moveList++ = make_gating<T>(from, to, forcedGate, forcedGateSquare);
82+
else
83+
*moveList++ = make<T>(from, to, pt);
6884

6985
// Gating moves
7086
if (pos.seirawan_gating() && (pos.gates(us) & from))
@@ -142,14 +158,20 @@ namespace {
142158
constexpr Direction UpRight = (Us == WHITE ? NORTH_EAST : SOUTH_WEST);
143159
constexpr Direction UpLeft = (Us == WHITE ? NORTH_WEST : SOUTH_EAST);
144160

161+
const bool allowFriendlyCaptures = pos.self_capture()
162+
&& (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS);
163+
145164
const Bitboard promotionZone = pos.promotion_zone(Us);
146165
const Bitboard standardPromotionZone = pos.sittuyin_promotion() ? Bitboard(0) : promotionZone;
147166
const Bitboard doubleStepRegion = pos.double_step_region(Us);
148167
const Bitboard tripleStepRegion = pos.triple_step_region(Us);
149168

150169
const Bitboard pawns = pos.pieces(Us, PAWN);
151170
const Bitboard movable = pos.board_bb(Us, PAWN) & ~pos.pieces();
152-
const Bitboard capturable = pos.board_bb(Us, PAWN) & pos.pieces(Them);
171+
const Bitboard friendlyCapturable = pos.pieces(Us) & ~pos.pieces(Us, KING);
172+
const Bitboard capturable = pos.board_bb(Us, PAWN)
173+
& (allowFriendlyCaptures ? (pos.pieces(Them) | friendlyCapturable)
174+
: pos.pieces(Them));
153175

154176
target = Type == EVASIONS ? target : AllSquares;
155177

@@ -287,26 +309,35 @@ namespace {
287309

288310

289311
template<Color Us, GenType Type>
290-
ExtMove* generate_moves(const Position& pos, ExtMove* moveList, PieceType Pt, Bitboard target) {
312+
ExtMove* generate_moves(const Position& pos, ExtMove* moveList, PieceType Pt, Bitboard target, Bitboard captureTarget) {
291313

292314
assert(Pt != KING && Pt != PAWN);
293315

294316
Bitboard bb = pos.pieces(Us, Pt);
295317

318+
const bool allowFriendlyCaptures = pos.self_capture()
319+
&& (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS);
320+
296321
while (bb)
297322
{
298323
Square from = pop_lsb(bb);
299324

300325
Bitboard attacks = pos.attacks_from(Us, Pt, from);
301326
Bitboard quiets = pos.moves_from(Us, Pt, from);
302-
Bitboard b = ( (attacks & pos.pieces())
303-
| (quiets & ~pos.pieces()));
304-
Bitboard b1 = b & target;
327+
Bitboard captureSquares = (attacks & pos.pieces()) & captureTarget;
328+
Bitboard quietSquares = (quiets & ~pos.pieces()) & target;
329+
Bitboard b = captureSquares | quietSquares;
330+
Bitboard b1 = b;
305331
Bitboard promotion_zone = pos.promotion_zone(Us);
306332
PieceType promPt = pos.promoted_piece_type(Pt);
307333
Bitboard b2 = promPt && (!pos.promotion_limit(promPt) || pos.promotion_limit(promPt) > pos.count(Us, promPt)) ? b1 : Bitboard(0);
308334
Bitboard b3 = pos.piece_demotion() && pos.is_promoted(from) ? b1 : Bitboard(0);
309-
Bitboard pawnPromotions = (pos.promotion_pawn_types(Us) & Pt) ? (b & (Type == EVASIONS ? target : ~pos.pieces(Us)) & promotion_zone) : Bitboard(0);
335+
Bitboard pawnPromotions = (pos.promotion_pawn_types(Us) & Pt)
336+
? (b & (Type == EVASIONS ? target
337+
: (~pos.pieces(Us)
338+
| (allowFriendlyCaptures ? pos.pieces(Us) : Bitboard(0))))
339+
& promotion_zone)
340+
: Bitboard(0);
310341
Bitboard epSquares = (pos.en_passant_types(Us) & Pt) ? (attacks & ~quiets & pos.ep_squares() & ~pos.pieces()) : Bitboard(0);
311342

312343
// target squares considering pawn promotions
@@ -380,6 +411,7 @@ namespace {
380411
constexpr bool Checks = Type == QUIET_CHECKS; // Reduce template instantiations
381412
const Square ksq = pos.count<KING>(Us) ? pos.square<KING>(Us) : SQ_NONE;
382413
Bitboard target;
414+
Bitboard captureTarget = Type == EVASIONS ? ~pos.pieces(Us) : Bitboard(0);
383415

384416
// Skip generating non-king moves when in double check
385417
if (Type != EVASIONS || !more_than_one(pos.checkers() & ~pos.non_sliding_riders()))
@@ -402,9 +434,13 @@ namespace {
402434
// Remove inaccessible squares (outside board + wall squares)
403435
target &= pos.board_bb();
404436

437+
captureTarget = target;
438+
if (pos.self_capture() && (Type == NON_EVASIONS || Type == CAPTURES))
439+
captureTarget |= pos.pieces(Us) & ~pos.pieces(Us, KING);
440+
405441
moveList = generate_pawn_moves<Us, Type>(pos, moveList, target);
406442
for (PieceSet ps = pos.piece_types() & ~(piece_set(PAWN) | KING); ps;)
407-
moveList = generate_moves<Us, Type>(pos, moveList, pop_lsb(ps), target);
443+
moveList = generate_moves<Us, Type>(pos, moveList, pop_lsb(ps), target, captureTarget);
408444
// generate drops
409445
if (pos.piece_drops() && Type != CAPTURES && (pos.can_drop(Us, ALL_PIECES) || pos.two_boards()))
410446
for (PieceSet ps = pos.piece_types(); ps;)
@@ -455,8 +491,13 @@ namespace {
455491
// King moves
456492
if (pos.count<KING>(Us) && (!Checks || pos.blockers_for_king(~Us) & ksq))
457493
{
458-
Bitboard b = ( (pos.attacks_from(Us, KING, ksq) & pos.pieces())
459-
| (pos.moves_from(Us, KING, ksq) & ~pos.pieces())) & (Type == EVASIONS ? ~pos.pieces(Us) : target);
494+
Bitboard kingAttacks = pos.attacks_from(Us, KING, ksq) & pos.pieces();
495+
Bitboard kingMoves = pos.moves_from (Us, KING, ksq) & ~pos.pieces();
496+
Bitboard kingCaptureMask = Type == EVASIONS ? ~pos.pieces(Us) : captureTarget;
497+
if (Type == EVASIONS && pos.self_capture())
498+
kingCaptureMask |= pos.pieces(Us) & ~pos.pieces(Us, KING);
499+
Bitboard kingQuietMask = Type == EVASIONS ? ~pos.pieces(Us) : target;
500+
Bitboard b = (kingAttacks & kingCaptureMask) | (kingMoves & kingQuietMask);
460501
while (b)
461502
moveList = make_move_and_gating<NORMAL>(pos, moveList, Us, ksq, pop_lsb(b));
462503

src/nnue/features/half_ka_v2_variants.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ namespace Stockfish::Eval::NNUE::Features {
106106
}
107107

108108
bool HalfKAv2Variants::requires_refresh(StateInfo* st, Color perspective, const Position& pos) {
109-
return st->dirtyPiece.piece[0] == make_piece(perspective, pos.nnue_king()) || pos.flip_enclosed_pieces();
109+
return true;
110110
}
111111

112112
} // namespace Stockfish::Eval::NNUE::Features

src/parser.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,7 @@ Variant* VariantParser<DoCheck>::parse(Variant* v) {
419419
parse_attribute("blastOnCapture", v->blastOnCapture);
420420
parse_attribute("blastImmuneTypes", v->blastImmuneTypes, v->pieceToChar);
421421
parse_attribute("mutuallyImmuneTypes", v->mutuallyImmuneTypes, v->pieceToChar);
422+
parse_attribute("ironPieceTypes", v->ironPieceTypes, v->pieceToChar);
422423
parse_attribute("petrifyOnCaptureTypes", v->petrifyOnCaptureTypes, v->pieceToChar);
423424
parse_attribute("petrifyBlastPieces", v->petrifyBlastPieces);
424425
parse_attribute("doubleStep", v->doubleStep);
@@ -454,6 +455,7 @@ Variant* VariantParser<DoCheck>::parse(Variant* v) {
454455
parse_attribute("checking", v->checking);
455456
parse_attribute("dropChecks", v->dropChecks);
456457
parse_attribute("mustCapture", v->mustCapture);
458+
parse_attribute("selfCapture", v->selfCapture);
457459
parse_attribute("mustDrop", v->mustDrop);
458460
parse_attribute("mustDropType", v->mustDropType, v->pieceToChar);
459461
parse_attribute("pieceDrops", v->pieceDrops);
@@ -514,6 +516,7 @@ Variant* VariantParser<DoCheck>::parse(Variant* v) {
514516
parse_attribute("extinctionValue", v->extinctionValue);
515517
parse_attribute("extinctionClaim", v->extinctionClaim);
516518
parse_attribute("extinctionPseudoRoyal", v->extinctionPseudoRoyal);
519+
parse_attribute("extinctionFirstCaptureWins", v->extinctionFirstCaptureWins);
517520
parse_attribute("dupleCheck", v->dupleCheck);
518521
// extinction piece types
519522
parse_attribute("extinctionPieceTypes", v->extinctionPieceTypes, v->pieceToChar);

0 commit comments

Comments
 (0)