From f6d827639a70d87ac1be9a6a5248ceb5877e2653 Mon Sep 17 00:00:00 2001 From: Fabian Fichter Date: Sun, 4 Nov 2018 17:50:10 +0100 Subject: [PATCH] Support Sittuyin (Burmese chess) - Rook drops are limited to back ranks. - Promotion is allowed on main diagonals within opponent's half or when only one pawn is left. The player must not have a general. - Promoting pawns stay on same square or move like a general. - Pawn promotions may not capture or give check. --- src/bitboard.h | 4 ++++ src/endgame.cpp | 22 ++++++++++++++++++---- src/movegen.cpp | 28 ++++++++++++++++++++++++++++ src/pawns.cpp | 8 +++++--- src/position.cpp | 27 ++++++++++++++++----------- src/position.h | 12 ++++++++++++ src/types.h | 2 +- src/variant.cpp | 17 +++++++++++++++++ src/variant.h | 2 ++ tests/perft.sh | 2 ++ 10 files changed, 105 insertions(+), 19 deletions(-) diff --git a/src/bitboard.h b/src/bitboard.h index 35ee1cb..a6aa395 100644 --- a/src/bitboard.h +++ b/src/bitboard.h @@ -272,6 +272,10 @@ inline Bitboard between_bb(Square s1, Square s2) { /// in front of the given one, from the point of view of the given color. For instance, /// forward_ranks_bb(BLACK, SQ_D3) will return the 16 squares on ranks 1 and 2. +inline Bitboard forward_ranks_bb(Color c, Rank r) { + return ForwardRanksBB[c][r]; +} + inline Bitboard forward_ranks_bb(Color c, Square s) { return ForwardRanksBB[c][rank_of(s)]; } diff --git a/src/endgame.cpp b/src/endgame.cpp index 6cf3aee..fad1d65 100644 --- a/src/endgame.cpp +++ b/src/endgame.cpp @@ -186,10 +186,20 @@ Value Endgame::operator()(const Position& pos) const { Color us = strongSide == pos.side_to_move() ? WHITE : BLACK; - if (!Bitbases::probe(wksq, psq, bksq, us)) - return VALUE_DRAW; + Value result; + if ( pos.promotion_rank() == RANK_8 + && pos.promotion_piece_types().find(QUEEN) != pos.promotion_piece_types().end()) + { + if (!Bitbases::probe(wksq, psq, bksq, us)) + return VALUE_DRAW; - Value result = VALUE_KNOWN_WIN + PawnValueEg + Value(rank_of(psq)); + result = VALUE_KNOWN_WIN + PawnValueEg + Value(rank_of(psq)); + } + else + { + // Non-standard promotion, evaluation unclear + result = PawnValueEg + Value(rank_of(psq)); + } return strongSide == pos.side_to_move() ? result : -result; } @@ -791,5 +801,9 @@ ScaleFactor Endgame::operator()(const Position& pos) const { // Probe the KPK bitbase with the weakest side's pawn removed. If it's a draw, // it's probably at least a draw even with the pawn. - return Bitbases::probe(wksq, psq, bksq, us) ? SCALE_FACTOR_NONE : SCALE_FACTOR_DRAW; + if ( pos.promotion_rank() == RANK_8 + && pos.promotion_piece_types().find(QUEEN) != pos.promotion_piece_types().end()) + return Bitbases::probe(wksq, psq, bksq, us) ? SCALE_FACTOR_NONE : SCALE_FACTOR_DRAW; + else + return SCALE_FACTOR_NONE; } diff --git a/src/movegen.cpp b/src/movegen.cpp index aa1ec14..6996f6d 100644 --- a/src/movegen.cpp +++ b/src/movegen.cpp @@ -96,6 +96,8 @@ namespace { for (File f = FILE_A; f <= pos.max_file(); ++f) if (file_bb(f) & pos.pieces(Us, pt)) b &= ~file_bb(f); + if (pt == ROOK && pos.sittuyin_rook_drop()) + b &= rank_bb(relative_rank(Us, RANK_1, pos.max_rank())); if (Checks) b &= pos.check_squares(pt); while (b) @@ -203,6 +205,32 @@ namespace { moveList = make_promotions(pos, moveList, pop_lsb(&b3)); } + // Sittuyin promotions + if (pos.sittuyin_promotion() && (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS)) + { + Bitboard pawns = pos.pieces(Us, PAWN); + // Pawns need to be on diagonals on opponent's half if there is more than one pawn + if (pos.count(Us) > 1) + pawns &= ( PseudoAttacks[Us][BISHOP][make_square(FILE_A, relative_rank(Us, RANK_1, pos.max_rank()))] + | PseudoAttacks[Us][BISHOP][make_square(pos.max_file(), relative_rank(Us, RANK_1, pos.max_rank()))]) + & forward_ranks_bb(Us, relative_rank(Us, Rank((pos.max_rank() - 1) / 2), pos.max_rank())); + while (pawns) + { + Square from = pop_lsb(&pawns); + for (PieceType pt : pos.promotion_piece_types()) + { + if (pos.count(Us, pt)) + continue; + Bitboard b = (pos.attacks_from(Us, pt, from) & ~pos.pieces()) | from; + if (Type == EVASIONS) + b &= target; + + while (b) + *moveList++ = make(from, pop_lsb(&b), pt); + } + } + } + // Standard and en-passant captures if (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS) { diff --git a/src/pawns.cpp b/src/pawns.cpp index 2ce62e8..bea3e55 100644 --- a/src/pawns.cpp +++ b/src/pawns.cpp @@ -102,7 +102,7 @@ namespace { opposed = theirPawns & forward_file_bb(Us, s); stoppers = theirPawns & passed_pawn_mask(Us, s); lever = theirPawns & PseudoAttacks[Us][PAWN][s]; - leverPush = theirPawns & PseudoAttacks[Us][PAWN][s + Up]; + leverPush = relative_rank(Them, s, pos.max_rank()) > RANK_1 ? theirPawns & PseudoAttacks[Us][PAWN][s + Up] : 0; doubled = relative_rank(Us, s, pos.max_rank()) > RANK_1 ? ourPawns & (s - Up) : 0; neighbours = ourPawns & adjacent_files_bb(f); phalanx = neighbours & rank_bb(s); @@ -110,7 +110,8 @@ namespace { // A pawn is backward when it is behind all pawns of the same color // on the adjacent files and cannot be safely advanced. - backward = !(ourPawns & pawn_attack_span(Them, s + Up)) + backward = relative_rank(Them, s, pos.max_rank()) > RANK_1 + && !(ourPawns & pawn_attack_span(Them, s + Up)) && (stoppers & (leverPush | (s + Up))); // Passed pawns will be properly scored in evaluation because we need @@ -123,7 +124,8 @@ namespace { && popcount(phalanx) >= popcount(leverPush)) e->passedPawns[Us] |= s; - else if ( stoppers == SquareBB[s + Up] + else if ( relative_rank(Them, s, pos.max_rank()) > RANK_1 + && stoppers == SquareBB[s + Up] && relative_rank(Us, s, pos.max_rank()) >= RANK_5) { b = shift(supported) & ~theirPawns; diff --git a/src/position.cpp b/src/position.cpp index e54a9f1..2400b54 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -703,15 +703,15 @@ bool Position::legal(Move m) const { assert(color_of(moved_piece(m)) == us); assert(!count(us) || piece_on(square(us)) == make_piece(us, KING)); - // illegal moves to squares outside of board + // Illegal moves to squares outside of board if (!(board_bb() & to)) return false; - // illegal checks - if (!checking_permitted() && gives_check(m)) + // Illegal checks + if ((!checking_permitted() || (sittuyin_promotion() && type_of(m) == PROMOTION)) && gives_check(m)) return false; - // illegal quiet moves + // Illegal quiet moves if (must_capture() && !capture(m)) { if (checkers()) @@ -728,7 +728,7 @@ bool Position::legal(Move m) const { } } - // illegal non-drop moves + // Illegal non-drop moves if (must_drop() && type_of(m) != DROP && count_in_hand(us, ALL_PIECES)) { if (checkers()) @@ -745,7 +745,7 @@ bool Position::legal(Move m) const { } } - // illegal drop move + // Illegal drop move if (drop_opposite_colored_bishop() && type_of(m) == DROP) { if (type_of(moved_piece(m)) != BISHOP) @@ -762,11 +762,11 @@ bool Position::legal(Move m) const { return false; } - // no legal moves from target square + // No legal moves from target square if (immobility_illegal() && (type_of(m) == DROP || type_of(m) == NORMAL) && !(moves_bb(us, type_of(moved_piece(m)), to, 0) & board_bb())) return false; - // game end + // Game end if (is_variant_end()) return false; @@ -969,6 +969,11 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { Square to = to_sq(m); Piece pc = moved_piece(m); Piece captured = type_of(m) == ENPASSANT ? make_piece(them, PAWN) : piece_on(to); + if (to == from) + { + assert(type_of(m) == PROMOTION && sittuyin_promotion()); + captured = NO_PIECE; + } Piece unpromotedCaptured = unpromoted_piece_on(to); assert(color_of(pc) == us); @@ -1113,7 +1118,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { { Piece promotion = make_piece(us, promotion_type(m)); - assert(relative_rank(us, to, max_rank()) == promotion_rank()); + assert(relative_rank(us, to, max_rank()) == promotion_rank() || sittuyin_promotion()); assert(type_of(promotion) >= KNIGHT && type_of(promotion) < KING); remove_piece(pc, to); @@ -1213,12 +1218,12 @@ void Position::undo_move(Move m) { Square to = to_sq(m); Piece pc = piece_on(to); - assert(type_of(m) == DROP || empty(from) || type_of(m) == CASTLING); + assert(type_of(m) == DROP || empty(from) || type_of(m) == CASTLING || (type_of(m) == PROMOTION && sittuyin_promotion())); assert(type_of(st->capturedPiece) != KING); if (type_of(m) == PROMOTION) { - assert(relative_rank(us, to, max_rank()) == promotion_rank()); + assert(relative_rank(us, to, max_rank()) == promotion_rank() || sittuyin_promotion()); assert(type_of(pc) == promotion_type(m)); assert(type_of(pc) >= KNIGHT && type_of(pc) < KING); diff --git a/src/position.h b/src/position.h index 7ecd822..d362e00 100644 --- a/src/position.h +++ b/src/position.h @@ -97,6 +97,7 @@ public: const std::string piece_to_char() const; Rank promotion_rank() const; const std::set >& promotion_piece_types() const; + bool sittuyin_promotion() const; PieceType promoted_piece_type(PieceType pt) const; bool mandatory_piece_promotion() const; bool endgame_eval() const; @@ -115,6 +116,7 @@ public: bool first_rank_drops() const; bool drop_on_top() const; Bitboard drop_region(Color c) const; + bool sittuyin_rook_drop() const; bool drop_opposite_colored_bishop() const; bool immobility_illegal() const; // winning conditions @@ -303,6 +305,11 @@ inline const std::set >& Position::promotion_ return var->promotionPieceTypes; } +inline bool Position::sittuyin_promotion() const { + assert(var != nullptr); + return var->sittuyinPromotion; +} + inline PieceType Position::promoted_piece_type(PieceType pt) const { assert(var != nullptr); return var->promotedPieceType[pt]; @@ -393,6 +400,11 @@ inline Bitboard Position::drop_region(Color c) const { return c == WHITE ? var->whiteDropRegion : var->blackDropRegion; } +inline bool Position::sittuyin_rook_drop() const { + assert(var != nullptr); + return var->sittuyinRookDrop; +} + inline bool Position::drop_opposite_colored_bishop() const { assert(var != nullptr); return var->dropOppositeColoredBishop; diff --git a/src/types.h b/src/types.h index f54e913..fd7a2d0 100644 --- a/src/types.h +++ b/src/types.h @@ -572,7 +572,7 @@ constexpr PieceType dropped_piece_type(Move m) { } inline bool is_ok(Move m) { - return from_sq(m) != to_sq(m); // Catch MOVE_NULL and MOVE_NONE + return from_sq(m) != to_sq(m) || type_of(m) == PROMOTION; // Catch MOVE_NULL and MOVE_NONE } #endif // #ifndef TYPES_H_INCLUDED diff --git a/src/variant.cpp b/src/variant.cpp index aeb0432..0328584 100644 --- a/src/variant.cpp +++ b/src/variant.cpp @@ -222,6 +222,22 @@ VariantMap variants; // Global object v->castlingDroppedPiece = true; return v; } + Variant* sittuyin_variant() { + Variant* v = makruk_variant(); + v->startFen = "8/8/4pppp/pppp4/4PPPP/PPPP4/8/8[KFRRSSNNkfrrssnn] w - - 0 1"; + v->remove_piece(MET); + v->add_piece(MET, 'f'); + v->mustDrop = true; + v->pieceDrops = true; + v->capturesToHand = false; + v->whiteDropRegion = Rank1BB | Rank2BB | Rank3BB; + v->blackDropRegion = Rank8BB | Rank7BB | Rank6BB; + v->sittuyinRookDrop = true; + v->promotionRank = RANK_1; // no regular promotions + v->sittuyinPromotion = true; + v->immobilityIllegal = false; + return v; + } Variant* euroshogi_variant() { Variant* v = fairy_variant_base(); v->reset_pieces(); @@ -541,6 +557,7 @@ void VariantMap::init() { add("chessgi", chessgi_variant()); add("pocketknight", pocketknight_variant()); add("placement", placement_variant()); + add("sittuyin", sittuyin_variant()); add("euroshogi", euroshogi_variant()); add("judkinshogi", judkinsshogi_variant()); add("minishogi", minishogi_variant()); diff --git a/src/variant.h b/src/variant.h index f197460..2aed7d9 100644 --- a/src/variant.h +++ b/src/variant.h @@ -40,6 +40,7 @@ struct Variant { std::string startFen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; Rank promotionRank = RANK_8; std::set > promotionPieceTypes = { QUEEN, ROOK, BISHOP, KNIGHT }; + bool sittuyinPromotion = false; PieceType promotedPieceType[PIECE_TYPE_NB] = {}; bool mandatoryPiecePromotion = false; bool endgameEval = false; @@ -59,6 +60,7 @@ struct Variant { bool dropOnTop = false; Bitboard whiteDropRegion = AllSquares; Bitboard blackDropRegion = AllSquares; + bool sittuyinRookDrop = false; bool dropOppositeColoredBishop = false; bool immobilityIllegal = false; // game end diff --git a/tests/perft.sh b/tests/perft.sh index e84f28c..1163dd3 100755 --- a/tests/perft.sh +++ b/tests/perft.sh @@ -48,6 +48,8 @@ expect perft.exp euroshogi startpos 5 9451149 > /dev/null expect perft.exp minishogi startpos 5 533203 > /dev/null expect perft.exp horde startpos 6 5396554 > /dev/null expect perft.exp placement startpos 4 1597696 > /dev/null +expect perft.exp sittuyin startpos 3 580096 > /dev/null +expect perft.exp sittuyin "fen 8/8/6R1/s3r3/P5R1/1KP3p1/1F2kr2/8[-] b - - 0 72" 4 657824 > /dev/null rm perft.exp -- 1.7.0.4