mini-arcade-core 0.9.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.
@@ -0,0 +1,135 @@
1
+ """
2
+ Menu system for mini arcade core.
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ from dataclasses import dataclass
8
+ from typing import Callable, Sequence
9
+
10
+ from mini_arcade_core.backend import Backend, Color, Event, EventType
11
+
12
+ MenuAction = Callable[[], None]
13
+
14
+
15
+ @dataclass(frozen=True)
16
+ class MenuItem:
17
+ """
18
+ Represents a single item in a menu.
19
+
20
+ :ivar label (str): The text label of the menu item.
21
+ :ivar on_select (MenuAction): The action to perform when the item is selected.
22
+ """
23
+
24
+ label: str
25
+ on_select: MenuAction
26
+
27
+
28
+ @dataclass
29
+ class MenuStyle:
30
+ """
31
+ Styling options for the Menu.
32
+
33
+ :ivar normal (Color): Color for unselected items.
34
+ :ivar selected (Color): Color for the selected item.
35
+ :ivar line_height (int): Vertical spacing between items.
36
+ """
37
+
38
+ normal: Color = (220, 220, 220)
39
+ selected: Color = (255, 255, 0)
40
+ line_height: int = 28
41
+
42
+
43
+ class Menu:
44
+ """A simple text-based menu system."""
45
+
46
+ def __init__(
47
+ self,
48
+ items: Sequence[MenuItem],
49
+ *,
50
+ x: int = 40,
51
+ y: int = 40,
52
+ style: MenuStyle | None = None,
53
+ ):
54
+ """
55
+ :param items: Sequence of MenuItem instances to display.
56
+ type items: Sequence[MenuItem]
57
+
58
+ :param x: X coordinate for the menu's top-left corner.
59
+ :param y: Y coordinate for the menu's top-left corner.
60
+
61
+ :param style: Optional MenuStyle for customizing appearance.
62
+ :type style: MenuStyle | None
63
+ """
64
+ self.items = list(items)
65
+ self.x = x
66
+ self.y = y
67
+ self.style = style or MenuStyle()
68
+ self.selected_index = 0
69
+
70
+ def move_up(self):
71
+ """Move the selection up by one item, wrapping around if necessary."""
72
+ if self.items:
73
+ self.selected_index = (self.selected_index - 1) % len(self.items)
74
+
75
+ def move_down(self):
76
+ """Move the selection down by one item, wrapping around if necessary."""
77
+ if self.items:
78
+ self.selected_index = (self.selected_index + 1) % len(self.items)
79
+
80
+ def select(self):
81
+ """Select the currently highlighted item, invoking its action."""
82
+ if self.items:
83
+ self.items[self.selected_index].on_select()
84
+
85
+ def handle_event(
86
+ self,
87
+ event: Event,
88
+ *,
89
+ up_key: int,
90
+ down_key: int,
91
+ select_key: int,
92
+ ):
93
+ """
94
+ Handle an input event to navigate the menu.
95
+
96
+ :param event: The input event to handle.
97
+ :type event: Event
98
+
99
+ :param up_key: Key code for moving selection up.
100
+ type up_key: int
101
+
102
+ :param down_key: Key code for moving selection down.
103
+ :type down_key: int
104
+
105
+ :param select_key: Key code for selecting the current item.
106
+ :type select_key: int
107
+ """
108
+ if event.type != EventType.KEYDOWN or event.key is None:
109
+ return
110
+ if event.key == up_key:
111
+ self.move_up()
112
+ elif event.key == down_key:
113
+ self.move_down()
114
+ elif event.key == select_key:
115
+ self.select()
116
+
117
+ def draw(self, surface: Backend):
118
+ """
119
+ Draw the menu onto the given backend surface.
120
+
121
+ :param surface: The backend surface to draw on.
122
+ :type surface: Backend
123
+ """
124
+ for i, item in enumerate(self.items):
125
+ color = (
126
+ self.style.selected
127
+ if i == self.selected_index
128
+ else self.style.normal
129
+ )
130
+ surface.draw_text(
131
+ self.x,
132
+ self.y + i * self.style.line_height,
133
+ item.label,
134
+ color=color,
135
+ )
@@ -0,0 +1,288 @@
1
+ Metadata-Version: 2.4
2
+ Name: mini-arcade-core
3
+ Version: 0.9.0
4
+ Summary: Tiny scene-based game loop core for small arcade games.
5
+ License: Copyright (c) 2025 Santiago Rincón
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ of this software and associated documentation files (the "Software"), to deal
9
+ in the Software without restriction, including without limitation the rights
10
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ copies of the Software, and to permit persons to whom the Software is
12
+ furnished to do so, subject to the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be included in all
15
+ copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
+ SOFTWARE.
24
+ License-File: LICENSE
25
+ Author: Santiago Rincon
26
+ Author-email: rincores@gmail.com
27
+ Requires-Python: >=3.9,<3.12
28
+ Classifier: License :: Other/Proprietary License
29
+ Classifier: Programming Language :: Python :: 3
30
+ Classifier: Programming Language :: Python :: 3.9
31
+ Classifier: Programming Language :: Python :: 3.10
32
+ Classifier: Programming Language :: Python :: 3.11
33
+ Provides-Extra: dev
34
+ Requires-Dist: black (>=24.10,<25.0) ; extra == "dev"
35
+ Requires-Dist: isort (>=5.13,<6.0) ; extra == "dev"
36
+ Requires-Dist: mypy (>=1.5,<2.0) ; extra == "dev"
37
+ Requires-Dist: pillow (<12)
38
+ Requires-Dist: pylint (>=3.3,<4.0) ; extra == "dev"
39
+ Requires-Dist: pytest (>=8.3,<9.0) ; extra == "dev"
40
+ Requires-Dist: pytest-cov (>=6.0,<7.0) ; extra == "dev"
41
+ Description-Content-Type: text/markdown
42
+
43
+ # mini-arcade-core 🎮
44
+
45
+ Tiny Python game core for building simple scene-based arcade games
46
+ (Pong, Breakout, Space Invaders, etc.).
47
+
48
+ > Minimal, opinionated abstractions: **Game**, **Scene**, and **Entity** – nothing else.
49
+
50
+ ---
51
+
52
+ ## Features
53
+
54
+ - 🎯 **Tiny API surface**
55
+ - `GameConfig` – basic window & FPS configuration
56
+ - `Game` – abstract game core to plug your own backend (e.g. pygame)
57
+ - `Scene` – base class for screens/states (menus, gameplay, pause)
58
+ - `Entity` / `SpriteEntity` – simple game object primitives
59
+ - `run_game()` – convenience helper once a concrete `Game` backend is wired
60
+
61
+ - 🧩 **Backend-agnostic**
62
+ - The core doesn’t depend on any specific rendering/input library.
63
+ - You can build backends using `pygame`, `pyglet`, or something custom.
64
+
65
+ - 🕹️ **Perfect for small arcade projects**
66
+ - Pong, Breakout, Snake, Asteroids-likes, runners, flappy-likes, etc.
67
+ - Great for learning, experiments, and portfolio-friendly mini games.
68
+
69
+ ---
70
+
71
+ ## Installation
72
+
73
+ > **Note:** Adjust this once it’s on PyPI.
74
+
75
+ ```bash
76
+ # From a local checkout
77
+ pip install -e .
78
+ ```
79
+
80
+ Or, once published:
81
+
82
+ ```bash
83
+ pip install mini-arcade-core
84
+ ```
85
+
86
+ Requires Python 3.9–3.11.
87
+
88
+ ---
89
+
90
+ ## Core Concepts
91
+
92
+ ### ``GameConfig``
93
+
94
+ Basic configuration for your game:
95
+
96
+ ```python
97
+ from mini_arcade_core import GameConfig
98
+
99
+ config = GameConfig(
100
+ width=800,
101
+ height=600,
102
+ title="My Mini Arcade Game",
103
+ fps=60,
104
+ background_color=(0, 0, 0), # RGB
105
+ )
106
+ ```
107
+
108
+ ### ``Game``
109
+
110
+ Abstract base class that owns:
111
+
112
+ - the main loop
113
+ - the active ``Scene``
114
+ - high-level control like ``run()`` and ``change_scene()``
115
+
116
+ You subclass ``Game`` to plug in your rendering/input backend.
117
+
118
+ ### ``Scene``
119
+
120
+ Represents one state of your game (menu, gameplay, pause, etc.):
121
+
122
+ ```python
123
+ from mini_arcade_core import Scene, Game
124
+
125
+ class MyScene(Scene):
126
+ def on_enter(self):
127
+ print("Scene entered")
128
+
129
+ def on_exit(self):
130
+ print("Scene exited")
131
+
132
+ def handle_event(self, event: object):
133
+ # Handle input / events from your backend
134
+ pass
135
+
136
+ def update(self, dt: float):
137
+ # Game logic
138
+ pass
139
+
140
+ def draw(self, surface: object):
141
+ # Rendering via your backend
142
+ pass
143
+ ```
144
+
145
+ ### ``Entity`` & ``SpriteEntity``
146
+
147
+ Lightweight game object primitives:
148
+
149
+ ```python
150
+ from mini_arcade_core import Entity, SpriteEntity
151
+
152
+ class Ball(Entity):
153
+ def __init__(self):
154
+ self.x = 100.0
155
+ self.y = 100.0
156
+ self.vx = 200.0
157
+ self.vy = 150.0
158
+
159
+ def update(self, dt: float):
160
+ self.x += self.vx * dt
161
+ self.y += self.vy * dt
162
+
163
+ def draw(self, surface: object):
164
+ # Use your backend to draw the ball on `surface`
165
+ pass
166
+
167
+ paddle = SpriteEntity(x=50.0, y=300.0, width=80, height=16)
168
+ ```
169
+
170
+ ---
171
+
172
+ ### Example: Minimal pygame Backend
173
+
174
+ ``mini-arcade-core`` doesn’t force any backend.
175
+ Here’s a minimal example using pygame as a backend:
176
+
177
+ ```python
178
+ # example_pygame_game.py
179
+
180
+ import pygame
181
+ from mini_arcade_core import Game, GameConfig, Scene
182
+
183
+
184
+ class PygameGame(Game):
185
+ def __init__(self, config: GameConfig):
186
+ super().__init__(config)
187
+ pygame.init()
188
+ self._screen = pygame.display.set_mode(
189
+ (config.width, config.height)
190
+ )
191
+ pygame.display.set_caption(config.title)
192
+ self._clock = pygame.time.Clock()
193
+
194
+ def change_scene(self, scene: Scene):
195
+ if self._current_scene is not None:
196
+ self._current_scene.on_exit()
197
+ self._current_scene = scene
198
+ self._current_scene.on_enter()
199
+
200
+ def run(self, initial_scene: Scene):
201
+ self.change_scene(initial_scene)
202
+ self._running = True
203
+
204
+ while self._running:
205
+ dt = self._clock.tick(self.config.fps) / 1000.0
206
+
207
+ for event in pygame.event.get():
208
+ if event.type == pygame.QUIT:
209
+ self._running = False
210
+ elif self._current_scene is not None:
211
+ self._current_scene.handle_event(event)
212
+
213
+ if self._current_scene is not None:
214
+ self._current_scene.update(dt)
215
+ self._screen.fill(self.config.background_color)
216
+ self._current_scene.draw(self._screen)
217
+ pygame.display.flip()
218
+
219
+ pygame.quit()
220
+
221
+
222
+ class PongScene(Scene):
223
+ def __init__(self, game: Game):
224
+ super().__init__(game)
225
+ self.x = 100.0
226
+ self.y = 100.0
227
+ self.vx = 200.0
228
+ self.vy = 150.0
229
+ self.radius = 10
230
+
231
+ def on_enter(self):
232
+ print("Pong started")
233
+
234
+ def on_exit(self):
235
+ print("Pong finished")
236
+
237
+ def handle_event(self, event: object):
238
+ # no input yet
239
+ pass
240
+
241
+ def update(self, dt: float):
242
+ self.x += self.vx * dt
243
+ self.y += self.vy * dt
244
+
245
+ width = self.game.config.width
246
+ height = self.game.config.height
247
+
248
+ if self.x < self.radius or self.x > width - self.radius:
249
+ self.vx *= -1
250
+ if self.y < self.radius or self.y > height - self.radius:
251
+ self.vy *= -1
252
+
253
+ def draw(self, surface: pygame.Surface): # type: ignore[override]
254
+ pygame.draw.circle(
255
+ surface, (255, 255, 255), (int(self.x), int(self.y)), self.radius
256
+ )
257
+
258
+
259
+ if __name__ == "__main__":
260
+ cfg = GameConfig(width=640, height=360, title="Mini Arcade - Pong")
261
+ game = PygameGame(cfg)
262
+ scene = PongScene(game)
263
+ game.run(scene)
264
+ ```
265
+
266
+ Once you have a shared backend like PygameGame in its own package (or inside your game repo), you can also wire run_game() to use it instead of the abstract Game.
267
+
268
+ ---
269
+
270
+ ## Testing
271
+
272
+ This project uses pytest for tests.
273
+
274
+ ```bash
275
+ pip install -e ".[dev]"
276
+ pytest
277
+ ```
278
+
279
+ ### Roadmap
280
+
281
+ [ ] First concrete backend (e.g. ``mini-arcade-pygame``)
282
+ [ ] Example games: Pong, Breakout, Snake, Asteroids-lite, Endless Runner
283
+ [ ] Packaging the example games as separate repos using this core
284
+
285
+ ## License
286
+
287
+ ![License: MIT License](https://img.shields.io/badge/License-mit-blue.svg) — feel free to use this as a learning tool, or as a base for your own mini arcade projects.
288
+
@@ -0,0 +1,16 @@
1
+ mini_arcade_core/__init__.py,sha256=2PBNxk-rxjJxPvF6PvpdJ_rat0knyo6a-VRI8sROofI,2554
2
+ mini_arcade_core/backend.py,sha256=Q1yNNLX0Hh-1ItUWDV8k1QHuoGPNKHYeoTHT_CxKGPk,6339
3
+ mini_arcade_core/boundaries2d.py,sha256=H1HkCR1422MkQIEve2DFKvnav4RpvtLx-qTMxzmdDMQ,2610
4
+ mini_arcade_core/collision2d.py,sha256=iq800wsoYQNft3SA-W4jeY1wXhbpz2E7IkIorZBI238,1718
5
+ mini_arcade_core/entity.py,sha256=1AuE-_iMJYHxSyG783fsyByKK8Gx4vWWKMCEPjtgHXw,1776
6
+ mini_arcade_core/game.py,sha256=2DaFRdu7ogrwukh2MRnTh0Hy2r1XcaGSHEpLBNxfqIU,7273
7
+ mini_arcade_core/geometry2d.py,sha256=js791mMpsE_YbEoqtTOsOxNSflvKgSk6VeVKhj9N318,1282
8
+ mini_arcade_core/kinematics2d.py,sha256=twBx-1jEwdknIJEyYBUg4VZyAvw1d7pGHaGFd36TPbo,2085
9
+ mini_arcade_core/physics2d.py,sha256=qIq86qWVuvcnLIMEPH6xx7XQO4tQhfiMZY6FseuVOR8,1636
10
+ mini_arcade_core/scene.py,sha256=e0E81aHt6saYiGfA-1V2II1yWwcZfvqGTRAv8pZnQtY,3865
11
+ mini_arcade_core/ui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
+ mini_arcade_core/ui/menu.py,sha256=2CbsMvqebZWbUdhilhyTNDL1LGcdaPqHFsIM_J2MWK8,3636
13
+ mini_arcade_core-0.9.0.dist-info/METADATA,sha256=eLtfBDXL_S0FRergm-Jr4JqgS4S0kDnfmdsVFuVAgEM,8188
14
+ mini_arcade_core-0.9.0.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
15
+ mini_arcade_core-0.9.0.dist-info/licenses/LICENSE,sha256=3lHAuV0584cVS5vAqi2uC6GcsVgxUijvwvtZckyvaZ4,1096
16
+ mini_arcade_core-0.9.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: poetry-core 2.2.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2025 Santiago Rincón
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.