dungeoncoder 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. dungeoncoder-0.2.0/LICENSE.txt +25 -0
  2. dungeoncoder-0.2.0/PKG-INFO +37 -0
  3. dungeoncoder-0.2.0/README.md +22 -0
  4. dungeoncoder-0.2.0/dungeoncoder/__init__.py +4 -0
  5. dungeoncoder-0.2.0/dungeoncoder/__main__.py +254 -0
  6. dungeoncoder-0.2.0/dungeoncoder/_api_config.py +6 -0
  7. dungeoncoder-0.2.0/dungeoncoder/_generated/__init__.py +8 -0
  8. dungeoncoder-0.2.0/dungeoncoder/_generated/api/__init__.py +1 -0
  9. dungeoncoder-0.2.0/dungeoncoder/_generated/api/game/__init__.py +1 -0
  10. dungeoncoder-0.2.0/dungeoncoder/_generated/api/game/get_statistics.py +158 -0
  11. dungeoncoder-0.2.0/dungeoncoder/_generated/api/game/get_version.py +166 -0
  12. dungeoncoder-0.2.0/dungeoncoder/_generated/api/hero/__init__.py +1 -0
  13. dungeoncoder-0.2.0/dungeoncoder/_generated/api/hero/ask_oracle.py +192 -0
  14. dungeoncoder-0.2.0/dungeoncoder/_generated/api/hero/configure.py +207 -0
  15. dungeoncoder-0.2.0/dungeoncoder/_generated/api/hero/drop.py +207 -0
  16. dungeoncoder-0.2.0/dungeoncoder/_generated/api/hero/get_inventory.py +188 -0
  17. dungeoncoder-0.2.0/dungeoncoder/_generated/api/hero/get_items_at_position.py +188 -0
  18. dungeoncoder-0.2.0/dungeoncoder/_generated/api/hero/interact.py +188 -0
  19. dungeoncoder-0.2.0/dungeoncoder/_generated/api/hero/is_abyss_in_front.py +188 -0
  20. dungeoncoder-0.2.0/dungeoncoder/_generated/api/hero/is_at_goal.py +192 -0
  21. dungeoncoder-0.2.0/dungeoncoder/_generated/api/hero/is_collision_in_front.py +188 -0
  22. dungeoncoder-0.2.0/dungeoncoder/_generated/api/hero/is_enemy_in_front.py +188 -0
  23. dungeoncoder-0.2.0/dungeoncoder/_generated/api/hero/is_facing_north.py +188 -0
  24. dungeoncoder-0.2.0/dungeoncoder/_generated/api/hero/is_switch_in_front.py +188 -0
  25. dungeoncoder-0.2.0/dungeoncoder/_generated/api/hero/is_torch_in_front.py +188 -0
  26. dungeoncoder-0.2.0/dungeoncoder/_generated/api/hero/move.py +188 -0
  27. dungeoncoder-0.2.0/dungeoncoder/_generated/api/hero/peek_item_value.py +211 -0
  28. dungeoncoder-0.2.0/dungeoncoder/_generated/api/hero/pickup.py +207 -0
  29. dungeoncoder-0.2.0/dungeoncoder/_generated/api/hero/read_item_value.py +188 -0
  30. dungeoncoder-0.2.0/dungeoncoder/_generated/api/hero/sense_goal.py +192 -0
  31. dungeoncoder-0.2.0/dungeoncoder/_generated/api/hero/set_pace.py +207 -0
  32. dungeoncoder-0.2.0/dungeoncoder/_generated/api/hero/turn_left.py +188 -0
  33. dungeoncoder-0.2.0/dungeoncoder/_generated/api/level/__init__.py +1 -0
  34. dungeoncoder-0.2.0/dungeoncoder/_generated/api/level/load_level.py +190 -0
  35. dungeoncoder-0.2.0/dungeoncoder/_generated/api/level/reset_level.py +158 -0
  36. dungeoncoder-0.2.0/dungeoncoder/_generated/client.py +271 -0
  37. dungeoncoder-0.2.0/dungeoncoder/_generated/errors.py +14 -0
  38. dungeoncoder-0.2.0/dungeoncoder/_generated/models/__init__.py +47 -0
  39. dungeoncoder-0.2.0/dungeoncoder/_generated/models/api_error_response.py +98 -0
  40. dungeoncoder-0.2.0/dungeoncoder/_generated/models/api_error_response_status.py +7 -0
  41. dungeoncoder-0.2.0/dungeoncoder/_generated/models/boolean_result.py +96 -0
  42. dungeoncoder-0.2.0/dungeoncoder/_generated/models/boolean_result_status.py +7 -0
  43. dungeoncoder-0.2.0/dungeoncoder/_generated/models/configure_params.py +84 -0
  44. dungeoncoder-0.2.0/dungeoncoder/_generated/models/distance_param.py +76 -0
  45. dungeoncoder-0.2.0/dungeoncoder/_generated/models/name_param.py +76 -0
  46. dungeoncoder-0.2.0/dungeoncoder/_generated/models/nullable_integer_result.py +104 -0
  47. dungeoncoder-0.2.0/dungeoncoder/_generated/models/nullable_integer_result_status.py +7 -0
  48. dungeoncoder-0.2.0/dungeoncoder/_generated/models/nullable_string_result.py +104 -0
  49. dungeoncoder-0.2.0/dungeoncoder/_generated/models/nullable_string_result_status.py +7 -0
  50. dungeoncoder-0.2.0/dungeoncoder/_generated/models/pace_params.py +76 -0
  51. dungeoncoder-0.2.0/dungeoncoder/_generated/models/statistics.py +217 -0
  52. dungeoncoder-0.2.0/dungeoncoder/_generated/models/statistics_result.py +104 -0
  53. dungeoncoder-0.2.0/dungeoncoder/_generated/models/statistics_result_status.py +7 -0
  54. dungeoncoder-0.2.0/dungeoncoder/_generated/models/string_array_result.py +100 -0
  55. dungeoncoder-0.2.0/dungeoncoder/_generated/models/string_array_result_status.py +7 -0
  56. dungeoncoder-0.2.0/dungeoncoder/_generated/models/tiled_level.py +67 -0
  57. dungeoncoder-0.2.0/dungeoncoder/_generated/models/version_result.py +104 -0
  58. dungeoncoder-0.2.0/dungeoncoder/_generated/models/version_result_result.py +84 -0
  59. dungeoncoder-0.2.0/dungeoncoder/_generated/models/version_result_status.py +7 -0
  60. dungeoncoder-0.2.0/dungeoncoder/_generated/types.py +53 -0
  61. dungeoncoder-0.2.0/dungeoncoder/asciimap.py +824 -0
  62. dungeoncoder-0.2.0/dungeoncoder/dungeoncoder.py +482 -0
  63. dungeoncoder-0.2.0/dungeoncoder/generator.py +213 -0
  64. dungeoncoder-0.2.0/dungeoncoder/packs/gelaende.json +1 -0
  65. dungeoncoder-0.2.0/dungeoncoder/packs/items.json +67 -0
  66. dungeoncoder-0.2.0/dungeoncoder/packs/station.json +1 -0
  67. dungeoncoder-0.2.0/dungeoncoder/packs/studio.json +1 -0
  68. dungeoncoder-0.2.0/dungeoncoder/packs/tilesets.json +1 -0
  69. dungeoncoder-0.2.0/dungeoncoder/packs/werkstatt.json +1 -0
  70. dungeoncoder-0.2.0/dungeoncoder/replay.py +80 -0
  71. dungeoncoder-0.2.0/dungeoncoder/requirements.txt +2 -0
  72. dungeoncoder-0.2.0/dungeoncoder/sim.py +792 -0
  73. dungeoncoder-0.2.0/dungeoncoder/testing.py +63 -0
  74. dungeoncoder-0.2.0/dungeoncoder/trace.py +92 -0
  75. dungeoncoder-0.2.0/dungeoncoder/variants.py +112 -0
  76. dungeoncoder-0.2.0/dungeoncoder.egg-info/PKG-INFO +37 -0
  77. dungeoncoder-0.2.0/dungeoncoder.egg-info/SOURCES.txt +80 -0
  78. dungeoncoder-0.2.0/dungeoncoder.egg-info/dependency_links.txt +1 -0
  79. dungeoncoder-0.2.0/dungeoncoder.egg-info/requires.txt +2 -0
  80. dungeoncoder-0.2.0/dungeoncoder.egg-info/top_level.txt +1 -0
  81. dungeoncoder-0.2.0/pyproject.toml +30 -0
  82. dungeoncoder-0.2.0/setup.cfg +4 -0
@@ -0,0 +1,25 @@
1
+ Copyright (c) Microsoft Corporation
2
+ Copyright (c) Munich University of Applied Sciences
3
+ Copyright (c) Benedikt Dietrich
4
+
5
+ All rights reserved.
6
+
7
+ MIT License
8
+
9
+ Permission is hereby granted, free of charge, to any person obtaining a copy
10
+ of this software and associated documentation files (the "Software"), to deal
11
+ in the Software without restriction, including without limitation the rights
12
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
+ copies of the Software, and to permit persons to whom the Software is
14
+ furnished to do so, subject to the following conditions:
15
+
16
+ The above copyright notice and this permission notice shall be included in all
17
+ copies or substantial portions of the Software.
18
+
19
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25
+ SOFTWARE.
@@ -0,0 +1,37 @@
1
+ Metadata-Version: 2.4
2
+ Name: dungeoncoder
3
+ Version: 0.2.0
4
+ Summary: Python API for Dungeon Coder: steer the hero from a script, in VS Code or in the built-in simulator
5
+ License-Expression: MIT
6
+ Classifier: Programming Language :: Python :: 3
7
+ Classifier: Intended Audience :: Education
8
+ Classifier: Topic :: Education
9
+ Requires-Python: >=3.10
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE.txt
12
+ Requires-Dist: httpx>=0.27
13
+ Requires-Dist: attrs>=23.1
14
+ Dynamic: license-file
15
+
16
+ # dungeoncoder
17
+
18
+ The Python API for Dungeon Coder, a VS Code extension in which a Python script steers a hero through a pixel-art dungeon.
19
+
20
+ ```python
21
+ from dungeoncoder import Game
22
+
23
+ game = Game("playground.json")
24
+ hero = game.get_hero()
25
+ while not hero.is_at_goal():
26
+ if hero.is_collision_in_front():
27
+ hero.turn_left()
28
+ else:
29
+ hero.move()
30
+ ```
31
+
32
+ - **With the extension:** start the game in VS Code (`Dungeon Coder: Enter the dungeon`); the script talks to it over a local REST API.
33
+ - **Without VS Code:** `dungeoncoder.use_simulator()` (or `DUNGEONCODER_SIM=1`) runs the same script in a pure-Python simulator of the game's rules: no picture, no waiting. `dungeoncoder.testing.spiel(map_text)` gives `(game, hero)` for pytest.
34
+ - **Levels from text maps:** `Game("karte.txt")`.
35
+ - **Setup check:** `python -m dungeoncoder` says which Python and which copy of the package run, whether the game answers and whether its version fits.
36
+
37
+ The package has the same version as the extension. Install it with `pip install dungeoncoder`.
@@ -0,0 +1,22 @@
1
+ # dungeoncoder
2
+
3
+ The Python API for Dungeon Coder, a VS Code extension in which a Python script steers a hero through a pixel-art dungeon.
4
+
5
+ ```python
6
+ from dungeoncoder import Game
7
+
8
+ game = Game("playground.json")
9
+ hero = game.get_hero()
10
+ while not hero.is_at_goal():
11
+ if hero.is_collision_in_front():
12
+ hero.turn_left()
13
+ else:
14
+ hero.move()
15
+ ```
16
+
17
+ - **With the extension:** start the game in VS Code (`Dungeon Coder: Enter the dungeon`); the script talks to it over a local REST API.
18
+ - **Without VS Code:** `dungeoncoder.use_simulator()` (or `DUNGEONCODER_SIM=1`) runs the same script in a pure-Python simulator of the game's rules: no picture, no waiting. `dungeoncoder.testing.spiel(map_text)` gives `(game, hero)` for pytest.
19
+ - **Levels from text maps:** `Game("karte.txt")`.
20
+ - **Setup check:** `python -m dungeoncoder` says which Python and which copy of the package run, whether the game answers and whether its version fits.
21
+
22
+ The package has the same version as the extension. Install it with `pip install dungeoncoder`.
@@ -0,0 +1,4 @@
1
+ from .dungeoncoder import Game, Hero, use_simulator
2
+
3
+ # The same as "version" in the extension's package.json (`npm run check:version`).
4
+ __version__ = "0.2.0"
@@ -0,0 +1,254 @@
1
+ """`python -m dungeoncoder` (in the exercise folder, with the Python that runs your programs).
2
+
3
+ python -m dungeoncoder setup check (below)
4
+ python -m dungeoncoder load LEVEL [--seed N] load a level (.json or .txt) into the game
5
+ python -m dungeoncoder variants PROG LEVEL... run a program on every level in the simulator
6
+ (--traces DIR keeps a trace of every failed run)
7
+ python -m dungeoncoder replay TRACE [--run N] [--pace X] watch a trace in the game
8
+ python -m dungeoncoder trace TRACE [--run N] list a trace with the hero's field after each call
9
+
10
+ The setup check prints what a program of yours will use: the Python, this package, where the game
11
+ is expected, whether it answers and fits, the asset packs, and whether the simulator runs. Each line
12
+ starts with ok, note or PROBLEM; a PROBLEM line says what to do.
13
+ """
14
+ import argparse
15
+ import glob
16
+ import json
17
+ import os
18
+ import subprocess
19
+ import sys
20
+ import tempfile
21
+
22
+
23
+ def line(state, text):
24
+ print(f"{state:8} {text}")
25
+
26
+
27
+ def _manifest(folder):
28
+ """A pack's pack.json (name, version, engine: the oldest Dungeon Coder it needs, ...), or {}."""
29
+ try:
30
+ with open(os.path.join(folder, "pack.json"), encoding="utf-8") as f:
31
+ data = json.load(f)
32
+ return data if isinstance(data, dict) else {}
33
+ except (OSError, ValueError):
34
+ return {}
35
+
36
+
37
+ def _at_least(have, need):
38
+ """Version have >= need ("major.minor.patch", missing parts count as 0)."""
39
+ def parts(version):
40
+ return [int(p) if p.isdigit() else 0 for p in str(version).split(".")]
41
+ a, b = parts(have), parts(need)
42
+ width = max(len(a), len(b))
43
+ return a + [0] * (width - len(a)) >= b + [0] * (width - len(b))
44
+
45
+
46
+ def check():
47
+ problems = 0
48
+ version = sys.version.split()[0]
49
+ if sys.version_info >= (3, 10):
50
+ line("ok", f"Python {version} ({sys.executable})")
51
+ else:
52
+ problems += 1
53
+ line("PROBLEM", f"Python {version} is too old: install Python 3.15 from python.org ({sys.executable})")
54
+
55
+ import dungeoncoder
56
+ from dungeoncoder import dungeoncoder as core
57
+ here = os.path.dirname(os.path.abspath(dungeoncoder.__file__))
58
+ copied = os.path.dirname(here) == os.getcwd()
59
+ line("ok", f"dungeoncoder {dungeoncoder.__version__}, {'copied into this folder' if copied else 'installed'}: {here}")
60
+
61
+ import httpx
62
+ url = core.DEFAULT_BASE_URL
63
+ try:
64
+ answer = httpx.get(url + "/version", timeout=2)
65
+ except httpx.HTTPError:
66
+ answer = None
67
+ if answer is None:
68
+ line("note", f"no game answers at {url}: start it in VS Code with \"Dungeon Coder: Enter the dungeon\" "
69
+ "(only needed for the game; the simulator works without)")
70
+ elif answer.status_code == 404:
71
+ problems += 1
72
+ line("PROBLEM", f"the game at {url} is an older Dungeon Coder without versions: update the extension "
73
+ "(Extensions view), or close the other Dungeon Coder")
74
+ else:
75
+ extension = answer.json().get("result", {}).get("extension", "?")
76
+ if extension.split(".")[:2] == dungeoncoder.__version__.split(".")[:2]:
77
+ line("ok", f"the game answers at {url}: Dungeon Coder {extension}, fits")
78
+ else:
79
+ problems += 1
80
+ line("PROBLEM", f"the game at {url} is Dungeon Coder {extension}, this package is "
81
+ f"{dungeoncoder.__version__}: run \"Dungeon Coder: Copy Python API to workspace\" again")
82
+
83
+ packs = [p for p in os.environ.get("DC_ASSET_PACKS", "").split(os.pathsep) if p]
84
+ if packs:
85
+ for pack in packs:
86
+ if os.path.isdir(pack):
87
+ manifest = _manifest(pack)
88
+ need = manifest.get("engine")
89
+ if isinstance(need, str) and not _at_least(dungeoncoder.__version__, need):
90
+ problems += 1
91
+ line("PROBLEM", f"asset pack {pack} needs Dungeon Coder {need} or newer, this is "
92
+ f"{dungeoncoder.__version__}: update the extension and the Python package")
93
+ else:
94
+ about = ", ".join(f"{k} {manifest[k]}" for k in ("name", "version") if manifest.get(k))
95
+ line("ok", f"asset pack {pack}" + (f" ({about})" if about else ""))
96
+ else:
97
+ problems += 1
98
+ line("PROBLEM", f"asset pack {pack} doesn't exist: clone it there (see the exercise sheet)")
99
+ else:
100
+ line("note", "no asset packs in this terminal (DC_ASSET_PACKS); only text maps of the course need them, "
101
+ "the game finds them through its settings")
102
+
103
+ from dungeoncoder.testing import spiel
104
+ import contextlib
105
+ import io
106
+ with contextlib.redirect_stdout(io.StringIO()):
107
+ # a style that ships with the package (the course styles come with the course's asset pack)
108
+ _, hero = spiel("start: east\nstyle: station\n---\n#####\n#H.Z#\n#####\n")
109
+ ok = hero.move() and hero.move() and hero.is_at_goal()
110
+ line("ok" if ok else "PROBLEM", "the simulator plays a small map" if ok else "the simulator failed on a small map")
111
+ problems += not ok
112
+
113
+ print()
114
+ print("Everything is ready." if not problems else f"{problems} problem(s): see the lines above.")
115
+ return 1 if problems else 0
116
+
117
+
118
+ def load(args):
119
+ from dungeoncoder import Game
120
+ Game(args.level, seed=args.seed)
121
+ print(f"loaded {args.level}")
122
+ return 0
123
+
124
+
125
+ def _runs(path, number):
126
+ from dungeoncoder import trace
127
+ try:
128
+ runs = trace.read(path)
129
+ except (OSError, ValueError) as err:
130
+ print(f"Error: {err}")
131
+ return None
132
+ if not runs:
133
+ print(f"Error: {path} holds no run")
134
+ return None
135
+ if number is None:
136
+ return runs
137
+ if not 1 <= number <= len(runs):
138
+ print(f"Error: {path} has {len(runs)} run(s), not {number}")
139
+ return None
140
+ return [runs[number - 1]]
141
+
142
+
143
+ def replay_command(args):
144
+ from dungeoncoder.replay import replay
145
+ runs = _runs(args.trace, args.run)
146
+ if runs is None:
147
+ return 1
148
+ differs = 0
149
+ for n, run in enumerate(runs, start=1):
150
+ print(f"run {n}: {run.get('level_file') or 'level'}, {len(run['calls'])} calls")
151
+ differs += replay(run, args.pace) is not None
152
+ return 1 if differs else 0
153
+
154
+
155
+ def trace_command(args):
156
+ from dungeoncoder.replay import describe, positions
157
+ runs = _runs(args.trace, args.run)
158
+ if runs is None:
159
+ return 1
160
+ for n, run in enumerate(runs, start=1):
161
+ print(f"run {n}: {run.get('level_file') or 'level'} (fields: column,row of the level)")
162
+ for number, method, params, result, (col, row), direction in positions(run):
163
+ if number == "end":
164
+ stats = result
165
+ print(f" end field {col},{row} facing {direction}: "
166
+ + ", ".join(f"{k} {stats[k]}" for k in ("moves", "turns", "bumps", "level_complete") if k in stats)
167
+ + (f", missing {', '.join(stats['missing'])}" if stats.get("missing") else ""))
168
+ else:
169
+ print(f"{number:>8} {describe(method, params):32} -> {result!r:8} field {col},{row} facing {direction}")
170
+ return 0
171
+
172
+
173
+ def variants_command(args):
174
+ """Runs the program once per level in the simulator: every level it loads is replaced by
175
+ that one (DUNGEONCODER_LEVEL), so the program itself stays unchanged."""
176
+ from dungeoncoder.replay import positions
177
+ from dungeoncoder import trace
178
+ levels = [p for pattern in args.levels for p in sorted(glob.glob(pattern)) or [pattern]]
179
+ if args.traces:
180
+ os.makedirs(args.traces, exist_ok=True)
181
+ package = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
182
+ failed = 0
183
+ for level in levels:
184
+ record = os.path.join(tempfile.mkdtemp(prefix="dungeoncoder-variant-"), "trace.json")
185
+ env = {**os.environ, "DUNGEONCODER_SIM": "1", "DUNGEONCODER_LEVEL": os.path.abspath(level),
186
+ "DUNGEONCODER_TRACE": record,
187
+ "PYTHONPATH": os.pathsep.join([package] + [p for p in [os.environ.get("PYTHONPATH")] if p])}
188
+ process = subprocess.Popen([sys.executable, os.path.abspath(args.program)],
189
+ cwd=os.path.dirname(os.path.abspath(args.program)) or ".", env=env,
190
+ stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
191
+ try:
192
+ code = process.wait(timeout=args.timeout)
193
+ note = "" if code == 0 else f"the program stopped with an error (exit {code})"
194
+ except subprocess.TimeoutExpired:
195
+ process.terminate() # SIGTERM: the program still writes its trace
196
+ try:
197
+ process.wait(timeout=10)
198
+ except subprocess.TimeoutExpired:
199
+ process.kill()
200
+ note = f"no end after {args.timeout} s"
201
+ try:
202
+ run = trace.read(record)[-1]
203
+ stats = positions(run)[-1][3]
204
+ except (OSError, ValueError, IndexError):
205
+ run, stats = None, {}
206
+ ok = bool(stats.get("level_complete")) and not note
207
+ failed += not ok
208
+ detail = (f"{stats.get('moves', 0)} moves, {stats.get('turns', 0)} turns, {stats.get('bumps', 0)} bumps"
209
+ if stats else "no result")
210
+ if stats.get("missing"):
211
+ detail += ", missing " + ", ".join(stats["missing"])
212
+ print(f"{'done' if ok else 'FAILED':7} {level}: {detail}" + (f"; {note}" if note else ""))
213
+ if not ok and args.traces and run is not None:
214
+ name = os.path.join(args.traces, os.path.splitext(os.path.basename(level))[0] + ".trace.json")
215
+ trace.write(name, [run])
216
+ print(f" watch it: python -m dungeoncoder replay {name}")
217
+ print(f"{len(levels) - failed} of {len(levels)} level(s) done")
218
+ return 1 if failed else 0
219
+
220
+
221
+ def main(argv=None):
222
+ argv = sys.argv[1:] if argv is None else argv
223
+ if not argv:
224
+ return check()
225
+ parser = argparse.ArgumentParser(prog="python -m dungeoncoder", description=__doc__,
226
+ formatter_class=argparse.RawDescriptionHelpFormatter)
227
+ sub = parser.add_subparsers(dest="command", required=True)
228
+ p = sub.add_parser("check", help="setup check")
229
+ p.set_defaults(func=lambda _: check())
230
+ p = sub.add_parser("load", help="load a level into the game")
231
+ p.add_argument("level")
232
+ p.add_argument("--seed", type=int)
233
+ p.set_defaults(func=load)
234
+ p = sub.add_parser("variants", help="run a program on several levels in the simulator")
235
+ p.add_argument("program")
236
+ p.add_argument("levels", nargs="+")
237
+ p.add_argument("--traces", help="folder for the traces of failed runs")
238
+ p.add_argument("--timeout", type=int, default=60)
239
+ p.set_defaults(func=variants_command)
240
+ p = sub.add_parser("replay", help="watch a trace in the game")
241
+ p.add_argument("trace")
242
+ p.add_argument("--run", type=int)
243
+ p.add_argument("--pace", type=float)
244
+ p.set_defaults(func=replay_command)
245
+ p = sub.add_parser("trace", help="list a trace")
246
+ p.add_argument("trace")
247
+ p.add_argument("--run", type=int)
248
+ p.set_defaults(func=trace_command)
249
+ args = parser.parse_args(argv)
250
+ return args.func(args)
251
+
252
+
253
+ if __name__ == "__main__":
254
+ sys.exit(main())
@@ -0,0 +1,6 @@
1
+ # GENERATED FILE - do not edit by hand.
2
+ # Source: api/openapi.yaml (servers[0].url).
3
+ # Regenerate with: npm run generate
4
+ HOST = "127.0.0.1"
5
+ PORT = 3000
6
+ BASE_URL = "http://127.0.0.1:3000"
@@ -0,0 +1,8 @@
1
+
2
+ """ A client library for accessing Dungeon Coder API """
3
+ from .client import AuthenticatedClient, Client
4
+
5
+ __all__ = (
6
+ "AuthenticatedClient",
7
+ "Client",
8
+ )
@@ -0,0 +1 @@
1
+ """ Contains methods for accessing the API """
@@ -0,0 +1 @@
1
+ """ Contains endpoint functions for accessing the API """
@@ -0,0 +1,158 @@
1
+ from http import HTTPStatus
2
+ from typing import Any, cast
3
+ from urllib.parse import quote
4
+
5
+ import httpx
6
+
7
+ from ...client import AuthenticatedClient, Client
8
+ from ...types import Response, UNSET
9
+ from ... import errors
10
+
11
+ from ...models.api_error_response import ApiErrorResponse
12
+ from ...models.statistics_result import StatisticsResult
13
+ from typing import cast
14
+
15
+
16
+
17
+ def _get_kwargs(
18
+
19
+ ) -> dict[str, Any]:
20
+
21
+
22
+
23
+
24
+
25
+
26
+ _kwargs: dict[str, Any] = {
27
+ "method": "get",
28
+ "url": "/game/statistics",
29
+ }
30
+
31
+
32
+ return _kwargs
33
+
34
+
35
+
36
+ def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> ApiErrorResponse | StatisticsResult | None:
37
+ if response.status_code == 200:
38
+ response_200 = StatisticsResult.from_dict(response.json())
39
+
40
+
41
+
42
+ return response_200
43
+
44
+ if response.status_code == 500:
45
+ response_500 = ApiErrorResponse.from_dict(response.json())
46
+
47
+
48
+
49
+ return response_500
50
+
51
+ if client.raise_on_unexpected_status:
52
+ raise errors.UnexpectedStatus(response.status_code, response.content)
53
+ else:
54
+ return None
55
+
56
+
57
+ def _build_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Response[ApiErrorResponse | StatisticsResult]:
58
+ return Response(
59
+ status_code=HTTPStatus(response.status_code),
60
+ content=response.content,
61
+ headers=response.headers,
62
+ parsed=_parse_response(client=client, response=response),
63
+ )
64
+
65
+
66
+ def sync_detailed(
67
+ *,
68
+ client: AuthenticatedClient | Client,
69
+
70
+ ) -> Response[ApiErrorResponse | StatisticsResult]:
71
+ """ Counters of the current level (moves, turns, bumps, ...) and whether it is complete.
72
+
73
+ Raises:
74
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
75
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
76
+
77
+ Returns:
78
+ Response[ApiErrorResponse | StatisticsResult]
79
+ """
80
+
81
+
82
+ kwargs = _get_kwargs(
83
+
84
+ )
85
+
86
+ response = client.get_httpx_client().request(
87
+ **kwargs,
88
+ )
89
+
90
+ return _build_response(client=client, response=response)
91
+
92
+ def sync(
93
+ *,
94
+ client: AuthenticatedClient | Client,
95
+
96
+ ) -> ApiErrorResponse | StatisticsResult | None:
97
+ """ Counters of the current level (moves, turns, bumps, ...) and whether it is complete.
98
+
99
+ Raises:
100
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
101
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
102
+
103
+ Returns:
104
+ ApiErrorResponse | StatisticsResult
105
+ """
106
+
107
+
108
+ return sync_detailed(
109
+ client=client,
110
+
111
+ ).parsed
112
+
113
+ async def asyncio_detailed(
114
+ *,
115
+ client: AuthenticatedClient | Client,
116
+
117
+ ) -> Response[ApiErrorResponse | StatisticsResult]:
118
+ """ Counters of the current level (moves, turns, bumps, ...) and whether it is complete.
119
+
120
+ Raises:
121
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
122
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
123
+
124
+ Returns:
125
+ Response[ApiErrorResponse | StatisticsResult]
126
+ """
127
+
128
+
129
+ kwargs = _get_kwargs(
130
+
131
+ )
132
+
133
+ response = await client.get_async_httpx_client().request(
134
+ **kwargs
135
+ )
136
+
137
+ return _build_response(client=client, response=response)
138
+
139
+ async def asyncio(
140
+ *,
141
+ client: AuthenticatedClient | Client,
142
+
143
+ ) -> ApiErrorResponse | StatisticsResult | None:
144
+ """ Counters of the current level (moves, turns, bumps, ...) and whether it is complete.
145
+
146
+ Raises:
147
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
148
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
149
+
150
+ Returns:
151
+ ApiErrorResponse | StatisticsResult
152
+ """
153
+
154
+
155
+ return (await asyncio_detailed(
156
+ client=client,
157
+
158
+ )).parsed