public override ExecutionResult Execute(SuperMetroidModel model, InGameState inGameState, int times = 1, bool usePreviousRoom = false) { IEnumerable <int> visitedNodeIds = inGameState.GetVisitedNodeIds(usePreviousRoom); // If the node at which we entered is not allowed, this is not fulfilled. if (!NodeIds.Contains(visitedNodeIds.First())) { return(null); } // If we have visited a node to avoid, this is not fulfilled. if (NodeIdsToAvoid.Intersect(visitedNodeIds).Any()) { return(null); } // If we were supposed to stay put but have visited more than the starting node, this is not fulfilled if (MustStayPut && visitedNodeIds.Count() > 1) { return(null); } // If we have destroyed an obstacle that needed to be preserved, this is not fulfilled if (ObstaclesIdsToAvoid.Intersect(inGameState.GetDestroyedObstacleIds(usePreviousRoom)).Any()) { return(null); } // We've avoided all pitfalls. This ResetRoom is fulfilled. Clone the InGameState to fulfill method contract return(new ExecutionResult(inGameState.Clone())); }
/// <summary> /// Creates and returns an ExecutionResult based on the provided in-game state, with the provided resources refilled. /// </summary> /// <param name="model">A model that can be used to obtain data about the current game configuration.</param> /// <param name="inGameState">The in-game state to use for execution. This will NOT be altered by this method.</param> /// <param name="resourcesToRefill">The resources that should be refilled.</param> /// <returns></returns> private ExecutionResult ExecuteRefill(SuperMetroidModel model, InGameState inGameState, IEnumerable <ConsumableResourceEnum> resourcesToRefill) { InGameState resultingState = inGameState.Clone(); foreach (ConsumableResourceEnum resource in resourcesToRefill) { resultingState.ApplyRefillResource(model, resource); } return(new ExecutionResult(resultingState)); }
public override ExecutionResult Execute(SuperMetroidModel model, InGameState inGameState, int times = 1, bool usePreviousRoom = false) { if (inGameState.HasGameFlag(GameFlag)) { // Clone the In-game state to fulfill method contract return(new ExecutionResult(inGameState.Clone())); } else { return(null); } }
public override ExecutionResult Execute(SuperMetroidModel model, InGameState inGameState, int times = 1, bool usePreviousRoom = false) { if (inGameState.HasItem(Item)) { // Clone the In-game state to fulfill method contract ExecutionResult result = new ExecutionResult(inGameState.Clone()); result.AddItemsInvolved(new[] { Item }); return(result); } else { return(null); } }
public override ExecutionResult Execute(SuperMetroidModel model, InGameState inGameState, int times = 1, bool usePreviousRoom = false) { int ammoCost = Count * times; if (inGameState.IsResourceAvailable(model, AmmoType.GetConsumableResourceEnum(), ammoCost)) { var resultingState = inGameState.Clone(); resultingState.ApplyConsumeResource(model, AmmoType.GetConsumableResourceEnum(), ammoCost); return(new ExecutionResult(resultingState)); } else { return(null); } }
public override ExecutionResult Execute(SuperMetroidModel model, InGameState inGameState, int times = 1, bool usePreviousRoom = false) { int damage = model.Rules.CalculateEnemyDamage(inGameState, Attack) * Hits * times; if (inGameState.IsResourceAvailable(model, ConsumableResourceEnum.ENERGY, damage)) { InGameState resultingState = inGameState.Clone(); resultingState.ApplyConsumeResource(model, ConsumableResourceEnum.ENERGY, damage); ExecutionResult result = new ExecutionResult(resultingState); result.AddDamageReducingItemsInvolved(model.Rules.GetEnemyDamageReducingItems(model, inGameState, Attack)); return(result); } else { return(null); } }
public override ExecutionResult Execute(SuperMetroidModel model, InGameState inGameState, int times = 1, bool usePreviousRoom = false) { int damage = CalculateDamage(model, inGameState, times: times, usePreviousRoom: usePreviousRoom); if (inGameState.IsResourceAvailable(model, ConsumableResourceEnum.ENERGY, damage)) { var resultingState = inGameState.Clone(); resultingState.ApplyConsumeResource(model, ConsumableResourceEnum.ENERGY, damage); ExecutionResult result = new ExecutionResult(resultingState); result.AddDamageReducingItemsInvolved(GetDamageReducingItems(model, inGameState)); return(result); } else { return(null); } }
public override ExecutionResult Execute(SuperMetroidModel model, InGameState inGameState, int times = 1, bool usePreviousRoom = false) { // Create an ExecutionResult immediately so we can record free kills in it ExecutionResult result = new ExecutionResult(inGameState.Clone()); // Filter the list of valid weapons, to keep only those we can actually use right now IEnumerable <Weapon> usableWeapons = ValidWeapons.Where(w => w.UseRequires.Execute(model, inGameState, times: times, usePreviousRoom: usePreviousRoom) != null); // Find all usable weapons that are free to use. That's all weapons without an ammo cost, plus all weapons whose ammo is farmable in this EnemyKill // Technically if a weapon were to exist with a shot cost that requires something other than ammo (something like energy or ammo drain?), // this wouldn't work. Should that be a worry? IEnumerable <Weapon> freeWeapons = usableWeapons.Where(w => !w.ShotRequires.LogicalElements.Where(le => le is Ammo ammo && !FarmableAmmo.Contains(ammo.AmmoType)).Any()); // Remove all enemies that can be killed by free weapons IEnumerable <IEnumerable <Enemy> > nonFreeGroups = GroupedEnemies .RemoveEnemies(e => { // Look for a free usable weapon this enemy is susceptible to. var firstWeaponSusceptibility = e.WeaponSusceptibilities.Values .Where(ws => freeWeapons.Contains(ws.Weapon, ObjectReferenceEqualityComparer <Weapon> .Default)) .FirstOrDefault(); // If we found a weapon, record a kill and return true (to remove the enemy) if (firstWeaponSusceptibility != null) { result.AddKilledEnemy(e, firstWeaponSusceptibility.Weapon, firstWeaponSusceptibility.Shots); return(true); } // If we didn't find a weapon, return false (to retain the enemy) else { return(false); } }); // If there are no enemies left, we are done! if (!nonFreeGroups.Any()) { return(result); } // The remaining enemies require ammo IEnumerable <Weapon> nonFreeWeapons = usableWeapons.Except(freeWeapons, ObjectReferenceEqualityComparer <Weapon> .Default); IEnumerable <Weapon> nonFreeSplashWeapons = nonFreeWeapons.Where(w => w.HitsGroup); IEnumerable <Weapon> nonFreeIndividualWeapons = nonFreeWeapons.Where(w => !w.HitsGroup); // Iterate over each group, killing it and updating the resulting state. // We'll test many scenarios, each with 0 to 1 splash weapon and a fixed number of splash weapon shots (after which enemies are killed with single-target weapons). // We will not test multiple combinations of splash weapons. foreach (IEnumerable <Enemy> currentEnemyGroup in nonFreeGroups) { // Build a list of combinations of splash weapons and splash shots (including one entry for no splash weapon at all) IEnumerable <(Weapon splashWeapon, int splashShots)> splashCombinations = nonFreeSplashWeapons.SelectMany(w => // Figure out what different shot counts for this weapon will lead to different numbers of casualties currentEnemyGroup .Select(e => e.WeaponSusceptibilities.TryGetValue(w.Name, out WeaponSusceptibility susceptibility) ? susceptibility.Shots : 0) .Where(shots => shots > 0) // Convert each different number of shot into a combination of this weapon and the number of shots .Select(shots => (splashWeapon: w, splashShots: shots)) ) // Add the one entry for not using a splash weapon at all .Append((splashWeapon: null, splashShots: 0)); // Evaluate all combinations and apply the cheapest to our current resulting state (_, ExecutionResult killResult) = model.ExecuteBest(splashCombinations.Select(combination => new EnemyGroupAmmoExecutable(currentEnemyGroup, nonFreeIndividualWeapons, combination.splashWeapon, combination.splashShots)), result.ResultingState, times: times, usePreviousRoom: usePreviousRoom); // If we failed to kill an enemy group, we can't kill all enemies if (killResult == null) { return(null); } // Update the sequential ExecutionResult result = result.ApplySubsequentResult(killResult); } return(result); }
public ExecutionResult Execute(SuperMetroidModel model, InGameState inGameState, int times = 1, bool usePreviousRoom = false) { var requirementsResult = FarmCycle.RequirementExecution.Execute(model, inGameState, times: times, usePreviousRoom: usePreviousRoom); // Can't even execute one cycle, so return a failure if (requirementsResult == null) { return(null); } // Build a dictionary of resources that are spent while doing the execution of a cycle ResourceCount resourceVariation = requirementsResult.ResultingState.GetResourceVariationWith(inGameState); // Start with all consumable resources IDictionary <ConsumableResourceEnum, int> costingResources = Enum.GetValues(typeof(ConsumableResourceEnum)) .Cast <ConsumableResourceEnum>() // Invert the resource variation to convert it to a resource cost .Select(resource => (resource: resource, cost: resourceVariation.GetAmount(resource) * -1)) // Keep only pairs where some of the resource has been spent .Where(resourceCost => resourceCost.cost > 0) // Finally, build a dictionary from the pairs .ToDictionary(resourceCost => resourceCost.resource, resourceCost => resourceCost.cost); // Identify all resources that can be refilled by this farm cycle IEnumerable <ConsumableResourceEnum> farmableResources = ComputeFarmableResources(model, costingResources); // Calculate the initial effective drop rates, taking into account currently full resources. // However, resources that are full but not farmable here should not be taken into account. IEnumerable <ConsumableResourceEnum> fullResources = inGameState.GetFullConsumableResources().Intersect(farmableResources).ToArray(); EnemyDrops initialEffectiveDropRates = FarmCycle.RoomEnemy.Enemy.GetEffectiveDropRates(model, fullResources); // Build a dictionary containing the variation per cycle for each consmable resource IDictionary <ConsumableResourceEnum, decimal> resourceVariationPerCycle = Enum.GetValues(typeof(ConsumableResourceEnum)) .Cast <ConsumableResourceEnum>() .ToDictionary(resource => resource, resource => CalculateResourceVariationPerCycle(model, resource, initialEffectiveDropRates, costingResources)); // Identify resources that are creeping down as we farm IEnumerable <ConsumableResourceEnum> initiallyUnstableResources = costingResources .Where(pair => resourceVariationPerCycle[pair.Key] < 0) .Select(pair => pair.Key) .ToArray(); // If there's no resources we can farm, just return now if (!farmableResources.Any()) { // If any of the resources initially lose out per farm cycle, we're not even able to farm. Return a failure. if (initiallyUnstableResources.Any()) { return(null); } // Otherwise, we're able to farm but it doesn't do anything according to logical options return(new ExecutionResult(inGameState.Clone())); } // If there's no resource that initially loses out, we're not concerned about losing any resources. // We can refill all farmable resources and report a success if (!initiallyUnstableResources.Any()) { return(ExecuteRefill(model, inGameState, farmableResources)); } // If we have resources that initially lose out, they must eventually turn farmable. // Otherwise, we consider this a failure. if (initiallyUnstableResources.Except(farmableResources).Any()) { return(null); } // Now we know we have at least one resource that currently loses out per cycle, but can eventually recharge. // Execute some farming to see if we can stabilize those resources before we run out. IEnumerable <ConsumableResourceEnum> notFullFarmableResources = farmableResources.Except(fullResources).ToArray(); IDictionary <ConsumableResourceEnum, decimal> resourceCounts = Enum.GetValues(typeof(ConsumableResourceEnum)) .Cast <ConsumableResourceEnum>() .ToDictionary(resource => resource, resource => (decimal)inGameState.GetCurrentAmount(resource)); EnemyDrops effectiveDropRates = initialEffectiveDropRates; // Execute farm cycles until a resource runs out or all costing resources have stabilized while (costingResources .Where(pair => resourceVariationPerCycle[pair.Key] < 0) .Any()) { // Figure out how many cycles we need to execute in order to refill something farmable and stable int cyclesToRefillSomething = notFullFarmableResources.Select(resource => decimal.ToInt32(decimal.Ceiling((inGameState.GetMaxAmount(resource) - resourceCounts[resource]) / resourceVariationPerCycle[resource]))) .Where(cycleCount => cycleCount > 0) .Min(); // Apply to each farmable resource the resource variation from executing that many cycles. // We don't care if it goes over maximum since we won't apply these to the in-game state foreach (ConsumableResourceEnum resource in notFullFarmableResources) { resourceCounts[resource] += resourceVariationPerCycle[resource] * cyclesToRefillSomething; } // If an unstable resource has dipped below the cost per cycle, we can't go on. Return a failure. if (costingResources.Where(costingResource => resourceCounts[costingResource.Key] < costingResource.Value).Any()) { return(null); } // If we haven't run out of anything, prepare the next loop // Update full resources fullResources = resourceCounts .Where(pair => pair.Value >= inGameState.GetMaxAmount(pair.Key)) .Select(pair => pair.Key) .Intersect(farmableResources) .ToArray(); // Update farmable resources by excluding newly-full resources notFullFarmableResources = notFullFarmableResources.Except(fullResources).ToArray(); // Calculate a new effective drop rate using the new list of full resources // If that new effective drop rate stabilizes all unstable resources, we'll make it out of the loop effectiveDropRates = model.Rules.CalculateEffectiveDropRates(FarmCycle.RoomEnemy.Enemy.Drops, model.Rules.GetUnneededDrops(fullResources)); // Use the new effective drop rate to calculate the new resourceVariationPerCycle for resources we still care about resourceVariationPerCycle = notFullFarmableResources .ToDictionary(resource => resource, resource => CalculateResourceVariationPerCycle(model, resource, effectiveDropRates, costingResources)); } // All resources are now stable. We already checked beforehand that all costing resources eventually become farmable, // so we can just apply a refill for all farmable resources and return a success. return(ExecuteRefill(model, inGameState, farmableResources)); }