/// <summary>
        /// Returns a SabberStoneAction for the current state.
        /// </summary>
        /// <param name="state">The current game state.</param>
        /// <returns>SabberStoneAction</returns>
        public SabberStoneAction Act(SabberStoneState state)
        {
            // Check to make sure the player to act in the game-state matches our player.
            if (state.CurrentPlayer() != Player.Id)
            {
                return(null);
            }

            SabberStoneAction selectedAction;

            // When we have to act, check which policy we are going to use
            switch (Selection)
            {
            case SelectionType.UCB:
                selectedAction = SelectUCB(state);
                break;

            case SelectionType.EGreedy:
                selectedAction = SelectEGreedy(state);
                break;

            case SelectionType.Random:
                selectedAction = RandomPlayoutBot.CreateRandomAction(state);
                break;

            default:
                throw new InvalidEnumArgumentException($"SelectionType `{Selection}' is not supported.");
            }

            // Remember the action that was selected.
            ActionsTaken.Add(selectedAction);
            return(selectedAction);
        }
        public double EvaluateStateTransition(SabberStoneState before, SabberStoneState after)
        {
            // These min and max values have been empirically determined and should be checked and re-set when it matters that they are correct.
            if (after.Game.CurrentOpponent.Hero.Health <= 0)
            {
                return(50);
            }
            if (after.Game.CurrentPlayer.Hero.Health <= 0)
            {
                return(-50);
            }

            double enemyPoints = calculateScoreHero(before.Game.CurrentOpponent, after.Game.CurrentOpponent);

            double myPoints = calculateScoreHero(before.Game.CurrentPlayer, after.Game.CurrentPlayer);

            double scoreEnemyMinions = calculateScoreMinions(before.Game.CurrentOpponent.BoardZone, after.Game.CurrentOpponent.BoardZone);

            double scoreMyMinions = calculateScoreMinions(before.Game.CurrentPlayer.BoardZone, after.Game.CurrentPlayer.BoardZone);

            int    usedMana      = before.Game.CurrentPlayer.RemainingMana - after.Game.CurrentPlayer.RemainingMana;
            double scoreManaUsed = usedMana * weights[MANA_REDUCED];

            return(enemyPoints - myPoints + scoreEnemyMinions - scoreMyMinions - scoreManaUsed);
        }
Exemplo n.º 3
0
        /// <summary>
        /// Completes an incomplete move caused by Hierarchical Expansion.
        /// </summary>
        /// <param name="state">The game state to create an action for.</param>
        /// <param name="action">The currently created action.</param>
        private void CompleteHEMove(SabberStoneState state, SabberStoneAction action)
        {
            // Copy state so that we can process the tasks and get an updated options list.
            var copyState = (SabberStoneState)state.Copy();

            // Process the currently selected tasks
            foreach (var task in action.Tasks)
            {
                try {
                    copyState.Game.Process(task.Task);
                }
                catch (Exception e) {
                    Console.WriteLine($"ERROR: {e.GetType()} thrown while trying to process a task.");
                    break;
                }
            }
            // Ask the Searcher to determine the best tasks to complete the action
            var completingAction = Searcher.DetermineBestTasks(copyState);

            // Add the tasks to the provided action
            foreach (var task in completingAction.Tasks)
            {
                action.AddTask(task);
            }

            // If the move is not complete yet (for example, when the game is over), add EndTurn
            if (!action.IsComplete())
            {
                action.AddTask((SabberStonePlayerTask)EndTurnTask.Any(Player));
            }
        }
Exemplo n.º 4
0
        /// <summary>
        /// Creates a SabberStoneAction by randomly selecting one of the available PlayerTasks until the End_Turn task is selected.
        /// </summary>
        /// <param name="state">The game state for which an action should be created. Note: </param>
        /// <returns>SabberStoneAction</returns>
        public SabberStoneAction CreateRandomAction(SabberStoneState state)
        {
            // Clone game so that we can process the selected tasks and get an updated options list.
            var clonedGame = state.Game.Clone();
            var playerID   = clonedGame.CurrentPlayer.Id;

            // Create an action to store the selected tasks.
            var action = new SabberStoneAction();

            // Keep adding random tasks until the player's turn is over, or the game has ended
            while (clonedGame.CurrentPlayer.Id == playerID && clonedGame.State != State.COMPLETE)
            {
                // Check if an duplicate positions need to be filtered out
                var availableOptions = clonedGame.CurrentPlayer.Options();
                if (FilterDuplicatePositionTasks)
                {
                    availableOptions = availableOptions.Where(i => i.ZonePosition <= 0).ToList();
                }
                // Select a random available task
                var selectedTask = availableOptions.RandomElementOrDefault();
                // Add the task to the action.
                action.AddTask((SabberStonePlayerTask)selectedTask);
                // Process the task on the cloned game state.
                clonedGame.Process(selectedTask);
            }

            // Check if the action is complete, if not, add EndTurn
            if (!action.IsComplete())
            {
                action.AddTask((SabberStonePlayerTask)EndTurnTask.Any(clonedGame.CurrentPlayer));
            }

            return(action);
        }
Exemplo n.º 5
0
        /// <summary>
        /// Plays out a player's turn.
        /// Note: this method asks the playout bot of the current player to Act and processes the returned action.
        /// </summary>
        /// <param name="game">The current game state.</param>
        private void PlayPlayerTurn(SabberStoneState game)
        {
            // Select the correct playoutBot to use.
            IPlayoutBot turnBot;

            if (Bots.ContainsKey(game.CurrentPlayer()))
            {
                turnBot = Bots[game.CurrentPlayer()];
            }
            else
            {
                // Fall back to the default playout bot if the current player does not have a playout bot specified.
                turnBot = Bots[DEFAULT_PLAYOUT_BOT_ID];
                turnBot.SetController(game.Game.CurrentPlayer);
            }

            // Ask the bot to act.
            var action = turnBot.Act(game);

            // Process each task.
            foreach (var item in action.Tasks)
            {
                game.Game.Process(item.Task);
            }
        }
        public SabberStoneAction Act(SabberStoneState state)
        {
            var timer     = System.Diagnostics.Stopwatch.StartNew();
            var gameState = (SabberStoneState)state.Copy();

            if (_debug)
            {
                Console.WriteLine();
            }
            if (_debug)
            {
                Console.WriteLine(Name());
            }
            if (_debug)
            {
                Console.WriteLine($"Starting a Heuristic search in turn {(gameState.Game.Turn + 1) / 2}");
            }

            var solution = new SabberStoneAction();

            while (gameState.CurrentPlayer() == PlayerID() && gameState.Game.State != State.COMPLETE)
            {
                var poGame = new POGame(gameState.Game, _debug);
                var task   = GetMove(poGame);
                solution.AddTask((SabberStonePlayerTask)task);
                gameState.Game.Process(task);
            }

            var time = timer.ElapsedMilliseconds;

            if (_debug)
            {
                Console.WriteLine();
            }
            if (_debug)
            {
                Console.WriteLine($"Heuristic returned with solution: {solution}");
            }
            if (_debug)
            {
                Console.WriteLine($"My total calculation time was: {time} ms.");
            }

            // Check if the solution is a complete action.
            if (!solution.IsComplete())
            {
                // Otherwise add an End-Turn task before returning.
                if (_debug)
                {
                    Console.WriteLine("Solution was an incomplete action; adding End-Turn task.");
                }
                solution.Tasks.Add((SabberStonePlayerTask)EndTurnTask.Any(Player));
            }

            if (_debug)
            {
                Console.WriteLine();
            }
            return(solution);
        }
Exemplo n.º 7
0
        /// <inheritdoc />
        public SabberStoneAction Act(SabberStoneState state)
        {
            var timer     = System.Diagnostics.Stopwatch.StartNew();
            var gameState = (SabberStoneState)state.Copy();

            if (_debug)
            {
                Console.WriteLine();
            }
            if (_debug)
            {
                Console.WriteLine(Name());
            }
            if (_debug)
            {
                Console.WriteLine($"Starting a NMCTS search in turn {(gameState.Game.Turn + 1) / 2}");
            }

            // Setup and start the ensemble-search
            EnsembleSolutions = new List <SabberStoneAction>();
            var search  = (NMCTS <List <SabberStoneAction>, SabberStoneState, SabberStoneAction, object, SabberStoneAction>)Builder.Build();
            var context = SearchContext <List <SabberStoneAction>, SabberStoneState, SabberStoneAction, object, SabberStoneAction> .GameSearchSetup(GameLogic, EnsembleSolutions, gameState, null, search);

            Ensemble.EnsembleSearch(context, Searcher.Search, EnsembleSize);
            IterationsSpent = EnsembleSolutions.Sum(i => i.BudgetUsed);

            // Determine the best tasks to play based on the ensemble search, or just take the one in case of a single search.
            var solution = EnsembleSize > 1 ? Searcher.VoteForSolution(EnsembleSolutions, state) : EnsembleSolutions.First();

            if (_debug)
            {
                Console.WriteLine();
            }
            if (_debug)
            {
                Console.WriteLine($"NMCTS returned with solution: {solution}");
            }
            if (_debug)
            {
                Console.WriteLine($"My total calculation time was: {timer.ElapsedMilliseconds} ms.");
            }

            // Check if the solution is a complete action.
            if (!solution.IsComplete())
            {
                // Otherwise add an End-Turn task before returning.
                if (_debug)
                {
                    Console.WriteLine("Solution was an incomplete action; adding End-Turn task.");
                }
                solution.Tasks.Add((SabberStonePlayerTask)EndTurnTask.Any(Player));
            }

            if (_debug)
            {
                Console.WriteLine();
            }
            return(solution);
        }
 /// <summary>
 /// Finalise these statistics using the completed game's state and write the information to a file.
 /// </summary>
 /// <param name="state">The state of the completed game.</param>
 public void Finalise(SabberStoneState state)
 {
     Player1HP = state.Player1.Hero.Health;
     Player2HP = state.Player2.Hero.Health;
     FinalTurn = state.Game.Turn;
     Status    = state.Game.State;
     WriteToFile();
 }
        /// <summary>
        /// Selects an action using the e-greedy algorithm.
        /// </summary>
        /// <param name="state">The game state.</param>
        /// <returns><see cref="SabberStoneAction"/>.</returns>
        private SabberStoneAction SelectEGreedy(SabberStoneState state)
        {
            // Determine whether or not to be greedy (chance is 1-e to use best action)
            if (Util.RNG.NextDouble() < EGreedyThreshold)
            {
                // Explore a random action
                return(RandomPlayoutBot.CreateRandomAction(state));
            }

            var action     = new SabberStoneAction();
            var stateClone = state.Game.Clone();

            // Repeatedly exploit the highest (average) reward task that is available in this state
            do
            {
                SabberStonePlayerTask selectedTask;
                // Get the stats of the tasks currently available in this state
                var availableTasks      = stateClone.Game.CurrentPlayer.Options().Where(i => i.ZonePosition <= 0).Select(i => (SabberStonePlayerTask)i).ToList();
                var availableTaskHashes = availableTasks.Select(i => i.GetHashCode()).ToList();
                var availableStatistics = MASTTable.Where(i => availableTaskHashes.Contains(i.Key)).ToList();

                // Find the task with the highest average value
                var bestTask = availableStatistics.OrderByDescending(i => i.Value.AverageValue()).FirstOrDefault();

                // If no best task was found, randomly choose an available task
                if (bestTask.IsDefault())
                {
                    var randomTask = availableTasks.RandomElementOrDefault();
                    // If we also can't randomly find a task, stop
                    if (randomTask == null)
                    {
                        break;
                    }
                    selectedTask = randomTask;
                }
                else
                {
                    // Find all available tasks that have an average value similar to the best
                    var bestValue = bestTask.Value.AverageValue();
                    var compTasks = availableStatistics.Where(i =>
                                                              Math.Abs(i.Value.AverageValue() - bestValue) < AVThesis.Constants.DOUBLE_EQUALITY_TOLERANCE).ToList();
                    // Select one of the tasks
                    selectedTask = compTasks.RandomElementOrDefault().Value.Task;
                }

                // Add the task to the action we are building
                action.AddTask(selectedTask);
                // Process the task
                stateClone.Process(selectedTask.Task);

                // Continue until we have created a complete action, or the game has completed
            } while (!action.IsComplete() && stateClone.Game.State != State.COMPLETE);

            // Return the action we've created
            return(action);
        }
        /// <summary>
        /// Plays out a player's turn.
        /// Note: this method continuously asks the bot to Act and stops when 'null' is returned.
        /// </summary>
        /// <param name="game">The current game state.</param>
        /// <param name="bot">The bot that should play the turn.</param>
        private void PlayPlayerTurn(SabberStoneState game, ISabberStoneBot bot)
        {
            var currentPlayerName = game.Game.CurrentPlayer.Name;

            if (_printToConsole)
            {
                Console.WriteLine($"- <{currentPlayerName}> ---------------------------");
            }
            var timer = Stopwatch.StartNew();

            // Ask the bot to act.
            var action = bot.Act(game);

            timer.Stop();

            // In the case where an incomplete action was returned, add end-turn
            if (!action.IsComplete())
            {
                Console.WriteLine("WARNING: Incomplete action received. Adding EndTurn.");
                action.AddTask((SabberStonePlayerTask)EndTurnTask.Any(game.Game.CurrentPlayer));
            }

            // Process the tasks in the action
            var executedTasks = new List <SabberStonePlayerTask>();

            foreach (var item in action.Tasks)
            {
                if (_printToConsole)
                {
                    Console.WriteLine(item.Task.FullPrint());
                }
                try {
                    // Process the task
                    game.Game.Process(item.Task);
                    executedTasks.Add(item);
                }
                catch (Exception e) {
                    Console.WriteLine($"ERROR: Exception thrown while processing a task for {game.Game.CurrentPlayer.Name} in turn {game.Game.Turn}.");
                    WriteExceptionToFile(e);
                    // If the game is still running and the current player is still active, pass the turn
                    if (game.Game.CurrentPlayer.Id == bot.PlayerID())
                    {
                        game.Game.Process(EndTurnTask.Any(game.Game.CurrentPlayer));
                    }
                    // Do not continue with any other tasks in this action
                    break;
                }
            }

            // Store the action in the match-statistics
            MatchStatistics.ProcessAction(currentPlayerName, executedTasks, timer.Elapsed, bot.BudgetSpent(), bot.MaxDepth());
            if (_printToConsole)
            {
                Console.WriteLine($"*Action computation time: {timer.Elapsed:g}");
            }
        }
Exemplo n.º 11
0
        public static void RunQuickMatch()
        {
            var game = new SabberStoneState(new SabberStoneCore.Model.Game(new GameConfig {
                StartPlayer      = 1,
                Player1Name      = Constants.SABBERSTONE_GAMECONFIG_PLAYER1_NAME,
                Player1HeroClass = CardClass.HUNTER,
                Player1Deck      = Decks.GetRandomTournamentDeck(),
                Player2Name      = Constants.SABBERSTONE_GAMECONFIG_PLAYER2_NAME,
                Player2HeroClass = CardClass.HUNTER,
                Player2Deck      = Decks.GetRandomTournamentDeck(),
                FillDecks        = false,
                Shuffle          = true,
                SkipMulligan     = false,
                History          = false
            }));

            // Create two bots to play
            var bot1 = BotFactory.CreateSabberStoneBot(BotSetupType.RandomBot, game.Player1);
            var bot2 = BotFactory.CreateSabberStoneBot(BotSetupType.RandomBot, game.Player2);

            game.Game.StartGame();

            game.Game.Process(MulliganStrategySabberStone.DefaultMulligan(game.Game.Player1));
            game.Game.Process(MulliganStrategySabberStone.DefaultMulligan(game.Game.Player2));

            game.Game.MainReady();

            while (game.Game.State != State.COMPLETE)
            {
                Console.WriteLine("");
                Console.WriteLine($"TURN {(game.Game.Turn + 1) / 2} - {game.Game.CurrentPlayer.Name}");
                Console.WriteLine($"Hero[P1] {game.Player1.Hero} HP: {game.Player1.Hero.Health} / Hero[P2] {game.Player2.Hero} HP: {game.Player2.Hero.Health}");
                Console.WriteLine($"- {game.Game.CurrentPlayer.Name} Action ----------------------------");

                // Ask the bot to act.
                var action = game.Game.CurrentPlayer.Id == game.Player1.Id ? bot1.Act(game) : bot2.Act(game);

                // Check if the action is valid
                if (action == null || !action.IsComplete())
                {
                    continue;
                }

                // Process the tasks in the action
                foreach (var item in action.Tasks)
                {
                    // Process the task
                    Console.WriteLine(item.Task.FullPrint());
                    game.Game.Process(item.Task);
                }
            }

            Console.WriteLine($"Game: {game.Game.State}, Player1: {game.Player1.PlayState} / Player2: {game.Player2.PlayState}");
        }
Exemplo n.º 12
0
        /// <summary>
        /// Returns a SabberStoneAction for the current state.
        /// Note: If this player has no available options, null is returned.
        /// </summary>
        /// <param name="state">The current game state.</param>
        /// <returns>SabberStoneAction or null in the case of no available options.</returns>
        public SabberStoneAction Act(SabberStoneState state)
        {
            // Check to make sure the player to act in the game-state matches our player.
            if (state.CurrentPlayer() != Player.Id)
            {
                return(null);
            }

            // Check if there are any options, otherwise return a randomly created action.
            return(Player.Options().IsNullOrEmpty() ? SabberStoneAction.CreateNullMove(Player) : CreateRandomAction(state));
        }
        /// <summary>
        /// Selects and action using the UCB1 algorithm.
        /// </summary>
        /// <param name="state">The game state.</param>
        /// <returns><see cref="SabberStoneAction"/>.</returns>
        private SabberStoneAction SelectUCB(SabberStoneState state)
        {
            var action     = new SabberStoneAction();
            var stateClone = state.Game.Clone();

            // Repeatedly exploit the highest UCB-value task that is available in this state
            do
            {
                SabberStonePlayerTask selectedTask;
                // Get the stats of the tasks currently available in this state
                var availableTasks      = stateClone.Game.CurrentPlayer.Options().Where(i => i.ZonePosition <= 0).Select(i => (SabberStonePlayerTask)i).ToList();
                var availableTaskHashes = availableTasks.Select(i => i.GetHashCode()).ToList();
                var availableStatistics = MASTTable.Where(i => availableTaskHashes.Contains(i.Key)).ToList();
                var totalVisits         = availableStatistics.Sum(i => i.Value.Visits);

                // Find the task with the highest UCB value
                var bestTask = availableStatistics.OrderByDescending(i => i.Value.UCB(totalVisits, UCBConstantC)).FirstOrDefault();

                // If no best task was found, randomly choose an available task
                if (bestTask.IsDefault())
                {
                    var randomTask = availableTasks.RandomElementOrDefault();
                    // If we also can't randomly find a task, stop
                    if (randomTask == null)
                    {
                        break;
                    }
                    selectedTask = randomTask;
                }
                else
                {
                    // Find all available tasks that have an UCB value similar to the best
                    var bestValue = bestTask.Value.UCB(totalVisits, UCBConstantC);
                    var compTasks = availableStatistics.Where(i =>
                                                              Math.Abs(i.Value.UCB(totalVisits, UCBConstantC) - bestValue) < AVThesis.Constants.DOUBLE_EQUALITY_TOLERANCE).ToList();
                    // Select one of the tasks
                    selectedTask = compTasks.RandomElementOrDefault().Value.Task;
                }

                // Add the task to the action we are building
                action.AddTask(selectedTask);
                // Process the task
                stateClone.Process(selectedTask.Task);

                // Continue until we have created a complete action, or the game has completed
            } while (!action.IsComplete() && stateClone.Game.State != State.COMPLETE);

            // Return the action we've created
            return(action);
        }
Exemplo n.º 14
0
            /// <inheritdoc />
            public SabberStoneAction Sample(SabberStoneState state, OddmentTable <SabberStonePlayerTask> sideInformation)
            {
                var copyState      = (SabberStoneState)state.Copy();
                var availableTasks = GetAvailablePlayerTasks(copyState);
                var action         = new SabberStoneAction();
                var tries          = 0;

                // Keep sampling tasks while we have not passed the turn yet and there are more tasks available than only EndTurn or HeroPower, of if we haven't generated a suitable task in 100 tries
                while (!action.IsComplete() && availableTasks.Any(i => i.Task.PlayerTaskType != PlayerTaskType.END_TURN && i.Task.PlayerTaskType != PlayerTaskType.HERO_POWER) && tries < 100)
                {
                    // Sample a task from the OddmentTable
                    var task = sideInformation.Next();
                    // Check if the task is available in the current state
                    if (!availableTasks.Contains(task, PlayerTaskComparer.Comparer))
                    {
                        tries++;
                        continue;
                    }

                    tries = 0;
                    action.AddTask(task);
                    copyState.Game.Process(task.Task);
                    availableTasks = GetAvailablePlayerTasks(copyState);
                }

                if (action.IsComplete())
                {
                    return(action);
                }

                // If hero power is available, add it
                if (availableTasks.Any(i => i.Task.PlayerTaskType == PlayerTaskType.HERO_POWER))
                {
                    action.AddTask(availableTasks.First(i => i.Task.PlayerTaskType == PlayerTaskType.HERO_POWER));
                }

                // If the action is not complete yet, add EndTurn
                action.AddTask((SabberStonePlayerTask)EndTurnTask.Any(state.Game.CurrentPlayer));

                return(action);
            }
Exemplo n.º 15
0
        /// <inheritdoc />
        public SabberStoneAction Act(SabberStoneState state)
        {
            var timer     = Stopwatch.StartNew();
            var stateCopy = (SabberStoneState)state.Copy();

            if (_debug)
            {
                Console.WriteLine();
            }
            if (_debug)
            {
                Console.WriteLine(Name());
            }
            if (_debug)
            {
                Console.WriteLine($"Starting an LSI search in turn {(stateCopy.Game.Turn + 1) / 2}");
            }

            // Create a new LSI search
            var search = new LSI <List <SabberStoneAction>, SabberStoneState, SabberStoneAction, object, TreeSearchNode <SabberStoneState, SabberStoneAction>, OddmentTable <SabberStonePlayerTask> >(
                SideInformationStrategy,
                SamplingStrategy,
                Playout,
                Evaluation,
                GameLogic,
                BudgetEstimationStrategy
                );

            // Reset the solutions collection
            EnsembleSolutions = new List <SabberStoneAction>();

            // Create a SearchContext that just holds the current state as Source and the Search.
            var context = SearchContext <List <SabberStoneAction>, SabberStoneState, SabberStoneAction, object, SabberStoneAction> .Context(EnsembleSolutions, stateCopy, null, null, search, null);

            // The Playout strategy will call the Goal strategy from the context, so we set it here
            context.Goal = Goal;

            // Execute the search
            Ensemble.EnsembleSearch(context, Searcher.Search, EnsembleSize);
            SamplesSpent = EnsembleSolutions.Sum(i => i.BudgetUsed);

            // Determine a solution
            var solution = Searcher.VoteForSolution(EnsembleSolutions, state);

            timer.Stop();
            if (_debug)
            {
                Console.WriteLine();
            }
            if (_debug)
            {
                Console.WriteLine($"LSI returned with solution: {solution}");
            }
            if (_debug)
            {
                Console.WriteLine($"My total calculation time was: {timer.ElapsedMilliseconds}ms");
            }

            // Check if the solution is a complete action.
            if (!solution.IsComplete())
            {
                // Otherwise add an End-Turn task before returning.
                solution.Tasks.Add((SabberStonePlayerTask)EndTurnTask.Any(Player));
            }

            // If we are estimating the budget by using the previous search's results, save these now
            if (BudgetEstimation == BudgetEstimationType.PreviousSearchAverage && BudgetEstimationStrategy is PreviousSearchAverageBudgetEstimationStrategy estimationStrategy)
            {
                estimationStrategy.PreviousSearchTime       = timer.ElapsedMilliseconds;
                estimationStrategy.PreviousSearchIterations = SamplesSpent;
            }

            if (_debug)
            {
                Console.WriteLine();
            }
            return(solution);
        }
Exemplo n.º 16
0
 /// <inheritdoc />
 public SabberStoneAction Sample(SabberStoneState state)
 {
     return(SabberStoneAction.CreateNullMove(state.CurrentPlayer() == state.Player1.Id ? state.Player1 : state.Player2));
 }
Exemplo n.º 17
0
 /// <summary>
 /// Expands a specific state and returns all available SabberStonePlayerTasks.
 /// Note: it is assumed that the expansion is performed Hierarchically and therefore only contains single SabberStonePlayerTask SabberStoneActions.
 /// </summary>
 /// <param name="state">The game state.</param>
 /// <returns>Collection of SabberStonePlayerTasks that are available in the provided state.</returns>
 private List <SabberStonePlayerTask> GetAvailablePlayerTasks(SabberStoneState state)
 {
     return(GameLogic.Expand(null, state).Select(expandedAction => expandedAction.Tasks.First()).ToList());
 }
Exemplo n.º 18
0
        /// <inheritdoc />
        /// <summary>
        /// Requests the bot to return a SabberStoneAction based on the current SabberStoneState.
        /// </summary>
        /// <param name="state">The current game state.</param>
        /// <returns>SabberStoneAction that was voted as the best option by the ensemble.</returns>
        public SabberStoneAction Act(SabberStoneState state)
        {
            var timer     = System.Diagnostics.Stopwatch.StartNew();
            var gameState = (SabberStoneState)state.Copy();

            if (_debug)
            {
                Console.WriteLine();
            }
            if (_debug)
            {
                Console.WriteLine(Name());
            }
            if (_debug)
            {
                Console.WriteLine($"Starting a MCTS search in turn {(gameState.Game.Turn + 1) / 2}");
            }

            // Check if the task statistics in the searcher should be reset
            if (!RetainTaskStatistics)
            {
                Searcher.ResetTaskStatistics();
            }

            // Setup and start the ensemble-search
            EnsembleSolutions = new List <SabberStoneAction>();
            var search  = (MCTS <List <SabberStoneAction>, SabberStoneState, SabberStoneAction, object, SabberStoneAction>)Builder.Build();
            var context = SearchContext <List <SabberStoneAction>, SabberStoneState, SabberStoneAction, object, SabberStoneAction> .GameSearchSetup(GameLogic, EnsembleSolutions, gameState, null, search);

            Ensemble.EnsembleSearch(context, Searcher.Search, EnsembleSize);
            IterationsSpent = EnsembleSolutions.Sum(i => i.BudgetUsed);

            // Determine the best tasks to play based on the ensemble search, or just take the one in case of a single search.
            var solution = EnsembleSize > 1 ? Searcher.VoteForSolution(EnsembleSolutions, state) : EnsembleSolutions.First();

            var time = timer.ElapsedMilliseconds;

            if (_debug)
            {
                Console.WriteLine();
            }
            if (_debug)
            {
                Console.WriteLine($"MCTS returned with solution: {solution}");
            }
            if (_debug)
            {
                Console.WriteLine($"My total calculation time was: {time} ms.");
            }
            if (_debug)
            {
                Console.WriteLine();
            }

            // Check if MoveCompletion should be used.
            if (!solution.IsComplete())
            {
                CompleteHEMove(state, solution);
            }

            return(solution);
        }
        /// <summary>
        /// Runs a game of this match with the specified index.
        /// </summary>
        /// <param name="gameIndex">The index of the game that should be run.</param>
        public void RunGame(int gameIndex)
        {
            Console.WriteLine($"{DateTime.Now:T} -> Starting Game {gameIndex+1} of {NumberOfGames}");
            try {
                var timer = Stopwatch.StartNew();

                // Alternate which player starts.
                var config = GetTournamentConfiguration();
                config.StartPlayer = gameIndex % 2 + 1;

                // Create a new game with the cloned configuration.
                var game = new SabberStoneState(new SabberStoneCore.Model.Game(config));

                // Set up the bots with their Controller from the created game.
                Bots[0].SetController(game.Player1);
                Bots[1].SetController(game.Player2);

                // Get the game ready.
                game.Game.StartGame();
                MatchStatistics.NewGameStarted(gameIndex + 1, game.Game.FirstPlayer.Name);

                // Default mulligan for each player.
                game.Game.Process(MulliganStrategySabberStone.DefaultMulligan(game.Game.Player1));
                game.Game.Process(MulliganStrategySabberStone.DefaultMulligan(game.Game.Player2));

                game.Game.MainReady();

                // Play out the game.
                while (game.Game.State != State.COMPLETE)
                {
                    if (_printToConsole)
                    {
                        Console.WriteLine("");
                    }
                    if (_printToConsole)
                    {
                        Console.WriteLine($"*TURN {(game.Game.Turn + 1) / 2} - {game.Game.CurrentPlayer.Name}");
                    }
                    if (_printToConsole)
                    {
                        Console.WriteLine($"*Hero[P1] {game.Player1.Hero} HP: {game.Player1.Hero.Health} / Hero[P2] {game.Player2.Hero} HP: {game.Player2.Hero.Health}");
                    }

                    // Play out the current player's turn until they pass.
                    if (game.Game.CurrentPlayer.Id == Bots[0].PlayerID())
                    {
                        PlayPlayerTurn(game, Bots[0]);
                    }
                    else if (game.Game.CurrentPlayer.Id == Bots[1].PlayerID())
                    {
                        PlayPlayerTurn(game, Bots[1]);
                    }
                }

                if (_printToConsole)
                {
                    Console.WriteLine($"*Game: {game.Game.State}, Player1: {game.Player1.PlayState} / Player2: {game.Player2.PlayState}");
                    Console.WriteLine($"*Game lasted {timer.Elapsed:g}");
                }

                // Create game data.
                MatchStatistics.EndCurrentGame(game);
            }
            catch (Exception e) {
                Console.WriteLine($"ERROR: Exception thrown during game {gameIndex+1}");
                WriteExceptionToFile(e);
            }
        }
 /// <summary>
 /// Process the end of the current game.
 /// </summary>
 /// <param name="state">The end state of the game.</param>
 public void EndCurrentGame(SabberStoneState state)
 {
     CurrentGame.Finalise(state);
     WriteGameResultToFile();
     Games.Add(CurrentGame);
 }
Exemplo n.º 21
0
        /// <summary>
        /// Plays a game to its end state. This end state should be determined by the goal strategy in the search's context.
        /// </summary>
        /// <param name="context">The context of the search.</param>
        /// <param name="position">The position from which to play out the game.</param>
        /// <returns>The end position.</returns>
        public SabberStoneState Playout(SearchContext <List <SabberStoneAction>, SabberStoneState, SabberStoneAction, object, SabberStoneAction> context, SabberStoneState position)
        {
            // Play out the game as long as the Goal strategy dictates.
            while (!context.Goal.Done(context, position))
            {
                PlayPlayerTurn(position);
            }

            // Tell the playout bot(s) that the playout has completed.
            foreach (var playoutBot in Bots)
            {
                playoutBot.Value.PlayoutCompleted(context, position);
            }

            return(position);
        }
        /// <summary>
        /// Returns the value of the argument state with respect to the argument node.
        /// </summary>
        /// <param name="context">The context of the search.</param>
        /// <param name="node">The node that provides the context to evaluate the state.</param>
        /// <param name="state">The state that should be evaluated.</param>
        /// <returns>Double representing the value of the state with respect to the node.</returns>
        public double Evaluate(SearchContext <List <SabberStoneAction>, SabberStoneState, SabberStoneAction, object, SabberStoneAction> context, TreeSearchNode <SabberStoneState, SabberStoneAction> node, SabberStoneState state)
        {
            // Check if we can and want to use the HeuristicBot's evaluation
            if (UseHeuristicBotEvaluation)
            {
                // This scoring function is actually used to score the effect of tasks, but we are using it here to score the effect of the transition from our Source state to the state from which we are currently evaluating.
                // TODO using the HeuristicBot's evaluation function could be improved
                var heuristicEvaluation = HeuristicAgent.EvaluateStateTransition(context.Source, state);
                // Colour the evaluation depending on who the active player is in the state
                var isRootPlayer = state.CurrentPlayer() == context.Source.CurrentPlayer();
                heuristicEvaluation = isRootPlayer ? heuristicEvaluation : heuristicEvaluation * -1;
                // Normalise the value between -1 and 1. The min and max values have been empirically set and equal the min and max possible evaluations that are returned by the HeuristicBot's function.
                var norm = 2 * Util.Normalise(heuristicEvaluation, -50, 50) - 1; // Note: this is a transformation from [0,1] to [-1,1]
                return(norm);
            }

            var rootPlayerId = context.Source.CurrentPlayer();
            var rootPlayer   = state.Player1.Id == rootPlayerId ? state.Player1 : state.Player2;
            var opponent     = rootPlayer.Opponent;

            // Check for a win/loss
            if (state.PlayerWon != State.DRAW)
            {
                return(state.PlayerWon == rootPlayerId ? 1 : -1);
            }

            // Gather stats that we need
            // TODO gather stats from cards in hand

            // Opponent HP
            var oH = opponent.Hero.Health;
            // Opponent's Taunt Minions HP
            var opponentMinions = opponent.BoardZone.GetAll();
            var oMtH            = opponentMinions.Where(i => i.HasTaunt).Sum(j => j.Health);
            // Opponent's Unknown HP in Hand
            var oUHh = 0;
            // Opponent's Unknown Direct Damage in Hand
            var oDdh = 0;
            // Opponent's Minion Power
            var oMP = opponentMinions.Where(i => i.CanAttack).Sum(j => j.AttackDamage);
            // Opponent's Unknown Minion Power from Hand
            var oUMPh = 0;
            // Opponent's Weapon Damage
            var oWD = opponent.Hero.Weapon?.AttackDamage ?? 0;
            // Opponent's Fatigue Damage
            var oFD = opponent.DeckZone.IsEmpty ? opponent.Hero.Fatigue + 1 : 0;

            // Root Player HP
            var rH = rootPlayer.Hero.Health;
            // Root Player's Taunt Minions HP
            var rootPlayerMinions = rootPlayer.BoardZone.GetAll();
            var rMtH = rootPlayerMinions.Where(i => i.HasTaunt).Sum(j => j.Health);
            // Root Player's HP in Hand
            var rHh = 0;
            // Root Player's Direct Damage in Hand
            var rDdh = 0;
            // Root Player's Minion Power
            var rMP = rootPlayerMinions.Where(i => i.CanAttack).Sum(j => j.AttackDamage);
            // Root Player's Minion Power from Hand
            var rMPh = 0;
            // Root Player's Weapon Damage
            var rWD = rootPlayer.Hero.Weapon?.AttackDamage ?? 0;
            // Root Player's Fatigue Damage
            var rFD = rootPlayer.DeckZone.IsEmpty ? rootPlayer.Hero.Fatigue + 1 : 0;

            // Calculate the approximate turns before the opponent dies
            var opponentHealth   = oH + oMtH + oUHh - oFD - rDdh;
            var rootPlayerDamage = rMP + rMPh + rWD;
            var oTD = rootPlayerDamage > 0 ? opponentHealth / (rootPlayerDamage * 1.0) : int.MaxValue;
            // Calculate the approximate turns before the root player dies
            var rootPlayerHealth = rH + rMtH + rHh - rFD - oDdh;
            var opponentDamage   = oMP + oUMPh + oWD;
            var rTD = opponentDamage > 0 ? rootPlayerHealth / (opponentDamage * 1.0) : int.MaxValue;

            // Check some situations
            var canKillOpponentThisTurn       = (int)Math.Ceiling(oTD) == 1;
            var canBeKilledByOpponentThisTurn = (int)Math.Ceiling(rTD) == 1;
            var notARaceSituation             = oTD >= 4 && rTD >= 4;

            // If the root player can kill the opponent, evaluation is 1
            if (canKillOpponentThisTurn)
            {
                return(1);
            }
            // If opponent can't be killed, but they can kill root player next turn, evaluation is -1
            if (canBeKilledByOpponentThisTurn)
            {
                return(-1);
            }
            // If this is not a racing situation (yet), return a cautious number
            if (notARaceSituation)
            {
                // Two aspects here (to keep it simple)
                // -> root player's HP vs opponent's HP
                // -> root player's #creatures vs opponent's #creatures

                // Having more HP ánd more creatures is quite good
                if (rH > oH && rootPlayerMinions.Length > opponentMinions.Length)
                {
                    return(0.75);
                }
                if (rH > oH && rootPlayerMinions.Length == opponentMinions.Length)
                {
                    return(0.25);
                }
                if (rH > oH && rootPlayerMinions.Length < opponentMinions.Length)
                {
                    return(0.1);
                }

                if (rH == oH && rootPlayerMinions.Length > opponentMinions.Length)
                {
                    return(0.33);
                }
                if (rH == oH && rootPlayerMinions.Length == opponentMinions.Length)
                {
                    return(0);
                }
                if (rH == oH && rootPlayerMinions.Length < opponentMinions.Length)
                {
                    return(-0.33);
                }

                if (rH < oH && rootPlayerMinions.Length > opponentMinions.Length)
                {
                    return(-0.1);
                }
                if (rH < oH && rootPlayerMinions.Length == opponentMinions.Length)
                {
                    return(-0.25);
                }
                // Having less HP ánd less creatures is quite bad
                if (rH < oH && rootPlayerMinions.Length < opponentMinions.Length)
                {
                    return(-0.75);
                }
            }

            // If none of the above applies, look at the difference between when the opponent dies and when the root player dies
            var difference = oTD - rTD;

            // If the difference is between -1 and 1, it is too close to tell
            if (difference >= -1 && difference <= 1)
            {
                return(0);
            }
            // If the difference is negative, it means the root player would die later than the opponent, so the root player would be slightly ahead
            if (difference < -1)
            {
                return(0.5);
            }
            // If the difference is positive, it means the opponent would die later than the root player, so the root player would be losing slightly
            if (difference > 1)
            {
                return(-0.5);
            }

            throw new ArgumentOutOfRangeException($"Evaluation values do not fall into the expected range: oTD={oTD:F3} | rTD={rTD:F3}");
        }
Exemplo n.º 23
0
        /// <summary>
        /// Determines if a Position represents a completed search.
        /// </summary>
        /// <param name="context">The context of the search.</param>
        /// <param name="position">The Position.</param>
        /// <returns>Whether or not the search is done.</returns>
        public bool Done(SearchContext <List <SabberStoneAction>, SabberStoneState, SabberStoneAction, object, SabberStoneAction> context, SabberStoneState position)
        {
            // Determine the turn in which the search started.
            var sourceTurn = context.Source.Game.Turn;

            // The goal will be reached if the amount of turns since the source turn is greater than or equal to the cutoff, or the game has been completed.
            return(position.Game.Turn - sourceTurn >= CutoffThreshold || position.Game.State == SabberStoneCore.Enums.State.COMPLETE);
        }
Exemplo n.º 24
0
 /// <inheritdoc />
 public void PlayoutCompleted(SearchContext <List <SabberStoneAction>, SabberStoneState, SabberStoneAction, object, SabberStoneAction> context, SabberStoneState endState)
 {
 }
        /// <inheritdoc />
        public void PlayoutCompleted(SearchContext <List <SabberStoneAction>, SabberStoneState, SabberStoneAction, object, SabberStoneAction> context, SabberStoneState endState)
        {
            // Evaluate the state.
            var value = Evaluation.Evaluate(context, null, endState);

            // Colour the value based on whether or not this playout bot is for the root player or not
            value = context.Source.CurrentPlayer() == PlayerID() ? value : value * -1;
            // Add data for all the actions that have been taken.
            foreach (var action in ActionsTaken)
            {
                AddData(action, value);
            }
            // Clear the taken actions.
            ActionsTaken = new List <SabberStoneAction>();
        }