Пример #1
0
        /// <summary>
        /// Creates the currently available options for a player after a specific action is executed.
        /// </summary>
        /// <param name="rootState">The game state before any tasks in the action are processed.</param>
        /// <param name="action">The action containing a list of tasks to process.</param>
        /// <param name="playerId">The unique identifier of the player that will play the action.</param>
        /// <param name="ignorePositioning">[Optional] Whether or not to treat minion play tasks with different positions as the same and ignore the extras. Default is true.</param>
        /// <returns>Collection of available tasks, potentially having minion play tasks with different positions filtered out.</returns>
        private static IEnumerable <SabberStonePlayerTask> CreateCurrentOptions(SabberStoneState rootState, SabberStoneAction action, int playerId, bool ignorePositioning = true)
        {
            // Clone the root state before tampering with it
            var clonedState = (SabberStoneState)rootState.Copy();

            // Apply the tasks in the current action to the root state
            foreach (var task in action.Tasks)
            {
                clonedState.Game.Process(task.Task);
            }

            // If it's no longer our player's turn, or if the game has ended return an empty list
            // Note: this will happen if the last task processed was an end-turn task or if that task ended the game
            if (clonedState.Game.CurrentPlayer.Id != playerId || clonedState.Game.State == State.COMPLETE)
            {
                return(new List <SabberStonePlayerTask>());
            }

            // Query the game for the currently available actions
            var currentOptions = clonedState.Game.CurrentPlayer.Options();

            if (ignorePositioning)
            {
                // Filter out all tasks that have position set to anything higher than 0
                // Note: a position of -1 means that the task doesn't care about positioning, so those are left in
                currentOptions = currentOptions.Where(i => i.ZonePosition <= 0).ToList();
            }

            // Return the options
            return(currentOptions.Select(i => (SabberStonePlayerTask)i));
        }
        /// <summary>
        /// Creates a SabberStoneAction from a collection of possible solutions by voting for separate tasks.
        /// </summary>
        /// <param name="solutions">The available solutions.</param>
        /// <param name="state">The game state.</param>
        /// <returns>SabberStoneAction.</returns>
        public SabberStoneAction VoteForSolution(List <SabberStoneAction> solutions, SabberStoneState state)
        {
            // Clone game so that we can process the selected tasks and get an updated options list.
            var clonedGame = state.Game.Clone();
            var action     = new SabberStoneAction();

            // Have all solutions vote on tasks
            var taskVotes = new Dictionary <int, int>();

            foreach (var solution in solutions)
            {
                foreach (var task in solution.Tasks)
                {
                    var taskHash = task.GetHashCode();
                    if (!taskVotes.ContainsKey(taskHash))
                    {
                        taskVotes.Add(taskHash, 0);
                    }
                    taskVotes[taskHash]++;
                }
            }

            // Keep selecting tasks until the action is complete or the game has ended
            while (!action.IsComplete() && clonedGame.State != State.COMPLETE)
            {
                // Make a dictionary of available tasks, indexed by their hashcode, but ignore the END-TURN task for now
                var availableTasks = clonedGame.CurrentPlayer.Options().Where(i => i.PlayerTaskType != PlayerTaskType.END_TURN).Select(i => (SabberStonePlayerTask)i).ToList();

                // Pick the one with most votes
                var votedOnTasks = availableTasks.Where(i => taskVotes.ContainsKey(i.GetHashCode())).ToList();
                var mostVoted    = votedOnTasks.OrderByDescending(i => taskVotes[i.GetHashCode()]).FirstOrDefault();

                // If this is null, it means none of the tasks that are available appear in any of the solutions
                if (mostVoted == null)
                {
                    // End the turn
                    action.AddTask((SabberStonePlayerTask)EndTurnTask.Any(clonedGame.CurrentPlayer));
                    break;
                }

                // Find any tasks tied for most votes
                var mostVotes = taskVotes[mostVoted.GetHashCode()];
                var ties      = votedOnTasks.Where(i => taskVotes[i.GetHashCode()] == mostVotes);

                // Add one of the tasks with the most votes to the action
                //TODO Ties during voting can be handled differently than random, but handling ties based on visit count would require extra information from the separate searches' solutions.
                var chosenTask = ties.RandomElementOrDefault();
                action.AddTask(chosenTask);

                // Process the task so we have an updated options list next iteration
                clonedGame.Process(chosenTask.Task);
            }

            return(action);
        }
Пример #3
0
        /// <summary>
        /// Recursively expands an action by creating new actions and adding possible tasks to them.
        /// </summary>
        /// <param name="rootState">The game state at the root of the recursive call.</param>
        /// <param name="action">The action.</param>
        /// <param name="playerId">The unique identifier of the player that will play the action.</param>
        /// <param name="completeActions">A reference to a collection of completely expanded actions.</param>
        private static void ExpandAction(SabberStoneState rootState, SabberStoneAction action, int playerId, ref List <SabberStoneAction> completeActions)
        {
            // If the latest option added was the end-turn task, return
            if (action.IsComplete())
            {
                completeActions.Add(action);
                return;
            }

            // Go through all available options
            foreach (var option in CreateCurrentOptions(rootState, action, playerId))
            {
                // Create a new action that is a copy of the current action
                var nextLevelAction = new SabberStoneAction(action.Tasks);
                // Add that task as the latest task
                nextLevelAction.AddTask(option);
                // Recursively call this method to find all combinations
                ExpandAction(rootState, nextLevelAction, playerId, ref completeActions);
            }
        }
        /// <summary>
        /// Determines the best tasks for the game state based on the provided statistics and creates a <see cref="SabberStoneAction"/> from them.
        /// </summary>
        /// <param name="state">The game state to create the best action for.</param>
        /// <returns><see cref="SabberStoneAction"/> created from the best individual tasks available in the provided state.</returns>
        public SabberStoneAction DetermineBestTasks(SabberStoneState state)
        {
            // Clone game so that we can process the selected tasks and get an updated options list.
            var clonedGame = state.Game.Clone();

            // We have to determine which tasks are the best to execute in this state, based on the provided values of the MCTS search.
            // So we'll check the statistics table for the highest value among tasks that are currently available in the state.
            // This continues until the end-turn task is selected.
            var action = new SabberStoneAction();

            while (!action.IsComplete() && clonedGame.State != State.COMPLETE)
            {
                // Get the available options in this state and find which tasks we have statistics on, but ignore the END-TURN task for now
                var availableTasks = clonedGame.CurrentPlayer.Options().Where(i => i.PlayerTaskType != PlayerTaskType.END_TURN).Select(i => ((SabberStonePlayerTask)i).GetHashCode());
                var stats          = TaskStatistics.Where(i => availableTasks.Contains(i.Key)).ToList();
                var bestTask       = stats.OrderByDescending(i => i.Value.AverageValue()).FirstOrDefault();

                // If we can't find any task, stop.
                if (bestTask.IsDefault())
                {
                    // End the turn
                    action.AddTask((SabberStonePlayerTask)EndTurnTask.Any(clonedGame.CurrentPlayer));
                    break;
                }

                // Handle the possibility of tasks with tied average value.
                var bestValue   = bestTask.Value.AverageValue();
                var tiedTasks   = stats.Where(i => Math.Abs(i.Value.AverageValue() - bestValue) < Constants.DOUBLE_EQUALITY_TOLERANCE);
                var orderedTies = tiedTasks.OrderByDescending(i => i.Value.Visits);
                bestTask = orderedTies.First();

                // If we found a task, add it to the Action and process it to progress the game.
                var task = bestTask.Value.Task;
                action.AddTask(task);
                clonedGame.Process(task.Task);
            }

            // Return the created action consisting of the best action available at each point.
            return(action);
        }
Пример #5
0
 /// <summary>
 /// Calculate the scores of a SabberStoneState.
 /// </summary>
 /// <param name="position">The SabberStoneState.</param>
 /// <returns>Array of Double containing the scores per player, indexed by player ID.</returns>
 public double[] Scores(SabberStoneState position) => new[] {
     position.PlayerWon == position.Player1.Id ? PLAYER_WIN_SCORE : PLAYER_LOSS_SCORE,
     position.PlayerWon == position.Player2.Id ? PLAYER_WIN_SCORE : PLAYER_LOSS_SCORE
 };
Пример #6
0
        /// <summary>
        /// Expand the search from a SabberStoneState.
        /// </summary>
        /// <param name="context">The context of the search.</param>
        /// <param name="position">The state to expand from.</param>
        /// <returns>An enumeration of possible actions from the argument state.</returns>
        public IPositionGenerator <SabberStoneAction> Expand(SearchContext <List <SabberStoneAction>, SabberStoneState, SabberStoneAction, object, SabberStoneAction> context, SabberStoneState position)
        {
            var availableActionSequences = new List <SabberStoneAction>();
            var activePlayer             = position.Game.CurrentPlayer;
            var activePlayerId           = activePlayer.Id;

            // When expanding on a position, we'll have a number of tasks to choose from.
            // The problem is that each task isn't exclusive with other tasks and/or may lead to more available tasks when processed.
            var availableOptions = activePlayer.Options();

            // Filter out duplicate actions with different zone positions.
            // This significantly reduces complexity at a minor loss of game theoretic optimality.
            availableOptions = availableOptions.Where(i => i.ZonePosition <= 0).ToList();

            // If we are expanding hierarchically we can just return the individual tasks.
            if (HierarchicalExpansion)
            {
                // Order the dimensions
                var orderedTasks = OrderTasks(availableOptions);
                return(new SabberStoneMoveGenerator(orderedTasks));
            }

            // If we are not expanding hierarchically, we'll need to generate all action sequences at once.
            var topLevelActions = new List <SabberStoneAction>();

            foreach (var task in availableOptions)
            {
                topLevelActions.Add(CreateActionFromSingleTask(task));
            }

            // Recursively expand the top-level actions.
            foreach (var action in topLevelActions)
            {
                ExpandAction(position, action, activePlayerId, ref availableActionSequences);
            }

            // Return a move generator based on the list of available action sequences.
            return(new SabberStoneMoveGenerator(availableActionSequences));
        }
Пример #7
0
 /// <summary>
 /// Determines if a SabberStoneState represents a completed game.
 /// </summary>
 /// <param name="context">The context of the search.</param>
 /// <param name="position">The game state.</param>
 /// <returns>Whether or not the game is completed.</returns>
 public bool Done(SearchContext <List <SabberStoneAction>, SabberStoneState, SabberStoneAction, object, SabberStoneAction> context, SabberStoneState position)
 {
     return(Goal.Done(context, position));
 }
Пример #8
0
        /// <summary>
        /// Applies a SabberStoneAction to a SabberStoneState which results in a new SabberStoneState.
        /// </summary>
        /// <param name="context">The context of the search.</param>
        /// <param name="position">The state to which the action should be applied.</param>
        /// <param name="action">The action to apply.</param>
        /// <returns>SabberStoneState that is the result of applying the action.</returns>
        public SabberStoneState Apply(SearchContext <List <SabberStoneAction>, SabberStoneState, SabberStoneAction, object, SabberStoneAction> context, SabberStoneState position, SabberStoneAction action)
        {
            // In case of hierarchical expansion, we'll arrive here with incomplete actions, i.e. actions that might not have end-turn tasks.
            // We'll still have to process these actions to move the state into a new state, ready for further expansion.

            // Check if the action is complete, or if we are applying Hierarchical Expansion (in that case the action will be incomplete).
            if (action.IsComplete() || HierarchicalExpansion)
            {
                // Process each task.
                foreach (var item in action.Tasks)
                {
                    position.Game.Process(item.Task);
                }
            }
            else
            {
                // In the case of an incomplete action, just pass the turn.
                position.Game.Process(EndTurnTask.Any(position.Game.CurrentPlayer));
            }

            // Return the position.
            return(position);
        }