mini-arcade-core 0.9.8__py3-none-any.whl → 0.10.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 (32) hide show
  1. mini_arcade_core/__init__.py +24 -37
  2. mini_arcade_core/bus.py +57 -0
  3. mini_arcade_core/commands.py +84 -0
  4. mini_arcade_core/entity.py +3 -2
  5. mini_arcade_core/game.py +1 -1
  6. mini_arcade_core/managers/__init__.py +22 -0
  7. mini_arcade_core/managers/base.py +132 -0
  8. mini_arcade_core/{cheats.py → managers/cheats.py} +131 -11
  9. mini_arcade_core/managers/entities.py +38 -0
  10. mini_arcade_core/managers/inputs.py +282 -0
  11. mini_arcade_core/managers/overlays.py +53 -0
  12. mini_arcade_core/managers/system.py +26 -0
  13. mini_arcade_core/scenes/__init__.py +11 -1
  14. mini_arcade_core/scenes/model.py +34 -0
  15. mini_arcade_core/scenes/runtime.py +29 -0
  16. mini_arcade_core/scenes/scene.py +42 -82
  17. mini_arcade_core/scenes/system.py +69 -0
  18. mini_arcade_core/spaces/__init__.py +12 -0
  19. mini_arcade_core/{two_d → spaces/d2}/kinematics2d.py +3 -0
  20. mini_arcade_core/ui/__init__.py +13 -1
  21. mini_arcade_core/ui/menu.py +232 -54
  22. mini_arcade_core/ui/overlays.py +41 -0
  23. {mini_arcade_core-0.9.8.dist-info → mini_arcade_core-0.10.0.dist-info}/METADATA +1 -1
  24. mini_arcade_core-0.10.0.dist-info/RECORD +40 -0
  25. mini_arcade_core-0.9.8.dist-info/RECORD +0 -27
  26. /mini_arcade_core/{two_d → spaces/d2}/__init__.py +0 -0
  27. /mini_arcade_core/{two_d → spaces/d2}/boundaries2d.py +0 -0
  28. /mini_arcade_core/{two_d → spaces/d2}/collision2d.py +0 -0
  29. /mini_arcade_core/{two_d → spaces/d2}/geometry2d.py +0 -0
  30. /mini_arcade_core/{two_d → spaces/d2}/physics2d.py +0 -0
  31. {mini_arcade_core-0.9.8.dist-info → mini_arcade_core-0.10.0.dist-info}/WHEEL +0 -0
  32. {mini_arcade_core-0.9.8.dist-info → mini_arcade_core-0.10.0.dist-info}/licenses/LICENSE +0 -0
@@ -9,24 +9,21 @@ import logging
9
9
  from importlib.metadata import PackageNotFoundError, version
10
10
  from typing import Callable, Type, Union
11
11
 
12
- from mini_arcade_core.backend import Backend, Event, EventType
13
- from mini_arcade_core.cheats import CheatCode, CheatManager
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
+ )
14
24
  from mini_arcade_core.entity import Entity, KinematicEntity, SpriteEntity
15
25
  from mini_arcade_core.game import Game, GameConfig
16
- from mini_arcade_core.keymaps.keys import Key, keymap
17
- from mini_arcade_core.scenes import Scene, SceneRegistry, register_scene
18
- from mini_arcade_core.two_d import (
19
- Bounds2D,
20
- KinematicData,
21
- Position2D,
22
- RectCollider,
23
- RectKinematic,
24
- RectSprite,
25
- Size2D,
26
- Velocity2D,
27
- VerticalBounce,
28
- VerticalWrap,
29
- )
26
+ from mini_arcade_core.scenes import Scene, SceneRegistry
30
27
 
31
28
  SceneFactoryLike = Union[Type[Scene], Callable[[Game], Scene]]
32
29
 
@@ -84,30 +81,20 @@ def run_game(
84
81
  __all__ = [
85
82
  "Game",
86
83
  "GameConfig",
87
- "Scene",
88
84
  "Entity",
89
85
  "SpriteEntity",
90
- "run_game",
91
- "Backend",
92
- "Event",
93
- "EventType",
94
- "Velocity2D",
95
- "Position2D",
96
- "Size2D",
97
86
  "KinematicEntity",
98
- "KinematicData",
99
- "RectCollider",
100
- "VerticalBounce",
101
- "Bounds2D",
102
- "VerticalWrap",
103
- "RectSprite",
104
- "RectKinematic",
105
- "Key",
106
- "keymap",
107
- "SceneRegistry",
108
- "register_scene",
109
- "CheatManager",
110
- "CheatCode",
87
+ "BaseCommand",
88
+ "BaseGameCommand",
89
+ "BaseSceneCommand",
90
+ "QuitGameCommand",
91
+ "event_bus",
92
+ "run_game",
93
+ "backend",
94
+ "keymaps",
95
+ "managers",
96
+ "spaces",
97
+ "ui",
111
98
  ]
112
99
 
113
100
  PACKAGE_NAME = "mini-arcade-core" # or whatever is in your pyproject.toml
@@ -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,84 @@
1
+ """
2
+ Command protocol for executing commands with a given context.
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ from typing import TYPE_CHECKING, Generic, Protocol, TypeVar
8
+
9
+ from mini_arcade_core.game import Game
10
+ from mini_arcade_core.scenes.model import SceneModel
11
+
12
+ if TYPE_CHECKING:
13
+ from mini_arcade_core.scenes.scene import Scene
14
+
15
+ # Justification: Generic type for context
16
+ # pylint: disable=invalid-name
17
+ TContext = TypeVar("TContext")
18
+ # pylint: enable=invalid-name
19
+
20
+
21
+ class BaseCommand(Protocol, Generic[TContext]):
22
+ """
23
+ Protocol for a command that can be executed with a given context.
24
+ """
25
+
26
+ def __call__(self, context: TContext) -> None:
27
+ """
28
+ Execute the cheat code with the given context.
29
+
30
+ :param context: Context object for cheat execution.
31
+ :type context: TContext
32
+ """
33
+ self.execute(context)
34
+
35
+ def execute(self, context: TContext):
36
+ """
37
+ Execute the command with the given context.
38
+
39
+ :param context: Context object for command execution.
40
+ :type context: TContext
41
+ """
42
+
43
+
44
+ class BaseGameCommand(BaseCommand[Game]):
45
+ """
46
+ Base class for commands that operate on the Game context.
47
+ """
48
+
49
+ def execute(self, context: Game) -> None:
50
+ """
51
+ Execute the command with the given Game context.
52
+
53
+ :param context: Game context for command execution.
54
+ :type context: Game
55
+ """
56
+ raise NotImplementedError(
57
+ "Execute method must be implemented by subclasses."
58
+ )
59
+
60
+
61
+ class BaseSceneCommand(BaseCommand[SceneModel]):
62
+ """
63
+ Base class for commands that operate on the Scene SceneModel context within a scene.
64
+ """
65
+
66
+ def execute(self, context: SceneModel) -> None:
67
+ """
68
+ Execute the command with the given Scene Model context.
69
+
70
+ :param context: Scene Model context for command execution.
71
+ :type context: SceneModel
72
+ """
73
+ raise NotImplementedError(
74
+ "Execute method must be implemented by subclasses."
75
+ )
76
+
77
+
78
+ class QuitGameCommand(BaseGameCommand):
79
+ """
80
+ Command to quit the game.
81
+ """
82
+
83
+ def execute(self, context: Game) -> None:
84
+ context.quit()
@@ -6,7 +6,7 @@ from __future__ import annotations
6
6
 
7
7
  from typing import Any
8
8
 
9
- from mini_arcade_core.two_d import (
9
+ from mini_arcade_core.spaces.d2 import (
10
10
  KinematicData,
11
11
  Position2D,
12
12
  RectCollider,
@@ -63,9 +63,10 @@ class KinematicEntity(SpriteEntity):
63
63
  size=kinematic_data.size,
64
64
  )
65
65
 
66
+ self.time_scale = kinematic_data.time_scale
66
67
  self.velocity = kinematic_data.velocity
67
68
 
68
69
  def update(self, dt: float):
69
70
  self.position.x, self.position.y = self.velocity.advance(
70
- self.position.x, self.position.y, dt
71
+ self.position.x, self.position.y, dt * self.time_scale
71
72
  )
mini_arcade_core/game.py CHANGED
@@ -14,7 +14,7 @@ from typing import TYPE_CHECKING, Literal, Union
14
14
  from PIL import Image # type: ignore[import]
15
15
 
16
16
  from mini_arcade_core.backend import Backend
17
- from mini_arcade_core.scenes import SceneRegistry
17
+ from mini_arcade_core.scenes.registry import SceneRegistry
18
18
 
19
19
  if TYPE_CHECKING: # avoid runtime circular import
20
20
  from mini_arcade_core.scenes import Scene
@@ -0,0 +1,22 @@
1
+ """
2
+ Managers module for Mini Arcade Core.
3
+ Provides various manager classes for handling game entities and resources.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from .cheats import BaseCheatCommand, CheatCode, CheatManager
9
+ from .entities import EntityManager
10
+ from .inputs import InputManager
11
+ from .overlays import OverlayManager
12
+ from .system import SystemManager
13
+
14
+ __all__ = [
15
+ "EntityManager",
16
+ "OverlayManager",
17
+ "CheatCode",
18
+ "CheatManager",
19
+ "BaseCheatCommand",
20
+ "InputManager",
21
+ "SystemManager",
22
+ ]
@@ -0,0 +1,132 @@
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)
@@ -5,17 +5,78 @@ Provides cheat codes and related functionality.
5
5
 
6
6
  from __future__ import annotations
7
7
 
8
+ from abc import ABC, abstractmethod
8
9
  from collections import deque
9
10
  from dataclasses import dataclass
10
11
  from enum import Enum
11
- from typing import Callable, Deque, Dict, Optional, Sequence, TypeVar
12
+ from typing import (
13
+ TYPE_CHECKING,
14
+ Callable,
15
+ Deque,
16
+ Dict,
17
+ Generic,
18
+ Optional,
19
+ Protocol,
20
+ Sequence,
21
+ TypeVar,
22
+ )
23
+
24
+ from mini_arcade_core.backend import Event, EventType
25
+ from mini_arcade_core.scenes.system import BaseSceneSystem
26
+
27
+ if TYPE_CHECKING:
28
+ from mini_arcade_core.scenes import Scene
12
29
 
13
30
  # Justification: We want to keep the type variable name simple here.
14
31
  # pylint: disable=invalid-name
15
32
  TContext = TypeVar("TContext")
33
+ TScene = TypeVar("TScene", bound="Scene")
16
34
  # pylint: enable=invalid-name
17
35
 
18
- CheatCallback = Callable[[TContext], None]
36
+
37
+ @dataclass
38
+ class BaseCheatCommand(ABC, Generic[TContext]):
39
+ """
40
+ Base class for cheat codes.
41
+
42
+ :ivar enabled (bool): Whether the cheat code is enabled.
43
+ """
44
+
45
+ enabled: bool = True
46
+
47
+ def __call__(self, context: TContext) -> None:
48
+ """
49
+ Execute the cheat code with the given context.
50
+
51
+ :param context: Context object for cheat execution.
52
+ :type context: TContext
53
+ """
54
+ if not self.enabled:
55
+ return False
56
+ self.execute(context)
57
+ return True
58
+
59
+ @abstractmethod
60
+ def execute(self, context: TContext):
61
+ """
62
+ Execute the cheat code with the given context.
63
+
64
+ :param context: Context object for cheat execution.
65
+ :type context: TContext
66
+ """
67
+ raise NotImplementedError("CheatCommand.execute must be overridden.")
68
+
69
+
70
+ class CheatAction(Protocol[TContext]):
71
+ """
72
+ Protocol for cheat code actions.
73
+
74
+ :ivar enabled: Whether the cheat action is enabled.
75
+ """
76
+
77
+ enabled: bool
78
+
79
+ def __call__(self, ctx: TContext) -> bool: ...
19
80
 
20
81
 
21
82
  @dataclass(frozen=True)
@@ -25,14 +86,14 @@ class CheatCode:
25
86
 
26
87
  :ivar name (str): Unique name of the cheat code.
27
88
  :ivar sequence (tuple[str, ...]): Sequence of key strings that trigger the cheat.
28
- :ivar callback (CheatCallback): Function to call when the cheat is activated.
89
+ :ivar action (CheatAction): BaseCheatCommand to call when the cheat is activated.
29
90
  :ivar clear_buffer_on_match (bool): Whether to clear the input buffer after a match.
30
91
  :ivar enabled (bool): Whether the cheat code is enabled.
31
92
  """
32
93
 
33
94
  name: str
34
95
  sequence: tuple[str, ...]
35
- callback: CheatCallback
96
+ action: CheatAction[TContext]
36
97
  clear_buffer_on_match: bool = False
37
98
  enabled: bool = True
38
99
 
@@ -74,17 +135,17 @@ class CheatManager:
74
135
 
75
136
  # Justification: We want to keep the number of arguments manageable here.
76
137
  # pylint: disable=too-many-arguments
77
- def register_code(
138
+ def register_command(
78
139
  self,
79
140
  name: str,
80
141
  sequence: Sequence[str],
81
- callback: CheatCallback,
142
+ command: BaseCheatCommand[TContext],
82
143
  *,
83
144
  clear_buffer_on_match: bool = False,
84
145
  enabled: bool = True,
85
146
  ):
86
147
  """
87
- Register a new cheat code.
148
+ Register a new cheat code that triggers a BaseCheatCommand.
88
149
 
89
150
  :param name: Unique name for the cheat code.
90
151
  :type name: str
@@ -92,8 +153,8 @@ class CheatManager:
92
153
  :param sequence: Sequence of key strings that trigger the cheat.
93
154
  :type sequence: Sequence[str]
94
155
 
95
- :param callback: Function to call when the cheat is activated.
96
- :type callback: CheatCallback
156
+ :param command: BaseCheatCommand to execute when the cheat is activated.
157
+ :type command: BaseCheatCommand[TContext]
97
158
 
98
159
  :param clear_buffer_on_match: Whether to clear the input buffer after a match.
99
160
  :type clear_buffer_on_match: bool
@@ -112,7 +173,7 @@ class CheatManager:
112
173
  self._codes[name] = CheatCode(
113
174
  name=name,
114
175
  sequence=norm_seq,
115
- callback=callback,
176
+ action=command,
116
177
  clear_buffer_on_match=clear_buffer_on_match,
117
178
  enabled=enabled,
118
179
  )
@@ -182,7 +243,7 @@ class CheatManager:
182
243
  if len(seq) > len(buf):
183
244
  continue
184
245
  if buf[-len(seq) :] == seq:
185
- code.callback(context)
246
+ code.action(context)
186
247
  matched.append(code.name)
187
248
  if code.clear_buffer_on_match:
188
249
  self.clear_buffer()
@@ -233,3 +294,62 @@ class CheatManager:
233
294
  return str(v)
234
295
 
235
296
  return None
297
+
298
+
299
+ class CheatSystem(BaseSceneSystem, Generic[TScene]):
300
+ """
301
+ Scene system for handling cheat codes.
302
+
303
+ :ivar priority (int): Priority of the system (lower runs first).
304
+ :ivar enabled (bool): Whether the system is enabled.
305
+ """
306
+
307
+ priority = 10
308
+
309
+ def __init__(self, scene: TScene, buffer_size=16):
310
+ """
311
+ :param buffer_size: Size of the cheat input buffer.
312
+ :type buffer_size: int
313
+ """
314
+ super().__init__(scene)
315
+ self.cheats = CheatManager(buffer_size=buffer_size)
316
+
317
+ def register(self, name: str, seq: list[str], cmd: Callable, **kwargs):
318
+ """
319
+ Helper to register a cheat command.
320
+
321
+ :param name: Unique name for the cheat code.
322
+ :type name: str
323
+
324
+ :param seq: Sequence of key strings that trigger the cheat.
325
+ :type seq: list[str]
326
+
327
+ :param cmd: Callable to execute when the cheat is activated.
328
+ :type cmd: Callable
329
+
330
+ :param kwargs: Additional keyword arguments for cheat registration.
331
+ """
332
+ self.cheats.register_command(name, seq, cmd, **kwargs)
333
+
334
+ def on_enter(self):
335
+ """
336
+ Called when the scene is entered.
337
+ """
338
+ # register codes here (or via a builder)
339
+
340
+ def handle_event(self, event: Event) -> bool:
341
+ """
342
+ Handle an event.
343
+
344
+ :param event: The event to handle.
345
+ :type event: Event
346
+
347
+ :param scene: The scene receiving the event.
348
+ :type scene: TScene
349
+
350
+ :return: True if the event was handled, False otherwise.
351
+ :rtype: bool
352
+ """
353
+ if event.type == EventType.KEYDOWN:
354
+ self.cheats.process_event(event, self.scene)
355
+ return False # usually don't consume
@@ -0,0 +1,38 @@
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)