/// <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); }
/// <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); }