mini-arcade-core 0.8.0__tar.gz → 0.8.1__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.
- {mini_arcade_core-0.8.0 → mini_arcade_core-0.8.1}/PKG-INFO +1 -1
- {mini_arcade_core-0.8.0 → mini_arcade_core-0.8.1}/pyproject.toml +1 -1
- {mini_arcade_core-0.8.0 → mini_arcade_core-0.8.1}/src/mini_arcade_core/game.py +79 -11
- {mini_arcade_core-0.8.0 → mini_arcade_core-0.8.1}/src/mini_arcade_core/scene.py +6 -0
- mini_arcade_core-0.8.1/src/mini_arcade_core/ui/__init__.py +0 -0
- mini_arcade_core-0.8.1/src/mini_arcade_core/ui/menu.py +135 -0
- {mini_arcade_core-0.8.0 → mini_arcade_core-0.8.1}/LICENSE +0 -0
- {mini_arcade_core-0.8.0 → mini_arcade_core-0.8.1}/README.md +0 -0
- {mini_arcade_core-0.8.0 → mini_arcade_core-0.8.1}/src/mini_arcade_core/__init__.py +0 -0
- {mini_arcade_core-0.8.0 → mini_arcade_core-0.8.1}/src/mini_arcade_core/backend.py +0 -0
- {mini_arcade_core-0.8.0 → mini_arcade_core-0.8.1}/src/mini_arcade_core/boundaries2d.py +0 -0
- {mini_arcade_core-0.8.0 → mini_arcade_core-0.8.1}/src/mini_arcade_core/collision2d.py +0 -0
- {mini_arcade_core-0.8.0 → mini_arcade_core-0.8.1}/src/mini_arcade_core/entity.py +0 -0
- {mini_arcade_core-0.8.0 → mini_arcade_core-0.8.1}/src/mini_arcade_core/geometry2d.py +0 -0
- {mini_arcade_core-0.8.0 → mini_arcade_core-0.8.1}/src/mini_arcade_core/kinematics2d.py +0 -0
- {mini_arcade_core-0.8.0 → mini_arcade_core-0.8.1}/src/mini_arcade_core/physics2d.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "mini-arcade-core"
|
|
7
|
-
version = "0.8.
|
|
7
|
+
version = "0.8.1"
|
|
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" },
|
|
@@ -40,6 +40,12 @@ class GameConfig:
|
|
|
40
40
|
backend: Backend | None = None
|
|
41
41
|
|
|
42
42
|
|
|
43
|
+
@dataclass
|
|
44
|
+
class _StackEntry:
|
|
45
|
+
scene: "Scene"
|
|
46
|
+
as_overlay: bool = False
|
|
47
|
+
|
|
48
|
+
|
|
43
49
|
class Game:
|
|
44
50
|
"""Core game object responsible for managing the main loop and active scene."""
|
|
45
51
|
|
|
@@ -59,6 +65,16 @@ class Game:
|
|
|
59
65
|
"GameConfig.backend must be set to a Backend instance"
|
|
60
66
|
)
|
|
61
67
|
self.backend: Backend = config.backend
|
|
68
|
+
self._scene_stack: list[_StackEntry] = []
|
|
69
|
+
|
|
70
|
+
def current_scene(self) -> "Scene | None":
|
|
71
|
+
"""
|
|
72
|
+
Get the currently active scene.
|
|
73
|
+
|
|
74
|
+
:return: The active Scene instance, or None if no scene is active.
|
|
75
|
+
:rtype: Scene | None
|
|
76
|
+
"""
|
|
77
|
+
return self._scene_stack[-1].scene if self._scene_stack else None
|
|
62
78
|
|
|
63
79
|
def change_scene(self, scene: Scene):
|
|
64
80
|
"""
|
|
@@ -68,10 +84,59 @@ class Game:
|
|
|
68
84
|
:param scene: The new scene to activate.
|
|
69
85
|
:type scene: Scene
|
|
70
86
|
"""
|
|
71
|
-
|
|
72
|
-
self.
|
|
73
|
-
|
|
74
|
-
|
|
87
|
+
while self._scene_stack:
|
|
88
|
+
entry = self._scene_stack.pop()
|
|
89
|
+
entry.scene.on_exit()
|
|
90
|
+
|
|
91
|
+
self._scene_stack.append(_StackEntry(scene=scene, as_overlay=False))
|
|
92
|
+
scene.on_enter()
|
|
93
|
+
|
|
94
|
+
def push_scene(self, scene: "Scene", as_overlay: bool = False):
|
|
95
|
+
"""
|
|
96
|
+
Push a scene on top of the current one.
|
|
97
|
+
If as_overlay=True, underlying scene(s) may still be drawn but never updated.
|
|
98
|
+
"""
|
|
99
|
+
top = self.current_scene()
|
|
100
|
+
if top is not None:
|
|
101
|
+
top.on_pause()
|
|
102
|
+
|
|
103
|
+
self._scene_stack.append(
|
|
104
|
+
_StackEntry(scene=scene, as_overlay=as_overlay)
|
|
105
|
+
)
|
|
106
|
+
scene.on_enter()
|
|
107
|
+
|
|
108
|
+
def pop_scene(self) -> "Scene | None":
|
|
109
|
+
"""Pop the top scene. If stack becomes empty, quit."""
|
|
110
|
+
if not self._scene_stack:
|
|
111
|
+
return None
|
|
112
|
+
|
|
113
|
+
popped = self._scene_stack.pop()
|
|
114
|
+
popped.scene.on_exit()
|
|
115
|
+
|
|
116
|
+
top = self.current_scene()
|
|
117
|
+
if top is None:
|
|
118
|
+
self.quit()
|
|
119
|
+
return popped.scene
|
|
120
|
+
|
|
121
|
+
top.on_resume()
|
|
122
|
+
return popped.scene
|
|
123
|
+
|
|
124
|
+
def _visible_stack(self) -> list["Scene"]:
|
|
125
|
+
"""
|
|
126
|
+
Return the list of scenes that should be drawn (base + overlays).
|
|
127
|
+
We draw from the top-most non-overlay scene upward.
|
|
128
|
+
"""
|
|
129
|
+
if not self._scene_stack:
|
|
130
|
+
return []
|
|
131
|
+
|
|
132
|
+
# find top-most base scene (as_overlay=False)
|
|
133
|
+
base_idx = 0
|
|
134
|
+
for i in range(len(self._scene_stack) - 1, -1, -1):
|
|
135
|
+
if not self._scene_stack[i].as_overlay:
|
|
136
|
+
base_idx = i
|
|
137
|
+
break
|
|
138
|
+
|
|
139
|
+
return [e.scene for e in self._scene_stack[base_idx:]]
|
|
75
140
|
|
|
76
141
|
def quit(self):
|
|
77
142
|
"""Request that the main loop stops."""
|
|
@@ -104,24 +169,27 @@ class Game:
|
|
|
104
169
|
dt = now - last_time
|
|
105
170
|
last_time = now
|
|
106
171
|
|
|
107
|
-
|
|
108
|
-
if
|
|
172
|
+
top = self.current_scene()
|
|
173
|
+
if top is None:
|
|
109
174
|
break
|
|
110
175
|
|
|
111
176
|
for ev in backend.poll_events():
|
|
112
|
-
|
|
177
|
+
top.handle_event(ev)
|
|
113
178
|
|
|
114
|
-
|
|
179
|
+
top.update(dt)
|
|
115
180
|
|
|
116
181
|
backend.begin_frame()
|
|
117
|
-
scene.
|
|
182
|
+
for scene in self._visible_stack():
|
|
183
|
+
scene.draw(backend)
|
|
118
184
|
backend.end_frame()
|
|
119
185
|
|
|
120
186
|
if target_dt > 0 and dt < target_dt:
|
|
121
187
|
sleep(target_dt - dt)
|
|
122
188
|
|
|
123
|
-
|
|
124
|
-
|
|
189
|
+
# exit remaining scenes
|
|
190
|
+
while self._scene_stack:
|
|
191
|
+
entry = self._scene_stack.pop()
|
|
192
|
+
entry.scene.on_exit()
|
|
125
193
|
|
|
126
194
|
@staticmethod
|
|
127
195
|
def _convert_bmp_to_image(bmp_path: str, out_path: str) -> bool:
|
|
File without changes
|
|
@@ -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
|
+
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|