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