Skip to content

Commit 863ed89

Browse files
committed
Adjust Battle of the Kings commoner extinction
1 parent d8cabde commit 863ed89

File tree

8 files changed

+195
-5
lines changed

8 files changed

+195
-5
lines changed

src/movegen.cpp

Lines changed: 17 additions & 1 deletion
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))

src/parser.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -516,6 +516,7 @@ Variant* VariantParser<DoCheck>::parse(Variant* v) {
516516
parse_attribute("extinctionValue", v->extinctionValue);
517517
parse_attribute("extinctionClaim", v->extinctionClaim);
518518
parse_attribute("extinctionPseudoRoyal", v->extinctionPseudoRoyal);
519+
parse_attribute("extinctionFirstCaptureWins", v->extinctionFirstCaptureWins);
519520
parse_attribute("dupleCheck", v->dupleCheck);
520521
// extinction piece types
521522
parse_attribute("extinctionPieceTypes", v->extinctionPieceTypes, v->pieceToChar);

src/position.cpp

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1263,6 +1263,17 @@ bool Position::legal(Move m) const {
12631263

12641264
Bitboard occupied = (type_of(m) != DROP ? pieces() ^ from : pieces()) | to;
12651265

1266+
if ( is_gating(m)
1267+
&& ( gating_type(m) == KING
1268+
|| (extinction_pseudo_royal() && (extinction_piece_types() & piece_set(gating_type(m))))))
1269+
{
1270+
Bitboard occ = occupied | gating_square(m);
1271+
if (type_of(m) == EN_PASSANT)
1272+
occ ^= capture_square(to);
1273+
if (attackers_to(gating_square(m), occ, ~us))
1274+
return false;
1275+
}
1276+
12661277
// Flying general rule and bikjang
12671278
// In case of bikjang passing is always allowed, even when in check
12681279
if (st->bikjang && is_pass(m))
@@ -1976,7 +1987,8 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
19761987
}
19771988

19781989
put_piece(gating_piece, gate);
1979-
remove_from_hand(gating_piece);
1990+
if (gating_from_hand())
1991+
remove_from_hand(gating_piece);
19801992

19811993
st->gatesBB[us] ^= gate;
19821994
k ^= Zobrist::psq[gating_piece][gate];
@@ -2204,7 +2216,8 @@ void Position::undo_move(Move m) {
22042216
Piece gating_piece = make_piece(us, gating_type(m));
22052217
remove_piece(gating_square(m));
22062218
board[gating_square(m)] = NO_PIECE;
2207-
add_to_hand(gating_piece);
2219+
if (gating_from_hand())
2220+
add_to_hand(gating_piece);
22082221
st->gatesBB[us] |= gating_square(m);
22092222
}
22102223

@@ -2465,6 +2478,10 @@ Value Position::blast_see(Move m) const {
24652478
}
24662479
else
24672480
{
2481+
if ( extinction_first_capture()
2482+
&& piece_on(to)
2483+
&& (extinction_piece_types() & type_of(piece_on(to))))
2484+
return -extinction_value();
24682485
if (extinctsUs)
24692486
return extinction_value();
24702487
if (extinctsThem)
@@ -2498,6 +2515,10 @@ bool Position::see_ge(Move m, Value threshold) const {
24982515
return blast_see(m) >= threshold;
24992516

25002517
// Extinction
2518+
if ( extinction_first_capture()
2519+
&& piece_on(to)
2520+
&& (extinction_piece_types() & type_of(piece_on(to))))
2521+
return extinction_value() < VALUE_ZERO;
25012522
if ( extinction_value() != VALUE_NONE
25022523
&& piece_on(to)
25032524
&& ( ( (extinction_piece_types() & type_of(piece_on(to)))
@@ -2777,6 +2798,21 @@ bool Position::is_optional_game_end(Value& result, int ply, int countStarted) co
27772798

27782799
bool Position::is_immediate_game_end(Value& result, int ply) const {
27792800

2801+
if (extinction_first_capture() && captured_piece() != NO_PIECE)
2802+
{
2803+
Piece captured = captured_piece();
2804+
PieceType capturedType = type_of(captured);
2805+
Color capturedColor = color_of(captured);
2806+
if ( (extinction_piece_types() & piece_set(capturedType))
2807+
&& capturedColor == sideToMove
2808+
&& ( !(extinction_must_appear() & piece_set(capturedType))
2809+
|| (st->extinctionSeen[capturedColor] & piece_set(capturedType))))
2810+
{
2811+
result = extinction_value(ply);
2812+
return true;
2813+
}
2814+
}
2815+
27802816
// Extinction
27812817
// Extinction does not apply for pseudo-royal pieces, because they can not be captured
27822818
if (extinction_value() != VALUE_NONE && (!var->extinctionPseudoRoyal || blast_on_capture()))
@@ -2785,6 +2821,8 @@ bool Position::is_immediate_game_end(Value& result, int ply) const {
27852821
for (PieceSet ps = extinction_piece_types(); ps;)
27862822
{
27872823
PieceType pt = pop_lsb(ps);
2824+
if ((extinction_must_appear() & piece_set(pt)) && !(st->extinctionSeen[c] & piece_set(pt)))
2825+
continue;
27882826
if ( count_with_hand( c, pt) <= var->extinctionPieceCount
27892827
&& count_with_hand(~c, pt) >= var->extinctionOpponentPieceCount + (extinction_claim() && c == sideToMove))
27902828
{

src/position.h

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ struct StateInfo {
5656
Square castlingKingSquare[COLOR_NB];
5757
Bitboard wallSquares;
5858
Bitboard gatesBB[COLOR_NB];
59+
PieceSet extinctionSeen[COLOR_NB];
5960

6061
// Not copied when making a move (will be recomputed anyhow)
6162
Key key;
@@ -187,6 +188,9 @@ class Position {
187188
PieceSet en_passant_types(Color c) const;
188189
bool immobility_illegal() const;
189190
bool gating() const;
191+
bool gating_from_hand() const;
192+
PieceType gating_piece_after(Color c, PieceType pt) const;
193+
PieceType forced_gating_type(Color c, PieceType pt) const;
190194
bool walling() const;
191195
WallingRule walling_rule() const;
192196
bool wall_or_move() const;
@@ -207,6 +211,8 @@ class Position {
207211
Value extinction_value(int ply = 0) const;
208212
bool extinction_claim() const;
209213
PieceSet extinction_piece_types() const;
214+
PieceSet extinction_must_appear() const;
215+
bool extinction_first_capture() const;
210216
bool extinction_single_piece() const;
211217
int extinction_piece_count() const;
212218
int extinction_opponent_piece_count() const;
@@ -853,6 +859,25 @@ inline bool Position::gating() const {
853859
return var->gating;
854860
}
855861

862+
inline bool Position::gating_from_hand() const {
863+
assert(var != nullptr);
864+
return var->gatingFromHand;
865+
}
866+
867+
inline PieceType Position::gating_piece_after(Color c, PieceType pt) const {
868+
assert(var != nullptr);
869+
return var->gatingPieceAfter[c][pt];
870+
}
871+
872+
inline PieceType Position::forced_gating_type(Color c, PieceType pt) const {
873+
PieceType next = gating_piece_after(c, pt);
874+
if (next == NO_PIECE_TYPE)
875+
return NO_PIECE_TYPE;
876+
if (next == KING && count<KING>(c))
877+
return NO_PIECE_TYPE;
878+
return next;
879+
}
880+
856881
inline bool Position::walling() const {
857882
assert(var != nullptr);
858883
return var->wallingRule != NO_WALLING;
@@ -1027,6 +1052,16 @@ inline PieceSet Position::extinction_piece_types() const {
10271052
return var->extinctionPieceTypes;
10281053
}
10291054

1055+
inline PieceSet Position::extinction_must_appear() const {
1056+
assert(var != nullptr);
1057+
return var->extinctionMustAppear;
1058+
}
1059+
1060+
inline bool Position::extinction_first_capture() const {
1061+
assert(var != nullptr);
1062+
return var->extinctionFirstCaptureWins;
1063+
}
1064+
10301065
inline bool Position::extinction_single_piece() const {
10311066
assert(var != nullptr);
10321067
return var->extinctionValue == -VALUE_MATE
@@ -1528,6 +1563,8 @@ inline void Position::put_piece(Piece pc, Square s, bool isPromoted, Piece unpro
15281563
if (isPromoted)
15291564
promotedPieces |= s;
15301565
unpromotedBoard[s] = unpromotedPc;
1566+
if (extinction_must_appear() & piece_set(type_of(pc)))
1567+
st->extinctionSeen[color_of(pc)] |= piece_set(type_of(pc));
15311568
}
15321569

15331570
inline void Position::remove_piece(Square s) {

src/uci.cpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -546,8 +546,13 @@ string UCI::move(const Position& pos, Move m) {
546546
move += '-';
547547
else if (is_gating(m))
548548
{
549-
move += pos.piece_to_char()[make_piece(BLACK, gating_type(m))];
550-
if (gating_square(m) != from)
549+
if (pos.gating_from_hand())
550+
{
551+
move += pos.piece_to_char()[make_piece(BLACK, gating_type(m))];
552+
if (gating_square(m) != from)
553+
move += UCI::square(pos, gating_square(m));
554+
}
555+
else if (gating_square(m) != from)
551556
move += UCI::square(pos, gating_square(m));
552557
}
553558

src/variant.cpp

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -756,6 +756,35 @@ namespace {
756756
v->promotionPieceTypes[BLACK] = piece_set(ARCHBISHOP) | CHANCELLOR | QUEEN | ROOK | BISHOP | KNIGHT;
757757
return v;
758758
}
759+
// Battle of the Kings
760+
// https://www.chessvariants.com/rules/battle-of-kings-
761+
Variant* battle_kings_variant() {
762+
Variant* v = chess_variant_base()->init();
763+
v->startFen = "8/pppppppp/8/8/8/8/PPPPPPPP/8 w - - 0 1";
764+
v->remove_piece(KING);
765+
v->add_piece(COMMONER, 'k');
766+
v->castling = false;
767+
v->gating = true;
768+
v->gatingFromHand = false;
769+
for (Color c : {WHITE, BLACK})
770+
{
771+
v->gatingPieceAfter[c][PAWN] = KNIGHT;
772+
v->gatingPieceAfter[c][KNIGHT] = BISHOP;
773+
v->gatingPieceAfter[c][BISHOP] = ROOK;
774+
v->gatingPieceAfter[c][ROOK] = QUEEN;
775+
v->gatingPieceAfter[c][QUEEN] = COMMONER;
776+
}
777+
v->stalemateValue = -VALUE_MATE;
778+
v->nMoveRule = 0;
779+
v->nFoldRule = 2;
780+
v->nFoldValue = VALUE_MATE;
781+
v->extinctionValue = -VALUE_MATE;
782+
v->extinctionPieceTypes = piece_set(COMMONER);
783+
v->extinctionMustAppear = piece_set(COMMONER);
784+
v->extinctionPseudoRoyal = true;
785+
v->extinctionFirstCaptureWins = true;
786+
return v;
787+
}
759788
// S-House
760789
// A hybrid variant of S-Chess and Crazyhouse.
761790
// Pieces in the pocket can either be gated or dropped.
@@ -1908,6 +1937,7 @@ void VariantMap::init() {
19081937
add("placement", placement_variant());
19091938
add("sittuyin", sittuyin_variant());
19101939
add("seirawan", seirawan_variant());
1940+
add("battlekings", battle_kings_variant());
19111941
add("shouse", shouse_variant());
19121942
add("dragon", dragon_variant());
19131943
add("paradigm", paradigm_variant());

src/variant.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#include <functional>
2828
#include <sstream>
2929
#include <iostream>
30+
#include <algorithm>
3031

3132
#include "types.h"
3233
#include "bitboard.h"
@@ -109,6 +110,8 @@ struct Variant {
109110
int dropNoDoubledCount = 1;
110111
bool immobilityIllegal = false;
111112
bool gating = false;
113+
bool gatingFromHand = true;
114+
PieceType gatingPieceAfter[COLOR_NB][PIECE_TYPE_NB] = {};
112115
WallingRule wallingRule = NO_WALLING;
113116
Bitboard wallingRegion[COLOR_NB] = {AllSquares, AllSquares};
114117
bool wallOrMove = false;
@@ -141,8 +144,10 @@ struct Variant {
141144
Value extinctionValue = VALUE_NONE;
142145
bool extinctionClaim = false;
143146
bool extinctionPseudoRoyal = false;
147+
bool extinctionFirstCaptureWins = false;
144148
bool dupleCheck = false;
145149
PieceSet extinctionPieceTypes = NO_PIECE_SET;
150+
PieceSet extinctionMustAppear = NO_PIECE_SET;
146151
int extinctionPieceCount = 0;
147152
int extinctionOpponentPieceCount = 0;
148153
PieceType flagPiece[COLOR_NB] = {ALL_PIECES, ALL_PIECES};
@@ -226,6 +231,10 @@ struct Variant {
226231
Variant* init() {
227232
nnueAlias = "";
228233
endgameEval = EG_EVAL_CHESS;
234+
gatingFromHand = true;
235+
extinctionMustAppear = NO_PIECE_SET;
236+
for (Color c : {WHITE, BLACK})
237+
std::fill(std::begin(gatingPieceAfter[c]), std::end(gatingPieceAfter[c]), NO_PIECE_TYPE);
229238
return this;
230239
}
231240

test.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
XIANGQI = "rnbakabnr/9/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/9/RNBAKABNR w - - 0 1"
2323
SHOGUN = "rnb+fkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNB+FKBNR[] w KQkq - 0 1"
2424
JANGGI = "rnba1abnr/4k4/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/4K4/RNBA1ABNR w - - 0 1"
25+
BATTLEKINGS = "8/pppppppp/8/8/8/8/PPPPPPPP/8 w - - 0 1"
2526

2627

2728
ini_text = """
@@ -315,6 +316,7 @@ def test_info(self):
315316
def test_variants_loaded(self):
316317
variants = sf.variants()
317318
self.assertTrue("shogun" in variants)
319+
self.assertIn("battlekings", variants)
318320

319321
def test_set_option(self):
320322
result = sf.set_option("UCI_Variant", "capablanca")
@@ -344,6 +346,58 @@ def test_start_fen(self):
344346
result = sf.start_fen("shogun")
345347
self.assertEqual(result, SHOGUN)
346348

349+
result = sf.start_fen("battlekings")
350+
self.assertEqual(result, BATTLEKINGS)
351+
352+
def test_battlekings_gating_sequence(self):
353+
start = sf.start_fen("battlekings")
354+
355+
initial_moves = sf.legal_moves("battlekings", start, [])
356+
self.assertIn("e2e4", initial_moves)
357+
self.assertNotIn("e2e4n", initial_moves)
358+
359+
knight_sequence = ["e2e4", "h7h6"]
360+
knight_moves = sf.legal_moves("battlekings", start, knight_sequence)
361+
self.assertIn("e2c3", knight_moves)
362+
self.assertNotIn("e2c3b", knight_moves)
363+
364+
bishop_sequence = knight_sequence + ["e2c3", "a7a5"]
365+
bishop_moves = sf.legal_moves("battlekings", start, bishop_sequence)
366+
self.assertIn("e2g4", bishop_moves)
367+
self.assertNotIn("e2g4r", bishop_moves)
368+
369+
rook_sequence = bishop_sequence + ["e2g4", "e7e5"]
370+
rook_moves = sf.legal_moves("battlekings", start, rook_sequence)
371+
self.assertIn("e2e3", rook_moves)
372+
self.assertNotIn("e2e3q", rook_moves)
373+
374+
queen_sequence = rook_sequence + ["e2e3", "d7d5"]
375+
queen_moves = sf.legal_moves("battlekings", start, queen_sequence)
376+
self.assertIn("e2e1", queen_moves)
377+
self.assertNotIn("e2e1k", queen_moves)
378+
379+
fen_after_queen = sf.get_fen("battlekings", start, queen_sequence)
380+
board_after_queen = fen_after_queen.split()[0]
381+
self.assertIn("Q", board_after_queen)
382+
383+
king_sequence = queen_sequence + ["e2e1"]
384+
fen_after_king = sf.get_fen("battlekings", start, king_sequence)
385+
board_after_king = fen_after_king.split()[0]
386+
self.assertIn("K", board_after_king)
387+
388+
post_king_moves = sf.legal_moves("battlekings", start, king_sequence + ["a5a4"])
389+
self.assertIn("e1d1", post_king_moves)
390+
self.assertNotIn("e1d1k", post_king_moves)
391+
392+
def test_battlekings_king_spawn_blocked(self):
393+
fen = "8/8/8/8/8/3p4/4Q3/8 w - - 0 1"
394+
moves = sf.legal_moves("battlekings", fen, [])
395+
self.assertFalse(moves)
396+
397+
def test_battlekings_capture_first_commoner_wins(self):
398+
fen = "8/4k1k1/8/8/8/8/4Q3/8 w - - 0 1"
399+
self._check_immediate_game_end("battlekings", fen, ["e2e7"], True, -sf.VALUE_MATE)
400+
347401
def test_legal_moves(self):
348402
fen = "10/10/10/10/10/k9/10/K9 w - - 0 1"
349403
result = sf.legal_moves("capablanca", fen, [])

0 commit comments

Comments
 (0)