Core: optimize early items and add unit test (#1197)

* optimize early items and add unit test

* move sorting list init closer to sorting
This commit is contained in:
Doug Hoskisson 2022-11-04 09:56:47 -07:00 committed by GitHub
parent f1123f2662
commit c933fa7e34
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 82 additions and 17 deletions

45
Fill.py
View File

@ -248,16 +248,10 @@ def inaccessible_location_rules(world: MultiWorld, state: CollectionState, locat
add_item_rule(location, forbid_important_item_rule)
def distribute_items_restrictive(world: MultiWorld) -> None:
fill_locations = sorted(world.get_unfilled_locations())
world.random.shuffle(fill_locations)
# get items to distribute
itempool = sorted(world.itempool)
world.random.shuffle(itempool)
progitempool: typing.List[Item] = []
usefulitempool: typing.List[Item] = []
filleritempool: typing.List[Item] = []
def distribute_early_items(world: MultiWorld,
fill_locations: typing.List[Location],
itempool: typing.List[Item]) -> typing.Tuple[typing.List[Location], typing.List[Item]]:
""" returns new fill_locations and itempool """
early_items_count: typing.Dict[typing.Tuple[str, int], int] = {}
for player in world.player_ids:
for item, count in world.early_items[player].value.items():
@ -265,26 +259,32 @@ def distribute_items_restrictive(world: MultiWorld) -> None:
if early_items_count:
early_locations: typing.List[Location] = []
early_priority_locations: typing.List[Location] = []
for loc in reversed(fill_locations):
loc_indexes_to_remove: typing.Set[int] = set()
for i, loc in enumerate(fill_locations):
if loc.can_reach(world.state):
if loc.progress_type == LocationProgressType.PRIORITY:
early_priority_locations.append(loc)
else:
early_locations.append(loc)
fill_locations.remove(loc)
loc_indexes_to_remove.add(i)
fill_locations = [loc for i, loc in enumerate(fill_locations) if i not in loc_indexes_to_remove]
early_prog_items: typing.List[Item] = []
early_rest_items: typing.List[Item] = []
for item in reversed(itempool):
item_indexes_to_remove: typing.Set[int] = set()
for i, item in enumerate(itempool):
if (item.name, item.player) in early_items_count:
if item.advancement:
early_prog_items.append(item)
else:
early_rest_items.append(item)
itempool.remove(item)
item_indexes_to_remove.add(i)
early_items_count[(item.name, item.player)] -= 1
if early_items_count[(item.name, item.player)] == 0:
del early_items_count[(item.name, item.player)]
if len(early_items_count) == 0:
break
itempool = [item for i, item in enumerate(itempool) if i not in item_indexes_to_remove]
fill_restrictive(world, world.state, early_locations, early_rest_items, lock=True)
early_locations += early_priority_locations
fill_restrictive(world, world.state, early_locations, early_prog_items, lock=True)
@ -294,8 +294,23 @@ def distribute_items_restrictive(world: MultiWorld) -> None:
{len(unplaced_early_items)} items early.")
itempool += unplaced_early_items
fill_locations += early_locations
fill_locations.extend(early_locations)
world.random.shuffle(fill_locations)
return fill_locations, itempool
def distribute_items_restrictive(world: MultiWorld) -> None:
fill_locations = sorted(world.get_unfilled_locations())
world.random.shuffle(fill_locations)
# get items to distribute
itempool = sorted(world.itempool)
world.random.shuffle(itempool)
fill_locations, itempool = distribute_early_items(world, fill_locations, itempool)
progitempool: typing.List[Item] = []
usefulitempool: typing.List[Item] = []
filleritempool: typing.List[Item] = []
for item in itempool:
if item.advancement:

View File

@ -1,7 +1,8 @@
from typing import List, Iterable
import unittest
from worlds.AutoWorld import World
from Fill import FillError, balance_multiworld_progression, fill_restrictive, distribute_items_restrictive
from Fill import FillError, balance_multiworld_progression, fill_restrictive, \
distribute_early_items, distribute_items_restrictive
from BaseClasses import Entrance, LocationProgressType, MultiWorld, Region, RegionType, Item, Location, \
ItemClassification
from worlds.generic.Rules import CollectionRule, add_item_rule, locality_rules, set_rule
@ -13,7 +14,7 @@ def generate_multi_world(players: int = 1) -> MultiWorld:
for i in range(players):
player_id = i+1
world = World(multi_world, player_id)
multi_world.game[player_id] = world
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", RegionType.Generic,
@ -623,6 +624,55 @@ class TestDistributeItemsRestrictive(unittest.TestCase):
self.assertEqual(item.player, item.location.player)
self.assertFalse(item.location.event, False)
def test_early_items(self) -> None:
mw = generate_multi_world(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].value[player1.basic_items[0].name] = 1
mw.early_items[2].value[player2.basic_items[2].name] = 1
mw.early_items[2].value[player2.basic_items[3].name] = 1
early_items = [
player1.basic_items[0],
player2.basic_items[2],
player2.basic_items[3],
]
# copied this code from the beginning of `distribute_items_restrictive`
# before `distribute_early_items` is called
fill_locations = sorted(mw.get_unfilled_locations())
mw.random.shuffle(fill_locations)
itempool = sorted(mw.itempool)
mw.random.shuffle(itempool)
fill_locations, itempool = distribute_early_items(mw, fill_locations, itempool)
remaining_p1 = [item for item in itempool if item.player == 1]
remaining_p2 = [item for item in itempool if item.player == 2]
assert len(itempool) == 7, f"number of items remaining after early_items: {len(itempool)}"
assert len(remaining_p1) == 4, f"number of p1 items after early_items: {len(remaining_p1)}"
assert len(remaining_p2) == 3, f"number of p2 items after early_items: {len(remaining_p1)}"
for i in range(5):
if i != 0:
assert player1.basic_items[i] in itempool, "non-early item to remain in itempool"
if i not in {2, 3}:
assert player2.basic_items[i] in itempool, "non-early item to remain in itempool"
for item in early_items:
assert item not in itempool, "early item to be taken out of itempool"
assert len(fill_locations) == len(mw.get_locations()) - len(early_items), \
f"early location count from {mw.get_locations()} to {len(fill_locations)} " \
f"after {len(early_items)} early items"
items_in_locations = {loc.item for loc in mw.get_locations() if loc.item}
assert len(items_in_locations) == len(early_items), \
f"{len(early_items)} early items in {len(items_in_locations)} locations"
for item in early_items:
assert item in items_in_locations, "early item to be placed in location"
class TestBalanceMultiworldProgression(unittest.TestCase):
def assertRegionContains(self, region: Region, item: Item) -> bool: