diff --git a/Rules.py b/Rules.py index 934738e8..cd6b7884 100644 --- a/Rules.py +++ b/Rules.py @@ -11,7 +11,7 @@ def set_rules(world, player): if world.players > 1: locality_rules(world, player) if world.logic[player] == 'nologic': - logging.getLogger('').info( + logging.info( 'WARNING! Seeds generated under this logic often require major glitches and may be impossible!') if world.players == 1: world.get_region('Menu', player).can_reach_private = lambda state: True @@ -449,9 +449,9 @@ def default_rules(world, player): set_rule(world.get_entrance('50 Rupee Cave', player), lambda state: state.can_lift_rocks(player)) set_rule(world.get_entrance('Death Mountain Entrance Rock', player), lambda state: state.can_lift_rocks(player)) set_rule(world.get_entrance('Bumper Cave Entrance Mirror Spot', player), lambda state: state.has_Mirror(player)) - set_rule(world.get_entrance('Flute Spot 1', player), lambda state: state.has('Flute', player)) + set_rule(world.get_entrance('Flute Spot 1', player), lambda state: state.can_flute(player)) set_rule(world.get_entrance('Lake Hylia Central Island Teleporter', player), lambda state: state.can_lift_heavy_rocks(player)) - set_rule(world.get_entrance('Dark Desert Teleporter', player), lambda state: state.has('Flute', player) and state.can_lift_heavy_rocks(player)) + set_rule(world.get_entrance('Dark Desert Teleporter', player), lambda state: state.can_flute(player) and state.can_lift_heavy_rocks(player)) set_rule(world.get_entrance('East Hyrule Teleporter', player), lambda state: state.has('Hammer', player) and state.can_lift_rocks(player) and state.has_Pearl(player)) # bunny cannot use hammer set_rule(world.get_entrance('South Hyrule Teleporter', player), lambda state: state.has('Hammer', player) and state.can_lift_rocks(player) and state.has_Pearl(player)) # bunny cannot use hammer set_rule(world.get_entrance('Kakariko Teleporter', player), lambda state: ((state.has('Hammer', player) and state.can_lift_rocks(player)) or state.can_lift_heavy_rocks(player)) and state.has_Pearl(player)) # bunny cannot lift bushes @@ -1127,52 +1127,52 @@ def set_big_bomb_rules(world, player): elif bombshop_entrance.name in Isolated_DW_entrances: # 1. mirror then flute then basic routes # -> M and Flute and BR - add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.has_Mirror(player) and state.has('Flute', player) and basic_routes(state)) + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.has_Mirror(player) and state.can_flute(player) and basic_routes(state)) elif bombshop_entrance.name in Isolated_LW_entrances: # 1. flute then basic routes # Prexisting mirror spot is not permitted, because mirror might have been needed to reach these isolated locations. # -> Flute and BR - add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.has('Flute', player) and basic_routes(state)) + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.can_flute(player) and basic_routes(state)) elif bombshop_entrance.name in West_LW_DM_entrances: # 1. flute then basic routes or mirror # Prexisting mirror spot is permitted, because flute can be used to reach west DM directly. # -> Flute and (M or BR) - add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.has('Flute', player) and (state.has_Mirror(player) or basic_routes(state))) + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.can_flute(player) and (state.has_Mirror(player) or basic_routes(state))) elif bombshop_entrance.name in East_LW_DM_entrances: # 1. flute then basic routes or mirror and hookshot # Prexisting mirror spot is permitted, because flute can be used to reach west DM directly and then east DM via Hookshot # -> Flute and ((M and Hookshot) or BR) - add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.has('Flute', player) and ((state.has_Mirror(player) and state.has('Hookshot', player)) or basic_routes(state))) + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.can_flute(player) and ((state.has_Mirror(player) and state.has('Hookshot', player)) or basic_routes(state))) elif bombshop_entrance.name == 'Fairy Ascension Cave (Bottom)': # Same as East_LW_DM_entrances except navigation without BR requires Mitts # -> Flute and ((M and Hookshot and Mitts) or BR) - add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.has('Flute', player) and ((state.has_Mirror(player) and state.has('Hookshot', player) and state.can_lift_heavy_rocks(player)) or basic_routes(state))) + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.can_flute(player) and ((state.has_Mirror(player) and state.has('Hookshot', player) and state.can_lift_heavy_rocks(player)) or basic_routes(state))) elif bombshop_entrance.name in Castle_ledge_entrances: # 1. mirror on pyramid to castle ledge, grab bomb, return through mirror spot: Needs mirror # 2. flute then basic routes # -> M or (Flute and BR) - add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.has_Mirror(player) or (state.has('Flute', player) and basic_routes(state))) + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.has_Mirror(player) or (state.can_flute(player) and basic_routes(state))) elif bombshop_entrance.name in Desert_mirrorable_ledge_entrances: # Cases when you have mire access: Mirror to reach locations, return via mirror spot, move to center of desert, mirror anagin and: # 1. Have mire access, Mirror to reach locations, return via mirror spot, move to center of desert, mirror again and then basic routes # 2. flute then basic routes # -> (Mire access and M) or Flute) and BR - add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: ((state.can_reach('Dark Desert', 'Region', player) and state.has_Mirror(player)) or state.has('Flute', player)) and basic_routes(state)) + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: ((state.can_reach('Dark Desert', 'Region', player) and state.has_Mirror(player)) or state.can_flute(player)) and basic_routes(state)) elif bombshop_entrance.name == 'Old Man Cave (West)': # 1. Lift rock then basic_routes # 2. flute then basic_routes # -> (Flute or G) and BR - add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: (state.has('Flute', player) or state.can_lift_rocks(player)) and basic_routes(state)) + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: (state.can_flute(player) or state.can_lift_rocks(player)) and basic_routes(state)) elif bombshop_entrance.name == 'Graveyard Cave': # 1. flute then basic routes # 2. (has west dark world access) use existing mirror spot (required Pearl), mirror again off ledge # -> (Flute or (M and P and West Dark World access) and BR - add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: (state.has('Flute', player) or (state.can_reach('West Dark World', 'Region', player) and state.has_Pearl(player) and state.has_Mirror(player))) and basic_routes(state)) + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: (state.can_flute(player) or (state.can_reach('West Dark World', 'Region', player) and state.has_Pearl(player) and state.has_Mirror(player))) and basic_routes(state)) elif bombshop_entrance.name in Mirror_from_SDW_entrances: # 1. flute then basic routes # 2. (has South dark world access) use existing mirror spot, mirror again off ledge # -> (Flute or (M and South Dark World access) and BR - add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: (state.has('Flute', player) or (state.can_reach('South Dark World', 'Region', player) and state.has_Mirror(player))) and basic_routes(state)) + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: (state.can_flute(player) or (state.can_reach('South Dark World', 'Region', player) and state.has_Mirror(player))) and basic_routes(state)) elif bombshop_entrance.name == 'Dark World Potion Shop': # 1. walk down by lifting rock: needs gloves and pearl` # 2. walk down by hammering peg: needs hammer and pearl @@ -1184,11 +1184,11 @@ def set_big_bomb_rules(world, player): # (because otherwise mirror was used to reach the grave, so would cancel a pre-existing mirror spot) # to account for insanity, must consider a way to escape without a cave for basic_routes # -> (M and Mitts) or ((Mitts or Flute or (M and P and West Dark World access)) and BR) - add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: (state.can_lift_heavy_rocks(player) and state.has_Mirror(player)) or ((state.can_lift_heavy_rocks(player) or state.has('Flute', player) or (state.can_reach('West Dark World', 'Region', player) and state.has_Pearl(player) and state.has_Mirror(player))) and basic_routes(state))) + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: (state.can_lift_heavy_rocks(player) and state.has_Mirror(player)) or ((state.can_lift_heavy_rocks(player) or state.can_flute(player) or (state.can_reach('West Dark World', 'Region', player) and state.has_Pearl(player) and state.has_Mirror(player))) and basic_routes(state))) elif bombshop_entrance.name == 'Waterfall of Wishing': # same as the Normal_LW_entrances case except in insanity it's possible you could be here without Flippers which # means you need an escape route of either Flippers or Flute - add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: (state.has('Flippers', player) or state.has('Flute', player)) and (basic_routes(state) or state.has_Mirror(player))) + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: (state.has('Flippers', player) or state.can_flute(player)) and (basic_routes(state) or state.has_Mirror(player))) def set_inverted_big_bomb_rules(world, player): diff --git a/test/TestBase.py b/test/TestBase.py index df5c981f..474285f2 100644 --- a/test/TestBase.py +++ b/test/TestBase.py @@ -23,7 +23,7 @@ class TestBase(unittest.TestCase): for location, access, *item_pool in access_pool: items = item_pool[0] all_except = item_pool[1] if len(item_pool) > 1 else None - with self.subTest(location=location, access=access, items=items, all_except=all_except): + with self.subTest(msg="Reach Location", location=location, access=access, items=items, all_except=all_except): if all_except and len(all_except) > 0: items = self.world.itempool[:] items = [item for item in items if item.name not in all_except and not ("Bottle" in item.name and "AnyBottle" in all_except)] @@ -34,11 +34,23 @@ class TestBase(unittest.TestCase): self.assertEqual(self.world.get_location(location, 1).can_reach(state), access) + + #check for partial solution + if not all_except and access:# we are not supposed to be able to reach location with partial inventory + for missing_item in item_pool[0]: + with self.subTest(msg="Location reachable without required item", location=location, items=item_pool[0], + all_except=all_except, missing_item=missing_item): + new_items = item_pool[0].copy() + new_items.remove(missing_item) + items = ItemFactory(new_items, 1) + state = self.get_state(items) + self.assertEqual(self.world.get_location(location, 1).can_reach(state), False) + def run_entrance_tests(self, access_pool): for entrance, access, *item_pool in access_pool: items = item_pool[0] all_except = item_pool[1] if len(item_pool) > 1 else None - with self.subTest(entrance=entrance, access=access, items=items, all_except=all_except): + with self.subTest(msg="Reach Entrance", entrance=entrance, access=access, items=items, all_except=all_except): if all_except and len(all_except) > 0: items = self.world.itempool[:] items = [item for item in items if item.name not in all_except and not ("Bottle" in item.name and "AnyBottle" in all_except)] @@ -47,4 +59,15 @@ class TestBase(unittest.TestCase): items = ItemFactory(items, 1) state = self.get_state(items) - self.assertEqual(self.world.get_entrance(entrance, 1).can_reach(state), access) \ No newline at end of file + self.assertEqual(self.world.get_entrance(entrance, 1).can_reach(state), access) + + #check for partial solution + if not all_except and access:# we are not supposed to be able to reach location with partial inventory + for missing_item in item_pool[0]: + with self.subTest(msg="Entrance reachable without required item", entrance=entrance, items=item_pool[0], + all_except=all_except, missing_item=missing_item): + new_items = item_pool[0].copy() + new_items.remove(missing_item) + items = ItemFactory(new_items, 1) + state = self.get_state(items) + self.assertEqual(self.world.get_entrance(entrance, 1).can_reach(state), False) \ No newline at end of file diff --git a/test/inverted/TestInvertedDarkWorld.py b/test/inverted/TestInvertedDarkWorld.py index 82ba2065..047d73c9 100644 --- a/test/inverted/TestInvertedDarkWorld.py +++ b/test/inverted/TestInvertedDarkWorld.py @@ -55,7 +55,7 @@ class TestInvertedDarkWorld(TestInverted): ["Pyramid", False, []], ["Pyramid", True, ['Beat Agahnim 1', 'Magic Mirror']], ["Pyramid", True, ['Hammer']], - ["Pyramid", True, ['Flippers', 'Progressive Glove']], + ["Pyramid", True, ['Flippers']], ["Pyramid", True, ['Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Moon Pearl']], ["Pyramid Fairy - Left", False, []], diff --git a/test/inverted/TestInvertedEntrances.py b/test/inverted/TestInvertedEntrances.py index 9ae260d6..6f8e1e63 100644 --- a/test/inverted/TestInvertedEntrances.py +++ b/test/inverted/TestInvertedEntrances.py @@ -114,5 +114,4 @@ class TestEntrances(TestInverted): ["Inverted Ganons Tower", False, [], ["Crystal 7"]], ["Inverted Ganons Tower", True, ["Beat Agahnim 1", "Crystal 1", "Crystal 2", "Crystal 3", "Crystal 4", "Crystal 5", "Crystal 6", "Crystal 7"]], ["Inverted Ganons Tower", True, ["Moon Pearl", "Progressive Glove", "Progressive Glove", "Crystal 1", "Crystal 2", "Crystal 3", "Crystal 4", "Crystal 5", "Crystal 6", "Crystal 7"]], - ["Inverted Ganons Tower", True, ["Moon Pearl", "Hammer", "Progressive Glove", "Progressive Glove", "Crystal 1", "Crystal 2", "Crystal 3", "Crystal 4", "Crystal 5", "Crystal 6", "Crystal 7"]], ]) \ No newline at end of file diff --git a/test/inverted/TestInvertedLightWorld.py b/test/inverted/TestInvertedLightWorld.py index 64ee267c..07ab39e8 100644 --- a/test/inverted/TestInvertedLightWorld.py +++ b/test/inverted/TestInvertedLightWorld.py @@ -195,7 +195,7 @@ class TestInvertedLightWorld(TestInverted): ["Bombos Tablet", True, ['Beat Agahnim 1', 'Book of Mudora', 'Progressive Sword', 'Progressive Sword']], ["Bombos Tablet", True, ['Moon Pearl', 'Book of Mudora', 'Progressive Glove', 'Progressive Glove', 'Progressive Sword', 'Progressive Sword']], ["Bombos Tablet", True, ['Moon Pearl', 'Book of Mudora', 'Progressive Glove', 'Hammer', 'Progressive Sword', 'Progressive Sword']], - ["Bombos Tablet", True, ['Moon Pearl', 'Book of Mudora', 'Beat Agahnim 1', 'Progressive Sword', 'Progressive Sword']], + ["Bombos Tablet", True, ['Book of Mudora', 'Beat Agahnim 1', 'Progressive Sword', 'Progressive Sword']], ["Floodgate Chest", False, []], ["Floodgate Chest", False, [], ['Moon Pearl']],