Beispiel #1
0
        private List <EvaluatedMove> GetPreSortedValidMoves(GameBoard gameBoard, EvaluatedMove bestMove)
        {
            List <Move> validMoves = GetPreSortedValidMoves(gameBoard, bestMove?.Move);

            List <EvaluatedMove> evaluatedMoves = new List <EvaluatedMove>(validMoves.Count);

            foreach (Move move in validMoves)
            {
                evaluatedMoves.Add(move == bestMove?.Move ? bestMove : new EvaluatedMove(move));
            }

            return(evaluatedMoves);
        }
Beispiel #2
0
        private void OnBestMoveFound(BestMoveParams bestMoveParams, EvaluatedMove evaluatedMove)
        {
            if (null == evaluatedMove)
            {
                throw new ArgumentNullException(nameof(evaluatedMove));
            }

            if (evaluatedMove != bestMoveParams.BestMove)
            {
                BestMoveFound?.Invoke(this, new BestMoveFoundEventArgs(evaluatedMove.Move, evaluatedMove.Depth, evaluatedMove.ScoreAfterMove));
                bestMoveParams.BestMove = evaluatedMove;
            }
        }
Beispiel #3
0
        private async Task <EvaluatedMoveCollection> EvaluateMovesToDepthAsync(GameBoard gameBoard, int depth, IEnumerable <EvaluatedMove> movesToEvaluate, CancellationToken token)
        {
            double alpha = double.NegativeInfinity;
            double beta  = double.PositiveInfinity;

            int color = gameBoard.CurrentTurnColor == PlayerColor.White ? 1 : -1;

            double alphaOriginal = alpha;

            double?bestValue = null;

            EvaluatedMoveCollection evaluatedMoves = new EvaluatedMoveCollection();

            bool firstMove = true;

            foreach (EvaluatedMove moveToEvaluate in movesToEvaluate)
            {
                bool updateAlpha = false;

                if (token.IsCancellationRequested)
                {
                    // Cancel
                    return(new EvaluatedMoveCollection(movesToEvaluate, false));
                }

                gameBoard.TrustedPlay(moveToEvaluate.Move);

                double?value = null;

                if (firstMove)
                {
                    // Full window search
                    value = -1 * await PrincipalVariationSearchAsync(gameBoard, depth - 1, -beta, -alpha, -color, OrderType.Default, token);

                    updateAlpha = true;
                    firstMove   = false;
                }
                else
                {
                    // Null window search
                    value = -1 * await PrincipalVariationSearchAsync(gameBoard, depth - 1, -alpha - double.Epsilon, -alpha, -color, OrderType.Default, token);

                    if (value.HasValue && value > alpha && value < beta)
                    {
                        // Research with full window
                        value = -1 * await PrincipalVariationSearchAsync(gameBoard, depth - 1, -beta, -alpha, -color, OrderType.Default, token);

                        updateAlpha = true;
                    }
                }

                gameBoard.UndoLastMove();

                if (!value.HasValue)
                {
                    // Cancel occurred during evaluation
                    return(new EvaluatedMoveCollection(movesToEvaluate, false));
                }

                EvaluatedMove evaluatedMove = new EvaluatedMove(moveToEvaluate.Move, value.Value, depth);
                evaluatedMoves.Add(evaluatedMove);

                if (updateAlpha)
                {
                    alpha = Math.Max(alpha, value.Value);
                }

                if (!bestValue.HasValue || value >= bestValue)
                {
                    bestValue = value;
                }

                if (bestValue >= beta)
                {
                    // A winning move has been found, since beta is always infinity in this function
                    break;
                }
            }

            ulong key = gameBoard.ZobristKey;

            TranspositionTableEntry tEntry = new TranspositionTableEntry();

            if (bestValue <= alphaOriginal)
            {
                // Losing move since alphaOriginal os negative infinity in this function
                tEntry.Type = TranspositionTableEntryType.UpperBound;
            }
            else
            {
                // Move is a lower bound winning move if bestValue >= beta (always infinity in this function), otherwise it's exact
                tEntry.Type     = bestValue >= beta ? TranspositionTableEntryType.LowerBound : TranspositionTableEntryType.Exact;
                tEntry.BestMove = evaluatedMoves.BestMove.Move;
            }

            tEntry.Value = bestValue.Value;
            tEntry.Depth = depth;

            TranspositionTable.Store(key, tEntry);

            return(evaluatedMoves);
        }
Beispiel #4
0
        private async Task <EvaluatedMoveCollection> EvaluateMovesAsync(GameBoard gameBoard, BestMoveParams bestMoveParams, CancellationToken token)
        {
            EvaluatedMoveCollection movesToEvaluate = new EvaluatedMoveCollection();

            EvaluatedMove bestMove = null;

            // Try to get cached best move if available
            ulong key = gameBoard.ZobristKey;

            if (TranspositionTable.TryLookup(key, out TranspositionTableEntry tEntry) && null != tEntry.BestMove)
            {
                bestMove = new EvaluatedMove(tEntry.BestMove, tEntry.Value, tEntry.Depth);
                OnBestMoveFound(bestMoveParams, bestMove);
            }

            if (null != bestMove && double.IsPositiveInfinity(bestMove.ScoreAfterMove))
            {
                // Winning move, don't search
                movesToEvaluate.Add(bestMove);
                return(movesToEvaluate);
            }

            List <EvaluatedMove> validMoves = GetPreSortedValidMoves(gameBoard, bestMove);

            movesToEvaluate.Add(validMoves, false);

            if (movesToEvaluate.Count <= 1 || bestMoveParams.MaxSearchDepth == 0)
            {
                // No need to search
                return(movesToEvaluate);
            }

            // Iterative search
            int depth = 1 + Math.Max(0, movesToEvaluate.BestMove.Depth);

            while (depth <= bestMoveParams.MaxSearchDepth)
            {
                // Start LazySMP helper threads
                CancellationTokenSource helperCTS = new CancellationTokenSource();
                Task[] helperThreads = StartHelperThreads(gameBoard, depth, bestMoveParams.MaxHelperThreads, helperCTS);

                // "Re-sort" moves to evaluate based on the next iteration
                movesToEvaluate = await EvaluateMovesToDepthAsync(gameBoard, depth, movesToEvaluate, token);

                // End LazySMP helper threads
                EndHelperThreads(helperThreads, helperCTS);

                // Fire BestMoveFound for current depth
                OnBestMoveFound(bestMoveParams, movesToEvaluate.BestMove);

                if (double.IsInfinity(movesToEvaluate.BestMove.ScoreAfterMove))
                {
                    // The best move ends the game, stop searching
                    break;
                }

                // Prune game-losing moves if possible
                movesToEvaluate.PruneGameLosingMoves();

                if (movesToEvaluate.Count <= 1)
                {
                    // Only one move, no reason to keep looking
                    break;
                }

                if (token.IsCancellationRequested)
                {
                    // Cancelled, stop searching
                    break;
                }

                depth = 1 + Math.Max(depth, movesToEvaluate.BestMove.Depth);
            }

            return(movesToEvaluate);
        }