Пример #1
0
        public IBestMoveResult BestMove(Board board, SearchConfig config, MoveTimer timer, CancellationToken cancelToken)
        {
            _config = config;
            _timer = timer;
            _numNodesQuiessenceSearched = 0;

            return BestMoveInternal(board, _config.DepthLimit, int.MinValue, int.MaxValue, cancelToken);
        }
Пример #2
0
 // constructors
 public SearchConfig(SearchConfig toCopy)
 {
     DepthLimit = toCopy.DepthLimit;
     PercentTimeLeftToIncrementDepthLimit = toCopy.PercentTimeLeftToIncrementDepthLimit;
     ReportStatistics = toCopy.ReportStatistics;
     MoveTimeout = toCopy.MoveTimeout;
     GameMode = toCopy.GameMode;
     _beginningHeuristic = toCopy._beginningHeuristic;
     _middleHeuristic = toCopy._middleHeuristic;
     _endHeuristic = toCopy._endHeuristic;
 }
Пример #3
0
        public IBestMoveResult BestMove(Board board, SearchConfig config, MoveTimer timer, CancellationToken cancelToken)
        {
            // initialize stats
            _config = config;
            _timer = timer;
            _nodesGeneratedByDepth = Enumerable.Range(1, _config.DepthLimit).ToDictionary(x => x, x => 0);
            _numNodesAtDepthLimit = 0;
            _numNodesQuiessenceSearched = 0;

            var result = BestMoveInternal(board, _config.DepthLimit, int.MinValue, int.MaxValue, cancelToken);

            // fill stats
            result.Config = _config;
            result.NumNodesAtDepthLimit = _numNodesAtDepthLimit;
            result.NodesGeneratedByDepth = _nodesGeneratedByDepth;
            result.NumNodesQuiessenceSearched = _numNodesQuiessenceSearched;
            result.TimedOut = _timedOut;

            return result;
        }
Пример #4
0
        public IBestMoveResult BestMove(Board board, SearchConfig config, MoveTimer timer, CancellationToken cancelToken)
        {
            // initialize stats
            _config = config;
            _timer  = timer;
            _nodesGeneratedByDepth      = Enumerable.Range(1, _config.DepthLimit).ToDictionary(x => x, x => 0);
            _numNodesAtDepthLimit       = 0;
            _numNodesQuiessenceSearched = 0;

            var result = BestMoveInternal(board, _config.DepthLimit, int.MinValue, int.MaxValue, cancelToken);

            // fill stats
            result.Config = _config;
            result.NumNodesAtDepthLimit       = _numNodesAtDepthLimit;
            result.NodesGeneratedByDepth      = _nodesGeneratedByDepth;
            result.NumNodesQuiessenceSearched = _numNodesQuiessenceSearched;
            result.TimedOut = _timedOut;

            return(result);
        }
Пример #5
0
        // get alpha-beta configuration
        private static SearchConfig GetConfig()
        {
            Console.WriteLine("Enter timeout in seconds: ");
            var input = Console.ReadLine();

            SearchConfig cfg = null;

            while (cfg == null)
            {
                try
                {
                    cfg = new SearchConfig(input);
                }
                catch
                {
                    Console.WriteLine("Please enter a valid timeout: ");
                    input = Console.ReadLine();
                }
            }

            return(cfg);
        }
Пример #6
0
        // get alpha-beta configuration
        private static SearchConfig GetConfig()
        {
            Console.WriteLine("Enter timeout in seconds: ");
            var input = Console.ReadLine();

            SearchConfig cfg = null;

            while (cfg == null)
            {
                try
                {
                    cfg = new SearchConfig(input);
                }
                catch
                {
                    Console.WriteLine("Please enter a valid timeout: ");
                    input = Console.ReadLine();
                }
            }

            return cfg;
        }
Пример #7
0
        public IBestMoveResult BestMove(Board board, SearchConfig config, MoveTimer timer, CancellationToken cancelToken)
        {
            _timer       = timer;
            _cancelToken = cancelToken;

            if (_isWalledOff)
            {
                var longestPath = NextMoveOnLongestPath(board, board.PlayerToMove);
                return(new BestMoveResult(longestPath.Item1, longestPath.Item2));
            }
            else // otherwise fall back to alpha beta
            {
                var ab = new AlphaBeta().BestMove(board, config, timer, cancelToken);

                // but if alpha beta thinks we'll lose, do longest move
                if (ab.Score == int.MinValue)
                {
                    var longestPath = NextMoveOnLongestPath(board, board.PlayerToMove);
                    return(new BestMoveResult(longestPath.Item1, longestPath.Item2));
                }

                return(ab);
            }
        }
Пример #8
0
        public BoardSpace GetMyNextMove(Board board)
        {
            // start the timer
            MoveTimer.I.StartTimer();

            // cancel all tasks that are now irrelevant
            foreach (var task in _tasksByOpponentMove.Where(x => !x.Key.Equals(board.LastMove)))
            {
                task.Value.CancelSource.Cancel();
            }

            TimeSpan?asyncTimeElapsed = null;

            IBestMoveResult bestMoveResult;

            if (board.LastMove == null || !_tasksByOpponentMove.ContainsKey(board.LastMove))
            {
                // we were not precomputing this path, so we need to start fresh
                bestMoveResult = MoveGetter().BestMove(board, _config, MoveTimer.I, new CancellationToken());
            }
            else
            {
                // we already started down this branch, so use the result from the task
                bestMoveResult = _tasksByOpponentMove[board.LastMove].BestMoveTask.Result;

                // check how long that took us
                asyncTimeElapsed = _tasksByOpponentMove[board.LastMove].Timer.GetTimeElapsed();
            }

            // stop timer and grab timer stats
            var percentRemaining = MoveTimer.I.GetPercentOfTimeRemaining();
            var elapsed          = MoveTimer.I.GetTimeElapsed();
            var timedOut         = MoveTimer.I.Timeout();

            MoveTimer.I.ResetTimer();

            // report stats, for debugging only, will be off in production
            if (_config.ReportStatistics)
            {
                Console.WriteLine("Time Taken (s): " + elapsed.TotalSeconds);
                Console.WriteLine("Time Left (%): " + percentRemaining * 100);
                if (asyncTimeElapsed != null)
                {
                    Console.WriteLine("Async Time Taken (s): " + asyncTimeElapsed.Value.TotalSeconds);
                }
                Console.WriteLine(bestMoveResult.ToString());
            }

            // if we have enough time remaining, increase the depth limit
            if (percentRemaining > _config.PercentTimeLeftToIncrementDepthLimit)
            {
                // if we didn't do the search async, just increase depth
                if (asyncTimeElapsed == null)
                {
                    _config.DepthLimit++;
                }
                else
                {
                    // only increase depth if async time was decent
                    var asyncPercentTimeLeft = (_config.MoveTimeout.TotalMilliseconds - asyncTimeElapsed.Value.TotalMilliseconds) / _config.MoveTimeout.TotalMilliseconds;
                    if (asyncPercentTimeLeft > _config.PercentTimeLeftToIncrementDepthLimit / 2)
                    {
                        _config.DepthLimit++;
                    }
                }
            }

            // if we timed out, decrease depth limit so we stop shooting ourself in the foot
            if (timedOut)
            {
                _config.DepthLimit--;
            }

            var emptySpaces = board.GetEmptySpacesRemaining();

            // figure out where we are in the game
            if (_config.GameMode == GameMode.Beginning)
            {
                if (emptySpaces <= 40)
                {
                    _config.DepthLimit--;
                    _config.GameMode = GameMode.Middle;
                }
            }
            else if (_config.GameMode == GameMode.Middle && !timedOut)
            {
                // find out what our areas look like
                var boardPostMove = board.Copy().Move(bestMoveResult.Move);
                var myArea        = OpenAreaHeuristic.GetOpenArea(boardPostMove, boardPostMove.MyPlayer);
                var oppArea       = OpenAreaHeuristic.GetOpenArea(boardPostMove, boardPostMove.OpponentPlayer);

                // if we are walled off, switch to end game
                if (myArea.All(x => !oppArea.Contains(x)))
                {
                    _isWalledOff     = true;
                    _config.GameMode = GameMode.End;
                }
                else // we aren't walled off
                {
                    // if it's time for end game, switch and max out depth
                    if (emptySpaces <= 28)
                    {
                        _config.DepthLimit = 30;
                        _config.GameMode   = GameMode.End;
                    }
                    // if we think we're going to lose, try to find our best move b/c alpha-beta will pick a random one
                    else if (bestMoveResult.Score == int.MinValue)
                    {
                        var tasksByDepthLimit = new Dictionary <int, AsyncSearchTask>();

                        // simultaneously search at smaller depths to get our best move that isn't -infinity
                        for (var newDepthLimit = _config.DepthLimit - 1; newDepthLimit > _config.DepthLimit / 2; newDepthLimit--)
                        {
                            var newConfig = new SearchConfig(_config)
                            {
                                DepthLimit = newDepthLimit
                            };

                            // new task with timeout set at our remaining time
                            var task = new AsyncSearchTask(_config.MoveTimeout.Subtract(elapsed));

                            task.BestMoveTask = Task.Factory.StartNew(() =>
                            {
                                task.Timer.StartTimer();
                                var result = MoveGetter().BestMove(board, newConfig, task.Timer, task.CancelSource.Token);
                                task.Timer.StopTimer();
                                return(result);
                            }, task.CancelSource.Token);

                            tasksByDepthLimit[newDepthLimit] = task;
                        }

                        foreach (var kvp in tasksByDepthLimit.OrderBy(x => x.Key))
                        {
                            var newBestMove = kvp.Value.BestMoveTask.Result;

                            // if we think we're going to lose or we timed out, cancel all deeper tasks because they will also think we're going to lose
                            if (newBestMove.Score == int.MinValue || kvp.Value.Timer.Timeout())
                            {
                                var kvpClosure = kvp;
                                foreach (var task in tasksByDepthLimit.Where(x => x.Key > kvpClosure.Key).Select(x => x.Value).Where(x => !x.BestMoveTask.IsCompleted))
                                {
                                    task.CancelSource.Cancel();
                                }
                                break;
                            }

                            // if we found a better move, use it
                            if (newBestMove.Score > bestMoveResult.Score)
                            {
                                bestMoveResult = newBestMove;
                            }
                        }
                    }
                }
            }

            return(bestMoveResult.Move);
        }
Пример #9
0
        public IBestMoveResult BestMove(Board board, SearchConfig config, MoveTimer timer, CancellationToken cancelToken)
        {
            _timer = timer;
            _cancelToken = cancelToken;

            if (_isWalledOff)
            {
                var longestPath = NextMoveOnLongestPath(board, board.PlayerToMove);
                return new BestMoveResult(longestPath.Item1, longestPath.Item2);
            }
            else // otherwise fall back to alpha beta
            {
                var ab = new AlphaBeta().BestMove(board, config, timer, cancelToken);

                // but if alpha beta thinks we'll lose, do longest move
                if (ab.Score == int.MinValue)
                {
                    var longestPath = NextMoveOnLongestPath(board, board.PlayerToMove);
                    return new BestMoveResult(longestPath.Item1, longestPath.Item2);
                }

                return ab;
            }
        }
Пример #10
0
        public BoardSpace GetMyNextMove(Board board)
        {
            // start the timer
            MoveTimer.I.StartTimer();

            // cancel all tasks that are now irrelevant
            foreach (var task in _tasksByOpponentMove.Where(x => !x.Key.Equals(board.LastMove)))
            {
                task.Value.CancelSource.Cancel();
            }

            TimeSpan? asyncTimeElapsed = null;

            IBestMoveResult bestMoveResult;

            if (board.LastMove == null || !_tasksByOpponentMove.ContainsKey(board.LastMove))
            {
                // we were not precomputing this path, so we need to start fresh
                bestMoveResult = MoveGetter().BestMove(board, _config, MoveTimer.I, new CancellationToken());
            }
            else
            {
                // we already started down this branch, so use the result from the task
                bestMoveResult = _tasksByOpponentMove[board.LastMove].BestMoveTask.Result;

                // check how long that took us
                asyncTimeElapsed = _tasksByOpponentMove[board.LastMove].Timer.GetTimeElapsed();
            }

            // stop timer and grab timer stats
            var percentRemaining = MoveTimer.I.GetPercentOfTimeRemaining();
            var elapsed = MoveTimer.I.GetTimeElapsed();
            var timedOut = MoveTimer.I.Timeout();
            MoveTimer.I.ResetTimer();

            // report stats, for debugging only, will be off in production
            if (_config.ReportStatistics)
            {
                Console.WriteLine("Time Taken (s): " + elapsed.TotalSeconds);
                Console.WriteLine("Time Left (%): " + percentRemaining * 100);
                if (asyncTimeElapsed != null)
                {
                    Console.WriteLine("Async Time Taken (s): " + asyncTimeElapsed.Value.TotalSeconds);
                }
                Console.WriteLine(bestMoveResult.ToString());
            }

            // if we have enough time remaining, increase the depth limit
            if (percentRemaining > _config.PercentTimeLeftToIncrementDepthLimit)
            {
                // if we didn't do the search async, just increase depth
                if (asyncTimeElapsed == null)
                {
                    _config.DepthLimit++;
                }
                else
                {
                    // only increase depth if async time was decent
                    var asyncPercentTimeLeft = (_config.MoveTimeout.TotalMilliseconds - asyncTimeElapsed.Value.TotalMilliseconds) / _config.MoveTimeout.TotalMilliseconds;
                    if (asyncPercentTimeLeft > _config.PercentTimeLeftToIncrementDepthLimit/2)
                    {
                        _config.DepthLimit++;
                    }
                }
            }

            // if we timed out, decrease depth limit so we stop shooting ourself in the foot
            if (timedOut)
            {
                _config.DepthLimit--;
            }

            var emptySpaces = board.GetEmptySpacesRemaining();

            // figure out where we are in the game
            if (_config.GameMode == GameMode.Beginning)
            {
                if (emptySpaces <= 40)
                {
                    _config.DepthLimit--;
                    _config.GameMode = GameMode.Middle;
                }
            }
            else if (_config.GameMode == GameMode.Middle && !timedOut)
            {
                // find out what our areas look like
                var boardPostMove = board.Copy().Move(bestMoveResult.Move);
                var myArea = OpenAreaHeuristic.GetOpenArea(boardPostMove, boardPostMove.MyPlayer);
                var oppArea = OpenAreaHeuristic.GetOpenArea(boardPostMove, boardPostMove.OpponentPlayer);

                // if we are walled off, switch to end game
                if (myArea.All(x => !oppArea.Contains(x)))
                {
                    _isWalledOff = true;
                    _config.GameMode = GameMode.End;
                }
                else // we aren't walled off
                {
                    // if it's time for end game, switch and max out depth
                    if (emptySpaces <= 28)
                    {
                        _config.DepthLimit = 30;
                        _config.GameMode = GameMode.End;
                    }
                    // if we think we're going to lose, try to find our best move b/c alpha-beta will pick a random one
                    else if (bestMoveResult.Score == int.MinValue)
                    {
                        var tasksByDepthLimit = new Dictionary<int, AsyncSearchTask>();

                        // simultaneously search at smaller depths to get our best move that isn't -infinity
                        for (var newDepthLimit = _config.DepthLimit - 1; newDepthLimit > _config.DepthLimit/2; newDepthLimit--)
                        {
                            var newConfig = new SearchConfig(_config) { DepthLimit = newDepthLimit };

                            // new task with timeout set at our remaining time
                            var task = new AsyncSearchTask(_config.MoveTimeout.Subtract(elapsed));

                            task.BestMoveTask = Task.Factory.StartNew(() =>
                            {
                                task.Timer.StartTimer();
                                var result = MoveGetter().BestMove(board, newConfig, task.Timer, task.CancelSource.Token);
                                task.Timer.StopTimer();
                                return result;
                            }, task.CancelSource.Token);

                            tasksByDepthLimit[newDepthLimit] = task;
                        }

                        foreach (var kvp in tasksByDepthLimit.OrderBy(x => x.Key))
                        {
                            var newBestMove = kvp.Value.BestMoveTask.Result;

                            // if we think we're going to lose or we timed out, cancel all deeper tasks because they will also think we're going to lose
                            if (newBestMove.Score == int.MinValue || kvp.Value.Timer.Timeout())
                            {
                                var kvpClosure = kvp;
                                foreach (var task in tasksByDepthLimit.Where(x => x.Key > kvpClosure.Key).Select(x => x.Value).Where(x => !x.BestMoveTask.IsCompleted))
                                {
                                    task.CancelSource.Cancel();
                                }
                                break;
                            }

                            // if we found a better move, use it
                            if (newBestMove.Score > bestMoveResult.Score)
                            {
                                bestMoveResult = newBestMove;
                            }
                        }
                    }
                }
            }

            return bestMoveResult.Move;
        }
Пример #11
0
        public void Initialize(SearchConfig config)
        {
            _config = config;

            // tell move timer about timeout
            MoveTimer.I.SetTimeout(config.MoveTimeout);
        }