コード例 #1
0
        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()));
        }
コード例 #2
0
        /// <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));
        }
コード例 #3
0
 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);
     }
 }
コード例 #4
0
 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);
     }
 }
コード例 #5
0
        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);
            }
        }
コード例 #6
0
        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);
            }
        }
コード例 #8
0
        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);
        }
コード例 #9
0
        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));
        }