/// <inheritdoc /> public OddmentTable <SabberStonePlayerTask> Create(SearchContext <List <SabberStoneAction>, SabberStoneState, SabberStoneAction, object, SabberStoneAction> context, int samplesForGeneration) { // So we have an issue here, because it's Hearthstone we don't know in advance how many dimensions we have. // -> We can just evenly distribute budget over the currently available dimensions // -- Reason is that some dimensions would be `locked' until after a specific dimensions is explored // -> Another option is to expand all possible sequences, this might be too complex though... // In Hearthstone we don't really have multiple available actions per dimension // It's more like you either play/attack or not // Although each minion can have multiple choices on what to attack // We can still try to distillate individual actions // It might be best to keep track of these through a dictionary/array // So we'd randomly choose actions and simulate when `end-turn' is chosen // And then update the value of any selected PlayerTask // I guess use OddmentTable again? var table = new Dictionary <SabberStonePlayerTask, double>(PlayerTaskComparer.Comparer); // So, we have a number of samples to use // For each of those, generate a random SabberStoneAction and playout for (var i = 0; i < samplesForGeneration; i++) { var action = PlayoutBot.CreateRandomAction(context.Source); var newState = GameLogic.Apply(context, (SabberStoneState)context.Source.Copy(), action); var endState = Playout.Playout(context, newState); var value = Evaluation.Evaluate(context, null, endState); foreach (var task in action.Tasks) { if (!table.ContainsKey(task)) { table.Add(task, 0); } table[task] += value; } } // Create the Oddment table var oddmentTable = new OddmentTable <SabberStonePlayerTask>(); foreach (var kvPair in table) { oddmentTable.Add(kvPair.Key, kvPair.Value, recalculate: false); } oddmentTable.Recalculate(); return(oddmentTable); }
/// <summary> /// Selects the best half of a collection of actions by evaluating them based on the provided search context. /// </summary> /// <param name="context">The current search context.</param> /// <param name="actions">The collection of actions to filter.</param> /// <param name="samplesPerAction">How many samples to run per action.</param> /// <returns>Collection of actions which Count is half of the original collection, rounded up. This collection is ordered by descending value.</returns> private List <ActionValue> SelectBestHalf(SearchContext <D, P, A, S, A> context, IReadOnlyCollection <ActionValue> actions, int samplesPerAction) { var clone = context.Cloner; // Evaluate each action by running a playout for it. foreach (var item in actions) { var tempNode = new N { Payload = item.Action }; var newState = GameLogic.Apply(context, clone.Clone(context.Source), item.Action); double value = 0; for (var i = 0; i < samplesPerAction; i++) { try { var endState = Playout.Playout(context, clone.Clone(newState)); value += Evaluation.Evaluate(context, tempNode, endState); SamplesUsedEvaluation++; } catch (Exception e) { //Console.WriteLine($"ERROR: {e.GetType()} while cloning state."); // TODO fix // Cloning here seems to very seldom cause a NullReference in the SabberStone dll // I believe failing and just using 0 as a value here is acceptable } } item.Value = value; } // Return the half with the highest value. var half = (int)Math.Max(1, Math.Ceiling(actions.Count / 2.0)); return(actions.OrderByDescending(i => i.Value).Take(half).ToList()); }