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
@@ -5,31 +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 logging
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 import backend # noqa: F401
13
- from mini_arcade_core import keymaps # noqa: F401
14
- from mini_arcade_core import managers # noqa: F401
15
- from mini_arcade_core import spaces # noqa: F401
16
- from mini_arcade_core import ui # noqa: F401
17
- from mini_arcade_core.bus import event_bus
18
- from mini_arcade_core.commands import (
19
- BaseCommand,
20
- BaseGameCommand,
21
- BaseSceneCommand,
22
- QuitGameCommand,
23
- )
24
- from mini_arcade_core.entity import Entity, KinematicEntity, SpriteEntity
25
- from mini_arcade_core.game import Game, GameConfig
26
- from mini_arcade_core.scenes import Scene, SceneRegistry
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
27
16
 
28
- SceneFactoryLike = Union[Type[Scene], Callable[[Game], Scene]]
29
-
30
- logger = logging.getLogger(__name__)
17
+ SceneFactoryLike = Union[Type[SimScene], Callable[[Game], SimScene]]
31
18
 
32
19
 
20
+ # TODO: Improve exception handling and logging in run_game
33
21
  def run_game(
34
22
  scene: SceneFactoryLike | None = None,
35
23
  config: GameConfig | None = None,
@@ -44,7 +32,7 @@ def run_game(
44
32
  - run_game(config=cfg, initial_scene="main", registry=...) # registry-based
45
33
  - run_game(cfg) # config-only
46
34
 
47
- :param initial_scene: The Scene ID to start the game with.
35
+ :param initial_scene: The SimScene ID to start the game with.
48
36
  :type initial_scene: str
49
37
 
50
38
  :param config: Optional GameConfig to customize game settings.
@@ -55,48 +43,36 @@ def run_game(
55
43
 
56
44
  :raises ValueError: If the provided config does not have a valid Backend.
57
45
  """
58
- # Handle run_game(cfg) where the first arg is actually a GameConfig
59
- if isinstance(scene, GameConfig) and config is None:
60
- config = scene
61
- scene = None
62
-
63
- cfg = config or GameConfig()
64
- if cfg.backend is None:
65
- raise ValueError(
66
- "GameConfig.backend must be set to a Backend instance"
67
- )
68
-
69
- # If user provided a Scene factory/class, ensure it's registered
70
- if scene is not None:
71
- if registry is None:
72
- registry = SceneRegistry(_factories={})
73
- registry.register(
74
- initial_scene, scene
75
- ) # Scene class is callable(game) -> Scene
76
-
77
- game = Game(cfg, registry=registry)
78
- game.run(initial_scene)
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
79
74
 
80
75
 
81
- __all__ = [
82
- "Game",
83
- "GameConfig",
84
- "Entity",
85
- "SpriteEntity",
86
- "KinematicEntity",
87
- "BaseCommand",
88
- "BaseGameCommand",
89
- "BaseSceneCommand",
90
- "QuitGameCommand",
91
- "event_bus",
92
- "run_game",
93
- "backend",
94
- "keymaps",
95
- "managers",
96
- "spaces",
97
- "ui",
98
- ]
99
-
100
76
  PACKAGE_NAME = "mini-arcade-core" # or whatever is in your pyproject.toml
101
77
 
102
78
 
@@ -123,4 +99,11 @@ def get_version() -> str:
123
99
  return "0.0.0"
124
100
 
125
101
 
102
+ __all__ = [
103
+ "Game",
104
+ "GameConfig",
105
+ "WindowConfig",
106
+ "run_game",
107
+ ]
108
+
126
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.
@@ -8,7 +8,7 @@ from dataclasses import dataclass
8
8
  from enum import Enum, auto
9
9
  from typing import Optional, Tuple
10
10
 
11
- from mini_arcade_core.keymaps.keys import Key
11
+ from mini_arcade_core.backend.keys import Key
12
12
 
13
13
 
14
14
  class EventType(Enum):
@@ -8,7 +8,7 @@ Maps SDL keycodes to mini arcade core Key enums.
8
8
 
9
9
  from __future__ import annotations
10
10
 
11
- from mini_arcade_core.keymaps.keys import Key
11
+ from mini_arcade_core.backend.keys import Key
12
12
 
13
13
  # SDL keycodes you need (minimal set)
14
14
  _SDLK_ESCAPE = 27
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)