mini-arcade-core 0.9.2__tar.gz → 0.9.4__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.
- {mini_arcade_core-0.9.2 → mini_arcade_core-0.9.4}/PKG-INFO +1 -1
- {mini_arcade_core-0.9.2 → mini_arcade_core-0.9.4}/pyproject.toml +1 -1
- {mini_arcade_core-0.9.2 → mini_arcade_core-0.9.4}/src/mini_arcade_core/__init__.py +10 -2
- mini_arcade_core-0.9.4/src/mini_arcade_core/autoreg.py +39 -0
- {mini_arcade_core-0.9.2 → mini_arcade_core-0.9.4}/src/mini_arcade_core/game.py +20 -5
- mini_arcade_core-0.9.4/src/mini_arcade_core/registry.py +112 -0
- {mini_arcade_core-0.9.2 → mini_arcade_core-0.9.4}/src/mini_arcade_core/ui/menu.py +3 -7
- {mini_arcade_core-0.9.2 → mini_arcade_core-0.9.4}/LICENSE +0 -0
- {mini_arcade_core-0.9.2 → mini_arcade_core-0.9.4}/README.md +0 -0
- {mini_arcade_core-0.9.2 → mini_arcade_core-0.9.4}/src/mini_arcade_core/backend.py +0 -0
- {mini_arcade_core-0.9.2 → mini_arcade_core-0.9.4}/src/mini_arcade_core/boundaries2d.py +0 -0
- {mini_arcade_core-0.9.2 → mini_arcade_core-0.9.4}/src/mini_arcade_core/collision2d.py +0 -0
- {mini_arcade_core-0.9.2 → mini_arcade_core-0.9.4}/src/mini_arcade_core/entity.py +0 -0
- {mini_arcade_core-0.9.2 → mini_arcade_core-0.9.4}/src/mini_arcade_core/geometry2d.py +0 -0
- {mini_arcade_core-0.9.2 → mini_arcade_core-0.9.4}/src/mini_arcade_core/keymaps/__init__.py +0 -0
- {mini_arcade_core-0.9.2 → mini_arcade_core-0.9.4}/src/mini_arcade_core/keymaps/sdl.py +0 -0
- {mini_arcade_core-0.9.2 → mini_arcade_core-0.9.4}/src/mini_arcade_core/keys.py +0 -0
- {mini_arcade_core-0.9.2 → mini_arcade_core-0.9.4}/src/mini_arcade_core/kinematics2d.py +0 -0
- {mini_arcade_core-0.9.2 → mini_arcade_core-0.9.4}/src/mini_arcade_core/physics2d.py +0 -0
- {mini_arcade_core-0.9.2 → mini_arcade_core-0.9.4}/src/mini_arcade_core/scene.py +0 -0
- {mini_arcade_core-0.9.2 → mini_arcade_core-0.9.4}/src/mini_arcade_core/ui/__init__.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "mini-arcade-core"
|
|
7
|
-
version = "0.9.
|
|
7
|
+
version = "0.9.4"
|
|
8
8
|
description = "Tiny scene-based game loop core for small arcade games."
|
|
9
9
|
authors = [
|
|
10
10
|
{ name = "Santiago Rincon", email = "rincores@gmail.com" },
|
|
@@ -8,6 +8,7 @@ from __future__ import annotations
|
|
|
8
8
|
import logging
|
|
9
9
|
from importlib.metadata import PackageNotFoundError, version
|
|
10
10
|
|
|
11
|
+
from .autoreg import register_scene
|
|
11
12
|
from .backend import Backend, Event, EventType
|
|
12
13
|
from .boundaries2d import (
|
|
13
14
|
RectKinematic,
|
|
@@ -22,12 +23,17 @@ from .geometry2d import Bounds2D, Position2D, Size2D
|
|
|
22
23
|
from .keys import Key, keymap
|
|
23
24
|
from .kinematics2d import KinematicData
|
|
24
25
|
from .physics2d import Velocity2D
|
|
26
|
+
from .registry import SceneRegistry
|
|
25
27
|
from .scene import Scene
|
|
26
28
|
|
|
27
29
|
logger = logging.getLogger(__name__)
|
|
28
30
|
|
|
29
31
|
|
|
30
|
-
def run_game(
|
|
32
|
+
def run_game(
|
|
33
|
+
initial_scene_cls: type[Scene],
|
|
34
|
+
config: GameConfig | None = None,
|
|
35
|
+
registry: SceneRegistry | None = None,
|
|
36
|
+
):
|
|
31
37
|
"""
|
|
32
38
|
Convenience helper to bootstrap and run a game with a single scene.
|
|
33
39
|
|
|
@@ -44,7 +50,7 @@ def run_game(initial_scene_cls: type[Scene], config: GameConfig | None = None):
|
|
|
44
50
|
raise ValueError(
|
|
45
51
|
"GameConfig.backend must be set to a Backend instance"
|
|
46
52
|
)
|
|
47
|
-
game = Game(cfg)
|
|
53
|
+
game = Game(cfg, registry=registry)
|
|
48
54
|
scene = initial_scene_cls(game)
|
|
49
55
|
game.run(scene)
|
|
50
56
|
|
|
@@ -72,6 +78,8 @@ __all__ = [
|
|
|
72
78
|
"RectKinematic",
|
|
73
79
|
"Key",
|
|
74
80
|
"keymap",
|
|
81
|
+
"SceneRegistry",
|
|
82
|
+
"register_scene",
|
|
75
83
|
]
|
|
76
84
|
|
|
77
85
|
PACKAGE_NAME = "mini-arcade-core" # or whatever is in your pyproject.toml
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Autoregistration utilities for mini arcade core.
|
|
3
|
+
Allows automatic registration of Scene classes via decorators.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from typing import TYPE_CHECKING, Dict, Type
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from .scene import Scene
|
|
12
|
+
|
|
13
|
+
_AUTO: Dict[str, Type["Scene"]] = {}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def register_scene(scene_id: str):
|
|
17
|
+
"""Decorator to mark and register a Scene class under an id."""
|
|
18
|
+
|
|
19
|
+
def deco(cls: Type["Scene"]):
|
|
20
|
+
_AUTO[scene_id] = cls
|
|
21
|
+
setattr(cls, "__scene_id__", scene_id)
|
|
22
|
+
return cls
|
|
23
|
+
|
|
24
|
+
return deco
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def snapshot() -> Dict[str, Type["Scene"]]:
|
|
28
|
+
"""
|
|
29
|
+
Copy of current catalog (useful for tests).
|
|
30
|
+
|
|
31
|
+
:return: A copy of the current scene catalog.
|
|
32
|
+
:rtype: Dict[str, Type["Scene"]]
|
|
33
|
+
"""
|
|
34
|
+
return dict(_AUTO)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def clear():
|
|
38
|
+
"""Clear the current catalog (useful for tests)."""
|
|
39
|
+
_AUTO.clear()
|
|
@@ -9,15 +9,18 @@ from dataclasses import dataclass
|
|
|
9
9
|
from datetime import datetime
|
|
10
10
|
from pathlib import Path
|
|
11
11
|
from time import perf_counter, sleep
|
|
12
|
-
from typing import TYPE_CHECKING
|
|
12
|
+
from typing import TYPE_CHECKING, Union
|
|
13
13
|
|
|
14
14
|
from PIL import Image # type: ignore[import]
|
|
15
15
|
|
|
16
16
|
from .backend import Backend
|
|
17
|
+
from .registry import SceneRegistry
|
|
17
18
|
|
|
18
19
|
if TYPE_CHECKING: # avoid runtime circular import
|
|
19
20
|
from .scene import Scene
|
|
20
21
|
|
|
22
|
+
SceneOrId = Union["Scene", str]
|
|
23
|
+
|
|
21
24
|
|
|
22
25
|
@dataclass
|
|
23
26
|
class GameConfig:
|
|
@@ -49,7 +52,9 @@ class _StackEntry:
|
|
|
49
52
|
class Game:
|
|
50
53
|
"""Core game object responsible for managing the main loop and active scene."""
|
|
51
54
|
|
|
52
|
-
def __init__(
|
|
55
|
+
def __init__(
|
|
56
|
+
self, config: GameConfig, registry: SceneRegistry | None = None
|
|
57
|
+
):
|
|
53
58
|
"""
|
|
54
59
|
:param config: Game configuration options.
|
|
55
60
|
:type config: GameConfig
|
|
@@ -65,6 +70,7 @@ class Game:
|
|
|
65
70
|
"GameConfig.backend must be set to a Backend instance"
|
|
66
71
|
)
|
|
67
72
|
self.backend: Backend = config.backend
|
|
73
|
+
self.registry = registry or SceneRegistry(_factories={})
|
|
68
74
|
self._scene_stack: list[_StackEntry] = []
|
|
69
75
|
|
|
70
76
|
def current_scene(self) -> "Scene | None":
|
|
@@ -76,7 +82,7 @@ class Game:
|
|
|
76
82
|
"""
|
|
77
83
|
return self._scene_stack[-1].scene if self._scene_stack else None
|
|
78
84
|
|
|
79
|
-
def change_scene(self, scene:
|
|
85
|
+
def change_scene(self, scene: SceneOrId):
|
|
80
86
|
"""
|
|
81
87
|
Swap the active scene. Concrete implementations should call
|
|
82
88
|
``on_exit``/``on_enter`` appropriately.
|
|
@@ -84,6 +90,8 @@ class Game:
|
|
|
84
90
|
:param scene: The new scene to activate.
|
|
85
91
|
:type scene: Scene
|
|
86
92
|
"""
|
|
93
|
+
scene = self._resolve_scene(scene)
|
|
94
|
+
|
|
87
95
|
while self._scene_stack:
|
|
88
96
|
entry = self._scene_stack.pop()
|
|
89
97
|
entry.scene.on_exit()
|
|
@@ -91,11 +99,13 @@ class Game:
|
|
|
91
99
|
self._scene_stack.append(_StackEntry(scene=scene, as_overlay=False))
|
|
92
100
|
scene.on_enter()
|
|
93
101
|
|
|
94
|
-
def push_scene(self, scene:
|
|
102
|
+
def push_scene(self, scene: SceneOrId, as_overlay: bool = False):
|
|
95
103
|
"""
|
|
96
104
|
Push a scene on top of the current one.
|
|
97
105
|
If as_overlay=True, underlying scene(s) may still be drawn but never updated.
|
|
98
106
|
"""
|
|
107
|
+
scene = self._resolve_scene(scene)
|
|
108
|
+
|
|
99
109
|
top = self.current_scene()
|
|
100
110
|
if top is not None:
|
|
101
111
|
top.on_pause()
|
|
@@ -142,7 +152,7 @@ class Game:
|
|
|
142
152
|
"""Request that the main loop stops."""
|
|
143
153
|
self._running = False
|
|
144
154
|
|
|
145
|
-
def run(self, initial_scene:
|
|
155
|
+
def run(self, initial_scene: SceneOrId):
|
|
146
156
|
"""
|
|
147
157
|
Run the main loop starting with the given scene.
|
|
148
158
|
|
|
@@ -241,3 +251,8 @@ class Game:
|
|
|
241
251
|
self._convert_bmp_to_image(bmp_path, str(out_path))
|
|
242
252
|
return str(out_path)
|
|
243
253
|
return None
|
|
254
|
+
|
|
255
|
+
def _resolve_scene(self, scene: SceneOrId) -> "Scene":
|
|
256
|
+
if isinstance(scene, str):
|
|
257
|
+
return self.registry.create(scene, self)
|
|
258
|
+
return scene
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Scene registry for mini arcade core.
|
|
3
|
+
Allows registering and creating scenes by string IDs.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import importlib
|
|
9
|
+
import pkgutil
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
from typing import TYPE_CHECKING, Dict, Protocol
|
|
12
|
+
|
|
13
|
+
from .autoreg import snapshot
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from mini_arcade_core.game import Game
|
|
17
|
+
from mini_arcade_core.scene import Scene
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class SceneFactory(Protocol):
|
|
21
|
+
"""
|
|
22
|
+
Protocol for scene factory callables.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __call__(self, game: "Game") -> "Scene": ...
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class SceneRegistry:
|
|
30
|
+
"""
|
|
31
|
+
Registry for scene factories, allowing registration and creation of scenes by string IDs.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
_factories: Dict[str, SceneFactory]
|
|
35
|
+
|
|
36
|
+
def register(self, scene_id: str, factory: SceneFactory):
|
|
37
|
+
"""
|
|
38
|
+
Register a scene factory under a given scene ID.
|
|
39
|
+
|
|
40
|
+
:param scene_id: The string ID for the scene.
|
|
41
|
+
:type scene_id: str
|
|
42
|
+
|
|
43
|
+
:param factory: A callable that creates a Scene instance.
|
|
44
|
+
:type factory: SceneFactory
|
|
45
|
+
"""
|
|
46
|
+
self._factories[scene_id] = factory
|
|
47
|
+
|
|
48
|
+
def register_cls(self, scene_id: str, scene_cls: type["Scene"]):
|
|
49
|
+
"""
|
|
50
|
+
Register a Scene class under a given scene ID.
|
|
51
|
+
|
|
52
|
+
:param scene_id: The string ID for the scene.
|
|
53
|
+
:type scene_id: str
|
|
54
|
+
|
|
55
|
+
:param scene_cls: The Scene class to register.
|
|
56
|
+
:type scene_cls: type["Scene"]
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
def return_factory(game: "Game") -> "Scene":
|
|
60
|
+
return scene_cls(game)
|
|
61
|
+
|
|
62
|
+
self.register(scene_id, return_factory)
|
|
63
|
+
|
|
64
|
+
def create(self, scene_id: str, game: "Game") -> "Scene":
|
|
65
|
+
"""
|
|
66
|
+
Create a scene instance using the registered factory for the given scene ID.
|
|
67
|
+
|
|
68
|
+
:param scene_id: The string ID of the scene to create.
|
|
69
|
+
:type scene_id: str
|
|
70
|
+
|
|
71
|
+
:param game: The Game instance to pass to the scene factory.
|
|
72
|
+
:type game: Game
|
|
73
|
+
|
|
74
|
+
:return: A new Scene instance.
|
|
75
|
+
:rtype: Scene
|
|
76
|
+
|
|
77
|
+
:raises KeyError: If no factory is registered for the given scene ID.
|
|
78
|
+
"""
|
|
79
|
+
try:
|
|
80
|
+
return self._factories[scene_id](game)
|
|
81
|
+
except KeyError as e:
|
|
82
|
+
raise KeyError(f"Unknown scene_id={scene_id!r}") from e
|
|
83
|
+
|
|
84
|
+
def load_catalog(self, catalog: Dict[str, type["Scene"]]):
|
|
85
|
+
"""
|
|
86
|
+
Load a catalog of Scene classes into the registry.
|
|
87
|
+
|
|
88
|
+
:param catalog: A dictionary mapping scene IDs to Scene classes.
|
|
89
|
+
:type catalog: Dict[str, type["Scene"]]
|
|
90
|
+
"""
|
|
91
|
+
for scene_id, cls in catalog.items():
|
|
92
|
+
self.register_cls(scene_id, cls)
|
|
93
|
+
|
|
94
|
+
def discover(self, package: str) -> "SceneRegistry":
|
|
95
|
+
"""
|
|
96
|
+
Import all modules in a package so @scene decorators run.
|
|
97
|
+
|
|
98
|
+
:param package: The package name to scan for scene modules.
|
|
99
|
+
:type package: str
|
|
100
|
+
|
|
101
|
+
:return: The SceneRegistry instance (for chaining).
|
|
102
|
+
:rtype: SceneRegistry
|
|
103
|
+
"""
|
|
104
|
+
pkg = importlib.import_module(package)
|
|
105
|
+
if not hasattr(pkg, "__path__"):
|
|
106
|
+
return self # not a package
|
|
107
|
+
|
|
108
|
+
for mod in pkgutil.walk_packages(pkg.__path__, pkg.__name__ + "."):
|
|
109
|
+
importlib.import_module(mod.name)
|
|
110
|
+
|
|
111
|
+
self.load_catalog(snapshot())
|
|
112
|
+
return self
|
|
@@ -253,9 +253,7 @@ class Menu:
|
|
|
253
253
|
color=self.style.hint_color,
|
|
254
254
|
)
|
|
255
255
|
|
|
256
|
-
def _draw_text_items(
|
|
257
|
-
self, surface: Backend, x_center: int, cursor_y: int
|
|
258
|
-
) -> None:
|
|
256
|
+
def _draw_text_items(self, surface: Backend, x_center: int, cursor_y: int):
|
|
259
257
|
for i, item in enumerate(self.items):
|
|
260
258
|
color = (
|
|
261
259
|
self.style.selected
|
|
@@ -272,9 +270,7 @@ class Menu:
|
|
|
272
270
|
|
|
273
271
|
# Justification: Local variables for layout calculations
|
|
274
272
|
# pylint: disable=too-many-locals
|
|
275
|
-
def _draw_buttons(
|
|
276
|
-
self, surface: Backend, x_center: int, cursor_y: int
|
|
277
|
-
) -> None:
|
|
273
|
+
def _draw_buttons(self, surface: Backend, x_center: int, cursor_y: int):
|
|
278
274
|
# Determine button width: fixed or auto-fit
|
|
279
275
|
if self.style.button_width is not None:
|
|
280
276
|
bw = self.style.button_width
|
|
@@ -366,6 +362,6 @@ class Menu:
|
|
|
366
362
|
text: str,
|
|
367
363
|
*,
|
|
368
364
|
color: Color,
|
|
369
|
-
)
|
|
365
|
+
):
|
|
370
366
|
w, _ = surface.measure_text(text)
|
|
371
367
|
surface.draw_text(x_center - (w // 2), y, text, color=color)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|