From: Fabian Fichter Date: Sun, 4 Apr 2021 09:08:48 +0000 (+0200) Subject: Support virtual piece drops (#122) X-Git-Url: http://winboard.nl/cgi-bin?a=commitdiff_plain;h=4a4ecccc31a10c3966f703fa62189de9dc17bbe0;p=fairystockfish.git Support virtual piece drops (#122) Support negative piece counts for bughouse, and allow virtual piece drops under certain conditions. This enables the engine to consider the effect of future piece flows, which is required for more sophisticated communication and strategy. This significantly improves performance against human opponents, with only a moderate regression in self-play. --- diff --git a/src/evaluate.cpp b/src/evaluate.cpp index d3fce55..f7b78f3 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -657,6 +657,11 @@ namespace { if (pt == pos.drop_no_doubled()) score -= make_score(50, 20) * std::max(pos.count_with_hand(Us, pt) - pos.max_file() - 1, 0); } + else if (pos.count_in_hand(Us, pt) < 0) + { + // Penalize drops of virtual pieces + score += (PSQT::psq[make_piece(WHITE, pt)][SQ_NONE] + make_score(1000, 1000)) * pos.count_in_hand(Us, pt); + } return score; } @@ -1581,6 +1586,10 @@ Value Eval::evaluate(const Position& pos) { v += pos.material_counting_result() / (10 * std::max(2 * pos.n_move_rule() - pos.rule50_count(), 1)); } + // Guarantee evaluation does not hit the virtual win/loss range + if (pos.two_boards() && std::abs(v) >= VALUE_VIRTUAL_MATE_IN_MAX_PLY) + v += v > VALUE_ZERO ? MAX_PLY + 1 : -MAX_PLY - 1; + // Guarantee evaluation does not hit the tablebase range v = std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); diff --git a/src/movegen.cpp b/src/movegen.cpp index 5a473cf..c2cefbc 100644 --- a/src/movegen.cpp +++ b/src/movegen.cpp @@ -70,9 +70,11 @@ namespace { return moveList; } - template + template ExtMove* generate_drops(const Position& pos, ExtMove* moveList, PieceType pt, Bitboard b) { - if (pos.count_in_hand(Us, pt) > 0) + assert(Type != CAPTURES); + // Do not generate virtual drops for perft and at root + if (pos.count_in_hand(Us, pt) > 0 || (Type != NON_EVASIONS && pos.two_boards() && pos.allow_virtual_drop(Us, pt))) { // Restrict to valid target b &= pos.drop_region(Us, pt); @@ -81,12 +83,12 @@ namespace { if (pos.drop_promoted() && pos.promoted_piece_type(pt)) { Bitboard b2 = b; - if (Checks) + if (Type == QUIET_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) + if (Type == QUIET_CHECKS || pos.count_in_hand(Us, pt) <= 0) b &= pos.check_squares(pt); while (b) *moveList++ = make_drop(pop_lsb(&b), pt, pt); @@ -373,9 +375,9 @@ namespace { if (pt != PAWN && pt != KING) moveList = generate_moves(pos, moveList, pt, piecesToMove, target); // generate drops - if (pos.piece_drops() && Type != CAPTURES && pos.count_in_hand(Us, ALL_PIECES) > 0) + if (pos.piece_drops() && Type != CAPTURES && (pos.count_in_hand(Us, ALL_PIECES) > 0 || pos.two_boards())) for (PieceType pt : pos.piece_types()) - moveList = generate_drops(pos, moveList, pt, target & ~pos.pieces(~Us)); + moveList = generate_drops(pos, moveList, pt, target & ~pos.pieces(~Us)); if (Type != QUIET_CHECKS && Type != EVASIONS && pos.count(Us)) { @@ -553,7 +555,7 @@ ExtMove* generate(const Position& pos, ExtMove* moveList) { moveList = pos.checkers() ? generate(pos, moveList) : generate(pos, moveList); while (cur != moveList) - if (!pos.legal(*cur)) + if (!pos.legal(*cur) || pos.virtual_drop(*cur)) *cur = (--moveList)->move; else ++cur; diff --git a/src/movepick.cpp b/src/movepick.cpp index ac1fe6e..5718dc5 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -163,7 +163,7 @@ top: case QSEARCH_TT: case PROBCUT_TT: ++stage; - assert(pos.legal(ttMove) == MoveList(pos).contains(ttMove)); + assert(pos.legal(ttMove) == MoveList(pos).contains(ttMove) || pos.virtual_drop(ttMove)); return ttMove; case CAPTURE_INIT: diff --git a/src/partner.cpp b/src/partner.cpp index c53d069..3225255 100644 --- a/src/partner.cpp +++ b/src/partner.cpp @@ -27,7 +27,7 @@ PartnerHandler Partner; // Global object void PartnerHandler::reset() { - fast = sitRequested = partnerDead = weDead = weWin = false; + fast = sitRequested = partnerDead = weDead = weWin = weVirtualWin = weVirtualLoss = false; time = opptime = 0; } diff --git a/src/partner.h b/src/partner.h index 8a78882..cf61c03 100644 --- a/src/partner.h +++ b/src/partner.h @@ -42,7 +42,7 @@ struct PartnerHandler { void parse_ptell(std::istringstream& is, const Position& pos); std::atomic isFairy; - std::atomic fast, sitRequested, partnerDead, weDead, weWin; + std::atomic fast, sitRequested, partnerDead, weDead, weWin, weVirtualWin, weVirtualLoss; std::atomic time, opptime; Move moveRequested; }; diff --git a/src/position.cpp b/src/position.cpp index 3157db0..cac4968 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -1118,7 +1118,7 @@ bool Position::pseudo_legal(const Move m) const { return piece_drops() && pc != NO_PIECE && color_of(pc) == us - && count_in_hand(us, in_hand_piece_type(m)) > 0 + && (count_in_hand(us, in_hand_piece_type(m)) > 0 || (two_boards() && allow_virtual_drop(us, type_of(pc)))) && (drop_region(us, type_of(pc)) & ~pieces() & to) && ( type_of(pc) == in_hand_piece_type(m) || (drop_promoted() && type_of(pc) == promoted_piece_type(in_hand_piece_type(m)))); @@ -2443,6 +2443,19 @@ bool Position::is_immediate_game_end(Value& result, int ply) const { result = mate_in(ply); return true; } + // Failing to checkmate with virtual pieces is a loss + if (two_boards() && !checkers()) + { + int virtualCount = 0; + for (PieceType pt : piece_types()) + virtualCount += std::max(-count_in_hand(~sideToMove, pt), 0); + + if (virtualCount > 0) + { + result = mate_in(ply); + return true; + } + } return false; } diff --git a/src/position.h b/src/position.h index bc80fc8..d67b99a 100644 --- a/src/position.h +++ b/src/position.h @@ -196,6 +196,7 @@ public: int count_in_hand(Color c, PieceType pt) const; int count_with_hand(Color c, PieceType pt) const; bool bikjang() const; + bool allow_virtual_drop(Color c, PieceType pt) const; // Position representation Bitboard pieces(PieceType pt = ALL_PIECES) const; @@ -245,6 +246,7 @@ public: // Properties of moves bool legal(Move m) const; bool pseudo_legal(const Move m) const; + bool virtual_drop(Move m) const; bool capture(Move m) const; bool capture_or_promotion(Move m) const; bool gives_check(Move m) const; @@ -792,6 +794,16 @@ inline Value Position::checkmate_value(int ply) const { // Niol return VALUE_DRAW; } + // Checkmate using virtual pieces + if (two_boards() && var->checkmateValue < VALUE_ZERO) + { + Value virtualMaterial = VALUE_ZERO; + for (PieceType pt : piece_types()) + virtualMaterial += std::max(-count_in_hand(~sideToMove, pt), 0) * PieceValue[MG][pt]; + + if (virtualMaterial > 0) + return -VALUE_VIRTUAL_MATE + virtualMaterial / 20 + ply; + } // Return mate value return convert_mate_value(var->checkmateValue, ply); } @@ -1166,6 +1178,11 @@ inline bool Position::capture(Move m) const { return (!empty(to_sq(m)) && type_of(m) != CASTLING && from_sq(m) != to_sq(m)) || type_of(m) == EN_PASSANT; } +inline bool Position::virtual_drop(Move m) const { + assert(is_ok(m)); + return type_of(m) == DROP && count_in_hand(side_to_move(), in_hand_piece_type(m)) <= 0; +} + inline Piece Position::captured_piece() const { return st->capturedPiece; } @@ -1244,6 +1261,16 @@ inline bool Position::bikjang() const { return st->bikjang; } +inline bool Position::allow_virtual_drop(Color c, PieceType pt) const { + assert(two_boards()); + // Do we allow a virtual drop? + return pt != KING && ( count_in_hand(c, PAWN) >= -(pt == PAWN) + && count_in_hand(c, KNIGHT) >= -(pt == PAWN) + && count_in_hand(c, BISHOP) >= -(pt == PAWN) + && count_in_hand(c, ROOK) >= 0 + && count_in_hand(c, QUEEN) >= 0); +} + inline Value Position::material_counting_result() const { auto weigth_count = [this](PieceType pt, int v){ return v * (count(WHITE, pt) - count(BLACK, pt)); }; int materialCount; diff --git a/src/search.cpp b/src/search.cpp index fbe2ad4..7b68b1a 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -302,10 +302,25 @@ void MainThread::search() { if (Options["Protocol"] == "xboard") { + Move bestMove = bestThread->rootMoves[0].pv[0]; + // Wait for virtual drop to become real + if (rootPos.two_boards() && rootPos.virtual_drop(bestMove)) + { + Partner.ptell("fast"); + while (!Threads.abort && !Partner.partnerDead && !Partner.fast && Limits.time[us] - Time.elapsed() > Partner.opptime) + {} + Partner.ptell("x"); + // Find best real move + for (const auto& m : this->rootMoves) + if (!rootPos.virtual_drop(m.pv[0])) + { + bestMove = m.pv[0]; + break; + } + } // Send move only when not in analyze mode and not at game end if (!Limits.infinite && !ponder && rootMoves[0].pv[0] != MOVE_NONE && !Threads.abort.exchange(true)) { - Move bestMove = bestThread->rootMoves[0].pv[0]; sync_cout << "move " << UCI::move(rootPos, bestMove) << sync_endl; if (XBoard::stateMachine->moveAfterSearch) { @@ -573,30 +588,68 @@ void Thread::search() { // Update partner in bughouse variants if (completedDepth >= 8 && rootPos.two_boards() && Options["Protocol"] == "xboard") { + // Communicate clock times relevant for sitting decisions if (Limits.time[us]) Partner.ptell("time " + std::to_string((Limits.time[us] - Time.elapsed()) / 10)); if (Limits.time[~us]) Partner.ptell("otim " + std::to_string(Limits.time[~us] / 10)); + // We are dead and need to sit if (!Partner.weDead && bestValue <= VALUE_MATED_IN_MAX_PLY) { Partner.ptell("dead"); Partner.weDead = true; } + // We were dead but are fine again else if (Partner.weDead && bestValue > VALUE_MATED_IN_MAX_PLY) { Partner.ptell("x"); Partner.weDead = false; } + // We win by force, so partner should sit else if (!Partner.weWin && bestValue >= VALUE_MATE_IN_MAX_PLY && Limits.time[~us] < Partner.time) { Partner.ptell("sit"); Partner.weWin = true; } + // We are no longer winning else if (Partner.weWin && (bestValue < VALUE_MATE_IN_MAX_PLY || Limits.time[~us] > Partner.time)) { Partner.ptell("x"); Partner.weWin = false; } + // We can win if partner delivers required material quickly + else if ( !Partner.weVirtualWin + && bestValue >= VALUE_VIRTUAL_MATE_IN_MAX_PLY + && bestValue <= VALUE_VIRTUAL_MATE + && Limits.time[us] - Time.elapsed() > Partner.opptime) + { + Partner.ptell("fast"); + Partner.weVirtualWin = true; + } + // Virtual mate is gone + else if ( Partner.weVirtualWin + && (bestValue < VALUE_VIRTUAL_MATE_IN_MAX_PLY || bestValue > VALUE_VIRTUAL_MATE || Limits.time[us] - Time.elapsed() < Partner.opptime)) + { + Partner.ptell("slow"); + Partner.weVirtualWin = false; + } + // We need to survive a virtual mate and play fast + else if ( !Partner.weVirtualLoss + && (bestValue <= -VALUE_VIRTUAL_MATE_IN_MAX_PLY && bestValue >= -VALUE_VIRTUAL_MATE) + && Limits.time[~us] > Partner.time) + { + Partner.ptell("sit"); + Partner.weVirtualLoss = true; + Partner.fast = true; + } + // Virtual mate threat is over + else if ( Partner.weVirtualLoss + && (bestValue > -VALUE_VIRTUAL_MATE_IN_MAX_PLY || bestValue < -VALUE_VIRTUAL_MATE || Limits.time[~us] < Partner.time)) + { + Partner.ptell("x"); + Partner.weVirtualLoss = false; + Partner.fast = false; + } } // Stop the search if we have exceeded the totalTime @@ -1965,7 +2018,7 @@ void MainThread::check_time() { if ( rootPos.two_boards() && Time.elapsed() < Limits.time[rootPos.side_to_move()] - 1000 - && (Partner.sitRequested || (Partner.weDead && !Partner.partnerDead))) + && (Partner.sitRequested || (Partner.weDead && !Partner.partnerDead) || Partner.weVirtualWin)) return; if ( (Limits.use_time_management() && (elapsed > Time.maximum() - 10 || stopOnPonderhit)) @@ -2017,8 +2070,10 @@ string UCI::pv(const Position& pos, Depth depth, Value alpha, Value beta) { << nodesSearched * 1000 / elapsed << " " << tbHits << "\t"; - for (Move m : rootMoves[i].pv) - ss << " " << UCI::move(pos, m); + // Do not print PVs with virtual drops in bughouse variants + if (!pos.two_boards()) + for (Move m : rootMoves[i].pv) + ss << " " << UCI::move(pos, m); } else { diff --git a/src/thread.cpp b/src/thread.cpp index ec9abce..7780d5e 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -20,6 +20,7 @@ #include // For std::count #include "movegen.h" +#include "partner.h" #include "search.h" #include "thread.h" #include "uci.h" @@ -188,6 +189,23 @@ void ThreadPool::start_thinking(Position& pos, StateListPtr& states, && (limits.banmoves.empty() || !std::count(limits.banmoves.begin(), limits.banmoves.end(), m))) rootMoves.emplace_back(m); + // Add virtual drops + if (pos.two_boards() && Partner.opptime && limits.time[pos.side_to_move()] > Partner.opptime + 1000) + { + if (pos.checkers()) + { + for (const auto& m : MoveList(pos)) + if (pos.virtual_drop(m) && pos.legal(m)) + rootMoves.emplace_back(m); + } + else + { + for (const auto& m : MoveList(pos)) + if (pos.virtual_drop(m) && pos.legal(m)) + rootMoves.emplace_back(m); + } + } + if (!rootMoves.empty()) Tablebases::rank_root_moves(pos, rootMoves); diff --git a/src/types.h b/src/types.h index 5f501bc..503e340 100644 --- a/src/types.h +++ b/src/types.h @@ -324,6 +324,8 @@ enum Value : int { VALUE_KNOWN_WIN = 10000, VALUE_MATE = 32000, XBOARD_VALUE_MATE = 200000, + VALUE_VIRTUAL_MATE = 3000, + VALUE_VIRTUAL_MATE_IN_MAX_PLY = VALUE_VIRTUAL_MATE - MAX_PLY, VALUE_INFINITE = 32001, VALUE_NONE = 32002,