mirror of
https://github.com/ArchipelagoMW/Archipelago.git
synced 2026-04-03 13:43:43 -07:00
Merge remote-tracking branch 'Main/main' into rework_accessibility
# Conflicts: # test/general/test_fill.py
This commit is contained in:
@@ -285,7 +285,7 @@ class WorldTestBase(unittest.TestCase):
|
||||
if not (self.run_default_tests and self.constructed):
|
||||
return
|
||||
with self.subTest("Game", game=self.game):
|
||||
excluded = self.multiworld.exclude_locations[1].value
|
||||
excluded = self.multiworld.worlds[1].options.exclude_locations.value
|
||||
state = self.multiworld.get_all_state(False)
|
||||
for location in self.multiworld.get_locations():
|
||||
if location.name not in excluded:
|
||||
|
||||
7
test/benchmark/__init__.py
Normal file
7
test/benchmark/__init__.py
Normal file
@@ -0,0 +1,7 @@
|
||||
if __name__ == "__main__":
|
||||
import path_change
|
||||
path_change.change_home()
|
||||
import load_worlds
|
||||
load_worlds.run_load_worlds_benchmark()
|
||||
import locations
|
||||
locations.run_locations_benchmark()
|
||||
27
test/benchmark/load_worlds.py
Normal file
27
test/benchmark/load_worlds.py
Normal file
@@ -0,0 +1,27 @@
|
||||
def run_load_worlds_benchmark():
|
||||
"""List worlds and their load time.
|
||||
Note that any first-time imports will be attributed to that world, as it is cached afterwards.
|
||||
Likely best used with isolated worlds to measure their time alone."""
|
||||
import logging
|
||||
|
||||
from Utils import init_logging
|
||||
|
||||
# get some general imports cached, to prevent it from being attributed to one world.
|
||||
import orjson
|
||||
orjson.loads("{}") # orjson runs initialization on first use
|
||||
|
||||
import BaseClasses, Launcher, Fill
|
||||
|
||||
from worlds import world_sources
|
||||
|
||||
init_logging("Benchmark Runner")
|
||||
logger = logging.getLogger("Benchmark")
|
||||
|
||||
for module in world_sources:
|
||||
logger.info(f"{module} took {module.time_taken:.4f} seconds.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from path_change import change_home
|
||||
change_home()
|
||||
run_load_worlds_benchmark()
|
||||
101
test/benchmark/locations.py
Normal file
101
test/benchmark/locations.py
Normal file
@@ -0,0 +1,101 @@
|
||||
def run_locations_benchmark():
|
||||
import argparse
|
||||
import logging
|
||||
import gc
|
||||
import collections
|
||||
import typing
|
||||
import sys
|
||||
|
||||
from time_it import TimeIt
|
||||
|
||||
from Utils import init_logging
|
||||
from BaseClasses import MultiWorld, CollectionState, Location
|
||||
from worlds import AutoWorld
|
||||
from worlds.AutoWorld import call_all
|
||||
|
||||
init_logging("Benchmark Runner")
|
||||
logger = logging.getLogger("Benchmark")
|
||||
|
||||
class BenchmarkRunner:
|
||||
gen_steps: typing.Tuple[str, ...] = (
|
||||
"generate_early", "create_regions", "create_items", "set_rules", "generate_basic", "pre_fill")
|
||||
rule_iterations: int = 100_000
|
||||
|
||||
if sys.version_info >= (3, 9):
|
||||
@staticmethod
|
||||
def format_times_from_counter(counter: collections.Counter[str], top: int = 5) -> str:
|
||||
return "\n".join(f" {time:.4f} in {name}" for name, time in counter.most_common(top))
|
||||
else:
|
||||
@staticmethod
|
||||
def format_times_from_counter(counter: collections.Counter, top: int = 5) -> str:
|
||||
return "\n".join(f" {time:.4f} in {name}" for name, time in counter.most_common(top))
|
||||
|
||||
def location_test(self, test_location: Location, state: CollectionState, state_name: str) -> float:
|
||||
with TimeIt(f"{test_location.game} {self.rule_iterations} "
|
||||
f"runs of {test_location}.access_rule({state_name})", logger) as t:
|
||||
for _ in range(self.rule_iterations):
|
||||
test_location.access_rule(state)
|
||||
# if time is taken to disentangle complex ref chains,
|
||||
# this time should be attributed to the rule.
|
||||
gc.collect()
|
||||
return t.dif
|
||||
|
||||
def main(self):
|
||||
for game in sorted(AutoWorld.AutoWorldRegister.world_types):
|
||||
summary_data: typing.Dict[str, collections.Counter[str]] = {
|
||||
"empty_state": collections.Counter(),
|
||||
"all_state": collections.Counter(),
|
||||
}
|
||||
try:
|
||||
multiworld = MultiWorld(1)
|
||||
multiworld.game[1] = game
|
||||
multiworld.player_name = {1: "Tester"}
|
||||
multiworld.set_seed(0)
|
||||
multiworld.state = CollectionState(multiworld)
|
||||
args = argparse.Namespace()
|
||||
for name, option in AutoWorld.AutoWorldRegister.world_types[game].options_dataclass.type_hints.items():
|
||||
setattr(args, name, {
|
||||
1: option.from_any(getattr(option, "default"))
|
||||
})
|
||||
multiworld.set_options(args)
|
||||
|
||||
gc.collect()
|
||||
for step in self.gen_steps:
|
||||
with TimeIt(f"{game} step {step}", logger):
|
||||
call_all(multiworld, step)
|
||||
gc.collect()
|
||||
|
||||
locations = sorted(multiworld.get_unfilled_locations())
|
||||
if not locations:
|
||||
continue
|
||||
|
||||
all_state = multiworld.get_all_state(False)
|
||||
for location in locations:
|
||||
time_taken = self.location_test(location, multiworld.state, "empty_state")
|
||||
summary_data["empty_state"][location.name] = time_taken
|
||||
|
||||
time_taken = self.location_test(location, all_state, "all_state")
|
||||
summary_data["all_state"][location.name] = time_taken
|
||||
|
||||
total_empty_state = sum(summary_data["empty_state"].values())
|
||||
total_all_state = sum(summary_data["all_state"].values())
|
||||
|
||||
logger.info(f"{game} took {total_empty_state/len(locations):.4f} "
|
||||
f"seconds per location in empty_state and {total_all_state/len(locations):.4f} "
|
||||
f"in all_state. (all times summed for {self.rule_iterations} runs.)")
|
||||
logger.info(f"Top times in empty_state:\n"
|
||||
f"{self.format_times_from_counter(summary_data['empty_state'])}")
|
||||
logger.info(f"Top times in all_state:\n"
|
||||
f"{self.format_times_from_counter(summary_data['all_state'])}")
|
||||
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
|
||||
runner = BenchmarkRunner()
|
||||
runner.main()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from path_change import change_home
|
||||
change_home()
|
||||
run_locations_benchmark()
|
||||
16
test/benchmark/path_change.py
Normal file
16
test/benchmark/path_change.py
Normal file
@@ -0,0 +1,16 @@
|
||||
import sys
|
||||
import os
|
||||
|
||||
|
||||
def change_home():
|
||||
"""Allow scripts to run from "this" folder."""
|
||||
old_home = os.path.dirname(__file__)
|
||||
sys.path.remove(old_home)
|
||||
new_home = os.path.normpath(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir))
|
||||
os.chdir(new_home)
|
||||
sys.path.append(new_home)
|
||||
# fallback to local import
|
||||
sys.path.append(old_home)
|
||||
|
||||
from Utils import local_path
|
||||
local_path.cached_path = new_home
|
||||
23
test/benchmark/time_it.py
Normal file
23
test/benchmark/time_it.py
Normal file
@@ -0,0 +1,23 @@
|
||||
import time
|
||||
|
||||
|
||||
class TimeIt:
|
||||
def __init__(self, name: str, time_logger=None):
|
||||
self.name = name
|
||||
self.logger = time_logger
|
||||
self.timer = None
|
||||
self.end_timer = None
|
||||
|
||||
def __enter__(self):
|
||||
self.timer = time.perf_counter()
|
||||
return self
|
||||
|
||||
@property
|
||||
def dif(self):
|
||||
return self.end_timer - self.timer
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
if not self.end_timer:
|
||||
self.end_timer = time.perf_counter()
|
||||
if self.logger:
|
||||
self.logger.info(f"{self.dif:.4f} seconds in {self.name}.")
|
||||
@@ -11,30 +11,30 @@ from BaseClasses import Entrance, LocationProgressType, MultiWorld, Region, Item
|
||||
from worlds.generic.Rules import CollectionRule, add_item_rule, locality_rules, set_rule
|
||||
|
||||
|
||||
def generate_multi_world(players: int = 1) -> MultiWorld:
|
||||
multi_world = MultiWorld(players)
|
||||
multi_world.player_name = {}
|
||||
multi_world.state = CollectionState(multi_world)
|
||||
def generate_multiworld(players: int = 1) -> MultiWorld:
|
||||
multiworld = MultiWorld(players)
|
||||
multiworld.player_name = {}
|
||||
multiworld.state = CollectionState(multiworld)
|
||||
for i in range(players):
|
||||
player_id = i+1
|
||||
world = World(multi_world, player_id)
|
||||
multi_world.game[player_id] = f"Game {player_id}"
|
||||
multi_world.worlds[player_id] = world
|
||||
multi_world.player_name[player_id] = "Test Player " + str(player_id)
|
||||
region = Region("Menu", player_id, multi_world, "Menu Region Hint")
|
||||
multi_world.regions.append(region)
|
||||
world = World(multiworld, player_id)
|
||||
multiworld.game[player_id] = f"Game {player_id}"
|
||||
multiworld.worlds[player_id] = world
|
||||
multiworld.player_name[player_id] = "Test Player " + str(player_id)
|
||||
region = Region("Menu", player_id, multiworld, "Menu Region Hint")
|
||||
multiworld.regions.append(region)
|
||||
for option_key, option in Options.PerGameCommonOptions.type_hints.items():
|
||||
if hasattr(multi_world, option_key):
|
||||
getattr(multi_world, option_key).setdefault(player_id, option.from_any(getattr(option, "default")))
|
||||
if hasattr(multiworld, option_key):
|
||||
getattr(multiworld, option_key).setdefault(player_id, option.from_any(getattr(option, "default")))
|
||||
else:
|
||||
setattr(multi_world, option_key, {player_id: option.from_any(getattr(option, "default"))})
|
||||
setattr(multiworld, option_key, {player_id: option.from_any(getattr(option, "default"))})
|
||||
# TODO - remove this loop once all worlds use options dataclasses
|
||||
world.options = world.options_dataclass(**{option_key: getattr(multi_world, option_key)[player_id]
|
||||
world.options = world.options_dataclass(**{option_key: getattr(multiworld, option_key)[player_id]
|
||||
for option_key in world.options_dataclass.type_hints})
|
||||
|
||||
multi_world.set_seed(0)
|
||||
multiworld.set_seed(0)
|
||||
|
||||
return multi_world
|
||||
return multiworld
|
||||
|
||||
|
||||
class PlayerDefinition(object):
|
||||
@@ -46,8 +46,8 @@ class PlayerDefinition(object):
|
||||
basic_items: List[Item]
|
||||
regions: List[Region]
|
||||
|
||||
def __init__(self, world: MultiWorld, id: int, menu: Region, locations: List[Location] = [], prog_items: List[Item] = [], basic_items: List[Item] = []):
|
||||
self.multiworld = world
|
||||
def __init__(self, multiworld: MultiWorld, id: int, menu: Region, locations: List[Location] = [], prog_items: List[Item] = [], basic_items: List[Item] = []):
|
||||
self.multiworld = multiworld
|
||||
self.id = id
|
||||
self.menu = menu
|
||||
self.locations = locations
|
||||
@@ -72,7 +72,7 @@ class PlayerDefinition(object):
|
||||
return region
|
||||
|
||||
|
||||
def fill_region(world: MultiWorld, region: Region, items: List[Item]) -> List[Item]:
|
||||
def fill_region(multiworld: MultiWorld, region: Region, items: List[Item]) -> List[Item]:
|
||||
items = items.copy()
|
||||
while len(items) > 0:
|
||||
location = region.locations.pop(0)
|
||||
@@ -80,7 +80,7 @@ def fill_region(world: MultiWorld, region: Region, items: List[Item]) -> List[It
|
||||
if location.item:
|
||||
return items
|
||||
item = items.pop(0)
|
||||
world.push_item(location, item, False)
|
||||
multiworld.push_item(location, item, False)
|
||||
location.event = item.advancement
|
||||
|
||||
return items
|
||||
@@ -94,15 +94,15 @@ def region_contains(region: Region, item: Item) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def generate_player_data(multi_world: MultiWorld, player_id: int, location_count: int = 0, prog_item_count: int = 0, basic_item_count: int = 0) -> PlayerDefinition:
|
||||
menu = multi_world.get_region("Menu", player_id)
|
||||
def generate_player_data(multiworld: MultiWorld, player_id: int, location_count: int = 0, prog_item_count: int = 0, basic_item_count: int = 0) -> PlayerDefinition:
|
||||
menu = multiworld.get_region("Menu", player_id)
|
||||
locations = generate_locations(location_count, player_id, None, menu)
|
||||
prog_items = generate_items(prog_item_count, player_id, True)
|
||||
multi_world.itempool += prog_items
|
||||
multiworld.itempool += prog_items
|
||||
basic_items = generate_items(basic_item_count, player_id, False)
|
||||
multi_world.itempool += basic_items
|
||||
multiworld.itempool += basic_items
|
||||
|
||||
return PlayerDefinition(multi_world, player_id, menu, locations, prog_items, basic_items)
|
||||
return PlayerDefinition(multiworld, player_id, menu, locations, prog_items, basic_items)
|
||||
|
||||
|
||||
def generate_locations(count: int, player_id: int, address: int = None, region: Region = None, tag: str = "") -> List[Location]:
|
||||
@@ -134,15 +134,15 @@ def names(objs: list) -> Iterable[str]:
|
||||
class TestFillRestrictive(unittest.TestCase):
|
||||
def test_basic_fill(self):
|
||||
"""Tests `fill_restrictive` fills and removes the locations and items from their respective lists"""
|
||||
multi_world = generate_multi_world()
|
||||
player1 = generate_player_data(multi_world, 1, 2, 2)
|
||||
multiworld = generate_multiworld()
|
||||
player1 = generate_player_data(multiworld, 1, 2, 2)
|
||||
|
||||
item0 = player1.prog_items[0]
|
||||
item1 = player1.prog_items[1]
|
||||
loc0 = player1.locations[0]
|
||||
loc1 = player1.locations[1]
|
||||
|
||||
fill_restrictive(multi_world, multi_world.state,
|
||||
fill_restrictive(multiworld, multiworld.state,
|
||||
player1.locations, player1.prog_items)
|
||||
|
||||
self.assertEqual(loc0.item, item1)
|
||||
@@ -152,16 +152,16 @@ class TestFillRestrictive(unittest.TestCase):
|
||||
|
||||
def test_ordered_fill(self):
|
||||
"""Tests `fill_restrictive` fulfills set rules"""
|
||||
multi_world = generate_multi_world()
|
||||
player1 = generate_player_data(multi_world, 1, 2, 2)
|
||||
multiworld = generate_multiworld()
|
||||
player1 = generate_player_data(multiworld, 1, 2, 2)
|
||||
items = player1.prog_items
|
||||
locations = player1.locations
|
||||
|
||||
multi_world.completion_condition[player1.id] = lambda state: state.has(
|
||||
multiworld.completion_condition[player1.id] = lambda state: state.has(
|
||||
items[0].name, player1.id) and state.has(items[1].name, player1.id)
|
||||
set_rule(locations[1], lambda state: state.has(
|
||||
items[0].name, player1.id))
|
||||
fill_restrictive(multi_world, multi_world.state,
|
||||
fill_restrictive(multiworld, multiworld.state,
|
||||
player1.locations.copy(), player1.prog_items.copy())
|
||||
|
||||
self.assertEqual(locations[0].item, items[0])
|
||||
@@ -169,8 +169,8 @@ class TestFillRestrictive(unittest.TestCase):
|
||||
|
||||
def test_partial_fill(self):
|
||||
"""Tests that `fill_restrictive` returns unfilled locations"""
|
||||
multi_world = generate_multi_world()
|
||||
player1 = generate_player_data(multi_world, 1, 3, 2)
|
||||
multiworld = generate_multiworld()
|
||||
player1 = generate_player_data(multiworld, 1, 3, 2)
|
||||
|
||||
item0 = player1.prog_items[0]
|
||||
item1 = player1.prog_items[1]
|
||||
@@ -178,14 +178,14 @@ class TestFillRestrictive(unittest.TestCase):
|
||||
loc1 = player1.locations[1]
|
||||
loc2 = player1.locations[2]
|
||||
|
||||
multi_world.completion_condition[player1.id] = lambda state: state.has(
|
||||
multiworld.completion_condition[player1.id] = lambda state: state.has(
|
||||
item0.name, player1.id) and state.has(item1.name, player1.id)
|
||||
set_rule(loc1, lambda state: state.has(
|
||||
item0.name, player1.id))
|
||||
# forces a swap
|
||||
set_rule(loc2, lambda state: state.has(
|
||||
item0.name, player1.id))
|
||||
fill_restrictive(multi_world, multi_world.state,
|
||||
fill_restrictive(multiworld, multiworld.state,
|
||||
player1.locations, player1.prog_items)
|
||||
|
||||
self.assertEqual(loc0.item, item0)
|
||||
@@ -195,19 +195,19 @@ class TestFillRestrictive(unittest.TestCase):
|
||||
|
||||
def test_minimal_fill(self):
|
||||
"""Test that fill for minimal player can have unreachable items"""
|
||||
multi_world = generate_multi_world()
|
||||
player1 = generate_player_data(multi_world, 1, 2, 2)
|
||||
multiworld = generate_multiworld()
|
||||
player1 = generate_player_data(multiworld, 1, 2, 2)
|
||||
|
||||
items = player1.prog_items
|
||||
locations = player1.locations
|
||||
|
||||
multi_world.worlds[player1.id].options.accessibility = Accessibility.from_any(Accessibility.option_minimal)
|
||||
multi_world.completion_condition[player1.id] = lambda state: state.has(
|
||||
multiworld.worlds[player1.id].options.accessibility = Accessibility.from_any(Accessibility.option_minimal)
|
||||
multiworld.completion_condition[player1.id] = lambda state: state.has(
|
||||
items[1].name, player1.id)
|
||||
set_rule(locations[1], lambda state: state.has(
|
||||
items[0].name, player1.id))
|
||||
|
||||
fill_restrictive(multi_world, multi_world.state,
|
||||
fill_restrictive(multiworld, multiworld.state,
|
||||
player1.locations.copy(), player1.prog_items.copy())
|
||||
|
||||
self.assertEqual(locations[0].item, items[1])
|
||||
@@ -220,15 +220,15 @@ class TestFillRestrictive(unittest.TestCase):
|
||||
the non-minimal player get all items.
|
||||
"""
|
||||
|
||||
multi_world = generate_multi_world(2)
|
||||
player1 = generate_player_data(multi_world, 1, 3, 3)
|
||||
player2 = generate_player_data(multi_world, 2, 3, 3)
|
||||
multiworld = generate_multiworld(2)
|
||||
player1 = generate_player_data(multiworld, 1, 3, 3)
|
||||
player2 = generate_player_data(multiworld, 2, 3, 3)
|
||||
|
||||
multi_world.accessibility[player1.id].value = multi_world.accessibility[player1.id].option_minimal
|
||||
multi_world.accessibility[player2.id].value = multi_world.accessibility[player2.id].option_full
|
||||
multiworld.worlds[player1.id].options.accessibility.value = Accessibility.option_minimal
|
||||
multiworld.worlds[player2.id].options.accessibility.value = Accessibility.option_locations
|
||||
|
||||
multi_world.completion_condition[player1.id] = lambda state: True
|
||||
multi_world.completion_condition[player2.id] = lambda state: state.has(player2.prog_items[2].name, player2.id)
|
||||
multiworld.completion_condition[player1.id] = lambda state: True
|
||||
multiworld.completion_condition[player2.id] = lambda state: state.has(player2.prog_items[2].name, player2.id)
|
||||
|
||||
set_rule(player1.locations[1], lambda state: state.has(player1.prog_items[0].name, player1.id))
|
||||
set_rule(player1.locations[2], lambda state: state.has(player1.prog_items[1].name, player1.id))
|
||||
@@ -241,28 +241,28 @@ class TestFillRestrictive(unittest.TestCase):
|
||||
# fill remaining locations with remaining items
|
||||
location_pool = player1.locations[1:] + player2.locations
|
||||
item_pool = player1.prog_items[:-1] + player2.prog_items
|
||||
fill_restrictive(multi_world, multi_world.state, location_pool, item_pool)
|
||||
multi_world.state.sweep_for_events() # collect everything
|
||||
fill_restrictive(multiworld, multiworld.state, location_pool, item_pool)
|
||||
multiworld.state.sweep_for_events() # collect everything
|
||||
|
||||
# all of player2's locations and items should be accessible (not all of player1's)
|
||||
for item in player2.prog_items:
|
||||
self.assertTrue(multi_world.state.has(item.name, player2.id),
|
||||
self.assertTrue(multiworld.state.has(item.name, player2.id),
|
||||
f'{item} is unreachable in {item.location}')
|
||||
|
||||
def test_reversed_fill(self):
|
||||
"""Test a different set of rules can be satisfied"""
|
||||
multi_world = generate_multi_world()
|
||||
player1 = generate_player_data(multi_world, 1, 2, 2)
|
||||
multiworld = generate_multiworld()
|
||||
player1 = generate_player_data(multiworld, 1, 2, 2)
|
||||
|
||||
item0 = player1.prog_items[0]
|
||||
item1 = player1.prog_items[1]
|
||||
loc0 = player1.locations[0]
|
||||
loc1 = player1.locations[1]
|
||||
|
||||
multi_world.completion_condition[player1.id] = lambda state: state.has(
|
||||
multiworld.completion_condition[player1.id] = lambda state: state.has(
|
||||
item0.name, player1.id) and state.has(item1.name, player1.id)
|
||||
set_rule(loc1, lambda state: state.has(item1.name, player1.id))
|
||||
fill_restrictive(multi_world, multi_world.state,
|
||||
fill_restrictive(multiworld, multiworld.state,
|
||||
player1.locations, player1.prog_items)
|
||||
|
||||
self.assertEqual(loc0.item, item1)
|
||||
@@ -270,13 +270,13 @@ class TestFillRestrictive(unittest.TestCase):
|
||||
|
||||
def test_multi_step_fill(self):
|
||||
"""Test that fill is able to satisfy multiple spheres"""
|
||||
multi_world = generate_multi_world()
|
||||
player1 = generate_player_data(multi_world, 1, 4, 4)
|
||||
multiworld = generate_multiworld()
|
||||
player1 = generate_player_data(multiworld, 1, 4, 4)
|
||||
|
||||
items = player1.prog_items
|
||||
locations = player1.locations
|
||||
|
||||
multi_world.completion_condition[player1.id] = lambda state: state.has(
|
||||
multiworld.completion_condition[player1.id] = lambda state: state.has(
|
||||
items[2].name, player1.id) and state.has(items[3].name, player1.id)
|
||||
set_rule(locations[1], lambda state: state.has(
|
||||
items[0].name, player1.id))
|
||||
@@ -285,7 +285,7 @@ class TestFillRestrictive(unittest.TestCase):
|
||||
set_rule(locations[3], lambda state: state.has(
|
||||
items[1].name, player1.id))
|
||||
|
||||
fill_restrictive(multi_world, multi_world.state,
|
||||
fill_restrictive(multiworld, multiworld.state,
|
||||
player1.locations.copy(), player1.prog_items.copy())
|
||||
|
||||
self.assertEqual(locations[0].item, items[1])
|
||||
@@ -295,25 +295,25 @@ class TestFillRestrictive(unittest.TestCase):
|
||||
|
||||
def test_impossible_fill(self):
|
||||
"""Test that fill raises an error when it can't place any items"""
|
||||
multi_world = generate_multi_world()
|
||||
player1 = generate_player_data(multi_world, 1, 2, 2)
|
||||
multiworld = generate_multiworld()
|
||||
player1 = generate_player_data(multiworld, 1, 2, 2)
|
||||
items = player1.prog_items
|
||||
locations = player1.locations
|
||||
|
||||
multi_world.completion_condition[player1.id] = lambda state: state.has(
|
||||
multiworld.completion_condition[player1.id] = lambda state: state.has(
|
||||
items[0].name, player1.id) and state.has(items[1].name, player1.id)
|
||||
set_rule(locations[1], lambda state: state.has(
|
||||
items[1].name, player1.id))
|
||||
set_rule(locations[0], lambda state: state.has(
|
||||
items[0].name, player1.id))
|
||||
|
||||
self.assertRaises(FillError, fill_restrictive, multi_world, multi_world.state,
|
||||
self.assertRaises(FillError, fill_restrictive, multiworld, multiworld.state,
|
||||
player1.locations.copy(), player1.prog_items.copy())
|
||||
|
||||
def test_circular_fill(self):
|
||||
"""Test that fill raises an error when it can't place all items"""
|
||||
multi_world = generate_multi_world()
|
||||
player1 = generate_player_data(multi_world, 1, 3, 3)
|
||||
multiworld = generate_multiworld()
|
||||
player1 = generate_player_data(multiworld, 1, 3, 3)
|
||||
|
||||
item0 = player1.prog_items[0]
|
||||
item1 = player1.prog_items[1]
|
||||
@@ -322,46 +322,46 @@ class TestFillRestrictive(unittest.TestCase):
|
||||
loc1 = player1.locations[1]
|
||||
loc2 = player1.locations[2]
|
||||
|
||||
multi_world.completion_condition[player1.id] = lambda state: state.has(
|
||||
multiworld.completion_condition[player1.id] = lambda state: state.has(
|
||||
item0.name, player1.id) and state.has(item1.name, player1.id) and state.has(item2.name, player1.id)
|
||||
set_rule(loc1, lambda state: state.has(item0.name, player1.id))
|
||||
set_rule(loc2, lambda state: state.has(item1.name, player1.id))
|
||||
set_rule(loc0, lambda state: state.has(item2.name, player1.id))
|
||||
|
||||
self.assertRaises(FillError, fill_restrictive, multi_world, multi_world.state,
|
||||
self.assertRaises(FillError, fill_restrictive, multiworld, multiworld.state,
|
||||
player1.locations.copy(), player1.prog_items.copy())
|
||||
|
||||
def test_competing_fill(self):
|
||||
"""Test that fill raises an error when it can't place items in a way to satisfy the conditions"""
|
||||
multi_world = generate_multi_world()
|
||||
player1 = generate_player_data(multi_world, 1, 2, 2)
|
||||
multiworld = generate_multiworld()
|
||||
player1 = generate_player_data(multiworld, 1, 2, 2)
|
||||
|
||||
item0 = player1.prog_items[0]
|
||||
item1 = player1.prog_items[1]
|
||||
loc1 = player1.locations[1]
|
||||
|
||||
multi_world.completion_condition[player1.id] = lambda state: state.has(
|
||||
multiworld.completion_condition[player1.id] = lambda state: state.has(
|
||||
item0.name, player1.id) and state.has(item0.name, player1.id) and state.has(item1.name, player1.id)
|
||||
set_rule(loc1, lambda state: state.has(item0.name, player1.id)
|
||||
and state.has(item1.name, player1.id))
|
||||
|
||||
self.assertRaises(FillError, fill_restrictive, multi_world, multi_world.state,
|
||||
self.assertRaises(FillError, fill_restrictive, multiworld, multiworld.state,
|
||||
player1.locations.copy(), player1.prog_items.copy())
|
||||
|
||||
def test_multiplayer_fill(self):
|
||||
"""Test that items can be placed across worlds"""
|
||||
multi_world = generate_multi_world(2)
|
||||
player1 = generate_player_data(multi_world, 1, 2, 2)
|
||||
player2 = generate_player_data(multi_world, 2, 2, 2)
|
||||
multiworld = generate_multiworld(2)
|
||||
player1 = generate_player_data(multiworld, 1, 2, 2)
|
||||
player2 = generate_player_data(multiworld, 2, 2, 2)
|
||||
|
||||
multi_world.completion_condition[player1.id] = lambda state: state.has(
|
||||
multiworld.completion_condition[player1.id] = lambda state: state.has(
|
||||
player1.prog_items[0].name, player1.id) and state.has(
|
||||
player1.prog_items[1].name, player1.id)
|
||||
multi_world.completion_condition[player2.id] = lambda state: state.has(
|
||||
multiworld.completion_condition[player2.id] = lambda state: state.has(
|
||||
player2.prog_items[0].name, player2.id) and state.has(
|
||||
player2.prog_items[1].name, player2.id)
|
||||
|
||||
fill_restrictive(multi_world, multi_world.state, player1.locations +
|
||||
fill_restrictive(multiworld, multiworld.state, player1.locations +
|
||||
player2.locations, player1.prog_items + player2.prog_items)
|
||||
|
||||
self.assertEqual(player1.locations[0].item, player1.prog_items[1])
|
||||
@@ -371,21 +371,21 @@ class TestFillRestrictive(unittest.TestCase):
|
||||
|
||||
def test_multiplayer_rules_fill(self):
|
||||
"""Test that fill across worlds satisfies the rules"""
|
||||
multi_world = generate_multi_world(2)
|
||||
player1 = generate_player_data(multi_world, 1, 2, 2)
|
||||
player2 = generate_player_data(multi_world, 2, 2, 2)
|
||||
multiworld = generate_multiworld(2)
|
||||
player1 = generate_player_data(multiworld, 1, 2, 2)
|
||||
player2 = generate_player_data(multiworld, 2, 2, 2)
|
||||
|
||||
multi_world.completion_condition[player1.id] = lambda state: state.has(
|
||||
multiworld.completion_condition[player1.id] = lambda state: state.has(
|
||||
player1.prog_items[0].name, player1.id) and state.has(
|
||||
player1.prog_items[1].name, player1.id)
|
||||
multi_world.completion_condition[player2.id] = lambda state: state.has(
|
||||
multiworld.completion_condition[player2.id] = lambda state: state.has(
|
||||
player2.prog_items[0].name, player2.id) and state.has(
|
||||
player2.prog_items[1].name, player2.id)
|
||||
|
||||
set_rule(player2.locations[1], lambda state: state.has(
|
||||
player2.prog_items[0].name, player2.id))
|
||||
|
||||
fill_restrictive(multi_world, multi_world.state, player1.locations +
|
||||
fill_restrictive(multiworld, multiworld.state, player1.locations +
|
||||
player2.locations, player1.prog_items + player2.prog_items)
|
||||
|
||||
self.assertEqual(player1.locations[0].item, player2.prog_items[0])
|
||||
@@ -395,10 +395,10 @@ class TestFillRestrictive(unittest.TestCase):
|
||||
|
||||
def test_restrictive_progress(self):
|
||||
"""Test that various spheres with different requirements can be filled"""
|
||||
multi_world = generate_multi_world()
|
||||
player1 = generate_player_data(multi_world, 1, prog_item_count=25)
|
||||
multiworld = generate_multiworld()
|
||||
player1 = generate_player_data(multiworld, 1, prog_item_count=25)
|
||||
items = player1.prog_items.copy()
|
||||
multi_world.completion_condition[player1.id] = lambda state: state.has_all(
|
||||
multiworld.completion_condition[player1.id] = lambda state: state.has_all(
|
||||
names(player1.prog_items), player1.id)
|
||||
|
||||
player1.generate_region(player1.menu, 5)
|
||||
@@ -411,16 +411,16 @@ class TestFillRestrictive(unittest.TestCase):
|
||||
player1.generate_region(player1.menu, 5, lambda state: state.has_all(
|
||||
names(items[17:22]), player1.id))
|
||||
|
||||
locations = multi_world.get_unfilled_locations()
|
||||
locations = multiworld.get_unfilled_locations()
|
||||
|
||||
fill_restrictive(multi_world, multi_world.state,
|
||||
fill_restrictive(multiworld, multiworld.state,
|
||||
locations, player1.prog_items)
|
||||
|
||||
def test_swap_to_earlier_location_with_item_rule(self):
|
||||
"""Test that item swap happens and works as intended"""
|
||||
# test for PR#1109
|
||||
multi_world = generate_multi_world(1)
|
||||
player1 = generate_player_data(multi_world, 1, 4, 4)
|
||||
multiworld = generate_multiworld(1)
|
||||
player1 = generate_player_data(multiworld, 1, 4, 4)
|
||||
locations = player1.locations[:] # copy required
|
||||
items = player1.prog_items[:] # copy required
|
||||
# for the test to work, item and location order is relevant: Sphere 1 last, allowed_item not last
|
||||
@@ -437,15 +437,15 @@ class TestFillRestrictive(unittest.TestCase):
|
||||
self.assertTrue(sphere1_loc.can_fill(None, allowed_item, False), "Test is flawed")
|
||||
self.assertFalse(sphere1_loc.can_fill(None, items[2], False), "Test is flawed")
|
||||
# fill has to place items[1] in locations[0] which will result in a swap because of placement order
|
||||
fill_restrictive(multi_world, multi_world.state, player1.locations, player1.prog_items)
|
||||
fill_restrictive(multiworld, multiworld.state, player1.locations, player1.prog_items)
|
||||
# assert swap happened
|
||||
self.assertTrue(sphere1_loc.item, "Did not swap required item into Sphere 1")
|
||||
self.assertEqual(sphere1_loc.item, allowed_item, "Wrong item in Sphere 1")
|
||||
|
||||
def test_swap_to_earlier_location_with_item_rule2(self):
|
||||
"""Test that swap works before all items are placed"""
|
||||
multi_world = generate_multi_world(1)
|
||||
player1 = generate_player_data(multi_world, 1, 5, 5)
|
||||
multiworld = generate_multiworld(1)
|
||||
player1 = generate_player_data(multiworld, 1, 5, 5)
|
||||
locations = player1.locations[:] # copy required
|
||||
items = player1.prog_items[:] # copy required
|
||||
# Two items provide access to sphere 2.
|
||||
@@ -477,7 +477,7 @@ class TestFillRestrictive(unittest.TestCase):
|
||||
|
||||
# Now fill should place one_to_two1 in sphere1_loc1 or sphere1_loc2 via swap,
|
||||
# which it will attempt before two_to_three and three_to_four are placed, testing the behavior.
|
||||
fill_restrictive(multi_world, multi_world.state, player1.locations, player1.prog_items)
|
||||
fill_restrictive(multiworld, multiworld.state, player1.locations, player1.prog_items)
|
||||
# assert swap happened
|
||||
self.assertTrue(sphere1_loc1.item and sphere1_loc2.item, "Did not swap required item into Sphere 1")
|
||||
self.assertTrue(sphere1_loc1.item.name == one_to_two1 or
|
||||
@@ -486,29 +486,29 @@ class TestFillRestrictive(unittest.TestCase):
|
||||
def test_double_sweep(self):
|
||||
"""Test that sweep doesn't duplicate Event items when sweeping"""
|
||||
# test for PR1114
|
||||
multi_world = generate_multi_world(1)
|
||||
player1 = generate_player_data(multi_world, 1, 1, 1)
|
||||
multiworld = generate_multiworld(1)
|
||||
player1 = generate_player_data(multiworld, 1, 1, 1)
|
||||
location = player1.locations[0]
|
||||
location.address = None
|
||||
location.event = True
|
||||
item = player1.prog_items[0]
|
||||
item.code = None
|
||||
location.place_locked_item(item)
|
||||
multi_world.state.sweep_for_events()
|
||||
multi_world.state.sweep_for_events()
|
||||
self.assertTrue(multi_world.state.prog_items[item.player][item.name], "Sweep did not collect - Test flawed")
|
||||
self.assertEqual(multi_world.state.prog_items[item.player][item.name], 1, "Sweep collected multiple times")
|
||||
multiworld.state.sweep_for_events()
|
||||
multiworld.state.sweep_for_events()
|
||||
self.assertTrue(multiworld.state.prog_items[item.player][item.name], "Sweep did not collect - Test flawed")
|
||||
self.assertEqual(multiworld.state.prog_items[item.player][item.name], 1, "Sweep collected multiple times")
|
||||
|
||||
def test_correct_item_instance_removed_from_pool(self):
|
||||
"""Test that a placed item gets removed from the submitted pool"""
|
||||
multi_world = generate_multi_world()
|
||||
player1 = generate_player_data(multi_world, 1, 2, 2)
|
||||
multiworld = generate_multiworld()
|
||||
player1 = generate_player_data(multiworld, 1, 2, 2)
|
||||
|
||||
player1.prog_items[0].name = "Different_item_instance_but_same_item_name"
|
||||
player1.prog_items[1].name = "Different_item_instance_but_same_item_name"
|
||||
loc0 = player1.locations[0]
|
||||
|
||||
fill_restrictive(multi_world, multi_world.state,
|
||||
fill_restrictive(multiworld, multiworld.state,
|
||||
[loc0], player1.prog_items)
|
||||
|
||||
self.assertEqual(1, len(player1.prog_items))
|
||||
@@ -518,14 +518,14 @@ class TestFillRestrictive(unittest.TestCase):
|
||||
class TestDistributeItemsRestrictive(unittest.TestCase):
|
||||
def test_basic_distribute(self):
|
||||
"""Test that distribute_items_restrictive is deterministic"""
|
||||
multi_world = generate_multi_world()
|
||||
multiworld = generate_multiworld()
|
||||
player1 = generate_player_data(
|
||||
multi_world, 1, 4, prog_item_count=2, basic_item_count=2)
|
||||
multiworld, 1, 4, prog_item_count=2, basic_item_count=2)
|
||||
locations = player1.locations
|
||||
prog_items = player1.prog_items
|
||||
basic_items = player1.basic_items
|
||||
|
||||
distribute_items_restrictive(multi_world)
|
||||
distribute_items_restrictive(multiworld)
|
||||
|
||||
self.assertEqual(locations[0].item, basic_items[1])
|
||||
self.assertFalse(locations[0].event)
|
||||
@@ -538,52 +538,52 @@ class TestDistributeItemsRestrictive(unittest.TestCase):
|
||||
|
||||
def test_excluded_distribute(self):
|
||||
"""Test that distribute_items_restrictive doesn't put advancement items on excluded locations"""
|
||||
multi_world = generate_multi_world()
|
||||
multiworld = generate_multiworld()
|
||||
player1 = generate_player_data(
|
||||
multi_world, 1, 4, prog_item_count=2, basic_item_count=2)
|
||||
multiworld, 1, 4, prog_item_count=2, basic_item_count=2)
|
||||
locations = player1.locations
|
||||
|
||||
locations[1].progress_type = LocationProgressType.EXCLUDED
|
||||
locations[2].progress_type = LocationProgressType.EXCLUDED
|
||||
|
||||
distribute_items_restrictive(multi_world)
|
||||
distribute_items_restrictive(multiworld)
|
||||
|
||||
self.assertFalse(locations[1].item.advancement)
|
||||
self.assertFalse(locations[2].item.advancement)
|
||||
|
||||
def test_non_excluded_item_distribute(self):
|
||||
"""Test that useful items aren't placed on excluded locations"""
|
||||
multi_world = generate_multi_world()
|
||||
multiworld = generate_multiworld()
|
||||
player1 = generate_player_data(
|
||||
multi_world, 1, 4, prog_item_count=2, basic_item_count=2)
|
||||
multiworld, 1, 4, prog_item_count=2, basic_item_count=2)
|
||||
locations = player1.locations
|
||||
basic_items = player1.basic_items
|
||||
|
||||
locations[1].progress_type = LocationProgressType.EXCLUDED
|
||||
basic_items[1].classification = ItemClassification.useful
|
||||
|
||||
distribute_items_restrictive(multi_world)
|
||||
distribute_items_restrictive(multiworld)
|
||||
|
||||
self.assertEqual(locations[1].item, basic_items[0])
|
||||
|
||||
def test_too_many_excluded_distribute(self):
|
||||
"""Test that fill fails if it can't place all progression items due to too many excluded locations"""
|
||||
multi_world = generate_multi_world()
|
||||
multiworld = generate_multiworld()
|
||||
player1 = generate_player_data(
|
||||
multi_world, 1, 4, prog_item_count=2, basic_item_count=2)
|
||||
multiworld, 1, 4, prog_item_count=2, basic_item_count=2)
|
||||
locations = player1.locations
|
||||
|
||||
locations[0].progress_type = LocationProgressType.EXCLUDED
|
||||
locations[1].progress_type = LocationProgressType.EXCLUDED
|
||||
locations[2].progress_type = LocationProgressType.EXCLUDED
|
||||
|
||||
self.assertRaises(FillError, distribute_items_restrictive, multi_world)
|
||||
self.assertRaises(FillError, distribute_items_restrictive, multiworld)
|
||||
|
||||
def test_non_excluded_item_must_distribute(self):
|
||||
"""Test that fill fails if it can't place useful items due to too many excluded locations"""
|
||||
multi_world = generate_multi_world()
|
||||
multiworld = generate_multiworld()
|
||||
player1 = generate_player_data(
|
||||
multi_world, 1, 4, prog_item_count=2, basic_item_count=2)
|
||||
multiworld, 1, 4, prog_item_count=2, basic_item_count=2)
|
||||
locations = player1.locations
|
||||
basic_items = player1.basic_items
|
||||
|
||||
@@ -592,47 +592,47 @@ class TestDistributeItemsRestrictive(unittest.TestCase):
|
||||
basic_items[0].classification = ItemClassification.useful
|
||||
basic_items[1].classification = ItemClassification.useful
|
||||
|
||||
self.assertRaises(FillError, distribute_items_restrictive, multi_world)
|
||||
self.assertRaises(FillError, distribute_items_restrictive, multiworld)
|
||||
|
||||
def test_priority_distribute(self):
|
||||
"""Test that priority locations receive advancement items"""
|
||||
multi_world = generate_multi_world()
|
||||
multiworld = generate_multiworld()
|
||||
player1 = generate_player_data(
|
||||
multi_world, 1, 4, prog_item_count=2, basic_item_count=2)
|
||||
multiworld, 1, 4, prog_item_count=2, basic_item_count=2)
|
||||
locations = player1.locations
|
||||
|
||||
locations[0].progress_type = LocationProgressType.PRIORITY
|
||||
locations[3].progress_type = LocationProgressType.PRIORITY
|
||||
|
||||
distribute_items_restrictive(multi_world)
|
||||
distribute_items_restrictive(multiworld)
|
||||
|
||||
self.assertTrue(locations[0].item.advancement)
|
||||
self.assertTrue(locations[3].item.advancement)
|
||||
|
||||
def test_excess_priority_distribute(self):
|
||||
"""Test that if there's more priority locations than advancement items, they can still fill"""
|
||||
multi_world = generate_multi_world()
|
||||
multiworld = generate_multiworld()
|
||||
player1 = generate_player_data(
|
||||
multi_world, 1, 4, prog_item_count=2, basic_item_count=2)
|
||||
multiworld, 1, 4, prog_item_count=2, basic_item_count=2)
|
||||
locations = player1.locations
|
||||
|
||||
locations[0].progress_type = LocationProgressType.PRIORITY
|
||||
locations[1].progress_type = LocationProgressType.PRIORITY
|
||||
locations[2].progress_type = LocationProgressType.PRIORITY
|
||||
|
||||
distribute_items_restrictive(multi_world)
|
||||
distribute_items_restrictive(multiworld)
|
||||
|
||||
self.assertFalse(locations[3].item.advancement)
|
||||
|
||||
def test_multiple_world_priority_distribute(self):
|
||||
"""Test that priority fill can be satisfied for multiple worlds"""
|
||||
multi_world = generate_multi_world(3)
|
||||
multiworld = generate_multiworld(3)
|
||||
player1 = generate_player_data(
|
||||
multi_world, 1, 4, prog_item_count=2, basic_item_count=2)
|
||||
multiworld, 1, 4, prog_item_count=2, basic_item_count=2)
|
||||
player2 = generate_player_data(
|
||||
multi_world, 2, 4, prog_item_count=1, basic_item_count=3)
|
||||
multiworld, 2, 4, prog_item_count=1, basic_item_count=3)
|
||||
player3 = generate_player_data(
|
||||
multi_world, 3, 6, prog_item_count=4, basic_item_count=2)
|
||||
multiworld, 3, 6, prog_item_count=4, basic_item_count=2)
|
||||
|
||||
player1.locations[2].progress_type = LocationProgressType.PRIORITY
|
||||
player1.locations[3].progress_type = LocationProgressType.PRIORITY
|
||||
@@ -644,7 +644,7 @@ class TestDistributeItemsRestrictive(unittest.TestCase):
|
||||
player3.locations[2].progress_type = LocationProgressType.PRIORITY
|
||||
player3.locations[3].progress_type = LocationProgressType.PRIORITY
|
||||
|
||||
distribute_items_restrictive(multi_world)
|
||||
distribute_items_restrictive(multiworld)
|
||||
|
||||
self.assertTrue(player1.locations[2].item.advancement)
|
||||
self.assertTrue(player1.locations[3].item.advancement)
|
||||
@@ -656,9 +656,9 @@ class TestDistributeItemsRestrictive(unittest.TestCase):
|
||||
|
||||
def test_can_remove_locations_in_fill_hook(self):
|
||||
"""Test that distribute_items_restrictive calls the fill hook and allows for item and location removal"""
|
||||
multi_world = generate_multi_world()
|
||||
multiworld = generate_multiworld()
|
||||
player1 = generate_player_data(
|
||||
multi_world, 1, 4, prog_item_count=2, basic_item_count=2)
|
||||
multiworld, 1, 4, prog_item_count=2, basic_item_count=2)
|
||||
|
||||
removed_item: list[Item] = []
|
||||
removed_location: list[Location] = []
|
||||
@@ -667,21 +667,21 @@ class TestDistributeItemsRestrictive(unittest.TestCase):
|
||||
removed_item.append(filleritempool.pop(0))
|
||||
removed_location.append(fill_locations.pop(0))
|
||||
|
||||
multi_world.worlds[player1.id].fill_hook = fill_hook
|
||||
multiworld.worlds[player1.id].fill_hook = fill_hook
|
||||
|
||||
distribute_items_restrictive(multi_world)
|
||||
distribute_items_restrictive(multiworld)
|
||||
|
||||
self.assertIsNone(removed_item[0].location)
|
||||
self.assertIsNone(removed_location[0].item)
|
||||
|
||||
def test_seed_robust_to_item_order(self):
|
||||
"""Test deterministic fill"""
|
||||
mw1 = generate_multi_world()
|
||||
mw1 = generate_multiworld()
|
||||
gen1 = generate_player_data(
|
||||
mw1, 1, 4, prog_item_count=2, basic_item_count=2)
|
||||
distribute_items_restrictive(mw1)
|
||||
|
||||
mw2 = generate_multi_world()
|
||||
mw2 = generate_multiworld()
|
||||
gen2 = generate_player_data(
|
||||
mw2, 1, 4, prog_item_count=2, basic_item_count=2)
|
||||
mw2.itempool.append(mw2.itempool.pop(0))
|
||||
@@ -694,12 +694,12 @@ class TestDistributeItemsRestrictive(unittest.TestCase):
|
||||
|
||||
def test_seed_robust_to_location_order(self):
|
||||
"""Test deterministic fill even if locations in a region are reordered"""
|
||||
mw1 = generate_multi_world()
|
||||
mw1 = generate_multiworld()
|
||||
gen1 = generate_player_data(
|
||||
mw1, 1, 4, prog_item_count=2, basic_item_count=2)
|
||||
distribute_items_restrictive(mw1)
|
||||
|
||||
mw2 = generate_multi_world()
|
||||
mw2 = generate_multiworld()
|
||||
gen2 = generate_player_data(
|
||||
mw2, 1, 4, prog_item_count=2, basic_item_count=2)
|
||||
reg = mw2.get_region("Menu", gen2.id)
|
||||
@@ -713,45 +713,45 @@ class TestDistributeItemsRestrictive(unittest.TestCase):
|
||||
|
||||
def test_can_reserve_advancement_items_for_general_fill(self):
|
||||
"""Test that priority locations fill still satisfies item rules"""
|
||||
multi_world = generate_multi_world()
|
||||
multiworld = generate_multiworld()
|
||||
player1 = generate_player_data(
|
||||
multi_world, 1, location_count=5, prog_item_count=5)
|
||||
multiworld, 1, location_count=5, prog_item_count=5)
|
||||
items = player1.prog_items
|
||||
multi_world.completion_condition[player1.id] = lambda state: state.has_all(
|
||||
multiworld.completion_condition[player1.id] = lambda state: state.has_all(
|
||||
names(items), player1.id)
|
||||
|
||||
location = player1.locations[0]
|
||||
location.progress_type = LocationProgressType.PRIORITY
|
||||
location.item_rule = lambda item: item not in items[:4]
|
||||
|
||||
distribute_items_restrictive(multi_world)
|
||||
distribute_items_restrictive(multiworld)
|
||||
|
||||
self.assertEqual(location.item, items[4])
|
||||
|
||||
def test_non_excluded_local_items(self):
|
||||
"""Test that local items get placed locally in a multiworld"""
|
||||
multi_world = generate_multi_world(2)
|
||||
multiworld = generate_multiworld(2)
|
||||
player1 = generate_player_data(
|
||||
multi_world, 1, location_count=5, basic_item_count=5)
|
||||
multiworld, 1, location_count=5, basic_item_count=5)
|
||||
player2 = generate_player_data(
|
||||
multi_world, 2, location_count=5, basic_item_count=5)
|
||||
multiworld, 2, location_count=5, basic_item_count=5)
|
||||
|
||||
for item in multi_world.get_items():
|
||||
for item in multiworld.get_items():
|
||||
item.classification = ItemClassification.useful
|
||||
|
||||
multi_world.local_items[player1.id].value = set(names(player1.basic_items))
|
||||
multi_world.local_items[player2.id].value = set(names(player2.basic_items))
|
||||
locality_rules(multi_world)
|
||||
multiworld.local_items[player1.id].value = set(names(player1.basic_items))
|
||||
multiworld.local_items[player2.id].value = set(names(player2.basic_items))
|
||||
locality_rules(multiworld)
|
||||
|
||||
distribute_items_restrictive(multi_world)
|
||||
distribute_items_restrictive(multiworld)
|
||||
|
||||
for item in multi_world.get_items():
|
||||
for item in multiworld.get_items():
|
||||
self.assertEqual(item.player, item.location.player)
|
||||
self.assertFalse(item.location.event, False)
|
||||
|
||||
def test_early_items(self) -> None:
|
||||
"""Test that the early items API successfully places items early"""
|
||||
mw = generate_multi_world(2)
|
||||
mw = generate_multiworld(2)
|
||||
player1 = generate_player_data(mw, 1, location_count=5, basic_item_count=5)
|
||||
player2 = generate_player_data(mw, 2, location_count=5, basic_item_count=5)
|
||||
mw.early_items[1][player1.basic_items[0].name] = 1
|
||||
@@ -810,19 +810,19 @@ class TestBalanceMultiworldProgression(unittest.TestCase):
|
||||
"\n Contains" + str(list(map(lambda location: location.item, region.locations))))
|
||||
|
||||
def setUp(self) -> None:
|
||||
multi_world = generate_multi_world(2)
|
||||
self.multi_world = multi_world
|
||||
multiworld = generate_multiworld(2)
|
||||
self.multiworld = multiworld
|
||||
player1 = generate_player_data(
|
||||
multi_world, 1, prog_item_count=2, basic_item_count=40)
|
||||
multiworld, 1, prog_item_count=2, basic_item_count=40)
|
||||
self.player1 = player1
|
||||
player2 = generate_player_data(
|
||||
multi_world, 2, prog_item_count=2, basic_item_count=40)
|
||||
multiworld, 2, prog_item_count=2, basic_item_count=40)
|
||||
self.player2 = player2
|
||||
|
||||
multi_world.completion_condition[player1.id] = lambda state: state.has(
|
||||
multiworld.completion_condition[player1.id] = lambda state: state.has(
|
||||
player1.prog_items[0].name, player1.id) and state.has(
|
||||
player1.prog_items[1].name, player1.id)
|
||||
multi_world.completion_condition[player2.id] = lambda state: state.has(
|
||||
multiworld.completion_condition[player2.id] = lambda state: state.has(
|
||||
player2.prog_items[0].name, player2.id) and state.has(
|
||||
player2.prog_items[1].name, player2.id)
|
||||
|
||||
@@ -830,42 +830,42 @@ class TestBalanceMultiworldProgression(unittest.TestCase):
|
||||
|
||||
# Sphere 1
|
||||
region = player1.generate_region(player1.menu, 20)
|
||||
items = fill_region(multi_world, region, [
|
||||
items = fill_region(multiworld, region, [
|
||||
player1.prog_items[0]] + items)
|
||||
|
||||
# Sphere 2
|
||||
region = player1.generate_region(
|
||||
player1.regions[1], 20, lambda state: state.has(player1.prog_items[0].name, player1.id))
|
||||
items = fill_region(
|
||||
multi_world, region, [player1.prog_items[1], player2.prog_items[0]] + items)
|
||||
multiworld, region, [player1.prog_items[1], player2.prog_items[0]] + items)
|
||||
|
||||
# Sphere 3
|
||||
region = player2.generate_region(
|
||||
player2.menu, 20, lambda state: state.has(player2.prog_items[0].name, player2.id))
|
||||
fill_region(multi_world, region, [player2.prog_items[1]] + items)
|
||||
fill_region(multiworld, region, [player2.prog_items[1]] + items)
|
||||
|
||||
def test_balances_progression(self) -> None:
|
||||
"""Tests that progression balancing moves progression items earlier"""
|
||||
self.multi_world.progression_balancing[self.player1.id].value = 50
|
||||
self.multi_world.progression_balancing[self.player2.id].value = 50
|
||||
self.multiworld.progression_balancing[self.player1.id].value = 50
|
||||
self.multiworld.progression_balancing[self.player2.id].value = 50
|
||||
|
||||
self.assertRegionContains(
|
||||
self.player1.regions[2], self.player2.prog_items[0])
|
||||
|
||||
balance_multiworld_progression(self.multi_world)
|
||||
balance_multiworld_progression(self.multiworld)
|
||||
|
||||
self.assertRegionContains(
|
||||
self.player1.regions[1], self.player2.prog_items[0])
|
||||
|
||||
def test_balances_progression_light(self) -> None:
|
||||
"""Test that progression balancing still moves items earlier on minimum value"""
|
||||
self.multi_world.progression_balancing[self.player1.id].value = 1
|
||||
self.multi_world.progression_balancing[self.player2.id].value = 1
|
||||
self.multiworld.progression_balancing[self.player1.id].value = 1
|
||||
self.multiworld.progression_balancing[self.player2.id].value = 1
|
||||
|
||||
self.assertRegionContains(
|
||||
self.player1.regions[2], self.player2.prog_items[0])
|
||||
|
||||
balance_multiworld_progression(self.multi_world)
|
||||
balance_multiworld_progression(self.multiworld)
|
||||
|
||||
# TODO: arrange for a result that's different from the default
|
||||
self.assertRegionContains(
|
||||
@@ -873,13 +873,13 @@ class TestBalanceMultiworldProgression(unittest.TestCase):
|
||||
|
||||
def test_balances_progression_heavy(self) -> None:
|
||||
"""Test that progression balancing moves items earlier on maximum value"""
|
||||
self.multi_world.progression_balancing[self.player1.id].value = 99
|
||||
self.multi_world.progression_balancing[self.player2.id].value = 99
|
||||
self.multiworld.progression_balancing[self.player1.id].value = 99
|
||||
self.multiworld.progression_balancing[self.player2.id].value = 99
|
||||
|
||||
self.assertRegionContains(
|
||||
self.player1.regions[2], self.player2.prog_items[0])
|
||||
|
||||
balance_multiworld_progression(self.multi_world)
|
||||
balance_multiworld_progression(self.multiworld)
|
||||
|
||||
# TODO: arrange for a result that's different from the default
|
||||
self.assertRegionContains(
|
||||
@@ -887,25 +887,25 @@ class TestBalanceMultiworldProgression(unittest.TestCase):
|
||||
|
||||
def test_skips_balancing_progression(self) -> None:
|
||||
"""Test that progression balancing is skipped when players have it disabled"""
|
||||
self.multi_world.progression_balancing[self.player1.id].value = 0
|
||||
self.multi_world.progression_balancing[self.player2.id].value = 0
|
||||
self.multiworld.progression_balancing[self.player1.id].value = 0
|
||||
self.multiworld.progression_balancing[self.player2.id].value = 0
|
||||
|
||||
self.assertRegionContains(
|
||||
self.player1.regions[2], self.player2.prog_items[0])
|
||||
|
||||
balance_multiworld_progression(self.multi_world)
|
||||
balance_multiworld_progression(self.multiworld)
|
||||
|
||||
self.assertRegionContains(
|
||||
self.player1.regions[2], self.player2.prog_items[0])
|
||||
|
||||
def test_ignores_priority_locations(self) -> None:
|
||||
"""Test that progression items on priority locations don't get moved by balancing"""
|
||||
self.multi_world.progression_balancing[self.player1.id].value = 50
|
||||
self.multi_world.progression_balancing[self.player2.id].value = 50
|
||||
self.multiworld.progression_balancing[self.player1.id].value = 50
|
||||
self.multiworld.progression_balancing[self.player2.id].value = 50
|
||||
|
||||
self.player2.prog_items[0].location.progress_type = LocationProgressType.PRIORITY
|
||||
|
||||
balance_multiworld_progression(self.multi_world)
|
||||
balance_multiworld_progression(self.multiworld)
|
||||
|
||||
self.assertRegionContains(
|
||||
self.player1.regions[2], self.player2.prog_items[0])
|
||||
|
||||
27
test/general/test_groups.py
Normal file
27
test/general/test_groups.py
Normal file
@@ -0,0 +1,27 @@
|
||||
from unittest import TestCase
|
||||
|
||||
from worlds.AutoWorld import AutoWorldRegister
|
||||
|
||||
|
||||
class TestNameGroups(TestCase):
|
||||
def test_item_name_groups_not_empty(self) -> None:
|
||||
"""
|
||||
Test that there are no empty item name groups, which is likely a bug.
|
||||
"""
|
||||
for game_name, world_type in AutoWorldRegister.world_types.items():
|
||||
if not world_type.item_id_to_name:
|
||||
continue # ignore worlds without items
|
||||
with self.subTest(game=game_name):
|
||||
for name, group in world_type.item_name_groups.items():
|
||||
self.assertTrue(group, f"Item name group \"{name}\" of \"{game_name}\" is empty")
|
||||
|
||||
def test_location_name_groups_not_empty(self) -> None:
|
||||
"""
|
||||
Test that there are no empty location name groups, which is likely a bug.
|
||||
"""
|
||||
for game_name, world_type in AutoWorldRegister.world_types.items():
|
||||
if not world_type.location_id_to_name:
|
||||
continue # ignore worlds without locations
|
||||
with self.subTest(game=game_name):
|
||||
for name, group in world_type.location_name_groups.items():
|
||||
self.assertTrue(group, f"Location name group \"{name}\" of \"{game_name}\" is empty")
|
||||
@@ -1,5 +1,6 @@
|
||||
import unittest
|
||||
from worlds.AutoWorld import AutoWorldRegister
|
||||
|
||||
from worlds.AutoWorld import AutoWorldRegister, call_all
|
||||
from . import setup_solo_multiworld
|
||||
|
||||
|
||||
@@ -42,18 +43,18 @@ class TestBase(unittest.TestCase):
|
||||
with self.subTest(group_name, group_name=group_name):
|
||||
self.assertNotIn(group_name, world_type.item_name_to_id)
|
||||
|
||||
def test_item_count_greater_equal_locations(self):
|
||||
"""Test that by the pre_fill step under default settings, each game submits items >= locations"""
|
||||
def test_item_count_equal_locations(self):
|
||||
"""Test that by the pre_fill step under default settings, each game submits items == locations"""
|
||||
for game_name, world_type in AutoWorldRegister.world_types.items():
|
||||
with self.subTest("Game", game=game_name):
|
||||
multiworld = setup_solo_multiworld(world_type)
|
||||
self.assertGreaterEqual(
|
||||
self.assertEqual(
|
||||
len(multiworld.itempool),
|
||||
len(multiworld.get_unfilled_locations()),
|
||||
f"{game_name} Item count MUST meet or exceed the number of locations",
|
||||
f"{game_name} Item count MUST match the number of locations",
|
||||
)
|
||||
|
||||
def testItemsInDatapackage(self):
|
||||
def test_items_in_datapackage(self):
|
||||
"""Test that any created items in the itempool are in the datapackage"""
|
||||
for game_name, world_type in AutoWorldRegister.world_types.items():
|
||||
with self.subTest("Game", game=game_name):
|
||||
@@ -69,3 +70,20 @@ class TestBase(unittest.TestCase):
|
||||
with self.subTest("Name should be valid", game=game_name, item=name):
|
||||
self.assertIn(name, valid_names,
|
||||
"All item descriptions must match defined item names")
|
||||
|
||||
def test_itempool_not_modified(self):
|
||||
"""Test that worlds don't modify the itempool after `create_items`"""
|
||||
gen_steps = ("generate_early", "create_regions", "create_items")
|
||||
additional_steps = ("set_rules", "generate_basic", "pre_fill")
|
||||
excluded_games = ("Links Awakening DX", "Ocarina of Time", "SMZ3")
|
||||
worlds_to_test = {game: world
|
||||
for game, world in AutoWorldRegister.world_types.items() if game not in excluded_games}
|
||||
for game_name, world_type in worlds_to_test.items():
|
||||
with self.subTest("Game", game=game_name):
|
||||
multiworld = setup_solo_multiworld(world_type, gen_steps)
|
||||
created_items = multiworld.itempool.copy()
|
||||
for step in additional_steps:
|
||||
with self.subTest("step", step=step):
|
||||
call_all(multiworld, step)
|
||||
self.assertEqual(created_items, multiworld.itempool,
|
||||
f"{game_name} modified the itempool during {step}")
|
||||
|
||||
@@ -11,14 +11,14 @@ class TestBase(unittest.TestCase):
|
||||
multiworld = setup_solo_multiworld(world_type)
|
||||
locations = Counter(location.name for location in multiworld.get_locations())
|
||||
if locations:
|
||||
self.assertLessEqual(locations.most_common(1)[0][1], 1,
|
||||
f"{world_type.game} has duplicate of location name {locations.most_common(1)}")
|
||||
self.assertEqual(locations.most_common(1)[0][1], 1,
|
||||
f"{world_type.game} has duplicate of location name {locations.most_common(1)}")
|
||||
|
||||
locations = Counter(location.address for location in multiworld.get_locations()
|
||||
if type(location.address) is int)
|
||||
if locations:
|
||||
self.assertLessEqual(locations.most_common(1)[0][1], 1,
|
||||
f"{world_type.game} has duplicate of location ID {locations.most_common(1)}")
|
||||
self.assertEqual(locations.most_common(1)[0][1], 1,
|
||||
f"{world_type.game} has duplicate of location ID {locations.most_common(1)}")
|
||||
|
||||
def test_locations_in_datapackage(self):
|
||||
"""Tests that created locations not filled before fill starts exist in the datapackage."""
|
||||
|
||||
@@ -10,3 +10,10 @@ class TestOptions(unittest.TestCase):
|
||||
for option_key, option in world_type.options_dataclass.type_hints.items():
|
||||
with self.subTest(game=gamename, option=option_key):
|
||||
self.assertTrue(option.__doc__)
|
||||
|
||||
def test_options_are_not_set_by_world(self):
|
||||
"""Test that options attribute is not already set"""
|
||||
for gamename, world_type in AutoWorldRegister.world_types.items():
|
||||
with self.subTest(game=gamename):
|
||||
self.assertFalse(hasattr(world_type, "options"),
|
||||
f"Unexpected assignment to {world_type.__name__}.options!")
|
||||
|
||||
@@ -36,15 +36,15 @@ class TestBase(unittest.TestCase):
|
||||
for game_name, world_type in AutoWorldRegister.world_types.items():
|
||||
unreachable_regions = self.default_settings_unreachable_regions.get(game_name, set())
|
||||
with self.subTest("Game", game=game_name):
|
||||
world = setup_solo_multiworld(world_type)
|
||||
excluded = world.exclude_locations[1].value
|
||||
state = world.get_all_state(False)
|
||||
for location in world.get_locations():
|
||||
multiworld = setup_solo_multiworld(world_type)
|
||||
excluded = multiworld.worlds[1].options.exclude_locations.value
|
||||
state = multiworld.get_all_state(False)
|
||||
for location in multiworld.get_locations():
|
||||
if location.name not in excluded:
|
||||
with self.subTest("Location should be reached", location=location):
|
||||
self.assertTrue(location.can_reach(state), f"{location.name} unreachable")
|
||||
|
||||
for region in world.get_regions():
|
||||
for region in multiworld.get_regions():
|
||||
if region.name in unreachable_regions:
|
||||
with self.subTest("Region should be unreachable", region=region):
|
||||
self.assertFalse(region.can_reach(state))
|
||||
@@ -53,15 +53,15 @@ class TestBase(unittest.TestCase):
|
||||
self.assertTrue(region.can_reach(state))
|
||||
|
||||
with self.subTest("Completion Condition"):
|
||||
self.assertTrue(world.can_beat_game(state))
|
||||
self.assertTrue(multiworld.can_beat_game(state))
|
||||
|
||||
def test_default_empty_state_can_reach_something(self):
|
||||
"""Ensure empty state can reach at least one location with the defined options"""
|
||||
for game_name, world_type in AutoWorldRegister.world_types.items():
|
||||
with self.subTest("Game", game=game_name):
|
||||
world = setup_solo_multiworld(world_type)
|
||||
state = CollectionState(world)
|
||||
all_locations = world.get_locations()
|
||||
multiworld = setup_solo_multiworld(world_type)
|
||||
state = CollectionState(multiworld)
|
||||
all_locations = multiworld.get_locations()
|
||||
if all_locations:
|
||||
locations = set()
|
||||
for location in all_locations:
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import io
|
||||
import unittest
|
||||
import json
|
||||
import yaml
|
||||
|
||||
|
||||
class TestDocs(unittest.TestCase):
|
||||
@@ -23,7 +25,7 @@ class TestDocs(unittest.TestCase):
|
||||
response = self.client.post("/api/generate")
|
||||
self.assertIn("No options found. Expected file attachment or json weights.", response.text)
|
||||
|
||||
def test_generation_queued(self):
|
||||
def test_generation_queued_weights(self):
|
||||
options = {
|
||||
"Tester1":
|
||||
{
|
||||
@@ -40,3 +42,19 @@ class TestDocs(unittest.TestCase):
|
||||
json_data = response.get_json()
|
||||
self.assertTrue(json_data["text"].startswith("Generation of seed "))
|
||||
self.assertTrue(json_data["text"].endswith(" started successfully."))
|
||||
|
||||
def test_generation_queued_file(self):
|
||||
options = {
|
||||
"game": "Archipelago",
|
||||
"name": "Tester",
|
||||
"Archipelago": {}
|
||||
}
|
||||
response = self.client.post(
|
||||
"/api/generate",
|
||||
data={
|
||||
'file': (io.BytesIO(yaml.dump(options, encoding="utf-8")), "test.yaml")
|
||||
},
|
||||
)
|
||||
json_data = response.get_json()
|
||||
self.assertTrue(json_data["text"].startswith("Generation of seed "))
|
||||
self.assertTrue(json_data["text"].endswith(" started successfully."))
|
||||
|
||||
Reference in New Issue
Block a user