From: ianfab Date: Mon, 25 Jun 2018 21:26:05 +0000 (+0200) Subject: Support crazyhouse and loop chess X-Git-Url: http://winboard.nl/cgi-bin?a=commitdiff_plain;h=b3ee525195e718b0a23ff6e22b7916468260c864;p=fairystockfish.git Support crazyhouse and loop chess --- diff --git a/src/bitboard.h b/src/bitboard.h index 0691e54..40a4d8f 100644 --- a/src/bitboard.h +++ b/src/bitboard.h @@ -123,6 +123,11 @@ inline Bitboard operator^(Bitboard b, Square s) { return b ^ SquareBB[s]; } +inline Bitboard operator-(Bitboard b, Square s) { + assert(s >= SQ_A1 && s <= SQ_H8); + return b & ~SquareBB[s]; +} + inline Bitboard& operator|=(Bitboard& b, Square s) { assert(s >= SQ_A1 && s <= SQ_H8); return b |= SquareBB[s]; @@ -133,6 +138,11 @@ inline Bitboard& operator^=(Bitboard& b, Square s) { return b ^= SquareBB[s]; } +inline Bitboard& operator-=(Bitboard& b, Square s) { + assert(s >= SQ_A1 && s <= SQ_H8); + return b &= ~SquareBB[s]; +} + constexpr bool more_than_one(Bitboard b) { return b & (b - 1); } @@ -216,6 +226,15 @@ inline Bitboard forward_ranks_bb(Color c, Square s) { } +/// promotion_zone_bb() returns a bitboard representing the squares on all the ranks +/// 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)); +} + + /// forward_file_bb() returns a bitboard representing all the squares along the line /// in front of the given one, from the point of view of the given color: /// ForwardFileBB[c][s] = forward_ranks_bb(c, s) & file_bb(s) diff --git a/src/movegen.cpp b/src/movegen.cpp index 288c0cc..88f350d 100644 --- a/src/movegen.cpp +++ b/src/movegen.cpp @@ -79,6 +79,20 @@ namespace { return moveList; } + template + ExtMove* generate_drops(const Position& pos, ExtMove* moveList, PieceType pt, Bitboard b) { + if (pos.count_in_hand(Us, pt)) + { + if (pt == PAWN) + b &= ~(promotion_zone_bb(Us, pos.promotion_rank()) | rank_bb(relative_rank(Us, RANK_1))); + if (Checks) + b &= pos.check_squares(pt); + while (b) + *moveList++ = make_drop(pop_lsb(&b), pt); + } + + return moveList; + } template ExtMove* generate_pawn_moves(const Position& pos, ExtMove* moveList, Bitboard target) { @@ -251,6 +265,10 @@ namespace { moveList = generate_pawn_moves(pos, moveList, target); for (PieceType pt = PieceType(PAWN + 1); pt < KING; ++pt) moveList = generate_moves(pos, moveList, Us, pt, target); + // generate drops + if (pos.piece_drops() && Type != CAPTURES && pos.count_in_hand(Us, ALL_PIECES)) + for (PieceType pt = PAWN; pt < KING; ++pt) + moveList = generate_drops(pos, moveList, pt, target & ~pos.pieces(~Us)); if (Type != QUIET_CHECKS && Type != EVASIONS) { diff --git a/src/movepick.h b/src/movepick.h index b6e5620..30a9c2b 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -85,7 +85,7 @@ enum StatsParams { NOT_USED = 0 }; /// unsuccessful during the current search, and is used for reduction and move /// ordering decisions. It uses 2 tables (one for each color) indexed by /// the move's from and to squares, see chessprogramming.wikispaces.com/Butterfly+Boards -typedef Stats ButterflyHistory; +typedef Stats ButterflyHistory; /// CounterMoveHistory stores counter moves indexed by [piece][to] of the previous /// move, see chessprogramming.wikispaces.com/Countermove+Heuristic diff --git a/src/position.cpp b/src/position.cpp index 841b8b9..1a0d010 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -273,7 +273,22 @@ Position& Position::set(const Variant* v, const string& fenStr, bool isChess960, put_piece(Piece(idx), sq); ++sq; } + // Set flag for promoted pieces + else if (piece_drops() && !drop_loop() && token == '~') + promotedPieces |= SquareBB[sq - 1]; + // Stop before pieces in hand + else if (token == '[') + break; } + // Pieces in hand + if (!isspace(token)) + while ((ss >> token) && !isspace(token)) + { + if (token == ']') + continue; + else if ((idx = piece_to_char().find(token)) != string::npos) + add_to_hand(color_of(Piece(idx)), type_of(Piece(idx))); + } // 2. Active color ss >> token; @@ -506,7 +521,13 @@ const string Position::fen() const { ss << emptyCnt; if (f <= FILE_H) + { ss << piece_to_char()[piece_on(make_square(f, r))]; + + // Set promoted pieces + if (piece_drops() && is_promoted(make_square(f, r))) + ss << "~"; + } } if (r > RANK_1) @@ -602,6 +623,7 @@ Bitboard Position::attackers_to(Square s, Bitboard occupied) const { bool Position::legal(Move m) const { assert(is_ok(m)); + assert(type_of(m) != DROP || piece_drops()); Color us = sideToMove; Square from = from_sq(m); @@ -632,6 +654,10 @@ 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)); + // game end if (is_variant_end()) return false; @@ -655,11 +681,12 @@ bool Position::legal(Move m) const { // If the moving piece is a king, check whether the destination // square is attacked by the opponent. Castling moves are checked // for legality during move generation. - if (type_of(piece_on(from)) == KING) + if (type_of(moved_piece(m)) == KING) return type_of(m) == CASTLING || !(attackers_to(to) & pieces(~us)); // A non-king move is legal if the king is not under attack after the move. - return !(attackers_to(ksq, (pieces() ^ from) | to) & pieces(~us) & ~SquareBB[to]); + return !( attackers_to(ksq, (type_of(m) != DROP ? pieces() ^ from : pieces()) | to) + & pieces(~us) & ~SquareBB[to]); } @@ -748,11 +775,12 @@ bool Position::gives_check(Move m) const { Square to = to_sq(m); // Is there a direct check? - if (st->checkSquares[type_of(piece_on(from))] & to) + if (st->checkSquares[type_of(moved_piece(m))] & to) return true; // Is there a discovered check? - if ( (st->blockersForKing[~sideToMove] & from) + if ( type_of(m) != DROP + && (st->blockersForKing[~sideToMove] & from) && ( !aligned(from, to, square(~sideToMove)) || (attackers_to(square(~sideToMove), (pieces() ^ from) | to) & pieces(sideToMove)))) return true; @@ -760,6 +788,7 @@ bool Position::gives_check(Move m) const { switch (type_of(m)) { case NORMAL: + case DROP: return false; case PROMOTION: @@ -822,7 +851,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { Color them = ~us; Square from = from_sq(m); Square to = to_sq(m); - Piece pc = piece_on(from); + Piece pc = moved_piece(m); Piece captured = type_of(m) == ENPASSANT ? make_piece(them, PAWN) : piece_on(to); assert(color_of(pc) == us); @@ -869,10 +898,24 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { st->pawnKey ^= Zobrist::psq[captured][capsq]; } else + { st->nonPawnMaterial[them] -= PieceValue[MG][captured]; + if (piece_drops() && !is_promoted(to)) + st->nonPawnMaterial[us] += PieceValue[MG][captured]; + } // Update board and piece lists remove_piece(captured, capsq); + if (piece_drops()) + { + st->capturedpromoted = is_promoted(to); + Piece pieceToHand = is_promoted(to) ? make_piece(~color_of(captured), PAWN) : ~captured; + 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; + } // Update material hash key and prefetch access to materialTable k ^= Zobrist::psq[captured][capsq]; @@ -887,7 +930,12 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { } // Update hash key - k ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to]; + if (type_of(m) == DROP) + 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)]]; + else + k ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to]; // Reset en passant square if (st->epSquare != SQ_NONE) @@ -897,7 +945,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { } // Update castling rights if needed - if (st->castlingRights && (castlingRightsMask[from] | castlingRightsMask[to])) + if (type_of(m) != DROP && st->castlingRights && (castlingRightsMask[from] | castlingRightsMask[to])) { int cr = castlingRightsMask[from] | castlingRightsMask[to]; k ^= Zobrist::castling[st->castlingRights & cr]; @@ -905,7 +953,12 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { } // Move the piece. The tricky Chess960 castling is handled earlier - if (type_of(m) != CASTLING) + if (type_of(m) == DROP) + { + drop_piece(pc, to); + st->materialKey ^= Zobrist::psq[pc][pieceCount[pc]-1]; + } + else if (type_of(m) != CASTLING) move_piece(pc, from, to); // If the moving piece is a pawn do some special extra work @@ -928,6 +981,8 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { remove_piece(pc, to); put_piece(promotion, to); + if (piece_drops() && !drop_loop()) + promotedPieces = promotedPieces | to; // Update hash keys k ^= Zobrist::psq[pc][to] ^ Zobrist::psq[promotion][to]; @@ -943,7 +998,10 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { } // Update pawn hash key and prefetch access to pawnsTable - st->pawnKey ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to]; + if (type_of(m) == DROP) + st->pawnKey ^= Zobrist::psq[pc][to]; + else + st->pawnKey ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to]; prefetch2(thisThread->pawnsTable[st->pawnKey]); // Reset rule 50 draw counter @@ -955,6 +1013,8 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { // Set capture piece st->capturedPiece = captured; + if (piece_drops() && !captured) + st->capturedpromoted = false; // Update the key with the final value st->key = k; @@ -962,6 +1022,9 @@ 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; + if (type_of(m) != DROP && is_promoted(from)) + promotedPieces = (promotedPieces - from) | to; + sideToMove = ~sideToMove; // Update king attacks used for fast check detection @@ -985,7 +1048,7 @@ void Position::undo_move(Move m) { Square to = to_sq(m); Piece pc = piece_on(to); - assert(empty(from) || type_of(m) == CASTLING); + assert(type_of(m) == DROP || empty(from) || type_of(m) == CASTLING); assert(type_of(st->capturedPiece) != KING); if (type_of(m) == PROMOTION) @@ -997,6 +1060,8 @@ void Position::undo_move(Move m) { remove_piece(pc, to); pc = make_piece(us, PAWN); put_piece(pc, to); + if (piece_drops() && !drop_loop()) + promotedPieces -= to; } if (type_of(m) == CASTLING) @@ -1006,7 +1071,12 @@ void Position::undo_move(Move m) { } else { - move_piece(pc, to, from); // Put the piece back at the source square + if (type_of(m) == DROP) + undrop_piece(pc, to); // Remove the dropped piece + else + move_piece(pc, to, from); // Put the piece back at the source square + if (piece_drops() && !drop_loop() && is_promoted(to)) + promotedPieces = (promotedPieces - to) | from; if (st->capturedPiece) { @@ -1024,6 +1094,13 @@ void Position::undo_move(Move m) { } put_piece(st->capturedPiece, capsq); // Restore the captured piece + if (piece_drops()) + { + remove_from_hand(~color_of(st->capturedPiece), + !drop_loop() && st->capturedpromoted ? PAWN : type_of(st->capturedPiece)); + if (!drop_loop() && st->capturedpromoted) + promotedPieces |= to; + } } } @@ -1102,12 +1179,23 @@ Key Position::key_after(Move m) const { Square from = from_sq(m); Square to = to_sq(m); - Piece pc = piece_on(from); + Piece pc = moved_piece(m); Piece captured = piece_on(to); Key k = st->key ^ Zobrist::side; if (captured) + { k ^= Zobrist::psq[captured][to]; + if (piece_drops()) + { + Piece removeFromHand = !drop_loop() && is_promoted(to) ? make_piece(~color_of(captured), PAWN) : ~captured; + k ^= Zobrist::inHand[removeFromHand][pieceCountInHand[color_of(removeFromHand)][type_of(removeFromHand)] + 1] + ^ Zobrist::inHand[removeFromHand][pieceCountInHand[color_of(removeFromHand)][type_of(removeFromHand)]]; + } + } + 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]; return k ^ Zobrist::psq[pc][to] ^ Zobrist::psq[pc][from]; } @@ -1205,7 +1293,7 @@ bool Position::is_draw(int ply) const { if (st->rule50 > 99 && (!checkers() || MoveList(*this).size())) return true; - int end = std::min(st->rule50, st->pliesFromNull); + int end = piece_drops() ? st->pliesFromNull : std::min(st->rule50, st->pliesFromNull); if (end < 4) return false; @@ -1367,15 +1455,15 @@ bool Position::pos_is_ok() const { || attackers_to(square(~sideToMove)) & pieces(sideToMove)) assert(0 && "pos_is_ok: Kings"); - if ( (pieces(PAWN) & (Rank1BB | Rank8BB)) - || pieceCount[make_piece(WHITE, PAWN)] > 8 - || pieceCount[make_piece(BLACK, PAWN)] > 8) + if ( (pieces(PAWN) & Rank8BB) + || pieceCount[make_piece(WHITE, PAWN)] > 16 + || pieceCount[make_piece(BLACK, PAWN)] > 16) assert(0 && "pos_is_ok: Pawns"); if ( (pieces(WHITE) & pieces(BLACK)) || (pieces(WHITE) | pieces(BLACK)) != pieces() - || popcount(pieces(WHITE)) > 16 - || popcount(pieces(BLACK)) > 16) + || popcount(pieces(WHITE)) > 32 + || popcount(pieces(BLACK)) > 32) assert(0 && "pos_is_ok: Bitboards"); for (PieceType p1 = PAWN; p1 <= KING; ++p1) diff --git a/src/position.h b/src/position.h index 1b8cbfe..6fc2006 100644 --- a/src/position.h +++ b/src/position.h @@ -56,6 +56,7 @@ struct StateInfo { Bitboard blockersForKing[COLOR_NB]; Bitboard pinners[COLOR_NB]; Bitboard checkSquares[PIECE_TYPE_NB]; + bool capturedpromoted; }; /// A list to keep track of the position states along the setup moves (from the @@ -94,6 +95,7 @@ public: bool checking_permitted() const; bool must_capture() const; bool piece_drops() const; + bool drop_loop() const; // winning conditions Value stalemate_value(int ply = 0) const; Value checkmate_value(int ply = 0) const; @@ -105,6 +107,9 @@ public: bool is_variant_end() const; bool is_variant_end(Value& result, int ply = 0) const; + // Variant-specific properties + int count_in_hand(Color c, PieceType pt) const; + // Position representation Bitboard pieces() const; Bitboard pieces(PieceType pt) const; @@ -206,8 +211,6 @@ private: Bitboard byColorBB[COLOR_NB]; int pieceCount[PIECE_NB]; Square pieceList[PIECE_NB][16]; - int pieceCountInHand[COLOR_NB][PIECE_TYPE_NB]; - Bitboard promotedPieces; int index[SQUARE_NB]; int castlingRightsMask[SQUARE_NB]; Square castlingRookSquare[CASTLING_RIGHT_NB]; @@ -216,8 +219,17 @@ private: Color sideToMove; Thread* thisThread; StateInfo* st; + + // variant-specific const Variant* var; 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); }; extern std::ostream& operator<<(std::ostream& os, const Position& pos); @@ -267,6 +279,11 @@ inline bool Position::piece_drops() const { return var->pieceDrops; } +inline bool Position::drop_loop() const { + assert(var != nullptr); + return var->dropLoop; +} + inline Value Position::stalemate_value(int ply) const { assert(var != nullptr); Value v = var->stalemateValue; @@ -365,6 +382,8 @@ inline Piece Position::piece_on(Square s) const { } inline Piece Position::moved_piece(Move m) const { + if (type_of(m) == DROP) + return make_piece(sideToMove, dropped_piece_type(m)); return board[from_sq(m)]; } @@ -515,7 +534,7 @@ inline bool Position::is_chess960() const { inline bool Position::capture_or_promotion(Move m) const { assert(is_ok(m)); - return type_of(m) != NORMAL ? type_of(m) != CASTLING : !empty(to_sq(m)); + return type_of(m) != NORMAL ? type_of(m) != DROP && type_of(m) != CASTLING : !empty(to_sq(m)); } inline bool Position::capture(Move m) const { @@ -578,4 +597,35 @@ inline void Position::do_move(Move m, StateInfo& newSt) { do_move(m, newSt, gives_check(m)); } +inline int Position::count_in_hand(Color c, PieceType pt) const { + return pieceCountInHand[c][pt]; +} + +inline void Position::add_to_hand(Color c, PieceType pt) { + pieceCountInHand[c][pt]++; + pieceCountInHand[c][ALL_PIECES]++; +} + +inline void Position::remove_from_hand(Color c, PieceType pt) { + pieceCountInHand[c][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::undrop_piece(Piece pc, Square s) { + remove_piece(pc, s); + board[s] = NO_PIECE; + add_to_hand(color_of(pc), type_of(pc)); + assert(pieceCountInHand[color_of(pc)][type_of(pc)]); +} + #endif // #ifndef POSITION_H_INCLUDED diff --git a/src/types.h b/src/types.h index dd9914b..074da9c 100644 --- a/src/types.h +++ b/src/types.h @@ -100,7 +100,7 @@ constexpr bool Is64Bit = false; typedef uint64_t Key; typedef uint64_t Bitboard; -constexpr int MAX_MOVES = 256; +constexpr int MAX_MOVES = 512; constexpr int MAX_PLY = 128; /// A move needs 16 bits to be stored @@ -128,6 +128,7 @@ enum MoveType { PROMOTION_STRAIGHT = PROMOTION, PROMOTION_LEFT = 4 << 12, PROMOTION_RIGHT = 5 << 12, + DROP = 6 << 12, }; enum Color { @@ -464,6 +465,8 @@ constexpr Square to_sq(Move m) { } inline Square from_sq(Move m) { + if (type_of(m) == DROP) + return SQ_NONE; if (type_of(m) == PROMOTION) { Square to = to_sq(m); @@ -500,6 +503,14 @@ inline Move make(Square from, Square to, PieceType pt = NO_PIECE_TYPE) { return Move(T + (from << 6) + to); } +constexpr Move make_drop(Square to, PieceType pt) { + return Move(DROP + (pt << 6) + to); +} + +constexpr PieceType dropped_piece_type(Move m) { + return PieceType((m >> 6) & 63); +} + inline bool is_ok(Move m) { return from_sq(m) != to_sq(m); // Catch MOVE_NULL and MOVE_NONE } diff --git a/src/uci.cpp b/src/uci.cpp index cff5242..1692705 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -288,7 +288,8 @@ string UCI::move(const Position& pos, Move m) { if (type_of(m) == CASTLING && !pos.is_chess960()) to = make_square(to > from ? FILE_G : FILE_C, rank_of(from)); - string move = UCI::square(from) + UCI::square(to); + string move = (type_of(m) == DROP ? std::string{pos.piece_to_char()[type_of(pos.moved_piece(m))], '@'} + : UCI::square(from)) + UCI::square(to); if (type_of(m) == PROMOTION) move += pos.piece_to_char()[make_piece(BLACK, promotion_type(m))]; diff --git a/src/variant.cpp b/src/variant.cpp index f04201f..75988fb 100644 --- a/src/variant.cpp +++ b/src/variant.cpp @@ -157,6 +157,13 @@ void VariantMap::init() { v->pieceDrops = true; return v; } (); + const Variant* loop = [&]{ + Variant* v = new Variant(); + v->startFen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[] w KQkq - 0 1"; + v->pieceDrops = true; + v->dropLoop = true; + return v; + } (); insert(std::pair(std::string("chess"), chess)); insert(std::pair(std::string("makruk"), makruk)); insert(std::pair(std::string("asean"), asean)); @@ -169,7 +176,8 @@ void VariantMap::init() { insert(std::pair(std::string("losers"), losers)); insert(std::pair(std::string("3check"), threecheck)); insert(std::pair(std::string("5check"), fivecheck)); - //insert(std::pair(std::string("crazyhouse"), crazyhouse)); + insert(std::pair(std::string("crazyhouse"), crazyhouse)); + insert(std::pair(std::string("loop"), loop)); } void VariantMap::clear_all() { diff --git a/src/variant.h b/src/variant.h index 160398d..f628f73 100644 --- a/src/variant.h +++ b/src/variant.h @@ -42,6 +42,7 @@ struct Variant { bool checking = true; bool mustCapture = false; bool pieceDrops = false; + bool dropLoop = false; // game end Value stalemateValue = VALUE_DRAW; Value checkmateValue = -VALUE_MATE;