Пример #1
0
        /// 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();
        }
Пример #2
0
        /// 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();
        }
Пример #3
0
        // 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;
        }
Пример #4
0
        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);
        }
Пример #5
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;
        }
Пример #6
0
        // 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;
        }