Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,7 @@ Variant* VariantParser<DoCheck>::parse(Variant* v) {
parse_attribute("passOnStalemateBlack", v->passOnStalemate[BLACK]);
parse_attribute("makpongRule", v->makpongRule);
parse_attribute("flyingGeneral", v->flyingGeneral);
parse_attribute("diagonalGeneral", v->diagonalGeneral);
parse_attribute("soldierPromotionRank", v->soldierPromotionRank);
parse_attribute("flipEnclosedPieces", v->flipEnclosedPieces);
// game end
Expand Down
48 changes: 45 additions & 3 deletions src/position.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1252,9 +1252,18 @@ bool Position::legal(Move m) const {
return true;

for (Square s = to; s != from; s += step)
if (attackers_to(s, ~us)
|| (var->flyingGeneral && (attacks_bb(~us, ROOK, s, pieces() ^ from) & pieces(~us, KING))))
{
if (attackers_to(s, ~us))
return false;

if (var->flyingGeneral
&& (attacks_bb(~us, ROOK, s, pieces() ^ from) & pieces(~us, KING)))
return false;

if (var->diagonalGeneral
&& (attacks_bb(~us, BISHOP, s, pieces() ^ from) & pieces(~us, KING)))
return false;
}

// In case of Chess960, verify if the Rook blocks some checks
// For instance an enemy queen in SQ_A1 when castling rook is in SQ_B1.
Expand All @@ -1273,6 +1282,12 @@ bool Position::legal(Move m) const {
if (attacks_bb(~us, ROOK, s, occupied) & pieces(~us, KING) & ~square_bb(to))
return false;
}
if (var->diagonalGeneral && count<KING>(us))
{
Square s = type_of(moved_piece(m)) == KING ? to : square<KING>(us);
if (attacks_bb(~us, BISHOP, s, occupied) & pieces(~us, KING) & ~square_bb(to))
return false;
}

// Makpong rule
if (var->makpongRule && checkers() && type_of(moved_piece(m)) == KING && (checkers() ^ to))
Expand Down Expand Up @@ -2540,6 +2555,13 @@ bool Position::see_ge(Move m, Value threshold) const {
if (attackers & pieces(~stm, KING))
attackers |= attacks_bb(~stm, ROOK, to, occupied & ~pieces(ROOK)) & pieces(stm, KING);
}
if (var->diagonalGeneral)
{
if (attackers & pieces(stm, KING))
attackers |= attacks_bb(stm, BISHOP, to, occupied & ~pieces(BISHOP)) & pieces(~stm, KING);
if (attackers & pieces(~stm, KING))
attackers |= attacks_bb(~stm, BISHOP, to, occupied & ~pieces(BISHOP)) & pieces(stm, KING);
}

// Janggi cannons can not capture each other
if (type_of(moved_piece(m)) == JANGGI_CANNON && !(attackers & pieces(~stm) & ~pieces(JANGGI_CANNON)))
Expand Down Expand Up @@ -3000,6 +3022,20 @@ Bitboard Position::chased() const {
if ((kingFilePieces & pieces(sideToMove, KING)) && !more_than_one(kingFilePieces & ~pieces(KING)))
pins |= kingFilePieces & ~pieces(KING);
}
if (var->diagonalGeneral)
{
Square enemyKing = square<KING>(~sideToMove);
Square ourKing = square<KING>(sideToMove);
int df = int(file_of(enemyKing)) - int(file_of(ourKing));
int dr = int(rank_of(enemyKing)) - int(rank_of(ourKing));
if (df == dr || df == -dr)
{
Bitboard kingDiagonalPieces = line_bb(enemyKing, ourKing) & pieces(sideToMove);
if ((kingDiagonalPieces & pieces(sideToMove, KING))
&& !more_than_one(kingDiagonalPieces & ~pieces(KING)))
pins |= kingDiagonalPieces & ~pieces(KING);
}
}
auto addChased = [&](Square attackerSq, PieceType attackerType, Bitboard attacks) {
if (attacks & ~b)
{
Expand Down Expand Up @@ -3031,8 +3067,14 @@ Bitboard Position::chased() const {
{
Square s = pop_lsb(attacks);
Bitboard roots = attackers_to(s, pieces() ^ attackerSq, sideToMove) & ~pins;
if (!roots || (var->flyingGeneral && roots == pieces(sideToMove, KING) && (attacks_bb(sideToMove, ROOK, square<KING>(~sideToMove), pieces() ^ attackerSq) & s)))
if (!roots
|| (var->flyingGeneral && roots == pieces(sideToMove, KING)
&& (attacks_bb(sideToMove, ROOK, square<KING>(~sideToMove), pieces() ^ attackerSq) & s))
|| (var->diagonalGeneral && roots == pieces(sideToMove, KING)
&& (attacks_bb(sideToMove, BISHOP, square<KING>(~sideToMove), pieces() ^ attackerSq) & s)))
{
b |= s;
}
}
}
};
Expand Down
31 changes: 31 additions & 0 deletions src/variant.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1559,6 +1559,36 @@ namespace {
v->castling = false;
return v;
}
// Eurasian chess
// https://www.chessvariants.com/large.dir/eurasian.html
Variant* eurasian_variant() {
Variant* v = chess_variant_base()->init();
v->pieceToCharTable = "PNBRQ.CV............Kpnbrq.cv............k";
v->maxRank = RANK_10;
v->maxFile = FILE_J;
v->add_piece(CANNON, 'c');
v->add_piece(CUSTOM_PIECE_1, 'v', "mBcpB");
v->startFen = "r1c4c1r/1nbvqkvbn1/pppppppppp/10/10/10/10/PPPPPPPPPP/1NBVQKVBN1/R1C4C1R w - - 0 1";
v->promotionPieceTypes[WHITE] = piece_set(QUEEN) | ROOK | BISHOP | KNIGHT | CANNON | CUSTOM_PIECE_1;
v->promotionPieceTypes[BLACK] = v->promotionPieceTypes[WHITE];
v->promotionRegion[WHITE] = Rank8BB | Rank9BB | Rank10BB;
v->promotionRegion[BLACK] = Rank3BB | Rank2BB | Rank1BB;
v->promotionLimit[QUEEN] = 1;
v->promotionLimit[ROOK] = 2;
v->promotionLimit[BISHOP] = 2;
v->promotionLimit[KNIGHT] = 2;
v->promotionLimit[CANNON] = 2;
v->promotionLimit[CUSTOM_PIECE_1] = 2;
v->mandatoryPawnPromotion = false;
v->doubleStepRegion[WHITE] = Rank3BB;
v->doubleStepRegion[BLACK] = Rank8BB;
v->castling = false;
v->flyingGeneral = true;
v->diagonalGeneral = true;
v->mobilityRegion[WHITE][KING] = Rank1BB | Rank2BB | Rank3BB | Rank4BB | Rank5BB;
v->mobilityRegion[BLACK][KING] = Rank6BB | Rank7BB | Rank8BB | Rank9BB | Rank10BB;
return v;
}
// Opulent chess
// Variant of Grand chess with two extra pieces
// https://www.chessvariants.com/rules/opulent-chess
Expand Down Expand Up @@ -1955,6 +1985,7 @@ void VariantMap::init() {
add("jesonmor", jesonmor_variant());
add("courier", courier_variant());
add("grand", grand_variant());
add("eurasian", eurasian_variant());
add("opulent", opulent_variant());
add("tencubed", tencubed_variant());
add("omicron", omicron_variant());
Expand Down
1 change: 1 addition & 0 deletions src/variant.h
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ struct Variant {
bool passOnStalemate[COLOR_NB] = {false, false};
bool makpongRule = false;
bool flyingGeneral = false;
bool diagonalGeneral = false;
Rank soldierPromotionRank = RANK_1;
EnclosingRule flipEnclosedPieces = NO_ENCLOSING;
bool freeDrops = false;
Expand Down
1 change: 1 addition & 0 deletions src/variants.ini
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@
# passOnStalemateBlack: allow passing in case of stalemate for black [bool] (default: false)
# makpongRule: the king may not move away from check [bool] (default: false)
# flyingGeneral: disallow general face-off like in xiangqi [bool] (default: false)
# diagonalGeneral: disallow king face-off along diagonals [bool] (default: false)
# soldierPromotionRank: restrict soldier to shogi pawn movements until reaching n-th rank [Rank] (default: 1)
# flipEnclosedPieces: change color of pieces that are enclosed by a drop [EnclosingRule] (default: none)
# nMoveRuleTypes: define pieces resetting n move rule on irreversible moves [PieceSet] (default: p)
Expand Down
15 changes: 15 additions & 0 deletions test.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
SEIRAWAN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[EHeh] w KQBCDFGkqbcdfg - 0 1"
GRAND = "r8r/1nbqkcabn1/pppppppppp/10/10/10/10/PPPPPPPPPP/1NBQKCABN1/R8R w - - 0 1"
GRANDHOUSE = "r8r/1nbqkcabn1/pppppppppp/10/10/10/10/PPPPPPPPPP/1NBQKCABN1/R8R[] w - - 0 1"
EURASIAN = "r1c4c1r/1nbvqkvbn1/pppppppppp/10/10/10/10/PPPPPPPPPP/1NBVQKVBN1/R1C4C1R w - - 0 1"
XIANGQI = "rnbakabnr/9/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/9/RNBAKABNR w - - 0 1"
SHOGUN = "rnb+fkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNB+FKBNR[] w KQkq - 0 1"
JANGGI = "rnba1abnr/4k4/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/4K4/RNBA1ABNR w - - 0 1"
Expand Down Expand Up @@ -315,6 +316,7 @@ def test_info(self):
def test_variants_loaded(self):
variants = sf.variants()
self.assertTrue("shogun" in variants)
self.assertIn("eurasian", variants)

def test_set_option(self):
result = sf.set_option("UCI_Variant", "capablanca")
Expand Down Expand Up @@ -344,6 +346,9 @@ def test_start_fen(self):
result = sf.start_fen("shogun")
self.assertEqual(result, SHOGUN)

result = sf.start_fen("eurasian")
self.assertEqual(result, EURASIAN)

def test_legal_moves(self):
fen = "10/10/10/10/10/k9/10/K9 w - - 0 1"
result = sf.legal_moves("capablanca", fen, [])
Expand Down Expand Up @@ -422,6 +427,16 @@ def test_legal_moves(self):
self.assertEqual(['d4c2', 'd4f3', 'd4b5', 'd4e6'], result)


def test_diagonal_faceoff_unblock_is_illegal(self):
# Eurasian board: white King c5, white Pawn d6 (blocking), black King e7
# Moving the pawn off d6 (e.g. d6d7) would expose the kings to a diagonal face-off.
fen = "10/10/10/4k5/3P6/2K7/10/10/10/10 w - - 0 1"
moves = sf.legal_moves("eurasian", fen, [])
self.assertNotIn("d6d7", moves)
# A neutral king move that keeps the block is still fine.
self.assertIn("c5c4", moves)


def test_castling(self):
legals = ['f5f4', 'a7a6', 'b7b6', 'c7c6', 'd7d6', 'e7e6', 'i7i6', 'j7j6', 'a7a5', 'b7b5', 'c7c5', 'e7e5', 'i7i5', 'j7j5', 'b8a6', 'b8c6', 'h6g4', 'h6i4', 'h6j5', 'h6f7', 'h6g8', 'h6i8', 'd5a2', 'd5b3', 'd5f3', 'd5c4', 'd5e4', 'd5c6', 'd5e6', 'd5f7', 'd5g8', 'j8g8', 'j8h8', 'j8i8', 'e8f7', 'c8b6', 'c8d6', 'g6g2', 'g6g3', 'g6f4', 'g6g4', 'g6h4', 'g6e5', 'g6g5', 'g6i5', 'g6a6', 'g6b6', 'g6c6', 'g6d6', 'g6e6', 'g6f6', 'g6h8', 'f8f7', 'f8g8', 'f8i8']
moves = ['b2b4', 'f7f5', 'c2c3', 'g8d5', 'a2a4', 'h8g6', 'f2f3', 'i8h6', 'h2h3']
Expand Down
Loading