mini-arcade-core 0.9.9__py3-none-any.whl → 1.0.0__py3-none-any.whl
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/__init__.py +44 -80
- mini_arcade_core/backend/__init__.py +0 -5
- mini_arcade_core/backend/backend.py +9 -0
- mini_arcade_core/backend/events.py +1 -1
- mini_arcade_core/{keymaps/sdl.py → backend/sdl_map.py} +1 -1
- mini_arcade_core/bus.py +57 -0
- mini_arcade_core/engine/__init__.py +0 -0
- mini_arcade_core/engine/commands.py +169 -0
- mini_arcade_core/engine/game.py +354 -0
- mini_arcade_core/engine/render/__init__.py +0 -0
- mini_arcade_core/engine/render/packet.py +56 -0
- mini_arcade_core/engine/render/pipeline.py +39 -0
- mini_arcade_core/managers/__init__.py +0 -14
- mini_arcade_core/managers/cheats.py +186 -0
- mini_arcade_core/managers/inputs.py +286 -0
- mini_arcade_core/runtime/__init__.py +0 -0
- mini_arcade_core/runtime/audio/__init__.py +0 -0
- mini_arcade_core/runtime/audio/audio_adapter.py +13 -0
- mini_arcade_core/runtime/audio/audio_port.py +17 -0
- mini_arcade_core/runtime/capture/__init__.py +0 -0
- mini_arcade_core/runtime/capture/capture_adapter.py +143 -0
- mini_arcade_core/runtime/capture/capture_port.py +32 -0
- mini_arcade_core/runtime/context.py +53 -0
- mini_arcade_core/runtime/file/__init__.py +0 -0
- mini_arcade_core/runtime/file/file_adapter.py +20 -0
- mini_arcade_core/runtime/file/file_port.py +31 -0
- mini_arcade_core/runtime/input/__init__.py +0 -0
- mini_arcade_core/runtime/input/input_adapter.py +49 -0
- mini_arcade_core/runtime/input/input_port.py +31 -0
- mini_arcade_core/runtime/input_frame.py +71 -0
- mini_arcade_core/runtime/scene/__init__.py +0 -0
- mini_arcade_core/runtime/scene/scene_adapter.py +97 -0
- mini_arcade_core/runtime/scene/scene_port.py +149 -0
- mini_arcade_core/runtime/services.py +35 -0
- mini_arcade_core/runtime/window/__init__.py +0 -0
- mini_arcade_core/runtime/window/window_adapter.py +26 -0
- mini_arcade_core/runtime/window/window_port.py +47 -0
- mini_arcade_core/scenes/__init__.py +0 -12
- mini_arcade_core/scenes/autoreg.py +1 -1
- mini_arcade_core/scenes/registry.py +21 -19
- mini_arcade_core/scenes/sim_scene.py +41 -0
- mini_arcade_core/scenes/systems/__init__.py +0 -0
- mini_arcade_core/scenes/systems/base_system.py +40 -0
- mini_arcade_core/scenes/systems/system_pipeline.py +57 -0
- mini_arcade_core/sim/__init__.py +0 -0
- mini_arcade_core/sim/protocols.py +41 -0
- mini_arcade_core/sim/runner.py +222 -0
- mini_arcade_core/spaces/__init__.py +0 -0
- mini_arcade_core/spaces/d2/__init__.py +0 -0
- mini_arcade_core/{two_d → spaces/d2}/collision2d.py +25 -28
- mini_arcade_core/{two_d → spaces/d2}/geometry2d.py +18 -0
- mini_arcade_core/{two_d → spaces/d2}/kinematics2d.py +5 -8
- mini_arcade_core/{two_d → spaces/d2}/physics2d.py +9 -0
- mini_arcade_core/ui/__init__.py +0 -14
- mini_arcade_core/ui/menu.py +415 -56
- mini_arcade_core/utils/__init__.py +10 -0
- mini_arcade_core/utils/deprecated_decorator.py +45 -0
- mini_arcade_core/utils/logging.py +174 -0
- {mini_arcade_core-0.9.9.dist-info → mini_arcade_core-1.0.0.dist-info}/METADATA +1 -1
- mini_arcade_core-1.0.0.dist-info/RECORD +65 -0
- {mini_arcade_core-0.9.9.dist-info → mini_arcade_core-1.0.0.dist-info}/WHEEL +1 -1
- mini_arcade_core/cheats.py +0 -235
- mini_arcade_core/entity.py +0 -71
- mini_arcade_core/game.py +0 -287
- mini_arcade_core/keymaps/__init__.py +0 -15
- mini_arcade_core/managers/base.py +0 -91
- mini_arcade_core/managers/entity_manager.py +0 -38
- mini_arcade_core/managers/overlay_manager.py +0 -33
- mini_arcade_core/scenes/scene.py +0 -93
- mini_arcade_core/two_d/__init__.py +0 -30
- mini_arcade_core-0.9.9.dist-info/RECORD +0 -31
- /mini_arcade_core/{keymaps → backend}/keys.py +0 -0
- /mini_arcade_core/{two_d → spaces/d2}/boundaries2d.py +0 -0
- {mini_arcade_core-0.9.9.dist-info → mini_arcade_core-1.0.0.dist-info}/licenses/LICENSE +0 -0
mini_arcade_core/__init__.py
CHANGED
|
@@ -5,39 +5,19 @@ Provides access to core classes and a convenience function to run a game.
|
|
|
5
5
|
|
|
6
6
|
from __future__ import annotations
|
|
7
7
|
|
|
8
|
-
import
|
|
8
|
+
import traceback
|
|
9
9
|
from importlib.metadata import PackageNotFoundError, version
|
|
10
10
|
from typing import Callable, Type, Union
|
|
11
11
|
|
|
12
|
-
from mini_arcade_core.
|
|
13
|
-
from mini_arcade_core.
|
|
14
|
-
from mini_arcade_core.
|
|
15
|
-
from mini_arcade_core.
|
|
16
|
-
from mini_arcade_core.keymaps.keys import Key, keymap
|
|
17
|
-
from mini_arcade_core.scenes import (
|
|
18
|
-
Scene,
|
|
19
|
-
SceneRegistry,
|
|
20
|
-
SceneServices,
|
|
21
|
-
register_scene,
|
|
22
|
-
)
|
|
23
|
-
from mini_arcade_core.two_d import (
|
|
24
|
-
Bounds2D,
|
|
25
|
-
KinematicData,
|
|
26
|
-
Position2D,
|
|
27
|
-
RectCollider,
|
|
28
|
-
RectKinematic,
|
|
29
|
-
RectSprite,
|
|
30
|
-
Size2D,
|
|
31
|
-
Velocity2D,
|
|
32
|
-
VerticalBounce,
|
|
33
|
-
VerticalWrap,
|
|
34
|
-
)
|
|
35
|
-
|
|
36
|
-
SceneFactoryLike = Union[Type[Scene], Callable[[Game], Scene]]
|
|
37
|
-
|
|
38
|
-
logger = logging.getLogger(__name__)
|
|
12
|
+
from mini_arcade_core.engine.game import Game, GameConfig, WindowConfig
|
|
13
|
+
from mini_arcade_core.scenes.registry import SceneRegistry
|
|
14
|
+
from mini_arcade_core.scenes.sim_scene import SimScene
|
|
15
|
+
from mini_arcade_core.utils import logger
|
|
39
16
|
|
|
17
|
+
SceneFactoryLike = Union[Type[SimScene], Callable[[Game], SimScene]]
|
|
40
18
|
|
|
19
|
+
|
|
20
|
+
# TODO: Improve exception handling and logging in run_game
|
|
41
21
|
def run_game(
|
|
42
22
|
scene: SceneFactoryLike | None = None,
|
|
43
23
|
config: GameConfig | None = None,
|
|
@@ -52,7 +32,7 @@ def run_game(
|
|
|
52
32
|
- run_game(config=cfg, initial_scene="main", registry=...) # registry-based
|
|
53
33
|
- run_game(cfg) # config-only
|
|
54
34
|
|
|
55
|
-
:param initial_scene: The
|
|
35
|
+
:param initial_scene: The SimScene ID to start the game with.
|
|
56
36
|
:type initial_scene: str
|
|
57
37
|
|
|
58
38
|
:param config: Optional GameConfig to customize game settings.
|
|
@@ -63,59 +43,36 @@ def run_game(
|
|
|
63
43
|
|
|
64
44
|
:raises ValueError: If the provided config does not have a valid Backend.
|
|
65
45
|
"""
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
config
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
if
|
|
80
|
-
registry
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
46
|
+
try:
|
|
47
|
+
# Handle run_game(cfg) where the first arg is actually a GameConfig
|
|
48
|
+
if isinstance(scene, GameConfig) and config is None:
|
|
49
|
+
config = scene
|
|
50
|
+
scene = None
|
|
51
|
+
|
|
52
|
+
cfg = config or GameConfig()
|
|
53
|
+
if cfg.backend is None:
|
|
54
|
+
raise ValueError(
|
|
55
|
+
"GameConfig.backend must be set to a Backend instance"
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
# If user provided a SimScene factory/class, ensure it's registered
|
|
59
|
+
if scene is not None:
|
|
60
|
+
if registry is None:
|
|
61
|
+
registry = SceneRegistry(_factories={})
|
|
62
|
+
registry.register(
|
|
63
|
+
initial_scene, scene
|
|
64
|
+
) # SimScene class is callable(game) -> SimScene
|
|
65
|
+
|
|
66
|
+
game = Game(cfg, registry=registry)
|
|
67
|
+
game.run(initial_scene)
|
|
68
|
+
# Justification: We need to catch all exceptions while we improve error handling.
|
|
69
|
+
# pylint: disable=broad-exception-caught
|
|
70
|
+
except Exception as e:
|
|
71
|
+
logger.exception(f"Unhandled exception in game loop: {e}")
|
|
72
|
+
logger.debug(traceback.format_exc())
|
|
73
|
+
# pylint: enable=broad-exception-caught
|
|
87
74
|
|
|
88
75
|
|
|
89
|
-
__all__ = [
|
|
90
|
-
"Game",
|
|
91
|
-
"GameConfig",
|
|
92
|
-
"Scene",
|
|
93
|
-
"Entity",
|
|
94
|
-
"SpriteEntity",
|
|
95
|
-
"run_game",
|
|
96
|
-
"Backend",
|
|
97
|
-
"Event",
|
|
98
|
-
"EventType",
|
|
99
|
-
"Velocity2D",
|
|
100
|
-
"Position2D",
|
|
101
|
-
"Size2D",
|
|
102
|
-
"KinematicEntity",
|
|
103
|
-
"KinematicData",
|
|
104
|
-
"RectCollider",
|
|
105
|
-
"VerticalBounce",
|
|
106
|
-
"Bounds2D",
|
|
107
|
-
"VerticalWrap",
|
|
108
|
-
"RectSprite",
|
|
109
|
-
"RectKinematic",
|
|
110
|
-
"Key",
|
|
111
|
-
"keymap",
|
|
112
|
-
"SceneRegistry",
|
|
113
|
-
"register_scene",
|
|
114
|
-
"CheatManager",
|
|
115
|
-
"CheatCode",
|
|
116
|
-
"SceneServices",
|
|
117
|
-
]
|
|
118
|
-
|
|
119
76
|
PACKAGE_NAME = "mini-arcade-core" # or whatever is in your pyproject.toml
|
|
120
77
|
|
|
121
78
|
|
|
@@ -142,4 +99,11 @@ def get_version() -> str:
|
|
|
142
99
|
return "0.0.0"
|
|
143
100
|
|
|
144
101
|
|
|
102
|
+
__all__ = [
|
|
103
|
+
"Game",
|
|
104
|
+
"GameConfig",
|
|
105
|
+
"WindowConfig",
|
|
106
|
+
"run_game",
|
|
107
|
+
]
|
|
108
|
+
|
|
145
109
|
__version__ = get_version()
|
|
@@ -7,12 +7,7 @@ This is the only part of the code that talks to SDL/pygame directly.
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
9
|
from .backend import Backend
|
|
10
|
-
from .events import Event, EventType
|
|
11
|
-
from .types import Color
|
|
12
10
|
|
|
13
11
|
__all__ = [
|
|
14
12
|
"Backend",
|
|
15
|
-
"Event",
|
|
16
|
-
"EventType",
|
|
17
|
-
"Color",
|
|
18
13
|
]
|
|
@@ -33,6 +33,15 @@ class Backend(Protocol):
|
|
|
33
33
|
:type title: str
|
|
34
34
|
"""
|
|
35
35
|
|
|
36
|
+
def set_window_title(self, title: str):
|
|
37
|
+
"""
|
|
38
|
+
Set the window title.
|
|
39
|
+
|
|
40
|
+
:param title: The new title for the window.
|
|
41
|
+
:type title: str
|
|
42
|
+
"""
|
|
43
|
+
raise NotImplementedError
|
|
44
|
+
|
|
36
45
|
def poll_events(self) -> Iterable[Event]:
|
|
37
46
|
"""
|
|
38
47
|
Return all pending events since last call.
|
mini_arcade_core/bus.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Event bus module.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from typing import Callable, ClassVar, Dict, List, Optional
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class EventBus:
|
|
11
|
+
"""
|
|
12
|
+
Simple event bus for managing event subscriptions and emissions.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
_instance: ClassVar[Optional["EventBus"]] = None
|
|
16
|
+
_subscribers: Dict[str, List[Callable]]
|
|
17
|
+
|
|
18
|
+
def __new__(cls):
|
|
19
|
+
if cls._instance is None:
|
|
20
|
+
inst = super().__new__(cls)
|
|
21
|
+
inst._subscribers = {}
|
|
22
|
+
cls._instance = inst
|
|
23
|
+
return cls._instance
|
|
24
|
+
|
|
25
|
+
def on(self, event_type: str, handler: Callable):
|
|
26
|
+
"""
|
|
27
|
+
Subscribe a handler to an event type.
|
|
28
|
+
|
|
29
|
+
:param event_type: The type of event to subscribe to.
|
|
30
|
+
:type event_type: str
|
|
31
|
+
|
|
32
|
+
:param handler: The handler function to call when the event is emitted.
|
|
33
|
+
:type handler: Callable
|
|
34
|
+
"""
|
|
35
|
+
if event_type not in self._subscribers:
|
|
36
|
+
self._subscribers[event_type] = []
|
|
37
|
+
self._subscribers[event_type].append(handler)
|
|
38
|
+
|
|
39
|
+
def emit(self, event_type: str, **kwargs):
|
|
40
|
+
"""
|
|
41
|
+
Emit an event of a given type, calling all subscribed handlers.
|
|
42
|
+
|
|
43
|
+
:param event_type: The type of event to emit.
|
|
44
|
+
:type event_type: str
|
|
45
|
+
|
|
46
|
+
:param kwargs: Additional keyword arguments to pass to handlers.
|
|
47
|
+
"""
|
|
48
|
+
handlers = self._subscribers.get(event_type, [])
|
|
49
|
+
for handler in handlers:
|
|
50
|
+
handler(**kwargs)
|
|
51
|
+
|
|
52
|
+
def clear(self):
|
|
53
|
+
"""Clear all subscribers from the event bus."""
|
|
54
|
+
self._subscribers.clear()
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
event_bus = EventBus()
|
|
File without changes
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Command protocol for executing commands with a given context.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from typing import TYPE_CHECKING, List, Optional, Protocol, TypeVar
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from mini_arcade_core.runtime.services import RuntimeServices
|
|
12
|
+
|
|
13
|
+
# Justification: Generic type for context
|
|
14
|
+
# pylint: disable=invalid-name
|
|
15
|
+
TContext = TypeVar("TContext")
|
|
16
|
+
# pylint: enable=invalid-name
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class CommandContext:
|
|
21
|
+
"""
|
|
22
|
+
Context for command execution.
|
|
23
|
+
|
|
24
|
+
:ivar services (RuntimeServices): The runtime services.
|
|
25
|
+
:ivar commands (CommandQueue | None): Optional command queue.
|
|
26
|
+
:ivar settings (object | None): Optional settings object.
|
|
27
|
+
:ivar world (object | None): The world object (can be any type).
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
services: RuntimeServices
|
|
31
|
+
commands: Optional["CommandQueue"] = None
|
|
32
|
+
settings: Optional[object] = None
|
|
33
|
+
world: Optional[object] = None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class Command(Protocol):
|
|
37
|
+
"""
|
|
38
|
+
A command is the only allowed "write path" from input/systems into:
|
|
39
|
+
- scene operations (push/pop/change/quit)
|
|
40
|
+
- capture
|
|
41
|
+
- global game lifecycle
|
|
42
|
+
- later: world mutations (if you pass a world reference)
|
|
43
|
+
|
|
44
|
+
For now we keep it simple: commands only need RuntimeServices.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
def execute(
|
|
48
|
+
self,
|
|
49
|
+
context: CommandContext,
|
|
50
|
+
):
|
|
51
|
+
"""
|
|
52
|
+
Execute the command with the given world and runtime services.
|
|
53
|
+
|
|
54
|
+
:param services: Runtime services for command execution.
|
|
55
|
+
:type services: RuntimeServices
|
|
56
|
+
|
|
57
|
+
:param commands: Optional command queue for command execution.
|
|
58
|
+
:type commands: object | None
|
|
59
|
+
|
|
60
|
+
:param settings: Optional settings object for command execution.
|
|
61
|
+
:type settings: object | None
|
|
62
|
+
|
|
63
|
+
:param world: The world object (can be any type).
|
|
64
|
+
:type world: object | None
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@dataclass
|
|
69
|
+
class CommandQueue:
|
|
70
|
+
"""
|
|
71
|
+
Queue for storing and executing commands.
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
_items: List[Command] = field(default_factory=list)
|
|
75
|
+
|
|
76
|
+
def push(self, cmd: Command):
|
|
77
|
+
"""
|
|
78
|
+
Push a command onto the queue.
|
|
79
|
+
|
|
80
|
+
:param cmd: Command to be added to the queue.
|
|
81
|
+
:type cmd: Command
|
|
82
|
+
"""
|
|
83
|
+
self._items.append(cmd)
|
|
84
|
+
|
|
85
|
+
def drain(self) -> List[Command]:
|
|
86
|
+
"""
|
|
87
|
+
Drain and return all commands from the queue.
|
|
88
|
+
|
|
89
|
+
:return: List of commands that were in the queue.
|
|
90
|
+
:rtype: list[Command]
|
|
91
|
+
"""
|
|
92
|
+
items = self._items
|
|
93
|
+
self._items = []
|
|
94
|
+
return items
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@dataclass(frozen=True)
|
|
98
|
+
class QuitCommand(Command):
|
|
99
|
+
"""Quit the game."""
|
|
100
|
+
|
|
101
|
+
def execute(
|
|
102
|
+
self,
|
|
103
|
+
context: CommandContext,
|
|
104
|
+
):
|
|
105
|
+
context.services.scenes.quit()
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@dataclass(frozen=True)
|
|
109
|
+
class ScreenshotCommand(Command):
|
|
110
|
+
"""
|
|
111
|
+
Take a screenshot.
|
|
112
|
+
|
|
113
|
+
:ivar label (str | None): Optional label for the screenshot file.
|
|
114
|
+
"""
|
|
115
|
+
|
|
116
|
+
label: str | None = None
|
|
117
|
+
|
|
118
|
+
def execute(
|
|
119
|
+
self,
|
|
120
|
+
context: CommandContext,
|
|
121
|
+
):
|
|
122
|
+
context.services.capture.screenshot(label=self.label, mode="manual")
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@dataclass(frozen=True)
|
|
126
|
+
class PushSceneCommand(Command):
|
|
127
|
+
"""
|
|
128
|
+
Push a new scene onto the scene stack.
|
|
129
|
+
|
|
130
|
+
:ivar scene_id (str): Identifier of the scene to push.
|
|
131
|
+
:ivar as_overlay (bool): Whether to push the scene as an overlay.
|
|
132
|
+
"""
|
|
133
|
+
|
|
134
|
+
scene_id: str
|
|
135
|
+
as_overlay: bool = False
|
|
136
|
+
|
|
137
|
+
def execute(
|
|
138
|
+
self,
|
|
139
|
+
context: CommandContext,
|
|
140
|
+
):
|
|
141
|
+
context.services.scenes.push(self.scene_id, as_overlay=self.as_overlay)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
@dataclass(frozen=True)
|
|
145
|
+
class PopSceneCommand(Command):
|
|
146
|
+
"""Pop the current scene from the scene stack."""
|
|
147
|
+
|
|
148
|
+
def execute(
|
|
149
|
+
self,
|
|
150
|
+
context: CommandContext,
|
|
151
|
+
):
|
|
152
|
+
context.services.scenes.pop()
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
@dataclass(frozen=True)
|
|
156
|
+
class ChangeSceneCommand(Command):
|
|
157
|
+
"""
|
|
158
|
+
Change the current scene to the specified scene.
|
|
159
|
+
|
|
160
|
+
:ivar scene_id (str): Identifier of the scene to switch to.
|
|
161
|
+
"""
|
|
162
|
+
|
|
163
|
+
scene_id: str
|
|
164
|
+
|
|
165
|
+
def execute(
|
|
166
|
+
self,
|
|
167
|
+
context: CommandContext,
|
|
168
|
+
):
|
|
169
|
+
context.services.scenes.change(self.scene_id)
|