From: Fabian Fichter Date: Sun, 13 Oct 2019 21:49:14 +0000 (+0200) Subject: Support Cambodian chess (Ouk Chatrang, Kar Ouk) (close #39) X-Git-Url: http://winboard.nl/cgi-bin?a=commitdiff_plain;h=8fa7223d3d092c383ec69c8ea8ad5b07e20fa929;p=fairystockfish.git Support Cambodian chess (Ouk Chatrang, Kar Ouk) (close #39) https://en.wikipedia.org/wiki/Makruk#Cambodian_chess - Captures by special move of queen/neang are allowed. - King may only make its leap if the side to move is not in check, and if the move does not capture a piece. No functional change for existing variants. --- diff --git a/Readme.md b/Readme.md index 915cd97..56e435f 100644 --- a/Readme.md +++ b/Readme.md @@ -15,7 +15,7 @@ The games currently supported besides chess are listed below. Fairy-Stockfish ca ### Regional and historical games - [Shatranj](https://en.wikipedia.org/wiki/Shatranj), [Courier](https://en.wikipedia.org/wiki/Courier_chess) -- [Makruk](https://en.wikipedia.org/wiki/Makruk), [ASEAN](http://hgm.nubati.net/rules/ASEAN.html), Ai-Wok +- [Makruk](https://en.wikipedia.org/wiki/Makruk), [Ouk Chatrang](https://en.wikipedia.org/wiki/Makruk#Cambodian_chess), [Kar Ouk](https://en.wikipedia.org/wiki/Makruk#Cambodian_chess), [ASEAN](http://hgm.nubati.net/rules/ASEAN.html), Ai-Wok - [Sittuyin](https://en.wikipedia.org/wiki/Sittuyin) - [Shatar](https://en.wikipedia.org/wiki/Shatar), [Jeson Mor](https://en.wikipedia.org/wiki/Jeson_Mor) - [Shogi](https://en.wikipedia.org/wiki/Shogi) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index bf4ac36..15eb155 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -1068,7 +1068,7 @@ namespace { score += pieces(pt) - pieces(pt); // Evaluate pieces in hand once attack tables are complete - if (pos.piece_drops() || pos.gating()) + if (pos.piece_drops() || pos.seirawan_gating()) for (PieceType pt = PAWN; pt < KING; ++pt) score += hand(pt) - hand(pt); diff --git a/src/movegen.cpp b/src/movegen.cpp index 55c4d5a..92d92dc 100644 --- a/src/movegen.cpp +++ b/src/movegen.cpp @@ -31,11 +31,11 @@ namespace { *moveList++ = make(from, to); // Gating moves - if (pos.gating() && (pos.gates(us) & from)) + if (pos.seirawan_gating() && (pos.gates(us) & from)) for (PieceType pt_gating = PAWN; pt_gating <= KING; ++pt_gating) if (pos.count_in_hand(us, pt_gating) && (pos.drop_region(us, pt_gating) & from)) *moveList++ = make_gating(from, to, pt_gating, from); - if (pos.gating() && T == CASTLING && (pos.gates(us) & to)) + if (pos.seirawan_gating() && T == CASTLING && (pos.gates(us) & to)) for (PieceType pt_gating = PAWN; pt_gating <= KING; ++pt_gating) if (pos.count_in_hand(us, pt_gating) && (pos.drop_region(us, pt_gating) & to)) *moveList++ = make_gating(from, to, pt_gating, to); @@ -351,6 +351,28 @@ namespace { moveList = make_move_and_gating(pos, moveList, Us, from, pos.castling_rook_square(OOO)); } + // Special moves + if (pos.cambodian_moves() && pos.gates(Us)) + { + if (Type != CAPTURES && Type != EVASIONS && (pos.pieces(Us, KING) & pos.gates(Us))) + { + Square from = pos.square(Us); + Bitboard b = PseudoAttacks[WHITE][KNIGHT][from] & rank_bb(rank_of(from + (Us == WHITE ? NORTH : SOUTH))) + & target & ~pos.pieces(); + while (b) + moveList = make_move_and_gating(pos, moveList, Us, from, pop_lsb(&b)); + } + + Bitboard b = pos.pieces(Us, FERS) & pos.gates(Us); + while (b) + { + Square from = pop_lsb(&b); + Square to = from + 2 * (Us == WHITE ? NORTH : SOUTH); + if (is_ok(to) && (target & to)) + moveList = make_move_and_gating(pos, moveList, Us, from, to); + } + } + return moveList; } diff --git a/src/parser.cpp b/src/parser.cpp index 13be2ee..ee7dd26 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -172,6 +172,8 @@ Variant* VariantParser::parse(Variant* v) { parse_attribute("shogiDoubledPawn", v->shogiDoubledPawn); parse_attribute("immobilityIllegal", v->immobilityIllegal); parse_attribute("gating", v->gating); + parse_attribute("seirawanGating", v->seirawanGating); + parse_attribute("cambodianMoves", v->cambodianMoves); // game end parse_attribute("nMoveRule", v->nMoveRule); parse_attribute("nFoldRule", v->nFoldRule); diff --git a/src/position.cpp b/src/position.cpp index 0462ff3..88c71ea 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -380,7 +380,7 @@ Position& Position::set(const Variant* v, const string& fenStr, bool isChess960, st->gatesBB[c] |= count(c) ? square(c) : make_square(FILE_E, relative_rank(c, castling_rank(), max_rank())); // Do not set castling rights for gates unless there are no pieces in hand, // which means that the file is referring to a chess960 castling right. - else if (count_in_hand(c, ALL_PIECES) || captures_to_hand()) + else if (!seirawan_gating() || count_in_hand(c, ALL_PIECES) || captures_to_hand()) continue; } @@ -391,7 +391,7 @@ Position& Position::set(const Variant* v, const string& fenStr, bool isChess960, // Set castling rights for 960 gating variants if (gating() && castling_enabled()) for (Color c : {WHITE, BLACK}) - if ((gates(c) & pieces(KING)) && !castling_rights(c) && (count_in_hand(c, ALL_PIECES) || captures_to_hand())) + if ((gates(c) & pieces(KING)) && !castling_rights(c) && (!seirawan_gating() || count_in_hand(c, ALL_PIECES) || captures_to_hand())) { Bitboard castling_rooks = gates(c) & pieces(ROOK); while (castling_rooks) @@ -417,11 +417,21 @@ Position& Position::set(const Variant* v, const string& fenStr, bool isChess960, // Check counter for nCheck ss >> std::skipws >> token >> std::noskipws; - if (check_counting() && ss.peek() == '+') + if (check_counting()) { - st->checksRemaining[WHITE] = CheckCount(std::max(token - '0', 0)); - ss >> token >> token; - st->checksRemaining[BLACK] = CheckCount(std::max(token - '0', 0)); + if (ss.peek() == '+') + { + st->checksRemaining[WHITE] = CheckCount(std::max(token - '0', 0)); + ss >> token >> token; + st->checksRemaining[BLACK] = CheckCount(std::max(token - '0', 0)); + } + else + { + // If check count is not provided, assume that the next check wins + st->checksRemaining[WHITE] = CheckCount(1); + st->checksRemaining[BLACK] = CheckCount(1); + ss.putback(token); + } } else ss.putback(token); @@ -549,7 +559,7 @@ void Position::set_state(StateInfo* si) const { for (int cnt = 0; cnt < pieceCount[pc]; ++cnt) si->materialKey ^= Zobrist::psq[pc][cnt]; - if (piece_drops() || gating()) + if (piece_drops() || seirawan_gating()) si->key ^= Zobrist::inHand[pc][pieceCountInHand[c][pt]]; } @@ -633,7 +643,7 @@ const string Position::fen(bool sfen) const { } // pieces in hand - if (piece_drops() || gating()) + if (piece_drops() || seirawan_gating()) { ss << '['; for (Color c : {WHITE, BLACK}) @@ -650,7 +660,7 @@ const string Position::fen(bool sfen) const { if (can_castle(WHITE_OOO)) ss << (chess960 ? char('A' + file_of(castling_rook_square(WHITE_OOO))) : 'Q'); - if (gating() && gates(WHITE) && (count_in_hand(WHITE, ALL_PIECES) || captures_to_hand())) + if (gating() && gates(WHITE) && (!seirawan_gating() || count_in_hand(WHITE, ALL_PIECES) || captures_to_hand())) for (File f = FILE_A; f <= max_file(); ++f) if (gates(WHITE) & file_bb(f)) ss << char('A' + f); @@ -661,7 +671,7 @@ const string Position::fen(bool sfen) const { if (can_castle(BLACK_OOO)) ss << (chess960 ? char('a' + file_of(castling_rook_square(BLACK_OOO))) : 'q'); - if (gating() && gates(BLACK) && (count_in_hand(BLACK, ALL_PIECES) || captures_to_hand())) + if (gating() && gates(BLACK) && (!seirawan_gating() || count_in_hand(BLACK, ALL_PIECES) || captures_to_hand())) for (File f = FILE_A; f <= max_file(); ++f) if (gates(BLACK) & file_bb(f)) ss << char('a' + f); @@ -741,6 +751,15 @@ Bitboard Position::attackers_to(Square s, Bitboard occupied, Color c) const { Bitboard b = 0; for (PieceType pt : piece_types()) b |= attacks_bb(~c, pt, s, occupied) & pieces(c, pt); + + // Consider special move of neang in cambodian chess + if (cambodian_moves()) + { + Square fers_sq = s + 2 * (c == WHITE ? SOUTH : NORTH); + if (is_ok(fers_sq)) + b |= pieces(c, FERS) & gates(c) & fers_sq; + } + return b; } @@ -1003,6 +1022,7 @@ bool Position::gives_check(Move m) const { { case NORMAL: case DROP: + case SPECIAL: return false; case PROMOTION: @@ -1313,7 +1333,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { st->gatesBB[us] ^= to_sq(m); if (gates(them) & to) st->gatesBB[them] ^= to; - if (!count_in_hand(us, ALL_PIECES) && !captures_to_hand()) + if (seirawan_gating() && !count_in_hand(us, ALL_PIECES) && !captures_to_hand()) st->gatesBB[us] = 0; } diff --git a/src/position.h b/src/position.h index 9852061..5e962ba 100644 --- a/src/position.h +++ b/src/position.h @@ -132,6 +132,8 @@ public: bool shogi_doubled_pawn() const; bool immobility_illegal() const; bool gating() const; + bool seirawan_gating() const; + bool cambodian_moves() const; // winning conditions int n_move_rule() const; int n_fold_rule() const; @@ -515,6 +517,16 @@ inline bool Position::gating() const { return var->gating; } +inline bool Position::seirawan_gating() const { + assert(var != nullptr); + return var->seirawanGating; +} + +inline bool Position::cambodian_moves() const { + assert(var != nullptr); + return var->cambodianMoves; +} + inline int Position::n_move_rule() const { assert(var != nullptr); return var->nMoveRule; diff --git a/src/types.h b/src/types.h index e553616..87d10f7 100644 --- a/src/types.h +++ b/src/types.h @@ -244,6 +244,7 @@ enum MoveType : int { DROP = 4 << (2 * SQUARE_BITS), PIECE_PROMOTION = 5 << (2 * SQUARE_BITS), PIECE_DEMOTION = 6 << (2 * SQUARE_BITS), + SPECIAL = 7 << (2 * SQUARE_BITS), }; constexpr int MOVE_TYPE_BITS = 4; diff --git a/src/variant.cpp b/src/variant.cpp index 47f0937..273a2c1 100644 --- a/src/variant.cpp +++ b/src/variant.cpp @@ -62,6 +62,18 @@ namespace { v->countingRule = MAKRUK_COUNTING; return v; } + Variant* cambodian_variant() { + Variant* v = makruk_variant(); + v->startFen = "rnsmksnr/8/pppppppp/8/8/PPPPPPPP/8/RNSKMSNR w DEde - 0 1"; + v->gating = true; + v->cambodianMoves = true; + return v; + } + Variant* karouk_variant() { + Variant* v = cambodian_variant(); + v->checkCounting = true; + return v; + } Variant* asean_variant() { Variant* v = chess_variant(); v->remove_piece(BISHOP); @@ -260,6 +272,7 @@ namespace { v->add_piece(CHANCELLOR, 'e'); v->startFen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[HEhe] w KQBCDFGkqbcdfg - 0 1"; v->gating = true; + v->seirawanGating = true; v->promotionPieceTypes = {ARCHBISHOP, CHANCELLOR, QUEEN, ROOK, BISHOP, KNIGHT}; return v; } @@ -648,6 +661,8 @@ void VariantMap::init() { add("standard", chess_variant()); add("fairy", fairy_variant()); // fairy variant used for endgame code initialization add("makruk", makruk_variant()); + add("cambodian", cambodian_variant()); + add("karouk", karouk_variant()); add("asean", asean_variant()); add("ai-wok", aiwok_variant()); add("shatranj", shatranj_variant()); diff --git a/src/variant.h b/src/variant.h index 340f3c7..a574df0 100644 --- a/src/variant.h +++ b/src/variant.h @@ -75,6 +75,8 @@ struct Variant { bool shogiDoubledPawn = true; bool immobilityIllegal = false; bool gating = false; + bool seirawanGating = false; + bool cambodianMoves = false; // game end int nMoveRule = 50; int nFoldRule = 3; diff --git a/src/variants.ini b/src/variants.ini index c57abc8..680c84e 100644 --- a/src/variants.ini +++ b/src/variants.ini @@ -115,7 +115,9 @@ # dropPromoted: [bool] (default: false) # shogiDoubledPawn: allow shogi pawns to be doubled [bool] (default: true) # immobilityIllegal: [bool] (default: false) -# gating: [bool] (default: false) +# gating: maintain squares on backrank with extra rights in castling field of FEN [bool] (default: false) +# seirawanGating: allow gating of pieces in hand like in S-Chess, requires "gating = true" [bool] (default: false) +# cambodianMoves: enable special moves of cambodian chess, requires "gating = true" [bool] (default: false) # nMoveRule: [int] (default: 50) # nFoldRule: [int] (default: 3) # nFoldValue: [Value] (default: draw)