mirror of
https://github.com/ArchipelagoMW/Archipelago.git
synced 2026-04-07 19:28:13 -07:00
Merge branch 'main' into rework_accessibility
# Conflicts: # worlds/alttp/test/inverted_minor_glitches/TestInvertedMinor.py # worlds/alttp/test/inverted_owg/TestInvertedOWG.py
This commit is contained in:
@@ -10,7 +10,7 @@ from worlds import AutoWorld
|
||||
from worlds.AutoWorld import World, call_all
|
||||
|
||||
from BaseClasses import Location, MultiWorld, CollectionState, ItemClassification, Item
|
||||
from worlds.alttp.Items import ItemFactory
|
||||
from worlds.alttp.Items import item_factory
|
||||
|
||||
|
||||
class TestBase(unittest.TestCase):
|
||||
@@ -91,15 +91,15 @@ class TestBase(unittest.TestCase):
|
||||
items = self.multiworld.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)]
|
||||
items.extend(ItemFactory(item_pool[0], 1))
|
||||
items.extend(item_factory(item_pool[0], self.multiworld.worlds[1]))
|
||||
else:
|
||||
items = ItemFactory(item_pool[0], 1)
|
||||
items = item_factory(item_pool[0], self.multiworld.worlds[1])
|
||||
return self.get_state(items)
|
||||
|
||||
def _get_items_partial(self, item_pool, missing_item):
|
||||
new_items = item_pool[0].copy()
|
||||
new_items.remove(missing_item)
|
||||
items = ItemFactory(new_items, 1)
|
||||
items = item_factory(new_items, self.multiworld.worlds[1])
|
||||
return self.get_state(items)
|
||||
|
||||
|
||||
|
||||
@@ -1,28 +1,50 @@
|
||||
from argparse import Namespace
|
||||
from typing import Type, Tuple
|
||||
from typing import List, Optional, Tuple, Type, Union
|
||||
|
||||
from BaseClasses import MultiWorld, CollectionState
|
||||
from worlds.AutoWorld import call_all, World
|
||||
from BaseClasses import CollectionState, MultiWorld
|
||||
from worlds.AutoWorld import World, call_all
|
||||
|
||||
gen_steps = ("generate_early", "create_regions", "create_items", "set_rules", "generate_basic", "pre_fill")
|
||||
|
||||
|
||||
def setup_solo_multiworld(world_type: Type[World], steps: Tuple[str, ...] = gen_steps) -> MultiWorld:
|
||||
def setup_solo_multiworld(
|
||||
world_type: Type[World], steps: Tuple[str, ...] = gen_steps, seed: Optional[int] = None
|
||||
) -> MultiWorld:
|
||||
"""
|
||||
Creates a multiworld with a single player of `world_type`, sets default options, and calls provided gen steps.
|
||||
|
||||
:param world_type: Type of the world to generate a multiworld for
|
||||
:param steps: The gen steps that should be called on the generated multiworld before returning. Default calls
|
||||
steps through pre_fill
|
||||
:param seed: The seed to be used when creating this multiworld
|
||||
"""
|
||||
multiworld = MultiWorld(1)
|
||||
multiworld.game[1] = world_type.game
|
||||
multiworld.player_name = {1: "Tester"}
|
||||
multiworld.set_seed()
|
||||
return setup_multiworld(world_type, steps, seed)
|
||||
|
||||
|
||||
def setup_multiworld(worlds: Union[List[Type[World]], Type[World]], steps: Tuple[str, ...] = gen_steps,
|
||||
seed: Optional[int] = None) -> MultiWorld:
|
||||
"""
|
||||
Creates a multiworld with a player for each provided world type, allowing duplicates, setting default options, and
|
||||
calling the provided gen steps.
|
||||
|
||||
:param worlds: type/s of worlds to generate a multiworld for
|
||||
:param steps: gen steps that should be called before returning. Default calls through pre_fill
|
||||
:param seed: The seed to be used when creating this multiworld
|
||||
"""
|
||||
if not isinstance(worlds, list):
|
||||
worlds = [worlds]
|
||||
players = len(worlds)
|
||||
multiworld = MultiWorld(players)
|
||||
multiworld.game = {player: world_type.game for player, world_type in enumerate(worlds, 1)}
|
||||
multiworld.player_name = {player: f"Tester{player}" for player in multiworld.player_ids}
|
||||
multiworld.set_seed(seed)
|
||||
multiworld.state = CollectionState(multiworld)
|
||||
args = Namespace()
|
||||
for name, option in world_type.options_dataclass.type_hints.items():
|
||||
setattr(args, name, {1: option.from_any(option.default)})
|
||||
for player, world_type in enumerate(worlds, 1):
|
||||
for key, option in world_type.options_dataclass.type_hints.items():
|
||||
updated_options = getattr(args, key, {})
|
||||
updated_options[player] = option.from_any(option.default)
|
||||
setattr(args, key, updated_options)
|
||||
multiworld.set_options(args)
|
||||
for step in steps:
|
||||
call_all(multiworld, step)
|
||||
|
||||
@@ -13,6 +13,7 @@ from worlds.generic.Rules import CollectionRule, add_item_rule, locality_rules,
|
||||
|
||||
def generate_multiworld(players: int = 1) -> MultiWorld:
|
||||
multiworld = MultiWorld(players)
|
||||
multiworld.set_seed(0)
|
||||
multiworld.player_name = {}
|
||||
multiworld.state = CollectionState(multiworld)
|
||||
for i in range(players):
|
||||
@@ -32,8 +33,6 @@ def generate_multiworld(players: int = 1) -> MultiWorld:
|
||||
world.options = world.options_dataclass(**{option_key: getattr(multiworld, option_key)[player_id]
|
||||
for option_key in world.options_dataclass.type_hints})
|
||||
|
||||
multiworld.set_seed(0)
|
||||
|
||||
return multiworld
|
||||
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ class TestBase(unittest.TestCase):
|
||||
def test_create_item(self):
|
||||
"""Test that a world can successfully create all items in its datapackage"""
|
||||
for game_name, world_type in AutoWorldRegister.world_types.items():
|
||||
proxy_world = world_type(None, 0) # this is identical to MultiServer.py creating worlds
|
||||
proxy_world = setup_solo_multiworld(world_type, ()).worlds[1]
|
||||
for item_name in world_type.item_name_to_id:
|
||||
with self.subTest("Create Item", item_name=item_name, game_name=game_name):
|
||||
item = proxy_world.create_item(item_name)
|
||||
@@ -23,8 +23,8 @@ class TestBase(unittest.TestCase):
|
||||
{"Pendants", "Crystals"},
|
||||
"Ocarina of Time":
|
||||
{"medallions", "stones", "rewards", "logic_bottles"},
|
||||
"Starcraft 2 Wings of Liberty":
|
||||
{"Missions"},
|
||||
"Starcraft 2":
|
||||
{"Missions", "WoL Missions"},
|
||||
}
|
||||
for game_name, world_type in AutoWorldRegister.world_types.items():
|
||||
with self.subTest(game_name, game_name=game_name):
|
||||
|
||||
39
test/general/test_player_options.py
Normal file
39
test/general/test_player_options.py
Normal file
@@ -0,0 +1,39 @@
|
||||
import unittest
|
||||
import Generate
|
||||
|
||||
|
||||
class TestPlayerOptions(unittest.TestCase):
|
||||
|
||||
def test_update_weights(self):
|
||||
original_weights = {
|
||||
"scalar_1": 50,
|
||||
"scalar_2": 25,
|
||||
"list_1": ["string"],
|
||||
"dict_1": {"option_a": 50, "option_b": 50},
|
||||
"dict_2": {"option_f": 50},
|
||||
"set_1": {"option_c"}
|
||||
}
|
||||
|
||||
# test that we don't allow +merge syntax on scalar variables
|
||||
with self.assertRaises(BaseException):
|
||||
Generate.update_weights(original_weights, {"+scalar_1": 0}, "Tested", "")
|
||||
|
||||
new_weights = Generate.update_weights(original_weights, {"scalar_2": 0,
|
||||
"+list_1": ["string_2"],
|
||||
"+dict_1": {"option_b": 0, "option_c": 50},
|
||||
"+set_1": {"option_c", "option_d"},
|
||||
"dict_2": {"option_g": 50},
|
||||
"+list_2": ["string_3"]},
|
||||
"Tested", "")
|
||||
|
||||
self.assertEqual(new_weights["scalar_1"], 50)
|
||||
self.assertEqual(new_weights["scalar_2"], 0)
|
||||
self.assertEqual(new_weights["list_2"], ["string_3"])
|
||||
self.assertEqual(new_weights["list_1"], ["string", "string_2"])
|
||||
self.assertEqual(new_weights["dict_1"]["option_a"], 50)
|
||||
self.assertEqual(new_weights["dict_1"]["option_b"], 0)
|
||||
self.assertEqual(new_weights["dict_1"]["option_c"], 50)
|
||||
self.assertNotIn("option_f", new_weights["dict_2"])
|
||||
self.assertEqual(new_weights["dict_2"]["option_g"], 50)
|
||||
self.assertEqual(len(new_weights["set_1"]), 2)
|
||||
self.assertIn("option_d", new_weights["set_1"])
|
||||
0
test/multiworld/__init__.py
Normal file
0
test/multiworld/__init__.py
Normal file
77
test/multiworld/test_multiworlds.py
Normal file
77
test/multiworld/test_multiworlds.py
Normal file
@@ -0,0 +1,77 @@
|
||||
import unittest
|
||||
from typing import List, Tuple
|
||||
from unittest import TestCase
|
||||
|
||||
from BaseClasses import CollectionState, Location, MultiWorld
|
||||
from Fill import distribute_items_restrictive
|
||||
from Options import Accessibility
|
||||
from worlds.AutoWorld import AutoWorldRegister, call_all, call_single
|
||||
from ..general import gen_steps, setup_multiworld
|
||||
|
||||
|
||||
class MultiworldTestBase(TestCase):
|
||||
multiworld: MultiWorld
|
||||
|
||||
# similar to the implementation in WorldTestBase.test_fill
|
||||
# but for multiple players and doesn't allow minimal accessibility
|
||||
def fulfills_accessibility(self) -> bool:
|
||||
"""
|
||||
Checks that the multiworld satisfies locations accessibility requirements, failing if all locations are cleared
|
||||
but not beatable, or some locations are unreachable.
|
||||
"""
|
||||
locations = [loc for loc in self.multiworld.get_locations()]
|
||||
state = CollectionState(self.multiworld)
|
||||
while locations:
|
||||
sphere: List[Location] = []
|
||||
for n in range(len(locations) - 1, -1, -1):
|
||||
if locations[n].can_reach(state):
|
||||
sphere.append(locations.pop(n))
|
||||
self.assertTrue(sphere, f"Unreachable locations: {locations}")
|
||||
if not sphere:
|
||||
return False
|
||||
for location in sphere:
|
||||
if location.item:
|
||||
state.collect(location.item, True, location)
|
||||
return self.multiworld.has_beaten_game(state, 1)
|
||||
|
||||
def assertSteps(self, steps: Tuple[str, ...]) -> None:
|
||||
"""Calls each step individually, continuing if a step for a specific world step fails."""
|
||||
world_types = {world.__class__ for world in self.multiworld.worlds.values()}
|
||||
for step in steps:
|
||||
for player, world in self.multiworld.worlds.items():
|
||||
with self.subTest(game=world.game, step=step):
|
||||
call_single(self.multiworld, step, player)
|
||||
for world_type in sorted(world_types, key=lambda world: world.__name__):
|
||||
with self.subTest(game=world_type.game, step=f"stage_{step}"):
|
||||
stage_callable = getattr(world_type, f"stage_{step}", None)
|
||||
if stage_callable:
|
||||
stage_callable(self.multiworld)
|
||||
|
||||
|
||||
@unittest.skip("too slow for main")
|
||||
class TestAllGamesMultiworld(MultiworldTestBase):
|
||||
def test_fills(self) -> None:
|
||||
"""Tests that a multiworld with one of every registered game world can generate."""
|
||||
all_worlds = list(AutoWorldRegister.world_types.values())
|
||||
self.multiworld = setup_multiworld(all_worlds, ())
|
||||
for world in self.multiworld.worlds.values():
|
||||
world.options.accessibility.value = Accessibility.option_locations
|
||||
self.assertSteps(gen_steps)
|
||||
with self.subTest("filling multiworld", seed=self.multiworld.seed):
|
||||
distribute_items_restrictive(self.multiworld)
|
||||
call_all(self.multiworld, "post_fill")
|
||||
self.assertTrue(self.fulfills_accessibility(), "Collected all locations, but can't beat the game")
|
||||
|
||||
|
||||
class TestTwoPlayerMulti(MultiworldTestBase):
|
||||
def test_two_player_single_game_fills(self) -> None:
|
||||
"""Tests that a multiworld of two players for each registered game world can generate."""
|
||||
for world in AutoWorldRegister.world_types.values():
|
||||
self.multiworld = setup_multiworld([world, world], ())
|
||||
for world in self.multiworld.worlds.values():
|
||||
world.options.accessibility.value = Accessibility.option_locations
|
||||
self.assertSteps(gen_steps)
|
||||
with self.subTest("filling multiworld", seed=self.multiworld.seed):
|
||||
distribute_items_restrictive(self.multiworld)
|
||||
call_all(self.multiworld, "post_fill")
|
||||
self.assertTrue(self.fulfills_accessibility(), "Collected all locations, but can't beat the game")
|
||||
Reference in New Issue
Block a user