/**
     * 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);
    }
    /**
     * 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);
            }
        }
    }