Don't swap items that reduce access (#247)

This commit is contained in:
Brad Humphrey 2022-01-27 21:40:08 -07:00 committed by GitHub
parent 65a92746d1
commit dd61d0d395
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 65 additions and 19 deletions

54
Fill.py
View File

@ -14,7 +14,7 @@ class FillError(RuntimeError):
pass pass
def sweep_from_pool(base_state: CollectionState, itempool): def sweep_from_pool(base_state: CollectionState, itempool=[]):
new_state = base_state.copy() new_state = base_state.copy()
for item in itempool: for item in itempool:
new_state.collect(item, True) new_state.collect(item, True)
@ -25,7 +25,7 @@ def sweep_from_pool(base_state: CollectionState, itempool):
def fill_restrictive(world: MultiWorld, base_state: CollectionState, locations, itempool: typing.List[Item], def fill_restrictive(world: MultiWorld, base_state: CollectionState, locations, itempool: typing.List[Item],
single_player_placement=False, lock=False): single_player_placement=False, lock=False):
unplaced_items = [] unplaced_items = []
placements = [] placements: typing.List[Location] = []
swapped_items = Counter() swapped_items = Counter()
reachable_items: typing.Dict[int, deque] = {} reachable_items: typing.Dict[int, deque] = {}
@ -39,6 +39,7 @@ def fill_restrictive(world: MultiWorld, base_state: CollectionState, locations,
for item in items_to_place: for item in items_to_place:
itempool.remove(item) itempool.remove(item)
maximum_exploration_state = sweep_from_pool(base_state, itempool) maximum_exploration_state = sweep_from_pool(base_state, itempool)
has_beaten_game = world.has_beaten_game(maximum_exploration_state) has_beaten_game = world.has_beaten_game(maximum_exploration_state)
for item_to_place in items_to_place: for item_to_place in items_to_place:
@ -64,26 +65,45 @@ def fill_restrictive(world: MultiWorld, base_state: CollectionState, locations,
placed_item = location.item placed_item = location.item
# Unplaceable items can sometimes be swapped infinitely. Limit the # Unplaceable items can sometimes be swapped infinitely. Limit the
# number of times we will swap an individual item to prevent this # number of times we will swap an individual item to prevent this
if swapped_items[placed_item.player, placed_item.name] > 0: swap_count = swapped_items[placed_item.player,
placed_item.name]
if swap_count > 1:
continue continue
location.item = None location.item = None
placed_item.location = None placed_item.location = None
swap_state = sweep_from_pool(base_state, itempool) swap_state = sweep_from_pool(base_state)
if (not single_player_placement or location.player == item_to_place.player) \ if (not single_player_placement or location.player == item_to_place.player) \
and location.can_fill(swap_state, item_to_place, perform_access_check): and location.can_fill(swap_state, item_to_place, perform_access_check):
# Add this item to the existing placement, and
# add the old item to the back of the queue # Verify that placing this item won't reduce available locations
spot_to_fill = placements.pop(i) prev_state = swap_state.copy()
swapped_items[placed_item.player, prev_state.collect(placed_item)
placed_item.name] += 1 prev_loc_count = len(
reachable_items[placed_item.player].appendleft( world.get_reachable_locations(prev_state))
placed_item)
itempool.append(placed_item) swap_state.collect(item_to_place, True)
break new_loc_count = len(
else: world.get_reachable_locations(swap_state))
# Item can't be placed here, restore original item
location.item = placed_item if new_loc_count >= prev_loc_count:
placed_item.location = location # Add this item to the existing placement, and
# add the old item to the back of the queue
spot_to_fill = placements.pop(i)
swap_count += 1
swapped_items[placed_item.player,
placed_item.name] = swap_count
reachable_items[placed_item.player].appendleft(
placed_item)
itempool.append(placed_item)
break
# Item can't be placed here, restore original item
location.item = placed_item
placed_item.location = location
if spot_to_fill is None: if spot_to_fill is None:
# Maybe the game can be beaten anyway? # Maybe the game can be beaten anyway?

View File

@ -115,6 +115,10 @@ def generate_items(count: int, player_id: int, advancement: bool = False, code:
return items return items
def names(objs: list) -> List[str]:
return map(lambda o: o.name, objs)
class TestFillRestrictive(unittest.TestCase): class TestFillRestrictive(unittest.TestCase):
def test_basic_fill(self): def test_basic_fill(self):
multi_world = generate_multi_world() multi_world = generate_multi_world()
@ -331,6 +335,28 @@ class TestFillRestrictive(unittest.TestCase):
self.assertEqual(player2.locations[0].item, player1.prog_items[0]) self.assertEqual(player2.locations[0].item, player1.prog_items[0])
self.assertEqual(player2.locations[1].item, player1.prog_items[1]) self.assertEqual(player2.locations[1].item, player1.prog_items[1])
def test_restrictive_progress(self):
multi_world = generate_multi_world()
player1 = generate_player_data(multi_world, 1, prog_item_count=25)
items = player1.prog_items.copy()
multi_world.completion_condition[player1.id] = lambda state: state.has_all(
names(player1.prog_items), player1.id)
region1 = player1.generate_region(player1.menu, 5)
region2 = player1.generate_region(player1.menu, 5, lambda state: state.has_all(
names(items[2:7]), player1.id))
region3 = player1.generate_region(player1.menu, 5, lambda state: state.has_all(
names(items[7:12]), player1.id))
region4 = player1.generate_region(player1.menu, 5, lambda state: state.has_all(
names(items[12:17]), player1.id))
region5 = player1.generate_region(player1.menu, 5, lambda state: state.has_all(
names(items[17:22]), player1.id))
locations = multi_world.get_unfilled_locations()
fill_restrictive(multi_world, multi_world.state,
locations, player1.prog_items)
class TestDistributeItemsRestrictive(unittest.TestCase): class TestDistributeItemsRestrictive(unittest.TestCase):
def test_basic_distribute(self): def test_basic_distribute(self):
@ -546,7 +572,7 @@ class TestBalanceMultiworldProgression(unittest.TestCase):
# Sphere 1 # Sphere 1
region = player1.generate_region(player1.menu, 20) region = player1.generate_region(player1.menu, 20)
items = fillRegion(multi_world, region, [ items = fillRegion(multi_world, region, [
player1.prog_items[0]] + items) player1.prog_items[0]] + items)
# Sphere 2 # Sphere 2
region = player1.generate_region( region = player1.generate_region(
@ -558,7 +584,7 @@ class TestBalanceMultiworldProgression(unittest.TestCase):
region = player2.generate_region( region = player2.generate_region(
player2.menu, 20, lambda state: state.has(player2.prog_items[0].name, player2.id)) player2.menu, 20, lambda state: state.has(player2.prog_items[0].name, player2.id))
items = fillRegion(multi_world, region, [ items = fillRegion(multi_world, region, [
player2.prog_items[1]] + items) player2.prog_items[1]] + items)
multi_world.progression_balancing[player1.id] = True multi_world.progression_balancing[player1.id] = True
multi_world.progression_balancing[player2.id] = True multi_world.progression_balancing[player2.id] = True