From d2ab62125e9032b97b0af7d4b95e7922f09bfb3a Mon Sep 17 00:00:00 2001 From: Fabian Fichter Date: Sun, 5 Jan 2020 22:02:38 +0100 Subject: [PATCH] Support Knightmate chess https://www.chessvariants.com/diffobjective.dir/knightmate.html No functional change for other variants. Closes #50. --- Readme.md | 3 ++- src/evaluate.cpp | 4 ++-- src/movegen.cpp | 21 +++++++-------------- src/parser.cpp | 36 +++++++++++++++--------------------- src/parser.h | 1 + src/position.cpp | 16 +++++----------- src/position.h | 16 ++++++++-------- src/variant.cpp | 12 +++++++++++- src/variant.h | 2 +- src/variants.ini | 2 +- tests/perft.sh | 1 + 11 files changed, 54 insertions(+), 60 deletions(-) diff --git a/Readme.md b/Readme.md index 1a233bd..a16d9af 100644 --- a/Readme.md +++ b/Readme.md @@ -25,7 +25,7 @@ The games currently supported besides chess are listed below. Fairy-Stockfish ca - [Capablanca](https://en.wikipedia.org/wiki/Capablanca_Chess), [Janus](https://en.wikipedia.org/wiki/Janus_Chess), [Modern](https://en.wikipedia.org/wiki/Modern_Chess_(chess_variant)), [Chancellor](https://en.wikipedia.org/wiki/Chancellor_Chess), [Embassy](https://en.wikipedia.org/wiki/Embassy_Chess), [Gothic](https://www.chessvariants.com/large.dir/gothicchess.html), [Capablanca random chess](https://en.wikipedia.org/wiki/Capablanca_Random_Chess) - [Grand](https://en.wikipedia.org/wiki/Grand_Chess), [Shako](https://www.chessvariants.com/large.dir/shako.html), [Centaur](https://www.chessvariants.com/large.dir/contest/royalcourt.html) - [Chess960](https://en.wikipedia.org/wiki/Chess960), [Placement/Pre-Chess](https://www.chessvariants.com/link/placement-chess) -- [Crazyhouse](https://en.wikipedia.org/wiki/Crazyhouse), [Loop](https://en.wikipedia.org/wiki/Crazyhouse#Variations), [Chessgi](https://en.wikipedia.org/wiki/Crazyhouse#Variations), [Pocket Knight](http://www.chessvariants.com/other.dir/pocket.html), Capablanca-Crazyhouse +- [Crazyhouse](https://en.wikipedia.org/wiki/Crazyhouse), [Bughouse](https://en.wikipedia.org/wiki/Bughouse_chess), [Loop](https://en.wikipedia.org/wiki/Crazyhouse#Variations), [Chessgi](https://en.wikipedia.org/wiki/Crazyhouse#Variations), [Pocket Knight](http://www.chessvariants.com/other.dir/pocket.html), Capablanca-Crazyhouse - [Seirawan](https://en.wikipedia.org/wiki/Seirawan_chess), Seirawan-Crazyhouse - [Amazon](https://en.wikipedia.org/wiki/Amazon_(chess)), [Chigorin](https://en.wikipedia.org/wiki/Chigorin_Chess), [Almost chess](https://en.wikipedia.org/wiki/Almost_Chess) - [Hoppel-Poppel](http://www.chessvariants.com/diffmove.dir/hoppel-poppel.html), New Zealand @@ -35,6 +35,7 @@ The games currently supported besides chess are listed below. Fairy-Stockfish ca - [Three-check](https://en.wikipedia.org/wiki/Three-check_chess), Five-check - [Los Alamos](https://en.wikipedia.org/wiki/Los_Alamos_chess) - [Horde](https://en.wikipedia.org/wiki/Dunsany%27s_Chess#Horde_Chess) +- [Knightmate](https://www.chessvariants.com/diffobjective.dir/knightmate.html) ### Shogi variants - [Minishogi](https://en.wikipedia.org/wiki/Minishogi), [EuroShogi](https://en.wikipedia.org/wiki/EuroShogi), [Judkins shogi](https://en.wikipedia.org/wiki/Judkins_shogi) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 3cf43af..ef375a9 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -564,7 +564,7 @@ namespace { + 4 * (kingFlankAttack - kingFlankDefense) + 3 * kingFlankAttack * kingFlankAttack / 8 + mg_value(mobility[Them] - mobility[Us]) - - 873 * !(pos.major_pieces(Them) || pos.captures_to_hand() || pos.xiangqi_general()) / (1 + pos.check_counting()) + - 873 * !(pos.major_pieces(Them) || pos.captures_to_hand() || pos.king_type() == WAZIR) / (1 + pos.check_counting()) - 100 * bool(attackedBy[Us][KNIGHT] & attackedBy[Us][KING]) - 35 * bool(attackedBy[Us][BISHOP] & attackedBy[Us][KING]) - 6 * mg_value(score) / 8 @@ -581,7 +581,7 @@ namespace { // Penalty if king flank is under attack, potentially moving toward the king score -= FlankAttacks * kingFlankAttack * (1 + 5 * pos.captures_to_hand() + pos.check_counting()); - if (pos.check_counting() || pos.xiangqi_general()) + if (pos.check_counting() || pos.king_type() == WAZIR) score += make_score(0, mg_value(score) / 2); // For drop games, king danger is independent of game phase diff --git a/src/movegen.cpp b/src/movegen.cpp index 119c205..422a91c 100644 --- a/src/movegen.cpp +++ b/src/movegen.cpp @@ -329,9 +329,8 @@ namespace { if (Type != QUIET_CHECKS && Type != EVASIONS && pos.count(Us)) { Square ksq = pos.square(Us); - Bitboard b = pos.attacks_from(ksq, Us) & target; - if (pos.xiangqi_general()) - b &= PseudoAttacks[Us][WAZIR][ksq]; + Bitboard b = ( (pos.attacks_from(Us, KING, ksq) & pos.pieces()) + | (pos.moves_from(Us, KING, ksq) & ~pos.pieces())) & target; while (b) moveList = make_move_and_gating(pos, moveList, Us, ksq, pop_lsb(&b)); @@ -433,12 +432,8 @@ ExtMove* generate(const Position& pos, ExtMove* moveList) { Bitboard b = pos.moves_from(us, pt, from) & ~pos.pieces(); - if (pt == KING) - { + if (pt == KING && pos.king_type() == KING) b &= ~PseudoAttacks[~us][QUEEN][pos.square(~us)]; - if (pos.xiangqi_general()) - b &= PseudoAttacks[us][WAZIR][from]; - } while (b) moveList = make_move_and_gating(pos, moveList, us, from, pop_lsb(&b)); @@ -465,9 +460,8 @@ ExtMove* generate(const Position& pos, ExtMove* moveList) { if (sliders & (pos.pieces(CANNON, BANNER) | pos.pieces(HORSE, ELEPHANT))) { Bitboard target = pos.board_bb() & ~pos.pieces(us); - Bitboard b = pos.attacks_from(ksq, us) & target; - if (pos.xiangqi_general()) - b &= PseudoAttacks[us][WAZIR][ksq]; + Bitboard b = ( (pos.attacks_from(us, KING, ksq) & pos.pieces()) + | (pos.moves_from(us, KING, ksq) & ~pos.pieces())) & target; while (b) moveList = make_move_and_gating(pos, moveList, us, ksq, pop_lsb(&b)); return us == WHITE ? generate_all(pos, moveList, target) @@ -484,9 +478,8 @@ ExtMove* generate(const Position& pos, ExtMove* moveList) { } // Generate evasions for king, capture and non capture moves - Bitboard b = pos.attacks_from(ksq, us) & ~pos.pieces(us) & ~sliderAttacks; - if (pos.xiangqi_general()) - b &= PseudoAttacks[us][WAZIR][ksq]; + Bitboard b = ( (pos.attacks_from(us, KING, ksq) & pos.pieces()) + | (pos.moves_from(us, KING, ksq) & ~pos.pieces())) & ~pos.pieces(us) & ~sliderAttacks; while (b) moveList = make_move_and_gating(pos, moveList, us, ksq, pop_lsb(&b)); diff --git a/src/parser.cpp b/src/parser.cpp index 3165970..52a66ca 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -91,6 +91,18 @@ template void VariantParser::parse_attribute(const std::string& key, T set(it->second, target); } +void VariantParser::parse_attribute(const std::string& key, PieceType& target, std::string pieceToChar) { + const auto& it = config.find(key); + if (it != config.end()) + { + char token; + size_t idx; + std::stringstream ss(it->second); + if (ss >> token && (idx = pieceToChar.find(toupper(token))) != std::string::npos) + target = PieceType(idx); + } +} + Variant* VariantParser::parse() { Variant* v = new Variant(); v->reset_pieces(); @@ -158,16 +170,8 @@ Variant* VariantParser::parse(Variant* v) { parse_attribute("castlingKingsideFile", v->castlingKingsideFile); parse_attribute("castlingQueensideFile", v->castlingQueensideFile); parse_attribute("castlingRank", v->castlingRank); - // castling rook piece type - const auto& it_castling_rook_piece = config.find("castlingRookPiece"); - if (it_castling_rook_piece != config.end()) - { - char token; - size_t idx; - std::stringstream ss(it_castling_rook_piece->second); - if (ss >> token && (idx = v->pieceToChar.find(toupper(token))) != std::string::npos) - v->castlingRookPiece = PieceType(idx); - } + parse_attribute("castlingRookPiece", v->castlingRookPiece, v->pieceToChar); + parse_attribute("kingType", v->kingType, v->pieceToChar); parse_attribute("checking", v->checking); parse_attribute("mustCapture", v->mustCapture); parse_attribute("mustDrop", v->mustDrop); @@ -187,7 +191,6 @@ Variant* VariantParser::parse(Variant* v) { parse_attribute("seirawanGating", v->seirawanGating); parse_attribute("cambodianMoves", v->cambodianMoves); parse_attribute("flyingGeneral", v->flyingGeneral); - parse_attribute("xiangqiGeneral", v->xiangqiGeneral); parse_attribute("xiangqiSoldier", v->xiangqiSoldier); // game end parse_attribute("nMoveRule", v->nMoveRule); @@ -212,16 +215,7 @@ Variant* VariantParser::parse(Variant* v) { while (ss >> token && ((idx = v->pieceToChar.find(toupper(token))) != std::string::npos || token == '*')) v->extinctionPieceTypes.insert(PieceType(token == '*' ? 0 : idx)); } - // flag piece type - const auto& it_flag_pt = config.find("flagPiece"); - if (it_flag_pt != config.end()) - { - char token; - size_t idx; - std::stringstream ss(it_flag_pt->second); - if (ss >> token && (idx = v->pieceToChar.find(toupper(token))) != std::string::npos) - v->flagPiece = PieceType(idx); - } + parse_attribute("flagPiece", v->flagPiece, v->pieceToChar); parse_attribute("whiteFlag", v->whiteFlag); parse_attribute("blackFlag", v->blackFlag); parse_attribute("flagMove", v->flagMove); diff --git a/src/parser.h b/src/parser.h index e634bda..310a81e 100644 --- a/src/parser.h +++ b/src/parser.h @@ -42,6 +42,7 @@ public: private: Config config; template void parse_attribute(const std::string& key, T& target); + void parse_attribute(const std::string& key, PieceType& target, std::string pieceToChar); }; #endif // #ifndef PARSER_H_INCLUDED \ No newline at end of file diff --git a/src/position.cpp b/src/position.cpp index 02f849e..105b5cf 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -773,19 +773,20 @@ Bitboard Position::attackers_to(Square s, Bitboard occupied, Color c) const { for (PieceType pt : piece_types()) if (board_bb(c, pt) & s) { + PieceType move_pt = pt == KING ? king_type() : pt; // Consider asymmetrical move of horse - if (pt == HORSE || pt == BANNER) + if (move_pt == HORSE || move_pt == BANNER) { - Bitboard horses = PseudoAttacks[~c][pt][s] & pieces(c, pt); + Bitboard horses = PseudoAttacks[~c][move_pt][s] & pieces(c, pt); while (horses) { Square s2 = pop_lsb(&horses); - if (attacks_bb(c, pt, s2, occupied) & s) + if (attacks_bb(c, move_pt, s2, occupied) & s) b |= s2; } } else - b |= attacks_bb(~c, pt, s, occupied) & pieces(c, pt); + b |= attacks_bb(~c, move_pt, s, occupied) & pieces(c, pt); } // Consider special move of neang in cambodian chess @@ -796,9 +797,6 @@ Bitboard Position::attackers_to(Square s, Bitboard occupied, Color c) const { b |= pieces(c, FERS) & gates(c) & fers_sq; } - if (xiangqi_general()) - b ^= b & pieces(KING) & ~PseudoAttacks[~c][WAZIR][s]; - if (unpromoted_soldier(c, s)) b ^= b & pieces(SOLDIER) & ~PseudoAttacks[~c][SHOGI_PAWN][s]; @@ -972,10 +970,6 @@ bool Position::pseudo_legal(const Move m) const { if (type_of(m) != NORMAL || is_gating(m)) return MoveList(*this).contains(m); - // Xiangqi general - if (xiangqi_general() && type_of(pc) == KING && !(PseudoAttacks[us][WAZIR][from] & to)) - return false; - // Xiangqi soldier if (type_of(pc) == SOLDIER && unpromoted_soldier(us, from) && file_of(from) != file_of(to)) return false; diff --git a/src/position.h b/src/position.h index 33723c6..3ea59f3 100644 --- a/src/position.h +++ b/src/position.h @@ -119,6 +119,7 @@ public: File castling_queenside_file() const; Rank castling_rank(Color c) const; PieceType castling_rook_piece() const; + PieceType king_type() const; bool checking_permitted() const; bool must_capture() const; bool must_drop() const; @@ -137,7 +138,6 @@ public: bool gating() const; bool seirawan_gating() const; bool cambodian_moves() const; - bool xiangqi_general() const; bool unpromoted_soldier(Color c, Square s) const; // winning conditions int n_move_rule() const; @@ -437,6 +437,11 @@ inline PieceType Position::castling_rook_piece() const { return var->castlingRookPiece; } +inline PieceType Position::king_type() const { + assert(var != nullptr); + return var->kingType; +} + inline bool Position::checking_permitted() const { assert(var != nullptr); return var->checking; @@ -547,11 +552,6 @@ inline bool Position::cambodian_moves() const { return var->cambodianMoves; } -inline bool Position::xiangqi_general() const { - assert(var != nullptr); - return var->xiangqiGeneral; -} - inline bool Position::unpromoted_soldier(Color c, Square s) const { assert(var != nullptr); return var->xiangqiSoldier && relative_rank(c, s, var->maxRank) <= RANK_5; @@ -787,11 +787,11 @@ inline Bitboard Position::attacks_from(Square s, Color c) const { } inline Bitboard Position::attacks_from(Color c, PieceType pt, Square s) const { - return attacks_bb(c, pt, s, byTypeBB[ALL_PIECES]) & board_bb(c, pt); + return attacks_bb(c, pt == KING ? king_type() : pt, s, byTypeBB[ALL_PIECES]) & board_bb(c, pt); } inline Bitboard Position::moves_from(Color c, PieceType pt, Square s) const { - return moves_bb(c, pt, s, byTypeBB[ALL_PIECES]) & board_bb(c, pt); + return moves_bb(c, pt == KING ? king_type() : pt, s, byTypeBB[ALL_PIECES]) & board_bb(c, pt); } inline Bitboard Position::attackers_to(Square s) const { diff --git a/src/variant.cpp b/src/variant.cpp index a2d3fc9..708cbcb 100644 --- a/src/variant.cpp +++ b/src/variant.cpp @@ -174,6 +174,15 @@ namespace { v->checking = false; return v; } + Variant* knightmate_variant() { + Variant* v = fairy_variant_base(); + v->add_piece(COMMONER, 'm'); + v->remove_piece(KNIGHT); + v->startFen = "rmbqkbmr/pppppppp/8/8/8/8/PPPPPPPP/RMBQKBMR w KQkq - 0 1"; + v->kingType = KNIGHT; + v->promotionPieceTypes = {COMMONER, QUEEN, ROOK, BISHOP}; + return v; + } Variant* losers_variant() { Variant* v = fairy_variant_base(); v->checkmateValue = VALUE_MATE; @@ -569,6 +578,7 @@ namespace { SQ_C7, SQ_D7, SQ_E7); v->mobilityRegion[WHITE][KING] = white_castle; v->mobilityRegion[BLACK][KING] = black_castle; + v->kingType = WAZIR; v->promotionPieceTypes = {}; v->doubleStep = false; v->castling = false; @@ -576,7 +586,6 @@ namespace { //v->nFoldValue = VALUE_MATE; v->perpetualCheckIllegal = true; v->flyingGeneral = true; - v->xiangqiGeneral = true; return v; } #ifdef LARGEBOARDS @@ -815,6 +824,7 @@ void VariantMap::init() { add("newzealand", newzealand_variant()); add("kingofthehill", kingofthehill_variant()); add("racingkings", racingkings_variant()); + add("knightmate", knightmate_variant()); add("losers", losers_variant()); add("giveaway", giveaway_variant()); add("antichess", antichess_variant()); diff --git a/src/variant.h b/src/variant.h index 49bbf76..e7f09fd 100644 --- a/src/variant.h +++ b/src/variant.h @@ -63,6 +63,7 @@ struct Variant { File castlingQueensideFile = FILE_C; Rank castlingRank = RANK_1; PieceType castlingRookPiece = ROOK; + PieceType kingType = KING; bool checking = true; bool mustCapture = false; bool mustDrop = false; @@ -82,7 +83,6 @@ struct Variant { bool seirawanGating = false; bool cambodianMoves = false; bool flyingGeneral = false; - bool xiangqiGeneral = false; bool xiangqiSoldier = false; // game end int nMoveRule = 50; diff --git a/src/variants.ini b/src/variants.ini index bd27721..5f55b45 100644 --- a/src/variants.ini +++ b/src/variants.ini @@ -118,6 +118,7 @@ # castlingQueensideFile: destination file of king after queenside castling [File] (default: c) # castlingRank: relative rank of castling [Rank] (default: 1) # castlingRookPiece: piece type that participates in castling [PieceType] (default: r) +# kingType: piece type defining moves of king/royal piece (default: k) # checking: allow checks [bool] (default: true) # mustCapture: captures are mandatory (check evasion still takes precedence) [bool] (default: false) # mustDrop: drops are mandatory (e.g., for Sittuyin setup phase) [bool] (default: false) @@ -137,7 +138,6 @@ # 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) # flyingGeneral: disallow general face-off like in xiangqi [bool] (default: false) -# xiangqiGeneral: restrict king to wazir-like movements [bool] (default: false) # xiangqiSoldier: restrict soldier to shogi pawn movements on first five ranks [bool] (default: false) # nMoveRule: move count for 50/n-move rule [int] (default: 50) # nFoldRule: move count for 3/n-fold repetition rule [int] (default: 3) diff --git a/tests/perft.sh b/tests/perft.sh index c8efc11..8e7eb9a 100755 --- a/tests/perft.sh +++ b/tests/perft.sh @@ -45,6 +45,7 @@ if [[ $1 == "" || $1 == "variant" ]]; then expect perft.exp loop startpos 5 4888832 > /dev/null expect perft.exp chessgi startpos 5 4889167 > /dev/null expect perft.exp racingkings startpos 5 9472927 > /dev/null + expect perft.exp knightmate startpos 5 3249033 > /dev/null expect perft.exp losers startpos 5 2723795 > /dev/null expect perft.exp antichess startpos 5 2732672 > /dev/null expect perft.exp giveaway startpos 5 2732672 > /dev/null -- 1.7.0.4