From 8dd358b03493571f28307f4972d01c3a6498815c Mon Sep 17 00:00:00 2001 From: ianfab Date: Sat, 21 Jul 2018 17:25:57 +0200 Subject: [PATCH] Partial support for shogi variants Support minishogi and euroshogi with the limitation that the position must be sent via "position startpos moves ..." and not using an FEN. Limitations: - FENs with promoted pieces do not work. - Mates by pawn drops are not recognized to be losing. bench: 4604661 --- src/bitboard.cpp | 6 +++++ src/bitboard.h | 4 +- src/movegen.cpp | 17 +++++++++++++- src/position.cpp | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++--- src/position.h | 19 +++++++++++++++++ src/psqt.cpp | 4 +- src/types.h | 6 +++- src/uci.cpp | 3 ++ src/variant.cpp | 16 ++++++++++++- src/variant.h | 2 + 10 files changed, 123 insertions(+), 14 deletions(-) diff --git a/src/bitboard.cpp b/src/bitboard.cpp index 52f42fb..534e4a3 100644 --- a/src/bitboard.cpp +++ b/src/bitboard.cpp @@ -167,6 +167,7 @@ void Bitboards::init() { { 15, 17 }, // shogi knight { -1, 1, 15, 17 }, // euroshogi knight { -8, -1, 1, 7, 8, 9 }, // gold + { -8, -1, 1, 8 }, // horse { -9, -8, -7, -1, 1, 7, 8, 9 }, // commoner { -9, -8, -7, -1, 1, 7, 8, 9 } // king }; @@ -191,6 +192,7 @@ void Bitboards::init() { { 15, 17 }, // shogi knight { -1, 1, 15, 17 }, // euroshogi knight { -8, -1, 1, 7, 8, 9 }, // gold + { -8, -1, 1, 8 }, // horse { -9, -8, -7, -1, 1, 7, 8, 9 }, // commoner { -9, -8, -7, -1, 1, 7, 8, 9 } // king }; @@ -215,6 +217,7 @@ void Bitboards::init() { {}, // shogi knight {}, // euroshogi knight {}, // gold + { NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST }, // horse {}, // commoner {} // king }; @@ -239,6 +242,7 @@ void Bitboards::init() { {}, // shogi knight {}, // euroshogi knight {}, // gold + { NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST }, // horse {}, // commoner {} // king }; @@ -263,6 +267,7 @@ void Bitboards::init() { 0, // shogi knight 0, // euroshogi knight 0, // gold + 7, // horse 0, // commoner 0 // king }; @@ -287,6 +292,7 @@ void Bitboards::init() { 0, // shogi knight 0, // euroshogi knight 0, // gold + 7, // horse 0, // commoner 0 // king }; diff --git a/src/bitboard.h b/src/bitboard.h index 40a4d8f..4abfd80 100644 --- a/src/bitboard.h +++ b/src/bitboard.h @@ -230,8 +230,8 @@ inline Bitboard forward_ranks_bb(Color c, Square s) { /// in front of and on the given relative rank, from the point of view of the given color. /// For instance, promotion_zone_bb(BLACK, RANK_7) will return the 16 squares on ranks 1 and 2. -inline Bitboard promotion_zone_bb(Color c, Rank r) { - return ForwardRanksBB[c][relative_rank(c, r)] | rank_bb(relative_rank(c, r)); +inline Bitboard promotion_zone_bb(Color c, Rank r, Rank maxRank) { + return ForwardRanksBB[c][relative_rank(c, r, maxRank)] | rank_bb(relative_rank(c, r, maxRank)); } diff --git a/src/movegen.cpp b/src/movegen.cpp index 4425bd7..2a84d87 100644 --- a/src/movegen.cpp +++ b/src/movegen.cpp @@ -88,10 +88,14 @@ namespace { { if (pt == PAWN) { - b &= ~promotion_zone_bb(Us, pos.promotion_rank()); + b &= ~promotion_zone_bb(Us, pos.promotion_rank(), pos.max_rank()); if (!pos.first_rank_drops()) b &= ~rank_bb(relative_rank(Us, RANK_1, pos.max_rank())); } + if (pt == SHOGI_PAWN) + for (File f = FILE_A; f <= pos.max_file(); ++f) + if (file_bb(f) & pos.pieces(Us, pt)) + b &= ~file_bb(f); if (Checks) b &= pos.check_squares(pt); while (b) @@ -257,7 +261,16 @@ namespace { b &= pos.check_squares(pt); while (b) - *moveList++ = make_move(from, pop_lsb(&b)); + { + Square s = pop_lsb(&b); + bool piece_promotion = pos.promoted_piece_type(pt) != NO_PIECE_TYPE + && (promotion_zone_bb(us, pos.promotion_rank(), pos.max_rank()) & (SquareBB[from] | s)); + if (!piece_promotion || !pos.mandatory_piece_promotion()) + *moveList++ = make_move(from, s); + // Shogi-style piece promotions + if (piece_promotion) + *moveList++ = make(from, s); + } } return moveList; diff --git a/src/position.cpp b/src/position.cpp index 618c82a..bb54593 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -664,7 +664,7 @@ bool Position::legal(Move m) const { // illegal drops if (piece_drops() && type_of(m) == DROP) - return pieceCountInHand[us][type_of(moved_piece(m))] && empty(to_sq(m)); + return pieceCountInHand[us][type_of(moved_piece(m))] && empty(to_sq(m)) && moves_bb(us, type_of(moved_piece(m)), to, 0); // game end if (is_variant_end()) @@ -788,7 +788,7 @@ bool Position::gives_check(Move m) const { return false; // Is there a direct check? - if (st->checkSquares[type_of(moved_piece(m))] & to) + if (type_of(m) != PROMOTION && type_of(m) != PIECE_PROMOTION && (st->checkSquares[type_of(moved_piece(m))] & to)) return true; // Is there a discovered check? @@ -807,6 +807,9 @@ bool Position::gives_check(Move m) const { case PROMOTION: return attacks_bb(sideToMove, promotion_type(m), to, pieces() ^ from) & square(~sideToMove); + case PIECE_PROMOTION: + return attacks_bb(sideToMove, promoted_piece_type(type_of(moved_piece(m))), 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 @@ -866,6 +869,7 @@ 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); + Piece unpromotedCaptured = unpromoted_piece_on(to); assert(color_of(pc) == us); assert(captured == NO_PIECE || color_of(captured) == (type_of(m) != CASTLING ? them : us)); @@ -922,13 +926,16 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { if (captures_to_hand()) { st->capturedpromoted = is_promoted(to); - Piece pieceToHand = is_promoted(to) ? make_piece(~color_of(captured), PAWN) : ~captured; + Piece pieceToHand = !is_promoted(to) ? ~captured + : unpromotedCaptured ? ~unpromotedCaptured + : make_piece(~color_of(captured), PAWN); add_to_hand(color_of(pieceToHand), type_of(pieceToHand)); st->psq += PSQT::psq[pieceToHand][SQ_NONE]; k ^= Zobrist::inHand[pieceToHand][pieceCountInHand[color_of(pieceToHand)][type_of(pieceToHand)] - 1] ^ Zobrist::inHand[pieceToHand][pieceCountInHand[color_of(pieceToHand)][type_of(pieceToHand)]]; promotedPieces -= to; } + unpromotedBoard[to] = NO_PIECE; // Update material hash key and prefetch access to materialTable k ^= Zobrist::psq[captured][capsq]; @@ -1020,12 +1027,33 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { // Reset rule 50 draw counter st->rule50 = 0; } + else if (type_of(m) == PIECE_PROMOTION) + { + Piece promotion = make_piece(us, promoted_piece_type(type_of(pc))); + + remove_piece(pc, to); + put_piece(promotion, to); + promotedPieces |= to; + unpromotedBoard[to] = pc; + + // Update hash keys + k ^= Zobrist::psq[pc][to] ^ Zobrist::psq[promotion][to]; + st->materialKey ^= Zobrist::psq[promotion][pieceCount[promotion]-1] + ^ Zobrist::psq[pc][pieceCount[pc]]; + + // Update incremental score + st->psq += PSQT::psq[promotion][to] - PSQT::psq[pc][to]; + + // Update material + st->nonPawnMaterial[us] += PieceValue[MG][promotion] - PieceValue[MG][pc]; + } // Update incremental scores st->psq += PSQT::psq[pc][to] - PSQT::psq[pc][from]; // Set capture piece st->capturedPiece = captured; + st->unpromotedCapturedPiece = captured ? unpromotedCaptured : NO_PIECE; if (captures_to_hand() && !captured) st->capturedpromoted = false; @@ -1035,9 +1063,16 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { // Calculate checkers bitboard (if move gives check) st->checkersBB = givesCheck ? attackers_to(square(them)) & pieces(us) : 0; + // Update information about promoted pieces if (type_of(m) != DROP && is_promoted(from)) promotedPieces = (promotedPieces - from) | to; + if (type_of(m) != DROP && unpromoted_piece_on(from)) + { + unpromotedBoard[to] = unpromotedBoard[from]; + unpromotedBoard[from] = NO_PIECE; + } + sideToMove = ~sideToMove; // Update king attacks used for fast check detection @@ -1076,6 +1111,14 @@ void Position::undo_move(Move m) { if (captures_to_hand() && !drop_loop()) promotedPieces -= to; } + else if (type_of(m) == PIECE_PROMOTION) + { + remove_piece(pc, to); + pc = unpromoted_piece_on(to); + put_piece(pc, to); + unpromotedBoard[to] = NO_PIECE; + promotedPieces -= to; + } if (type_of(m) == CASTLING) { @@ -1090,6 +1133,11 @@ void Position::undo_move(Move m) { 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; + if (unpromoted_piece_on(to)) + { + unpromotedBoard[from] = unpromotedBoard[to]; + unpromotedBoard[to] = NO_PIECE; + } if (st->capturedPiece) { @@ -1110,10 +1158,14 @@ void Position::undo_move(Move m) { if (captures_to_hand()) { remove_from_hand(~color_of(st->capturedPiece), - !drop_loop() && st->capturedpromoted ? PAWN : type_of(st->capturedPiece)); + !drop_loop() && st->capturedpromoted ? (st->unpromotedCapturedPiece ? type_of(st->unpromotedCapturedPiece) + : PAWN) + : type_of(st->capturedPiece)); if (!drop_loop() && st->capturedpromoted) promotedPieces |= to; } + if (st->unpromotedCapturedPiece) + unpromotedBoard[to] = st->unpromotedCapturedPiece; } } diff --git a/src/position.h b/src/position.h index 9adf7cd..bf53546 100644 --- a/src/position.h +++ b/src/position.h @@ -53,6 +53,7 @@ struct StateInfo { Key key; Bitboard checkersBB; Piece capturedPiece; + Piece unpromotedCapturedPiece; StateInfo* previous; Bitboard blockersForKing[COLOR_NB]; Bitboard pinners[COLOR_NB]; @@ -94,6 +95,8 @@ public: const std::string piece_to_char() const; Rank promotion_rank() const; const std::set >& promotion_piece_types() const; + PieceType promoted_piece_type(PieceType pt) const; + bool mandatory_piece_promotion() const; bool endgame_eval() const; bool double_step_enabled() const; bool castling_enabled() const; @@ -128,6 +131,7 @@ public: Bitboard pieces(Color c, PieceType pt) const; Bitboard pieces(Color c, PieceType pt1, PieceType pt2) const; Piece piece_on(Square s) const; + Piece unpromoted_piece_on(Square s) const; Square ep_square() const; bool empty(Square s) const; int count(Color c, PieceType pt) const; @@ -218,6 +222,7 @@ private: // Data members Piece board[SQUARE_NB]; + Piece unpromotedBoard[SQUARE_NB]; Bitboard byTypeBB[PIECE_TYPE_NB]; Bitboard byColorBB[COLOR_NB]; int pieceCount[PIECE_NB]; @@ -280,6 +285,16 @@ inline const std::set >& Position::promotion_ return var->promotionPieceTypes; } +inline PieceType Position::promoted_piece_type(PieceType pt) const { + assert(var != nullptr); + return var->promotedPieceType[pt]; +} + +inline bool Position::mandatory_piece_promotion() const { + assert(var != nullptr); + return var->mandatoryPiecePromotion; +} + inline bool Position::endgame_eval() const { assert(var != nullptr); return var->endgameEval; @@ -449,6 +464,10 @@ inline Piece Position::piece_on(Square s) const { return board[s]; } +inline Piece Position::unpromoted_piece_on(Square s) const { + return unpromotedBoard[s]; +} + inline Piece Position::moved_piece(Move m) const { if (type_of(m) == DROP) return make_piece(sideToMove, dropped_piece_type(m)); diff --git a/src/psqt.cpp b/src/psqt.cpp index ae83b84..04c6919 100644 --- a/src/psqt.cpp +++ b/src/psqt.cpp @@ -26,12 +26,12 @@ Value PieceValue[PHASE_NB][PIECE_NB] = { { VALUE_ZERO, PawnValueMg, KnightValueMg, BishopValueMg, RookValueMg, QueenValueMg, FersValueMg, AlfilValueMg, SilverValueMg, AiwokValueMg, BersValueMg, ChancellorValueMg, AmazonValueMg, KnibisValueMg, BiskniValueMg, - ShogiPawnValueMg, LanceValueMg, ShogiKnightValueMg, EuroShogiKnightValueMg, GoldValueMg, + ShogiPawnValueMg, LanceValueMg, ShogiKnightValueMg, EuroShogiKnightValueMg, GoldValueMg, HorseValueMg, CommonerValueMg }, { VALUE_ZERO, PawnValueEg, KnightValueEg, BishopValueEg, RookValueEg, QueenValueEg, FersValueEg, AlfilValueEg, SilverValueEg, AiwokValueEg, BersValueEg, ChancellorValueEg, AmazonValueEg, KnibisValueMg, BiskniValueMg, - ShogiPawnValueEg, LanceValueEg, ShogiKnightValueEg, EuroShogiKnightValueEg, GoldValueEg, + ShogiPawnValueEg, LanceValueEg, ShogiKnightValueEg, EuroShogiKnightValueEg, GoldValueEg, HorseValueEg, CommonerValueEg } }; diff --git a/src/types.h b/src/types.h index 93868ea..f127211 100644 --- a/src/types.h +++ b/src/types.h @@ -129,6 +129,7 @@ enum MoveType { PROMOTION_LEFT = 4 << 12, PROMOTION_RIGHT = 5 << 12, DROP = 6 << 12, + PIECE_PROMOTION = 7 << 12, }; enum Color { @@ -199,7 +200,7 @@ enum Value : int { AlfilValueMg = 300, AlfilValueEg = 300, SilverValueMg = 600, SilverValueEg = 600, AiwokValueMg = 2500, AiwokValueEg = 2500, - BersValueMg = 2500, BersValueEg = 2500, + BersValueMg = 2000, BersValueEg = 2000, ChancellorValueMg = 2500, ChancellorValueEg = 2500, AmazonValueMg = 3000, AmazonValueEg = 3000, KnibisValueMg = 800, KnibisValueEg = 800, @@ -209,6 +210,7 @@ enum Value : int { ShogiKnightValueMg = 300, ShogiKnightValueEg = 300, EuroShogiKnightValueMg = 400, EuroShogiKnightValueEg = 400, GoldValueMg = 600, GoldValueEg = 600, + HorseValueMg = 1500, HorseValueEg = 1500, CommonerValueMg = 600, CommonerValueEg = 600, MidgameLimit = 15258, EndgameLimit = 3915 @@ -219,7 +221,7 @@ const int PIECE_TYPE_BITS = 5; // PIECE_TYPE_NB = pow(2, PIECE_TYPE_BITS) enum PieceType { NO_PIECE_TYPE, PAWN, KNIGHT, BISHOP, ROOK, QUEEN, FERS, MET = FERS, ALFIL, SILVER, KHON = SILVER, AIWOK, BERS, DRAGON = BERS, CHANCELLOR, - AMAZON, KNIBIS, BISKNI, SHOGI_PAWN, LANCE, SHOGI_KNIGHT, EUROSHOGI_KNIGHT, GOLD, COMMONER, KING, + AMAZON, KNIBIS, BISKNI, SHOGI_PAWN, LANCE, SHOGI_KNIGHT, EUROSHOGI_KNIGHT, GOLD, HORSE, COMMONER, KING, ALL_PIECES = 0, PIECE_TYPE_NB = 1 << PIECE_TYPE_BITS diff --git a/src/uci.cpp b/src/uci.cpp index 1692705..e5c5f20 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -294,6 +294,9 @@ string UCI::move(const Position& pos, Move m) { if (type_of(m) == PROMOTION) move += pos.piece_to_char()[make_piece(BLACK, promotion_type(m))]; + if (type_of(m) == PIECE_PROMOTION) + move += '+'; + return move; } diff --git a/src/variant.cpp b/src/variant.cpp index f1ade24..87be444 100644 --- a/src/variant.cpp +++ b/src/variant.cpp @@ -230,8 +230,10 @@ void VariantMap::init() { v->add_piece(EUROSHOGI_KNIGHT, 'n'); v->add_piece(GOLD, 'g'); v->add_piece(BISHOP, 'b'); + v->add_piece(HORSE, 'h'); v->add_piece(ROOK, 'r'); v->add_piece(KING, 'k'); + v->add_piece(DRAGON, 'd'); v->startFen = "1nbgkgn1/1r4b1/pppppppp/8/8/PPPPPPPP/1B4R1/1NGKGBN1[-] w 0 1"; v->pieceDrops = true; v->capturesToHand = true; @@ -239,7 +241,12 @@ void VariantMap::init() { v->promotionPieceTypes = {}; v->doubleStep = false; v->castling = false; - // TODO: piece promotions, illegal pawn drops + v->promotedPieceType[SHOGI_PAWN] = GOLD; + v->promotedPieceType[EUROSHOGI_KNIGHT] = GOLD; + v->promotedPieceType[SILVER] = GOLD; + v->promotedPieceType[BISHOP] = HORSE; + v->promotedPieceType[ROOK] = DRAGON; + v->mandatoryPiecePromotion = true; return v; } (); const Variant* minishogi = [&]{ @@ -251,7 +258,9 @@ void VariantMap::init() { v->add_piece(SILVER, 's'); v->add_piece(GOLD, 'g'); v->add_piece(BISHOP, 'b'); + v->add_piece(HORSE, 'h'); v->add_piece(ROOK, 'r'); + v->add_piece(DRAGON, 'd'); v->add_piece(KING, 'k'); v->startFen = "rbsgk/4p/5/P4/KGSBR[-] w 0 1"; v->pieceDrops = true; @@ -260,7 +269,10 @@ void VariantMap::init() { v->promotionPieceTypes = {}; v->doubleStep = false; v->castling = false; - // TODO: piece promotions, illegal pawn drops + v->promotedPieceType[SHOGI_PAWN] = GOLD; + v->promotedPieceType[SILVER] = GOLD; + v->promotedPieceType[BISHOP] = HORSE; + v->promotedPieceType[ROOK] = DRAGON; return v; } (); const Variant* losalamos = [&]{ diff --git a/src/variant.h b/src/variant.h index 8af0c54..4f751d0 100644 --- a/src/variant.h +++ b/src/variant.h @@ -42,6 +42,8 @@ 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 }; + PieceType promotedPieceType[PIECE_TYPE_NB] = {}; + bool mandatoryPiecePromotion = false; bool endgameEval = false; bool doubleStep = true; bool castling = true; -- 1.7.0.4