mini-arcade-core 0.10.0__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.
Files changed (76) hide show
  1. mini_arcade_core/__init__.py +43 -60
  2. mini_arcade_core/backend/__init__.py +0 -5
  3. mini_arcade_core/backend/backend.py +9 -0
  4. mini_arcade_core/backend/events.py +1 -1
  5. mini_arcade_core/{keymaps/sdl.py → backend/sdl_map.py} +1 -1
  6. mini_arcade_core/engine/__init__.py +0 -0
  7. mini_arcade_core/engine/commands.py +169 -0
  8. mini_arcade_core/engine/game.py +354 -0
  9. mini_arcade_core/engine/render/__init__.py +0 -0
  10. mini_arcade_core/engine/render/packet.py +56 -0
  11. mini_arcade_core/engine/render/pipeline.py +39 -0
  12. mini_arcade_core/managers/__init__.py +0 -22
  13. mini_arcade_core/managers/cheats.py +71 -240
  14. mini_arcade_core/managers/inputs.py +5 -1
  15. mini_arcade_core/runtime/__init__.py +0 -0
  16. mini_arcade_core/runtime/audio/__init__.py +0 -0
  17. mini_arcade_core/runtime/audio/audio_adapter.py +13 -0
  18. mini_arcade_core/runtime/audio/audio_port.py +17 -0
  19. mini_arcade_core/runtime/capture/__init__.py +0 -0
  20. mini_arcade_core/runtime/capture/capture_adapter.py +143 -0
  21. mini_arcade_core/runtime/capture/capture_port.py +32 -0
  22. mini_arcade_core/runtime/context.py +53 -0
  23. mini_arcade_core/runtime/file/__init__.py +0 -0
  24. mini_arcade_core/runtime/file/file_adapter.py +20 -0
  25. mini_arcade_core/runtime/file/file_port.py +31 -0
  26. mini_arcade_core/runtime/input/__init__.py +0 -0
  27. mini_arcade_core/runtime/input/input_adapter.py +49 -0
  28. mini_arcade_core/runtime/input/input_port.py +31 -0
  29. mini_arcade_core/runtime/input_frame.py +71 -0
  30. mini_arcade_core/runtime/scene/__init__.py +0 -0
  31. mini_arcade_core/runtime/scene/scene_adapter.py +97 -0
  32. mini_arcade_core/runtime/scene/scene_port.py +149 -0
  33. mini_arcade_core/runtime/services.py +35 -0
  34. mini_arcade_core/runtime/window/__init__.py +0 -0
  35. mini_arcade_core/runtime/window/window_adapter.py +26 -0
  36. mini_arcade_core/runtime/window/window_port.py +47 -0
  37. mini_arcade_core/scenes/__init__.py +0 -22
  38. mini_arcade_core/scenes/autoreg.py +1 -1
  39. mini_arcade_core/scenes/registry.py +21 -19
  40. mini_arcade_core/scenes/sim_scene.py +41 -0
  41. mini_arcade_core/scenes/systems/__init__.py +0 -0
  42. mini_arcade_core/scenes/systems/base_system.py +40 -0
  43. mini_arcade_core/scenes/systems/system_pipeline.py +57 -0
  44. mini_arcade_core/sim/__init__.py +0 -0
  45. mini_arcade_core/sim/protocols.py +41 -0
  46. mini_arcade_core/sim/runner.py +222 -0
  47. mini_arcade_core/spaces/__init__.py +0 -12
  48. mini_arcade_core/spaces/d2/__init__.py +0 -30
  49. mini_arcade_core/spaces/d2/collision2d.py +25 -28
  50. mini_arcade_core/spaces/d2/geometry2d.py +18 -0
  51. mini_arcade_core/spaces/d2/kinematics2d.py +2 -8
  52. mini_arcade_core/spaces/d2/physics2d.py +9 -0
  53. mini_arcade_core/ui/__init__.py +0 -26
  54. mini_arcade_core/ui/menu.py +265 -84
  55. mini_arcade_core/utils/__init__.py +10 -0
  56. mini_arcade_core/utils/deprecated_decorator.py +45 -0
  57. mini_arcade_core/utils/logging.py +174 -0
  58. {mini_arcade_core-0.10.0.dist-info → mini_arcade_core-1.0.0.dist-info}/METADATA +1 -1
  59. mini_arcade_core-1.0.0.dist-info/RECORD +65 -0
  60. {mini_arcade_core-0.10.0.dist-info → mini_arcade_core-1.0.0.dist-info}/WHEEL +1 -1
  61. mini_arcade_core/commands.py +0 -84
  62. mini_arcade_core/entity.py +0 -72
  63. mini_arcade_core/game.py +0 -287
  64. mini_arcade_core/keymaps/__init__.py +0 -15
  65. mini_arcade_core/managers/base.py +0 -132
  66. mini_arcade_core/managers/entities.py +0 -38
  67. mini_arcade_core/managers/overlays.py +0 -53
  68. mini_arcade_core/managers/system.py +0 -26
  69. mini_arcade_core/scenes/model.py +0 -34
  70. mini_arcade_core/scenes/runtime.py +0 -29
  71. mini_arcade_core/scenes/scene.py +0 -109
  72. mini_arcade_core/scenes/system.py +0 -69
  73. mini_arcade_core/ui/overlays.py +0 -41
  74. mini_arcade_core-0.10.0.dist-info/RECORD +0 -40
  75. /mini_arcade_core/{keymaps → backend}/keys.py +0 -0
  76. {mini_arcade_core-0.10.0.dist-info → mini_arcade_core-1.0.0.dist-info}/licenses/LICENSE +0 -0
mini_arcade_core/game.py DELETED
@@ -1,287 +0,0 @@
1
- """
2
- Game core module defining the Game class and configuration.
3
- """
4
-
5
- from __future__ import annotations
6
-
7
- import os
8
- from dataclasses import dataclass
9
- from datetime import datetime
10
- from pathlib import Path
11
- from time import perf_counter, sleep
12
- from typing import TYPE_CHECKING, Literal, Union
13
-
14
- from PIL import Image # type: ignore[import]
15
-
16
- from mini_arcade_core.backend import Backend
17
- from mini_arcade_core.scenes.registry import SceneRegistry
18
-
19
- if TYPE_CHECKING: # avoid runtime circular import
20
- from mini_arcade_core.scenes import Scene
21
-
22
- SceneOrId = Union["Scene", str]
23
-
24
-
25
- @dataclass
26
- class GameConfig:
27
- """
28
- Configuration options for the Game.
29
-
30
- :ivar width: Width of the game window in pixels.
31
- :ivar height: Height of the game window in pixels.
32
- :ivar title: Title of the game window.
33
- :ivar fps: Target frames per second.
34
- :ivar background_color: RGB background color.
35
- :ivar backend: Optional Backend instance to use for rendering and input.
36
- """
37
-
38
- width: int = 800
39
- height: int = 600
40
- title: str = "Mini Arcade Game"
41
- fps: int = 60
42
- background_color: tuple[int, int, int] = (0, 0, 0)
43
- backend: Backend | None = None
44
-
45
-
46
- Difficulty = Literal["easy", "normal", "hard", "insane"]
47
-
48
-
49
- @dataclass
50
- class GameSettings:
51
- """
52
- Game settings that can be modified during gameplay.
53
-
54
- :ivar difficulty: Current game difficulty level.
55
- """
56
-
57
- difficulty: Difficulty = "normal"
58
-
59
-
60
- @dataclass
61
- class _StackEntry:
62
- scene: "Scene"
63
- as_overlay: bool = False
64
-
65
-
66
- class Game:
67
- """Core game object responsible for managing the main loop and active scene."""
68
-
69
- def __init__(
70
- self, config: GameConfig, registry: SceneRegistry | None = None
71
- ):
72
- """
73
- :param config: Game configuration options.
74
- :type config: GameConfig
75
-
76
- :param registry: Optional SceneRegistry for scene management.
77
- :type registry: SceneRegistry | None
78
-
79
- :raises ValueError: If the provided config does not have a valid Backend.
80
- """
81
- self.config = config
82
- self._current_scene: Scene | None = None
83
- self._running: bool = False
84
-
85
- if config.backend is None:
86
- raise ValueError(
87
- "GameConfig.backend must be set to a Backend instance"
88
- )
89
- self.backend: Backend = config.backend
90
- self.registry = registry or SceneRegistry(_factories={})
91
- self._scene_stack: list[_StackEntry] = []
92
- self.settings = GameSettings()
93
-
94
- def current_scene(self) -> "Scene | None":
95
- """
96
- Get the currently active scene.
97
-
98
- :return: The active Scene instance, or None if no scene is active.
99
- :rtype: Scene | None
100
- """
101
- return self._scene_stack[-1].scene if self._scene_stack else None
102
-
103
- def change_scene(self, scene: SceneOrId):
104
- """
105
- Swap the active scene. Concrete implementations should call
106
- ``on_exit``/``on_enter`` appropriately.
107
-
108
- :param scene: The new scene to activate.
109
- :type scene: SceneOrId
110
- """
111
- scene = self._resolve_scene(scene)
112
-
113
- while self._scene_stack:
114
- entry = self._scene_stack.pop()
115
- entry.scene.on_exit()
116
-
117
- self._scene_stack.append(_StackEntry(scene=scene, as_overlay=False))
118
- scene.on_enter()
119
-
120
- def push_scene(self, scene: SceneOrId, as_overlay: bool = False):
121
- """
122
- Push a scene on top of the current one.
123
- If as_overlay=True, underlying scene(s) may still be drawn but never updated.
124
-
125
- :param scene: The scene to push onto the stack.
126
- :type scene: SceneOrId
127
-
128
- :param as_overlay: Whether to treat the scene as an overlay.
129
- :type as_overlay: bool
130
- """
131
- scene = self._resolve_scene(scene)
132
-
133
- top = self.current_scene()
134
- if top is not None:
135
- top.on_pause()
136
-
137
- self._scene_stack.append(
138
- _StackEntry(scene=scene, as_overlay=as_overlay)
139
- )
140
- scene.on_enter()
141
-
142
- def pop_scene(self) -> "Scene | None":
143
- """
144
- Pop the top scene. If stack becomes empty, quit.
145
-
146
- :return: The popped Scene instance, or None if the stack is now empty.
147
- :rtype: Scene | None
148
- """
149
- if not self._scene_stack:
150
- return None
151
-
152
- popped = self._scene_stack.pop()
153
- popped.scene.on_exit()
154
-
155
- top = self.current_scene()
156
- if top is None:
157
- self.quit()
158
- return popped.scene
159
-
160
- top.on_resume()
161
- return popped.scene
162
-
163
- def _visible_stack(self) -> list["Scene"]:
164
- """
165
- Return the list of scenes that should be drawn (base + overlays).
166
- We draw from the top-most non-overlay scene upward.
167
- """
168
- if not self._scene_stack:
169
- return []
170
-
171
- # find top-most base scene (as_overlay=False)
172
- base_idx = 0
173
- for i in range(len(self._scene_stack) - 1, -1, -1):
174
- if not self._scene_stack[i].as_overlay:
175
- base_idx = i
176
- break
177
-
178
- return [e.scene for e in self._scene_stack[base_idx:]]
179
-
180
- def quit(self):
181
- """Request that the main loop stops."""
182
- self._running = False
183
-
184
- def run(self, initial_scene: SceneOrId):
185
- """
186
- Run the main loop starting with the given scene.
187
-
188
- This is intentionally left abstract so you can plug pygame, pyglet,
189
- or another backend.
190
-
191
- :param initial_scene: The scene to start the game with.
192
- :type initial_scene: SceneOrId
193
- """
194
- backend = self.backend
195
- backend.init(self.config.width, self.config.height, self.config.title)
196
-
197
- br, bg, bb = self.config.background_color
198
- backend.set_clear_color(br, bg, bb)
199
-
200
- self.change_scene(initial_scene)
201
-
202
- self._running = True
203
- target_dt = 1.0 / self.config.fps if self.config.fps > 0 else 0.0
204
- last_time = perf_counter()
205
-
206
- while self._running:
207
- now = perf_counter()
208
- dt = now - last_time
209
- last_time = now
210
-
211
- top = self.current_scene()
212
- if top is None:
213
- break
214
-
215
- for ev in backend.poll_events():
216
- top.handle_event(ev)
217
-
218
- top.update(dt)
219
-
220
- backend.begin_frame()
221
- for scene in self._visible_stack():
222
- scene.draw(backend)
223
- backend.end_frame()
224
-
225
- if target_dt > 0 and dt < target_dt:
226
- sleep(target_dt - dt)
227
-
228
- # exit remaining scenes
229
- while self._scene_stack:
230
- entry = self._scene_stack.pop()
231
- entry.scene.on_exit()
232
-
233
- @staticmethod
234
- def _convert_bmp_to_image(bmp_path: str, out_path: str) -> bool:
235
- """
236
- Convert a BMP file to another image format using Pillow.
237
-
238
- :param bmp_path: Path to the input BMP file.
239
- :type bmp_path: str
240
-
241
- :param out_path: Path to the output image file.
242
- :type out_path: str
243
-
244
- :return: True if conversion was successful, False otherwise.
245
- :rtype: bool
246
- """
247
- try:
248
- img = Image.open(bmp_path)
249
- img.save(out_path) # Pillow chooses format from extension
250
- return True
251
- # Justification: Pillow can raise various exceptions on failure
252
- # pylint: disable=broad-exception-caught
253
- except Exception:
254
- return False
255
- # pylint: enable=broad-exception-caught
256
-
257
- def screenshot(
258
- self, label: str | None = None, directory: str = "screenshots"
259
- ) -> str | None:
260
- """
261
- Ask backend to save a screenshot. Returns the file path or None.
262
-
263
- :param label: Optional label to include in the filename.
264
- :type label: str | None
265
-
266
- :param directory: Directory to save screenshots in.
267
- :type directory: str
268
-
269
- :return: The file path of the saved screenshot, or None on failure.
270
- :rtype: str | None
271
- """
272
- os.makedirs(directory, exist_ok=True)
273
- stamp = datetime.now().strftime("%Y%m%d_%H%M%S")
274
- label = label or "shot"
275
- filename = f"{stamp}_{label}"
276
- bmp_path = os.path.join(directory, f"{filename}.bmp")
277
-
278
- if self.backend.capture_frame(bmp_path):
279
- out_path = Path(directory) / f"{filename}.png"
280
- self._convert_bmp_to_image(bmp_path, str(out_path))
281
- return str(out_path)
282
- return None
283
-
284
- def _resolve_scene(self, scene: SceneOrId) -> "Scene":
285
- if isinstance(scene, str):
286
- return self.registry.create(scene, self)
287
- return scene
@@ -1,15 +0,0 @@
1
- """
2
- Keymaps for Mini Arcade Core.
3
- Includes key definitions and default key mappings.
4
- """
5
-
6
- from __future__ import annotations
7
-
8
- from .keys import Key, keymap
9
- from .sdl import SDL_KEYCODE_TO_KEY
10
-
11
- __all__ = [
12
- "Key",
13
- "keymap",
14
- "SDL_KEYCODE_TO_KEY",
15
- ]
@@ -1,132 +0,0 @@
1
- """
2
- Base manager classes for handling collections of items.
3
- """
4
-
5
- from __future__ import annotations
6
-
7
- from dataclasses import dataclass, field
8
- from typing import Callable, Generic, Iterable, List, Protocol, TypeVar
9
-
10
- from mini_arcade_core.backend.backend import Backend
11
-
12
- # ---- shared types ----
13
- T = TypeVar("T")
14
- OverlayFunc = Callable[[Backend], None]
15
-
16
-
17
- class Overlay(Protocol):
18
- """
19
- Defines an overlay function.
20
-
21
- :ivar enabled (bool): Whether the overlay is enabled.
22
- """
23
-
24
- enabled: bool
25
-
26
- def update(self, dt: float):
27
- """
28
- Update the overlay state.
29
-
30
- :param dt: Time delta in seconds.
31
- :type dt: float
32
- """
33
-
34
- def draw(self, surface: "Backend"):
35
- """
36
- Draw the overlay on the given surface.
37
-
38
- :param surface: The backend surface to draw on.
39
- :type surface: Backend
40
- """
41
-
42
-
43
- class Drawable(Protocol):
44
- """Defines a drawable entity."""
45
-
46
- def draw(self, surface: Backend):
47
- """
48
- Draw the entity on the given surface.
49
-
50
- :param surface: The backend surface to draw on.
51
- :type surface: Backend
52
- """
53
-
54
-
55
- class Updatable(Protocol):
56
- """Defines an updatable entity."""
57
-
58
- def update(self, dt: float):
59
- """
60
- Update the entity state.
61
-
62
- :param dt: Time delta in seconds.
63
- :type dt: float
64
- """
65
-
66
-
67
- class EntityLike(Drawable, Updatable, Protocol):
68
- """Defines a game entity."""
69
-
70
-
71
- @dataclass
72
- class ListManager(Generic[T]):
73
- """
74
- Generic manager for a list of items.
75
-
76
- :ivar items (List[T]): List of managed items.
77
- """
78
-
79
- items: List[T] = field(default_factory=list)
80
-
81
- def add(self, *items: T):
82
- """
83
- Add one or more items to the manager.
84
-
85
- :param items: One or more items to add.
86
- :type items: T
87
- """
88
- self.items.extend(items)
89
-
90
- def add_many(self, items: Iterable[T]):
91
- """
92
- Add multiple items to the manager.
93
-
94
- :param items: An iterable of items to add.
95
- :type items: Iterable[T]
96
- """
97
- self.items.extend(items)
98
-
99
- def remove(self, item: T):
100
- """
101
- Remove a single item from the manager, if present.
102
-
103
- :param item: The item to remove.
104
- :type item: T
105
- """
106
- if item in self.items:
107
- self.items.remove(item)
108
-
109
- def get(self, item: T) -> T | None:
110
- """
111
- Get an item from the manager.
112
-
113
- :param item: The item to get.
114
- :type item: T
115
-
116
- :return: The item if found, else None.
117
- :rtype: T | None
118
- """
119
- for it in self.items:
120
- if it == item:
121
- return it
122
- return None
123
-
124
- def clear(self):
125
- """Clear all items from the manager."""
126
- self.items.clear()
127
-
128
- def __iter__(self):
129
- return iter(self.items)
130
-
131
- def __len__(self) -> int:
132
- return len(self.items)
@@ -1,38 +0,0 @@
1
- """
2
- Entity manager for handling a collection of entities.
3
- """
4
-
5
- from __future__ import annotations
6
-
7
- from dataclasses import dataclass
8
-
9
- from mini_arcade_core.backend import Backend
10
-
11
- from .base import EntityLike, ListManager
12
-
13
-
14
- @dataclass
15
- class EntityManager(ListManager[EntityLike]):
16
- """
17
- Manages a collection of entities within a scene.
18
- """
19
-
20
- def update(self, dt: float):
21
- """
22
- Update all managed entities.
23
-
24
- :param dt: Time delta in seconds.
25
- :type dt: float
26
- """
27
- for ent in list(self.items):
28
- ent.update(dt)
29
-
30
- def draw(self, surface: "Backend"):
31
- """
32
- Draw all managed entities.
33
-
34
- :param surface: The backend surface to draw on.
35
- :type surface: Backend
36
- """
37
- for ent in list(self.items):
38
- ent.draw(surface)
@@ -1,53 +0,0 @@
1
- """
2
- Overlay manager for handling a collection of overlays.
3
- """
4
-
5
- from __future__ import annotations
6
-
7
- from dataclasses import dataclass
8
- from typing import Union
9
-
10
- from mini_arcade_core.backend import Backend
11
- from mini_arcade_core.managers.base import ListManager, Overlay, OverlayFunc
12
-
13
- OverlayType = Union[Overlay, OverlayFunc]
14
-
15
-
16
- @dataclass
17
- class OverlayManager(ListManager[OverlayType]):
18
- """
19
- Manages a collection of overlays within a scene.
20
- """
21
-
22
- def update(self, dt: float):
23
- """
24
- Update all managed overlays.
25
-
26
- :param dt: Time delta in seconds.
27
- :type dt: float
28
- """
29
- for ov in list(self.items):
30
- # class overlays only
31
- if hasattr(ov, "update") and hasattr(ov, "draw"):
32
- if getattr(ov, "enabled", True):
33
- ov.update(dt)
34
-
35
- def draw(self, surface: "Backend"):
36
- """
37
- Call all overlays. Scenes should call this at the end of draw().
38
-
39
- :param surface: The backend surface to draw on.
40
- :type surface: Backend
41
- """
42
-
43
- def prio(o: OverlayType) -> int:
44
- """Priority for sorting overlays."""
45
- return getattr(o, "priority", 0)
46
-
47
- for ov in sorted(list(self.items), key=prio):
48
- if hasattr(ov, "draw"):
49
- if getattr(ov, "enabled", True):
50
- ov.draw(surface)
51
- else:
52
- # function overlay
53
- ov(surface)
@@ -1,26 +0,0 @@
1
- """
2
- Manager for scene systems.
3
- """
4
-
5
- from __future__ import annotations
6
-
7
- from dataclasses import dataclass
8
-
9
- from mini_arcade_core.managers.base import ListManager
10
- from mini_arcade_core.scenes.system import BaseSceneSystem
11
-
12
-
13
- @dataclass
14
- class SystemManager(ListManager[BaseSceneSystem]):
15
- """
16
- Manager for scene systems.
17
- """
18
-
19
- def sorted(self) -> list[BaseSceneSystem]:
20
- """
21
- Get systems sorted by priority.
22
-
23
- :return: List of systems sorted by priority.
24
- :rtype: list[BaseSceneSystem]
25
- """
26
- return sorted(self.items, key=lambda s: getattr(s, "priority", 0))
@@ -1,34 +0,0 @@
1
- """
2
- Scene base model module.
3
- Provides the Scene class and related services.
4
- """
5
-
6
- from __future__ import annotations
7
-
8
- from dataclasses import dataclass, fields
9
- from typing import Any
10
-
11
-
12
- @dataclass
13
- class SceneModel:
14
- """
15
- Basic data model for a scene.
16
- """
17
-
18
- @staticmethod
19
- def _serialize_dataclass(obj) -> dict[str, Any]:
20
- out = {}
21
- for f in fields(obj):
22
- if f.metadata.get("serialize", True) is False:
23
- continue
24
- out[f.name] = getattr(obj, f.name)
25
- return out
26
-
27
- def to_dict(self) -> dict:
28
- """
29
- Convert the SceneModel to a dictionary.
30
-
31
- :return: Dictionary representation of the SceneModel.
32
- :rtype: dict
33
- """
34
- return self._serialize_dataclass(self)
@@ -1,29 +0,0 @@
1
- """
2
- Container for scene services like entity and overlay managers.
3
- """
4
-
5
- from __future__ import annotations
6
-
7
- from dataclasses import dataclass, field
8
-
9
- from mini_arcade_core.managers.entities import EntityManager
10
- from mini_arcade_core.managers.inputs import InputManager
11
- from mini_arcade_core.managers.overlays import OverlayManager
12
- from mini_arcade_core.managers.system import SystemManager
13
-
14
-
15
- @dataclass
16
- class SceneRuntime:
17
- """
18
- Container for scene services like entity and overlay managers.
19
-
20
- :ivar input (InputManager): InputManager for handling input bindings and commands.
21
- :ivar entities (EntityManager): EntityManager for managing scene entities.
22
- :ivar overlays (OverlayManager): OverlayManager for managing scene overlays.
23
- :ivar systems (SystemManager): SystemManager for managing scene systems.
24
- """
25
-
26
- input: InputManager = field(default_factory=InputManager)
27
- entities: EntityManager = field(default_factory=EntityManager)
28
- overlays: OverlayManager = field(default_factory=OverlayManager)
29
- systems: SystemManager = field(default_factory=SystemManager)