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;
}
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);
return moveList;
}
- template<Color Us, bool Checks>
+ template<Color Us, GenType Type>
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);
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);
if (pt != PAWN && pt != KING)
moveList = generate_moves<Checks>(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<Us, Checks>(pos, moveList, pt, target & ~pos.pieces(~Us));
+ moveList = generate_drops<Us, Type>(pos, moveList, pt, target & ~pos.pieces(~Us));
if (Type != QUIET_CHECKS && Type != EVASIONS && pos.count<KING>(Us))
{
moveList = pos.checkers() ? generate<EVASIONS >(pos, moveList)
: generate<NON_EVASIONS>(pos, moveList);
while (cur != moveList)
- if (!pos.legal(*cur))
+ if (!pos.legal(*cur) || pos.virtual_drop(*cur))
*cur = (--moveList)->move;
else
++cur;
case QSEARCH_TT:
case PROBCUT_TT:
++stage;
- assert(pos.legal(ttMove) == MoveList<LEGAL>(pos).contains(ttMove));
+ assert(pos.legal(ttMove) == MoveList<LEGAL>(pos).contains(ttMove) || pos.virtual_drop(ttMove));
return ttMove;
case CAPTURE_INIT:
PartnerHandler Partner; // Global object
void PartnerHandler::reset() {
- fast = sitRequested = partnerDead = weDead = weWin = false;
+ fast = sitRequested = partnerDead = weDead = weWin = weVirtualWin = weVirtualLoss = false;
time = opptime = 0;
}
void parse_ptell(std::istringstream& is, const Position& pos);
std::atomic<bool> isFairy;
- std::atomic<bool> fast, sitRequested, partnerDead, weDead, weWin;
+ std::atomic<bool> fast, sitRequested, partnerDead, weDead, weWin, weVirtualWin, weVirtualLoss;
std::atomic<TimePoint> time, opptime;
Move moveRequested;
};
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))));
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;
}
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;
// 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;
// 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);
}
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;
}
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;
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)
{
// 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<FAIRY>("time " + std::to_string((Limits.time[us] - Time.elapsed()) / 10));
if (Limits.time[~us])
Partner.ptell<FAIRY>("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
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))
<< 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
{
#include <algorithm> // For std::count
#include "movegen.h"
+#include "partner.h"
#include "search.h"
#include "thread.h"
#include "uci.h"
&& (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<EVASIONS>(pos))
+ if (pos.virtual_drop(m) && pos.legal(m))
+ rootMoves.emplace_back(m);
+ }
+ else
+ {
+ for (const auto& m : MoveList<QUIETS>(pos))
+ if (pos.virtual_drop(m) && pos.legal(m))
+ rootMoves.emplace_back(m);
+ }
+ }
+
if (!rootMoves.empty())
Tablebases::rank_root_moves(pos, rootMoves);
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,