/// <summary> /// Creates Places list, walking critical path and adding subpaths recursively. /// Creates _lockedDoorCandidates list, places on the way to end room and to chest places. /// </summary> /// <param name="startRoom"></param> /// <param name="path"></param> private void InitLists(RoomTrait startRoom, List <MazeMove> path) { var room = startRoom; var depth = 0; foreach (var move in path) { this.Places.Add(new PlaceNode(room, depth, false)); room.RoomIndex = this.Places.Count - 1; room.isOnPath = true; var lockedDoorCandidate = new LockedDoorCandidateNode(room, move.Direction, this.Places.Count - 1); _lockedDoorCandidates.AddLast(lockedDoorCandidate); for (var direction = 0; direction < 4; direction++) { if (move.Direction != direction && room.Links[direction] == LinkType.To) { var nextRoom = room.Neighbor[direction]; if (nextRoom != null) { CreateEmptyPlacesRecursive(nextRoom, depth + 1); } } } room = room.Neighbor[move.Direction]; depth++; } }
/// <summary> /// Finds appropriate place to use as unlock place for given locked place. /// </summary> /// <param name="lockedPlace"></param> /// <returns>Index of unlock place in Places list</returns> public int GetUnlock(PuzzlePlace lockedPlace) { var lockedPlaceIndex = lockedPlace.PlaceIndex; // Get index of room behind lockedPlace door. lockedPlaceIndex = this.Places[lockedPlaceIndex].Room.Neighbor[lockedPlace.DoorDirection].RoomIndex; if (lockedPlaceIndex == 0 || lockedPlaceIndex == -1) // should be last room in section. lockedPlaceIndex = this.Places.Count; List<int> possiblePlacesOnPath = new List<int>(); List<int> possiblePlacesNotOnPath = new List<int>(); var deadEnd = -1; var deadEndDepth = 0; RoomTrait room; // places before lockedPlaceIndex are always behind it. for (var i = 0; i < lockedPlaceIndex; ++i) { room = this.Places[i].Room; if (!this.Places[i].IsUsed && !room.isLocked && room.RoomType != RoomType.Start) { // Check is this place have locked doors. // Locked places tend to not reserve themselves, so they could share place with some puzzle, like another locked place. // But they couldn't be shared with unlock places, because they have to control all doors. var haveLockedDoors = false; for (var j = 0; j < 4; ++j) if (room.DoorType[j] == (int)DungeonBlockType.DoorWithLock && room.Links[j] == LinkType.To) { haveLockedDoors = true; break; } if (!haveLockedDoors) { var emptyNeighborCount = 0; for (var dir = 0; dir < 4; ++dir) if (room.IsLinked(dir) && !room.Neighbor[dir].isReserved) emptyNeighborCount++; if (emptyNeighborCount < 2) { if (deadEndDepth < this.Places[i].Depth) deadEnd = i; } if (room.isOnPath) possiblePlacesOnPath.Add(i); else possiblePlacesNotOnPath.Add(i); } } } // Chance to not use deepest corner for unlock place. if (_rng.GetUInt32(100) < 40) deadEnd = -1; if (possiblePlacesOnPath.Count == 0 && possiblePlacesNotOnPath.Count == 0) { // Convert locked place room back to alley if there are no more locked doors. room = this.Places[lockedPlace.PlaceIndex].Room; room.SetDoorType(lockedPlace.DoorDirection, (int)DungeonBlockType.Alley); room.SetPuzzleDoor(null, lockedPlace.DoorDirection); var isLockedRoom = room.DoorType.Any(x => (x == (int)DungeonBlockType.DoorWithLock || x == (int)DungeonBlockType.BossDoor)); if (!isLockedRoom) { room.isLocked = false; room.RoomType = RoomType.Alley; } // Return locked place door to list on available doors. var lockedDoorCandidate = new LockedDoorCandidateNode(room, lockedPlace.DoorDirection, room.RoomIndex); _lockedDoorCandidates.AddFirst(lockedDoorCandidate); throw new PuzzleException("Out of unlock places."); } var possiblePlaces = (possiblePlacesNotOnPath.Count > 0 ? possiblePlacesNotOnPath : possiblePlacesOnPath); var placeIndex = deadEnd != -1 ? deadEnd : possiblePlaces[(int)_rng.GetUInt32((uint)possiblePlaces.Count)]; // Walk down from current place to our path and add new possible doors to this._lockedDoorCandidates room = this.Places[placeIndex].Room; while (room.RoomType != RoomType.Start) { if (room.isOnPath) break; var dir = room.GetIncomingDirection(); room.isOnPath = true; room = room.Neighbor[dir]; // skip reserved doors //if (room.ReservedDoor[Direction.GetOppositeDirection(dir)]) continue; // skip reserved places if (room.isReserved) continue; var lockedDoorCandidate = new LockedDoorCandidateNode(room, Direction.GetOppositeDirection(dir), room.RoomIndex); _lockedDoorCandidates.AddFirst(lockedDoorCandidate); } return placeIndex; }
/// <summary> /// Creates Places list, walking critical path and adding subpaths recursively. /// Creates _lockedDoorCandidates list, places on the way to end room and to chest places. /// </summary> /// <param name="startRoom"></param> /// <param name="path"></param> private void InitLists(RoomTrait startRoom, List<MazeMove> path) { var room = startRoom; var depth = 0; foreach (var move in path) { this.Places.Add(new PlaceNode(room, depth, false)); room.RoomIndex = this.Places.Count - 1; room.isOnPath = true; var lockedDoorCandidate = new LockedDoorCandidateNode(room, move.Direction, this.Places.Count - 1); _lockedDoorCandidates.AddLast(lockedDoorCandidate); for (var direction = 0; direction < 4; direction++) { if (move.Direction != direction && room.Links[direction] == LinkType.To) { var nextRoom = room.Neighbor[direction]; if (nextRoom != null) CreateEmptyPlacesRecursive(nextRoom, depth + 1); } } room = room.Neighbor[move.Direction]; depth++; } }
/// <summary> /// Finds appropriate place to use as unlock place for given locked place. /// </summary> /// <param name="lockedPlace"></param> /// <returns>Index of unlock place in Places list</returns> public int GetUnlock(PuzzlePlace lockedPlace) { var lockedPlaceIndex = lockedPlace.PlaceIndex; // Get index of room behind lockedPlace door. lockedPlaceIndex = this.Places[lockedPlaceIndex].Room.Neighbor[lockedPlace.DoorDirection].RoomIndex; if (lockedPlaceIndex == 0 || lockedPlaceIndex == -1) // should be last room in section. { lockedPlaceIndex = this.Places.Count; } List <int> possiblePlacesOnPath = new List <int>(); List <int> possiblePlacesNotOnPath = new List <int>(); var deadEnd = -1; var deadEndDepth = 0; RoomTrait room; // places before lockedPlaceIndex are always behind it. for (var i = 0; i < lockedPlaceIndex; ++i) { room = this.Places[i].Room; if (!this.Places[i].IsUsed && !room.isLocked && room.RoomType != RoomType.Start) { // Check is this place have locked doors. // Locked places tend to not reserve themselves, so they could share place with some puzzle, like another locked place. // But they couldn't be shared with unlock places, because they have to control all doors. var haveLockedDoors = false; for (var j = 0; j < 4; ++j) { if (room.DoorType[j] == (int)DungeonBlockType.DoorWithLock && room.Links[j] == LinkType.To) { haveLockedDoors = true; break; } } if (!haveLockedDoors) { var emptyNeighborCount = 0; for (var dir = 0; dir < 4; ++dir) { if (room.IsLinked(dir) && !room.Neighbor[dir].isReserved) { emptyNeighborCount++; } } if (emptyNeighborCount < 2) { if (deadEndDepth < this.Places[i].Depth) { deadEnd = i; } } if (room.isOnPath) { possiblePlacesOnPath.Add(i); } else { possiblePlacesNotOnPath.Add(i); } } } } // Chance to not use deepest corner for unlock place. if (_rng.GetUInt32(100) < 40) { deadEnd = -1; } if (possiblePlacesOnPath.Count == 0 && possiblePlacesNotOnPath.Count == 0) { // Convert locked place room back to alley if there are no more locked doors. room = this.Places[lockedPlace.PlaceIndex].Room; room.SetDoorType(lockedPlace.DoorDirection, (int)DungeonBlockType.Alley); room.SetPuzzleDoor(null, lockedPlace.DoorDirection); var isLockedRoom = room.DoorType.Any(x => (x == (int)DungeonBlockType.DoorWithLock || x == (int)DungeonBlockType.BossDoor)); if (!isLockedRoom) { room.isLocked = false; room.RoomType = RoomType.Alley; } // Return locked place door to list on available doors. var lockedDoorCandidate = new LockedDoorCandidateNode(room, lockedPlace.DoorDirection, room.RoomIndex); _lockedDoorCandidates.AddFirst(lockedDoorCandidate); throw new PuzzleException("Out of unlock places."); } var possiblePlaces = (possiblePlacesNotOnPath.Count > 0 ? possiblePlacesNotOnPath : possiblePlacesOnPath); var placeIndex = deadEnd != -1 ? deadEnd : possiblePlaces[(int)_rng.GetUInt32((uint)possiblePlaces.Count)]; // Walk down from current place to our path and add new possible doors to this._lockedDoorCandidates room = this.Places[placeIndex].Room; while (room.RoomType != RoomType.Start) { if (room.isOnPath) { break; } var dir = room.GetIncomingDirection(); room.isOnPath = true; room = room.Neighbor[dir]; // skip reserved doors //if (room.ReservedDoor[Direction.GetOppositeDirection(dir)]) continue; // skip reserved places if (room.isReserved) { continue; } var lockedDoorCandidate = new LockedDoorCandidateNode(room, Direction.GetOppositeDirection(dir), room.RoomIndex); _lockedDoorCandidates.AddFirst(lockedDoorCandidate); } return(placeIndex); }
/// <summary> /// Gets a node from _lockedDoorCandidates, that will be used to make locked place, and removes it from the list. /// </summary> /// <param name="lockSelf"></param> /// <returns></returns> public LockedDoorCandidateNode GetLock(bool lockSelf = false) { LockedDoorCandidateNode result = null; var count = _lockedDoorCandidates.Count; if (count == 0) { throw new PuzzleException("Out of locked door candidates."); } // Always place locked place before the boss door. // todo: always place a locked place at the end of section, add script handler if (_placeBossDoor) { result = _lockedDoorCandidates.FirstOrDefault(x => x.Room.Neighbor[Direction.Up] != null && x.Room.Neighbor[Direction.Up].RoomType == RoomType.End); } if (result == null) { var lockedDoor = _lockedDoorCandidates.Last; if (lockSelf) { while (lockedDoor != null && lockedDoor.Value.Room.isReserved) { lockedDoor = lockedDoor.Previous; } } else { while (lockedDoor != null) { // Test if there is a free room for unlock place before this door var placeIndex = lockedDoor.Value.PlaceIndex; // Get index of room behind lockedPlace door. placeIndex = this.Places[placeIndex].Room.Neighbor[lockedDoor.Value.Direction].RoomIndex; if (placeIndex == 0 || placeIndex == -1) // should be last room in section. { placeIndex = this.Places.Count; } --placeIndex; while (placeIndex >= 0 && (placeIndex == lockedDoor.Value.PlaceIndex || this.Places[placeIndex].IsUsed || this.Places[placeIndex].Room.isLocked)) { --placeIndex; } if (placeIndex >= 0) { break; } lockedDoor = lockedDoor.Previous; } } if (lockedDoor == null) { if (lockSelf) { throw new PuzzleException("Out of candidates for self lock."); } throw new PuzzleException("None of lock candidates can serve as lock with unlock place."); } result = lockedDoor.Value; } if (_placeBossDoor) { _placeBossDoor = false; } _lockedDoorCandidates.Remove(result); return(result); }