Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
CookieCat45 committed Nov 5, 2023
2 parents b8948bc + bd8698e commit a2360fe
Show file tree
Hide file tree
Showing 101 changed files with 1,186 additions and 1,458 deletions.
218 changes: 76 additions & 142 deletions BaseClasses.py

Large diffs are not rendered by default.

45 changes: 10 additions & 35 deletions Fill.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,6 @@ class FillError(RuntimeError):
pass


def _log_fill_progress(name: str, placed: int, total_items: int) -> None:
logging.info(f"Current fill step ({name}) at {placed}/{total_items} items placed.")


def sweep_from_pool(base_state: CollectionState, itempool: typing.Sequence[Item] = tuple()) -> CollectionState:
new_state = base_state.copy()
for item in itempool:
Expand All @@ -30,7 +26,7 @@ def sweep_from_pool(base_state: CollectionState, itempool: typing.Sequence[Item]
def fill_restrictive(world: MultiWorld, base_state: CollectionState, locations: typing.List[Location],
item_pool: typing.List[Item], single_player_placement: bool = False, lock: bool = False,
swap: bool = True, on_place: typing.Optional[typing.Callable[[Location], None]] = None,
allow_partial: bool = False, allow_excluded: bool = False, name: str = "Unknown") -> None:
allow_partial: bool = False, allow_excluded: bool = False) -> None:
"""
:param world: Multiworld to be filled.
:param base_state: State assumed before fill.
Expand All @@ -42,20 +38,16 @@ def fill_restrictive(world: MultiWorld, base_state: CollectionState, locations:
:param on_place: callback that is called when a placement happens
:param allow_partial: only place what is possible. Remaining items will be in the item_pool list.
:param allow_excluded: if true and placement fails, it is re-attempted while ignoring excluded on Locations
:param name: name of this fill step for progress logging purposes
"""
unplaced_items: typing.List[Item] = []
placements: typing.List[Location] = []
cleanup_required = False

swapped_items: typing.Counter[typing.Tuple[int, str, bool]] = Counter()
reachable_items: typing.Dict[int, typing.Deque[Item]] = {}
for item in item_pool:
reachable_items.setdefault(item.player, deque()).append(item)

# for progress logging
total = min(len(item_pool), len(locations))
placed = 0

while any(reachable_items.values()) and locations:
# grab one item per player
items_to_place = [items.pop()
Expand Down Expand Up @@ -160,15 +152,9 @@ def fill_restrictive(world: MultiWorld, base_state: CollectionState, locations:
spot_to_fill.locked = lock
placements.append(spot_to_fill)
spot_to_fill.event = item_to_place.advancement
placed += 1
if not placed % 1000:
_log_fill_progress(name, placed, total)
if on_place:
on_place(spot_to_fill)

if total > 1000:
_log_fill_progress(name, placed, total)

if cleanup_required:
# validate all placements and remove invalid ones
state = sweep_from_pool(base_state, [])
Expand Down Expand Up @@ -212,8 +198,6 @@ def remaining_fill(world: MultiWorld,
unplaced_items: typing.List[Item] = []
placements: typing.List[Location] = []
swapped_items: typing.Counter[typing.Tuple[int, str]] = Counter()
total = min(len(itempool), len(locations))
placed = 0
while locations and itempool:
item_to_place = itempool.pop()
spot_to_fill: typing.Optional[Location] = None
Expand Down Expand Up @@ -263,12 +247,6 @@ def remaining_fill(world: MultiWorld,

world.push_item(spot_to_fill, item_to_place, False)
placements.append(spot_to_fill)
placed += 1
if not placed % 1000:
_log_fill_progress("Remaining", placed, total)

if total > 1000:
_log_fill_progress("Remaining", placed, total)

if unplaced_items and locations:
# There are leftover unplaceable items and locations that won't accept them
Expand Down Expand Up @@ -304,7 +282,7 @@ def accessibility_corrections(world: MultiWorld, state: CollectionState, locatio
locations.append(location)
if pool and locations:
locations.sort(key=lambda loc: loc.progress_type != LocationProgressType.PRIORITY)
fill_restrictive(world, state, locations, pool, name="Accessibility Corrections")
fill_restrictive(world, state, locations, pool)


def inaccessible_location_rules(world: MultiWorld, state: CollectionState, locations):
Expand Down Expand Up @@ -374,25 +352,23 @@ def distribute_early_items(world: MultiWorld,
player_local = early_local_rest_items[player]
fill_restrictive(world, base_state,
[loc for loc in early_locations if loc.player == player],
player_local, lock=True, allow_partial=True, name=f"Local Early Items P{player}")
player_local, lock=True, allow_partial=True)
if player_local:
logging.warning(f"Could not fulfill rules of early items: {player_local}")
early_rest_items.extend(early_local_rest_items[player])
early_locations = [loc for loc in early_locations if not loc.item]
fill_restrictive(world, base_state, early_locations, early_rest_items, lock=True, allow_partial=True,
name="Early Items")
fill_restrictive(world, base_state, early_locations, early_rest_items, lock=True, allow_partial=True)
early_locations += early_priority_locations
for player in world.player_ids:
player_local = early_local_prog_items[player]
fill_restrictive(world, base_state,
[loc for loc in early_locations if loc.player == player],
player_local, lock=True, allow_partial=True, name=f"Local Early Progression P{player}")
player_local, lock=True, allow_partial=True)
if player_local:
logging.warning(f"Could not fulfill rules of early items: {player_local}")
early_prog_items.extend(player_local)
early_locations = [loc for loc in early_locations if not loc.item]
fill_restrictive(world, base_state, early_locations, early_prog_items, lock=True, allow_partial=True,
name="Early Progression")
fill_restrictive(world, base_state, early_locations, early_prog_items, lock=True, allow_partial=True)
unplaced_early_items = early_rest_items + early_prog_items
if unplaced_early_items:
logging.warning("Ran out of early locations for early items. Failed to place "
Expand Down Expand Up @@ -446,14 +422,13 @@ def mark_for_locking(location: Location):

if prioritylocations:
# "priority fill"
fill_restrictive(world, world.state, prioritylocations, progitempool, swap=False, on_place=mark_for_locking,
name="Priority")
fill_restrictive(world, world.state, prioritylocations, progitempool, swap=False, on_place=mark_for_locking)
accessibility_corrections(world, world.state, prioritylocations, progitempool)
defaultlocations = prioritylocations + defaultlocations

if progitempool:
# "advancement/progression fill"
fill_restrictive(world, world.state, defaultlocations, progitempool, name="Progression")
# "progression fill"
fill_restrictive(world, world.state, defaultlocations, progitempool)
if progitempool:
raise FillError(
f'Not enough locations for progress items. There are {len(progitempool)} more items than locations')
Expand Down
17 changes: 4 additions & 13 deletions Generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
import string
import urllib.parse
import urllib.request
from collections import Counter
from typing import Any, Dict, Tuple, Union
from collections import ChainMap, Counter
from typing import Any, Callable, Dict, Tuple, Union

import ModuleUpdate

Expand Down Expand Up @@ -225,7 +225,7 @@ def main(args=None, callback=ERmain):
with open(os.path.join(args.outputpath if args.outputpath else ".", f"generate_{seed_name}.yaml"), "wt") as f:
yaml.dump(important, f)

return callback(erargs, seed)
callback(erargs, seed)


def read_weights_yamls(path) -> Tuple[Any, ...]:
Expand Down Expand Up @@ -639,15 +639,6 @@ def roll_alttp_settings(ret: argparse.Namespace, weights, plando_options):
if __name__ == '__main__':
import atexit
confirmation = atexit.register(input, "Press enter to close.")
multiworld = main()
if __debug__:
import gc
import sys
import weakref
weak = weakref.ref(multiworld)
del multiworld
gc.collect() # need to collect to deref all hard references
assert not weak(), f"MultiWorld object was not de-allocated, it's referenced {sys.getrefcount(weak())} times." \
" This would be a memory leak."
main()
# in case of error-free exit should not need confirmation
atexit.unregister(confirmation)
7 changes: 6 additions & 1 deletion Main.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,10 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No
logger.info('Creating Items.')
AutoWorld.call_all(world, "create_items")

# All worlds should have finished creating all regions, locations, and entrances.
# Recache to ensure that they are all visible for locality rules.
world._recache()

logger.info('Calculating Access Rules.')

for player in world.player_ids:
Expand Down Expand Up @@ -229,7 +233,7 @@ def find_common_pool(players: Set[int], shared_pool: Set[str]) -> Tuple[

region = Region("Menu", group_id, world, "ItemLink")
world.regions.append(region)
locations = region.locations
locations = region.locations = []
for item in world.itempool:
count = common_item_count.get(item.player, {}).get(item.name, 0)
if count:
Expand Down Expand Up @@ -263,6 +267,7 @@ def find_common_pool(players: Set[int], shared_pool: Set[str]) -> Tuple[
world.itempool.extend(items_to_add[:itemcount - len(world.itempool)])

if any(world.item_links.values()):
world._recache()
world._all_state = None

logger.info("Running Item Plando")
Expand Down
4 changes: 2 additions & 2 deletions SNIClient.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,12 +207,12 @@ def on_deathlink(self, data: typing.Dict[str, typing.Any]) -> None:
self.killing_player_task = asyncio.create_task(deathlink_kill_player(self))
super(SNIContext, self).on_deathlink(data)

async def handle_deathlink_state(self, currently_dead: bool, death_text: str = "") -> None:
async def handle_deathlink_state(self, currently_dead: bool) -> None:
# in this state we only care about triggering a death send
if self.death_state == DeathState.alive:
if currently_dead:
self.death_state = DeathState.dead
await self.send_death(death_text)
await self.send_death()
# in this state we care about confirming a kill, to move state to dead
elif self.death_state == DeathState.killing_player:
# this is being handled in deathlink_kill_player(ctx) already
Expand Down
64 changes: 10 additions & 54 deletions Utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import typing
import builtins
import os
import itertools
import subprocess
import sys
import pickle
Expand Down Expand Up @@ -74,8 +73,6 @@ def snes_to_pc(value: int) -> int:


RetType = typing.TypeVar("RetType")
S = typing.TypeVar("S")
T = typing.TypeVar("T")


def cache_argsless(function: typing.Callable[[], RetType]) -> typing.Callable[[], RetType]:
Expand All @@ -93,31 +90,6 @@ def _wrap() -> RetType:
return _wrap


def cache_self1(function: typing.Callable[[S, T], RetType]) -> typing.Callable[[S, T], RetType]:
"""Specialized cache for self + 1 arg. Does not keep global ref to self and skips building a dict key tuple."""

assert function.__code__.co_argcount == 2, "Can only cache 2 argument functions with this cache."

cache_name = f"__cache_{function.__name__}__"

@functools.wraps(function)
def wrap(self: S, arg: T) -> RetType:
cache: Optional[Dict[T, RetType]] = typing.cast(Optional[Dict[T, RetType]],
getattr(self, cache_name, None))
if cache is None:
res = function(self, arg)
setattr(self, cache_name, {arg: res})
return res
try:
return cache[arg]
except KeyError:
res = function(self, arg)
cache[arg] = res
return res

return wrap


def is_frozen() -> bool:
return typing.cast(bool, getattr(sys, 'frozen', False))

Expand Down Expand Up @@ -174,16 +146,12 @@ def user_path(*path: str) -> str:
if user_path.cached_path != local_path():
import filecmp
if not os.path.exists(user_path("manifest.json")) or \
not os.path.exists(local_path("manifest.json")) or \
not filecmp.cmp(local_path("manifest.json"), user_path("manifest.json"), shallow=True):
import shutil
for dn in ("Players", "data/sprites", "data/lua"):
for dn in ("Players", "data/sprites"):
shutil.copytree(local_path(dn), user_path(dn), dirs_exist_ok=True)
if not os.path.exists(local_path("manifest.json")):
warnings.warn(f"Upgrading {user_path()} from something that is not a proper install")
else:
shutil.copy2(local_path("manifest.json"), user_path("manifest.json"))
os.makedirs(user_path("worlds"), exist_ok=True)
for fn in ("manifest.json",):
shutil.copy2(local_path(fn), user_path(fn))

return os.path.join(user_path.cached_path, *path)

Expand Down Expand Up @@ -289,13 +257,15 @@ def get_public_ipv6() -> str:
return ip


OptionsType = Settings # TODO: remove when removing get_options
OptionsType = Settings # TODO: remove ~2 versions after 0.4.1


def get_options() -> Settings:
# TODO: switch to Utils.deprecate after 0.4.4
warnings.warn("Utils.get_options() is deprecated. Use the settings API instead.", DeprecationWarning)
return get_settings()
@cache_argsless
def get_default_options() -> Settings: # TODO: remove ~2 versions after 0.4.1
return Settings(None)


get_options = get_settings # TODO: add a warning ~2 versions after 0.4.1 and remove once all games are ported


def persistent_store(category: str, key: typing.Any, value: typing.Any):
Expand Down Expand Up @@ -935,17 +905,3 @@ def visualize_other_regions() -> None:

with open(file_name, "wt", encoding="utf-8") as f:
f.write("\n".join(uml))


class RepeatableChain:
def __init__(self, iterable: typing.Iterable):
self.iterable = iterable

def __iter__(self):
return itertools.chain.from_iterable(self.iterable)

def __bool__(self):
return any(sub_iterable for sub_iterable in self.iterable)

def __len__(self):
return sum(len(iterable) for iterable in self.iterable)
6 changes: 0 additions & 6 deletions WebHostLib/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,13 +139,7 @@ def get_html_doc(option_type: type(Options.Option)) -> str:
weighted_options["games"][game_name] = {}
weighted_options["games"][game_name]["gameSettings"] = game_options
weighted_options["games"][game_name]["gameItems"] = tuple(world.item_names)
weighted_options["games"][game_name]["gameItemGroups"] = [
group for group in world.item_name_groups.keys() if group != "Everything"
]
weighted_options["games"][game_name]["gameLocations"] = tuple(world.location_names)
weighted_options["games"][game_name]["gameLocationGroups"] = [
group for group in world.location_name_groups.keys() if group != "Everywhere"
]

with open(os.path.join(target_folder, 'weighted-options.json'), "w") as f:
json.dump(weighted_options, f, indent=2, separators=(',', ': '))
Loading

0 comments on commit a2360fe

Please sign in to comment.