public ExecutionResult Execute(SuperMetroidModel model, InGameState inGameState, int times = 1, bool usePreviousRoom = false)
        {
            // There may be up to 2 requirements. This StratObstacle may have some, and the RoomObstacle may also have some general requirements that apply to any strat.

            // Start with the RoomObstacle's requirements
            ExecutionResult result = StratObstacle.Obstacle.Requires.Execute(model, inGameState, times: times, usePreviousRoom: usePreviousRoom);

            // If we couldn't execute the RoomObstacle's requirements, give up
            if (result == null)
            {
                return(null);
            }

            // Add this specific StratObstacle's requirements
            result = result.AndThen(StratObstacle.Requires, model, times: times, usePreviousRoom: usePreviousRoom);
            // If that failed, give up
            if (result == null)
            {
                return(null);
            }

            // We have succeeded, but we must update the ExecutionResult and its InGameState to reflect any destroyed obstacles
            result.ApplyDestroyedObstacles(new[] { StratObstacle.Obstacle }.Concat(StratObstacle.AdditionalObstacles), usePreviousRoom);

            return(result);
        }
        public ExecutionResult Execute(SuperMetroidModel model, InGameState inGameState, int times = 1, bool usePreviousRoom = false)
        {
            times = times * model.LogicalOptions.NumberOfTries(this);

            ExecutionResult result = Requires.Execute(model, inGameState, times: times, usePreviousRoom: usePreviousRoom);

            if (result == null)
            {
                return(null);
            }

            // Iterate over intact obstacles that need to be dealt with
            foreach (StratObstacle obstacle in Obstacles.Where(o => !inGameState.GetDestroyedObstacleIds(usePreviousRoom).Contains(o.ObstacleId)))
            {
                // Try destroying the obstacle first
                ExecutionResult destroyResult = result.AndThen(obstacle.DestroyExecution, model, times: times, usePreviousRoom: usePreviousRoom);

                // If destruction fails, try to bypass instead
                if (destroyResult == null)
                {
                    result = result.AndThen(obstacle.BypassExecution, model, times: times, usePreviousRoom: usePreviousRoom);
                    // If bypass also fails, we cannot get past this obstacle. Give up.
                    if (result == null)
                    {
                        return(null);
                    }
                }
                // If destruction succeeded, carry on with the result of that
                else
                {
                    result = destroyResult;
                }
            }

            return(result);
        }