diff --git a/Fill.py b/Fill.py index 686923e9..f19cf0fd 100644 --- a/Fill.py +++ b/Fill.py @@ -2,7 +2,7 @@ import logging import typing import collections import itertools -from collections import Counter +from collections import Counter, deque from BaseClasses import CollectionState, Location, MultiWorld, Item @@ -14,7 +14,7 @@ class FillError(RuntimeError): pass -def sweep_from_pool(base_state: CollectionState, itempool: list[Item]): +def sweep_from_pool(base_state: CollectionState, itempool): new_state = base_state.copy() for item in itempool: new_state.collect(item, True) @@ -28,9 +28,9 @@ def fill_restrictive(world: MultiWorld, base_state: CollectionState, locations, placements = [] swapped_items = Counter() - reachable_items = {} + reachable_items: dict[str, deque] = {} for item in itempool: - reachable_items.setdefault(item.player, []).append(item) + reachable_items.setdefault(item.player, deque()).append(item) while any(reachable_items.values()) and locations: # grab one item per player @@ -75,8 +75,7 @@ def fill_restrictive(world: MultiWorld, base_state: CollectionState, locations, # add the old item to the back of the queue spot_to_fill = placements.pop(i) swapped_items[placed_item.player, placed_item.name] += 1 - reachable_items.setdefault( - placed_item.player, []).append(placed_item) + reachable_items[placed_item.player].appendleft(placed_item) itempool.append(placed_item) break else: diff --git a/test/base/TestFill.py b/test/base/TestFill.py index 8e032109..259cc8e4 100644 --- a/test/base/TestFill.py +++ b/test/base/TestFill.py @@ -1,3 +1,4 @@ +from typing import NamedTuple import unittest import pytest from worlds.AutoWorld import World @@ -6,13 +7,19 @@ from BaseClasses import MultiWorld, Region, RegionType, Item, Location from worlds.generic.Rules import set_rule -def generate_multi_world() -> MultiWorld: - multi_world = MultiWorld(1) - player1_id = 1 - world = World(multi_world, player1_id) - multi_world.game[player1_id] = world - multi_world.worlds[player1_id] = world - multi_world.player_name = {player1_id: "Test Player 1"} +def generate_multi_world(players: int = 1) -> MultiWorld: + multi_world = MultiWorld(players) + multi_world.player_name = {} + for i in range(players): + player_id = i+1 + world = World(multi_world, player_id) + multi_world.game[player_id] = world + multi_world.worlds[player_id] = world + multi_world.player_name[player_id] = "Test Player " + str(player_id) + region = Region("Menu", RegionType.Generic, + "Menu Region Hint", player_id, multi_world) + multi_world.regions.append(region) + multi_world.set_seed() # args = Namespace() # for name, option in world_type.options.items(): @@ -20,13 +27,24 @@ def generate_multi_world() -> MultiWorld: # multi_world.set_options(args) multi_world.set_default_common_options() - region = Region("Menu", RegionType.Generic, - "Menu Region Hint", player1_id, multi_world) - multi_world.regions.append(region) - return multi_world +class PlayerDefinition(NamedTuple): + id: int + menu: Region + locations: list[Location] + prog_items: list[Item] + + +def generate_player_data(multi_world: MultiWorld, player_id: int, location_count: int, prog_item_count: int) -> PlayerDefinition: + menu = multi_world.get_region("Menu", player_id) + locations = generate_locations(location_count, player_id, None, menu) + prog_items = generate_items(prog_item_count, player_id, True) + + return PlayerDefinition(player_id, menu, locations, prog_items) + + def generate_locations(count: int, player_id: int, address: int = None, region: Region = None) -> list[Location]: locations = [] for i in range(count): @@ -48,124 +66,171 @@ def generate_items(count: int, player_id: int, advancement: bool = False, code: class TestBase(unittest.TestCase): def test_basic_fill_restrictive(self): multi_world = generate_multi_world() - player1_id = 1 - player1_menu = multi_world.get_region("Menu", player1_id) + player1 = generate_player_data(multi_world, 1, 2, 2) - locations = generate_locations(2, player1_id, None, player1_menu) - items = generate_items(2, player1_id, True) + item0 = player1.prog_items[0] + item1 = player1.prog_items[1] + loc0 = player1.locations[0] + loc1 = player1.locations[1] - item0 = items[0] - item1 = items[1] - loc0 = locations[0] - loc1 = locations[1] - - fill_restrictive(multi_world, multi_world.state, locations, items) + fill_restrictive(multi_world, multi_world.state, + player1.locations, player1.prog_items) self.assertEqual(loc0.item, item1) self.assertEqual(loc1.item, item0) - self.assertEqual([], locations) - self.assertEqual([], items) + self.assertEqual([], player1.locations) + self.assertEqual([], player1.prog_items) def test_ordered_fill_restrictive(self): multi_world = generate_multi_world() - player1_id = 1 - player1_menu = multi_world.get_region("Menu", player1_id) + player1 = generate_player_data(multi_world, 1, 2, 2) - locations = generate_locations(2, player1_id, None, player1_menu) - items = generate_items(2, player1_id, True) + item0 = player1.prog_items[0] + item1 = player1.prog_items[1] + loc0 = player1.locations[0] + loc1 = player1.locations[1] - item0 = items[0] - item1 = items[1] - loc0 = locations[0] - loc1 = locations[1] - - multi_world.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)) - fill_restrictive(multi_world, multi_world.state, locations, items) + multi_world.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)) + fill_restrictive(multi_world, multi_world.state, + player1.locations, player1.prog_items) self.assertEqual(loc0.item, item0) self.assertEqual(loc1.item, item1) def test_reversed_fill_restrictive(self): multi_world = generate_multi_world() - player1_id = 1 - player1_menu = multi_world.get_region("Menu", player1_id) + player1 = generate_player_data(multi_world, 1, 2, 2) - locations = generate_locations(2, player1_id, None, player1_menu) - items = generate_items(2, player1_id, True) + item0 = player1.prog_items[0] + item1 = player1.prog_items[1] + loc0 = player1.locations[0] + loc1 = player1.locations[1] - item0 = items[0] - item1 = items[1] - loc0 = locations[0] - loc1 = locations[1] - - multi_world.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, locations, items) + multi_world.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, + player1.locations, player1.prog_items) self.assertEqual(loc0.item, item1) self.assertEqual(loc1.item, item0) + def test_multi_step_fill_restrictive(self): + multi_world = generate_multi_world() + player1 = generate_player_data(multi_world, 1, 4, 4) + + items = player1.prog_items + locations = player1.locations + + multi_world.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)) + set_rule(locations[2], lambda state: state.has(items[1].name, player1.id)) + set_rule(locations[3], lambda state: state.has(items[1].name, player1.id)) + + fill_restrictive(multi_world, multi_world.state, + player1.locations.copy(), player1.prog_items.copy()) + + self.assertEqual(locations[0].item, items[1]) + self.assertEqual(locations[1].item, items[2]) + self.assertEqual(locations[2].item, items[0]) + self.assertEqual(locations[3].item, items[3]) + def test_impossible_fill_restrictive(self): multi_world = generate_multi_world() - player1_id = 1 - player1_menu = multi_world.get_region("Menu", player1_id) + player1 = generate_player_data(multi_world, 1, 2, 2) - locations = generate_locations(2, player1_id, None, player1_menu) - items = generate_items(2, player1_id, True) + item0 = player1.prog_items[0] + item1 = player1.prog_items[1] + loc0 = player1.locations[0] + loc1 = player1.locations[1] - item0 = items[0] - item1 = items[1] - loc0 = locations[0] - loc1 = locations[1] - - multi_world.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)) - set_rule(loc0, lambda state: state.has(item0.name, player1_id)) + multi_world.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)) + set_rule(loc0, lambda state: state.has(item0.name, player1.id)) with pytest.raises(FillError): - fill_restrictive(multi_world, multi_world.state, locations, items) + fill_restrictive(multi_world, multi_world.state, + player1.locations, player1.prog_items) def test_circular_fill_restrictive(self): multi_world = generate_multi_world() - player1_id = 1 - player1_menu = multi_world.get_region("Menu", player1_id) + player1 = generate_player_data(multi_world, 1, 3, 3) - locations = generate_locations(3, player1_id, None, player1_menu) - items = generate_items(3, player1_id, True) + item0 = player1.prog_items[0] + item1 = player1.prog_items[1] + item2 = player1.prog_items[2] + loc0 = player1.locations[0] + loc1 = player1.locations[1] + loc2 = player1.locations[2] - item0 = items[0] - item1 = items[1] - item2 = items[2] - loc0 = locations[0] - loc1 = locations[1] - loc2 = locations[2] - - multi_world.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)) + multi_world.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)) with pytest.raises(FillError): - fill_restrictive(multi_world, multi_world.state, locations, items) + fill_restrictive(multi_world, multi_world.state, + player1.locations, player1.prog_items) def test_competing_fill_restrictive(self): multi_world = generate_multi_world() - player1_id = 1 - player1_menu = multi_world.get_region("Menu", player1_id) + player1 = generate_player_data(multi_world, 1, 2, 2) - locations = generate_locations(2, player1_id, None, player1_menu) - items = generate_items(2, player1_id, True) + item0 = player1.prog_items[0] + item1 = player1.prog_items[1] + loc1 = player1.locations[1] - item0 = items[0] - item1 = items[1] - loc1 = locations[1] - - multi_world.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)) + multi_world.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)) with pytest.raises(FillError): - fill_restrictive(multi_world, multi_world.state, locations, items) + fill_restrictive(multi_world, multi_world.state, + player1.locations, player1.prog_items) + + def test_multiplayer_fill_restrictive(self): + multi_world = generate_multi_world(2) + player1 = generate_player_data(multi_world, 1, 2, 2) + player2 = generate_player_data(multi_world, 2, 2, 2) + + multi_world.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( + 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 + + player2.locations, player1.prog_items + player2.prog_items) + + self.assertEqual(player1.locations[0].item, player1.prog_items[1]) + self.assertEqual(player1.locations[1].item, player2.prog_items[1]) + self.assertEqual(player2.locations[0].item, player1.prog_items[0]) + self.assertEqual(player2.locations[1].item, player2.prog_items[0]) + + def test_multiplayer_rules_fill_restrictive(self): + multi_world = generate_multi_world(2) + player1 = generate_player_data(multi_world, 1, 2, 2) + player2 = generate_player_data(multi_world, 2, 2, 2) + + multi_world.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( + 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)) + # set_rule(player2.locations[1], lambda state: state.has(player2.prog_items[1])) + + fill_restrictive(multi_world, multi_world.state, player1.locations + + player2.locations, player1.prog_items + player2.prog_items) + + self.assertEqual(player1.locations[0].item, player2.prog_items[0]) + self.assertEqual(player1.locations[1].item, player2.prog_items[1]) + self.assertEqual(player2.locations[0].item, player1.prog_items[0]) + self.assertEqual(player2.locations[1].item, player1.prog_items[1])