From: Fabian Fichter Date: Sun, 23 Dec 2018 13:45:46 +0000 (+0100) Subject: Support kyoto shogi X-Git-Url: http://winboard.nl/cgi-bin?a=commitdiff_plain;h=f0d0829f5f88895a457db964d43484ca60a5a0f3;p=fairystockfish.git Support kyoto shogi https://en.wikipedia.org/wiki/Kyoto_shogi Added features: - Piece demotion - Dropping pieces in promoted state Repeating unchanged bench here to fix CI. bench: 5141418 --- diff --git a/src/movegen.cpp b/src/movegen.cpp index 296105b..98fc09d 100644 --- a/src/movegen.cpp +++ b/src/movegen.cpp @@ -97,10 +97,20 @@ namespace { b &= ~file_bb(f); if (pt == ROOK && pos.sittuyin_rook_drop()) b &= rank_bb(relative_rank(Us, RANK_1, pos.max_rank())); + + // Add to move list + if (pos.drop_promoted() && pos.promoted_piece_type(pt)) + { + Bitboard b2 = b; + if (Checks) + b2 &= pos.check_squares(pos.promoted_piece_type(pt)); + while (b2) + *moveList++ = make_drop(pop_lsb(&b2), pt, pos.promoted_piece_type(pt)); + } if (Checks) b &= pos.check_squares(pt); while (b) - *moveList++ = make_drop(pop_lsb(&b), pt); + *moveList++ = make_drop(pop_lsb(&b), pt, pt); } return moveList; @@ -287,24 +297,29 @@ namespace { Bitboard b1 = ( (pos.attacks_from(us, pt, from) & pos.pieces()) | (pos.moves_from(us, pt, from) & ~pos.pieces())) & target; - PieceType pt_promotion = pos.promoted_piece_type(pt); - Bitboard b2 = pt_promotion ? b1 : 0; + Bitboard b2 = pos.promoted_piece_type(pt) ? b1 : 0; + Bitboard b3 = pos.piece_demotion() && pos.is_promoted(from) ? b1 : 0; if (Checks) { b1 &= pos.check_squares(pt); - if (pt_promotion) - b2 &= pos.check_squares(pt_promotion); + if (b2) + b2 &= pos.check_squares(pos.promoted_piece_type(pt)); + if (b3) + b3 &= pos.check_squares(type_of(pos.unpromoted_piece_on(from))); } // Restrict target squares considering promotion zone - if (pt_promotion) + if (b2 | b3) { Bitboard promotion_zone = promotion_zone_bb(us, pos.promotion_rank(), pos.max_rank()); if (pos.mandatory_piece_promotion()) b1 &= promotion_zone & from ? 0 : ~promotion_zone; if (!(promotion_zone & from)) + { b2 &= promotion_zone; + b3 &= promotion_zone; + } } while (b1) @@ -313,6 +328,10 @@ namespace { // Shogi-style piece promotions while (b2) *moveList++ = make(from, pop_lsb(&b2)); + + // Piece demotions + while (b3) + *moveList++ = make(from, pop_lsb(&b3)); } return moveList; diff --git a/src/position.cpp b/src/position.cpp index bed440e..e5828ee 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -813,6 +813,12 @@ bool Position::pseudo_legal(const Move m) const { if (type_of(m) != NORMAL) return MoveList(*this).contains(m); + // 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) + && (promotion_zone_bb(us, promotion_rank(), max_rank()) & (SquareBB[from] | to))) + return false; + // Is not a promotion, so promotion piece must be empty if (promotion_type(m) != NO_PIECE_TYPE) return false; @@ -888,7 +894,7 @@ bool Position::gives_check(Move m) const { return false; // Is there a direct check? - if (type_of(m) != PROMOTION && type_of(m) != PIECE_PROMOTION && (st->checkSquares[type_of(moved_piece(m))] & to)) + if (type_of(m) != PROMOTION && type_of(m) != PIECE_PROMOTION && type_of(m) != PIECE_DEMOTION && (st->checkSquares[type_of(moved_piece(m))] & to)) return true; // Is there a discovered check? @@ -909,6 +915,9 @@ bool Position::gives_check(Move m) const { case PIECE_PROMOTION: return attacks_bb(sideToMove, promoted_piece_type(type_of(moved_piece(m))), to, pieces() ^ from) & square(~sideToMove); + case PIECE_DEMOTION: + return attacks_bb(sideToMove, type_of(unpromoted_piece_on(from)), to, pieces() ^ from) & square(~sideToMove); + // En passant capture with check? We have already handled the case // of direct checks and ordinary discovered check, so the only case we // need to handle is the unusual case of a discovered check through @@ -1051,9 +1060,12 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { // Update hash key if (type_of(m) == DROP) + { + Piece pc_hand = make_piece(us, in_hand_piece_type(m)); k ^= Zobrist::psq[pc][to] - ^ Zobrist::inHand[pc][pieceCountInHand[color_of(pc)][type_of(pc)] - 1] - ^ Zobrist::inHand[pc][pieceCountInHand[color_of(pc)][type_of(pc)]]; + ^ Zobrist::inHand[pc_hand][pieceCountInHand[color_of(pc_hand)][type_of(pc_hand)] - 1] + ^ Zobrist::inHand[pc_hand][pieceCountInHand[color_of(pc_hand)][type_of(pc_hand)]]; + } else k ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to]; @@ -1075,7 +1087,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { // Move the piece. The tricky Chess960 castling is handled earlier if (type_of(m) == DROP) { - drop_piece(pc, to); + drop_piece(make_piece(us, in_hand_piece_type(m)), pc, to); st->materialKey ^= Zobrist::psq[pc][pieceCount[pc]-1]; if (type_of(pc) != PAWN) st->nonPawnMaterial[us] += PieceValue[MG][pc]; @@ -1168,6 +1180,26 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { // Update material st->nonPawnMaterial[us] += PieceValue[MG][promotion] - PieceValue[MG][pc]; } + else if (type_of(m) == PIECE_DEMOTION) + { + Piece demotion = unpromoted_piece_on(from); + + remove_piece(pc, to); + put_piece(demotion, to); + promotedPieces ^= from; + unpromotedBoard[from] = NO_PIECE; + + // Update hash keys + k ^= Zobrist::psq[pc][to] ^ Zobrist::psq[demotion][to]; + st->materialKey ^= Zobrist::psq[demotion][pieceCount[demotion]-1] + ^ Zobrist::psq[pc][pieceCount[pc]]; + + // Update incremental score + st->psq += PSQT::psq[demotion][to] - PSQT::psq[pc][to]; + + // Update material + st->nonPawnMaterial[us] += PieceValue[MG][demotion] - PieceValue[MG][pc]; + } // Update incremental scores st->psq += PSQT::psq[pc][to] - PSQT::psq[pc][from]; @@ -1187,12 +1219,16 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { // Update information about promoted pieces if (type_of(m) != DROP && is_promoted(from)) promotedPieces = (promotedPieces - from) | to; + else if (type_of(m) == DROP && in_hand_piece_type(m) != dropped_piece_type(m)) + promotedPieces = promotedPieces | to; if (type_of(m) != DROP && unpromoted_piece_on(from)) { unpromotedBoard[to] = unpromotedBoard[from]; unpromotedBoard[from] = NO_PIECE; } + else if (type_of(m) == DROP && in_hand_piece_type(m) != dropped_piece_type(m)) + unpromotedBoard[to] = make_piece(us, in_hand_piece_type(m)); sideToMove = ~sideToMove; @@ -1240,6 +1276,14 @@ void Position::undo_move(Move m) { unpromotedBoard[to] = NO_PIECE; promotedPieces -= to; } + else if (type_of(m) == PIECE_DEMOTION) + { + remove_piece(pc, to); + unpromotedBoard[from] = pc; + pc = make_piece(us, promoted_piece_type(type_of(pc))); + put_piece(pc, to); + promotedPieces |= from; + } if (type_of(m) == CASTLING) { @@ -1249,14 +1293,19 @@ void Position::undo_move(Move m) { else { if (type_of(m) == DROP) - undrop_piece(pc, to); // Remove the dropped piece + undrop_piece(make_piece(us, in_hand_piece_type(m)), pc, to); // Remove the dropped piece else move_piece(pc, to, from); // Put the piece back at the source square if (captures_to_hand() && !drop_loop() && is_promoted(to)) - promotedPieces = (promotedPieces - to) | from; + { + promotedPieces = (promotedPieces - to); + if (type_of(m) != DROP) + promotedPieces |= from; + } if (unpromoted_piece_on(to)) { - unpromotedBoard[from] = unpromotedBoard[to]; + if (type_of(m) != DROP) + unpromotedBoard[from] = unpromotedBoard[to]; unpromotedBoard[to] = NO_PIECE; } @@ -1382,8 +1431,11 @@ Key Position::key_after(Move m) const { } } if (type_of(m) == DROP) - return k ^ Zobrist::psq[pc][to] ^ Zobrist::inHand[pc][pieceCountInHand[color_of(pc)][type_of(pc)]] - ^ Zobrist::inHand[pc][pieceCountInHand[color_of(pc)][type_of(pc)] - 1]; + { + Piece pc_hand = make_piece(sideToMove, in_hand_piece_type(m)); + return k ^ Zobrist::psq[pc][to] ^ Zobrist::inHand[pc_hand][pieceCountInHand[color_of(pc_hand)][type_of(pc_hand)]] + ^ Zobrist::inHand[pc_hand][pieceCountInHand[color_of(pc_hand)][type_of(pc_hand)] - 1]; + } return k ^ Zobrist::psq[pc][to] ^ Zobrist::psq[pc][from]; } diff --git a/src/position.h b/src/position.h index 350b9c9..c9163fb 100644 --- a/src/position.h +++ b/src/position.h @@ -100,6 +100,7 @@ public: bool sittuyin_promotion() const; PieceType promoted_piece_type(PieceType pt) const; bool mandatory_piece_promotion() const; + bool piece_demotion() const; bool endgame_eval() const; bool double_step_enabled() const; bool first_rank_double_steps() const; @@ -118,6 +119,7 @@ public: Bitboard drop_region(Color c) const; bool sittuyin_rook_drop() const; bool drop_opposite_colored_bishop() const; + bool drop_promoted() const; bool shogi_doubled_pawn() const; bool immobility_illegal() const; // winning conditions @@ -191,6 +193,7 @@ public: // Piece specific bool pawn_passed(Color c, Square s) const; bool opposite_bishops() const; + bool is_promoted(Square s) const; // Doing and undoing moves void do_move(Move m, StateInfo& newSt); @@ -259,11 +262,10 @@ private: bool chess960; int pieceCountInHand[COLOR_NB][PIECE_TYPE_NB]; Bitboard promotedPieces; - bool is_promoted(Square s) const; void add_to_hand(Color c, PieceType pt); void remove_from_hand(Color c, PieceType pt); - void drop_piece(Piece pc, Square s); - void undrop_piece(Piece pc, Square s); + void drop_piece(Piece pc_hand, Piece pc_drop, Square s); + void undrop_piece(Piece pc_hand, Piece pc_drop, Square s); }; extern std::ostream& operator<<(std::ostream& os, const Position& pos); @@ -323,6 +325,11 @@ inline bool Position::mandatory_piece_promotion() const { return var->mandatoryPiecePromotion; } +inline bool Position::piece_demotion() const { + assert(var != nullptr); + return var->pieceDemotion; +} + inline bool Position::endgame_eval() const { assert(var != nullptr); return var->endgameEval; @@ -413,6 +420,11 @@ inline bool Position::drop_opposite_colored_bishop() const { return var->dropOppositeColoredBishop; } +inline bool Position::drop_promoted() const { + assert(var != nullptr); + return var->dropPromoted; +} + inline bool Position::shogi_doubled_pawn() const { assert(var != nullptr); return var->shogiDoubledPawn; @@ -757,6 +769,10 @@ inline bool Position::opposite_bishops() const { && opposite_colors(square(WHITE), square(BLACK)); } +inline bool Position::is_promoted(Square s) const { + return promotedPieces & s; +} + inline bool Position::is_chess960() const { return chess960; } @@ -840,21 +856,17 @@ inline void Position::remove_from_hand(Color c, PieceType pt) { pieceCountInHand[c][ALL_PIECES]--; } -inline bool Position::is_promoted(Square s) const { - return promotedPieces & s; -} - -inline void Position::drop_piece(Piece pc, Square s) { - assert(pieceCountInHand[color_of(pc)][type_of(pc)]); - put_piece(pc, s); - remove_from_hand(color_of(pc), type_of(pc)); +inline void Position::drop_piece(Piece pc_hand, Piece pc_drop, Square s) { + assert(pieceCountInHand[color_of(pc_hand)][type_of(pc_hand)]); + put_piece(pc_drop, s); + remove_from_hand(color_of(pc_hand), type_of(pc_hand)); } -inline void Position::undrop_piece(Piece pc, Square s) { - remove_piece(pc, s); +inline void Position::undrop_piece(Piece pc_hand, Piece pc_drop, Square s) { + remove_piece(pc_drop, s); board[s] = NO_PIECE; - add_to_hand(color_of(pc), type_of(pc)); - assert(pieceCountInHand[color_of(pc)][type_of(pc)]); + add_to_hand(color_of(pc_hand), type_of(pc_hand)); + assert(pieceCountInHand[color_of(pc_hand)][type_of(pc_hand)]); } #endif // #ifndef POSITION_H_INCLUDED diff --git a/src/types.h b/src/types.h index fd7a2d0..2a63e68 100644 --- a/src/types.h +++ b/src/types.h @@ -137,8 +137,11 @@ enum MoveType : int { PROMOTION = 3 << (2 * SQUARE_BITS), DROP = 4 << (2 * SQUARE_BITS), PIECE_PROMOTION = 5 << (2 * SQUARE_BITS), + PIECE_DEMOTION = 6 << (2 * SQUARE_BITS), }; +constexpr int MOVE_TYPE_BITS = 4; + enum Color { WHITE, BLACK, COLOR_NB = 2 }; @@ -228,7 +231,7 @@ enum Value : int { MidgameLimit = 15258, EndgameLimit = 3915 }; -const int PIECE_TYPE_BITS = 5; // PIECE_TYPE_NB = pow(2, PIECE_TYPE_BITS) +constexpr int PIECE_TYPE_BITS = 5; // PIECE_TYPE_NB = pow(2, PIECE_TYPE_BITS) enum PieceType { NO_PIECE_TYPE, PAWN, KNIGHT, BISHOP, ROOK, QUEEN, @@ -244,6 +247,8 @@ static_assert(KING < PIECE_TYPE_NB, "KING exceeds PIECE_TYPE_NB."); static_assert(PIECE_TYPE_BITS <= 6, "PIECE_TYPE uses more than 6 bit"); static_assert(!(PIECE_TYPE_NB & (PIECE_TYPE_NB - 1)), "PIECE_TYPE_NB is not a power of 2"); +static_assert(2 * SQUARE_BITS + MOVE_TYPE_BITS + 2 * PIECE_TYPE_BITS <= 32, "Move encoding uses more than 32 bits"); + enum Piece { NO_PIECE, PIECE_NB = 2 * PIECE_TYPE_NB @@ -551,7 +556,7 @@ inline int from_to(Move m) { } inline PieceType promotion_type(Move m) { - return type_of(m) == PROMOTION ? PieceType((m >> (2 * SQUARE_BITS + 4)) & (PIECE_TYPE_NB - 1)) : NO_PIECE_TYPE; + return type_of(m) == PROMOTION ? PieceType((m >> (2 * SQUARE_BITS + MOVE_TYPE_BITS)) & (PIECE_TYPE_NB - 1)) : NO_PIECE_TYPE; } inline Move make_move(Square from, Square to) { @@ -560,15 +565,19 @@ inline Move make_move(Square from, Square to) { template inline Move make(Square from, Square to, PieceType pt = NO_PIECE_TYPE) { - return Move((pt << (2 * SQUARE_BITS + 4)) + T + (from << SQUARE_BITS) + to); + return Move((pt << (2 * SQUARE_BITS + MOVE_TYPE_BITS)) + T + (from << SQUARE_BITS) + to); } -constexpr Move make_drop(Square to, PieceType pt) { - return Move((pt << (2 * SQUARE_BITS + 4)) + DROP + to); +constexpr Move make_drop(Square to, PieceType pt_in_hand, PieceType pt_dropped) { + return Move((pt_in_hand << (2 * SQUARE_BITS + MOVE_TYPE_BITS + PIECE_TYPE_BITS)) + (pt_dropped << (2 * SQUARE_BITS + MOVE_TYPE_BITS)) + DROP + to); } constexpr PieceType dropped_piece_type(Move m) { - return PieceType((m >> (2 * SQUARE_BITS + 4)) & (PIECE_TYPE_NB - 1)); + return PieceType((m >> (2 * SQUARE_BITS + MOVE_TYPE_BITS)) & (PIECE_TYPE_NB - 1)); +} + +constexpr PieceType in_hand_piece_type(Move m) { + return PieceType((m >> (2 * SQUARE_BITS + MOVE_TYPE_BITS + PIECE_TYPE_BITS)) & (PIECE_TYPE_NB - 1)); } inline bool is_ok(Move m) { diff --git a/src/uci.cpp b/src/uci.cpp index e1a9238..97a9639 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -289,6 +289,17 @@ std::string UCI::square(const Position& pos, Square s) { #endif } +/// UCI::dropped_piece() generates a piece label string from a Move. + +string UCI::dropped_piece(const Position& pos, Move m) { + assert(type_of(m) == DROP); + if (dropped_piece_type(m) == pos.promoted_piece_type(in_hand_piece_type(m))) + // Dropping as promoted piece + return std::string{'+', pos.piece_to_char()[in_hand_piece_type(m)]}; + else + return std::string{pos.piece_to_char()[dropped_piece_type(m)]}; +} + /// UCI::move() converts a Move to a string in coordinate notation (g1f3, a7a8q). /// The only special case is castling, where we print in the e1g1 notation in @@ -309,15 +320,15 @@ string UCI::move(const Position& pos, Move m) { if (type_of(m) == CASTLING && !pos.is_chess960()) to = make_square(to > from ? pos.castling_kingside_file() : pos.castling_queenside_file(), rank_of(from)); - string move = (type_of(m) == DROP ? std::string{pos.piece_to_char()[type_of(pos.moved_piece(m))], - Options["Protocol"] == "usi" ? '*' : '@'} + string move = (type_of(m) == DROP ? UCI::dropped_piece(pos, m) + (Options["Protocol"] == "usi" ? '*' : '@') : UCI::square(pos, from)) + UCI::square(pos, to); if (type_of(m) == PROMOTION) move += pos.piece_to_char()[make_piece(BLACK, promotion_type(m))]; - - if (type_of(m) == PIECE_PROMOTION) + else if (type_of(m) == PIECE_PROMOTION) move += '+'; + else if (type_of(m) == PIECE_DEMOTION) + move += '-'; return move; } diff --git a/src/uci.h b/src/uci.h index 408d191..553c350 100644 --- a/src/uci.h +++ b/src/uci.h @@ -73,6 +73,7 @@ void init(OptionsMap&); void loop(int argc, char* argv[]); std::string value(Value v); std::string square(const Position& pos, Square s); +std::string dropped_piece(const Position& pos, Move m); std::string move(const Position& pos, Move m); std::string pv(const Position& pos, Depth depth, Value alpha, Value beta); Move to_move(const Position& pos, std::string& str); diff --git a/src/variant.cpp b/src/variant.cpp index d90e2e0..5e2eb40 100644 --- a/src/variant.cpp +++ b/src/variant.cpp @@ -268,6 +268,27 @@ VariantMap variants; // Global object v->shogiPawnDropMateIllegal = true; return v; } + Variant* kyotoshogi_variant() { + Variant* v = minishogi_variant(); + v->add_piece(LANCE, 'l'); + v->add_piece(SHOGI_KNIGHT, 'n'); + v->startFen = "p+nks+l/5/5/5/+LSK+NP[-] w 0 1"; + v->promotionRank = RANK_1; + v->mandatoryPiecePromotion = true; + v->pieceDemotion = true; + v->dropPromoted = true; + v->promotedPieceType[LANCE] = GOLD; + v->promotedPieceType[SILVER] = BISHOP; + v->promotedPieceType[SHOGI_KNIGHT] = GOLD; + v->promotedPieceType[SHOGI_PAWN] = ROOK; + v->promotedPieceType[GOLD] = NO_PIECE_TYPE; + v->promotedPieceType[BISHOP] = NO_PIECE_TYPE; + v->promotedPieceType[ROOK] = NO_PIECE_TYPE; + v->immobilityIllegal = false; + v->shogiPawnDropMateIllegal = false; + v->shogiDoubledPawn = true; + return v; + } Variant* dobutsu_variant() { Variant* v = minishogi_variant(); v->maxRank = RANK_4; @@ -534,6 +555,7 @@ void VariantMap::init() { add("placement", placement_variant()); add("sittuyin", sittuyin_variant()); add("minishogi", minishogi_variant()); + add("kyotoshogi", kyotoshogi_variant()); add("dobutsu", dobutsu_variant()); add("gorogoro", gorogoroshogi_variant()); add("judkinshogi", judkinsshogi_variant()); diff --git a/src/variant.h b/src/variant.h index 2c182a3..c2e8d4a 100644 --- a/src/variant.h +++ b/src/variant.h @@ -44,6 +44,7 @@ struct Variant { bool sittuyinPromotion = false; PieceType promotedPieceType[PIECE_TYPE_NB] = {}; bool mandatoryPiecePromotion = false; + bool pieceDemotion = false; bool endgameEval = false; bool doubleStep = true; bool firstRankDoubleSteps = false; @@ -63,6 +64,7 @@ struct Variant { Bitboard blackDropRegion = AllSquares; bool sittuyinRookDrop = false; bool dropOppositeColoredBishop = false; + bool dropPromoted = false; bool shogiDoubledPawn = true; bool immobilityIllegal = false; // game end