apworld mode april-fools-release
authorJordan Rose <jrose@belkadan.com>
Mon, 31 Mar 2025 01:53:28 +0000 (18:53 -0700)
committerJordan Rose <jrose@belkadan.com>
Mon, 31 Mar 2025 02:26:43 +0000 (19:26 -0700)
.gitignore [new file with mode: 0644]
Makefile [new file with mode: 0644]
__init__.py [new file with mode: 0644]
apworld/sicp/__init__.py
apworld/sicp/client.py
apworld/sicp/locations.py
apworld/sicp/util.py [new file with mode: 0644]
game.py

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..fdc72cc
--- /dev/null
@@ -0,0 +1,2 @@
+__pycache__
+sicp.apworld
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..c996fea
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,2 @@
+sicp.apworld: apworld/sicp/*.py apworld/sicp/*.yml apworld/sicp/game/*.py apworld/sicp/game/game_prelude.scm apworld/sicp/game/*.yml
+   (cd apworld && zip ../$@ $(subst apworld/,,$^))
\ No newline at end of file
diff --git a/__init__.py b/__init__.py
new file mode 100644 (file)
index 0000000..410ab3d
--- /dev/null
@@ -0,0 +1 @@
+# Placeholder to allow treating this directory as a module.
index 1152ba056fac2112a0ba98cbb1f3430ee3616d03..fc70bbfba5422da01769a09154d1dc5ce4e73d67 100644 (file)
@@ -13,6 +13,7 @@ from worlds.AutoWorld import World
 from worlds.LauncherComponents import Component, SuffixIdentifier, components, Type, launch
 
 from .locations import all_locations, Location, LocationData, OriginRegionName, set_up_rules
+from .util import load_resource
 
 GAME = "SICPelago"
 
@@ -40,8 +41,8 @@ class ItemData:
             return ItemClassification.trap
         return ItemClassification.filler
 
-with open(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'items.yml'), 'r') as input:
-    all_items = {data['name']: ItemData(data) for data in yaml.safe_load(input)}
+
+all_items = {data['name']: ItemData(data) for data in yaml.safe_load(load_resource('items.yml'))}
 
 
 class SicpWorld(World):
index a08d00a80468817dc2243d9a58900a496686e949..dc3ad629b02c30859d9d69c1a1b5f5e187807910 100644 (file)
@@ -28,6 +28,7 @@ from MultiServer import mark_raw
 from NetUtils import ClientStatus
 
 from . import all_items
+from .util import load_resource
 from .game import game
 from .game.scheme import buffer_input, scheme_read, scheme_eval, scheme_format, SchemeError
 
@@ -45,7 +46,7 @@ async def send_locations(ctx: Context, problem: game.Problem):
         ctx.should_wait_for_events = True
 
 async def game_loop(ctx: Context):
-    game.setup()
+    game.setup(load_resource=lambda rsrc: load_resource(os.path.join('game', rsrc)))
     unsent_checks = []
     game.problem_solved_hook = lambda problem: unsent_checks.append(problem) or True
     env = game.ThePlayerFrame
index 117ab57e95b5658066ee88cd53054ce4bb491837..cb9e56dac9ef0acac7e4f2a594f71d1d754ee2a8 100644 (file)
@@ -6,6 +6,8 @@ from typing import Any, Callable
 import BaseClasses
 from worlds.generic.Rules import set_rule
 
+from .util import load_resource
+
 GAME = "SICPelago"
 OriginRegionName = "REPL"
 
@@ -21,8 +23,7 @@ class LocationData:
         (chapter, exercise) = str(self).split(' ')[0].split('.')
         return int(chapter) * 100 + int(exercise)
 
-with open(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'locations.yml'), 'r') as input:
-    all_locations = [LocationData(data) for data in yaml.safe_load(input)]
+all_locations = [LocationData(data) for data in yaml.safe_load(load_resource('locations.yml'))]
 
 
 def set_up_rules(loc: LocationData, root: BaseClasses.Region, region: BaseClasses.Region, player: int):
diff --git a/apworld/sicp/util.py b/apworld/sicp/util.py
new file mode 100644 (file)
index 0000000..e9a2376
--- /dev/null
@@ -0,0 +1,11 @@
+import io
+import os.path
+
+def load_resource(resource_name: str) -> bytes:
+    dirname = os.path.dirname(os.path.realpath(__file__))
+    if '.apworld/' in dirname:
+        import zipfile
+        with zipfile.ZipFile(os.path.dirname(dirname)) as apworld:
+            return apworld.read(os.path.join(os.path.basename(dirname), resource_name))
+    with open(os.path.join(dirname, resource_name), 'rb') as rsrc:
+        return rsrc.read()
diff --git a/game.py b/game.py
index a9178143f7ec052aa45f3a9dabc9786ee6fb3fdb..0f649907b59e0768cddf2e201db4d60e12abf4a6 100644 (file)
--- a/game.py
+++ b/game.py
@@ -2,9 +2,10 @@ if __name__ == '__main__':
     __path__ = ["."]
 
 from .scheme import *
+from .scheme_tokens import tokenize_lines
 from .ucb import main
 
-from collections.abc import Iterable, Iterator
+from collections.abc import Callable, Iterable, Iterator
 from typing import Any, Optional, Union
 
 import itertools
@@ -205,10 +206,9 @@ def shadow_all_forms_except(frame: Frame, *exceptions: Symbol):
         if name not in exceptions and not name.startswith("#"):
             frame.define(name, Locked(name))
 
-def load_problems(problems: dict[str, Problem], yaml_path: str):
-    with open(yaml_path, 'r') as input:
-        for problem_data in yaml.safe_load(input):
-            problems[Problem.label_for(problem_data)] = Problem(problem_data)
+def load_problems(problems: dict[str, Problem], problem_yaml: bytes):
+    for problem_data in yaml.safe_load(problem_yaml):
+        problems[Problem.label_for(problem_data)] = Problem(problem_data)
 
 def generate_problem_rewards_except(problems: Iterable[str], frame_of_locks: Frame, *exceptions: Symbol) -> dict[str, list[Union[Symbol, MacGuffin]]]:
     rewards: dict[str, list[Union[Symbol, MacGuffin]]] = {}
@@ -235,15 +235,25 @@ ThePlayerFrame = Frame(TheLockingFrame)
 Problems: dict[str, Problem] = {}
 ProblemRewards: dict[str, Optional[list[Union[Symbol, MacGuffin]]]] = {}
 
-def setup() -> None:
+def default_load_resource(resource_name: str) -> bytes:
+    dirname = os.path.dirname(os.path.realpath(__file__))
+    with open(os.path.join(dirname, resource_name), 'rb') as rsrc:
+        return rsrc.read()
+
+def setup(load_resource: Optional[Callable[[str], bytes]] = None) -> None:
+    if not load_resource:
+        load_resource = default_load_resource
+
     TheGlobalFrame.define('#undef', SpecialForm('#undef', do_undef))
 
-    game_dir = os.path.dirname(os.path.realpath(__file__))
-    prelude_path = os.path.join(game_dir, "game_prelude.scm")
-    scheme_load(prelude_path, True, TheGlobalFrame)
+    prelude = str(load_resource('game_prelude.scm'), 'utf-8')
+    prelude_buf = Buffer(tokenize_lines(prelude.splitlines()))
+    while prelude_buf.current():
+        expr = scheme_read(prelude_buf)
+        scheme_eval(expr, TheGlobalFrame)
 
     shadow_all_forms_except(TheLockingFrame, 'lambda', 'quote', 'display', 'print', 'newline', 'error')
-    load_problems(Problems, os.path.join(game_dir, 'locations.yml'))
+    load_problems(Problems, load_resource('locations.yml'))
     global ProblemRewards
     ProblemRewards = generate_problem_rewards_except(Problems.keys(), TheLockingFrame, 'eval', 'exit', 'load')