/// <summary>
        /// Returns a sequence of IDs of obstacles that have been destroyed in the current room since entering.
        /// May be empty if the in-room state is not being tracked.
        /// </summary>
        /// <param name="usePreviousRoom">If true, uses the last known room state at the previous room instead of the current room to answer.</param>
        /// <returns></returns>
        public IEnumerable <string> GetDestroyedObstacleIds(bool usePreviousRoom = false)
        {
            InRoomState          roomState   = usePreviousRoom ? PreviousRoomState : InRoomState;
            IEnumerable <string> returnValue = roomState?.DestroyedObstacleIds;

            return(returnValue == null?Enumerable.Empty <string>() : returnValue);
        }
        /// <summary>
        /// Returns a sequence of IDs of nodes that have been visited in the current room since entering, in order,
        /// starting with the node through which the room was entered. May be empty if the in-room state is not being tracked.
        /// </summary>
        /// <param name="usePreviousRoom">If true, uses the last known room state at the previous room instead of the current room to answer.</param>
        /// <returns></returns>
        public IEnumerable <int> GetVisitedNodeIds(bool usePreviousRoom = false)
        {
            InRoomState       roomState   = usePreviousRoom ? PreviousRoomState : InRoomState;
            IEnumerable <int> returnValue = roomState?.VisitedRoomPath?.Select(pathNode => pathNode.node.Id);

            return(returnValue == null?Enumerable.Empty <int>() : returnValue);
        }
        /// <summary>
        /// Returns a sequence of nodes that have been visited in this room since entering, in order,
        /// starting with the node through which the room was entered. May be empty if the in-room state is not being tracked.
        /// Each node ID is accompanied by the strat that was used to reach it, when applicable.
        /// This strat can be null since nodes are reached without using a strat when entering.
        /// </summary>
        /// <param name="usePreviousRoom">If true, uses the last known room state at the previous room instead of the current room to answer.</param>
        /// <returns></returns>
        public IEnumerable <(RoomNode node, Strat strat)> GetVisitedPath(bool usePreviousRoom = false)
        {
            InRoomState roomState   = usePreviousRoom ? PreviousRoomState : InRoomState;
            var         returnValue = roomState?.VisitedRoomPath;

            return(returnValue == null?Enumerable.Empty <(RoomNode, Strat)>() : returnValue);
        }
        // STITCHME It might be valuable to eventually have InGameState be able to say which nodes are reachable?

        // STITCHME It could be nice to keep track of all canResets in the room and evaluate them as you move around?
        // Another option would be to have something in an initialization phase that converts canResets into just names,
        // and adds information on nodes and strats that they invalidate the canReset.
        // We'll see when we get to the step of reducing logical elements *shrug*

        /// <summary>
        /// Creates a new InGameState
        /// </summary>
        /// <param name="model">A SuperMetroidModel. Its rooms must have both been set and initialized.
        /// Its items and game flags must also have been set.</param>
        /// <param name="itemContainer">The result of reading the items.json file.</param>
        public InGameState(SuperMetroidModel model, ItemContainer itemContainer)
        {
            IEnumerable <ResourceCapacity> startingResources = itemContainer.StartingResources;

            Inventory = new ItemInventory(startingResources);

            Resources = new ResourceCount();

            // Start the player at full
            foreach (ResourceCapacity capacity in startingResources)
            {
                Resources.ApplyAmountIncrease(capacity.Resource, capacity.MaxAmount);
            }

            // Initialize starting game flags
            foreach (string gameFlagName in itemContainer.StartingGameFlagNames)
            {
                ApplyAddGameFlag(model.GameFlags[gameFlagName]);
            }

            // Initialize starting items
            foreach (string itemName in itemContainer.StartingItemNames)
            {
                ApplyAddItem(model.Items[itemName]);
            }

            RoomNode startingNode = model.Rooms[itemContainer.StartingRoomName].Nodes[itemContainer.StartingNodeId];

            InRoomState = new InRoomState(startingNode);
        }
 public InRoomState(InRoomState other)
 {
     CurrentNode             = other.CurrentNode;
     VisitedRoomPathList     = new List <(RoomNode node, Strat strat)>(other.VisitedRoomPathList);
     DestroyedObstacleIdsSet = new HashSet <string>(other.DestroyedObstacleIdsSet);
     BypassedExitLock        = other.BypassedExitLock;
     OpenedExitLock          = other.OpenedExitLock;
     LastStrat = other.LastStrat;
 }
        /// <summary>
        /// <para>Positions the in-game state as it would be after entering a room via the provided node. This may place the player at a different node immediately
        /// if the node calls for it.</para>
        /// <para>This method allows for leaving "remotely": leaving through a node the player is not at, by performing an action that must be initiated where the player is.
        /// A known example is using a CanLeaveCharged that initiates at a different node than the door it's on.</para>
        /// <para>Be aware that when leaving remotely, the exact action used is only determined retroactively once in the next room, and that leaving remotely is
        /// not considered valid if it's not made use of by a strat in the next room.
        /// </para>
        /// </summary>
        /// <param name="entryNode">The node (in the next room) through which the next room will be enteted.</param>
        /// <param name="bypassExitLock">Indicates whether the player is leaving the current room by bypassing a lock on the exit door.</param>
        /// <param name="openExitLock">Indicates whether the player is leaving the current room by opening a lock on the exit door.</param>
        public void ApplyEnterRoom(RoomNode entryNode, bool bypassExitLock, bool openExitLock)
        {
            // Finalize current room state with exit state
            InRoomState.ApplyExitRoom(bypassExitLock, openExitLock);

            // Copy current room state and remember it as previous
            PreviousRoomState = new InRoomState(InRoomState);

            // Enter next room
            InRoomState.ApplyEnterRoom(entryNode);
        }
        /// <summary>
        /// A copy constructor that creates a new InGameState based on the provided one.
        /// This is a somewhat shallow copy; referenced objects whose inner state does not change with a game state (such as Room, GameFlag, etc.) will not be copied.
        /// The inner InRoomState and anything else that fully belongs to the InGameState does get copied.
        /// </summary>
        /// <param name="other">The InGameState to copy</param>
        public InGameState(InGameState other)
        {
            ActiveGameFlags = new Dictionary <string, GameFlag>(other.ActiveGameFlags);

            TakenItemLocations = new Dictionary <string, RoomNode>(other.TakenItemLocations);

            OpenedLocks = new Dictionary <String, NodeLock>(other.OpenedLocks);

            Inventory = other.Inventory.Clone();

            Resources = other.Resources.Clone();

            InRoomState = new InRoomState(other.InRoomState);

            if (other.PreviousRoomState != null)
            {
                PreviousRoomState = new InRoomState(other.PreviousRoomState);
            }
        }
 /// <summary>
 /// Removes all in-room data from this InGameState. Useful if this has been initialized at a starting node but in-room state is not going to be maintained.
 /// </summary>
 public void ApplyClearRoomState()
 {
     InRoomState.ClearRoomState();
     PreviousRoomState?.ClearRoomState();
 }
 /// <summary>
 /// Updates the in-room state to contain a mention of the destruction of the provided obstacle.
 /// This obstacle should be in the current room.
 /// </summary>
 /// <param name="obstacle">The obstacle to destroy.</param>
 public void ApplyDestroyedObstacle(RoomObstacle obstacle)
 {
     InRoomState.ApplyDestroyedObstacle(obstacle);
 }
 /// <summary>
 /// Positions the in-game state at the provided node. This node should be inside the current room.
 /// </summary>
 /// <param name="nodeToVisit">The node to go to</param>
 /// <param name="strat">The strat through which the node is being reached. Can be null. If not null, only makes sense if
 /// it's on a link that connects previous node to new node.</param>
 public void ApplyVisitNode(RoomNode nodeToVisit, Strat strat)
 {
     InRoomState.ApplyVisitNode(nodeToVisit, strat);
 }
        /// <summary>
        /// Returns the strat that was used to reach the current node, if any. Otherwise, returns null.
        /// </summary>
        /// <param name="usePreviousRoom">If true, uses the last known room state at the previous room instead of the current room to answer.</param>
        /// <returns></returns>
        public Strat GetLastStrat(bool usePreviousRoom = false)
        {
            InRoomState roomState = usePreviousRoom ? PreviousRoomState : InRoomState;

            return(roomState?.LastStrat);
        }
        /// <summary>
        /// Returns the room the player is currently in. This can be null if in-room state isn't being tracked.
        /// </summary>
        /// <param name="usePreviousRoom">If true, uses the last known room state at the previous room instead of the current room to answer.</param>
        /// <returns></returns>
        public Room GetCurrentRoom(bool usePreviousRoom = false)
        {
            InRoomState roomState = usePreviousRoom ? PreviousRoomState : InRoomState;

            return(roomState?.CurrentRoom);
        }
        /// <summary>
        /// Returns whether the player is exiting the room by opening a lock on the node they are exiting by.
        /// </summary>
        /// <param name="usePreviousRoom">If true, uses the last known room state at the previous room instead of the current room to answer.</param>
        /// <returns></returns>
        public bool OpeningExitLock(bool usePreviousRoom = false)
        {
            InRoomState roomState = usePreviousRoom ? PreviousRoomState : InRoomState;

            return(roomState?.OpenedExitLock ?? false);
        }
        /// <summary>
        /// Returns whether the player is exiting the room by bypassing a lock on the node they are exiting by.
        /// </summary>
        /// <param name="usePreviousRoom">If true, uses the last known room state at the previous room instead of the current room to answer.</param>
        /// <returns></returns>
        public bool BypassingExitLock(bool usePreviousRoom = false)
        {
            InRoomState roomState = usePreviousRoom ? PreviousRoomState : InRoomState;

            return(roomState?.BypassedExitLock ?? false);
        }
        /// <summary>
        /// Returns the node the player is currently at. This can be null if in-room state isn't being tracked.
        /// </summary>
        /// <param name="usePreviousRoom">If true, uses the last known room state at the previous room instead of the current room to answer.</param>
        /// <returns></returns>
        public RoomNode GetCurrentNode(bool usePreviousRoom = false)
        {
            InRoomState roomState = usePreviousRoom ? PreviousRoomState : InRoomState;

            return(roomState?.CurrentNode);
        }