Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion RandomizerCore/Sidescroll/ChaosPalaceGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ internal override async Task<Palace> GeneratePalace(RandomizerProperties props,
debug++;
bool duplicateProtection = (props.NoDuplicateRooms || props.NoDuplicateRoomsBySideview) && AllowDuplicatePrevention(props, palaceNumber);
RoomPool roomPool = new(rooms);
ILookup<string, Room>? duplicateRoomLookup = CreateRoomVariantsLookupOrNull(props, palaceNumber, roomPool);
DetermineRoomVariants(r, duplicateRoomLookup, roomPool.NormalRooms);
Palace palace = new(palaceNumber);
var palaceGroup = Util.AsPalaceGrouping(palaceNumber);

Expand Down Expand Up @@ -90,7 +92,7 @@ internal override async Task<Palace> GeneratePalace(RandomizerProperties props,
int roomIndex = r.Next(roomPool.NormalRooms.Count);
Room newRoom = new(roomPool.NormalRooms[roomIndex]);
palace.AllRooms.Add(newRoom);
if (duplicateProtection) { RemoveDuplicatesFromPool(props, roomPool.NormalRooms, newRoom); }
if (duplicateProtection) { RemoveDuplicatesFromPool(roomPool.NormalRooms, newRoom); }
}

Dictionary<Room, RoomExitType> roomExits = [];
Expand Down
14 changes: 9 additions & 5 deletions RandomizerCore/Sidescroll/CoordinatePalaceGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ namespace Z2Randomizer.RandomizerCore.Sidescroll;
public abstract class CoordinatePalaceGenerator() : PalaceGenerator
{
private static readonly RoomExitType[] PRIORITY_ROOM_SHAPES = [RoomExitType.DROP_STUB, RoomExitType.DROP_T];
private const int PRIORITY_ROOM_SHAPE_WEIGHT = 6;

protected static readonly Logger logger = LogManager.GetCurrentClassLogger();
protected static bool AddSpecialRoomsByReplacement(Palace palace, RoomPool roomPool, Random r, RandomizerProperties props)
{
Expand Down Expand Up @@ -313,10 +315,12 @@ private static void UnmergeMergedRooms(Palace palace, RoomPool roomPool)

public static List<RoomExitType> ShuffleItemRoomShapes(List<RoomExitType> possibleItemRoomExitTypes, Random r)
{
List<RoomExitType> priorityShapes = [.. possibleItemRoomExitTypes.Where(i => PRIORITY_ROOM_SHAPES.Contains(i))];
List<RoomExitType> nonPriorityShapes = [.. possibleItemRoomExitTypes.Where(i => !PRIORITY_ROOM_SHAPES.Contains(i))];
priorityShapes.FisherYatesShuffle(r);
nonPriorityShapes.FisherYatesShuffle(r);
return [.. priorityShapes, .. nonPriorityShapes];
List<(RoomExitType, int)> weights = [];
var priorityRooms = possibleItemRoomExitTypes.Where(i => PRIORITY_ROOM_SHAPES.Contains(i)).Select(shape => (shape, PRIORITY_ROOM_SHAPE_WEIGHT)).ToList();
var nonPriorityRooms = possibleItemRoomExitTypes.Where(i => !PRIORITY_ROOM_SHAPES.Contains(i)).Select(shape => (shape, 1)).ToList();
weights.AddRange(priorityRooms);
weights.AddRange(nonPriorityRooms);
var sampler = new WeightedShuffler<RoomExitType>(weights);
return sampler.Shuffle(r).ToList();
}
}
4 changes: 2 additions & 2 deletions RandomizerCore/Sidescroll/Palace.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1045,7 +1045,7 @@ public bool CanClearAllRooms(IEnumerable<RequirementType> requireables, Collecta
return unclearableRooms.Count == 0;
}

public bool HasInescapableDrop(bool palacesContinueAfterBoss)
public bool HasInescapableDrop(bool exitViaBossRoomAllowed)
{
//get a list of effective drop zones in the palace
List<Room> dropZonesToCheck = [];
Expand All @@ -1069,7 +1069,7 @@ public bool HasInescapableDrop(bool palacesContinueAfterBoss)
{
Room room = pendingRooms.Pop();
//if you find the entrance, remove and continue
if (room == Entrance || (room.IsBossRoom && !palacesContinueAfterBoss))
if (room == Entrance || (room.IsBossRoom && exitViaBossRoomAllowed))
{
found = true;
break;
Expand Down
57 changes: 44 additions & 13 deletions RandomizerCore/Sidescroll/PalaceGenerator.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Numerics;
using System.Threading.Tasks;

namespace Z2Randomizer.RandomizerCore.Sidescroll;
Expand Down Expand Up @@ -61,25 +61,56 @@ protected static bool AllowDuplicatePrevention(RandomizerProperties props, int p
return true;
}

protected static void RemoveDuplicatesFromPool(RandomizerProperties props, ICollection<Room> rooms, Room roomThatWasUsed)
/// <summary>
/// If full duplicate protection by sideview is enabled, this will return a lookup map of DuplicateGroupId -> collection of rooms.
/// Otherwise it will return null. The lookup being null can safely be passed to DetermineRoomVariants and nothing will be done.
/// </summary>
protected static ILookup<string, Room>? CreateRoomVariantsLookupOrNull(RandomizerProperties props, int palaceNumber, RoomPool roomPool)
{
if (props.NoDuplicateRoomsBySideview)
if (props.NoDuplicateRoomsBySideview && AllowDuplicatePrevention(props, palaceNumber))
{
var sideviewBytes = roomThatWasUsed.SideView;
if (rooms is List<Room> list)
{
list.RemoveAll(r => byteArrayEqualityComparer.Equals(r.SideView, sideviewBytes));
}
else if (rooms is HashSet<Room> set)
return roomPool.NormalRooms.Where(r => r.DuplicateGroup != null && r.DuplicateGroup != "").ToLookup(r => r.DuplicateGroup);
}
else
{
return null;
}
}

/// <summary>
/// With full duplicate protection enabled (meaning duplicateRoomLookup is non-null),
/// this will remove all but one rooms of each duplicate group, at random.
/// </summary>
protected static void DetermineRoomVariants(Random r, ILookup<string, Room>? duplicateRoomLookup, List<Room> rooms)
{
if (duplicateRoomLookup == null) { return; }
HashSet<Room> toRemove = new();
foreach (IGrouping<string, Room> group in duplicateRoomLookup)
{
// randomly pick one room from the duplicate group to keep
int keepIndex = r.Next(group.Count());
Room keep = group.ElementAt(keepIndex);
foreach (var room in group)
{
set.RemoveWhere(r => byteArrayEqualityComparer.Equals(r.SideView, sideviewBytes));
if (!ReferenceEquals(room, keep)) { toRemove.Add(room); }
}
else { throw new NotImplementedException(); }
}
else if (props.NoDuplicateRooms)
rooms.RemoveAll(toRemove.Contains);
}

protected static void RemoveDuplicatesFromPool(ICollection<Room> rooms, Room roomThatWasUsed)
{
if (rooms is List<Room> list)
{
var removed = list.RemoveAll(r => r.Name == roomThatWasUsed.Name);
Debug.Assert(removed == 1);
}
else if (rooms is HashSet<Room> set)
{
rooms.Remove(roomThatWasUsed);
var removed = set.RemoveWhere(r => r.Name == roomThatWasUsed.Name);
Debug.Assert(removed == 1);
}
else { throw new NotImplementedException(); }
}

[Conditional("DEBUG")]
Expand Down
33 changes: 24 additions & 9 deletions RandomizerCore/Sidescroll/Palaces.cs
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,9 @@ public async Task<List<Palace>> CreatePalaces(Random r, RandomizerProperties pro
byte group1MapIndex = 0, group2MapIndex = 0, group3MapIndex = 0;
for (int currentPalace = 1; currentPalace < 8; currentPalace++)
{
PalaceGenerator palaceGenerator = props.PalaceStyles[currentPalace - 1] switch
PalaceStyle palaceStyle = props.PalaceStyles[currentPalace - 1];

PalaceGenerator palaceGenerator = palaceStyle switch
{
PalaceStyle.VANILLA => new VanillaPalaceGenerator(),
PalaceStyle.SHUFFLED => new VanillaShufflePalaceGenerator(),
Expand All @@ -183,23 +185,36 @@ public async Task<List<Palace>> CreatePalaces(Random r, RandomizerProperties pro
_ => throw new Exception("Unrecognized palace style while generating palaces")
};

bool oneWayDropToBossAllowed = !props.BossRoomsExitToPalace[currentPalace - 1];
if (palaceGenerator is CoordinatePalaceGenerator)
{
// Do an additional roll for coordinate palaces to determine
// whether drops are allowed to be one-way drops to boss exit.
// This number will have to be tweaked to account for bias
// as less strict validation will be more likely to succeed.
// Gameplay-wise both taking a drop and exploring other paths
// first should be reasonable decisions.
if (oneWayDropToBossAllowed)
{
oneWayDropToBossAllowed = r.NextDouble() < 0.4;
}
}

RoomPool roomPool;
if(props.PalaceStyles[currentPalace - 1].UsesVanillaRoomPool())
if(palaceStyle.UsesVanillaRoomPool())
{
roomPool = new VanillaRoomPool(palaceRooms, currentPalace, props);
}
else
{
roomPool = new(palaceRooms, currentPalace, props);
}

Palace palace;
do
{
palace = await palaceGenerator.GeneratePalace(props, roomPool, r, sizes[currentPalace - 1], currentPalace);
} while (
!palace.IsValid ||
(props.PalaceStyles[currentPalace - 1] != PalaceStyle.VANILLA
&& palace.HasInescapableDrop(props.BossRoomsExitToPalace[currentPalace - 1])));
} while (!palace.IsValid || palaceStyle != PalaceStyle.VANILLA && palace.HasInescapableDrop(oneWayDropToBossAllowed));
PalaceGenerator.DebugCheckDuplicates(props, palace);
if (props.UsePalaceItemRoomCountIndicator && currentPalace != 7)
{
Expand All @@ -208,17 +223,17 @@ public async Task<List<Palace>> CreatePalaces(Random r, RandomizerProperties pro

if (palace.PalaceGroup == PalaceGrouping.Palace125)
{
group1MapIndex = palace.AssignMapNumbers(group1MapIndex, currentPalace == 7, props.PalaceStyles[currentPalace - 1].UsesVanillaRoomPool());
group1MapIndex = palace.AssignMapNumbers(group1MapIndex, currentPalace == 7, palaceStyle.UsesVanillaRoomPool());
}

if (palace.PalaceGroup == PalaceGrouping.Palace346)
{
group2MapIndex = palace.AssignMapNumbers(group2MapIndex, currentPalace == 7, props.PalaceStyles[currentPalace - 1].UsesVanillaRoomPool());
group2MapIndex = palace.AssignMapNumbers(group2MapIndex, currentPalace == 7, palaceStyle.UsesVanillaRoomPool());
}

if (palace.PalaceGroup == PalaceGrouping.PalaceGp)
{
group3MapIndex = palace.AssignMapNumbers(group3MapIndex, currentPalace == 7, props.PalaceStyles[currentPalace - 1].UsesVanillaRoomPool());
group3MapIndex = palace.AssignMapNumbers(group3MapIndex, currentPalace == 7, palaceStyle.UsesVanillaRoomPool());
}
palace.AllRooms.ForEach(i => i.PalaceNumber = currentPalace);
palace.ValidateRoomConnections();
Expand Down
10 changes: 5 additions & 5 deletions RandomizerCore/Sidescroll/RandomWalkCoordinatePalaceGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@ protected override async Task<Dictionary<Coord, RoomExitType>> GetPalaceShape(Ra
var currentCoord = Coord.Origin;

//Back to even weight for now.
WeightedRandom<int> weightedRandomDirection = new([
(0, 35), // left
(1, 35), // down
(2, 35), // up
(3, 35), // right
TableWeightedRandom<int> weightedRandomDirection = new([
(0, 1), // left
(1, 1), // down
(2, 1), // up
(3, 1), // right
]);

//Create graph
Expand Down
10 changes: 6 additions & 4 deletions RandomizerCore/Sidescroll/ReconstructedPalaceGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,12 @@ internal override async Task<Palace> GeneratePalace(RandomizerProperties props,
bool duplicateProtection = (props.NoDuplicateRooms || props.NoDuplicateRoomsBySideview) && AllowDuplicatePrevention(props, palaceNumber);
// var palaceGroup = Util.AsPalaceGrouping(palaceNumber);
Palace palace = new(palaceNumber);
ILookup<string, Room>? duplicateRoomLookup = CreateRoomVariantsLookupOrNull(props, palaceNumber, rooms);
do // while (tries >= PALACE_SHUFFLE_ATTEMPT_LIMIT);
{
await Task.Yield();
RoomPool roomPool = new(rooms);
DetermineRoomVariants(r, duplicateRoomLookup, roomPool.NormalRooms);
if (ct.IsCancellationRequested)
{
palace.IsValid = false;
Expand Down Expand Up @@ -83,7 +85,7 @@ internal override async Task<Palace> GeneratePalace(RandomizerProperties props,
palace.ItemRooms.Add(itemRoom);
palace.AllRooms.Add(itemRoom);

if (duplicateProtection) { RemoveDuplicatesFromPool(props, roomPool.ItemRoomsByDirection[itemRoomDirection], itemRoom); }
if (duplicateProtection) { RemoveDuplicatesFromPool(roomPool.ItemRoomsByDirection[itemRoomDirection], itemRoom); }

if (itemRoom.LinkedRoomName != null)
{
Expand Down Expand Up @@ -147,7 +149,7 @@ internal override async Task<Palace> GeneratePalace(RandomizerProperties props,
}
if (added)
{
if (duplicateProtection) { RemoveDuplicatesFromPool(props, roomPool.NormalRooms, roomToAdd); }
if (duplicateProtection) { RemoveDuplicatesFromPool(roomPool.NormalRooms, roomToAdd); }
if (roomToAdd.LinkedRoom?.HasDrop ?? false)
{
roomToAdd = roomToAdd.LinkedRoom;
Expand All @@ -172,7 +174,7 @@ internal override async Task<Palace> GeneratePalace(RandomizerProperties props,
bool added2 = AddRoom(palace, dropZoneRoom, props.BlockersAnywhere);
if (added2)
{
if (duplicateProtection) { RemoveDuplicatesFromPool(props, roomPool.NormalRooms, dropZoneRoom); }
if (duplicateProtection) { RemoveDuplicatesFromPool(roomPool.NormalRooms, dropZoneRoom); }
continueDropping = dropZoneRoom.HasDrop;
if (dropZoneRoom.LinkedRoomName != null)
{
Expand Down Expand Up @@ -235,7 +237,7 @@ internal override async Task<Palace> GeneratePalace(RandomizerProperties props,
(!reachable
|| (palaceNumber == 7 && props.RequireTbird && !palace.RequiresThunderbird())
|| (palaceNumber == 7 && !palace.BossRoomMinDistance(props.DarkLinkMinDistance))
|| palace.HasInescapableDrop(props.BossRoomsExitToPalace[palace.Number - 1])
|| palace.HasInescapableDrop(!props.BossRoomsExitToPalace[palace.Number - 1])
) && (tries < ROOM_SHUFFLE_ATTEMPT_LIMIT)
);
} while (tries >= ROOM_SHUFFLE_ATTEMPT_LIMIT);
Expand Down
Loading
Loading