/// move_to_san() takes a position and a legal Move as input and returns its /// short algebraic notation representation. internal static string move_to_san(Position pos, int m) { if (m == MoveC.MOVE_NONE) { return "(none)"; } if (m == MoveC.MOVE_NULL) { return "(null)"; } Debug.Assert(pos.move_is_legal(m)); Bitboard others, b; Color us = pos.sideToMove; var san = new StringBuilder(); Square from = from_sq(m); Square to = to_sq(m); Piece pc = pos.piece_on(from); PieceType pt = type_of(pc); if (type_of_move(m) == MoveTypeC.CASTLING) { san.Append(to > from ? "O-O" : "O-O-O"); } else { if (pt != PieceTypeC.PAWN) { san.Append(PieceToChar[ColorC.WHITE][pt]); // Upper case // Disambiguation if we have more then one piece of type 'pt' that can // reach 'to' with a legal move. others = b = (pos.attacks_from_PS(pc, to) & pos.pieces_PTC(pt, us)) ^ (ulong)from; while (others != 0) { Move move = make_move(pop_lsb(ref b), to); if (!pos.pl_move_is_legal(move, pos.pinned_pieces())) { others ^= (ulong)from_sq(move); } } if (others != 0) { if ((others & file_bb_S(from)) == 0) { san.Append(file_to_char(file_of(from))); } else if ((others & rank_bb_S(from)) == 0) { san.Append(rank_to_char(rank_of(from))); } else { san.Append(square_to_string(from)); } } } else if (pos.is_capture(m)) { san.Append(file_to_char(file_of(from))); } if (pos.is_capture(m)) { san.Append('x'); } san.Append(square_to_string(to)); if (type_of_move(m) == MoveTypeC.PROMOTION) { san.Append('='); san.Append(PieceToChar[ColorC.WHITE][promotion_type(m)]); } } var ci = CheckInfoBroker.GetObject(); ci.CreateCheckInfo(pos); if (pos.move_gives_check(m, ci)) { var st = new StateInfo(); pos.do_move(m, st); var mlist = MListBroker.GetObject(); mlist.pos = 0; Movegen.generate_legal(pos, mlist.moves, ref mlist.pos); san.Append(mlist.pos > 0 ? "+" : "#"); MListBroker.Free(); pos.undo_move(m); } CheckInfoBroker.Free(); return san.ToString(); }
/// move_to_san() takes a position and a move as input, where it is assumed /// that the move is a legal move for the position. The return value is /// a string containing the move in short algebraic notation. internal static string move_to_san(Position pos, Move m) { if (m == MoveC.MOVE_NONE) return "(none)"; if (m == MoveC.MOVE_NULL) return "(null)"; Debug.Assert(is_ok_M(m)); Bitboard attackers; bool ambiguousMove, ambiguousFile, ambiguousRank; Square sq, from = from_sq(m); Square to = to_sq(m); PieceType pt = type_of(pos.piece_moved(m)); StringBuilder san = new StringBuilder(); if (is_castle(m)) san.Append((to_sq(m) < from_sq(m) ? "O-O-O" : "O-O")); else { if (pt != PieceTypeC.PAWN) { san.Append(piece_type_to_char(pt).ToString()); // Disambiguation if we have more then one piece with destination 'to' // note that for pawns is not needed because starting file is explicit. attackers = pos.attackers_to(to) & pos.pieces_PTC(pt, pos.sideToMove); xor_bit(ref attackers, from); ambiguousMove = ambiguousFile = ambiguousRank = false; while (attackers != 0) { sq = pop_1st_bit(ref attackers); // Pinned pieces are not included in the possible sub-set if (!pos.pl_move_is_legal(make_move(sq, to), pos.pinned_pieces())) continue; if (file_of(sq) == file_of(from)) ambiguousFile = true; if (rank_of(sq) == rank_of(from)) ambiguousRank = true; ambiguousMove = true; } if (ambiguousMove) { if (!ambiguousFile) san.Append(file_to_char(file_of(from))); else if (!ambiguousRank) san.Append(rank_to_char(rank_of(from))); else san.Append(square_to_string(from)); } } if (pos.is_capture(m)) { if (pt == PieceTypeC.PAWN) san.Append(file_to_char(file_of(from))); san.Append('x'); } san.Append(square_to_string(to)); if (is_promotion(m)) { san.Append('='); san.Append(piece_type_to_char(promotion_type(m))); } } CheckInfo ci = CheckInfoBroker.GetObject(); ci.CreateCheckInfo(pos); if (pos.move_gives_check(m, ci)) { StateInfo st = new StateInfo(); pos.do_move(m, st); MList mlist = MListBroker.GetObject(); mlist.pos = 0; Movegen.generate_legal(pos, mlist.moves, ref mlist.pos); san.Append(mlist.pos > 0 ? "+" : "#"); MListBroker.Free(); pos.undo_move(m); } CheckInfoBroker.Free(); return san.ToString(); }
// connected_threat() tests whether it is safe to forward prune a move or if // is somehow connected to the threat move returned by null search. static bool connected_threat(Position pos, Move m, Move threat) { Debug.Assert(Utils.is_ok_M(m)); Debug.Assert(Utils.is_ok_M(threat)); Debug.Assert(!pos.is_capture_or_promotion(m)); Debug.Assert(!pos.is_passed_pawn_push(m)); Square mfrom, mto, tfrom, tto; mfrom = Utils.from_sq(m); mto = Utils.to_sq(m); tfrom = Utils.from_sq(threat); tto = Utils.to_sq(threat); // Case 1: Don't prune moves which move the threatened piece if (mfrom == tto) return true; // Case 2: If the threatened piece has value less than or equal to the // value of the threatening piece, don't prune moves which defend it. if (pos.is_capture(threat) && (Position.PieceValueMidgame[pos.piece_on(tfrom)] >= Position.PieceValueMidgame[pos.piece_on(tto)] || Utils.type_of(pos.piece_on(tfrom)) == PieceTypeC.KING) && pos.move_attacks_square(m, tto)) return true; // Case 3: If the moving piece in the threatened move is a slider, don't // prune safe moves which block its ray. if (piece_is_slider(pos.piece_on(tfrom)) && (Utils.bit_is_set(Utils.between_bb(tfrom, tto), mto) != 0) && pos.see(m, true) >= 0) return true; return false; }
internal void MovePickerC(Position p, Move ttm, History h, PieceType pt) { pos = p; H = h; curMovePos = 0; lastMovePos = 0; Debug.Assert(!pos.in_check()); depth = 0; ttMove = 0; lastQuietPos = 0; lastBadCapturePos = 0; mpos = 0; phase = SequencerC.PROBCUT; // In ProbCut we generate only captures better than parent's captured piece captureThreshold = Position.PieceValueMidgame[pt]; ttMove = ((ttm != 0) && pos.is_pseudo_legal(ttm) ? ttm : MoveC.MOVE_NONE); if ((ttMove != 0) && (!pos.is_capture(ttMove) || pos.see(ttMove, false) <= captureThreshold)) ttMove = MoveC.MOVE_NONE; lastMovePos += ((ttMove != MoveC.MOVE_NONE) ? 1 : 0); }
// refutes() tests whether a 'first' move is able to defend against a 'second' // opponent's move. In this case will not be pruned. Normally the second move // is the threat (the best move returned from a null search that fails low). private static bool refutes(Position pos, int move, int threat) { Debug.Assert(Utils.is_ok_M(move)); Debug.Assert(Utils.is_ok_M(threat)); Square mfrom = Utils.from_sq(move); Square mto = Utils.to_sq(move); Square tfrom = Utils.from_sq(threat); Square tto = Utils.to_sq(threat); // Don't prune moves of the threatened piece if (mfrom == tto) { return true; } // If the threatened piece has value less than or equal to the value of the // threat piece, don't prune moves which defend it. if (pos.is_capture(threat) && (Position.PieceValue[PhaseC.MG][pos.piece_on(tfrom)] >= Position.PieceValue[PhaseC.MG][pos.piece_on(tto)] || Utils.type_of(pos.piece_on(tfrom)) == PieceTypeC.KING)) { // Update occupancy as if the piece and the threat are moving var occ = Utils.xor_bit(Utils.xor_bit(Utils.xor_bit(pos.occupied_squares, mfrom), mto), tfrom); Piece piece = pos.piece_on(mfrom); // The piece moved in 'to' attacks the square 's' ? if (Utils.bit_is_set(Position.attacks_from(piece, mto, occ), tto) != 0) { return true; } // Scan for possible X-ray attackers behind the moved piece var xray = (Utils.rook_attacks_bb(tto, occ) & pos.pieces(PieceTypeC.ROOK, PieceTypeC.QUEEN, Utils.color_of(piece))) | (Utils.bishop_attacks_bb(tto, occ) & pos.pieces(PieceTypeC.BISHOP, PieceTypeC.QUEEN, Utils.color_of(piece))); // Verify attackers are triggered by our move and not already existing if ((xray != 0) && ((xray ^ (xray & pos.attacks_from_QUEEN(tto))) != 0)) { return true; } } // Don't prune safe moves which block the threat path if ((Utils.bit_is_set(Utils.between_bb(tfrom, tto), mto) != 0) && pos.see(move, true) >= 0) { return true; } return false; }
// 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; }