コード例 #1
0
        public override ExecutionResult Execute(SuperMetroidModel model, InGameState inGameState, int times = 1, bool usePreviousRoom = false)
        {
            var mustShinespark            = ShinesparkFrames > 0;
            var energyNeededForShinespark = model.Rules.CalculateEnergyNeededForShinespark(ShinesparkFrames, times: times);
            var shinesparkEnergyCost      = model.Rules.CalculateShinesparkDamage(inGameState, ShinesparkFrames, times: times);
            // Not calling IsResourceAvailable() because Samus only needs to have that much energy, not necessarily spend all of it
            Predicate <InGameState>  hasEnergyForShinespark  = state => state.GetCurrentResources().GetAmount(ConsumableResourceEnum.ENERGY) >= energyNeededForShinespark;
            Action <ExecutionResult> consumeShinesparkEnergy = result => result.ResultingState.ApplyConsumeResource(model, ConsumableResourceEnum.ENERGY, shinesparkEnergyCost);

            // Check simple preconditions before looking at anything
            if (!inGameState.HasSpeedBooster() || (mustShinespark && !model.CanShinespark()))
            {
                return(null);
            }

            ExecutionResult bestOverallResult = null;

            // So we have to see if we can use an in-room runway (not coming in).
            // And we have to see if we can use an adjacent runway by itself.
            // And we have to see if we can use a canLeavecharged.
            // And we have to see if we can combine an adjacent runway with a an in-room runway (coming in).
            // And after that we have to still be able to execute the shinespark.

            // We'll start with looking at in-room runways (not coming in).
            // For all in-room runways we are able to use while still doing the shinespark after,
            // figure out the resulting state, effective length, and the overall best resulting state
            var(usableInRoomRunwayEvaluations, bestInRoomResult) =
                EvaluateRunways(model, inGameState, FromNode.Runways, times, usePreviousRoom, hasEnergyForShinespark, runwaysReversible: true);

            // If using this in-room runway cost nothing, spend the shinespark and return
            if (model.CompareInGameStates(inGameState, bestInRoomResult?.ResultingState) == 0)
            {
                consumeShinesparkEnergy(bestInRoomResult);
                bestInRoomResult.AddItemsInvolved(new Item[] { model.Items[SuperMetroidModel.SPEED_BOOSTER_NAME] });
                return(bestInRoomResult);
            }

            // If the best in-room runway we found is an improvement over the previous best solution, replace it
            if (model.CompareInGameStates(bestInRoomResult?.ResultingState, bestOverallResult?.ResultingState) > 0)
            {
                bestOverallResult = bestInRoomResult;
            }

            // Next Step: all adjacent runways with their resulting state.

            // If no in-room path is specified, then player is expected to have entered at fromNode and not moved
            var requiredInRoomPath = (InRoomPath == null || !InRoomPath.Any()) ? new[] { FromNodeId } : InRoomPath;

            // For all adjacent runways that can be used retroactively while still doing the shinespark after,
            // figure out the resulting state, effective length, and the overall best resulting state
            var(usableAdjacentRunwayEvaluations, bestAdjacentRunwayResult) =
                EvaluateRunways(model, inGameState, inGameState.GetRetroactiveRunways(requiredInRoomPath, usePreviousRoom: true), times, usePreviousRoom,
                                hasEnergyForShinespark, runwaysReversible: false);

            // If using this adjacent runway cost nothing, spend the shinespark and return
            if (model.CompareInGameStates(inGameState, bestAdjacentRunwayResult?.ResultingState) == 0)
            {
                consumeShinesparkEnergy(bestAdjacentRunwayResult);
                bestAdjacentRunwayResult.AddItemsInvolved(new Item[] { model.Items[SuperMetroidModel.SPEED_BOOSTER_NAME] });
                return(bestAdjacentRunwayResult);
            }

            // If the best adjacent runway we found is an improvement over the previous best solution, replace it
            if (model.CompareInGameStates(bestAdjacentRunwayResult?.ResultingState, bestOverallResult?.ResultingState) > 0)
            {
                bestOverallResult = bestAdjacentRunwayResult;
            }

            // Next step: Find the best retroactive CanLeaveCharged that has enough frames
            // remaining and leaves Samus with enough energy for the shinespark
            var usableCanLeaveChargeds = inGameState.GetRetroactiveCanLeaveChargeds(model, requiredInRoomPath, usePreviousRoom: usePreviousRoom)
                                         .Where(clc => clc.FramesRemaining >= FramesRemaining);

            (_, ExecutionResult bestLeaveChargedResult) = model.ExecuteBest(usableCanLeaveChargeds, inGameState, times: times,
                                                                            usePreviousRoom: usePreviousRoom, hasEnergyForShinespark);

            // If using this CanLeaveCharged cost nothing, spend the shinespark and return
            if (model.CompareInGameStates(inGameState, bestLeaveChargedResult?.ResultingState) == 0)
            {
                consumeShinesparkEnergy(bestLeaveChargedResult);
                bestLeaveChargedResult.AddItemsInvolved(new Item[] { model.Items[SuperMetroidModel.SPEED_BOOSTER_NAME] });
                return(bestLeaveChargedResult);
            }

            // If the best CanLeaveCharged we found is an improvement over the previous best solution, replace it
            if (model.CompareInGameStates(bestLeaveChargedResult?.ResultingState, bestOverallResult?.ResultingState) > 0)
            {
                bestOverallResult = bestLeaveChargedResult;
            }

            // Next step: Find the best combination of adjacent and in-room runway
            // We can re-use the results from evaluating the adjacent runways that we calculated, but not
            // the in-room ones (because executions, not results, must be applied on top of each other).
            // Iterate over usable adjacent runways that actually offer a gain over the number of
            // tiles lost by the room transation, and match each of those against each in-room
            // runway that is usable coming in and combines for a long enough runway
            foreach (var(_, currentAdjacentRunwayResult, currentLength) in usableAdjacentRunwayEvaluations.Where(runway => runway.length > model.Rules.RoomTransitionTilesLost))
            {
                var requiredInRoomLength = model.LogicalOptions.TilesToShineCharge + model.Rules.RoomTransitionTilesLost - currentLength;

                // Determine which runways we may attempt to use. Limit to the ones we evaluated
                // earlier because there's no point re-evaluating those we couldn't execute then,
                // but we'll re-attempt to use the runways using the resulting state of the current
                // adjacent runway.
                // We'll also ignore in-room runways that are not long enough to combine with our
                // current adjacent runway.
                var adequateRunways =
                    from r in usableInRoomRunwayEvaluations
                    where r.length >= requiredInRoomLength
                    select r.runway;
                var(_, bestCombinationResult) = model.ExecuteBest(adequateRunways.Select(runway => runway.AsExecutable(comingIn: true)),
                                                                  currentAdjacentRunwayResult.ResultingState, times: times, usePreviousRoom: usePreviousRoom, hasEnergyForShinespark);

                // If the best combination we found is free, spend energy for the shinespark and return it.
                // Make sure to apply the in-room runway result on top of the adjacent runway result.
                if (model.CompareInGameStates(inGameState, bestCombinationResult?.ResultingState) == 0)
                {
                    consumeShinesparkEnergy(bestCombinationResult);
                    bestCombinationResult.AddItemsInvolved(new Item[] { model.Items[SuperMetroidModel.SPEED_BOOSTER_NAME] });
                    return(currentAdjacentRunwayResult.Clone().ApplySubsequentResult(bestCombinationResult));
                }

                // If the best combination we found is free, spend energy for the shinespark and return it.
                // Make sure to apply the in-room runway result on top of the adjacent runway result.
                if (model.CompareInGameStates(bestCombinationResult?.ResultingState, bestOverallResult?.ResultingState) > 0)
                {
                    consumeShinesparkEnergy(bestCombinationResult);
                    bestOverallResult = currentAdjacentRunwayResult.Clone().ApplySubsequentResult(bestCombinationResult);
                }
            }

            // If we have found no solution at all that we can execute and that leaves us with
            // enough energy for the shinespark, we cannot do this
            if (bestOverallResult == null)
            {
                return(null);
            }
            // Apply shinespark on the best solution we've found and return it
            else
            {
                consumeShinesparkEnergy(bestOverallResult);
                bestOverallResult.AddItemsInvolved(new Item[] { model.Items[SuperMetroidModel.SPEED_BOOSTER_NAME] });
                return(bestOverallResult);
            }
        }