コード例 #1
0
        /// <summary>
        /// Returns the estimated material exchange value of the given move on the
        /// given position as determined by static analysis.
        /// </summary>
        /// <param name="position">The position the move is to be played on.</param>
        /// <param name="move">The move to evaluate.</param>
        /// <returns>The estimated material exchange value of the move.</returns>
        private static Int32 EvaluateStaticExchange(Position position, Int32 move)
        {
            Int32 from    = Move.From(move);
            Int32 to      = Move.To(move);
            Int32 piece   = Move.Piece(move);
            Int32 capture = Move.Capture(move);

            position.Bitboard[piece]  ^= 1UL << from;
            position.OccupiedBitboard ^= 1UL << from;
            position.Square[to]        = piece;

            Int32 value = 0;

            if (Move.IsPromotion(move))
            {
                Int32 promotion = Move.Special(move);
                position.Square[to] = promotion;
                value += PieceValue[promotion] - PieceValue[Piece.Pawn];
            }
            value += PieceValue[capture] - EvaluateStaticExchange(position, 1 - position.SideToMove, to);

            position.Bitboard[piece]  ^= 1UL << from;
            position.OccupiedBitboard ^= 1UL << from;
            position.Square[to]        = capture;

            return(value);
        }
コード例 #2
0
        /// <summary>
        /// Unmakes the given move on the visual position.
        /// </summary>
        /// <param name="move">The move to unmake.</param>
        public static void Unmake(Int32 move)
        {
            Int32 from    = Move.From(move);
            Int32 to      = Move.To(move);
            Point initial = new Point(Position.File(to) * SquareWidth, Position.Rank(to) * SquareWidth);
            Point final   = new Point(Position.File(from) * SquareWidth, Position.Rank(from) * SquareWidth);

            // Remove captured pieces.
            lock (PiecesLock)
                _pieces.Add(new VisualPiece(
                                Move.Capture(move),
                                Position.File(to) * SquareWidth,
                                Position.Rank(to) * SquareWidth));

            // Perform special moves.
            switch (Move.Special(move) & Piece.Mask)
            {
            case Piece.King:
                Point rookInitial = new Point((Position.File(to) / 2 + 2) * SquareWidth, Position.Rank(to) * SquareWidth);
                Point rookFinal   = new Point(7 * (Position.File(to) - 2) / 4 * SquareWidth, Position.Rank(to) * SquareWidth);
                Animate(move, rookInitial, rookFinal);
                break;

            case Piece.Pawn:
                lock (PiecesLock)
                    _pieces.Add(new VisualPiece(
                                    Move.Special(move),
                                    Position.File(to) * SquareWidth,
                                    Position.Rank(from) * SquareWidth));
                break;
            }
            Animate(move, initial, final);
        }
コード例 #3
0
        /// <summary>
        /// Returns a value for the given move that indicates its immediate threat.
        /// Non-capture moves have a default value of zero, while captures have a
        /// value that is the ratio of the captured piece to the moving piece. Pawns
        /// promoting to queen are given an additional increase in value.
        /// </summary>
        /// <param name="move">The move to consider.</param>
        /// <returns>A value for the given move that is useful for move ordering.</returns>
        private Single MoveOrderingValue(Int32 move)
        {
            Single value = PieceValue[Move.Capture(move)] / (Single)PieceValue[Move.Piece(move)];

            if (Move.IsQueenPromotion(move))
            {
                value += QueenPromotionMoveValue;
            }
            return(value);
        }
コード例 #4
0
        /// <summary>
        /// Returns the dynamic value of the position as determined by a recursive
        /// search to the given depth. This implements the main search algorithm.
        /// </summary>
        /// <param name="position">The position to search on.</param>
        /// <param name="depth">The depth to search to.</param>
        /// <param name="ply">The number of plies from the root position.</param>
        /// <param name="alpha">The lower bound on the value of the best move.</param>
        /// <param name="beta">The upper bound on the value of the best move.</param>
        /// <param name="inCheck">Whether the side to play is in check.</param>
        /// <param name="allowNull">Whether a null move is permitted.</param>
        /// <returns>The value of the termination position given optimal play.</returns>
        private Int32 Search(Position position, Int32 depth, Int32 ply, Int32 alpha, Int32 beta, Boolean inCheck, Boolean allowNull = true)
        {
            // Check whether to enter quiescence search and initialize pv length.
            _pvLength[ply] = 0;
            if (depth <= 0 && !inCheck)
            {
                return(Quiescence(position, ply, alpha, beta));
            }

            // Check for time extension and search termination. This is done once for
            // every given number of nodes for efficency.
            if (++_totalNodes > _referenceNodes)
            {
                _referenceNodes += NodeResolution;

                // Apply loss time extension. The value of the best move for the current
                // root position is compared with the value of the previous root position.
                // If there is a large loss, a time extension is given.
                Int32 loss = _finalAlpha - _rootAlpha;
                if (loss >= TimeControlsLossResolution)
                {
                    Int32 index = Math.Min(loss / TimeControlsLossResolution, TimeControlsLossExtension.Length - 1);
                    TryTimeExtension(TimeControlsLossThreshold, TimeControlsLossExtension[index]);
                }

                if (_stopwatch.ElapsedMilliseconds >= _timeLimit + _timeExtension || _totalNodes >= Restrictions.Nodes)
                {
                    _abortSearch = true;
                }
            }
            if (_abortSearch)
            {
                return(Infinity);
            }

            // Perform draw detection.
            Int32 drawValue       = ((ply & 1) == 0) ? DrawValue : -DrawValue;
            Int32 drawRepetitions = (ply > 2) ? 2 : 3;

            if (position.FiftyMovesClock >= 100 || position.InsufficientMaterial() || position.HasRepeated(drawRepetitions))
            {
                return(drawValue);
            }

            // Perform mate distance pruning.
            Int32 mateAlpha = Math.Max(alpha, -(CheckmateValue - ply));
            Int32 mateBeta  = Math.Min(beta, CheckmateValue - (ply + 1));

            if (mateAlpha >= mateBeta)
            {
                return(mateAlpha);
            }

            // Perform hash probe.
            _hashProbes++;
            Int32 hashMove = Move.Invalid;

            if (_table.TryProbe(position.ZobristKey, out HashEntry hashEntry))
            {
                if (hashEntry.Depth >= depth)
                {
                    Int32 hashType  = hashEntry.Type;
                    Int32 hashValue = hashEntry.GetValue(ply);
                    if ((hashType == HashEntry.Beta && hashValue >= beta) || (hashType == HashEntry.Alpha && hashValue <= alpha))
                    {
                        _hashCutoffs++;
                        return(hashValue);
                    }
                }
                hashMove = hashEntry.Move;
            }

            Int32 colour = position.SideToMove;

            // Apply null move heuristic.
            if (allowNull && !inCheck && position.Bitboard[colour] != (position.Bitboard[colour | Piece.King] | position.Bitboard[colour | Piece.Pawn]))
            {
                position.MakeNull();
                Int32 reduction = NullMoveReduction + (depth >= NullMoveAggressiveDepth ? depth / NullMoveAggressiveDivisor : 0);
                Int32 value     = -Search(position, depth - 1 - reduction, ply + 1, -beta, -beta + 1, false, false);
                position.UnmakeNull();
                if (value >= beta)
                {
                    return(value);
                }
            }

            // Generate legal moves and perform basic move ordering.
            Int32[] moves      = _generatedMoves[ply];
            Int32   movesCount = position.LegalMoves(moves);

            if (movesCount == 0)
            {
                return(inCheck ? -(CheckmateValue - ply) : drawValue);
            }
            for (Int32 i = 0; i < movesCount; i++)
            {
                _moveValues[i] = MoveOrderingValue(moves[i]);
            }

            // Apply single reply and check extensions.
            if (movesCount == 1 || inCheck)
            {
                depth++;
            }

            // Perform killer move ordering.
            _killerMoveChecks++;
            bool killerMoveFound = false;

            for (Int32 slot = 0; slot < KillerMovesAllocation; slot++)
            {
                Int32 killerMove = _killerMoves[ply][slot];
                for (Int32 i = 0; i < movesCount; i++)
                {
                    if (moves[i] == killerMove)
                    {
                        _moveValues[i] = KillerMoveValue + slot * KillerMoveSlotValue;
                        if (!killerMoveFound)
                        {
                            _killerMoveMatches++;
                        }
                        killerMoveFound = true;
                        break;
                    }
                }
            }

            // Perform hash move ordering.
            _hashMoveChecks++;
            if (hashMove != Move.Invalid)
            {
                for (Int32 i = 0; i < movesCount; i++)
                {
                    if (moves[i] == hashMove)
                    {
                        _moveValues[i] = HashMoveValue;
                        _hashMoveMatches++;
                        break;
                    }
                }
            }

            // Check for futility pruning activation.
            Boolean futileNode    = false;
            Int32   futilityValue = 0;

            if (depth < FutilityMargin.Length && !inCheck)
            {
                futilityValue = Evaluate(position) + FutilityMargin[depth];
                futileNode    = futilityValue <= alpha;
            }

            // Sort the moves based on their ordering values and initialize variables.
            Int32  irreducibleMoves   = Sort(moves, _moveValues, movesCount);
            UInt64 preventionBitboard = PassedPawnPreventionBitboard(position);
            Int32  bestType           = HashEntry.Alpha;
            Int32  bestMove           = moves[0];

            // Go through the move list.
            for (Int32 i = 0; i < movesCount; i++)
            {
                _movesSearched++;

                Int32   move        = moves[i];
                Boolean causesCheck = position.CausesCheck(move);
                Boolean dangerous   = inCheck || causesCheck || alpha < -NearCheckmateValue || IsDangerousPawnAdvance(move, preventionBitboard);
                Boolean reducible   = i + 1 > irreducibleMoves;

                // Perform futility pruning.
                if (futileNode && !dangerous && futilityValue + PieceValue[Move.Capture(move)] <= alpha)
                {
                    _futileMoves++;
                    continue;
                }

                // Make the move and initialize its value.
                position.Make(move);
                Int32 value = alpha + 1;

                // Perform late move reductions.
                if (reducible && !dangerous)
                {
                    value = -Search(position, depth - 1 - LateMoveReduction, ply + 1, -alpha - 1, -alpha, causesCheck);
                }

                // Perform principal variation search.
                else if (i > 0)
                {
                    value = -Search(position, depth - 1, ply + 1, -alpha - 1, -alpha, causesCheck);
                }

                // Perform a full search.
                if (value > alpha)
                {
                    value = -Search(position, depth - 1, ply + 1, -beta, -alpha, causesCheck);
                }

                // Unmake the move and check for search termination.
                position.Unmake(move);
                if (_abortSearch)
                {
                    return(Infinity);
                }

                // Check for upper bound cutoff.
                if (value >= beta)
                {
                    _table.Store(new HashEntry(position, depth, ply, move, value, HashEntry.Beta));
                    if (reducible)
                    {
                        for (Int32 j = _killerMoves[ply].Length - 2; j >= 0; j--)
                        {
                            _killerMoves[ply][j + 1] = _killerMoves[ply][j];
                        }
                        _killerMoves[ply][0] = move;
                    }
                    return(value);
                }

                // Check for lower bound improvement.
                if (value > alpha)
                {
                    alpha    = value;
                    bestMove = move;
                    bestType = HashEntry.Exact;

                    // Collect the principal variation.
                    _pvMoves[ply][0] = move;
                    for (Int32 j = 0; j < _pvLength[ply + 1]; j++)
                    {
                        _pvMoves[ply][j + 1] = _pvMoves[ply + 1][j];
                    }
                    _pvLength[ply] = _pvLength[ply + 1] + 1;
                }
            }

            // Store the results in the hash table and return the lower bound of the
            // value of the position.
            _table.Store(new HashEntry(position, depth, ply, bestMove, alpha, bestType));
            return(alpha);
        }
コード例 #5
0
        /// <summary>
        /// Unmakes the given move from the position.
        /// </summary>
        /// <param name="move">The move to unmake.</param>
        public void Unmake(Int32 move)
        {
            Int32 from    = Move.From(move);
            Int32 to      = Move.To(move);
            Int32 piece   = Move.Piece(move);
            Int32 capture = Move.Capture(move);
            Int32 special = Move.Special(move);

            // Rewind core board state.
            SideToMove            = 1 - SideToMove;
            Square[from]          = piece;
            Square[to]            = capture;
            Bitboard[piece]      ^= (1UL << from) | (1UL << to);
            Bitboard[SideToMove] ^= (1UL << from) | (1UL << to);
            OccupiedBitboard     ^= (1UL << from) | (1UL << to);

            // Rewind metainformation.
            ZobristKey = ZobristKeyHistory[HalfMoves - 1];
            EnPassantHistory[HalfMoves] = InvalidSquare;
            EnPassantSquare             = EnPassantHistory[HalfMoves - 1];
            FiftyMovesClock             = FiftyMovesHistory[HalfMoves - 1];
            HalfMoves--;

            // Rewind capture if applicable.
            switch (capture & Piece.Mask)
            {
            case Piece.Empty:
                break;

            case Piece.Rook:
                if ((SideToMove == Colour.White && to == 0) || (SideToMove == Colour.Black && to == 56))
                {
                    CastleQueenside[1 - SideToMove]++;
                }
                else if ((SideToMove == Colour.White && to == 7) || (SideToMove == Colour.Black && to == 63))
                {
                    CastleKingside[1 - SideToMove]++;
                }
                goto default;

            default:
                Bitboard[capture]        ^= 1UL << to;
                Bitboard[1 - SideToMove] ^= 1UL << to;
                OccupiedBitboard         |= 1UL << to;
                Material[1 - SideToMove] += Engine.PieceValue[capture];
                break;
            }

            switch (special & Piece.Mask)
            {
            // Rewind regular move.
            case Piece.Empty:
                switch (piece & Piece.Mask)
                {
                // For rook move, restore castling on one side if applicable.
                case Piece.Rook:
                    if ((SideToMove == Colour.White && from == 56) || (SideToMove == Colour.Black && from == 0))
                    {
                        CastleQueenside[SideToMove]++;
                    }
                    else if ((SideToMove == Colour.White && from == 63) || (SideToMove == Colour.Black && from == 7))
                    {
                        CastleKingside[SideToMove]++;
                    }
                    break;

                // For king move, restore castling on both sides if applicable.
                case Piece.King:
                    CastleQueenside[SideToMove]++;
                    CastleKingside[SideToMove]++;
                    break;
                }
                break;

            // Rewind castling.
            case Piece.King:
                CastleQueenside[SideToMove]++;
                CastleKingside[SideToMove]++;
                Int32 rookFrom;
                Int32 rookTo;
                if (to < from)
                {
                    rookFrom = Rank(to) * 8;
                    rookTo   = 3 + Rank(to) * 8;
                }
                else
                {
                    rookFrom = 7 + Rank(to) * 8;
                    rookTo   = 5 + Rank(to) * 8;
                }
                Bitboard[SideToMove | Piece.Rook] ^= (1UL << rookFrom) | (1UL << rookTo);
                Bitboard[SideToMove] ^= (1UL << rookFrom) | (1UL << rookTo);
                OccupiedBitboard     ^= (1UL << rookFrom) | (1UL << rookTo);
                Square[rookFrom]      = SideToMove | Piece.Rook;
                Square[rookTo]        = Piece.Empty;
                break;

            // Rewind en passant.
            case Piece.Pawn:
                Square[File(to) + Rank(from) * 8] = special;
                Bitboard[special]        ^= 1UL << (File(to) + Rank(from) * 8);
                Bitboard[1 - SideToMove] ^= 1UL << (File(to) + Rank(from) * 8);
                OccupiedBitboard         ^= 1UL << (File(to) + Rank(from) * 8);
                Material[1 - SideToMove] += Engine.PieceValue[special];
                break;

            // Rewind pawn promotion.
            default:
                Bitboard[piece]      ^= 1UL << to;
                Bitboard[special]    ^= 1UL << to;
                Material[SideToMove] -= Engine.PieceValue[special] - Engine.PieceValue[piece];
                break;
            }
        }
コード例 #6
0
        /// <summary>
        /// Makes the given move on the position.
        /// </summary>
        /// <param name="move">The move to make.</param>
        public void Make(Int32 move)
        {
            Int32 from    = Move.From(move);
            Int32 to      = Move.To(move);
            Int32 piece   = Move.Piece(move);
            Int32 capture = Move.Capture(move);
            Int32 special = Move.Special(move);

            // Update core board state.
            Square[to]            = piece;
            Square[from]          = Piece.Empty;
            Bitboard[piece]      ^= (1UL << from) | (1UL << to);
            Bitboard[SideToMove] ^= (1UL << from) | (1UL << to);
            OccupiedBitboard     ^= (1UL << from) | (1UL << to);

            // Update metainformation.
            ZobristKey ^= Zobrist.PiecePosition[piece][from] ^ Zobrist.PiecePosition[piece][to];
            ZobristKey ^= Zobrist.Colour;
            if (EnPassantSquare != InvalidSquare)
            {
                ZobristKey     ^= Zobrist.EnPassant[EnPassantSquare];
                EnPassantSquare = InvalidSquare;
            }
            FiftyMovesClock++;
            HalfMoves++;

            // Handle capture if applicable.
            switch (capture & Piece.Mask)
            {
            case Piece.Empty:
                break;

            case Piece.Rook:
                if ((SideToMove == Colour.White && to == 0) || (SideToMove == Colour.Black && to == 56))
                {
                    if (CastleQueenside[1 - SideToMove]-- > 0)
                    {
                        ZobristKey ^= Zobrist.CastleQueenside[1 - SideToMove];
                    }
                }
                else if ((SideToMove == Colour.White && to == 7) || (SideToMove == Colour.Black && to == 63))
                {
                    if (CastleKingside[1 - SideToMove]-- > 0)
                    {
                        ZobristKey ^= Zobrist.CastleKingside[1 - SideToMove];
                    }
                }
                goto default;

            default:
                Bitboard[capture]        ^= 1UL << to;
                Bitboard[1 - SideToMove] ^= 1UL << to;
                OccupiedBitboard         |= 1UL << to;
                ZobristKey ^= Zobrist.PiecePosition[capture][to];
                Material[1 - SideToMove] -= Engine.PieceValue[capture];
                FiftyMovesClock           = 0;
                break;
            }

            switch (special & Piece.Mask)
            {
            // Handle regular move (not en passant, castling, or pawn promotion).
            case Piece.Empty:
                switch (piece & Piece.Mask)
                {
                // For pawn move, update fifty moves clock and en passant state.
                case Piece.Pawn:
                    FiftyMovesClock = 0;
                    if ((from - to) * (from - to) == 256)
                    {
                        ZobristKey ^= Zobrist.EnPassant[from];
                        EnPassantHistory[HalfMoves] = EnPassantSquare = (from + to) / 2;
                    }
                    break;

                // For rook move, disable castling on one side.
                case Piece.Rook:
                    if ((SideToMove == Colour.White && from == 56) || (SideToMove == Colour.Black && from == 0))
                    {
                        if (CastleQueenside[SideToMove]-- > 0)
                        {
                            ZobristKey ^= Zobrist.CastleQueenside[SideToMove];
                        }
                    }
                    else if ((SideToMove == Colour.White && from == 63) || (SideToMove == Colour.Black && from == 7))
                    {
                        if (CastleKingside[SideToMove]-- > 0)
                        {
                            ZobristKey ^= Zobrist.CastleKingside[SideToMove];
                        }
                    }
                    break;

                // For king move, disable castling on both sides.
                case Piece.King:
                    if (CastleQueenside[SideToMove]-- > 0)
                    {
                        ZobristKey ^= Zobrist.CastleQueenside[SideToMove];
                    }
                    if (CastleKingside[SideToMove]-- > 0)
                    {
                        ZobristKey ^= Zobrist.CastleKingside[SideToMove];
                    }
                    break;
                }
                break;

            // Handle castling.
            case Piece.King:
                if (CastleQueenside[SideToMove]-- > 0)
                {
                    ZobristKey ^= Zobrist.CastleQueenside[SideToMove];
                }
                if (CastleKingside[SideToMove]-- > 0)
                {
                    ZobristKey ^= Zobrist.CastleKingside[SideToMove];
                }
                Int32 rookFrom;
                Int32 rookTo;
                if (to < from)
                {
                    rookFrom = Rank(to) * 8;
                    rookTo   = 3 + Rank(to) * 8;
                }
                else
                {
                    rookFrom = 7 + Rank(to) * 8;
                    rookTo   = 5 + Rank(to) * 8;
                }
                Bitboard[SideToMove | Piece.Rook] ^= (1UL << rookFrom) | (1UL << rookTo);
                Bitboard[SideToMove] ^= (1UL << rookFrom) | (1UL << rookTo);
                OccupiedBitboard     ^= (1UL << rookFrom) | (1UL << rookTo);
                ZobristKey           ^= Zobrist.PiecePosition[SideToMove | Piece.Rook][rookFrom];
                ZobristKey           ^= Zobrist.PiecePosition[SideToMove | Piece.Rook][rookTo];
                Square[rookFrom]      = Piece.Empty;
                Square[rookTo]        = SideToMove | Piece.Rook;
                break;

            // Handle en passant.
            case Piece.Pawn:
                Square[File(to) + Rank(from) * 8] = Piece.Empty;
                Bitboard[special]        ^= 1UL << (File(to) + Rank(from) * 8);
                Bitboard[1 - SideToMove] ^= 1UL << (File(to) + Rank(from) * 8);
                OccupiedBitboard         ^= 1UL << (File(to) + Rank(from) * 8);
                ZobristKey ^= Zobrist.PiecePosition[special][File(to) + Rank(from) * 8];
                Material[1 - SideToMove] -= Engine.PieceValue[special];
                break;

            // Handle pawn promotion.
            default:
                Bitboard[piece]      ^= 1UL << to;
                Bitboard[special]    ^= 1UL << to;
                ZobristKey           ^= Zobrist.PiecePosition[piece][to];
                ZobristKey           ^= Zobrist.PiecePosition[special][to];
                Material[SideToMove] += Engine.PieceValue[special] - Engine.PieceValue[piece];
                Square[to]            = special;
                break;
            }

            SideToMove = 1 - SideToMove;
            FiftyMovesHistory[HalfMoves] = FiftyMovesClock;
            ZobristKeyHistory[HalfMoves] = ZobristKey;
        }
コード例 #7
0
ファイル: Search.cs プロジェクト: mdelgert/absolute-zero
        /// <summary>
        /// Returns the dynamic value of the position as determined by a recursive
        /// search that terminates upon reaching a quiescent position.
        /// </summary>
        /// <param name="position">The position to search on.</param>
        /// <param name="ply">The number of plies from the root position.</param>
        /// <param name="alpha">The lower bound on the value of the best move.</param>
        /// <param name="beta">The upper bound on the value of the best move.</param>
        /// <returns>The value of the termination position given optimal play.</returns>
        private Int32 Quiescence(Position position, Int32 ply, Int32 alpha, Int32 beta)
        {
            _totalNodes++;
            _quiescenceNodes++;

            // Evaluate the position statically. Check for upper bound cutoff and lower
            // bound improvement.
            Int32 value = Evaluate(position);

            if (value >= beta)
            {
                return(value);
            }
            if (value > alpha)
            {
                alpha = value;
            }

            // Initialize variables and generate the pseudo-legal moves to be
            // considered. Perform basic move ordering and sort the moves.
            Int32 colour = position.SideToMove;

            Int32[] moves      = _generatedMoves[ply];
            Int32   movesCount = position.PseudoQuiescenceMoves(moves);

            for (Int32 i = 0; i < movesCount; i++)
            {
                _moveValues[i] = MoveOrderingValue(moves[i]);
            }
            Sort(moves, _moveValues, movesCount);

            // Go through the move list.
            for (Int32 i = 0; i < movesCount; i++)
            {
                _movesSearched++;
                Int32 move = moves[i];

                // Consider the move only if it doesn't immediately lose material. This
                // improves efficiency.
                if ((Move.Piece(move) & Piece.Mask) <= (Move.Capture(move) & Piece.Mask) || EvaluateStaticExchange(position, move) >= 0)
                {
                    // Make the move.
                    position.Make(move);

                    // Search the move if it is legal. This is equivalent to not leaving the
                    // king in check.
                    if (!position.InCheck(colour))
                    {
                        value = -Quiescence(position, ply + 1, -beta, -alpha);

                        // Check for upper bound cutoff and lower bound improvement.
                        if (value >= beta)
                        {
                            position.Unmake(move);
                            return(value);
                        }
                        if (value > alpha)
                        {
                            alpha = value;
                        }
                    }

                    // Unmake the move.
                    position.Unmake(move);
                }
            }
            return(alpha);
        }