Beispiel #1
0
        internal static int SetupStatePos = 0; // *SetupState = StateRingBuf;

        #endregion Fields

        #region Methods

        // go() is called when engine receives the "go" UCI command. The function sets
        // the thinking time and other parameters from the input string, and then starts
        // the main searching thread.
        internal static void go(Position pos, Stack<string> stack)
        {
            string token = string.Empty;
            LimitsType limits = new LimitsType();
            List<Move> searchMoves = new List<Phase>();

            while (stack.Count > 0)
            {
                token = stack.Pop();

                if (token == "wtime")
                    limits.time[ColorC.WHITE] = int.Parse(stack.Pop());
                else if (token == "btime")
                    limits.time[ColorC.BLACK] = int.Parse(stack.Pop());
                else if (token == "winc")
                    limits.inc[ColorC.WHITE] = int.Parse(stack.Pop());
                else if (token == "binc")
                    limits.inc[ColorC.BLACK] = int.Parse(stack.Pop());
                else if (token == "movestogo")
                    limits.movesToGo = int.Parse(stack.Pop());
                else if (token == "depth")
                    limits.depth = int.Parse(stack.Pop());
                else if (token == "nodes")
                    limits.nodes = int.Parse(stack.Pop());
                else if (token == "movetime")
                    limits.movetime = int.Parse(stack.Pop());
                else if (token == "infinite")
                    limits.infinite = 1;
                else if (token == "ponder")
                    limits.ponder = true;
                else if (token == "searchmoves")
                    while ((token = stack.Pop()) != null)
                    {
                        searchMoves.Add(Utils.move_from_uci(pos, token));
                    }
            }

            Threads.start_searching(pos, limits, searchMoves);
        }
Beispiel #2
0
        /// benchmark() runs a simple benchmark by letting Stockfish analyze a set
        /// of positions for a given limit each. There are five parameters; the
        /// transposition table size, the number of search threads that should
        /// be used, the limit value spent for each position (optional, default is
        /// depth 12), an optional file name where to look for positions in fen
        /// format (defaults are the positions defined above) and the type of the
        /// limit value: depth (default), time in secs or number of nodes.
        internal static void benchmark(Position current, Stack<string> stack)
        {
            List<string> fens = new List<string>();

            LimitsType limits = new LimitsType();
            Int64 nodes = 0;
            Int64 nodesAll = 0;
            long e = 0;
            long eAll = 0;

            // Assign default values to missing arguments
            string ttSize = (stack.Count > 0) ? (stack.Pop()) : "128";
            string threads = (stack.Count > 0) ? (stack.Pop()) : "1";
            string limit = (stack.Count > 0) ? (stack.Pop()) : "12";
            string fenFile = (stack.Count > 0) ? (stack.Pop()) : "default";
            string limitType = (stack.Count > 0) ? (stack.Pop()) : "depth";

            OptionMap.Instance["Hash"].v = ttSize;
            OptionMap.Instance["Threads"].v = threads;
            TT.clear();

            if (limitType == "time")
                limits.movetime = 1000 * int.Parse(limit); // maxTime is in ms

            else if (limitType == "nodes")
                limits.nodes = int.Parse(limit);

            else
                limits.depth = int.Parse(limit);

            if (fenFile == "default")
            {
                fens.AddRange(Defaults);
            }
            else if (fenFile == "current")
            {
                fens.Add(current.to_fen());
            }
            else
            {
            #if PORTABLE
                throw new Exception("File cannot be read.");
            #else
                System.IO.StreamReader sr = new System.IO.StreamReader(fenFile, true);
                string fensFromFile = sr.ReadToEnd();
                sr.Close();
                sr.Dispose();

                string[] split = fensFromFile.Replace("\r", "").Split('\n');
                foreach (string fen in split)
                {
                    if (fen.Trim().Length > 0)
                    {
                        fens.Add(fen.Trim());
                    }
                }
            #endif
            }

            Stopwatch time = new Stopwatch();
            long[] res = new long[fens.Count];
            for (int i = 0; i < fens.Count; i++)
            {
                time.Reset(); time.Start();
                Position pos = new Position(fens[i], bool.Parse(OptionMap.Instance["UCI_Chess960"].v), Threads.main_thread());

                Plug.Write("\nPosition: ");
                Plug.Write((i + 1).ToString());
                Plug.Write("/");
                Plug.Write(fens.Count.ToString());
                Plug.Write(Constants.endl);

                if (limitType == "perft")
                {
                    Int64 cnt = Search.perft(pos, limits.depth * DepthC.ONE_PLY);
                    Plug.Write("\nPerft ");
                    Plug.Write(limits.depth.ToString());
                    Plug.Write(" leaf nodes: ");
                    Plug.Write(cnt.ToString());
                    Plug.Write(Constants.endl);
                    nodes = cnt;
                }
                else
                {
                    Threads.start_searching(pos, limits, new List<Move>());
                    Threads.wait_for_search_finished();
                    nodes = Search.RootPosition.nodes;
                    res[i] = nodes;
                }

                e = time.ElapsedMilliseconds;

                nodesAll += nodes;
                eAll += e;

                Plug.Write("\n===========================");
                Plug.Write("\nTotal time (ms) : ");
                Plug.Write(e.ToString());
                Plug.Write("\nNodes searched  : ");
                Plug.Write(nodes.ToString());
                Plug.Write("\nNodes/second    : ");
                Plug.Write(((int)(nodes / (e / 1000.0))).ToString());
                Plug.Write(Constants.endl);

            }

            Plug.Write("\n===========================");
            Plug.Write("\nTotal time (ms) : ");
            Plug.Write(eAll.ToString());
            Plug.Write("\nNodes searched  : ");
            Plug.Write(nodesAll.ToString());
            Plug.Write("\nNodes/second    : ");
            Plug.Write(((int)(nodesAll / (eAll / 1000.0))).ToString());
            Plug.Write(Constants.endl);

            //for (int i = 0; i < res.Length; i++)
            //{
            //    Plug.Write(string.Format("{0}: {1}", i, res[i]));
            //    Plug.Write(Constants.endl);
            //}
        }
Beispiel #3
0
 internal static Stack<string> CreateStack(string input)
 {
     var lines = input.Trim().Split(' ');
     var stack = new Stack<string>(); // LIFO
     for (var i = (lines.Length - 1); i >= 0; i--)
     {
         var line = lines[i];
         if (!string.IsNullOrEmpty(line))
         {
             line = line.Trim();
             stack.Push(line);
         }
     }
     return stack;
 }
Beispiel #4
0
        // set_position() is called when engine receives the "position" UCI
        // command. The function sets up the position described in the given
        // fen string ("fen") or the starting position ("startpos") and then
        // makes the moves given in the following move list ("moves").
        internal static void set_position(Position pos, Stack<string> stack)
        {
            Move m;
            string token, fen = string.Empty;

            token = stack.Pop();

            if (token == "startpos")
            {
                fen = StartFEN;
                if (stack.Count > 0) { token = stack.Pop(); } // Consume "moves" token if any
            }
            else if (token == "fen")
                while ((stack.Count > 0) && (token = stack.Pop()) != "moves")
                    fen += token + " ";
            else
                return;

            pos.from_fen(fen, bool.Parse(OptionMap.Instance["UCI_Chess960"].v), Threads.main_thread());

            // Parse move list (if any)
            while ((stack.Count > 0) && (m = Utils.move_from_uci(pos, token = stack.Pop())) != MoveC.MOVE_NONE)
            {
                pos.do_move(m, StateRingBuf[SetupStatePos]);

                // Increment pointer to StateRingBuf circular buffer
                SetupStatePos = (SetupStatePos + 1) % 102;
            }
        }
Beispiel #5
0
        // split() does the actual work of distributing the work at a node between
        // several available threads. If it does not succeed in splitting the node
        // (because no idle threads are available, or because we have no unused split
        // point objects), the function immediately returns. If splitting is possible, a
        // SplitPoint object is initialized with all the data that must be copied to the
        // helper threads and then helper threads are told that they have been assigned
        // work. This will cause them to instantly leave their idle loops and call
        // search(). When all threads have returned from search() then split() returns.
        internal static Value split(bool Fake, Position pos, Stack[] ss, int ssPos, Value alpha, Value beta,
                                    Value bestValue, ref Move bestMove, Depth depth, Move threatMove,
                                    int moveCount, MovePicker mp, int nodeType)
        {
            Debug.Assert(pos.pos_is_ok());
            Debug.Assert(bestValue > -ValueC.VALUE_INFINITE);
            Debug.Assert(bestValue <= alpha);
            Debug.Assert(alpha < beta);
            Debug.Assert(beta <= ValueC.VALUE_INFINITE);
            Debug.Assert(depth > DepthC.DEPTH_ZERO);

            Thread master = pos.this_thread();

            if (master.splitPointsCnt >= Constants.MAX_SPLITPOINTS_PER_THREAD)
                return bestValue;

            // Pick the next available split point from the split point stack
            SplitPoint sp = master.splitPoints[master.splitPointsCnt];

            sp.parent = master.curSplitPoint;
            sp.master = master;
            sp.cutoff = false;
            sp.slavesMask = 1UL << master.idx;
#if ACTIVE_REPARENT
            sp.allSlavesRunning = true;
#endif

            sp.depth = depth;
            sp.bestMove = bestMove;
            sp.threatMove = threatMove;
            sp.alpha = alpha;
            sp.beta = beta;
            sp.nodeType = nodeType;
            sp.bestValue = bestValue;
            sp.mp = mp;
            sp.moveCount = moveCount;
            sp.pos = pos;
            sp.nodes = 0;
            sp.ss = ss;
            sp.ssPos = ssPos;

            Debug.Assert(master.is_searching);
            master.curSplitPoint = sp;

            int slavesCnt = 0;

            ThreadHelper.lock_grab(sp.Lock);
            ThreadHelper.lock_grab(splitLock);

            for (int i = 0; i < size() && !Fake; ++i)
                if (threads[i].is_available_to(master))
                {
                    sp.slavesMask |= 1UL << i;
                    threads[i].curSplitPoint = sp;
                    threads[i].is_searching = true; // Slave leaves idle_loop()

                    if (useSleepingThreads)
                        threads[i].wake_up();

                    if (++slavesCnt + 1 >= maxThreadsPerSplitPoint) // Master is always included
                        break;
                }

            master.splitPointsCnt++;

            ThreadHelper.lock_release(splitLock);
            ThreadHelper.lock_release(sp.Lock);

            // Everything is set up. The master thread enters the idle loop, from which
            // it will instantly launch a search, because its is_searching flag is set.
            // We pass the split point as a parameter to the idle loop, which means that
            // the thread will return from the idle loop when all slaves have finished
            // their work at this split point.
            if (slavesCnt != 0 || Fake)
            {
                master.idle_loop(sp, null);
                // In helpful master concept a master can help only a sub-tree of its split
                // point, and because here is all finished is not possible master is booked.
                Debug.Assert(!master.is_searching);
            }

            // We have returned from the idle loop, which means that all threads are
            // finished. Note that setting is_searching and decreasing activeSplitPoints is
            // done under lock protection to avoid a race with Thread::is_available_to().
            ThreadHelper.lock_grab(sp.Lock); // To protect sp->nodes
            ThreadHelper.lock_grab(splitLock);

            master.is_searching = true;
            master.splitPointsCnt--;
            master.curSplitPoint = sp.parent;
            pos.nodes += sp.nodes;
            bestMove = sp.bestMove;

            ThreadHelper.lock_release(splitLock);
            ThreadHelper.lock_release(sp.Lock);

            return sp.bestValue;
        }
Beispiel #6
0
        // set_option() is called when engine receives the "setoption" UCI command. The
        // function updates the UCI option ("name") to the given value ("value").
        // setoption name Ponder value false
        internal static void set_option(Stack<string> stack)
        {
            string token, name = null, value = null;

            // Consume "name" token
            stack.Pop();

            // Read option name (can contain spaces)
            while ((stack.Count > 0) && ((token = stack.Pop()) != "value"))
            {
                name += (name == null ? string.Empty : " ") + token;
            }

            // Read option value (can contain spaces)
            while ((stack.Count > 0) && ((token = stack.Pop()) != "value"))
            {
                value += (value == null ? string.Empty : " ") + token;
            }

            if (OptionMap.Instance.Contains(name))
            {
                OptionMap.Instance[name].v = value;
            }
            else
            {
                Plug.Write("No such option: ");
                Plug.Write(name);
                Plug.Write(Constants.endl);
            }
        }
Beispiel #7
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).
        static Value qsearch(NodeType NT, Position pos, Stack[] ss, int ssPos, Value alpha, Value beta, Depth depth)
        {
            bool PvNode = (NT == NodeTypeC.PV);

            Debug.Assert(NT == NodeTypeC.PV || NT == NodeTypeC.NonPV);
            Debug.Assert(alpha >= -ValueC.VALUE_INFINITE && alpha < beta && beta <= ValueC.VALUE_INFINITE);
            Debug.Assert((alpha == beta - 1) || PvNode);
            Debug.Assert(depth <= DepthC.DEPTH_ZERO);

            StateInfo st = null;
            Move ttMove, move, bestMove;
            Value ttValue, bestValue, value, evalMargin = 0, futilityValue, futilityBase;

            bool inCheck, enoughMaterial, givesCheck, evasionPrunable;
            bool tteHasValue = false;
            TTEntry tte;
            UInt32 ttePos = 0;
            Depth ttDepth;
            Bound bt;
            Value oldAlpha = alpha;

            ss[ssPos].currentMove = bestMove = MoveC.MOVE_NONE;
            ss[ssPos].ply = ss[ssPos - 1].ply + 1;

            // Check for an instant draw or maximum ply reached
            if (pos.is_draw(true) || ss[ssPos].ply > Constants.MAX_PLY)
                return ValueC.VALUE_DRAW;

            // 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.
            inCheck = pos.st.checkersBB != 0;
            ttDepth = (inCheck || depth >= DepthC.DEPTH_QS_CHECKS ? DepthC.DEPTH_QS_CHECKS : DepthC.DEPTH_QS_NO_CHECKS);

            // Transposition table lookup. At PV nodes, we don't use the TT for
            // pruning, but only for move ordering.
            tteHasValue = TT.probe(pos.key(), ref ttePos, out tte);
            ttMove = (tteHasValue ? tte.move() : MoveC.MOVE_NONE);
            ttValue = tteHasValue ? value_from_tt(tte.value(), ss[ssPos].ply) : ValueC.VALUE_ZERO;

            if (!PvNode && tteHasValue && can_return_tt(tte, ttDepth, ttValue, beta))
            {
                ss[ssPos].currentMove = ttMove; // Can be MOVE_NONE
                return ttValue;
            }

            // Evaluate the position statically
            if (inCheck)
            {
                bestValue = futilityBase = -ValueC.VALUE_INFINITE;
                ss[ssPos].eval = evalMargin = ValueC.VALUE_NONE;
                enoughMaterial = false;
            }
            else
            {
                if (tteHasValue)
                {
                    Debug.Assert(tte.static_value() != ValueC.VALUE_NONE);
                    evalMargin = tte.static_value_margin();
                    ss[ssPos].eval = bestValue = tte.static_value();
                }
                else
                    ss[ssPos].eval = bestValue = Evaluate.do_evaluate(false, pos, ref 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].eval, evalMargin);

                    return bestValue;
                }

                if (PvNode && bestValue > alpha)
                    alpha = bestValue;

                futilityBase = ss[ssPos].eval + evalMargin + FutilityMarginQS;
                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.
            MovePicker mp = MovePickerBroker.GetObject();
            mp.MovePickerC(pos, ttMove, depth, H, (ss[ssPos - 1].currentMove) & 0x3F);
            CheckInfo ci = CheckInfoBroker.GetObject();
            ci.CreateCheckInfo(pos);

            // Loop through the moves until no moves remain or a beta cutoff occurs
            while (bestValue < beta
                   && (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
                    && move != ttMove
                    && enoughMaterial
                    && ((move & (3 << 14)) != (1 << 14))
                    && !pos.is_passed_pawn_push(move))
                {
                    futilityValue = futilityBase
                                   + Position.PieceValueEndgame[pos.board[move & 0x3F]]
                                   + (((move & (3 << 14)) == (2 << 14)) ? Constants.PawnValueEndgame : ValueC.VALUE_ZERO);

                    if (futilityValue < beta)
                    {
                        if (futilityValue > bestValue)
                            bestValue = futilityValue;

                        continue;
                    }

                    // Prune moves with negative or equal SEE
                    if (futilityBase < beta
                        && depth < DepthC.DEPTH_ZERO
                        && pos.see(move, false) <= 0)
                        continue;
                }

                // Detect non-capture evasions that are candidate to be pruned
                evasionPrunable = !PvNode
                                 && inCheck
                                 && bestValue > ValueC.VALUE_MATED_IN_MAX_PLY
                                 && !(((pos.board[move & 0x3F] != PieceC.NO_PIECE) && !((move & (3 << 14)) == (3 << 14))) || ((move & (3 << 14)) == (2 << 14)))
                                 && ((pos.st.castleRights & (CastleRightC.WHITE_ANY << (pos.sideToMove << 1))) == 0);

                // Don't search moves with negative SEE values
                if (!PvNode
                    && move != ttMove
                    && (!inCheck || evasionPrunable)
                    && (move & (3 << 14)) != (1 << 14)
                    && pos.see(move, true) < 0)
                    continue;

                // Don't search useless checks
                if (!PvNode
                    && !inCheck
                    && givesCheck
                    && move != ttMove
                    && !(((move & (3 << 14)) != 0) ? ((move & (3 << 14)) != (3 << 14)) : (pos.board[move & 0x3F] != PieceC.NO_PIECE))
                    && ss[ssPos].eval + 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, pos, ss, ssPos + 1, -beta, -alpha, depth - DepthC.ONE_PLY);
                pos.undo_move(move);

                Debug.Assert(value > -ValueC.VALUE_INFINITE && value < ValueC.VALUE_INFINITE);

                // New best move?
                if (value > bestValue)
                {
                    bestValue = value;
                    bestMove = move;

                    if (PvNode
                        && value > alpha
                        && value < beta) // We want always alpha < beta
                        alpha = 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
            }

            // Update transposition table
            move = bestValue <= oldAlpha ? MoveC.MOVE_NONE : bestMove;
            bt = bestValue <= oldAlpha ? Bound.BOUND_UPPER
                 : bestValue >= beta ? Bound.BOUND_LOWER : Bound.BOUND_EXACT;

            TT.store(pos.key(), value_to_tt(bestValue, ss[ssPos].ply), bt, ttDepth, move, ss[ssPos].eval, 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;
        }
Beispiel #8
0
        // search<>() is the main search function for both PV and non-PV nodes and for
        // normal and SplitPoint nodes. When called just after a split point the search
        // is simpler because we have already probed the hash table, done a null move
        // search, and searched the first move before splitting, we don't have to repeat
        // all this work again. We also don't need to store anything to the hash table
        // here: This is taken care of after we return from the split point.
        internal static Value search(NodeType NT, Position pos, Stack[] ss, int ssPos, Value alpha, Value beta, Depth depth)
        {
            bool PvNode = (NT == NodeTypeC.PV || NT == NodeTypeC.Root || NT == NodeTypeC.SplitPointPV || NT == NodeTypeC.SplitPointRoot);
            bool SpNode = (NT == NodeTypeC.SplitPointPV || NT == NodeTypeC.SplitPointNonPV || NT == NodeTypeC.SplitPointRoot);
            bool RootNode = (NT == NodeTypeC.Root || NT == NodeTypeC.SplitPointRoot);

            Debug.Assert(alpha >= -ValueC.VALUE_INFINITE && alpha < beta && beta <= ValueC.VALUE_INFINITE);
            Debug.Assert((alpha == beta - 1) || PvNode);
            Debug.Assert(depth > DepthC.DEPTH_ZERO);

            MovesSearched ms = MovesSearchedBroker.GetObject();
            Move[] movesSearched = ms.movesSearched;

            StateInfo st = null;
            TTEntry tte = TT.StaticEntry;
            bool tteHasValue = false;
            UInt32 ttePos = 0;
            Key posKey = 0;
            Move ttMove, move, excludedMove, bestMove, threatMove;
            Depth ext, newDepth;
            Bound bt;
            Value bestValue, value, oldAlpha, ttValue;
            Value refinedValue, nullValue, futilityBase, futilityValue;
            bool isPvMove, inCheck, singularExtensionNode, givesCheck;
            bool captureOrPromotion, dangerous, doFullDepthSearch;
            int moveCount = 0, playedMoveCount = 0;
            Thread thisThread = pos.this_thread();
            SplitPoint sp = null;

            refinedValue = bestValue = value = -ValueC.VALUE_INFINITE;
            oldAlpha = alpha;
            inCheck = pos.in_check();
            ss[ssPos].ply = ss[ssPos - 1].ply + 1;

            // Used to send selDepth info to GUI
            if (PvNode && thisThread.maxPly < ss[ssPos].ply)
                thisThread.maxPly = ss[ssPos].ply;

            // Step 1. Initialize node
            if (SpNode)
            {
                ttMove = excludedMove = MoveC.MOVE_NONE;
                ttValue = ValueC.VALUE_ZERO;

                sp = ss[ssPos].sp;
                bestMove = sp.bestMove;
                threatMove = sp.threatMove;
                bestValue = sp.bestValue;
                moveCount = sp.moveCount; // Lock must be held here

                Debug.Assert(bestValue > -ValueC.VALUE_INFINITE && moveCount > 0);

                goto split_point_start;
            }
            else
            {
                ss[ssPos].currentMove = threatMove = ss[ssPos + 1].excludedMove = bestMove = MoveC.MOVE_NONE;
                ss[ssPos + 1].skipNullMove = 0; ss[ssPos + 1].reduction = DepthC.DEPTH_ZERO;
                ss[ssPos + 2].killers0 = ss[ssPos + 2].killers1 = MoveC.MOVE_NONE;
            }

            // Step 2. Check for aborted search and immediate draw
            // Enforce node limit here. FIXME: This only works with 1 search thread.
            if ((Limits.nodes != 0) && pos.nodes >= Limits.nodes)
                SignalsStop = true;

            if ((SignalsStop
                 || pos.is_draw(false)
                 || ss[ssPos].ply > Constants.MAX_PLY) && !RootNode)
            {
                MovesSearchedBroker.Free();
                return ValueC.VALUE_DRAW;
            }

            // Step 3. Mate distance pruning. Even if we mate at the next move our score
            // would be at best mate_in(ss[ssPos].ply+1), but if alpha is already bigger because
            // a shorter mate was found upward in the tree then there is no need to search
            // further, we will never beat current alpha. Same logic but with reversed signs
            // applies also in the opposite condition of being mated instead of giving mate,
            // in this case return a fail-high score.
            if (!RootNode)
            {
                alpha = Math.Max(Utils.mated_in(ss[ssPos].ply), alpha);
                beta = Math.Min(Utils.mate_in(ss[ssPos].ply + 1), beta);
                if (alpha >= beta)
                {
                    MovesSearchedBroker.Free();
                    return alpha;
                }
            }

            // Step 4. Transposition table lookup
            // We don't want the score of a partial search to overwrite a previous full search
            // TT value, so we use a different position key in case of an excluded move.
            excludedMove = ss[ssPos].excludedMove;
            posKey = (excludedMove != 0) ? pos.exclusion_key() : pos.key();
            tteHasValue = TT.probe(posKey, ref ttePos, out tte);
            ttMove = RootNode ? RootMoves[PVIdx].pv[0] : tteHasValue ? tte.move() : MoveC.MOVE_NONE;
            ttValue = tteHasValue ? value_from_tt(tte.value(), ss[ssPos].ply) : ValueC.VALUE_ZERO;

            // At PV nodes we check for exact scores, while at non-PV nodes we check for
            // a fail high/low. Biggest advantage at probing at PV nodes is to have a
            // smooth experience in analysis mode. We don't probe at Root nodes otherwise
            // we should also update RootMoveList to avoid bogus output.
            if (!RootNode && tteHasValue && (PvNode ? tte.depth() >= depth && tte.type() == Bound.BOUND_EXACT
                                            : can_return_tt(tte, depth, ttValue, beta)))
            {
                TT.entries[ttePos].set_generation(TT.generation);
                ss[ssPos].currentMove = ttMove; // Can be MOVE_NONE

                if (ttValue >= beta
                    && (ttMove != 0)
                    && !pos.is_capture_or_promotion(ttMove)
                    && ttMove != ss[ssPos].killers0)
                {
                    ss[ssPos].killers1 = ss[ssPos].killers0;
                    ss[ssPos].killers0 = ttMove;
                }

                MovesSearchedBroker.Free();
                return ttValue;
            }

            // Step 5. Evaluate the position statically and update parent's gain statistics
            if (inCheck)
                ss[ssPos].eval = ss[ssPos].evalMargin = ValueC.VALUE_NONE;
            else if (tteHasValue)
            {
                Debug.Assert(tte.static_value() != ValueC.VALUE_NONE);
                ss[ssPos].eval = tte.static_value();
                ss[ssPos].evalMargin = tte.static_value_margin();
                refinedValue = refine_eval(tte, ttValue, ss[ssPos].eval);
            }
            else
            {
                refinedValue = ss[ssPos].eval = Evaluate.do_evaluate(false, pos, ref ss[ssPos].evalMargin);
                TT.store(posKey, ValueC.VALUE_NONE, Bound.BOUND_NONE, DepthC.DEPTH_NONE, MoveC.MOVE_NONE, ss[ssPos].eval, ss[ssPos].evalMargin);
            }

            // Update gain for the parent non-capture move given the static position
            // evaluation before and after the move.
            if ((move = ss[ssPos - 1].currentMove) != MoveC.MOVE_NULL
                && ss[ssPos - 1].eval != ValueC.VALUE_NONE
                && ss[ssPos].eval != ValueC.VALUE_NONE
                && (pos.captured_piece_type() == 0)
                && !Utils.is_special(move))
            {
                Square to = Utils.to_sq(move);
                H.update_gain(pos.piece_on(to), to, -ss[ssPos - 1].eval - ss[ssPos].eval);
            }

            // Step 6. Razoring (is omitted in PV nodes)
            if (!PvNode && !inCheck
                && depth < RazorDepth
                && refinedValue + razor_margin(depth) < beta
                && ttMove == MoveC.MOVE_NONE
                && Math.Abs(beta) < ValueC.VALUE_MATE_IN_MAX_PLY
                && !pos.pawn_on_7th(pos.sideToMove))
            {
                Value rbeta = beta - razor_margin(depth);
                Value v = qsearch(NodeTypeC.NonPV, pos, ss, ssPos, rbeta - 1, rbeta, DepthC.DEPTH_ZERO);
                if (v < rbeta)
                {
                    // Logically we should return (v + razor_margin(depth)), but
                    // surprisingly this did slightly weaker in tests.
                    MovesSearchedBroker.Free();
                    return v;
                }
            }

            // Step 7. Static null move pruning (is omitted in PV nodes)
            // We're betting that the opponent doesn't have a move that will reduce
            // the score by more than futility_margin(depth) if we do a null move.
            if (!PvNode && !inCheck
                && (ss[ssPos].skipNullMove == 0)
                && depth < RazorDepth
                && Math.Abs(beta) < ValueC.VALUE_MATE_IN_MAX_PLY
                && refinedValue - futility_margin(depth, 0) >= beta
                && (pos.non_pawn_material(pos.sideToMove) != 0))
            {
                MovesSearchedBroker.Free();
                return refinedValue - futility_margin(depth, 0);
            }

            // Step 8. Null move search with verification search (is omitted in PV nodes)
            if (!PvNode && !inCheck
                && (ss[ssPos].skipNullMove == 0)
                && depth > DepthC.ONE_PLY
                && refinedValue >= beta
                && Math.Abs(beta) < ValueC.VALUE_MATE_IN_MAX_PLY
                && (pos.non_pawn_material(pos.sideToMove) != 0))
            {
                ss[ssPos].currentMove = MoveC.MOVE_NULL;

                // Null move dynamic reduction based on depth
                int R = 3 + (depth >= 5 * DepthC.ONE_PLY ? depth / 8 : 0);

                // Null move dynamic reduction based on value
                if (refinedValue - Constants.PawnValueMidgame > beta)
                    R++;

                if (st == null) { st = StateInfoBroker.GetObject(); }
                pos.do_null_move(true, st);
                ss[ssPos + 1].skipNullMove = 1;
                nullValue = depth - R * DepthC.ONE_PLY < DepthC.ONE_PLY ? -qsearch(NodeTypeC.NonPV, pos, ss, ssPos + 1, -beta, -alpha, DepthC.DEPTH_ZERO)
                                                      : -search(NodeTypeC.NonPV, pos, ss, ssPos + 1, -beta, -alpha, depth - R * DepthC.ONE_PLY);
                ss[ssPos + 1].skipNullMove = 0;
                pos.do_null_move(false, st);

                if (nullValue >= beta)
                {
                    // Do not return unproven mate scores
                    if (nullValue >= ValueC.VALUE_MATE_IN_MAX_PLY)
                        nullValue = beta;

                    if (depth < 6 * DepthC.ONE_PLY)
                    {
                        if (st != null) { st.previous = null; StateInfoBroker.Free(); }
                        MovesSearchedBroker.Free();
                        return nullValue;
                    }

                    // Do verification search at high depths
                    ss[ssPos].skipNullMove = 1;
                    Value v = search(NodeTypeC.NonPV, pos, ss, ssPos, alpha, beta, depth - R * DepthC.ONE_PLY);
                    ss[ssPos].skipNullMove = 0;

                    if (v >= beta)
                    {
                        if (st != null) { st.previous = null; StateInfoBroker.Free(); }
                        MovesSearchedBroker.Free();
                        return nullValue;
                    }
                }
                else
                {
                    // The null move failed low, which means that we may be faced with
                    // some kind of threat. If the previous move was reduced, check if
                    // the move that refuted the null move was somehow connected to the
                    // move which was reduced. If a connection is found, return a fail
                    // low score (which will cause the reduced move to fail high in the
                    // parent node, which will trigger a re-search with full depth).
                    threatMove = ss[ssPos + 1].currentMove;

                    if (depth < ThreatDepth
                        && (ss[ssPos - 1].reduction != 0)
                        && threatMove != MoveC.MOVE_NONE
                        && connected_moves(pos, ss[ssPos - 1].currentMove, threatMove))
                    {
                        if (st != null) { st.previous = null; StateInfoBroker.Free(); }
                        MovesSearchedBroker.Free();
                        return beta - 1;
                    }
                }
            }

            // Step 9. ProbCut (is omitted in PV nodes)
            // If we have a very good capture (i.e. SEE > seeValues[captured_piece_type])
            // and a reduced search returns a value much above beta, we can (almost) safely
            // prune the previous move.
            if (!PvNode && !inCheck
                && excludedMove == MoveC.MOVE_NONE
                && depth >= RazorDepth + DepthC.ONE_PLY
                && (ss[ssPos].skipNullMove == 0)
                && Math.Abs(beta) < ValueC.VALUE_MATE_IN_MAX_PLY)
            {
                Value rbeta = beta + 200;
                Depth rdepth = depth - DepthC.ONE_PLY - 3 * DepthC.ONE_PLY;

                Debug.Assert(rdepth >= DepthC.ONE_PLY);
                Debug.Assert(ss[ssPos - 1].currentMove != MoveC.MOVE_NONE);
                Debug.Assert(ss[ssPos - 1].currentMove != MoveC.MOVE_NULL);

                MovePicker mp2 = MovePickerBroker.GetObject();
                mp2.MovePickerC(pos, ttMove, H, pos.captured_piece_type());
                CheckInfo ci2 = CheckInfoBroker.GetObject();
                ci2.CreateCheckInfo(pos);

                while ((move = mp2.next_move()) != MoveC.MOVE_NONE)
                {
                    if (pos.pl_move_is_legal(move, ci2.pinned))
                    {
                        ss[ssPos].currentMove = move;
                        if (st == null) { st = StateInfoBroker.GetObject(); }
                        pos.do_move(move, st, ci2, pos.move_gives_check(move, ci2));
                        value = -search(NodeTypeC.NonPV, pos, ss, ssPos + 1, -rbeta, -rbeta + 1, rdepth);
                        pos.undo_move(move);
                        if (value >= rbeta)
                        {
                            if (st != null) { st.previous = null; StateInfoBroker.Free(); }
                            CheckInfoBroker.Free();
                            MovePickerBroker.Free(mp2);
                            MovesSearchedBroker.Free();
                            return value;
                        }
                    }
                }

                CheckInfoBroker.Free();
                MovePickerBroker.Free(mp2);
            }

            // Step 10. Internal iterative deepening
            if (ttMove == MoveC.MOVE_NONE
                && depth >= IIDDepth[PvNode ? 1 : 0]
                && (PvNode || (!inCheck && ss[ssPos].eval + IIDMargin >= beta)))
            {
                Depth d = (PvNode ? depth - 2 * DepthC.ONE_PLY : depth / 2);

                ss[ssPos].skipNullMove = 1;
                search(PvNode ? NodeTypeC.PV : NodeTypeC.NonPV, pos, ss, ssPos, alpha, beta, d);
                ss[ssPos].skipNullMove = 0;

                tteHasValue = TT.probe(posKey, ref ttePos, out tte);
                ttMove = (tteHasValue) ? tte.move() : MoveC.MOVE_NONE;
            }
            else
            {
                // Re-read (needed as TTEntry is a struct in the port)
                if ((tteHasValue) && (TT.entries[ttePos].key == tte.key)) { tte = TT.entries[ttePos]; }
            }

        split_point_start: // At split points actual search starts from here

            MovePicker mp = MovePickerBroker.GetObject();
            mp.MovePickerC(pos, ttMove, depth, H, ss[ssPos], PvNode ? -ValueC.VALUE_INFINITE : beta, SpNode ? ss[ssPos].sp.mp : null);
            CheckInfo ci = CheckInfoBroker.GetObject();
            ci.CreateCheckInfo(pos);
            futilityBase = ss[ssPos].eval + ss[ssPos].evalMargin;
            singularExtensionNode = !RootNode
                                   && !SpNode
                                   && depth >= SingularExtensionDepth[PvNode ? 1 : 0]
                                   && ttMove != MoveC.MOVE_NONE
                                   && (excludedMove == 0) // Recursive singular search is not allowed
                                   && ((tte.type() & Bound.BOUND_LOWER) != 0) // FIXME: uninitialized!
                                   && tte.depth() >= depth - 3 * DepthC.ONE_PLY;

            // Step 11. Loop through moves
            // Loop through all pseudo-legal moves until no moves remain or a beta cutoff occurs
            while (bestValue < beta
                   && (move = mp.next_move()) != MoveC.MOVE_NONE
                    && !thisThread.cutoff_occurred()
                    && !SignalsStop)
            {
                Debug.Assert(Utils.is_ok_M(move));

                if (move == excludedMove)
                    continue;

                // At root obey the "searchmoves" option and skip moves not listed in Root
                // Move List, as a consequence any illegal move is also skipped. In MultiPV
                // mode we also skip PV moves which have been already searched.

                // If we find none, it means !count
                if (RootNode && (find(RootMoves, PVIdx, RootMoves.Count, move) == -1))
                    continue;

                // At PV and SpNode nodes we want all moves to be legal since the beginning
                if ((PvNode || SpNode) && !pos.pl_move_is_legal(move, ci.pinned))
                    continue;

                if (SpNode)
                {
                    moveCount = ++sp.moveCount;
                    ThreadHelper.lock_release(sp.Lock);
                }
                else
                    moveCount++;

                if (RootNode)
                {
                    SignalsFirstRootMove = (moveCount == 1);

                    if (thisThread == Threads.main_thread() && SearchTime.ElapsedMilliseconds > 2000)
                    {
                        Plug.Write("info depth ");
                        Plug.Write((depth / DepthC.ONE_PLY).ToString());
                        Plug.Write(" currmove ");
                        Plug.Write(Utils.move_to_uci(move, Chess960));
                        Plug.Write(" nodes ");
                        Plug.Write(pos.nodes.ToString());
                        Plug.Write(" currmovenumber ");
                        Plug.Write((moveCount + PVIdx).ToString());
                        Plug.Write(Constants.endl);
                    }
                }

                isPvMove = (PvNode && moveCount <= 1);
                captureOrPromotion = pos.is_capture_or_promotion(move);
                givesCheck = pos.move_gives_check(move, ci);
                dangerous = givesCheck || is_dangerous(pos, move, captureOrPromotion);
                ext = DepthC.DEPTH_ZERO;

                // Step 12. Extend checks and, in PV nodes, also dangerous moves
                if (PvNode && dangerous)
                    ext = DepthC.ONE_PLY;

                else if (givesCheck && pos.see(move, true) >= 0)
                    ext = DepthC.ONE_PLY / 2;

                // Singular extension search. If all moves but one fail low on a search of
                // (alpha-s, beta-s), and just one fails high on (alpha, beta), then that move
                // is singular and should be extended. To verify this we do a reduced search
                // on all the other moves but the ttMove, if result is lower than ttValue minus
                // a margin then we extend ttMove.
                if (singularExtensionNode
                    && (ext == 0)
                    && move == ttMove
                    && pos.pl_move_is_legal(move, ci.pinned))
                {
                    if (Math.Abs(ttValue) < ValueC.VALUE_KNOWN_WIN)
                    {
                        Value rBeta = ttValue - (int)(depth);
                        ss[ssPos].excludedMove = move;
                        ss[ssPos].skipNullMove = 1;
                        value = search(NodeTypeC.NonPV, pos, ss, ssPos, rBeta - 1, rBeta, depth / 2);
                        ss[ssPos].skipNullMove = 0;
                        ss[ssPos].excludedMove = MoveC.MOVE_NONE;
                        if (value < rBeta)
                            ext = DepthC.ONE_PLY;
                    }
                }

                // Update current move (this must be done after singular extension search)
                newDepth = depth - DepthC.ONE_PLY + ext;

                // Step 13. Futility pruning (is omitted in PV nodes)
                if (!PvNode && !inCheck
                    && !captureOrPromotion
                    && !dangerous
                    && move != ttMove
                    && (bestValue > ValueC.VALUE_MATED_IN_MAX_PLY || bestValue == -ValueC.VALUE_INFINITE)
                    && !Utils.is_castle(move))
                {
                    // Move count based pruning
                    if (moveCount >= futility_move_count(depth)
                        && ((threatMove == 0) || !connected_threat(pos, move, threatMove)))
                    {
                        if (SpNode)
                            ThreadHelper.lock_grab(sp.Lock);

                        continue;
                    }

                    // Value based pruning
                    // We illogically ignore reduction condition depth >= 3*ONE_PLY for predicted depth,
                    // but fixing this made program slightly weaker.
                    Depth predictedDepth = newDepth - reduction(PvNode, depth, moveCount);
                    futilityValue = futilityBase + futility_margin(predictedDepth, moveCount)
                                                 + H.gain(pos.piece_moved(move), Utils.to_sq(move));

                    if (futilityValue < beta)
                    {
                        if (SpNode)
                            ThreadHelper.lock_grab(sp.Lock);

                        continue;
                    }

                    // Prune moves with negative SEE at low depths
                    if (predictedDepth < 2 * DepthC.ONE_PLY
                        && pos.see(move, true) < 0)
                    {
                        if (SpNode)
                            ThreadHelper.lock_grab(sp.Lock);

                        continue;
                    }
                }

                // Check for legality only before to do the move
                if (!pos.pl_move_is_legal(move, ci.pinned))
                {
                    moveCount--;
                    continue;
                }

                ss[ssPos].currentMove = move;
                if (!SpNode && !captureOrPromotion && playedMoveCount < 64)
                    movesSearched[playedMoveCount++] = move;

                // Step 14. Make the move
                if (st == null) { st = StateInfoBroker.GetObject(); }
                pos.do_move(move, st, ci, givesCheck);

                // Step 15. Reduced depth search (LMR). If the move fails high will be
                // re-searched at full depth.
                if (
                    !isPvMove
                    && !captureOrPromotion
                    && !dangerous
                    && ss[ssPos].killers0 != move
                    && ss[ssPos].killers1 != move
                    && depth > 3 * DepthC.ONE_PLY
                    && !Utils.is_castle(move))
                {
                    ss[ssPos].reduction = reduction(PvNode, depth, moveCount);
                    Depth d = Math.Max(newDepth - ss[ssPos].reduction, DepthC.ONE_PLY);
                    alpha = SpNode ? sp.alpha : alpha;

                    value = -search(NodeTypeC.NonPV, pos, ss, ssPos + 1, -(alpha + 1), -alpha, d);

                    doFullDepthSearch = (value > alpha && ss[ssPos].reduction != DepthC.DEPTH_ZERO);
                    ss[ssPos].reduction = DepthC.DEPTH_ZERO;
                }
                else
                    doFullDepthSearch = !isPvMove;

                // Step 16. Full depth search, when LMR is skipped or fails high
                if (doFullDepthSearch)
                {
                    alpha = SpNode ? sp.alpha : alpha;
                    value = newDepth < DepthC.ONE_PLY ? -qsearch(NodeTypeC.NonPV, pos, ss, ssPos + 1, -(alpha + 1), -alpha, DepthC.DEPTH_ZERO)
                                               : -search(NodeTypeC.NonPV, pos, ss, ssPos + 1, -(alpha + 1), -alpha, newDepth);
                }

                // Only for PV nodes do a full PV search on the first move or after a fail
                // high, in the latter case search only if value < beta, otherwise let the
                // parent node to fail low with value <= alpha and to try another move.
                if (PvNode && (isPvMove || (value > alpha && (RootNode || value < beta))))
                {
                    value = newDepth < DepthC.ONE_PLY ? -qsearch(NodeTypeC.PV, pos, ss, ssPos + 1, -beta, -alpha, DepthC.DEPTH_ZERO)
                                               : -search(NodeTypeC.PV, pos, ss, ssPos + 1, -beta, -alpha, newDepth);
                }

                // Step 17. Undo move
                pos.undo_move(move);

                Debug.Assert(value > -ValueC.VALUE_INFINITE && value < ValueC.VALUE_INFINITE);

                // Step 18. Check for new best move
                if (SpNode)
                {
                    ThreadHelper.lock_grab(sp.Lock);
                    bestValue = sp.bestValue;
                    alpha = sp.alpha;
                }

                // Finished searching the move. If Signals.stop is true, the search
                // was aborted because the user interrupted the search or because we
                // ran out of time. In this case, the return value of the search cannot
                // be trusted, and we don't update the best move and/or PV.
                if (RootNode && !SignalsStop)
                {
                    int rmPos = find(RootMoves, 0, RootMoves.Count, move);

                    // PV move or new best move ?
                    if (isPvMove || value > alpha)
                    {
                        RootMoves[rmPos].score = value;
                        RootMoves[rmPos].extract_pv_from_tt(pos);

                        // We record how often the best move has been changed in each
                        // iteration. This information is used for time management: When
                        // the best move changes frequently, we allocate some more time.
                        if (!isPvMove && MultiPV == 1)
                            BestMoveChanges++;
                    }
                    else
                        // All other moves but the PV are set to the lowest value, this
                        // is not a problem when sorting becuase sort is stable and move
                        // position in the list is preserved, just the PV is pushed up.
                        RootMoves[rmPos].score = -ValueC.VALUE_INFINITE;

                }

                if (value > bestValue)
                {
                    bestValue = value;
                    bestMove = move;

                    if (PvNode
                        && value > alpha
                        && value < beta) // We want always alpha < beta
                        alpha = value;

                    if (SpNode && !thisThread.cutoff_occurred())
                    {
                        sp.bestValue = value;
                        sp.bestMove = move;
                        sp.alpha = alpha;

                        if (value >= beta)
                            sp.cutoff = true;
                    }
                }

                // Step 19. Check for split
                if (!SpNode
                  && depth >= Threads.min_split_depth()
                  && bestValue < beta
                  && Threads.available_slave_exists(thisThread)
                  && !SignalsStop
                  && !thisThread.cutoff_occurred())
                {
                    bestValue = Threads.split(Constants.FakeSplit, pos, ss, ssPos, alpha, beta, bestValue, ref bestMove, depth, threatMove, moveCount, mp, NT);
                }
            }

            // Step 20. Check for mate and stalemate
            // All legal moves have been searched and if there are no legal moves, it
            // must be mate or stalemate. Note that we can have a false positive in
            // case of Signals.stop or thread.cutoff_occurred() are set, but this is
            // harmless because return value is discarded anyhow in the parent nodes.
            // If we are in a singular extension search then return a fail low score.
            if (moveCount == 0)
            {
                if (st != null) { st.previous = null; StateInfoBroker.Free(); }
                CheckInfoBroker.Free();
                MovePickerBroker.Free(mp);
                MovesSearchedBroker.Free();
                return (excludedMove != 0) ? oldAlpha : inCheck ? Utils.mated_in(ss[ssPos].ply) : ValueC.VALUE_DRAW;
            }

            // If we have pruned all the moves without searching return a fail-low score
            if (bestValue == -ValueC.VALUE_INFINITE)
            {
                Debug.Assert(playedMoveCount == 0);
                bestValue = oldAlpha;
            }

            // Step 21. Update tables
            // Update transposition table entry, killers and history
            if (!SpNode && !SignalsStop && !thisThread.cutoff_occurred())
            {
                move = bestValue <= oldAlpha ? MoveC.MOVE_NONE : bestMove;
                bt = bestValue <= oldAlpha ? Bound.BOUND_UPPER
                     : bestValue >= beta ? Bound.BOUND_LOWER : Bound.BOUND_EXACT;

                TT.store(posKey, value_to_tt(bestValue, ss[ssPos].ply), bt, depth, move, ss[ssPos].eval, ss[ssPos].evalMargin);

                // Update killers and history for non capture cut-off moves
                if (!inCheck
                    && bestValue >= beta
                    && !pos.is_capture_or_promotion(move)
                    )
                {
                    if (move != ss[ssPos].killers0)
                    {
                        ss[ssPos].killers1 = ss[ssPos].killers0;
                        ss[ssPos].killers0 = move;
                    }

                    // Increase history value of the cut-off move
                    Value bonus = (depth * depth);
                    H.add(pos.piece_moved(move), Utils.to_sq(move), bonus);

                    // Decrease history of all the other played non-capture moves
                    for (int i = 0; i < playedMoveCount - 1; i++)
                    {
                        Move m = movesSearched[i];
                        H.add(pos.piece_moved(m), Utils.to_sq(m), -bonus);
                    }
                }
            }

            Debug.Assert(bestValue > -ValueC.VALUE_INFINITE && bestValue < ValueC.VALUE_INFINITE);

            if (st != null) { st.previous = null; StateInfoBroker.Free(); }
            CheckInfoBroker.Free();
            MovePickerBroker.Free(mp);
            MovesSearchedBroker.Free();

            return bestValue;
        }
Beispiel #9
0
        /// Constructors of the MovePicker class. As arguments we pass information
        /// to help it to return the presumably good moves first, to decide which
        /// moves to return (in the quiescence search, for instance, we only want to
        /// search captures, promotions and some checks) and about how important good
        /// move ordering is at the current node.
        internal void MovePickerC(Position p, Move ttm, Depth d, History h,
                       Stack ss, Value beta, MovePicker mpExt)
        {
            pos = p;
            H = h;
            depth = d;
            mpExternal = mpExt;

            Debug.Assert(d > DepthC.DEPTH_ZERO);

            captureThreshold = 0;
            curMovePos = lastMovePos = 0;
            lastBadCapturePos = Constants.MAX_MOVES - 1;

            recaptureSquare = 0;
            lastQuietPos = 0;
            mpos = 0;

            if (p.in_check())
            {
                phase = SequencerC.EVASION;
            }
            else
            {
                phase = SequencerC.MAIN_SEARCH;

                ms[Constants.MAX_MOVES].move = ss.killers0;
                ms[Constants.MAX_MOVES + 1].move = ss.killers1;

                // Consider sligtly negative captures as good if at low depth and far from beta
                if (ss.eval < beta - Constants.PawnValueMidgame && d < 3 * DepthC.ONE_PLY)
                    captureThreshold = -Constants.PawnValueMidgame;

                // Consider negative captures as good if still enough to reach beta
                else if (ss.eval > beta)
                    captureThreshold = beta - ss.eval;
            }

            ttMove = (ttm != 0 && pos.is_pseudo_legal(ttm) ? ttm : MoveC.MOVE_NONE);
            lastMovePos += ((ttMove != MoveC.MOVE_NONE) ? 1 : 0);
        }
Beispiel #10
0
        // split() does the actual work of distributing the work at a node between
        // several available threads. If it does not succeed in splitting the node
        // (because no idle threads are available), the function immediately returns.
        // If splitting is possible, a SplitPoint object is initialized with all the
        // data that must be copied to the helper threads and then helper threads are
        // told that they have been assigned work. This will cause them to instantly
        // leave their idle loops and call search(). When all threads have returned from
        // search() then split() returns.
        internal static void split(
            bool Fake,
            Position pos,
            Stack[] ss,
            int ssPos,
            int alpha,
            int beta,
            ref int bestValue,
            ref int bestMove,
            int depth,
            int threatMove,
            int moveCount,
            MovePicker movePicker,
            int nodeType)
        {
            Debug.Assert(bestValue <= alpha && alpha < beta && beta <= ValueC.VALUE_INFINITE);
            Debug.Assert(pos.pos_is_ok());
            Debug.Assert(bestValue > -ValueC.VALUE_INFINITE);
            Debug.Assert(depth > DepthC.DEPTH_ZERO);

            var thisThread = pos.this_thread();

            Debug.Assert(thisThread.searching);
            Debug.Assert(thisThread.splitPointsSize < Constants.MAX_SPLITPOINTS_PER_THREAD);

            // Pick the next available split point from the split point stack
            var sp = thisThread.splitPoints[thisThread.splitPointsSize];

            sp.parentSplitPoint = thisThread.activeSplitPoint;
            sp.master = thisThread;
            sp.cutoff = false;
            sp.slavesMask = 1UL << thisThread.idx;
#if ACTIVE_REPARENT
            sp.allSlavesRunning = true;
#endif

            sp.depth = depth;
            sp.bestMove = bestMove;
            sp.threatMove = threatMove;
            sp.alpha = alpha;
            sp.beta = beta;
            sp.nodeType = nodeType;
            sp.bestValue = bestValue;
            sp.movePicker = movePicker;
            sp.moveCount = moveCount;
            sp.pos = pos;
            sp.nodes = 0;
            sp.ss = ss;
            sp.ssPos = ssPos;

            // Try to allocate available threads and ask them to start searching setting
            // 'searching' flag. This must be done under lock protection to avoid concurrent
            // allocation of the same slave by another master.
            ThreadHelper.lock_grab(splitLock);
            ThreadHelper.lock_grab(sp.Lock);

            thisThread.splitPointsSize++;
            thisThread.activeSplitPoint = sp;
            thisThread.activePosition = null;

            var slavesCnt = 1; // Master is always included
            Thread slave;
            while ((slave = Threads.available_slave(thisThread)) != null
                && ++slavesCnt <= Threads.maxThreadsPerSplitPoint && !Fake)
            {
                sp.slavesMask |= 1UL << slave.idx;
                slave.activeSplitPoint = sp;
                slave.searching = true; // Slave leaves idle_loop()

                slave.notify_one(); // Could be sleeping
            }

            ThreadHelper.lock_release(sp.Lock);
            ThreadHelper.lock_release(splitLock);

            // Everything is set up. The master thread enters the idle loop, from which
            // it will instantly launch a search, because its searching flag is set.
            // We pass the split point as a parameter to the idle loop, which means that
            // the thread will return from the idle loop when all slaves have finished
            // their work at this split point.
            if (slavesCnt > 1 || Fake)
            {
                thisThread.base_idle_loop(null); // Force a call to base class idle_loop()

                // In helpful master concept a master can help only a sub-tree of its split
                // point, and because here is all finished is not possible master is booked.
                Debug.Assert(!thisThread.searching);
                Debug.Assert(thisThread.activePosition == null);
            }

            // We have returned from the idle loop, which means that all threads are
            // finished. Note that setting searching and decreasing activeSplitPoints is
            // done under lock protection to avoid a race with Thread::is_available_to().
            ThreadHelper.lock_grab(splitLock);
            ThreadHelper.lock_grab(sp.Lock); // To protect sp->nodes

            thisThread.searching = true;
            thisThread.splitPointsSize--;
            thisThread.activeSplitPoint = sp.parentSplitPoint;
            thisThread.activePosition = pos;
            pos.nodes += sp.nodes;
            bestMove = sp.bestMove;
            bestValue = sp.bestValue;

            ThreadHelper.lock_release(sp.Lock);
            ThreadHelper.lock_release(splitLock);
        }
Beispiel #11
0
        // search<>() is the main search function for both PV and non-PV nodes and for
        // normal and SplitPoint nodes. When called just after a split point the search
        // is simpler because we have already probed the hash table, done a null move
        // search, and searched the first move before splitting, we don't have to repeat
        // all this work again. We also don't need to store anything to the hash table
        // here: This is taken care of after we return from the split point.
        internal static int search(int NT, Position pos, Stack[] ss, int ssPos, int alpha, int beta, int depth)
        {
            var PvNode = (NT == NodeTypeC.PV || NT == NodeTypeC.Root || NT == NodeTypeC.SplitPointPV
                          || NT == NodeTypeC.SplitPointRoot);
            var SpNode = (NT == NodeTypeC.SplitPointPV || NT == NodeTypeC.SplitPointNonPV
                          || NT == NodeTypeC.SplitPointRoot);
            var RootNode = (NT == NodeTypeC.Root || NT == NodeTypeC.SplitPointRoot);

            Debug.Assert(alpha >= -ValueC.VALUE_INFINITE && alpha < beta && beta <= ValueC.VALUE_INFINITE);
            Debug.Assert((PvNode || alpha == beta - 1));
            Debug.Assert(depth > DepthC.DEPTH_ZERO);

            var ms = MovesSearchedBroker.GetObject();
            var movesSearched = ms.movesSearched;

            StateInfo st = null;
            var tte = TT.StaticEntry;
            var tteHasValue = false;
            uint ttePos = 0;
            ulong posKey = 0;
            int ttMove, move, excludedMove, bestMove, threatMove;
            int ext, newDepth;
            int bestValue, value, ttValue;
            int eval = 0, nullValue, futilityValue;
            bool inCheck, givesCheck, pvMove, singularExtensionNode;
            bool captureOrPromotion, dangerous, doFullDepthSearch;
            int moveCount = 0, playedMoveCount = 0;
            SplitPoint sp = null;

            // Step 1. Initialize node
            var thisThread = pos.this_thread();
            //var threatExtension = false;
            inCheck = pos.in_check();
            
            if (SpNode)
            {
                sp = ss[ssPos].sp;
                bestMove = sp.bestMove;
                threatMove = sp.threatMove;
                bestValue = sp.bestValue;
                ttMove = excludedMove = MoveC.MOVE_NONE;
                ttValue = ValueC.VALUE_NONE;

                Debug.Assert(sp.bestValue > -ValueC.VALUE_INFINITE && sp.moveCount > 0);

                goto split_point_start;
            }

            bestValue = -ValueC.VALUE_INFINITE;
            ss[ssPos].currentMove = threatMove = ss[ssPos + 1].excludedMove = bestMove = MoveC.MOVE_NONE;
            ss[ssPos].ply = ss[ssPos - 1].ply + 1;
            ss[ssPos + 1].skipNullMove = 0;
            ss[ssPos + 1].reduction = DepthC.DEPTH_ZERO;
            ss[ssPos + 2].killers0 = ss[ssPos + 2].killers1 = MoveC.MOVE_NONE;

            // Used to send selDepth info to GUI
            if (PvNode && thisThread.maxPly < ss[ssPos].ply)
            {
                thisThread.maxPly = ss[ssPos].ply;
            }
            
            if (!RootNode)
            {
                // Step 2. Check for aborted search and immediate draw
                if ((SignalsStop || pos.is_draw(false) || ss[ssPos].ply > Constants.MAX_PLY))
                {
                    MovesSearchedBroker.Free();
                    return DrawValue[pos.sideToMove];
                }

                // Step 3. Mate distance pruning. Even if we mate at the next move our score
                // would be at best mate_in(ss->ply+1), but if alpha is already bigger because
                // a shorter mate was found upward in the tree then there is no need to search
                // further, we will never beat current alpha. Same logic but with reversed signs
                // applies also in the opposite condition of being mated instead of giving mate,
                // in this case return a fail-high score.
                alpha = Math.Max(Utils.mated_in(ss[ssPos].ply), alpha);
                beta = Math.Min(Utils.mate_in(ss[ssPos].ply + 1), beta);
                if (alpha >= beta)
                {
                    MovesSearchedBroker.Free();
                    return alpha;
                }
            }

            // Step 4. Transposition table lookup
            // We don't want the score of a partial search to overwrite a previous full search
            // TT value, so we use a different position key in case of an excluded move.
            excludedMove = ss[ssPos].excludedMove;
            posKey = (excludedMove != 0) ? pos.exclusion_key() : pos.key();
            tteHasValue = TT.probe(posKey, ref ttePos, out tte);
            ttMove = RootNode ? RootMoves[PVIdx].pv[0] : tteHasValue ? tte.move() : MoveC.MOVE_NONE;
            ttValue = tteHasValue ? value_from_tt(tte.value(), ss[ssPos].ply) : ValueC.VALUE_NONE;

            // At PV nodes we check for exact scores, while at non-PV nodes we check for
            // a fail high/low. Biggest advantage at probing at PV nodes is to have a
            // smooth experience in analysis mode. We don't probe at Root nodes otherwise
            // we should also update RootMoveList to avoid bogus output.
            if (!RootNode 
                && 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)))
            {
                Debug.Assert(ttValue != ValueC.VALUE_NONE); // Due to depth > DEPTH_NONE

                TT.table[ttePos].set_generation(TT.generation);
                ss[ssPos].currentMove = ttMove; // Can be MOVE_NONE

                if (ttValue >= beta && (ttMove != 0) && !pos.is_capture_or_promotion(ttMove)
                    && ttMove != ss[ssPos].killers0)
                {
                    ss[ssPos].killers1 = ss[ssPos].killers0;
                    ss[ssPos].killers0 = ttMove;
                }

                MovesSearchedBroker.Free();
                return ttValue;
            }

            // Step 5. Evaluate the position statically and update parentSplitPoint's gain statistics
            if (inCheck)
            {
                ss[ssPos].staticEval = ss[ssPos].evalMargin = eval = ValueC.VALUE_NONE;
            }
            else if (tteHasValue)
            {
                // Never assume anything on values stored in TT
                if ((ss[ssPos].staticEval = eval = tte.eval_value()) == ValueC.VALUE_NONE
                    || (ss[ssPos].evalMargin = tte.eval_margin()) == ValueC.VALUE_NONE)
                {
                    eval = ss[ssPos].staticEval = Evaluate.do_evaluate(false, pos, ref ss[ssPos].evalMargin);
                }

                // Can ttValue be used as a better position evaluation?
                if (ttValue != ValueC.VALUE_NONE)
                {
                    if ((((tte.type() & Bound.BOUND_LOWER) != 0) && ttValue > eval)
                        || (((tte.type() & Bound.BOUND_UPPER) != 0) && ttValue < eval))
                    {
                        eval = ttValue;
                    }
                }
            }
            else
            {
                eval = ss[ssPos].staticEval = Evaluate.do_evaluate(false, pos, ref ss[ssPos].evalMargin);
                TT.store(
                    posKey,
                    ValueC.VALUE_NONE,
                    Bound.BOUND_NONE,
                    DepthC.DEPTH_NONE,
                    MoveC.MOVE_NONE,
                    ss[ssPos].staticEval,
                    ss[ssPos].evalMargin);
            }

            // Update gain for the parentSplitPoint non-capture move given the static position
            // evaluation before and after the move.
            if ((move = ss[ssPos - 1].currentMove) != MoveC.MOVE_NULL && ss[ssPos - 1].staticEval != ValueC.VALUE_NONE
                && ss[ssPos].staticEval != ValueC.VALUE_NONE && (pos.captured_piece_type() == 0) && Utils.type_of_move(move) == MoveTypeC.NORMAL)
            {
                var to = Utils.to_sq(move);
                H.update_gain(pos.piece_on(to), to, -ss[ssPos - 1].staticEval - ss[ssPos].staticEval);
            }

            // Step 6. Razoring (is omitted in PV nodes)
            if (!PvNode && !inCheck && depth < 4 * DepthC.ONE_PLY && eval + razor_margin(depth) < beta
                && ttMove == MoveC.MOVE_NONE && Math.Abs(beta) < ValueC.VALUE_MATE_IN_MAX_PLY
                && !pos.pawn_on_7th(pos.sideToMove))
            {
                var rbeta = beta - razor_margin(depth);
                var v = qsearch(NodeTypeC.NonPV, false, pos, ss, ssPos, rbeta - 1, rbeta, DepthC.DEPTH_ZERO);
                if (v < rbeta)
                {
                    // Logically we should return (v + razor_margin(depth)), but
                    // surprisingly this did slightly weaker in tests.
                    MovesSearchedBroker.Free();
                    return v;
                }
            }

            // Step 7. Static null move pruning (is omitted in PV nodes)
            // We're betting that the opponent doesn't have a move that will reduce
            // the score by more than futility_margin(depth) if we do a null move.
            if (!PvNode && !inCheck && (ss[ssPos].skipNullMove == 0) && depth < 4 * DepthC.ONE_PLY
                && Math.Abs(beta) < ValueC.VALUE_MATE_IN_MAX_PLY && eval - FutilityMargins[depth][0] >= beta
                && (pos.non_pawn_material(pos.sideToMove) != 0))
            {
                MovesSearchedBroker.Free();
                return eval - FutilityMargins[depth][0];
            }

            // Step 8. Null move search with verification search (is omitted in PV nodes)
            if (!PvNode && !inCheck && (ss[ssPos].skipNullMove == 0) && depth > DepthC.ONE_PLY && eval >= beta
                && Math.Abs(beta) < ValueC.VALUE_MATE_IN_MAX_PLY && (pos.non_pawn_material(pos.sideToMove) != 0))
            {
                ss[ssPos].currentMove = MoveC.MOVE_NULL;

                // Null move dynamic reduction based on depth
                Depth R = 3 * DepthC.ONE_PLY + depth / 4;

                // Null move dynamic reduction based on value
                if (eval - Constants.PawnValueMidgame > beta)
                {
                    R += DepthC.ONE_PLY;
                }

                if (st == null)
                {
                    st = StateInfoBroker.GetObject();
                }
                pos.do_null_move(st);
                ss[ssPos + 1].skipNullMove = 1;
                
                nullValue = depth - R < DepthC.ONE_PLY ? -qsearch(NodeTypeC.NonPV, false, pos, ss, ssPos + 1, -beta, -alpha, DepthC.DEPTH_ZERO)
                                      : -search(NodeTypeC.NonPV, pos, ss, ssPos + 1, -beta, -alpha, depth - R);

                ss[ssPos + 1].skipNullMove = 0;
                pos.undo_null_move(st);

                if (nullValue >= beta)
                {
                    // Do not return unproven mate scores
                    if (nullValue >= ValueC.VALUE_MATE_IN_MAX_PLY)
                    {
                        nullValue = beta;
                    }

                    if (depth < 6 * DepthC.ONE_PLY)
                    {
                        if (st != null)
                        {
                            st.previous = null;
                            StateInfoBroker.Free();
                        }
                        MovesSearchedBroker.Free();
                        return nullValue;
                    }

                    // Do verification search at high depths
                    ss[ssPos].skipNullMove = 1;
                    var v = search(NodeTypeC.NonPV, pos, ss, ssPos, alpha, beta, depth - R);
                    ss[ssPos].skipNullMove = 0;

                    if (v >= beta)
                    {
                        if (st != null)
                        {
                            st.previous = null;
                            StateInfoBroker.Free();
                        }
                        MovesSearchedBroker.Free();
                        return nullValue;
                    }
                }
                else
                {
                    // The null move failed low, which means that we may be faced with
                    // some kind of threat. If the previous move was reduced, check if
                    // the move that refuted the null move was somehow connected to the
                    // the move that refuted the null move was somehow connected to the
                    // move which was reduced. If a connection is found extend moves that
                    // defend against threat.
                    threatMove = ss[ssPos + 1].currentMove;

                    if (depth < 5 * DepthC.ONE_PLY && (ss[ssPos - 1].reduction != 0) && threatMove != MoveC.MOVE_NONE
                        && allows(pos, ss[ssPos - 1].currentMove, threatMove))
                    {
                        //threatExtension = true;
                        
                        if (st != null)
                        {
                            st.previous = null;
                            StateInfoBroker.Free();
                        }
                        MovesSearchedBroker.Free();
                        return beta - 1;
                    }
                }
            }

            // Step 9. ProbCut (is omitted in PV nodes)
            // If we have a very good capture (i.e. SEE > seeValues[captured_piece_type])
            // and a reduced search returns a value much above beta, we can (almost) safely
            // prune the previous move.
            if (!PvNode && !inCheck && excludedMove == MoveC.MOVE_NONE && depth >= 4 * DepthC.ONE_PLY + DepthC.ONE_PLY
                && (ss[ssPos].skipNullMove == 0) && Math.Abs(beta) < ValueC.VALUE_MATE_IN_MAX_PLY)
            {
                var rbeta = beta + 200;
                var rdepth = depth - DepthC.ONE_PLY - 3 * DepthC.ONE_PLY;

                Debug.Assert(rdepth >= DepthC.ONE_PLY);
                Debug.Assert(ss[ssPos - 1].currentMove != MoveC.MOVE_NONE);
                Debug.Assert(ss[ssPos - 1].currentMove != MoveC.MOVE_NULL);

                var mp2 = MovePickerBroker.GetObject();
                mp2.MovePickerC(pos, ttMove, H, pos.captured_piece_type());
                var ci2 = CheckInfoBroker.GetObject();
                ci2.CreateCheckInfo(pos);

                while ((move = mp2.next_move()) != MoveC.MOVE_NONE)
                {
                    if (pos.pl_move_is_legal(move, ci2.pinned))
                    {
                        ss[ssPos].currentMove = move;
                        if (st == null)
                        {
                            st = StateInfoBroker.GetObject();
                        }
                        pos.do_move(move, st, ci2, pos.move_gives_check(move, ci2));
                        value = -search(NodeTypeC.NonPV, pos, ss, ssPos + 1, -rbeta, -rbeta + 1, rdepth);
                        pos.undo_move(move);
                        if (value >= rbeta)
                        {
                            if (st != null)
                            {
                                st.previous = null;
                                StateInfoBroker.Free();
                            }
                            CheckInfoBroker.Free();
                            MovePickerBroker.Free(mp2);
                            MovesSearchedBroker.Free();
                            return value;
                        }
                    }
                }

                CheckInfoBroker.Free();
                MovePickerBroker.Free(mp2);
            }

            // Step 10. Internal iterative deepening
            if (ttMove == MoveC.MOVE_NONE && depth >= (PvNode ? 5 * DepthC.ONE_PLY : 8 * DepthC.ONE_PLY)
                && (PvNode || (!inCheck && ss[ssPos].staticEval + 256 >= beta)))
            {
                var d = (PvNode ? depth - 2 * DepthC.ONE_PLY : depth / 2);

                ss[ssPos].skipNullMove = 1;
                search(PvNode ? NodeTypeC.PV : NodeTypeC.NonPV, pos, ss, ssPos, alpha, beta, d);
                ss[ssPos].skipNullMove = 0;

                tteHasValue = TT.probe(posKey, ref ttePos, out tte);
                ttMove = (tteHasValue) ? tte.move() : MoveC.MOVE_NONE;
            }
            else
            {
                // Re-read (needed as TTEntry is a struct in the port)
                if ((tteHasValue) && (TT.table[ttePos].key == tte.key))
                {
                    tte = TT.table[ttePos];
                }
            }

            split_point_start: // At split points actual search starts from here

            var mp = MovePickerBroker.GetObject();
            mp.MovePickerC(
                pos,
                ttMove,
                depth,
                H,
                ss[ssPos],
                PvNode ? -ValueC.VALUE_INFINITE : beta,
                SpNode ? ss[ssPos].sp.movePicker : null);
            var ci = CheckInfoBroker.GetObject();
            ci.CreateCheckInfo(pos);
            
            value = bestValue; // Workaround a bogus 'uninitialized' warning under gcc
            singularExtensionNode = !RootNode && !SpNode && depth >= (PvNode ? 6 * DepthC.ONE_PLY : 8 * DepthC.ONE_PLY)
                                    && ttMove != MoveC.MOVE_NONE && (excludedMove == 0)
                                    // Recursive singular search is not allowed
                                    && ((tte.type() & Bound.BOUND_LOWER) != 0) // FIXME: uninitialized!
                                    && tte.depth() >= depth - 3 * DepthC.ONE_PLY;

            // Step 11. Loop through moves
            // Loop through all pseudo-legal moves until no moves remain or a beta cutoff occurs
            while ((move = mp.next_move()) != MoveC.MOVE_NONE && !thisThread.cutoff_occurred()
                   && !SignalsStop)
            {
                Debug.Assert(Utils.is_ok_M(move));

                if (move == excludedMove)
                {
                    continue;
                }

                // At root obey the "searchmoves" option and skip moves not listed in Root
                // Move List, as a consequence any illegal move is also skipped. In MultiPV
                // mode we also skip PV moves which have been already searched.

                // If we find none, it means !count
                if (RootNode && (find(RootMoves, PVIdx, RootMoves.Count, move) == -1))
                {
                    continue;
                }

                if (SpNode)
                {
                    // Shared counter cannot be decremented later if move turns out to be illegal
                    if (!pos.pl_move_is_legal(move, ci.pinned))
                    {
                        continue;
                    }

                    moveCount = ++sp.moveCount;
                    ThreadHelper.lock_release(sp.Lock);
                }
                else
                {
                    moveCount++;
                }

                if (RootNode)
                {
                    SignalsFirstRootMove = (moveCount == 1);

                    if (thisThread == Threads.main_thread() && SearchTime.ElapsedMilliseconds > 3000)
                    {
                        Plug.Write("info depth ");
                        Plug.Write((depth / DepthC.ONE_PLY).ToString());
                        Plug.Write(" currmove ");
                        Plug.Write(Utils.move_to_uci(move, pos.chess960));
                        Plug.Write(" nodes ");
                        Plug.Write(pos.nodes.ToString());
                        Plug.Write(" currmovenumber ");
                        Plug.Write((moveCount + PVIdx).ToString());
                        Plug.Write(Constants.endl);
                    }
                }

                ext = DepthC.DEPTH_ZERO;
                captureOrPromotion = pos.is_capture_or_promotion(move);
                givesCheck = pos.move_gives_check(move, ci);
                dangerous = givesCheck 
                            || pos.is_passed_pawn_push(move)
                            || Utils.type_of_move(move) == MoveTypeC.CASTLING
                            || (captureOrPromotion // Entering a pawn endgame?
                                    && Utils.type_of(pos.piece_on(Utils.to_sq(move))) != PieceTypeC.PAWN
                                    && Utils.type_of_move(move) == MoveTypeC.NORMAL
                                    && (pos.non_pawn_material(ColorC.WHITE) + pos.non_pawn_material(ColorC.BLACK) 
                                        - Position.PieceValue[PhaseC.MG][pos.piece_on(Utils.to_sq(move))] == ValueC.VALUE_ZERO));

                // Step 12. Extend checks and, in PV nodes, also dangerous moves
                if (PvNode && dangerous)
                {
                    ext = DepthC.ONE_PLY;
                }
                // else if (threatExtension && refutes(pos, move, threatMove))
                // {
                // ext = DepthC.ONE_PLY;
                // }
                else if (givesCheck && pos.see(move, true) >= 0)
                {
                    ext = DepthC.ONE_PLY / 2;
                }

                // Singular extension search. If all moves but one fail low on a search of
                // (alpha-s, beta-s), and just one fails high on (alpha, beta), then that move
                // is singular and should be extended. To verify this we do a reduced search
                // on all the other moves but the ttMove, if result is lower than ttValue minus
                // a margin then we extend ttreMove.
                if (singularExtensionNode && move == ttMove && (ext == 0) && pos.pl_move_is_legal(move, ci.pinned))
                {
                    Debug.Assert(ttValue != ValueC.VALUE_NONE);

                    var rBeta = ttValue - depth;
                    ss[ssPos].excludedMove = move;
                    ss[ssPos].skipNullMove = 1;
                    value = search(NodeTypeC.NonPV, pos, ss, ssPos, rBeta - 1, rBeta, depth / 2);
                    ss[ssPos].skipNullMove = 0;
                    ss[ssPos].excludedMove = MoveC.MOVE_NONE;
                    if (value < rBeta)
                    {
                        ext = DepthC.ONE_PLY;
                    }
                }

                // Update current move (this must be done after singular extension search)
                newDepth = depth - DepthC.ONE_PLY + ext;

                // Step 13. Futility pruning (is omitted in PV nodes)
                if (!PvNode
                    && !captureOrPromotion
                    && !inCheck 
                    && !dangerous 
                    && move != ttMove
                    && (bestValue > ValueC.VALUE_MATED_IN_MAX_PLY || (bestValue == -ValueC.VALUE_INFINITE && alpha > ValueC.VALUE_MATED_IN_MAX_PLY)))
                {
                    // Move count based pruning
                    if (depth < 16 * DepthC.ONE_PLY
                        && moveCount >= FutilityMoveCounts[depth]
                        && ((threatMove == 0) || !refutes(pos, move, threatMove)))
                    {
                        if (SpNode)
                        {
                            ThreadHelper.lock_grab(sp.Lock);
                        }

                        continue;
                    }

                    // Value based pruning
                    // We illogically ignore reduction condition depth >= 3*ONE_PLY for predicted depth,
                    // but fixing this made program slightly weaker.
                    var predictedDepth = newDepth - reduction(PvNode, depth, moveCount);
                    futilityValue = ss[ssPos].staticEval + ss[ssPos].evalMargin + futility_margin(predictedDepth, moveCount)
                                    + H.gain(pos.piece_moved(move), Utils.to_sq(move));

                    if (futilityValue < beta)
                    {
                        if (SpNode)
                        {
                            ThreadHelper.lock_grab(sp.Lock);
                        }

                        continue;
                    }

                    // Prune moves with negative SEE at low depths
                    if (predictedDepth < 2 * DepthC.ONE_PLY && pos.see(move, true) < 0)
                    {
                        if (SpNode)
                        {
                            ThreadHelper.lock_grab(sp.Lock);
                        }

                        continue;
                    }
                }

                // Check for legality only before to do the move
                if (!RootNode && !SpNode && !pos.pl_move_is_legal(move, ci.pinned))
                {
                    moveCount--;
                    continue;
                }

                pvMove = (PvNode && moveCount == 1);
                ss[ssPos].currentMove = move;
                if (!SpNode && !captureOrPromotion && playedMoveCount < 64)
                {
                    movesSearched[playedMoveCount++] = move;
                }

                // Step 14. Make the move
                if (st == null)
                {
                    st = StateInfoBroker.GetObject();
                }
                pos.do_move(move, st, ci, givesCheck);

                // Step 15. Reduced depth search (LMR). If the move fails high will be
                // re-searched at full depth.
                if (depth > 3 * DepthC.ONE_PLY
                    && !pvMove 
                    && !captureOrPromotion 
                    && !dangerous
                    && move != ttMove
                    && move != ss[ssPos].killers0
                    && move != ss[ssPos].killers1)
                {
                    ss[ssPos].reduction = reduction(PvNode, depth, moveCount);
                    var d = Math.Max(newDepth - ss[ssPos].reduction, DepthC.ONE_PLY);
                    alpha = SpNode ? sp.alpha : alpha;

                    value = -search(NodeTypeC.NonPV, pos, ss, ssPos + 1, -(alpha + 1), -alpha, d);

                    doFullDepthSearch = (value > alpha && ss[ssPos].reduction != DepthC.DEPTH_ZERO);
                    ss[ssPos].reduction = DepthC.DEPTH_ZERO;
                }
                else
                {
                    doFullDepthSearch = !pvMove;
                }

                // Step 16. Full depth search, when LMR is skipped or fails high
                if (doFullDepthSearch)
                {
                    alpha = SpNode ? sp.alpha : alpha;
                    value = newDepth < DepthC.ONE_PLY 
                            ? -qsearch(NodeTypeC.NonPV, givesCheck, pos, ss, ssPos + 1, -(alpha + 1), -alpha, DepthC.DEPTH_ZERO)
                            : -search(NodeTypeC.NonPV, pos, ss, ssPos + 1, -(alpha + 1), -alpha, newDepth);
                }

                // Only for PV nodes do a full PV search on the first move or after a fail
                // high, in the latter case search only if value < beta, otherwise let the
                // parentSplitPoint node to fail low with value <= alpha and to try another move.
                if (PvNode && (pvMove || (value > alpha && (RootNode || value < beta))))
                {
                    value = newDepth < DepthC.ONE_PLY
                                ? -qsearch(NodeTypeC.PV, givesCheck, pos, ss, ssPos + 1, -beta, -alpha, DepthC.DEPTH_ZERO)
                                : -search(NodeTypeC.PV, pos, ss, ssPos + 1, -beta, -alpha, newDepth);
                }

                // Step 17. Undo move
                pos.undo_move(move);

                Debug.Assert(value > -ValueC.VALUE_INFINITE && value < ValueC.VALUE_INFINITE);

                // Step 18. Check for new best move
                if (SpNode)
                {
                    ThreadHelper.lock_grab(sp.Lock);
                    bestValue = sp.bestValue;
                    alpha = sp.alpha;
                }

                // Finished searching the move. If Signals.stop is true, the search
                // was aborted because the user interrupted the search or because we
                // ran out of time. In this case, the return value of the search cannot
                // be trusted, and we don't update the best move and/or PV.
                if (SignalsStop || thisThread.cutoff_occurred())
                {
                    if (st != null)
                    {
                        st.previous = null;
                        StateInfoBroker.Free();
                    }
                    CheckInfoBroker.Free();
                    MovePickerBroker.Free(mp);
                    MovesSearchedBroker.Free();

                    return value; // To avoid returning VALUE_INFINITE
                }

                // Finished searching the move. If Signals.stop is true, the search
                // was aborted because the user interrupted the search or because we
                // ran out of time. In this case, the return value of the search cannot
                // be trusted, and we don't update the best move and/or PV.
                if (RootNode)
                {
                    var rmPos = find(RootMoves, 0, RootMoves.Count, move);

                    // PV move or new best move ?
                    if (pvMove || value > alpha)
                    {
                        RootMoves[rmPos].score = value;
                        RootMoves[rmPos].extract_pv_from_tt(pos);

                        // We record how often the best move has been changed in each
                        // iteration. This information is used for time management: When
                        // the best move changes frequently, we allocate some more time.
                        if (!pvMove)
                        {
                            BestMoveChanges++;
                        }
                    }
                    else
                    {
                        // All other moves but the PV are set to the lowest value, this
                        // is not a problem when sorting becuase sort is stable and move
                        // position in the list is preserved, just the PV is pushed up.
                        RootMoves[rmPos].score = -ValueC.VALUE_INFINITE;
                    }
                }

                if (value > bestValue)
                {
                    bestValue = value;
                    if (SpNode) sp.bestValue = value;

                    if (value > alpha)
                    {
                        bestMove = move;
                        if (SpNode) sp.bestMove = move;

                        if (PvNode && value < beta)
                        {
                            alpha = value; // Update alpha here! Always alpha < beta
                            if (SpNode) sp.alpha = value;
                        }
                        else 
                        {
                            Debug.Assert(value >= beta); // Fail high

                            if (SpNode) sp.cutoff = true;
                            break;
                        }
                    }
                }

                // Step 19. Check for split
                if (!SpNode 
                    && depth >= Threads.minimumSplitDepth 
                    && Threads.available_slave(thisThread) != null
                    && thisThread.splitPointsSize < Constants.MAX_SPLITPOINTS_PER_THREAD)
                {
                    Debug.Assert(bestValue < beta);

                    Threads.split(
                        Constants.FakeSplit,
                        pos,
                        ss,
                        ssPos,
                        alpha,
                        beta,
                        ref bestValue,
                        ref bestMove,
                        depth,
                        threatMove,
                        moveCount,
                        mp,
                        NT);

                    if (bestValue >= beta)
                    {
                        break;
                    }
                }
            }

            // Step 20. Check for mate and stalemate
            // All legal moves have been searched and if there are no legal moves, it
            // must be mate or stalemate. Note that we can have a false positive in
            // case of Signals.stop or thread.cutoff_occurred() are set, but this is
            // harmless because return value is discarded anyhow in the parentSplitPoint nodes.
            // If we are in a singular extension search then return a fail low score.
            // A split node has at least one move, the one tried before to be splitted.
            if (!SpNode && moveCount == 0)
            {
                if (st != null)
                {
                    st.previous = null;
                    StateInfoBroker.Free();
                }
                CheckInfoBroker.Free();
                MovePickerBroker.Free(mp);
                MovesSearchedBroker.Free();
                return (excludedMove != 0) ? alpha : inCheck ? Utils.mated_in(ss[ssPos].ply) : DrawValue[pos.sideToMove];
            }

            // If we have pruned all the moves without searching return a fail-low score
            if (bestValue == -ValueC.VALUE_INFINITE)
            {
                Debug.Assert(playedMoveCount == 0);
                bestValue = alpha;
            }

            if (bestValue >= beta) // Failed high
            {
                TT.store(
                    posKey,
                    value_to_tt(bestValue, ss[ssPos].ply),
                    Bound.BOUND_LOWER,
                    depth,
                    bestMove,
                    ss[ssPos].staticEval,
                    ss[ssPos].evalMargin);

                if (!pos.is_capture_or_promotion(bestMove) && !inCheck)
                {
                    if (bestMove != ss[ssPos].killers0)
                    {
                        ss[ssPos].killers1 = ss[ssPos].killers0;
                        ss[ssPos].killers0 = bestMove;
                    }

                    // Increase history value of the cut-off move
                    var bonus = (depth * depth);
                    H.add(pos.piece_moved(bestMove), Utils.to_sq(bestMove), bonus);

                    // Decrease history of all the other played non-capture moves
                    for (var i = 0; i < playedMoveCount - 1; i++)
                    {
                        var m = movesSearched[i];
                        H.add(pos.piece_moved(m), Utils.to_sq(m), -bonus);
                    }
                }
            }
            else // Failed low or PV search
            {
                TT.store(posKey, value_to_tt(bestValue, ss[ssPos].ply), PvNode && bestMove != MoveC.MOVE_NONE ? Bound.BOUND_EXACT : Bound.BOUND_UPPER, depth, 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);
            MovesSearchedBroker.Free();

            return bestValue;
        }
Beispiel #12
0
        // Search::validmoves() will return the list of all valid moves for a 'square' in UCI notation - all valid moves for the piece occupying that square
        // The list will be empty if no square is given or there is no piece on that square or the piece have no possible moves
        internal static void validmoves(Position pos, Stack<string> stack)
        {
            if (stack.Count > 0)
            {
                var squareFromString = stack.Pop();

                var st = new StateInfo();
                var mlist = MListBroker.GetObject();
                mlist.pos = 0;
                Movegen.generate_legal(pos, mlist.moves, ref mlist.pos);

                var firstOne = true;
                for (var i = 0; i < mlist.pos; ++i)
                {
                    var ms = mlist.moves[i];
                    var m = ms.move;
                    var from = ((m >> 6) & 0x3F);
                    if (Utils.square_to_string(from) == squareFromString)
                    {
                        if (!firstOne)
                        {
                            Plug.Write(" ");
                        }
                        Plug.Write(Utils.move_to_uci(m, false));
                        firstOne = false;
                    }
                }
                MListBroker.Free();
            }
            Plug.Write(Constants.endl);
        }
Beispiel #13
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;
        }