Skip to content

Commit 95ef34f

Browse files
committed
Try filling shapes with rooms a few times before giving up
1 parent ea265e3 commit 95ef34f

1 file changed

Lines changed: 140 additions & 125 deletions

File tree

RandomizerCore/Sidescroll/ShapeFirstCoordinatePalaceGenerator.cs

Lines changed: 140 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ namespace Z2Randomizer.RandomizerCore.Sidescroll;
1212

1313
public abstract class ShapeFirstCoordinatePalaceGenerator() : CoordinatePalaceGenerator()
1414
{
15+
public const int FILL_SHAPE_TRIES = 10;
1516

1617
internal override async Task<Palace> GeneratePalace(RandomizerProperties props, RoomPool rooms, Random r, int roomCount, int palaceNumber)
1718
{
@@ -47,168 +48,182 @@ internal override async Task<Palace> GeneratePalace(RandomizerProperties props,
4748
//prepopulatedCoordinates.Add(palace.AllRooms.First(i => i.IsThunderBirdRoom).coords);
4849

4950
//Add rooms
50-
roomsByExitType = roomPool.CategorizeNormalRoomExits(true);
51-
Dictionary<RoomExitType, bool> stubOnlyExitTypes = new();
52-
foreach (KeyValuePair<Coord, RoomExitType> item in shape.OrderBy(i => i.Key.X).ThenByDescending(i => i.Key.Y))
51+
bool success = false;
52+
for (int i = 0; i < FILL_SHAPE_TRIES; i++)
5353
{
54-
if (prepopulatedCoordinates.Contains(item.Key))
54+
if (await FillShape())
5555
{
56-
continue;
56+
success = true;
57+
break;
5758
}
58-
var (x, y) = item.Key;
59-
RoomExitType roomExitType = item.Value;
60-
61-
bool stubOnly;
62-
List<Room>? roomCandidates;
63-
Room? newRoom = null;
64-
if (!stubOnlyExitTypes.TryGetValue(roomExitType, out stubOnly))
65-
{
66-
roomsByExitType.TryGetValue(roomExitType, out roomCandidates);
67-
stubOnly = roomCandidates == null || roomCandidates.Count == 0;
68-
stubOnlyExitTypes[roomExitType] = stubOnly;
69-
}
70-
else
71-
{
72-
roomCandidates = stubOnly ? null : roomsByExitType.GetValueOrDefault(roomExitType);
73-
}
74-
if (!stubOnly)
59+
}
60+
if (!success)
61+
{
62+
palace.IsValid = false;
63+
return palace;
64+
}
65+
async Task<bool> FillShape()
66+
{
67+
roomsByExitType = roomPool.CategorizeNormalRoomExits(true);
68+
Dictionary<RoomExitType, bool> stubOnlyExitTypes = new();
69+
foreach (KeyValuePair<Coord, RoomExitType> item in shape.OrderBy(i => i.Key.X).ThenByDescending(i => i.Key.Y))
7570
{
76-
Debug.Assert(roomCandidates != null);
77-
if (duplicateProtection && roomCandidates!.Count == 0)
71+
if (prepopulatedCoordinates.Contains(item.Key))
72+
{
73+
continue;
74+
}
75+
var (x, y) = item.Key;
76+
RoomExitType roomExitType = item.Value;
77+
78+
bool stubOnly;
79+
List<Room>? roomCandidates;
80+
Room? newRoom = null;
81+
if (!stubOnlyExitTypes.TryGetValue(roomExitType, out stubOnly))
82+
{
83+
roomsByExitType.TryGetValue(roomExitType, out roomCandidates);
84+
stubOnly = roomCandidates == null || roomCandidates.Count == 0;
85+
stubOnlyExitTypes[roomExitType] = stubOnly;
86+
}
87+
else
7888
{
79-
roomCandidates = roomPool.GetNormalRoomsForExitType(roomExitType, true);
80-
Debug.Assert(roomCandidates.Count() > 0);
81-
roomsByExitType[roomExitType] = roomCandidates;
82-
logger.Debug($"RandomWalk ran out of rooms of exit type: {roomExitType} in palace {palaceNumber}. Starting to use duplicate rooms.");
89+
roomCandidates = stubOnly ? null : roomsByExitType.GetValueOrDefault(roomExitType);
8390
}
84-
roomCandidates!.FisherYatesShuffle(r);
85-
Room? upRoom = palace.AllRooms.FirstOrDefault(i => i.coords == new Coord(x, y + 1));
86-
foreach (Room roomCandidate in roomCandidates!)
91+
if (!stubOnly)
8792
{
88-
if (upRoom == null || !upRoom.HasDrop || roomCandidate.IsDropZone)
93+
Debug.Assert(roomCandidates != null);
94+
if (duplicateProtection && roomCandidates!.Count == 0)
8995
{
90-
Debug.Assert(roomCandidate.IsNormalRoom());
91-
newRoom = roomCandidate;
92-
break;
96+
roomCandidates = roomPool.GetNormalRoomsForExitType(roomExitType, true);
97+
Debug.Assert(roomCandidates.Count() > 0);
98+
roomsByExitType[roomExitType] = roomCandidates;
99+
logger.Debug($"RandomWalk ran out of rooms of exit type: {roomExitType} in palace {palaceNumber}. Starting to use duplicate rooms.");
93100
}
101+
roomCandidates!.FisherYatesShuffle(r);
102+
Room? upRoom = palace.AllRooms.FirstOrDefault(i => i.coords == new Coord(x, y + 1));
103+
foreach (Room roomCandidate in roomCandidates!)
104+
{
105+
if (upRoom == null || !upRoom.HasDrop || roomCandidate.IsDropZone)
106+
{
107+
Debug.Assert(roomCandidate.IsNormalRoom());
108+
newRoom = roomCandidate;
109+
break;
110+
}
111+
}
112+
if (newRoom != null && duplicateProtection) { roomPool.RemoveDuplicates(props, newRoom); }
94113
}
95-
if (newRoom != null && duplicateProtection) { roomPool.RemoveDuplicates(props, newRoom); }
96-
}
97114

98-
if (newRoom == null)
99-
{
100-
Room? upRoom = palace.AllRooms.FirstOrDefault(i => i.coords == new Coord(x, y + 1));
101-
roomPool.DefaultStubsByDirection.TryGetValue(roomExitType, out newRoom);
102-
if (newRoom != null && upRoom != null && upRoom.HasDrop && !newRoom.IsDropZone)
115+
if (newRoom == null)
103116
{
104-
//We need to use a drop zone stub but one does not (and cannot) exist so this graph is doomed.
105-
//Debug.WriteLine(GetLayoutDebug(walkGraph, false));
106-
palace.IsValid = false;
107-
return palace;
117+
Room? upRoom = palace.AllRooms.FirstOrDefault(i => i.coords == new Coord(x, y + 1));
118+
roomPool.DefaultStubsByDirection.TryGetValue(roomExitType, out newRoom);
119+
if (newRoom != null && upRoom != null && upRoom.HasDrop && !newRoom.IsDropZone)
120+
{
121+
//We need to use a drop zone stub but one does not (and cannot) exist so this graph is doomed.
122+
//Debug.WriteLine(GetLayoutDebug(walkGraph, false));
123+
return false;
124+
}
125+
}
126+
if (newRoom == null)
127+
{
128+
return false;
129+
}
130+
else
131+
{
132+
newRoom = new(newRoom);
108133
}
109-
}
110-
if (newRoom == null)
111-
{
112-
palace.IsValid = false;
113-
return palace;
114-
}
115-
else
116-
{
117-
newRoom = new(newRoom);
118-
}
119134

120-
newRoom.coords = item.Key;
121-
if (newRoom.LinkedRoomName == null)
122-
{
123-
palace.AllRooms.Add(newRoom);
124-
}
125-
else
126-
{
127-
Room linkedRoom = new(roomPool.LinkedRooms[newRoom.LinkedRoomName]);
128-
newRoom.LinkedRoom = linkedRoom;
129-
linkedRoom.LinkedRoom = newRoom;
130-
linkedRoom.coords = item.Key;
131-
Room mergedRoom = newRoom.Merge(linkedRoom);
132-
palace.AllRooms.Add(mergedRoom);
135+
newRoom.coords = item.Key;
136+
if (newRoom.LinkedRoomName == null)
137+
{
138+
palace.AllRooms.Add(newRoom);
139+
}
140+
else
141+
{
142+
Room linkedRoom = new(roomPool.LinkedRooms[newRoom.LinkedRoomName]);
143+
newRoom.LinkedRoom = linkedRoom;
144+
linkedRoom.LinkedRoom = newRoom;
145+
linkedRoom.coords = item.Key;
146+
Room mergedRoom = newRoom.Merge(linkedRoom);
147+
palace.AllRooms.Add(mergedRoom);
148+
}
133149
}
134-
}
135-
136-
//Connect adjacent rooms if they exist
137-
foreach (Room room in palace.AllRooms)
138-
{
139-
await Task.Yield();
140-
Room[] leftRooms = palace.AllRooms.Where(i => i.coords == room.coords with { X = room.coords.X - 1 }).ToArray();
141-
Room[] downRooms = palace.AllRooms.Where(i => i.coords == room.coords with { Y = room.coords.Y - 1 }).ToArray();
142-
Room[] upRooms = palace.AllRooms.Where(i => i.coords == room.coords with { Y = room.coords.Y + 1 }).ToArray();
143-
Room[] rightRooms = palace.AllRooms.Where(i => i.coords == room.coords with { X = room.coords.X + 1 }).ToArray();
144150

145-
foreach(Room left in leftRooms)
151+
//Connect adjacent rooms if they exist
152+
foreach (Room room in palace.AllRooms)
146153
{
147-
if (left != null && room.FitsWithLeft(left) > 0)
154+
await Task.Yield();
155+
Room[] leftRooms = palace.AllRooms.Where(i => i.coords == room.coords with { X = room.coords.X - 1 }).ToArray();
156+
Room[] downRooms = palace.AllRooms.Where(i => i.coords == room.coords with { Y = room.coords.Y - 1 }).ToArray();
157+
Room[] upRooms = palace.AllRooms.Where(i => i.coords == room.coords with { Y = room.coords.Y + 1 }).ToArray();
158+
Room[] rightRooms = palace.AllRooms.Where(i => i.coords == room.coords with { X = room.coords.X + 1 }).ToArray();
159+
160+
foreach(Room left in leftRooms)
148161
{
149-
room.Left = left;
150-
left.Right = room;
162+
if (left != null && room.FitsWithLeft(left) > 0)
163+
{
164+
room.Left = left;
165+
left.Right = room;
166+
}
151167
}
152-
}
153168

154-
foreach(Room down in downRooms)
155-
{
156-
if (down != null && room.FitsWithDown(down) > 0)
169+
foreach(Room down in downRooms)
157170
{
158-
room.Down = down;
159-
if (!room.HasDrop)
171+
if (down != null && room.FitsWithDown(down) > 0)
160172
{
161-
down.Up = room;
173+
room.Down = down;
174+
if (!room.HasDrop)
175+
{
176+
down.Up = room;
177+
}
162178
}
163179
}
164-
}
165-
foreach(Room up in upRooms)
166-
{
167-
if (up != null && room.FitsWithUp(up) > 0)
180+
foreach(Room up in upRooms)
168181
{
169-
if (!up.HasDrop)
182+
if (up != null && room.FitsWithUp(up) > 0)
170183
{
171-
room.Up = up;
184+
if (!up.HasDrop)
185+
{
186+
room.Up = up;
187+
}
188+
up.Down = room;
172189
}
173-
up.Down = room;
174190
}
175-
}
176-
foreach(Room right in rightRooms)
177-
{
178-
if (right != null && room.FitsWithRight(right) > 0)
191+
foreach(Room right in rightRooms)
179192
{
180-
room.Right = right;
181-
right.Left = room;
193+
if (right != null && room.FitsWithRight(right) > 0)
194+
{
195+
room.Right = right;
196+
right.Left = room;
197+
}
182198
}
183199
}
184-
}
185200

186-
ConnectNonEuclideanPaths(palace);
201+
ConnectNonEuclideanPaths(palace);
187202

188-
//Some percentage of the time, dropifying some rooms causes part of the palace to become
189-
//unreachable because up was the only way to get there.
190-
if (!palace.AllReachable())
191-
{
192-
palace.IsValid = false;
193-
return palace;
194-
}
203+
//Some percentage of the time, dropifying some rooms causes part of the palace to become
204+
//unreachable because up was the only way to get there.
205+
if (!palace.AllReachable())
206+
{
207+
return false;
208+
}
195209

196210

197-
if (!AddSpecialRoomsByReplacement(palace, roomPool, r, props, GetItemRoomSelectionStrategy()))
198-
{
199-
palace.IsValid = false;
200-
return palace;
201-
}
211+
if (!AddSpecialRoomsByReplacement(palace, roomPool, r, props, GetItemRoomSelectionStrategy()))
212+
{
213+
return false;
214+
}
202215

203-
if (palace.AllRooms.Count(i => i.Enabled) != roomCount)
204-
{
205-
throw new Exception("Generated palace has the incorrect number of rooms");
206-
}
216+
if (palace.AllRooms.Count(i => i.Enabled) != roomCount)
217+
{
218+
throw new Exception("Generated palace has the incorrect number of rooms");
219+
}
207220

208-
if (palace.HasDisallowedDrop(props.BossRoomsExitToPalace[palace.Number - 1], props.PalaceDropStyle, r))
209-
{
210-
palace.IsValid = false;
211-
return palace;
221+
if (palace.HasDisallowedDrop(props.BossRoomsExitToPalace[palace.Number - 1], props.PalaceDropStyle, r))
222+
{
223+
return false;
224+
}
225+
226+
return true;
212227
}
213228

214229
palace.AllRooms.ForEach(i => i.PalaceNumber = palaceNumber);

0 commit comments

Comments
 (0)