mini-arcade-core 0.9.9__tar.gz → 1.0.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 (79) hide show
  1. {mini_arcade_core-0.9.9 → mini_arcade_core-1.0.0}/PKG-INFO +1 -1
  2. {mini_arcade_core-0.9.9 → mini_arcade_core-1.0.0}/pyproject.toml +1 -1
  3. mini_arcade_core-1.0.0/src/mini_arcade_core/__init__.py +109 -0
  4. {mini_arcade_core-0.9.9 → mini_arcade_core-1.0.0}/src/mini_arcade_core/backend/__init__.py +0 -5
  5. {mini_arcade_core-0.9.9 → mini_arcade_core-1.0.0}/src/mini_arcade_core/backend/backend.py +9 -0
  6. {mini_arcade_core-0.9.9 → mini_arcade_core-1.0.0}/src/mini_arcade_core/backend/events.py +1 -1
  7. mini_arcade_core-0.9.9/src/mini_arcade_core/keymaps/sdl.py → mini_arcade_core-1.0.0/src/mini_arcade_core/backend/sdl_map.py +1 -1
  8. mini_arcade_core-1.0.0/src/mini_arcade_core/bus.py +57 -0
  9. mini_arcade_core-1.0.0/src/mini_arcade_core/engine/__init__.py +0 -0
  10. mini_arcade_core-1.0.0/src/mini_arcade_core/engine/commands.py +169 -0
  11. mini_arcade_core-1.0.0/src/mini_arcade_core/engine/game.py +354 -0
  12. mini_arcade_core-1.0.0/src/mini_arcade_core/engine/render/__init__.py +0 -0
  13. mini_arcade_core-1.0.0/src/mini_arcade_core/engine/render/packet.py +56 -0
  14. mini_arcade_core-1.0.0/src/mini_arcade_core/engine/render/pipeline.py +39 -0
  15. mini_arcade_core-1.0.0/src/mini_arcade_core/managers/__init__.py +0 -0
  16. mini_arcade_core-1.0.0/src/mini_arcade_core/managers/cheats.py +186 -0
  17. mini_arcade_core-1.0.0/src/mini_arcade_core/managers/inputs.py +286 -0
  18. mini_arcade_core-1.0.0/src/mini_arcade_core/runtime/__init__.py +0 -0
  19. mini_arcade_core-1.0.0/src/mini_arcade_core/runtime/audio/__init__.py +0 -0
  20. mini_arcade_core-1.0.0/src/mini_arcade_core/runtime/audio/audio_adapter.py +13 -0
  21. mini_arcade_core-1.0.0/src/mini_arcade_core/runtime/audio/audio_port.py +17 -0
  22. mini_arcade_core-1.0.0/src/mini_arcade_core/runtime/capture/__init__.py +0 -0
  23. mini_arcade_core-1.0.0/src/mini_arcade_core/runtime/capture/capture_adapter.py +143 -0
  24. mini_arcade_core-1.0.0/src/mini_arcade_core/runtime/capture/capture_port.py +32 -0
  25. mini_arcade_core-1.0.0/src/mini_arcade_core/runtime/context.py +53 -0
  26. mini_arcade_core-1.0.0/src/mini_arcade_core/runtime/file/__init__.py +0 -0
  27. mini_arcade_core-1.0.0/src/mini_arcade_core/runtime/file/file_adapter.py +20 -0
  28. mini_arcade_core-1.0.0/src/mini_arcade_core/runtime/file/file_port.py +31 -0
  29. mini_arcade_core-1.0.0/src/mini_arcade_core/runtime/input/__init__.py +0 -0
  30. mini_arcade_core-1.0.0/src/mini_arcade_core/runtime/input/input_adapter.py +49 -0
  31. mini_arcade_core-1.0.0/src/mini_arcade_core/runtime/input/input_port.py +31 -0
  32. mini_arcade_core-1.0.0/src/mini_arcade_core/runtime/input_frame.py +71 -0
  33. mini_arcade_core-1.0.0/src/mini_arcade_core/runtime/scene/__init__.py +0 -0
  34. mini_arcade_core-1.0.0/src/mini_arcade_core/runtime/scene/scene_adapter.py +97 -0
  35. mini_arcade_core-1.0.0/src/mini_arcade_core/runtime/scene/scene_port.py +149 -0
  36. mini_arcade_core-1.0.0/src/mini_arcade_core/runtime/services.py +35 -0
  37. mini_arcade_core-1.0.0/src/mini_arcade_core/runtime/window/__init__.py +0 -0
  38. mini_arcade_core-1.0.0/src/mini_arcade_core/runtime/window/window_adapter.py +26 -0
  39. mini_arcade_core-1.0.0/src/mini_arcade_core/runtime/window/window_port.py +47 -0
  40. mini_arcade_core-1.0.0/src/mini_arcade_core/scenes/__init__.py +0 -0
  41. {mini_arcade_core-0.9.9 → mini_arcade_core-1.0.0}/src/mini_arcade_core/scenes/autoreg.py +1 -1
  42. {mini_arcade_core-0.9.9 → mini_arcade_core-1.0.0}/src/mini_arcade_core/scenes/registry.py +21 -19
  43. mini_arcade_core-1.0.0/src/mini_arcade_core/scenes/sim_scene.py +41 -0
  44. mini_arcade_core-1.0.0/src/mini_arcade_core/scenes/systems/__init__.py +0 -0
  45. mini_arcade_core-1.0.0/src/mini_arcade_core/scenes/systems/base_system.py +40 -0
  46. mini_arcade_core-1.0.0/src/mini_arcade_core/scenes/systems/system_pipeline.py +57 -0
  47. mini_arcade_core-1.0.0/src/mini_arcade_core/sim/__init__.py +0 -0
  48. mini_arcade_core-1.0.0/src/mini_arcade_core/sim/protocols.py +41 -0
  49. mini_arcade_core-1.0.0/src/mini_arcade_core/sim/runner.py +222 -0
  50. mini_arcade_core-1.0.0/src/mini_arcade_core/spaces/__init__.py +0 -0
  51. mini_arcade_core-1.0.0/src/mini_arcade_core/spaces/d2/__init__.py +0 -0
  52. {mini_arcade_core-0.9.9/src/mini_arcade_core/two_d → mini_arcade_core-1.0.0/src/mini_arcade_core/spaces/d2}/collision2d.py +25 -28
  53. {mini_arcade_core-0.9.9/src/mini_arcade_core/two_d → mini_arcade_core-1.0.0/src/mini_arcade_core/spaces/d2}/geometry2d.py +18 -0
  54. {mini_arcade_core-0.9.9/src/mini_arcade_core/two_d → mini_arcade_core-1.0.0/src/mini_arcade_core/spaces/d2}/kinematics2d.py +5 -8
  55. {mini_arcade_core-0.9.9/src/mini_arcade_core/two_d → mini_arcade_core-1.0.0/src/mini_arcade_core/spaces/d2}/physics2d.py +9 -0
  56. mini_arcade_core-1.0.0/src/mini_arcade_core/ui/__init__.py +0 -0
  57. mini_arcade_core-1.0.0/src/mini_arcade_core/ui/menu.py +775 -0
  58. mini_arcade_core-1.0.0/src/mini_arcade_core/utils/__init__.py +10 -0
  59. mini_arcade_core-1.0.0/src/mini_arcade_core/utils/deprecated_decorator.py +45 -0
  60. mini_arcade_core-1.0.0/src/mini_arcade_core/utils/logging.py +174 -0
  61. mini_arcade_core-0.9.9/src/mini_arcade_core/__init__.py +0 -145
  62. mini_arcade_core-0.9.9/src/mini_arcade_core/cheats.py +0 -235
  63. mini_arcade_core-0.9.9/src/mini_arcade_core/entity.py +0 -71
  64. mini_arcade_core-0.9.9/src/mini_arcade_core/game.py +0 -287
  65. mini_arcade_core-0.9.9/src/mini_arcade_core/keymaps/__init__.py +0 -15
  66. mini_arcade_core-0.9.9/src/mini_arcade_core/managers/__init__.py +0 -14
  67. mini_arcade_core-0.9.9/src/mini_arcade_core/managers/base.py +0 -91
  68. mini_arcade_core-0.9.9/src/mini_arcade_core/managers/entity_manager.py +0 -38
  69. mini_arcade_core-0.9.9/src/mini_arcade_core/managers/overlay_manager.py +0 -33
  70. mini_arcade_core-0.9.9/src/mini_arcade_core/scenes/__init__.py +0 -12
  71. mini_arcade_core-0.9.9/src/mini_arcade_core/scenes/scene.py +0 -93
  72. mini_arcade_core-0.9.9/src/mini_arcade_core/two_d/__init__.py +0 -30
  73. mini_arcade_core-0.9.9/src/mini_arcade_core/ui/__init__.py +0 -14
  74. mini_arcade_core-0.9.9/src/mini_arcade_core/ui/menu.py +0 -416
  75. {mini_arcade_core-0.9.9 → mini_arcade_core-1.0.0}/LICENSE +0 -0
  76. {mini_arcade_core-0.9.9 → mini_arcade_core-1.0.0}/README.md +0 -0
  77. {mini_arcade_core-0.9.9/src/mini_arcade_core/keymaps → mini_arcade_core-1.0.0/src/mini_arcade_core/backend}/keys.py +0 -0
  78. {mini_arcade_core-0.9.9 → mini_arcade_core-1.0.0}/src/mini_arcade_core/backend/types.py +0 -0
  79. {mini_arcade_core-0.9.9/src/mini_arcade_core/two_d → mini_arcade_core-1.0.0/src/mini_arcade_core/spaces/d2}/boundaries2d.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mini-arcade-core
3
- Version: 0.9.9
3
+ Version: 1.0.0
4
4
  Summary: Tiny scene-based game loop core for small arcade games.
5
5
  License: Copyright (c) 2025 Santiago Rincón
6
6
 
@@ -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.9"
7
+ version = "1.0.0"
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" },
@@ -0,0 +1,109 @@
1
+ """
2
+ Entry point for the mini_arcade_core package.
3
+ Provides access to core classes and a convenience function to run a game.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import traceback
9
+ from importlib.metadata import PackageNotFoundError, version
10
+ from typing import Callable, Type, Union
11
+
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
16
+
17
+ SceneFactoryLike = Union[Type[SimScene], Callable[[Game], SimScene]]
18
+
19
+
20
+ # TODO: Improve exception handling and logging in run_game
21
+ def run_game(
22
+ scene: SceneFactoryLike | None = None,
23
+ config: GameConfig | None = None,
24
+ registry: SceneRegistry | None = None,
25
+ initial_scene: str = "main",
26
+ ):
27
+ """
28
+ Convenience helper to bootstrap and run a game with a single scene.
29
+
30
+ Supports both:
31
+ - run_game(SceneClass, cfg) # legacy
32
+ - run_game(config=cfg, initial_scene="main", registry=...) # registry-based
33
+ - run_game(cfg) # config-only
34
+
35
+ :param initial_scene: The SimScene ID to start the game with.
36
+ :type initial_scene: str
37
+
38
+ :param config: Optional GameConfig to customize game settings.
39
+ :type config: GameConfig | None
40
+
41
+ :param registry: Optional SceneRegistry for scene management.
42
+ :type registry: SceneRegistry | None
43
+
44
+ :raises ValueError: If the provided config does not have a valid Backend.
45
+ """
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
74
+
75
+
76
+ PACKAGE_NAME = "mini-arcade-core" # or whatever is in your pyproject.toml
77
+
78
+
79
+ def get_version() -> str:
80
+ """
81
+ Return the installed package version.
82
+
83
+ This is a thin helper around importlib.metadata.version so games can do:
84
+
85
+ from mini_arcade_core import get_version
86
+ print(get_version())
87
+
88
+ :return: The version string of the installed package.
89
+ :rtype: str
90
+
91
+ :raises PackageNotFoundError: If the package is not installed.
92
+ """
93
+ try:
94
+ return version(PACKAGE_NAME)
95
+ except PackageNotFoundError: # if running from source / editable
96
+ logger.warning(
97
+ f"Package '{PACKAGE_NAME}' not found. Returning default version '0.0.0'."
98
+ )
99
+ return "0.0.0"
100
+
101
+
102
+ __all__ = [
103
+ "Game",
104
+ "GameConfig",
105
+ "WindowConfig",
106
+ "run_game",
107
+ ]
108
+
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
@@ -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()
@@ -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)