Tests: Implement generic default options reachability test

Tests: remove duplicate TestDeathMountain.py
LttP: Move er_seeds out of Main
OriBF: Fix Mapstone typo
This commit is contained in:
Fabian Dill 2021-10-06 11:32:49 +02:00
parent 29a207b73e
commit 1217179f8a
10 changed files with 169 additions and 126 deletions

View File

@ -25,7 +25,6 @@ class MultiWorld():
plando_texts: List[Dict[str, str]]
plando_items: List
plando_connections: List
er_seeds: Dict[int, str]
worlds: Dict[int, Any]
is_race: bool = False
@ -66,15 +65,20 @@ class MultiWorld():
self.dynamic_regions = []
self.dynamic_locations = []
self.spoiler = Spoiler(self)
self.fix_trock_doors = self.AttributeProxy(lambda player: self.shuffle[player] != 'vanilla' or self.mode[player] == 'inverted')
self.fix_skullwoods_exit = self.AttributeProxy(lambda player: self.shuffle[player] not in ['vanilla', 'simple', 'restricted', 'dungeonssimple'])
self.fix_palaceofdarkness_exit = self.AttributeProxy(lambda player: self.shuffle[player] not in ['vanilla', 'simple', 'restricted', 'dungeonssimple'])
self.fix_trock_exit = self.AttributeProxy(lambda player: self.shuffle[player] not in ['vanilla', 'simple', 'restricted', 'dungeonssimple'])
self.fix_trock_doors = self.AttributeProxy(
lambda player: self.shuffle[player] != 'vanilla' or self.mode[player] == 'inverted')
self.fix_skullwoods_exit = self.AttributeProxy(
lambda player: self.shuffle[player] not in ['vanilla', 'simple', 'restricted', 'dungeonssimple'])
self.fix_palaceofdarkness_exit = self.AttributeProxy(
lambda player: self.shuffle[player] not in ['vanilla', 'simple', 'restricted', 'dungeonssimple'])
self.fix_trock_exit = self.AttributeProxy(
lambda player: self.shuffle[player] not in ['vanilla', 'simple', 'restricted', 'dungeonssimple'])
self.NOTCURSED = self.AttributeProxy(lambda player: not self.CURSED[player])
for player in range(1, players + 1):
def set_player_attr(attr, val):
self.__dict__.setdefault(attr, {})[player] = val
set_player_attr('tech_tree_layout_prerequisites', {})
set_player_attr('_region_cache', {})
set_player_attr('shuffle', "vanilla")
@ -122,6 +126,17 @@ class MultiWorld():
set_player_attr('completion_condition', lambda state: True)
self.custom_data = {}
self.worlds = {}
self.slot_seeds = {}
def set_seed(self, seed: Optional[int] = None, secure: bool = False, name: Optional[str] = None):
self.seed = get_seed(seed)
if secure:
self.secure()
else:
self.random.seed(self.seed)
self.seed_name = name if name else str(self.seed)
self.slot_seeds = {player: random.Random(self.random.getrandbits(64)) for player in
range(1, self.players + 1)}
def set_options(self, args):
from worlds import AutoWorld
@ -230,7 +245,7 @@ class MultiWorld():
if item.name in subworld.dungeon_local_item_names:
subworld.collect(ret, item)
ret.sweep_for_events()
if use_cache:
self._all_state = ret
return ret
@ -270,7 +285,7 @@ class MultiWorld():
else:
raise RuntimeError('Cannot assign item %s to location %s.' % (item, location))
def get_entrances(self) -> list:
def get_entrances(self) -> List[Entrance]:
if self._cached_entrances is None:
self._cached_entrances = [entrance for region in self.regions for entrance in region.entrances]
return self._cached_entrances
@ -278,7 +293,7 @@ class MultiWorld():
def clear_entrance_cache(self):
self._cached_entrances = None
def get_locations(self) -> list:
def get_locations(self) -> List[Location]:
if self._cached_locations is None:
self._cached_locations = [location for region in self.regions for location in region.locations]
return self._cached_locations
@ -286,7 +301,7 @@ class MultiWorld():
def clear_location_cache(self):
self._cached_locations = None
def get_unfilled_locations(self, player=None) -> list:
def get_unfilled_locations(self, player=None) -> List[Location]:
if player is not None:
return [location for location in self.get_locations() if
location.player == player and not location.item]
@ -295,19 +310,19 @@ class MultiWorld():
def get_unfilled_dungeon_locations(self):
return [location for location in self.get_locations() if not location.item and location.parent_region.dungeon]
def get_filled_locations(self, player=None) -> list:
def get_filled_locations(self, player=None) -> List[Location]:
if player is not None:
return [location for location in self.get_locations() if
location.player == player and location.item is not None]
return [location for location in self.get_locations() if location.item is not None]
def get_reachable_locations(self, state=None, player=None) -> list:
def get_reachable_locations(self, state=None, player=None) -> List[Location]:
if state is None:
state = self.state
return [location for location in self.get_locations() if
(player is None or location.player == player) and location.can_reach(state)]
def get_placeable_locations(self, state=None, player=None) -> list:
def get_placeable_locations(self, state=None, player=None) -> List[Location]:
if state is None:
state = self.state
return [location for location in self.get_locations() if
@ -335,7 +350,7 @@ class MultiWorld():
else:
return all((self.has_beaten_game(state, p) for p in range(1, self.players + 1)))
def can_beat_game(self, starting_state : Optional[CollectionState]=None):
def can_beat_game(self, starting_state: Optional[CollectionState] = None):
if starting_state:
if self.has_beaten_game(starting_state):
return True
@ -392,7 +407,7 @@ class MultiWorld():
"""Check if accessibility rules are fulfilled with current or supplied state."""
if not state:
state = CollectionState(self)
players = {"minimal" : set(),
players = {"minimal": set(),
"items": set(),
"locations": set()}
for player, access in self.accessibility.items():
@ -400,13 +415,13 @@ class MultiWorld():
beatable_fulfilled = False
def location_conditition(location : Location):
def location_conditition(location: Location):
"""Determine if this location has to be accessible, location is already filtered by location_relevant"""
if location.player in players["minimal"]:
return False
return True
def location_relevant(location : Location):
def location_relevant(location: Location):
"""Determine if this location is relevant to sweep."""
if location.player in players["locations"] or location.event or \
(location.item and location.item.advancement):
@ -499,7 +514,8 @@ class CollectionState(object):
ret.prog_items = self.prog_items.copy()
ret.reachable_regions = {player: copy.copy(self.reachable_regions[player]) for player in
range(1, self.world.players + 1)}
ret.blocked_connections = {player: copy.copy(self.blocked_connections[player]) for player in range(1, self.world.players + 1)}
ret.blocked_connections = {player: copy.copy(self.blocked_connections[player]) for player in
range(1, self.world.players + 1)}
ret.events = copy.copy(self.events)
ret.path = copy.copy(self.path)
ret.locations_checked = copy.copy(self.locations_checked)
@ -535,10 +551,10 @@ class CollectionState(object):
def has(self, item, player: int, count: int = 1):
return self.prog_items[item, player] >= count
def has_all(self, items: Set[str], player:int):
def has_all(self, items: Set[str], player: int):
return all(self.prog_items[item, player] for item in items)
def has_any(self, items: Set[str], player:int):
def has_any(self, items: Set[str], player: int):
return any(self.prog_items[item, player] for item in items)
def has_group(self, item_name_group: str, player: int, count: int = 1):
@ -638,10 +654,10 @@ class CollectionState(object):
self.is_not_bunny(cave, player)
)
def can_retrieve_tablet(self, player:int) -> bool:
def can_retrieve_tablet(self, player: int) -> bool:
return self.has('Book of Mudora', player) and (self.has_beam_sword(player) or
(self.world.swordless[player] and
self.has("Hammer", player)))
(self.world.swordless[player] and
self.has("Hammer", player)))
def has_sword(self, player: int) -> bool:
return self.has('Fighter Sword', player) \
@ -650,7 +666,8 @@ class CollectionState(object):
or self.has('Golden Sword', player)
def has_beam_sword(self, player: int) -> bool:
return self.has('Master Sword', player) or self.has('Tempered Sword', player) or self.has('Golden Sword', player)
return self.has('Master Sword', player) or self.has('Tempered Sword', player) or self.has('Golden Sword',
player)
def has_melee_weapon(self, player: int) -> bool:
return self.has_sword(player) or self.has('Hammer', player)
@ -714,7 +731,7 @@ class CollectionState(object):
rules.append(self.has('Moon Pearl', player))
return all(rules)
def can_bomb_clip(self, region: Region, player: int) -> bool:
def can_bomb_clip(self, region: Region, player: int) -> bool:
return self.is_not_bunny(region, player) and self.has('Pegasus Boots', player)
def collect(self, item: Item, event: bool = False, location: Location = None) -> bool:
@ -742,12 +759,13 @@ class CollectionState(object):
self.blocked_connections[item.player] = set()
self.stale[item.player] = True
@unique
class RegionType(int, Enum):
Generic = 0
LightWorld = 1
DarkWorld = 2
Cave = 3 # Also includes Houses
Cave = 3 # Also includes Houses
Dungeon = 4
@property
@ -869,6 +887,7 @@ class Dungeon(object):
def __str__(self):
return self.world.get_name_string_for_object(self) if self.world else f'{self.name} (Player {self.player})'
class Boss():
def __init__(self, name, enemizer_name, defeat_rule, player: int):
self.name = name
@ -882,6 +901,7 @@ class Boss():
def __repr__(self):
return f"Boss({self.name})"
class Location():
# If given as integer, then this is the shop's inventory index
shop_slot: Optional[int] = None
@ -897,7 +917,7 @@ class Location():
access_rule = staticmethod(lambda state: True)
item_rule = staticmethod(lambda item: True)
def __init__(self, player: int, name: str = '', address:int = None, parent=None):
def __init__(self, player: int, name: str = '', address: int = None, parent=None):
self.name: str = name
self.address: Optional[int] = address
self.parent_region: Region = parent
@ -1021,39 +1041,58 @@ class Spoiler():
def set_entrance(self, entrance, exit, direction, player):
if self.world.players == 1:
self.entrances[(entrance, direction, player)] = OrderedDict([('entrance', entrance), ('exit', exit), ('direction', direction)])
self.entrances[(entrance, direction, player)] = OrderedDict(
[('entrance', entrance), ('exit', exit), ('direction', direction)])
else:
self.entrances[(entrance, direction, player)] = OrderedDict([('player', player), ('entrance', entrance), ('exit', exit), ('direction', direction)])
self.entrances[(entrance, direction, player)] = OrderedDict(
[('player', player), ('entrance', entrance), ('exit', exit), ('direction', direction)])
def parse_data(self):
self.medallions = OrderedDict()
for player in self.world.get_game_players("A Link to the Past"):
self.medallions[f'Misery Mire ({self.world.get_player_name(player)})'] = self.world.required_medallions[player][0]
self.medallions[f'Turtle Rock ({self.world.get_player_name(player)})'] = self.world.required_medallions[player][1]
self.medallions[f'Misery Mire ({self.world.get_player_name(player)})'] = \
self.world.required_medallions[player][0]
self.medallions[f'Turtle Rock ({self.world.get_player_name(player)})'] = \
self.world.required_medallions[player][1]
self.locations = OrderedDict()
listed_locations = set()
lw_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations and loc.parent_region and loc.parent_region.type == RegionType.LightWorld and loc.show_in_spoiler]
self.locations['Light World'] = OrderedDict([(str(location), str(location.item) if location.item is not None else 'Nothing') for location in lw_locations])
lw_locations = [loc for loc in self.world.get_locations() if
loc not in listed_locations and loc.parent_region and loc.parent_region.type == RegionType.LightWorld and loc.show_in_spoiler]
self.locations['Light World'] = OrderedDict(
[(str(location), str(location.item) if location.item is not None else 'Nothing') for location in
lw_locations])
listed_locations.update(lw_locations)
dw_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations and loc.parent_region and loc.parent_region.type == RegionType.DarkWorld and loc.show_in_spoiler]
self.locations['Dark World'] = OrderedDict([(str(location), str(location.item) if location.item is not None else 'Nothing') for location in dw_locations])
dw_locations = [loc for loc in self.world.get_locations() if
loc not in listed_locations and loc.parent_region and loc.parent_region.type == RegionType.DarkWorld and loc.show_in_spoiler]
self.locations['Dark World'] = OrderedDict(
[(str(location), str(location.item) if location.item is not None else 'Nothing') for location in
dw_locations])
listed_locations.update(dw_locations)
cave_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations and loc.parent_region and loc.parent_region.type == RegionType.Cave and loc.show_in_spoiler]
self.locations['Caves'] = OrderedDict([(str(location), str(location.item) if location.item is not None else 'Nothing') for location in cave_locations])
cave_locations = [loc for loc in self.world.get_locations() if
loc not in listed_locations and loc.parent_region and loc.parent_region.type == RegionType.Cave and loc.show_in_spoiler]
self.locations['Caves'] = OrderedDict(
[(str(location), str(location.item) if location.item is not None else 'Nothing') for location in
cave_locations])
listed_locations.update(cave_locations)
for dungeon in self.world.dungeons.values():
dungeon_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations and loc.parent_region and loc.parent_region.dungeon == dungeon and loc.show_in_spoiler]
self.locations[str(dungeon)] = OrderedDict([(str(location), str(location.item) if location.item is not None else 'Nothing') for location in dungeon_locations])
dungeon_locations = [loc for loc in self.world.get_locations() if
loc not in listed_locations and loc.parent_region and loc.parent_region.dungeon == dungeon and loc.show_in_spoiler]
self.locations[str(dungeon)] = OrderedDict(
[(str(location), str(location.item) if location.item is not None else 'Nothing') for location in
dungeon_locations])
listed_locations.update(dungeon_locations)
other_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations and loc.show_in_spoiler]
other_locations = [loc for loc in self.world.get_locations() if
loc not in listed_locations and loc.show_in_spoiler]
if other_locations:
self.locations['Other Locations'] = OrderedDict([(str(location), str(location.item) if location.item is not None else 'Nothing') for location in other_locations])
self.locations['Other Locations'] = OrderedDict(
[(str(location), str(location.item) if location.item is not None else 'Nothing') for location in
other_locations])
listed_locations.update(other_locations)
self.shops = []
@ -1069,10 +1108,13 @@ class Spoiler():
if item is None:
continue
my_price = item['price'] // price_rate_display.get(item['price_type'], 1)
shopdata['item_{}'.format(index)] = f"{item['item']}{my_price} {price_type_display_name[item['price_type']]}"
shopdata['item_{}'.format(
index)] = f"{item['item']}{my_price} {price_type_display_name[item['price_type']]}"
if item['player'] > 0:
shopdata['item_{}'.format(index)] = shopdata['item_{}'.format(index)].replace('', '(Player {}) — '.format(item['player']))
shopdata['item_{}'.format(index)] = shopdata['item_{}'.format(index)].replace('',
'(Player {}) — '.format(
item['player']))
if item['max'] == 0:
continue
@ -1080,7 +1122,8 @@ class Spoiler():
if item['replacement'] is None:
continue
shopdata['item_{}'.format(index)] += f", {item['replacement']} - {item['replacement_price']} {price_type_display_name[item['replacement_price_type']]}"
shopdata['item_{}'.format(
index)] += f", {item['replacement']} - {item['replacement_price']} {price_type_display_name[item['replacement_price_type']]}"
self.shops.append(shopdata)
for player in self.world.get_game_players("A Link to the Past"):
@ -1089,7 +1132,8 @@ class Spoiler():
self.bosses[str(player)]["Desert Palace"] = self.world.get_dungeon("Desert Palace", player).boss.name
self.bosses[str(player)]["Tower Of Hera"] = self.world.get_dungeon("Tower of Hera", player).boss.name
self.bosses[str(player)]["Hyrule Castle"] = "Agahnim"
self.bosses[str(player)]["Palace Of Darkness"] = self.world.get_dungeon("Palace of Darkness", player).boss.name
self.bosses[str(player)]["Palace Of Darkness"] = self.world.get_dungeon("Palace of Darkness",
player).boss.name
self.bosses[str(player)]["Swamp Palace"] = self.world.get_dungeon("Swamp Palace", player).boss.name
self.bosses[str(player)]["Skull Woods"] = self.world.get_dungeon("Skull Woods", player).boss.name
self.bosses[str(player)]["Thieves Town"] = self.world.get_dungeon("Thieves Town", player).boss.name
@ -1097,13 +1141,19 @@ class Spoiler():
self.bosses[str(player)]["Misery Mire"] = self.world.get_dungeon("Misery Mire", player).boss.name
self.bosses[str(player)]["Turtle Rock"] = self.world.get_dungeon("Turtle Rock", player).boss.name
if self.world.mode[player] != 'inverted':
self.bosses[str(player)]["Ganons Tower Basement"] = self.world.get_dungeon('Ganons Tower', player).bosses['bottom'].name
self.bosses[str(player)]["Ganons Tower Middle"] = self.world.get_dungeon('Ganons Tower', player).bosses['middle'].name
self.bosses[str(player)]["Ganons Tower Top"] = self.world.get_dungeon('Ganons Tower', player).bosses['top'].name
self.bosses[str(player)]["Ganons Tower Basement"] = \
self.world.get_dungeon('Ganons Tower', player).bosses['bottom'].name
self.bosses[str(player)]["Ganons Tower Middle"] = self.world.get_dungeon('Ganons Tower', player).bosses[
'middle'].name
self.bosses[str(player)]["Ganons Tower Top"] = self.world.get_dungeon('Ganons Tower', player).bosses[
'top'].name
else:
self.bosses[str(player)]["Ganons Tower Basement"] = self.world.get_dungeon('Inverted Ganons Tower', player).bosses['bottom'].name
self.bosses[str(player)]["Ganons Tower Middle"] = self.world.get_dungeon('Inverted Ganons Tower', player).bosses['middle'].name
self.bosses[str(player)]["Ganons Tower Top"] = self.world.get_dungeon('Inverted Ganons Tower', player).bosses['top'].name
self.bosses[str(player)]["Ganons Tower Basement"] = \
self.world.get_dungeon('Inverted Ganons Tower', player).bosses['bottom'].name
self.bosses[str(player)]["Ganons Tower Middle"] = \
self.world.get_dungeon('Inverted Ganons Tower', player).bosses['middle'].name
self.bosses[str(player)]["Ganons Tower Top"] = \
self.world.get_dungeon('Inverted Ganons Tower', player).bosses['top'].name
self.bosses[str(player)]["Ganons Tower"] = "Agahnim 2"
self.bosses[str(player)]["Ganon"] = "Ganon"
@ -1217,22 +1267,30 @@ class Spoiler():
outfile.write(f"\n{recipe.name} ({name}): {recipe.ingredients} -> {recipe.products}")
outfile.write('\n\nLocations:\n\n')
outfile.write('\n'.join(['%s: %s' % (location, item) for grouping in self.locations.values() for (location, item) in grouping.items()]))
outfile.write('\n'.join(
['%s: %s' % (location, item) for grouping in self.locations.values() for (location, item) in
grouping.items()]))
if self.shops:
outfile.write('\n\nShops:\n\n')
outfile.write('\n'.join("{} [{}]\n {}".format(shop['location'], shop['type'], "\n ".join(item for item in [shop.get('item_0', None), shop.get('item_1', None), shop.get('item_2', None)] if item)) for shop in self.shops))
outfile.write('\n'.join("{} [{}]\n {}".format(shop['location'], shop['type'], "\n ".join(
item for item in [shop.get('item_0', None), shop.get('item_1', None), shop.get('item_2', None)] if
item)) for shop in self.shops))
for player in self.world.get_game_players("A Link to the Past"):
if self.world.boss_shuffle[player] != 'none':
bossmap = self.bosses[str(player)] if self.world.players > 1 else self.bosses
outfile.write(f'\n\nBosses{(f" ({self.world.get_player_name(player)})" if self.world.players > 1 else "")}:\n')
outfile.write(' '+'\n '.join([f'{x}: {y}' for x, y in bossmap.items()]))
outfile.write(
f'\n\nBosses{(f" ({self.world.get_player_name(player)})" if self.world.players > 1 else "")}:\n')
outfile.write(' ' + '\n '.join([f'{x}: {y}' for x, y in bossmap.items()]))
outfile.write('\n\nPlaythrough:\n\n')
outfile.write('\n'.join(['%s: {\n%s\n}' % (sphere_nr, '\n'.join([' %s: %s' % (location, item) for (location, item) in sphere.items()] if sphere_nr != '0' else [f' {item}' for item in sphere])) for (sphere_nr, sphere) in self.playthrough.items()]))
outfile.write('\n'.join(['%s: {\n%s\n}' % (sphere_nr, '\n'.join(
[' %s: %s' % (location, item) for (location, item) in sphere.items()] if sphere_nr != '0' else [
f' {item}' for item in sphere])) for (sphere_nr, sphere) in self.playthrough.items()]))
if self.unreachables:
outfile.write('\n\nUnreachable Items:\n\n')
outfile.write('\n'.join(['%s: %s' % (unreachable.item, unreachable) for unreachable in self.unreachables]))
outfile.write(
'\n'.join(['%s: %s' % (unreachable.item, unreachable) for unreachable in self.unreachables]))
if self.paths:
outfile.write('\n\nPaths:\n\n')
@ -1247,3 +1305,13 @@ class Spoiler():
path_listings.append("{}\n {}".format(location, "\n => ".join(path_lines)))
outfile.write('\n'.join(path_listings))
seeddigits = 20
def get_seed(seed=None):
if seed is None:
random.seed(None)
return random.randint(0, pow(10, seeddigits) - 1)
return seed

View File

@ -17,7 +17,7 @@ from worlds.generic import PlandoItem, PlandoConnection
from Utils import parse_yaml, version_tuple, __version__, tuplize_version, get_options
from worlds.alttp.EntranceRandomizer import parse_arguments
from Main import main as ERmain
from Main import get_seed, seeddigits
from BaseClasses import seeddigits, get_seed
import Options
from worlds.alttp import Bosses
from worlds.alttp.Text import TextTable

23
Main.py
View File

@ -1,7 +1,6 @@
from itertools import zip_longest
import logging
import os
import random
import time
import zlib
import concurrent.futures
@ -19,15 +18,6 @@ from Utils import output_path, get_options, __version__, version_tuple
from worlds.generic.Rules import locality_rules, exclusion_rules
from worlds import AutoWorld
seeddigits = 20
def get_seed(seed=None):
if seed is None:
random.seed(None)
return random.randint(0, pow(10, seeddigits) - 1)
return seed
def main(args, seed=None):
if args.outputpath:
@ -39,13 +29,8 @@ def main(args, seed=None):
# initialize the world
world = MultiWorld(args.multi)
logger = logging.getLogger('')
world.seed = get_seed(seed)
if args.race:
world.secure()
else:
world.random.seed(world.seed)
world.seed_name = str(args.outputname if args.outputname else world.seed)
logger = logging.getLogger()
world.set_seed(args.race, str(args.outputname if args.outputname else world.seed))
world.shuffle = args.shuffle.copy()
world.logic = args.logic.copy()
@ -81,7 +66,6 @@ def main(args, seed=None):
world.plando_items = args.plando_items.copy()
world.plando_texts = args.plando_texts.copy()
world.plando_connections = args.plando_connections.copy()
world.er_seeds = getattr(args, "er_seeds", {})
world.required_medallions = args.required_medallions.copy()
world.game = args.game.copy()
world.set_options(args)
@ -90,9 +74,6 @@ def main(args, seed=None):
world.sprite = args.sprite.copy()
world.glitch_triforce = args.glitch_triforce # This is enabled/disabled globally, no per player option.
world.slot_seeds = {player: random.Random(world.random.getrandbits(64)) for player in
range(1, world.players + 1)}
logger.info('Archipelago Version %s - Seed: %s\n', __version__, world.seed)
logger.info("Found World Types:")

View File

@ -9,7 +9,7 @@ from flask import request, flash, redirect, url_for, session, render_template
from worlds.alttp.EntranceRandomizer import parse_arguments
from Main import main as ERmain
from Main import get_seed, seeddigits
from BaseClasses import seeddigits, get_seed
from Generate import handle_name
import pickle

32
test/Reachability.py Normal file
View File

@ -0,0 +1,32 @@
import unittest
from argparse import Namespace
from BaseClasses import MultiWorld
from worlds.AutoWorld import AutoWorldRegister, call_all
class TestBase(unittest.TestCase):
_state_cache = {}
gen_steps = ["generate_early", "create_regions", "create_items", "set_rules", "generate_basic", "pre_fill"]
def testAllStateCanReachEverything(self):
for game_name, world_type in AutoWorldRegister.world_types.items():
if game_name != "Ori and the Blind Forest": # TODO: fix Ori Logic
with self.subTest("Game", game=game_name):
world = MultiWorld(1)
world.game[1] = game_name
world.player_name = {1: "Tester"}
world.set_seed()
args = Namespace()
for name, option in world_type.options.items():
setattr(args, name, {1: option.from_any(option.default)})
world.set_options(args)
world.set_default_common_options()
for step in self.gen_steps:
call_all(world, step)
state = world.get_all_state(False)
for location in world.get_locations():
with self.subTest("Location should be reached", location=location):
if not location.can_reach(state):
print("Bla!")
self.assertTrue(location.can_reach(state))

View File

@ -1,38 +0,0 @@
from test.vanilla.TestVanilla import TestVanilla
class TestDeathMountain(TestVanilla):
def testWestDeathMountain(self):
self.run_location_tests([
["Ether Tablet", False, []],
["Ether Tablet", False, [], ['Progressive Glove', 'Flute']],
["Ether Tablet", False, [], ['Lamp', 'Flute']],
["Ether Tablet", False, [], ['Magic Mirror', 'Hookshot']],
["Ether Tablet", False, [], ['Magic Mirror', 'Hammer']],
["Ether Tablet", False, ['Progressive Sword'], ['Progressive Sword']],
["Ether Tablet", False, [], ['Book of Mudora']],
["Ether Tablet", True, ['Flute', 'Magic Mirror', 'Book of Mudora', 'Progressive Sword', 'Progressive Sword']],
["Ether Tablet", True, ['Progressive Glove', 'Lamp', 'Magic Mirror', 'Book of Mudora', 'Progressive Sword', 'Progressive Sword']],
["Ether Tablet", True, ['Flute', 'Hammer', 'Hookshot', 'Book of Mudora', 'Progressive Sword', 'Progressive Sword']],
["Ether Tablet", True, ['Progressive Glove', 'Lamp', 'Hammer', 'Hookshot', 'Book of Mudora', 'Progressive Sword', 'Progressive Sword']],
["Old Man", False, []],
["Old Man", False, [], ['Progressive Glove', 'Flute']],
["Old Man", False, [], ['Lamp']],
["Old Man", True, ['Flute', 'Lamp']],
["Old Man", True, ['Progressive Glove', 'Lamp']],
["Spectacle Rock Cave", False, []],
["Spectacle Rock Cave", False, [], ['Progressive Glove', 'Flute']],
["Spectacle Rock Cave", False, [], ['Lamp', 'Flute']],
["Spectacle Rock Cave", True, ['Flute']],
["Spectacle Rock Cave", True, ['Progressive Glove', 'Lamp']],
["Spectacle Rock", False, []],
["Spectacle Rock", False, [], ['Progressive Glove', 'Flute']],
["Spectacle Rock", False, [], ['Lamp', 'Flute']],
["Spectacle Rock", False, [], ['Magic Mirror']],
["Spectacle Rock", True, ['Flute', 'Magic Mirror']],
["Spectacle Rock", True, ['Progressive Glove', 'Lamp', 'Magic Mirror']],
])

View File

@ -48,6 +48,7 @@ def call_all(world: MultiWorld, method_name: str, *args):
for player in world.player_ids:
world_types.add(world.worlds[player].__class__)
call_single(world, method_name, player, *args)
for world_type in world_types:
stage_callable = getattr(world_type, f"stage_{method_name}", None)
if stage_callable:

View File

@ -257,7 +257,6 @@ def parse_arguments(argv, no_defaults=False):
ret.plando_items = []
ret.plando_texts = {}
ret.plando_connections = []
ret.er_seeds = {}
if ret.timer == "none":
ret.timer = False
@ -280,7 +279,7 @@ def parse_arguments(argv, no_defaults=False):
"triforce_pieces_available",
"triforce_pieces_required", "shop_shuffle",
"required_medallions",
"plando_items", "plando_texts", "plando_connections", "er_seeds",
"plando_items", "plando_texts", "plando_connections",
'dungeon_counters',
'shuffle_prizes', 'sprite_pool', 'dark_room_logic',
'game']:

View File

@ -60,20 +60,20 @@ class ALTTPWorld(World):
world = self.world
# system for sharing ER layouts
world.er_seeds[player] = str(world.random.randint(0, 2 ** 64))
self.er_seed = str(world.random.randint(0, 2 ** 64))
if "-" in world.shuffle[player]:
shuffle, seed = world.shuffle[player].split("-", 1)
world.shuffle[player] = shuffle
if shuffle == "vanilla":
world.er_seeds[player] = "vanilla"
self.er_seed = "vanilla"
elif seed.startswith("group-") or world.is_race:
world.er_seeds[player] = get_same_seed(world, (
self.er_seed = get_same_seed(world, (
shuffle, seed, world.retro[player], world.mode[player], world.logic[player]))
else: # not a race or group seed, use set seed as is.
world.er_seeds[player] = seed
self.er_seed = seed
elif world.shuffle[player] == "vanilla":
world.er_seeds[player] = "vanilla"
self.er_seed = "vanilla"
for dungeon_item in ["smallkey_shuffle", "bigkey_shuffle", "compass_shuffle", "map_shuffle"]:
option = getattr(world, dungeon_item)[player]
if option == "own_world":
@ -118,7 +118,7 @@ class ALTTPWorld(World):
# seeded entrance shuffle
old_random = world.random
world.random = random.Random(world.er_seeds[player])
world.random = random.Random(self.er_seed)
if world.mode[player] != 'inverted':
link_entrances(world, player)

File diff suppressed because one or more lines are too long