mirror of
https://github.com/MarioSpore/Grinch-AP.git
synced 2025-10-21 12:11:33 -06:00
GER: Only consider usable exits when calculating dead-ends (#4701)
* Only consider usable exits when calculating whether or not a region is a dead-end * Update EntranceLookup unit tests * Add new dead-end test * Add additional explanation to the new test * minor formatting tweak based on review feedback --------- Co-authored-by: CodeGorilla <3672561+Ars-Ignis@users.noreply.github.com>
This commit is contained in:
@@ -50,13 +50,15 @@ class EntranceLookup:
|
|||||||
_random: random.Random
|
_random: random.Random
|
||||||
_expands_graph_cache: dict[Entrance, bool]
|
_expands_graph_cache: dict[Entrance, bool]
|
||||||
_coupled: bool
|
_coupled: bool
|
||||||
|
_usable_exits: set[Entrance]
|
||||||
|
|
||||||
def __init__(self, rng: random.Random, coupled: bool):
|
def __init__(self, rng: random.Random, coupled: bool, usable_exits: set[Entrance]):
|
||||||
self.dead_ends = EntranceLookup.GroupLookup()
|
self.dead_ends = EntranceLookup.GroupLookup()
|
||||||
self.others = EntranceLookup.GroupLookup()
|
self.others = EntranceLookup.GroupLookup()
|
||||||
self._random = rng
|
self._random = rng
|
||||||
self._expands_graph_cache = {}
|
self._expands_graph_cache = {}
|
||||||
self._coupled = coupled
|
self._coupled = coupled
|
||||||
|
self._usable_exits = usable_exits
|
||||||
|
|
||||||
def _can_expand_graph(self, entrance: Entrance) -> bool:
|
def _can_expand_graph(self, entrance: Entrance) -> bool:
|
||||||
"""
|
"""
|
||||||
@@ -95,7 +97,8 @@ class EntranceLookup:
|
|||||||
# randomizable exits which are not reverse of the incoming entrance.
|
# randomizable exits which are not reverse of the incoming entrance.
|
||||||
# uncoupled mode is an exception because in this case going back in the door you just came in could
|
# uncoupled mode is an exception because in this case going back in the door you just came in could
|
||||||
# actually lead somewhere new
|
# actually lead somewhere new
|
||||||
if not exit_.connected_region and (not self._coupled or exit_.name != entrance.name):
|
if (not exit_.connected_region and (not self._coupled or exit_.name != entrance.name)
|
||||||
|
and exit_ in self._usable_exits):
|
||||||
self._expands_graph_cache[entrance] = True
|
self._expands_graph_cache[entrance] = True
|
||||||
return True
|
return True
|
||||||
elif exit_.connected_region and exit_.connected_region not in visited:
|
elif exit_.connected_region and exit_.connected_region not in visited:
|
||||||
@@ -333,7 +336,6 @@ def randomize_entrances(
|
|||||||
|
|
||||||
start_time = time.perf_counter()
|
start_time = time.perf_counter()
|
||||||
er_state = ERPlacementState(world, coupled)
|
er_state = ERPlacementState(world, coupled)
|
||||||
entrance_lookup = EntranceLookup(world.random, coupled)
|
|
||||||
# similar to fill, skip validity checks on entrances if the game is beatable on minimal accessibility
|
# similar to fill, skip validity checks on entrances if the game is beatable on minimal accessibility
|
||||||
perform_validity_check = True
|
perform_validity_check = True
|
||||||
|
|
||||||
@@ -349,6 +351,7 @@ def randomize_entrances(
|
|||||||
|
|
||||||
# used when membership checks are needed on the exit list, e.g. speculative sweep
|
# used when membership checks are needed on the exit list, e.g. speculative sweep
|
||||||
exits_set = set(exits)
|
exits_set = set(exits)
|
||||||
|
entrance_lookup = EntranceLookup(world.random, coupled, exits_set)
|
||||||
for entrance in er_targets:
|
for entrance in er_targets:
|
||||||
entrance_lookup.add(entrance)
|
entrance_lookup.add(entrance)
|
||||||
|
|
||||||
|
@@ -65,8 +65,10 @@ class TestEntranceLookup(unittest.TestCase):
|
|||||||
"""tests that get_targets shuffles targets between groups when requested"""
|
"""tests that get_targets shuffles targets between groups when requested"""
|
||||||
multiworld = generate_test_multiworld()
|
multiworld = generate_test_multiworld()
|
||||||
generate_disconnected_region_grid(multiworld, 5)
|
generate_disconnected_region_grid(multiworld, 5)
|
||||||
|
exits_set = set([ex for region in multiworld.get_regions(1)
|
||||||
|
for ex in region.exits if not ex.connected_region])
|
||||||
|
|
||||||
lookup = EntranceLookup(multiworld.worlds[1].random, coupled=True)
|
lookup = EntranceLookup(multiworld.worlds[1].random, coupled=True, usable_exits=exits_set)
|
||||||
er_targets = [entrance for region in multiworld.get_regions(1)
|
er_targets = [entrance for region in multiworld.get_regions(1)
|
||||||
for entrance in region.entrances if not entrance.parent_region]
|
for entrance in region.entrances if not entrance.parent_region]
|
||||||
for entrance in er_targets:
|
for entrance in er_targets:
|
||||||
@@ -86,8 +88,10 @@ class TestEntranceLookup(unittest.TestCase):
|
|||||||
"""tests that get_targets does not shuffle targets between groups when requested"""
|
"""tests that get_targets does not shuffle targets between groups when requested"""
|
||||||
multiworld = generate_test_multiworld()
|
multiworld = generate_test_multiworld()
|
||||||
generate_disconnected_region_grid(multiworld, 5)
|
generate_disconnected_region_grid(multiworld, 5)
|
||||||
|
exits_set = set([ex for region in multiworld.get_regions(1)
|
||||||
|
for ex in region.exits if not ex.connected_region])
|
||||||
|
|
||||||
lookup = EntranceLookup(multiworld.worlds[1].random, coupled=True)
|
lookup = EntranceLookup(multiworld.worlds[1].random, coupled=True, usable_exits=exits_set)
|
||||||
er_targets = [entrance for region in multiworld.get_regions(1)
|
er_targets = [entrance for region in multiworld.get_regions(1)
|
||||||
for entrance in region.entrances if not entrance.parent_region]
|
for entrance in region.entrances if not entrance.parent_region]
|
||||||
for entrance in er_targets:
|
for entrance in er_targets:
|
||||||
@@ -99,6 +103,30 @@ class TestEntranceLookup(unittest.TestCase):
|
|||||||
group_order = [prev := group.randomization_group for group in retrieved_targets if prev != group.randomization_group]
|
group_order = [prev := group.randomization_group for group in retrieved_targets if prev != group.randomization_group]
|
||||||
self.assertEqual([ERTestGroups.TOP, ERTestGroups.BOTTOM], group_order)
|
self.assertEqual([ERTestGroups.TOP, ERTestGroups.BOTTOM], group_order)
|
||||||
|
|
||||||
|
def test_selective_dead_ends(self):
|
||||||
|
"""test that entrances that EntranceLookup has not been told to consider are ignored when finding dead-ends"""
|
||||||
|
multiworld = generate_test_multiworld()
|
||||||
|
generate_disconnected_region_grid(multiworld, 5)
|
||||||
|
exits_set = set([ex for region in multiworld.get_regions(1)
|
||||||
|
for ex in region.exits if not ex.connected_region
|
||||||
|
and ex.name != "region20_right" and ex.name != "region21_left"])
|
||||||
|
|
||||||
|
lookup = EntranceLookup(multiworld.worlds[1].random, coupled=True, usable_exits=exits_set)
|
||||||
|
er_targets = [entrance for region in multiworld.get_regions(1)
|
||||||
|
for entrance in region.entrances if not entrance.parent_region and
|
||||||
|
entrance.name != "region20_right" and entrance.name != "region21_left"]
|
||||||
|
for entrance in er_targets:
|
||||||
|
lookup.add(entrance)
|
||||||
|
# region 20 is the bottom left corner of the grid, and therefore only has a right entrance from region 21
|
||||||
|
# and a top entrance from region 15; since we've told lookup to ignore the right entrance from region 21,
|
||||||
|
# the top entrance from region 15 should be considered a dead-end
|
||||||
|
dead_end_region = multiworld.get_region("region20", 1)
|
||||||
|
for dead_end in dead_end_region.entrances:
|
||||||
|
if dead_end.name == "region20_top":
|
||||||
|
break
|
||||||
|
# there should be only this one dead-end
|
||||||
|
self.assertTrue(dead_end in lookup.dead_ends)
|
||||||
|
self.assertEqual(len(lookup.dead_ends), 1)
|
||||||
|
|
||||||
class TestBakeTargetGroupLookup(unittest.TestCase):
|
class TestBakeTargetGroupLookup(unittest.TestCase):
|
||||||
def test_lookup_generation(self):
|
def test_lookup_generation(self):
|
||||||
|
Reference in New Issue
Block a user