Add fallback item swap for unreachable items
This commit is contained in:
parent
461961c3be
commit
6a34fe5184
42
Fill.py
42
Fill.py
|
@ -3,7 +3,7 @@ import typing
|
||||||
import collections
|
import collections
|
||||||
import itertools
|
import itertools
|
||||||
|
|
||||||
from BaseClasses import CollectionState, Location, MultiWorld
|
from BaseClasses import CollectionState, Location, MultiWorld, Item
|
||||||
from worlds.generic import PlandoItem
|
from worlds.generic import PlandoItem
|
||||||
from worlds.AutoWorld import call_all
|
from worlds.AutoWorld import call_all
|
||||||
|
|
||||||
|
@ -12,15 +12,16 @@ class FillError(RuntimeError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def fill_restrictive(world: MultiWorld, base_state: CollectionState, locations, itempool, single_player_placement=False,
|
def sweep_from_pool(base_state: CollectionState, itempool: list[Item]):
|
||||||
lock=False):
|
|
||||||
def sweep_from_pool():
|
|
||||||
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)
|
||||||
new_state.sweep_for_events()
|
new_state.sweep_for_events()
|
||||||
return new_state
|
return new_state
|
||||||
|
|
||||||
|
|
||||||
|
def fill_restrictive(world: MultiWorld, base_state: CollectionState, locations, itempool: list[Item], single_player_placement=False,
|
||||||
|
lock=False):
|
||||||
unplaced_items = []
|
unplaced_items = []
|
||||||
placements = []
|
placements = []
|
||||||
|
|
||||||
|
@ -29,13 +30,16 @@ def fill_restrictive(world: MultiWorld, base_state: CollectionState, locations,
|
||||||
reachable_items.setdefault(item.player, []).append(item)
|
reachable_items.setdefault(item.player, []).append(item)
|
||||||
|
|
||||||
while any(reachable_items.values()) and locations:
|
while any(reachable_items.values()) and locations:
|
||||||
items_to_place = [items.pop() for items in reachable_items.values() if items] # grab one item per player
|
# grab one item per player
|
||||||
|
items_to_place = [items.pop()
|
||||||
|
for items in reachable_items.values() if items]
|
||||||
for item in items_to_place:
|
for item in items_to_place:
|
||||||
itempool.remove(item)
|
itempool.remove(item)
|
||||||
maximum_exploration_state = sweep_from_pool()
|
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:
|
||||||
|
spot_to_fill: Location = None
|
||||||
if world.accessibility[item_to_place.player] == 'minimal':
|
if world.accessibility[item_to_place.player] == 'minimal':
|
||||||
perform_access_check = not world.has_beaten_game(maximum_exploration_state,
|
perform_access_check = not world.has_beaten_game(maximum_exploration_state,
|
||||||
item_to_place.player) if single_player_placement else not has_beaten_game
|
item_to_place.player) if single_player_placement else not has_beaten_game
|
||||||
|
@ -45,12 +49,34 @@ def fill_restrictive(world: MultiWorld, base_state: CollectionState, locations,
|
||||||
for i, location in enumerate(locations):
|
for i, location in enumerate(locations):
|
||||||
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(maximum_exploration_state, item_to_place, perform_access_check):
|
and location.can_fill(maximum_exploration_state, item_to_place, perform_access_check):
|
||||||
spot_to_fill = locations.pop(i) # poping by index is faster than removing by content,
|
# poping by index is faster than removing by content,
|
||||||
|
spot_to_fill = locations.pop(i)
|
||||||
# skipping a scan for the element
|
# skipping a scan for the element
|
||||||
break
|
break
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# we filled all reachable spots. Maybe the game can be beaten anyway?
|
# we filled all reachable spots.
|
||||||
|
# try swaping this item with previously placed items
|
||||||
|
for(i, location) in enumerate(placements):
|
||||||
|
placed_item = location.item
|
||||||
|
location.item = None
|
||||||
|
placed_item.location = None
|
||||||
|
swap_state = sweep_from_pool(base_state, itempool)
|
||||||
|
if (not single_player_placement or location.player == item_to_place.player) \
|
||||||
|
and location.can_fill(swap_state, item_to_place, perform_access_check):
|
||||||
|
# Add this item to the exisiting placement, and
|
||||||
|
# add the old item to the back of the queue
|
||||||
|
spot_to_fill = placements.pop(i)
|
||||||
|
reachable_items.setdefault(placed_item.player, []).append(placed_item)
|
||||||
|
itempool.append(placed_item)
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
# Item can't be placed here, restore original item
|
||||||
|
location.item = placed_item
|
||||||
|
placed_item.location = location
|
||||||
|
|
||||||
|
if spot_to_fill == None:
|
||||||
|
# Maybe the game can be beaten anyway?
|
||||||
unplaced_items.append(item_to_place)
|
unplaced_items.append(item_to_place)
|
||||||
if world.accessibility[item_to_place.player] != 'minimal' and world.can_beat_game():
|
if world.accessibility[item_to_place.player] != 'minimal' and world.can_beat_game():
|
||||||
logging.warning(
|
logging.warning(
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import unittest
|
import unittest
|
||||||
|
import pytest
|
||||||
from worlds.AutoWorld import World
|
from worlds.AutoWorld import World
|
||||||
from Fill import fill_restrictive
|
from Fill import FillError, fill_restrictive
|
||||||
from BaseClasses import MultiWorld, Region, RegionType, Item, Location
|
from BaseClasses import MultiWorld, Region, RegionType, Item, Location
|
||||||
from worlds.generic.Rules import set_rule
|
from worlds.generic.Rules import set_rule
|
||||||
|
|
||||||
|
@ -106,3 +107,46 @@ class TestBase(unittest.TestCase):
|
||||||
|
|
||||||
self.assertEqual(loc0.item, item1)
|
self.assertEqual(loc0.item, item1)
|
||||||
self.assertEqual(loc1.item, item0)
|
self.assertEqual(loc1.item, item0)
|
||||||
|
|
||||||
|
def test_impossible_fill_restrictive(self):
|
||||||
|
multi_world = generate_multi_world()
|
||||||
|
player1_id = 1
|
||||||
|
player1_menu = multi_world.get_region("Menu", player1_id)
|
||||||
|
|
||||||
|
locations = generate_locations(2, player1_id, None, player1_menu)
|
||||||
|
items = generate_items(2, player1_id, True)
|
||||||
|
|
||||||
|
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))
|
||||||
|
with pytest.raises(FillError):
|
||||||
|
fill_restrictive(multi_world, multi_world.state, locations, items)
|
||||||
|
|
||||||
|
def test_circular_fill_restrictive(self):
|
||||||
|
multi_world = generate_multi_world()
|
||||||
|
player1_id = 1
|
||||||
|
player1_menu = multi_world.get_region("Menu", player1_id)
|
||||||
|
|
||||||
|
locations = generate_locations(3, player1_id, None, player1_menu)
|
||||||
|
items = generate_items(3, player1_id, True)
|
||||||
|
|
||||||
|
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))
|
||||||
|
with pytest.raises(FillError):
|
||||||
|
fill_restrictive(multi_world, multi_world.state, locations, items)
|
Loading…
Reference in New Issue