Skip to content

Commit 4fdbded

Browse files
committed
Remove room selection bias for rooms with multiple variants
- Instead of picking a room from the pool and then removing all its variants - which gives a 5x chance for a 5 variant room to appear - we're picking one variant of each duplicate group at the start of palace generation. - Probably fixed the other no duplicates flag as it was comparing by object reference - Remove unnecessary while placed loop in Sequential that always ran at most once
1 parent 1769020 commit 4fdbded

5 files changed

Lines changed: 113 additions & 51 deletions

File tree

RandomizerCore/Sidescroll/ChaosPalaceGenerator.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ internal override async Task<Palace> GeneratePalace(RandomizerProperties props,
1818
debug++;
1919
bool duplicateProtection = (props.NoDuplicateRooms || props.NoDuplicateRoomsBySideview) && AllowDuplicatePrevention(props, palaceNumber);
2020
RoomPool roomPool = new(rooms);
21+
ILookup<string, Room>? duplicateRoomLookup = CreateRoomVariantsLookupOrNull(props, palaceNumber, roomPool);
22+
DetermineRoomVariants(r, duplicateRoomLookup, roomPool.NormalRooms);
2123
Palace palace = new(palaceNumber);
2224
var palaceGroup = Util.AsPalaceGrouping(palaceNumber);
2325

RandomizerCore/Sidescroll/PalaceGenerator.cs

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
using System;
2+
using System.Collections;
23
using System.Collections.Generic;
34
using System.Diagnostics;
45
using System.Linq;
5-
using System.Numerics;
66
using System.Threading.Tasks;
77

88
namespace Z2Randomizer.RandomizerCore.Sidescroll;
@@ -61,6 +61,58 @@ protected static bool AllowDuplicatePrevention(RandomizerProperties props, int p
6161
return true;
6262
}
6363

64+
/// <summary>
65+
/// If full duplicate protection by sideview is enabled, this will return a lookup map of DuplicateGroupId -> collection of rooms.
66+
/// Otherwise it will return null. The lookup being null can safely be passed to DetermineRoomVariants and nothing will be done.
67+
/// </summary>
68+
protected static ILookup<string, Room>? CreateRoomVariantsLookupOrNull(RandomizerProperties props, int palaceNumber, RoomPool roomPool)
69+
{
70+
if (props.NoDuplicateRoomsBySideview && AllowDuplicatePrevention(props, palaceNumber))
71+
{
72+
return roomPool.NormalRooms.Where(r => r.DuplicateGroup != null && r.DuplicateGroup != "").ToLookup(r => r.DuplicateGroup);
73+
}
74+
else
75+
{
76+
return null;
77+
}
78+
}
79+
80+
/// <summary>
81+
/// With full duplicate protection enabled (meaning duplicateRoomLookup is non-null),
82+
/// this will remove all but one rooms of each duplicate group, at random.
83+
/// </summary>
84+
protected static void DetermineRoomVariants(Random r, ILookup<string, Room>? duplicateRoomLookup, List<Room> rooms)
85+
{
86+
if (duplicateRoomLookup == null) { return; }
87+
HashSet<Room> toRemove = new();
88+
foreach (IGrouping<string, Room> group in duplicateRoomLookup)
89+
{
90+
// randomly pick one room from the duplicate group to keep
91+
int keepIndex = r.Next(group.Count());
92+
Room keep = group.ElementAt(keepIndex);
93+
foreach (var room in group)
94+
{
95+
if (!ReferenceEquals(room, keep)) { toRemove.Add(room); }
96+
}
97+
}
98+
rooms.RemoveAll(toRemove.Contains);
99+
}
100+
101+
protected static void RemoveDuplicatesFromPool(ICollection<Room> rooms, Room roomThatWasUsed)
102+
{
103+
if (rooms is List<Room> list)
104+
{
105+
var removed = list.RemoveAll(r => r.Name == roomThatWasUsed.Name);
106+
Debug.Assert(removed == 1);
107+
}
108+
else if (rooms is HashSet<Room> set)
109+
{
110+
var removed = set.RemoveWhere(r => r.Name == roomThatWasUsed.Name);
111+
Debug.Assert(removed == 1);
112+
}
113+
else { throw new NotImplementedException(); }
114+
}
115+
64116
[Conditional("DEBUG")]
65117
public static void DebugCheckDuplicates(RandomizerProperties props, Palace palace)
66118
{

RandomizerCore/Sidescroll/ReconstructedPalaceGenerator.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,12 @@ internal override async Task<Palace> GeneratePalace(RandomizerProperties props,
2323
bool duplicateProtection = (props.NoDuplicateRooms || props.NoDuplicateRoomsBySideview) && AllowDuplicatePrevention(props, palaceNumber);
2424
// var palaceGroup = Util.AsPalaceGrouping(palaceNumber);
2525
Palace palace = new(palaceNumber);
26+
ILookup<string, Room>? duplicateRoomLookup = CreateRoomVariantsLookupOrNull(props, palaceNumber, rooms);
2627
do // while (tries >= PALACE_SHUFFLE_ATTEMPT_LIMIT);
2728
{
2829
await Task.Yield();
2930
RoomPool roomPool = new(rooms);
31+
DetermineRoomVariants(r, duplicateRoomLookup, roomPool.NormalRooms);
3032
if (ct.IsCancellationRequested)
3133
{
3234
palace.IsValid = false;
@@ -69,6 +71,8 @@ internal override async Task<Palace> GeneratePalace(RandomizerProperties props,
6971
palace.ItemRooms.Add(itemRoom);
7072
palace.AllRooms.Add(itemRoom);
7173

74+
if (duplicateProtection) { roomPool.RemoveDuplicates(props, itemRoom); }
75+
7276
if (itemRoom.LinkedRoomName != null)
7377
{
7478
Room segmentedItemRoom1, segmentedItemRoom2;

RandomizerCore/Sidescroll/SequentialPlacementCoordinatePalaceGenerator.cs

Lines changed: 47 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ internal override async Task<Palace> GeneratePalace(RandomizerProperties props,
3737
openCoords = new();
3838
Dictionary<RoomExitType, List<Room>> roomsByExitType;
3939
roomPool = new(rooms);
40+
ILookup<string, Room>? duplicateRoomLookup = CreateRoomVariantsLookupOrNull(props, palaceNumber, roomPool);
41+
DetermineRoomVariants(r, duplicateRoomLookup, roomPool.NormalRooms);
4042
// var palaceGroup = Util.AsPalaceGrouping(palaceNumber);
4143
Room entrance = new(roomPool.Entrances[r.Next(roomPool.Entrances.Count)])
4244
{
@@ -174,60 +176,55 @@ internal override async Task<Palace> GeneratePalace(RandomizerProperties props,
174176
}
175177
roomsByExitType.TryGetValue(exitType, out var possibleStubs);
176178

177-
bool placed = false;
178-
do //while (placed == false)
179+
Room? newRoom = possibleStubs?.Sample(r);
180+
if (newRoom == null)
179181
{
180-
Room? newRoom = possibleStubs?.Sample(r);
181-
if (newRoom == null)
182-
{
183-
roomPool.DefaultStubsByDirection.TryGetValue(exitType, out newRoom);
184-
}
185-
//This should no longer be possible since default stubs aren't removable
186-
if (newRoom == null)
187-
{
188-
palace.IsValid = false;
189-
return palace;
190-
}
182+
roomPool.DefaultStubsByDirection.TryGetValue(exitType, out newRoom);
183+
}
184+
//This should no longer be possible since default stubs aren't removable
185+
if (newRoom == null)
186+
{
187+
palace.IsValid = false;
188+
return palace;
189+
}
191190

192-
newRoom = new(newRoom);
193-
//If the stub is a drop zone, pretend it isn't, otherwise junctions can appear
194-
//as a result of adding the stub.
195-
if (newRoom.IsDropZone)
196-
{
197-
newRoom.IsDropZone = false;
198-
}
199-
newRoom.coords = openCoord;
200-
roomsByCoordinate.Add(newRoom.coords, newRoom);
201-
palace.AllRooms.Add(newRoom);
202-
openCoords.Remove(openCoord);
203-
placed = true;
191+
newRoom = new(newRoom);
192+
//If the stub is a drop zone, pretend it isn't, otherwise junctions can appear
193+
//as a result of adding the stub.
194+
if (newRoom.IsDropZone)
195+
{
196+
newRoom.IsDropZone = false;
197+
}
198+
newRoom.coords = openCoord;
199+
roomsByCoordinate.Add(newRoom.coords, newRoom);
200+
palace.AllRooms.Add(newRoom);
201+
openCoords.Remove(openCoord);
204202

205-
if (left != null && newRoom.HasLeftExit)
206-
{
207-
newRoom.Left = left;
208-
left.Right = newRoom;
209-
}
210-
if (down != null && newRoom.HasDownExit)
211-
{
212-
newRoom.Down = down;
213-
down.Up = newRoom;
214-
}
215-
if (up != null && newRoom.HasUpExit)
216-
{
217-
newRoom.Up = up;
218-
up.Down = newRoom;
219-
}
220-
if (right != null && newRoom.HasRightExit)
221-
{
222-
newRoom.Right = right;
223-
right.Left = newRoom;
224-
}
203+
if (left != null && newRoom.HasLeftExit)
204+
{
205+
newRoom.Left = left;
206+
left.Right = newRoom;
207+
}
208+
if (down != null && newRoom.HasDownExit)
209+
{
210+
newRoom.Down = down;
211+
down.Up = newRoom;
212+
}
213+
if (up != null && newRoom.HasUpExit)
214+
{
215+
newRoom.Up = up;
216+
up.Down = newRoom;
217+
}
218+
if (right != null && newRoom.HasRightExit)
219+
{
220+
newRoom.Right = right;
221+
right.Left = newRoom;
222+
}
225223

226-
if (newRoom.Group != RoomGroup.STUBS)
227-
{
228-
if (duplicateProtection) { roomPool.RemoveDuplicates(props, newRoom); }
229-
}
230-
} while (placed == false);
224+
if (newRoom.Group != RoomGroup.STUBS)
225+
{
226+
if (duplicateProtection) { roomPool.RemoveDuplicates(props, newRoom); }
227+
}
231228
}
232229
}
233230
//Debug.WriteLine(palace.GetLayoutDebug(PalaceStyle.SEQUENTIAL, false));

RandomizerCore/Sidescroll/ShapeFirstCoordinatePalaceGenerator.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ internal override async Task<Palace> GeneratePalace(RandomizerProperties props,
1919
Palace palace = new(palaceNumber);
2020
Dictionary<RoomExitType, List<Room>> roomsByExitType;
2121
RoomPool roomPool = new(rooms);
22+
ILookup<string, Room>? duplicateRoomLookup = CreateRoomVariantsLookupOrNull(props, palaceNumber, roomPool);
2223
var itemRoomSelector = GetItemRoomSelectionStrategy();
2324
// var palaceGroup = Util.AsPalaceGrouping(palaceNumber);
2425

@@ -73,6 +74,10 @@ internal override async Task<Palace> GeneratePalace(RandomizerProperties props,
7374

7475
//Add rooms
7576
roomsByExitType = roomPool.CategorizeNormalRoomExits(true);
77+
foreach (var roomsForExitType in roomsByExitType.Values)
78+
{
79+
DetermineRoomVariants(r, duplicateRoomLookup, roomsForExitType);
80+
}
7681
Dictionary<RoomExitType, bool> stubOnlyExitTypes = new();
7782
foreach (KeyValuePair<Coord, RoomExitType> item in shape.OrderBy(i => i.Key.X).ThenByDescending(i => i.Key.Y))
7883
{
@@ -102,6 +107,8 @@ internal override async Task<Palace> GeneratePalace(RandomizerProperties props,
102107
if (duplicateProtection && roomCandidates!.Count == 0)
103108
{
104109
roomCandidates = roomPool.GetNormalRoomsForExitType(roomExitType, true);
110+
// as the pool is re-used for this shape, determine variants to include again
111+
DetermineRoomVariants(r, duplicateRoomLookup, roomCandidates);
105112
Debug.Assert(roomCandidates.Count() > 0);
106113
roomsByExitType[roomExitType] = roomCandidates;
107114
logger.Debug($"RandomWalk ran out of rooms of exit type: {roomExitType} in palace {palaceNumber}. Starting to use duplicate rooms.");

0 commit comments

Comments
 (0)