/**
     * Computes the 'intensity' of each {@link MZRoom}. MZRooms generally Get more
     * intense the deeper they are into the dungeon.
     *
     * @param levels    the keyLevel -> room-set mapping to update
     * @ if it fails
     * @see KeyLevelRoomMapping
     * @see MZRoom
     */
    protected void ComputeIntensity(KeyLevelRoomMapping levels)
    {
        double nextLevelBaseIntensity = 0.0;

        for (int level = 0; level < levels.KeyCount(); ++level)
        {
            double intensity = nextLevelBaseIntensity *
                               (1.0 - intensityEaseOff);

            foreach (MZRoom room in levels.GetRooms(level))
            {
                if (room.GetParent() == null ||
                    !room.GetParent().GetPrecond().
                    Implies(room.GetPrecond()))
                {
                    nextLevelBaseIntensity = Math.Max(
                        nextLevelBaseIntensity,
                        ApplyIntensity(room, intensity));
                }
            }
        }

        NormalizeIntensity();

        dungeon.FindBoss().SetIntensity(1.0);
        MZRoom goalRoom = dungeon.FindGoal();

        if (goalRoom != null)
        {
            goalRoom.SetIntensity(0.0);
        }
    }
    /**
     * Places keys within the dungeon in such a way that the dungeon is
     * guaranteed to be solvable.
     *
     * @param levels    the keyLevel -> room-set mapping to use
     * @ if it fails
     * @see KeyLevelRoomMapping
     */
    protected void PlaceKeys(KeyLevelRoomMapping levels)
    {
        // Now place the keys. For every key-level but the last one, place a
        // key for the next level in it, preferring rooms with fewest links
        // (dead end rooms).
        for (int key = 0; key < levels.KeyCount() - 1; ++key)
        {
            List <MZRoom> rooms = levels.GetRooms(key);

            Shuffle(rooms);
            // Collections.sort is stable: it doesn't reorder "equal" elements,
            // which means the shuffling we just did is still useful.
            rooms.Sort(new MZRoomIntensityComparer());
            // Alternatively, use the EDGE_COUNT_COMPARATOR to put keys at
            // 'dead end' rooms.

            MZSymbol keySym = new MZSymbol(key);

            bool placedKey = false;
            foreach (MZRoom room in rooms)
            {
                if (room.GetItem() == null && constraints.RoomCanFitItem(room.id, keySym))
                {
                    room.SetItem(keySym);
                    placedKey = true;
                    break;
                }
            }
            if (!placedKey)
            {
                // there were no rooms into which the key would fit
                throw new RetryException();
            }
        }
    }
    /**
     * Fill the dungeon's space with rooms and doors (some locked).
     * Keys are not inserted at this point.
     *
     * @param levels    the keyLevel -> room-set mapping to update
     * @ if it fails
     * @see KeyLevelRoomMapping
     */
    protected void PlaceRooms(KeyLevelRoomMapping levels, int roomsPerLock)
    {
        // keyLevel: the number of keys required to Get to the new room
        int      keyLevel  = 0;
        MZSymbol latestKey = null;
        // condition that must hold true for the player to reach the new room
        // (the set of keys they must have).
        MZCondition cond = new MZCondition();

        // Loop to place rooms and link them
        while (dungeon.RoomCount() < constraints.GetMaxRooms())
        {
            bool doLock = false;

            // Decide whether we need to place a new lock
            // (Don't place the last lock, since that's reserved for the boss)
            if (ShouldAddNewLock(keyLevel, levels.GetRooms(keyLevel).Count, roomsPerLock))
            {
                latestKey = new MZSymbol(keyLevel++);
                cond      = cond.And(latestKey);
                doLock    = true;
            }

            // Find an existing room with a free edge:
            MZRoom parentRoom = null;
            if (!doLock && UnityEngine.Random.Range(0, 10) > 0)
            {
                parentRoom = ChooseRoomWithFreeEdge(levels.GetRooms(keyLevel),
                                                    keyLevel);
            }
            if (parentRoom == null)
            {
                parentRoom = ChooseRoomWithFreeEdge(new List <MZRoom>(dungeon.GetRooms()),
                                                    keyLevel);
                doLock = true;
            }

            if (parentRoom == null)
            {
                throw new OutOfRoomsException();
            }

            // Decide which direction to put the new room in relative to the
            // parent
            int nextId = ChooseFreeEdge(parentRoom, keyLevel);
            List <Vector2Int> coords = constraints.GetCoords(nextId);
            MZRoom            room   = new MZRoom(nextId, coords, parentRoom, null, cond);

            // Add the room to the dungeon
            dungeon.Add(room);
            parentRoom.AddChild(room);
            dungeon.Link(parentRoom, room, doLock ? latestKey : null);
            levels.AddRoom(keyLevel, room);
        }
    }
    /**
     * Sets up the dungeon's entrance room.
     *
     * @param levels    the keyLevel -> room-set mapping to update
     * @see KeyLevelRoomMapping
     */
    protected void InitEntranceRoom(KeyLevelRoomMapping levels)
    {
        int        id;
        List <int> possibleEntries = new List <int>(constraints.InitialRooms());

        id = possibleEntries[UnityEngine.Random.Range(0, possibleEntries.Count)];

        MZRoom entry = new MZRoom(id, constraints.GetCoords(id), null,
                                  new MZSymbol((int)MZSymbol.MZSymbolValue.Start), new MZCondition());

        dungeon.Add(entry);

        levels.AddRoom(0, entry);
    }
    public void Generate()
    {
        int attempt = 0;

        while (true)
        {
            try {
                KeyLevelRoomMapping levels;
                int roomsPerLock;
                if (constraints.GetMaxKeys() > 0)
                {
                    roomsPerLock = constraints.GetMaxRooms() /
                                   constraints.GetMaxKeys();
                }
                else
                {
                    roomsPerLock = constraints.GetMaxRooms();
                }

                bool keepTrying = true;
                levels = null;
                while (keepTrying)
                {
                    dungeon = new MZDungeon();

                    // Maps keyLevel -> MZRooms that were created when lockCount had that
                    // value
                    levels = new KeyLevelRoomMapping(constraints.GetMaxKeys());

                    // Create the entrance to the dungeon:
                    InitEntranceRoom(levels);

                    try {
                        // Fill the dungeon with rooms:
                        PlaceRooms(levels, roomsPerLock);
                        keepTrying = false;
                    } catch (OutOfRoomsException e) {
                        // We can run out of rooms where certain links have
                        // predetermined locks. Example: if a river bisects the
                        // map, the keyLevel for rooms in the river > 0 because
                        // crossing water requires a key. If there are not
                        // enough rooms before the river to build up to the
                        // key for the river, we've run out of rooms.
                        if (debug)
                        {
                            Debug.Log("Ran out of rooms. roomsPerLock was " + roomsPerLock);
                        }
                        roomsPerLock = roomsPerLock * constraints.GetMaxKeys() /
                                       (constraints.GetMaxKeys() + 1);
                        if (debug)
                        {
                            Debug.Log("roomsPerLock is now " + roomsPerLock);
                        }

                        if (roomsPerLock == 0)
                        {
                            throw new MZGenerationFailureException(
                                      "Failed to place rooms. Have you forgotten to disable boss-locking?");
                            // If the boss room is locked, the final key is used
                            // only for the boss room. So if the final key is
                            // also used to cross the river, rooms cannot be
                            // placed.
                        }
                    }
                }

                // Place the boss and goal rooms:
                PlaceBossGoalRooms(levels);

                // Place switches and the locks that require it:
                PlaceSwitches();

                ComputeIntensity(levels);

                // Place the keys within the dungeon:
                PlaceKeys(levels);

                if (levels.KeyCount() - 1 != constraints.GetMaxKeys())
                {
                    throw new RetryException();
                }

                // Make the dungeon less tree-like:
                Graphify();

                CheckAcceptable();
                return;
            } catch (RetryException e) {
                if (++attempt > maxRetries)
                {
                    throw new MZGenerationFailureException("MZDungeon generator failed", e);
                }
                if (debug)
                {
                    Debug.Log("Retrying dungeon generation...");
                }
            }
        }
    }
    /**
     * Places the BOSS and GOAL rooms within the dungeon, in existing rooms.
     * These rooms are moved into the next keyLevel.
     *
     * @param levels    the keyLevel -> room-set mapping to update
     * @ if it fails
     * @see KeyLevelRoomMapping
     */
    protected void PlaceBossGoalRooms(KeyLevelRoomMapping levels)
    {
        List <MZRoom> possibleGoalRooms = new List <MZRoom>(dungeon.RoomCount());

        MZSymbol goalSym = new MZSymbol((int)MZSymbol.MZSymbolValue.Goal),
                 bossSym = new MZSymbol((int)MZSymbol.MZSymbolValue.Boss);

        foreach (MZRoom room in dungeon.GetRooms())
        {
            if (room.GetChildren().Count > 0 || room.GetItem() != null)
            {
                continue;
            }
            MZRoom parent = room.GetParent();
            if (parent == null)
            {
                continue;
            }
            if (IsGenerateGoal() && (parent.GetChildren().Count != 1 ||
                                     !parent.GetPrecond().Implies(room.GetPrecond())))
            {
                continue;
            }
            if (IsGenerateGoal())
            {
                if (!constraints.RoomCanFitItem(room.id, goalSym) ||
                    !constraints.RoomCanFitItem(parent.id, bossSym))
                {
                    continue;
                }
            }
            else
            {
                if (!constraints.RoomCanFitItem(room.id, bossSym))
                {
                    continue;
                }
            }
            possibleGoalRooms.Add(room);
        }

        if (possibleGoalRooms.Count == 0)
        {
            throw new RetryException();
        }

        MZRoom goalRoom = possibleGoalRooms[UnityEngine.Random.Range(0,
                                                                     possibleGoalRooms.Count)],
               bossRoom = goalRoom.GetParent();

        if (!IsGenerateGoal())
        {
            bossRoom = goalRoom;
            goalRoom = null;
        }

        if (goalRoom != null)
        {
            goalRoom.SetItem(goalSym);
        }
        bossRoom.SetItem(bossSym);

        int oldKeyLevel = bossRoom.GetPrecond().GetKeyLevel(),
            newKeyLevel = Math.Min(levels.KeyCount(), constraints.GetMaxKeys());

        if (oldKeyLevel != newKeyLevel)
        {
            List <MZRoom> oklRooms = levels.GetRooms(oldKeyLevel);
            if (goalRoom != null)
            {
                oklRooms.Remove(goalRoom);
            }
            oklRooms.Remove(bossRoom);

            if (goalRoom != null)
            {
                levels.AddRoom(newKeyLevel, goalRoom);
            }
            levels.AddRoom(newKeyLevel, bossRoom);

            MZSymbol    bossKey = new MZSymbol(newKeyLevel - 1);
            MZCondition precond = bossRoom.GetPrecond().And(bossKey);
            bossRoom.SetPrecond(precond);
            if (goalRoom != null)
            {
                goalRoom.SetPrecond(precond);
            }

            if (newKeyLevel == 0)
            {
                dungeon.Link(bossRoom.GetParent(), bossRoom);
            }
            else
            {
                dungeon.Link(bossRoom.GetParent(), bossRoom, bossKey);
            }
            if (goalRoom != null)
            {
                dungeon.Link(bossRoom, goalRoom);
            }
        }
    }