// qsearch() is the quiescence search function, which is called by the main // search function when the remaining depth is zero (or, to be more precise, // less than ONE_PLY). private static int qsearch(int NT, bool InCheck, Position pos, Stack[] ss, int ssPos, int alpha, int beta, int depth) { var PvNode = (NT == NodeTypeC.PV); Debug.Assert(NT == NodeTypeC.PV || NT == NodeTypeC.NonPV); Debug.Assert(InCheck == pos.in_check()); Debug.Assert(alpha >= -ValueC.VALUE_INFINITE && alpha < beta && beta <= ValueC.VALUE_INFINITE); Debug.Assert(PvNode || (alpha == beta - 1)); Debug.Assert(depth <= DepthC.DEPTH_ZERO); StateInfo st = null; int ttMove, move, bestMove; int ttValue, bestValue, value, futilityValue, futilityBase, oldAlpha = 0; bool givesCheck, enoughMaterial, evasionPrunable, fromNull; var tteHasValue = false; TTEntry tte; uint ttePos = 0; int ttDepth; Key posKey; // To flag BOUND_EXACT a node with eval above alpha and no available moves if (PvNode) { oldAlpha = alpha; } ss[ssPos].currentMove = bestMove = MoveC.MOVE_NONE; ss[ssPos].ply = ss[ssPos - 1].ply + 1; fromNull = ss[ssPos - 1].currentMove == MoveC.MOVE_NULL; // Check for an instant draw or maximum ply reached if (pos.is_draw(true) || ss[ssPos].ply > Constants.MAX_PLY) { return DrawValue[pos.sideToMove]; } // Transposition table lookup. At PV nodes, we don't use the TT for // pruning, but only for move ordering. posKey = pos.key(); tteHasValue = TT.probe(posKey, ref ttePos, out tte); ttMove = (tteHasValue ? tte.move() : MoveC.MOVE_NONE); ttValue = tteHasValue ? value_from_tt(tte.value(), ss[ssPos].ply) : ValueC.VALUE_NONE; // Decide whether or not to include checks, this fixes also the type of // TT entry depth that we are going to use. Note that in qsearch we use // only two types of depth in TT: DEPTH_QS_CHECKS or DEPTH_QS_NO_CHECKS. ttDepth = (InCheck || depth >= DepthC.DEPTH_QS_CHECKS ? DepthC.DEPTH_QS_CHECKS : DepthC.DEPTH_QS_NO_CHECKS); if (tteHasValue && tte.depth() >= depth && ttValue != ValueC.VALUE_NONE // Only in case of TT access race && (PvNode ? tte.type() == Bound.BOUND_EXACT : ttValue >= beta ? ((tte.type() & Bound.BOUND_LOWER) != 0) : ((tte.type() & Bound.BOUND_UPPER) != 0))) { ss[ssPos].currentMove = ttMove; // Can be MOVE_NONE return ttValue; } // Evaluate the position statically if (InCheck) { ss[ssPos].staticEval = ss[ssPos].evalMargin = ValueC.VALUE_NONE; bestValue = futilityBase = -ValueC.VALUE_INFINITE; enoughMaterial = false; } else { if (fromNull) { // Approximated score. Real one is slightly higher due to tempo ss[ssPos].staticEval = bestValue = -ss[ssPos - 1].staticEval; ss[ssPos].evalMargin = ValueC.VALUE_ZERO; } else if (tteHasValue) { // Never assume anything on values stored in TT if ((ss[ssPos].staticEval = bestValue = tte.eval_value()) == ValueC.VALUE_NONE || (ss[ssPos].evalMargin = tte.eval_margin()) == ValueC.VALUE_NONE) { ss[ssPos].staticEval = bestValue = Evaluate.do_evaluate(false, pos, ref ss[ssPos].evalMargin); } } else { ss[ssPos].staticEval = bestValue = Evaluate.do_evaluate(false, pos, ref ss[ssPos].evalMargin); } // Stand pat. Return immediately if static value is at least beta if (bestValue >= beta) { if (!tteHasValue) { TT.store( pos.key(), value_to_tt(bestValue, ss[ssPos].ply), Bound.BOUND_LOWER, DepthC.DEPTH_NONE, MoveC.MOVE_NONE, ss[ssPos].staticEval, ss[ssPos].evalMargin); } return bestValue; } if (PvNode && bestValue > alpha) { alpha = bestValue; } futilityBase = ss[ssPos].staticEval + ss[ssPos].evalMargin + 128; enoughMaterial = (pos.sideToMove == 0 ? pos.st.npMaterialWHITE : pos.st.npMaterialBLACK) > Constants.RookValueMidgame; } // Initialize a MovePicker object for the current position, and prepare // to search the moves. Because the depth is <= 0 here, only captures, // queen promotions and checks (only if depth >= DEPTH_QS_CHECKS) will // be generated. var mp = MovePickerBroker.GetObject(); mp.MovePickerC(pos, ttMove, depth, H, (ss[ssPos - 1].currentMove) & 0x3F); var ci = CheckInfoBroker.GetObject(); ci.CreateCheckInfo(pos); // Loop through the moves until no moves remain or a beta cutoff occurs while ((move = mp.next_move()) != MoveC.MOVE_NONE) { Debug.Assert(Utils.is_ok_M(move)); givesCheck = pos.move_gives_check(move, ci); // Futility pruning if (!PvNode && !InCheck && !givesCheck && !fromNull && move != ttMove && enoughMaterial && Utils.type_of_move(move) != MoveTypeC.PROMOTION && !pos.is_passed_pawn_push(move)) { futilityValue = futilityBase + Position.PieceValue[PhaseC.EG][pos.board[move & 0x3F]] + (Utils.type_of_move(move) == MoveTypeC.ENPASSANT ? Constants.PawnValueEndgame : ValueC.VALUE_ZERO); if (futilityValue < beta) { bestValue = Math.Max(bestValue, futilityValue); continue; } // Prune moves with negative or equal SEE if (futilityBase < beta && depth < DepthC.DEPTH_ZERO && pos.see(move, false) <= 0) { bestValue = Math.Max(bestValue, futilityBase); continue; } } // Detect non-capture evasions that are candidate to be pruned evasionPrunable = !PvNode && InCheck && bestValue > ValueC.VALUE_MATED_IN_MAX_PLY && !pos.is_capture(move) && (pos.can_castle_C(pos.sideToMove) == 0); // Don't search moves with negative SEE values if (!PvNode && move != ttMove && (!InCheck || evasionPrunable) && Utils.type_of_move(move) != MoveTypeC.PROMOTION && pos.see(move, true) < 0) { continue; } // Don't search useless checks if (!PvNode && !InCheck && givesCheck && move != ttMove && !pos.is_capture_or_promotion(move) && ss[ssPos].staticEval + Constants.PawnValueMidgame / 4 < beta && !check_is_dangerous(pos, move, futilityBase, beta)) { continue; } // Check for legality only before to do the move if (!pos.pl_move_is_legal(move, ci.pinned)) { continue; } ss[ssPos].currentMove = move; // Make and search the move if (st == null) { st = StateInfoBroker.GetObject(); } pos.do_move(move, st, ci, givesCheck); value = -qsearch(NT, givesCheck, pos, ss, ssPos + 1, -beta, -alpha, depth - DepthC.ONE_PLY); pos.undo_move(move); Debug.Assert(value > -ValueC.VALUE_INFINITE && value < ValueC.VALUE_INFINITE); // Check for new best move if (value > bestValue) { bestValue = value; if (value > alpha) { if (PvNode && value < beta) // Update alpha here! Always alpha < beta { alpha = value; bestMove = move; } else // Fail high { TT.store(posKey, value_to_tt(value, ss[ssPos].ply), Bound.BOUND_LOWER, ttDepth, move, ss[ssPos].staticEval, ss[ssPos].evalMargin); if (st != null) { st.previous = null; StateInfoBroker.Free(); } CheckInfoBroker.Free(); MovePickerBroker.Free(mp); return value; } } } } // All legal moves have been searched. A special case: If we're in check // and no legal moves were found, it is checkmate. if (InCheck && bestValue == -ValueC.VALUE_INFINITE) { if (st != null) { st.previous = null; StateInfoBroker.Free(); } CheckInfoBroker.Free(); MovePickerBroker.Free(mp); return Utils.mated_in(ss[ssPos].ply); // Plies to mate from the root } TT.store(posKey, value_to_tt(bestValue, ss[ssPos].ply), //PvNode && bestMove != MoveC.MOVE_NONE ? Bound.BOUND_EXACT : Bound.BOUND_UPPER, PvNode && bestMove > oldAlpha ? Bound.BOUND_EXACT : Bound.BOUND_UPPER, // TODO: this line asserts in bench ttDepth, bestMove, ss[ssPos].staticEval, ss[ssPos].evalMargin); Debug.Assert(bestValue > -ValueC.VALUE_INFINITE && bestValue < ValueC.VALUE_INFINITE); if (st != null) { st.previous = null; StateInfoBroker.Free(); } CheckInfoBroker.Free(); MovePickerBroker.Free(mp); return bestValue; }
private static void generate_all( GenType type, Position pos, MoveStack[] mlist, ref int mpos, int us, ulong target, CheckInfo ci) { var Checks = type == GenType.QUIET_CHECKS; generate_pawn_moves(us, type, pos, mlist, ref mpos, target, ci); generate_moves(PieceTypeC.KNIGHT, Checks, pos, mlist, ref mpos, us, target, ci); generate_moves(PieceTypeC.BISHOP, Checks, pos, mlist, ref mpos, us, target, ci); generate_moves(PieceTypeC.ROOK, Checks, pos, mlist, ref mpos, us, target, ci); generate_moves(PieceTypeC.QUEEN, Checks, pos, mlist, ref mpos, us, target, ci); if (!Checks && type != GenType.EVASIONS) { Square from = pos.king_square(us); Bitboard b = Position.attacks_from_KING(from) & target; // SERIALIZE(b); while (b != 0) { #if X64 Bitboard bb = b; b &= (b - 1); mlist[mpos++].move = ((Utils.BSFTable[((bb & (0xffffffffffffffff - bb + 1)) * DeBruijn_64) >> 58]) | (from << 6)); #else mlist[mpos++].move = Utils.make_move(from, Utils.pop_lsb(ref b)); #endif } } if (type != GenType.CAPTURES && type != GenType.EVASIONS && pos.can_castle_C(us) != 0) { generate_castle(CastlingSideC.KING_SIDE, Checks, pos, mlist, ref mpos, us); generate_castle(CastlingSideC.QUEEN_SIDE, Checks, pos, mlist, ref mpos, us); } }
internal static void generate_quiet_check(Position pos, MoveStack[] ms, ref int mpos) { /// generate<MV_NON_CAPTURE_CHECK> generates all pseudo-legal non-captures and knight /// underpromotions that give check. Returns a pointer to the end of the move list. Debug.Assert(!pos.in_check()); Color us = pos.sideToMove; CheckInfo ci = CheckInfoBroker.GetObject(); ci.CreateCheckInfo(pos); Bitboard dc = ci.dcCandidates; while (dc != 0) { Square from = Utils.pop_1st_bit(ref dc); PieceType pt = Utils.type_of(pos.piece_on(from)); if (pt == PieceTypeC.PAWN) continue; // Will be generated together with direct checks Bitboard b = pos.attacks_from_PTS(pt, from) & ~pos.occupied_squares; if (pt == PieceTypeC.KING) b &= ~Utils.PseudoAttacks[PieceTypeC.QUEEN][ci.ksq]; while (b != 0) { ms[mpos++].move = Utils.make_move(from, Utils.pop_1st_bit(ref b)); } } generate_pawn_moves(us, MoveType.MV_QUIET_CHECK, pos, ms, ref mpos, ci.dcCandidates, ci.ksq); generate_direct_checks(PieceTypeC.KNIGHT, pos, ms, ref mpos, us, ci); generate_direct_checks(PieceTypeC.BISHOP, pos, ms, ref mpos, us, ci); generate_direct_checks(PieceTypeC.ROOK, pos, ms, ref mpos, us, ci); generate_direct_checks(PieceTypeC.QUEEN, pos, ms, ref mpos, us, ci); if (pos.can_castle_C(us) != 0) { generate_castle(CastlingSideC.KING_SIDE, true, pos, ms, ref mpos, us); generate_castle(CastlingSideC.QUEEN_SIDE, true, pos, ms, ref mpos, us); } CheckInfoBroker.Free(); }