diff --git a/src/apiutil.h b/src/apiutil.h index ca5858e9..0794abc3 100644 --- a/src/apiutil.h +++ b/src/apiutil.h @@ -346,7 +346,7 @@ inline const std::string move_to_san(Position& pos, Move m, Notation n) { } // Wall square - if (pos.wall_gating()) + if (pos.walling()) san += "," + square(pos, gating_square(m), n); // Check and checkmate diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 82e37128..39c153ca 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -176,10 +176,13 @@ namespace Eval { exit(EXIT_FAILURE); } - if (useNNUE != UseNNUEMode::False) - sync_cout << "info string NNUE evaluation using " << eval_file << " enabled" << sync_endl; - else - sync_cout << "info string classical evaluation enabled" << sync_endl; + if (CurrentProtocol != XBOARD) + { + if (useNNUE != UseNNUEMode::False) + sync_cout << "info string NNUE evaluation using " << eval_file_loaded << " enabled" << sync_endl; + else + sync_cout << "info string classical evaluation enabled" << sync_endl; + } } } diff --git a/src/movegen.cpp b/src/movegen.cpp index b33d9864..0c14dd75 100644 --- a/src/movegen.cpp +++ b/src/movegen.cpp @@ -29,7 +29,7 @@ namespace { ExtMove* make_move_and_gating(const Position& pos, ExtMove* moveList, Color us, Square from, Square to, PieceType pt = NO_PIECE_TYPE) { // Wall placing moves - if (pos.wall_gating()) + if (pos.walling()) { Bitboard b = pos.board_bb() & ~((pos.pieces() ^ from) | to); if (T == CASTLING) @@ -41,11 +41,11 @@ namespace { } if (T == EN_PASSANT) b ^= pos.capture_square(to); - if (pos.variant()->arrowGating) + if (pos.variant()->arrowWalling) b &= moves_bb(us, type_of(pos.piece_on(from)), to, pos.pieces() ^ from); - if (pos.variant()->staticGating) - b &= pos.variant()->staticGatingRegion; - if (pos.variant()->pastGating) + if ((pos.variant()->staticWalling)||(pos.variant()->duckWalling)) + b &= pos.variant()->wallingRegion[us]; + if (pos.variant()->pastWalling) b &= square_bb(from); while (b) diff --git a/src/parser.cpp b/src/parser.cpp index 43d2bc82..078c3387 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -105,8 +105,9 @@ namespace { template <> bool set(const std::string& value, EnclosingRule& target) { target = value == "reversi" ? REVERSI : value == "ataxx" ? ATAXX + : value == "quadwrangle" ? QUADWRANGLE : NO_ENCLOSING; - return value == "reversi" || value == "ataxx" || value == "none"; + return value == "reversi" || value == "ataxx" || value == "quadwrangle" || value == "none"; } template <> bool set(const std::string& value, Bitboard& target) { @@ -123,6 +124,27 @@ namespace { return !ss.fail(); } + template <> bool set(const std::string& value, CastlingRights& target) { + char c; + CastlingRights castlingRight; + std::stringstream ss(value); + target = NO_CASTLING; + bool valid = true; + while (ss >> c && c != '-') + { + castlingRight = c == 'K' ? WHITE_OO + : c == 'Q' ? WHITE_OOO + : c == 'k' ? BLACK_OO + : c == 'q' ? BLACK_OOO + : NO_CASTLING; + if (castlingRight) + target = CastlingRights(target | castlingRight); + else + valid = false; + } + return valid; + } + template void set(PieceType pt, T& target) { target.insert(pt); } @@ -157,6 +179,7 @@ template bool VariantParser::parse_attribute(co : std::is_same() ? "ChasingRule" : std::is_same() ? "EnclosingRule" : std::is_same() ? "Bitboard" + : std::is_same() ? "CastlingRights" : typeid(T).name(); std::cerr << key << " - Invalid value " << it->second << " for type " << typeName << std::endl; } @@ -387,6 +410,7 @@ Variant* VariantParser::parse(Variant* v) { parse_attribute("castlingRookPieces", v->castlingRookPieces[BLACK], v->pieceToChar); parse_attribute("castlingRookPiecesWhite", v->castlingRookPieces[WHITE], v->pieceToChar); parse_attribute("castlingRookPiecesBlack", v->castlingRookPieces[BLACK], v->pieceToChar); + parse_attribute("oppositeCastling", v->oppositeCastling); parse_attribute("checking", v->checking); parse_attribute("dropChecks", v->dropChecks); parse_attribute("mustCapture", v->mustCapture); @@ -409,11 +433,14 @@ Variant* VariantParser::parse(Variant* v) { parse_attribute("dropNoDoubledCount", v->dropNoDoubledCount); parse_attribute("immobilityIllegal", v->immobilityIllegal); parse_attribute("gating", v->gating); - parse_attribute("arrowGating", v->arrowGating); - parse_attribute("duckGating", v->duckGating); - parse_attribute("staticGating", v->staticGating); - parse_attribute("pastGating", v->pastGating); - parse_attribute("staticGatingRegion", v->staticGatingRegion); + parse_attribute("arrowWalling", v->arrowWalling); + parse_attribute("duckWalling", v->duckWalling); + parse_attribute("wallingRegionWhite", v->wallingRegion[WHITE]); + parse_attribute("wallingRegionBlack", v->wallingRegion[BLACK]); + parse_attribute("wallingRegion", v->wallingRegion[WHITE]); + parse_attribute("wallingRegion", v->wallingRegion[BLACK]); + parse_attribute("staticWalling", v->staticWalling); + parse_attribute("pastWalling", v->pastWalling); parse_attribute("seirawanGating", v->seirawanGating); parse_attribute("cambodianMoves", v->cambodianMoves); parse_attribute("diagonalLines", v->diagonalLines); @@ -467,7 +494,8 @@ Variant* VariantParser::parse(Variant* v) { parse_attribute("connectDiagonal", v->connectDiagonal); parse_attribute("materialCounting", v->materialCounting); parse_attribute("countingRule", v->countingRule); - + parse_attribute("castlingWins", v->castlingWins); + // Report invalid options if (DoCheck) { @@ -524,8 +552,8 @@ Variant* VariantParser::parse(Variant* v) { std::cerr << "Inconsistent settings: castlingQueensideFile > castlingKingsideFile." << std::endl; // Check for limitations - if (v->pieceDrops && (v->arrowGating || v->duckGating || v->staticGating || v->pastGating)) - std::cerr << "pieceDrops and arrowGating/duckGating are incompatible." << std::endl; + if (v->pieceDrops && (v->arrowWalling || v->duckWalling || v->staticWalling || v->pastWalling)) + std::cerr << "pieceDrops and arrowWalling/duckWalling are incompatible." << std::endl; // Options incompatible with royal kings if (v->pieceTypes & KING) @@ -534,8 +562,8 @@ Variant* VariantParser::parse(Variant* v) { std::cerr << "Can not use kings with blastOnCapture." << std::endl; if (v->flipEnclosedPieces) std::cerr << "Can not use kings with flipEnclosedPieces." << std::endl; - if (v->duckGating) - std::cerr << "Can not use kings with duckGating." << std::endl; + if (v->duckWalling) + std::cerr << "Can not use kings with duckWalling." << std::endl; // We can not fully check support for custom king movements at this point, // since custom pieces are only initialized on loading of the variant. // We will assume this is valid, but it might cause problems later if it's not. diff --git a/src/piece.cpp b/src/piece.cpp index 39bec12b..1a48f802 100644 --- a/src/piece.cpp +++ b/src/piece.cpp @@ -152,9 +152,9 @@ namespace { v[Direction(atom.first * FILE_NB + atom.second)] = distance; if (directions.size() == 0 || has_dir("bb") || has_dir("vv") || has_dir("lb") || has_dir("lv") || has_dir("bh") || has_dir("lh") || has_dir("hr")) v[Direction(-atom.first * FILE_NB - atom.second)] = distance; - if (directions.size() == 0 || has_dir("rr") || has_dir("ss") || has_dir("br") || has_dir("bs") || has_dir("bh") || has_dir("lh") || has_dir("hr")) + if (directions.size() == 0 || has_dir("rr") || has_dir("ss") || has_dir("br") || has_dir("bs") || has_dir("bh") || has_dir("rh") || has_dir("hr")) v[Direction(-atom.second * FILE_NB + atom.first)] = distance; - if (directions.size() == 0 || has_dir("ll") || has_dir("ss") || has_dir("fl") || has_dir("fs") || has_dir("fh") || has_dir("rh") || has_dir("hr")) + if (directions.size() == 0 || has_dir("ll") || has_dir("ss") || has_dir("fl") || has_dir("fs") || has_dir("fh") || has_dir("lh") || has_dir("hr")) v[Direction(atom.second * FILE_NB - atom.first)] = distance; if (directions.size() == 0 || has_dir("rr") || has_dir("ss") || has_dir("fr") || has_dir("fs") || has_dir("fh") || has_dir("rh") || has_dir("hl")) v[Direction(atom.second * FILE_NB + atom.first)] = distance; diff --git a/src/position.cpp b/src/position.cpp index 3d7b6900..cd44c18a 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -269,7 +269,8 @@ Position& Position::set(const Variant* v, const string& fenStr, bool isChess960, ss >> std::noskipws; - Square sq = SQ_A1 + max_rank() * NORTH; + Rank r = max_rank(); + Square sq = SQ_A1 + r * NORTH; // 1. Piece placement while ((ss >> token) && !isspace(token)) @@ -288,11 +289,19 @@ Position& Position::set(const Variant* v, const string& fenStr, bool isChess960, else if (token == '/') { - sq += 2 * SOUTH + (FILE_MAX - max_file()) * EAST; + sq = SQ_A1 + --r * NORTH; if (!is_ok(sq)) break; } + // Stop before pieces in hand + else if (token == '[') + break; + + // Ignore pieces outside the board and wait for next / or [ to return to a valid state + else if (!is_ok(sq) || file_of(sq) > max_file() || rank_of(sq) > r) + continue; + // Wall square else if (token == '*') { @@ -316,10 +325,6 @@ Position& Position::set(const Variant* v, const string& fenStr, bool isChess960, put_piece(make_piece(color_of(Piece(idx)), promoted_piece_type(type_of(Piece(idx)))), sq, true, Piece(idx)); ++sq; } - - // Stop before pieces in hand - else if (token == '[') - break; } // Pieces in hand if (!isspace(token)) @@ -1104,9 +1109,9 @@ bool Position::legal(Move m) const { { Square kto = to; Bitboard occupied = (type_of(m) != DROP ? pieces() ^ from : pieces()); - if (var->duckGating) + if (var->duckWalling) occupied ^= st->wallSquares; - if (wall_gating() || is_gating(m)) + if (walling() || is_gating(m)) occupied |= gating_square(m); if (type_of(m) == CASTLING) { @@ -1301,15 +1306,15 @@ bool Position::pseudo_legal(const Move m) const { : MoveList(*this).contains(m); // Illegal wall square placement - if (wall_gating() && !((board_bb() & ~((pieces() ^ from) | to)) & gating_square(m))) + if (walling() && !((board_bb() & ~((pieces() ^ from) | to)) & gating_square(m))) return false; - if (var->arrowGating && !(moves_bb(us, type_of(pc), to, pieces() ^ from) & gating_square(m))) + if (var->arrowWalling && !(moves_bb(us, type_of(pc), to, pieces() ^ from) & gating_square(m))) return false; - if (var->pastGating && (from != gating_square(m))) + if (var->pastWalling && (from != gating_square(m))) return false; - if (var->staticGating && !(var->staticGatingRegion & gating_square(m))) + if ((var->staticWalling || var->duckWalling) && !(var->wallingRegion[us] & gating_square(m))) return false; - + // Handle the case where a mandatory piece promotion/demotion is not taken if ( mandatory_piece_promotion() && (is_promoted(from) ? piece_demotion() : promoted_piece_type(type_of(pc)) != NO_PIECE_TYPE) @@ -1664,6 +1669,13 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { { k ^= Zobrist::castling[st->castlingRights]; st->castlingRights &= ~(castlingRightsMask[from] | castlingRightsMask[to]); + + // Remove castling rights from opponent on the same side if oppositeCastling + if ((var->oppositeCastling) && (type_of(m) == CASTLING)) + { + bool kingSide = to > from; + st->castlingRights &= ~(~us & (kingSide ? KING_SIDE : QUEEN_SIDE)); + } k ^= Zobrist::castling[st->castlingRights]; } @@ -1680,8 +1692,11 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { } else { - assert(flip_enclosed_pieces() == ATAXX); - st->flippedPieces = PseudoAttacks[us][KING][to] & pieces(~us); + assert((flip_enclosed_pieces() == ATAXX) || (flip_enclosed_pieces() == QUADWRANGLE)); + if ((flip_enclosed_pieces() == ATAXX) || (flip_enclosed_pieces() == QUADWRANGLE && (PseudoAttacks[us][KING][to] & pieces(us) || type_of(m) == NORMAL))) + { + st->flippedPieces = PseudoAttacks[us][KING][to] & pieces(~us); + } } // Flip pieces @@ -1805,7 +1820,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { { if ( (var->enPassantRegion & (to - pawn_push(us))) && ((pawn_attacks_bb(us, to - pawn_push(us)) & pieces(them, PAWN)) || var->enPassantTypes[them] & ~piece_set(PAWN)) - && !(wall_gating() && gating_square(m) == to - pawn_push(us))) + && !(walling() && gating_square(m) == to - pawn_push(us))) { st->epSquares |= to - pawn_push(us); k ^= Zobrist::enpassant[file_of(to)]; @@ -1813,7 +1828,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { if ( std::abs(int(to) - int(from)) == 3 * NORTH && (var->enPassantRegion & (to - 2 * pawn_push(us))) && ((pawn_attacks_bb(us, to - 2 * pawn_push(us)) & pieces(them, PAWN)) || var->enPassantTypes[them] & ~piece_set(PAWN)) - && !(wall_gating() && gating_square(m) == to - 2 * pawn_push(us))) + && !(walling() && gating_square(m) == to - 2 * pawn_push(us))) { st->epSquares |= to - 2 * pawn_push(us); k ^= Zobrist::enpassant[file_of(to)]; @@ -2018,10 +2033,10 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { } // Add gated wall square - if (wall_gating()) + if (walling()) { - // Reset wall squares for duck gating - if (var->duckGating) + // Reset wall squares for duck walling + if (var->duckWalling) { Bitboard b = st->previous->wallSquares; byTypeBB[ALL_PIECES] ^= b; @@ -2460,7 +2475,7 @@ bool Position::see_ge(Move m, Value threshold) const { stmAttackers &= ~blockers_for_king(stm); // Ignore distant sliders - if (var->duckGating) + if (var->duckWalling) stmAttackers &= attacks_bb(to) | ~(pieces(BISHOP, ROOK) | pieces(QUEEN)); if (!stmAttackers) @@ -2727,6 +2742,32 @@ bool Position::is_immediate_game_end(Value& result, int ply) const { return true; } } + + // Castle chess + if (var->castlingWins) + { + if (st->pliesFromNull > 0 && type_of(st->move) == CASTLING) + { + // check for victory first, because castling also removes castling rights. + CastlingRights justCastled = ~sideToMove & ((from_sq(st->move) < to_sq(st->move)) ? KING_SIDE : QUEEN_SIDE); + if (var->castlingWins & justCastled) + { + result = mated_in(ply); + return true; + } + } + // We check the opponent side first, because a rook capturing a rook could remove both sides castling rights, + // which should likely be seen as losing, analogous to extinction rules. + for (Color c : { ~sideToMove, sideToMove }) + if ((c & var->castlingWins) && !(c & var->castlingWins & st->castlingRights)) + { + // player permanently losing castling rights. either through moving a castling piece, + // or having their rook captured. + result = c == sideToMove ? mated_in(ply) : mate_in(ply); + return true; + } + } + // nCheck if (check_counting() && checks_remaining(~sideToMove) == 0) { @@ -2927,7 +2968,7 @@ bool Position::has_game_cycle(int ply) const { int end = captures_to_hand() ? st->pliesFromNull : std::min(st->rule50, st->pliesFromNull); - if (end < 3 || var->nFoldValue != VALUE_DRAW || var->perpetualCheckIllegal || var->materialCounting || var->moveRepetitionIllegal || var->duckGating) + if (end < 3 || var->nFoldValue != VALUE_DRAW || var->perpetualCheckIllegal || var->materialCounting || var->moveRepetitionIllegal || var->duckWalling) return false; Key originalKey = st->key; diff --git a/src/position.h b/src/position.h index 5be942c8..f3c7b4da 100644 --- a/src/position.h +++ b/src/position.h @@ -181,7 +181,7 @@ class Position { PieceType drop_no_doubled() const; bool immobility_illegal() const; bool gating() const; - bool wall_gating() const; + bool walling() const; bool seirawan_gating() const; bool cambodian_moves() const; Bitboard diagonal_lines() const; @@ -778,9 +778,9 @@ inline bool Position::gating() const { return var->gating; } -inline bool Position::wall_gating() const { +inline bool Position::walling() const { assert(var != nullptr); - return var->arrowGating || var->duckGating || var->staticGating || var->pastGating; + return var->arrowWalling || var->duckWalling || var->staticWalling || var->pastWalling; } inline bool Position::seirawan_gating() const { diff --git a/src/search.cpp b/src/search.cpp index b9d5ffc0..24252e59 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -75,7 +75,7 @@ namespace { } int futility_move_count(bool improving, Depth depth, const Position& pos) { - return (3 + depth * depth * (1 + pos.wall_gating()) + 2 * pos.blast_on_capture()) / (2 - improving + pos.blast_on_capture()); + return (3 + depth * depth * (1 + pos.walling()) + 2 * pos.blast_on_capture()) / (2 - improving + pos.blast_on_capture()); } // History and stats update bonus, based on depth @@ -276,7 +276,7 @@ void MainThread::search() { if (!Limits.infinite && !ponder && rootMoves[0].pv[0] != MOVE_NONE && !Threads.abort.exchange(true)) { std::string move = UCI::move(rootPos, bestMove); - if (rootPos.wall_gating()) + if (rootPos.walling()) { sync_cout << "move " << move.substr(0, move.find(",")) << "," << sync_endl; sync_cout << "move " << move.substr(move.find(",") + 1) << sync_endl; @@ -798,7 +798,7 @@ namespace { { int penalty = -stat_bonus(depth); thisThread->mainHistory[us][from_to(ttMove)] << penalty; - if (pos.wall_gating()) + if (pos.walling()) thisThread->gateHistory[us][gating_square(ttMove)] << penalty; update_continuation_histories(ss, pos.moved_piece(ttMove), to_sq(ttMove), penalty); } @@ -1184,7 +1184,7 @@ namespace { continue; // Prune moves with negative SEE (~20 Elo) - if (!pos.variant()->duckGating && !pos.see_ge(move, Value(-(30 - std::min(lmrDepth, 18) + 10 * !!pos.flag_region(pos.side_to_move())) * lmrDepth * lmrDepth))) + if (!pos.variant()->duckWalling && !pos.see_ge(move, Value(-(30 - std::min(lmrDepth, 18) + 10 * !!pos.flag_region(pos.side_to_move())) * lmrDepth * lmrDepth))) continue; } } @@ -1822,9 +1822,9 @@ namespace { // Decrease stats for all non-best quiet moves for (int i = 0; i < quietCount; ++i) { - if (!(pos.wall_gating() && from_to(quietsSearched[i]) == from_to(bestMove))) + if (!(pos.walling() && from_to(quietsSearched[i]) == from_to(bestMove))) thisThread->mainHistory[us][from_to(quietsSearched[i])] << -bonus2; - if (pos.wall_gating()) + if (pos.walling()) thisThread->gateHistory[us][gating_square(quietsSearched[i])] << -bonus2; update_continuation_histories(ss, pos.moved_piece(quietsSearched[i]), to_sq(quietsSearched[i]), -bonus2); } @@ -1833,7 +1833,7 @@ namespace { { // Increase stats for the best move in case it was a capture move captureHistory[moved_piece][to_sq(bestMove)][captured] << bonus1; - if (pos.wall_gating()) + if (pos.walling()) thisThread->gateHistory[us][gating_square(bestMove)] << bonus1; } @@ -1848,9 +1848,9 @@ namespace { { moved_piece = pos.moved_piece(capturesSearched[i]); captured = type_of(pos.piece_on(to_sq(capturesSearched[i]))); - if (!(pos.wall_gating() && from_to(capturesSearched[i]) == from_to(bestMove))) + if (!(pos.walling() && from_to(capturesSearched[i]) == from_to(bestMove))) captureHistory[moved_piece][to_sq(capturesSearched[i])][captured] << -bonus1; - if (pos.wall_gating()) + if (pos.walling()) thisThread->gateHistory[us][gating_square(capturesSearched[i])] << -bonus1; } } @@ -1886,7 +1886,7 @@ namespace { Color us = pos.side_to_move(); Thread* thisThread = pos.this_thread(); thisThread->mainHistory[us][from_to(move)] << bonus; - if (pos.wall_gating()) + if (pos.walling()) thisThread->gateHistory[us][gating_square(move)] << bonus; update_continuation_histories(ss, pos.moved_piece(move), to_sq(move), bonus); diff --git a/src/types.h b/src/types.h index bf00fca5..ab457ba2 100644 --- a/src/types.h +++ b/src/types.h @@ -304,7 +304,7 @@ enum ChasingRule { }; enum EnclosingRule { - NO_ENCLOSING, REVERSI, ATAXX + NO_ENCLOSING, REVERSI, ATAXX, QUADWRANGLE }; enum OptBool { diff --git a/src/uci.cpp b/src/uci.cpp index 9dc8d75f..a594bc6f 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -634,7 +634,7 @@ string UCI::move(const Position& pos, Move m) { : UCI::square(pos, from)) + UCI::square(pos, to); // Wall square - if (pos.wall_gating() && CurrentProtocol == XBOARD) + if (pos.walling() && CurrentProtocol == XBOARD) move += "," + UCI::square(pos, to) + UCI::square(pos, gating_square(m)); if (type_of(m) == PROMOTION) @@ -651,7 +651,7 @@ string UCI::move(const Position& pos, Move m) { } // Wall square - if (pos.wall_gating() && CurrentProtocol != XBOARD) + if (pos.walling() && CurrentProtocol != XBOARD) move += "," + UCI::square(pos, to) + UCI::square(pos, gating_square(m)); return move; diff --git a/src/variant.cpp b/src/variant.cpp index b7d37f0e..7b404457 100644 --- a/src/variant.cpp +++ b/src/variant.cpp @@ -500,7 +500,7 @@ namespace { v->castlingKingPiece[WHITE] = v->castlingKingPiece[BLACK] = COMMONER; v->extinctionValue = -VALUE_MATE; v->extinctionPieceTypes = piece_set(COMMONER); - v->duckGating = true; + v->duckWalling = true; v->stalemateValue = VALUE_MATE; return v; } @@ -512,10 +512,10 @@ namespace { v->maxFile = FILE_F; v->reset_pieces(); v->add_piece(CUSTOM_PIECE_1, 'p', "mK"); //move as a King, but can't capture - v->startFen = "2p3/6/6/6/6/6/6/3P2 w - - 0 1"; + v->startFen = "3p2/6/6/6/6/6/6/2P3 w - - 0 1"; v->stalemateValue = -VALUE_MATE; - v->staticGating = true; - v->staticGatingRegion = AllSquares ^ make_bitboard(SQ_C1, SQ_D8); + v->staticWalling = true; + v->wallingRegion[WHITE] = v->wallingRegion[BLACK] = AllSquares ^ make_bitboard(SQ_C1, SQ_D8); return v; } @@ -524,7 +524,7 @@ namespace { v->maxRank = RANK_7; v->maxFile = FILE_G; v->startFen = "3p3/7/7/7/7/7/3P3 w - - 0 1"; - v->staticGatingRegion = AllSquares ^ make_bitboard(SQ_D1, SQ_D7); + v->wallingRegion[WHITE] = v->wallingRegion[BLACK] = AllSquares ^ make_bitboard(SQ_D1, SQ_D7); return v; } @@ -536,7 +536,7 @@ namespace { v->add_piece(CUSTOM_PIECE_1, 'p', "mK"); //move as a King, but can't capture v->startFen = "6p/7/7/7/7/7/P6 w - - 0 1"; v->stalemateValue = -VALUE_MATE; - v->pastGating = true; + v->pastWalling = true; return v; } @@ -547,7 +547,7 @@ namespace { v->add_piece(CUSTOM_PIECE_1, 'n', "mN"); //move as a Knight, but can't capture v->startFen = "8/8/8/4n3/3N4/8/8/8 w - - 0 1"; v->stalemateValue = -VALUE_MATE; - v->pastGating = true; + v->pastWalling = true; return v; } @@ -1653,7 +1653,7 @@ namespace { v->add_piece(CUSTOM_PIECE_1, 'q', "mQ"); v->startFen = "3q2q3/10/10/q8q/10/10/Q8Q/10/10/3Q2Q3 w - - 0 1"; v->stalemateValue = -VALUE_MATE; - v->arrowGating = true; + v->arrowWalling = true; return v; } #endif @@ -1807,11 +1807,11 @@ void VariantMap::init() { add("horde", horde_variant()); add("nocheckatomic", nocheckatomic_variant()); add("atomic", atomic_variant()); + add("atomar", atomar_variant()); add("isolation", isolation_variant()); add("isolation7x7", isolation7x7_variant()); add("snailtrail", snailtrail_variant()); add("fox-and-hounds", fox_and_hounds_variant()); - add("atomar", atomar_variant()); #ifdef ALLVARS add("duck", duck_variant()); #endif diff --git a/src/variant.h b/src/variant.h index f8566a61..00ffce3c 100644 --- a/src/variant.h +++ b/src/variant.h @@ -83,6 +83,7 @@ struct Variant { File castlingRookKingsideFile = FILE_MAX; // only has to match if rook is not in corner in non-960 variants File castlingRookQueensideFile = FILE_A; // only has to match if rook is not in corner in non-960 variants PieceSet castlingRookPieces[COLOR_NB] = {piece_set(ROOK), piece_set(ROOK)}; + bool oppositeCastling = false; PieceType kingType = KING; bool checking = true; bool dropChecks = true; @@ -106,11 +107,11 @@ struct Variant { int dropNoDoubledCount = 1; bool immobilityIllegal = false; bool gating = false; - bool arrowGating = false; - bool duckGating = false; - bool staticGating = false; - bool pastGating = false; - Bitboard staticGatingRegion = AllSquares; + bool arrowWalling = false; + bool duckWalling = false; + bool staticWalling = false; + bool pastWalling = false; + Bitboard wallingRegion[COLOR_NB] = {AllSquares, AllSquares}; bool seirawanGating = false; bool cambodianMoves = false; Bitboard diagonalLines = 0; @@ -156,6 +157,7 @@ struct Variant { bool connectDiagonal = true; MaterialCounting materialCounting = NO_MATERIAL_COUNTING; CountingRule countingRule = NO_COUNTING; + CastlingRights castlingWins = NO_CASTLING; // Derived properties bool fastAttacks = true; diff --git a/src/variants.ini b/src/variants.ini index 63bd4da6..7d0b36ff 100644 --- a/src/variants.ini +++ b/src/variants.ini @@ -124,12 +124,13 @@ # [int]: any natural number [0, 1, ...] # [PieceType]: a piece type [letters defined for pieces, e.g., p] # [PieceSet]: multiple piece types [letters defined for pieces, e.g., nbrq] +# [CastlingRights]: set of castling rights [letters for castling rights as in FEN, e.g., KQkq] # [Bitboard]: list of squares [e.g., d4 e4 d5 e5]. * can be used as wildcard for files (e.g., *1 is the first rank) # [Value]: game result for the side to move [win, loss, draw] # [MaterialCounting]: material counting rules for adjudication [janggi, unweighted, whitedrawodds, blackdrawodds, none] # [CountingRule]: makruk, cambodian, or ASEAN counting rules [makruk, cambodian, asean, none] # [ChasingRule]: xiangqi chasing rules [axf, none] -# [EnclosingRule]: reversi or ataxx enclosing rules [reversi, ataxx, none] +# [EnclosingRule]: reversi or ataxx enclosing rules [reversi, ataxx, quadwrangle, none] ### Additional options relevant for usage in Winboard/XBoard # A few options only have the purpose of improving compatibility with Winboard/Xboard. @@ -172,10 +173,10 @@ # petrifyBlastPieces: if petrify and blast combined, should pieces destroyed in the blast be petrified? [bool] (default: false) # doubleStep: enable pawn double step [bool] (default: true) # doubleStepRegionWhite: region where pawn double steps are allowed for white [Bitboard] (default: *2) -# doubleStepRegionBlack: region where pawn double steps are allowed for black [Bitboard] (default: *2) +# doubleStepRegionBlack: region where pawn double steps are allowed for black [Bitboard] (default: *7) # tripleStepRegionWhite: region where pawn triple steps are allowed for white [Bitboard] (default: -) # tripleStepRegionBlack: region where pawn triple steps are allowed for black [Bitboard] (default: -) -# enPassantRegion: define region (target squares) where en passant is allowed after double steps [Bitboard] +# enPassantRegion: define region (target squares) where en passant is allowed after double steps [Bitboard] (default: AllSquares) # enPassantTypes: define pieces able to capture en passant [PieceSet] (default: p) # enPassantTypesWhite: define white pieces able to capture en passant [PieceSet] (default: p) # enPassantTypesBlack: define black pieces able to capture en passant [PieceSet] (default: p) @@ -189,6 +190,7 @@ # castlingRookKingsideFile: starting file of castlingRookPieces on kingside (if not in corner) [File] (default: l) # castlingRookQueensideFile: starting file of castlingRookPieces on queenside (if not in corner) [File] (default: a) # castlingRookPieces: second piece type that participates in castling [PieceSet] (default: r) +# oppositeCastling: can't castle same side as opponent [bool] (default: false) # checking: allow checks [bool] (default: true) # dropChecks: allow checks by piece drops [bool] (default: true) # mustCapture: captures are mandatory (check evasion still takes precedence) [bool] (default: false) @@ -211,11 +213,12 @@ # dropNoDoubledCount: specifies the count of already existing pieces for dropNoDoubled [int] (default: 1) # immobilityIllegal: pieces may not move to squares where they can never move from [bool] (default: false) # gating: maintain squares on backrank with extra rights in castling field of FEN [bool] (default: false) -# arrowGating: gating of wall squares in Game of the Amazons style [bool] (default: false) -# duckGating: gating of a wall square in Duck chess style [bool] (default: false) -# staticGating: gating of wall squares in Isolation style [bool] (default: false) -# staticGatingRegion: mask where wall squares can be placed [Bitboard] (default: all squares) -# pastGating: gating of previous square [bool] (default: false) +# arrowWalling: walling squares in Game of the Amazons style [bool] (default: false) +# duckWalling: walling square in Duck chess style [bool] (default: false) +# staticWalling: walling squares in Isolation style [bool] (default: false) +# wallingRegionWhite: mask where wall squares (including duck) can be placed by white [Bitboard] (default: all squares) +# wallingRegionBlack: mask where wall squares (including duck) can be placed by black [Bitboard] (default: all squares) +# pastWalling: walling of previous square in Snailtrail style [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) # diagonalLines: enable special moves along diagonal for specific squares (Janggi) [Bitboard] @@ -264,6 +267,7 @@ # connectDiagonal: connectN looks at Diagonal rows [bool] (default: true) # materialCounting: enable material counting rules [MaterialCounting] (default: none) # countingRule: enable counting rules [CountingRule] (default: none) +# castlingWins: Specified castling moves are win conditions. Losing these rights is losing. [CastlingRights] (default: -) ################################################ ### Example for minishogi configuration that would be equivalent to the built-in variant: @@ -455,6 +459,15 @@ flagPiece = q flagRegionWhite = *8 flagRegionBlack = *1 +#https://github.com/yagu0/vchess/blob/master/client/src/translations/rules/Pawnsking/en.pug +[pawnsking:pawnsonly] +commoner = k +flagPiece = * +startFen = 4k3/pppppppp/8/8/8/8/PPPPPPPP/4K3 w - - 0 1 +extinctionValue = loss +extinctionPieceTypes = k +stalemateValue = draw + [tictactoe] maxRank = 3 maxFile = 3 @@ -514,6 +527,39 @@ whiteDropRegion = *1 *2 *3 *4 *5 blackDropRegion = *4 *5 *6 *7 *8 immobilityIllegal = true +#https://www.chess.com/variants/caught-in-a-snag +[caught-in-a-snag:chess] +pieceToCharTable = P..........GUFW......Mp..........gufw......m +maxRank = 6 +pawnTypes = p +fers = f +horse = u +wazir = w +centaur = m +king = g:gBgR +customPiece1 = p:fmWfcF +startFen = fuwmgwuf/pppppppp/8/8/PPPPPPPP/FUWGMWUF w - - 0 1 +stalemateValue = win +promotionPawnTypes = - + +#https://www.chess.com/variants/minihouse +[minihouse:crazyhouse] +maxRank = 6 +maxFile = 6 +startFen = 2bnrk/5p/6/6/P5/KRNB2 + +#https://www.chess.com/variants/rookmate +[rookmate:chess] +#in this variant, one rook is royal, one isn't +#to define this, the royal rook will be a king(K) with custom moves, and the non-royal king a commoner(C) +pieceToCharTable = PNBRQC...............Kpnbrqc...............k +startFen = rnbqcbnk/pppppppp/8/8/8/8/PPPPPPPP/RNBQCBNK w 7+7 0 1 +king = k:R +commoner = c +extinctionPieceCount = 1 +extinctionValue = loss +extinctionPieceTypes = * + # Asymmetric variant with one army using pieces that move like knights but attack like other pieces (kniroo and knibis) [orda:chess] pieceToCharTable = PNBRQ..AH...........LKp...q..ah.y.........lk @@ -1303,9 +1349,27 @@ pieceToCharTable = P...Q..AH..ECTDY....LKp...q..ah..ectdy....lk # Atomic + duck chess hybrid. # Playable as a custom variant in chess.com [atomicduck:atomic] -duckGating = true +duckWalling = true stalemateValue = win +#https://www.chessvariants.com/diffmove.dir/checkers.html +[forward:chess] +pieceToCharTable = PNBRQGE......S.F..LKpnbrqge......s.f..lk +#pieces move only forward. Not even sideways, so no castling. +castling = false +#I tried to pick letters that would remind people of the full piece. +startFen = lesfgsel/pppppppp/8/8/8/8/PPPPPPPP/LESFGSEL w KQkq - 0 1 +lance = l +customPiece1 = g:fFfW +customPiece2 = e:ffNfsN +customPiece3 = s:fB +customPiece4 = f:fBfR +promotedPieceType = l:r e:n s:b f:q g:k +#since the king changes forms, should be an extinction variant +extinctionValue = loss +extinctionPieceTypes = kg +extinctionPseudoRoyal = true + [kono] maxRank = 5 maxFile = e @@ -1594,3 +1658,72 @@ customPiece1 = p:fmWfceFifmnD pawnTypes = p petrifyOnCapture = true enPassantRegion = - + +#https://en.wikipedia.org/wiki/Nim +#FSF can be used to analyse Nim. The number of empty squares between the pieces is the number of items in the stack, +#the number in the custom piece is the number of items you can nim. This is the popular 1-3-5-7 stacks layout. +[nim] +maxRank = 9 +maxFile = d +#if the Nim variant has special rules, ie.nimming<=3 pieces then: +#customPiece1 = p:mfR3 +customPiece1 = p:mfR +startFen = 3p/4/2p1/4/1p2/4/p3/4/PPPP +stalemateValue = loss + +#https://www.ludii.games/details.php?keyword=Roll-Ing%20to%20Four +[roll-ing-to-four] +maxRank = 10 +maxFile = d +customPiece1 = p:mfFmfW +startFen = 1ppp/4/4/4/1PPP/ppp1/4/4/4/PPP1 +connectN = 4 + +#https://www.ludii.games/details.php?keyword=Quad%20Wrangle +[quadwrangle:ataxx] +#different sources give different info on whether it is a 7x7 or 8x8 board. +#7x7 is below +maxRank = 8 +maxFile = 8 +startFen = 1PPPPPP1/p6P/p6P/p6P/p6P/p6P/p6P/1pppppp1 +customPiece1 = p:mQ +flipEnclosedPieces = quadwrangle +#override rule from ataxx since drops can be done freely +enclosingDrop = none + +[quadwrangle7x7:quadwrangle] +maxRank = 7 +maxFile = 7 +startFen = 1PPPPP1/p5P/p5P/p5P/p5P/p5P/1ppppp1 + +#https://www.ludii.games/details.php?keyword=Crusade +[crusade:quadwrangle] +startFen = PpPpPpPp/pPpPpPpP/PpPpPpPp/pPpPpPpP/PpPpPpPp/pPpPpPpP/PpPpPpPp/pPpPpPpP +customPiece1 = p:cK +flipEnclosedPieces = ataxx +pieceDrops = false +passOnStalemate = false + +#https://www.chess.com/variants/blackletter-chess +[blackletter:chess] +pieceToCharTable = PNBRQ.......E...M...HKpnbrq.......e...m...hk +maxRank = 10 +maxFile = 9 +promotionRegionWhite = *10 +promotionRegionBlack = *1 +archbishop = e +chancellor = m +centaur = h +startFen = 1ebq1meb1/rn2k2nr/ppppppppp/9/9/9/9/PPPPPPPPP/RN2K2NR/1EBQ1MEB1[HHhh] w KQkq - 0 1 +promotionPieceTypes = behmnqr +doubleStepRegionWhite = *3 +doubleStepRegionBlack = *8 +pieceDrops = true +castlingRank = 2 + +#https://www.chessvariants.com/winning.dir/castle.html +[castle:chess] +castlingWins = q + +[opposite-castling:chess] +oppositeCastling = true diff --git a/test.py b/test.py index 11558177..b2d953a5 100644 --- a/test.py +++ b/test.py @@ -91,6 +91,15 @@ startFen = 7k/5Kq1/8/8/8/8/8/8 w - - 0 1 stalemateValue = loss nFoldValue = loss + +[betzatest] +maxRank = 7 +maxFile = 7 +customPiece1 = a:lhN +customPiece2 = b:rhN +customPiece3 = c:hlN +customPiece4 = d:hrN +startFen = 7/7/7/3A3/7/7/7 w - - 0 1 """ sf.load_variant_config(ini_text) @@ -330,6 +339,17 @@ def test_legal_moves(self): result = sf.legal_moves("yarishogi", sf.start_fen("yarishogi"), []) self.assertCountEqual(legals, result) + # Test betza parsing + result = sf.legal_moves("betzatest", "7/7/7/3A3/7/7/7 w - - 0 1", []) + self.assertEqual(['d4c2', 'd4b3', 'd4b5', 'd4c6'], result) + result = sf.legal_moves("betzatest", "7/7/7/3B3/7/7/7 w - - 0 1", []) + self.assertEqual(['d4e2', 'd4f3', 'd4f5', 'd4e6'], result) + result = sf.legal_moves("betzatest", "7/7/7/3C3/7/7/7 w - - 0 1", []) + self.assertEqual(['d4e2', 'd4b3', 'd4f5', 'd4c6'], result) + result = sf.legal_moves("betzatest", "7/7/7/3D3/7/7/7 w - - 0 1", []) + self.assertEqual(['d4c2', 'd4f3', 'd4b5', 'd4e6'], result) + + def test_short_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'] diff --git a/tests/perft.sh b/tests/perft.sh index 1cc3886f..f6845f9a 100755 --- a/tests/perft.sh +++ b/tests/perft.sh @@ -92,6 +92,10 @@ if [[ $1 == "all" || $1 == "variant" ]]; then expect perft.exp atomic "fen rn2kb1r/1pp1p2p/p2q1pp1/3P4/2P3b1/4PN2/PP3PPP/R2QKB1R b KQkq - 0 1" 4 1434825 > /dev/null expect perft.exp atomic "fen rn1qkb1r/p5pp/2p5/3p4/N3P3/5P2/PPP4P/R1BQK3 w Qkq - 0 1" 4 714499 > /dev/null expect perft.exp atomic "fen r4b1r/2kb1N2/p2Bpnp1/8/2Pp3p/1P1PPP2/P5PP/R3K2R b KQ - 0 1" 2 148 > /dev/null + expect perft.exp atomar startpos 4 197779 > /dev/null + expect perft.exp atomar "fen 7r/6Pb/1np5/1p2kp2/P1P1n3/1P2KP1B/5N2/8 w - - 0 1" 3 24644 > /dev/null + expect perft.exp nocheckatomic startpos 4 197779 > /dev/null + expect perft.exp nocheckatomic "fen 7r/6Pb/1np5/1p2kp2/P1P1n3/1P2KP1B/5N2/8 w - - 0 1" 3 21347 > /dev/null expect perft.exp antichess startpos 4 153299 > /dev/null expect perft.exp giveaway startpos 4 153299 > /dev/null expect perft.exp giveaway "fen 8/1p6/8/8/8/8/P7/8 w - - 0 1" 4 3 > /dev/null