mini-arcade-core 0.7.5__py3-none-any.whl → 0.8.1__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.
- mini_arcade_core/__init__.py +30 -2
- mini_arcade_core/backend.py +13 -11
- mini_arcade_core/boundaries2d.py +107 -0
- mini_arcade_core/collision2d.py +71 -0
- mini_arcade_core/entity.py +31 -14
- mini_arcade_core/game.py +82 -13
- mini_arcade_core/geometry2d.py +69 -0
- mini_arcade_core/kinematics2d.py +78 -0
- mini_arcade_core/physics2d.py +73 -0
- mini_arcade_core/scene.py +62 -10
- mini_arcade_core/ui/__init__.py +0 -0
- mini_arcade_core/ui/menu.py +135 -0
- {mini_arcade_core-0.7.5.dist-info → mini_arcade_core-0.8.1.dist-info}/METADATA +18 -18
- mini_arcade_core-0.8.1.dist-info/RECORD +16 -0
- mini_arcade_core-0.7.5.dist-info/RECORD +0 -9
- {mini_arcade_core-0.7.5.dist-info → mini_arcade_core-0.8.1.dist-info}/WHEEL +0 -0
- {mini_arcade_core-0.7.5.dist-info → mini_arcade_core-0.8.1.dist-info}/licenses/LICENSE +0 -0
mini_arcade_core/__init__.py
CHANGED
|
@@ -9,8 +9,18 @@ import logging
|
|
|
9
9
|
from importlib.metadata import PackageNotFoundError, version
|
|
10
10
|
|
|
11
11
|
from .backend import Backend, Event, EventType
|
|
12
|
-
from .
|
|
12
|
+
from .boundaries2d import (
|
|
13
|
+
RectKinematic,
|
|
14
|
+
RectSprite,
|
|
15
|
+
VerticalBounce,
|
|
16
|
+
VerticalWrap,
|
|
17
|
+
)
|
|
18
|
+
from .collision2d import RectCollider
|
|
19
|
+
from .entity import Entity, KinematicEntity, SpriteEntity
|
|
13
20
|
from .game import Game, GameConfig
|
|
21
|
+
from .geometry2d import Bounds2D, Position2D, Size2D
|
|
22
|
+
from .kinematics2d import KinematicData
|
|
23
|
+
from .physics2d import Velocity2D
|
|
14
24
|
from .scene import Scene
|
|
15
25
|
|
|
16
26
|
logger = logging.getLogger(__name__)
|
|
@@ -25,8 +35,15 @@ def run_game(initial_scene_cls: type[Scene], config: GameConfig | None = None):
|
|
|
25
35
|
|
|
26
36
|
:param config: Optional GameConfig to customize game settings.
|
|
27
37
|
:type config: GameConfig | None
|
|
38
|
+
|
|
39
|
+
:raises ValueError: If the provided config does not have a valid Backend.
|
|
28
40
|
"""
|
|
29
|
-
|
|
41
|
+
cfg = config or GameConfig()
|
|
42
|
+
if config.backend is None:
|
|
43
|
+
raise ValueError(
|
|
44
|
+
"GameConfig.backend must be set to a Backend instance"
|
|
45
|
+
)
|
|
46
|
+
game = Game(cfg)
|
|
30
47
|
scene = initial_scene_cls(game)
|
|
31
48
|
game.run(scene)
|
|
32
49
|
|
|
@@ -41,6 +58,17 @@ __all__ = [
|
|
|
41
58
|
"Backend",
|
|
42
59
|
"Event",
|
|
43
60
|
"EventType",
|
|
61
|
+
"Velocity2D",
|
|
62
|
+
"Position2D",
|
|
63
|
+
"Size2D",
|
|
64
|
+
"KinematicEntity",
|
|
65
|
+
"KinematicData",
|
|
66
|
+
"RectCollider",
|
|
67
|
+
"VerticalBounce",
|
|
68
|
+
"Bounds2D",
|
|
69
|
+
"VerticalWrap",
|
|
70
|
+
"RectSprite",
|
|
71
|
+
"RectKinematic",
|
|
44
72
|
]
|
|
45
73
|
|
|
46
74
|
PACKAGE_NAME = "mini-arcade-core" # or whatever is in your pyproject.toml
|
mini_arcade_core/backend.py
CHANGED
|
@@ -7,7 +7,9 @@ from __future__ import annotations
|
|
|
7
7
|
|
|
8
8
|
from dataclasses import dataclass
|
|
9
9
|
from enum import Enum, auto
|
|
10
|
-
from typing import Iterable, Protocol
|
|
10
|
+
from typing import Iterable, Protocol, Tuple, Union
|
|
11
|
+
|
|
12
|
+
Color = Union[Tuple[int, int, int], Tuple[int, int, int, int]]
|
|
11
13
|
|
|
12
14
|
|
|
13
15
|
class EventType(Enum):
|
|
@@ -50,7 +52,7 @@ class Backend(Protocol):
|
|
|
50
52
|
mini-arcade-core only talks to this protocol, never to SDL/pygame directly.
|
|
51
53
|
"""
|
|
52
54
|
|
|
53
|
-
def init(self, width: int, height: int, title: str)
|
|
55
|
+
def init(self, width: int, height: int, title: str):
|
|
54
56
|
"""
|
|
55
57
|
Initialize the backend and open a window.
|
|
56
58
|
Should be called once before the main loop.
|
|
@@ -74,7 +76,7 @@ class Backend(Protocol):
|
|
|
74
76
|
:rtype: Iterable[Event]
|
|
75
77
|
"""
|
|
76
78
|
|
|
77
|
-
def set_clear_color(self, r: int, g: int, b: int)
|
|
79
|
+
def set_clear_color(self, r: int, g: int, b: int):
|
|
78
80
|
"""
|
|
79
81
|
Set the background/clear color used by begin_frame.
|
|
80
82
|
|
|
@@ -88,12 +90,12 @@ class Backend(Protocol):
|
|
|
88
90
|
:type b: int
|
|
89
91
|
"""
|
|
90
92
|
|
|
91
|
-
def begin_frame(self)
|
|
93
|
+
def begin_frame(self):
|
|
92
94
|
"""
|
|
93
95
|
Prepare for drawing a new frame (e.g. clear screen).
|
|
94
96
|
"""
|
|
95
97
|
|
|
96
|
-
def end_frame(self)
|
|
98
|
+
def end_frame(self):
|
|
97
99
|
"""
|
|
98
100
|
Present the frame to the user (swap buffers).
|
|
99
101
|
"""
|
|
@@ -106,8 +108,8 @@ class Backend(Protocol):
|
|
|
106
108
|
y: int,
|
|
107
109
|
w: int,
|
|
108
110
|
h: int,
|
|
109
|
-
color:
|
|
110
|
-
)
|
|
111
|
+
color: Color = (255, 255, 255),
|
|
112
|
+
):
|
|
111
113
|
"""
|
|
112
114
|
Draw a filled rectangle in some default color.
|
|
113
115
|
We'll keep this minimal for now; later we can extend with colors/sprites.
|
|
@@ -125,7 +127,7 @@ class Backend(Protocol):
|
|
|
125
127
|
:type h: int
|
|
126
128
|
|
|
127
129
|
:param color: RGB color tuple.
|
|
128
|
-
:type color:
|
|
130
|
+
:type color: Color
|
|
129
131
|
"""
|
|
130
132
|
|
|
131
133
|
# pylint: enable=too-many-arguments,too-many-positional-arguments
|
|
@@ -135,8 +137,8 @@ class Backend(Protocol):
|
|
|
135
137
|
x: int,
|
|
136
138
|
y: int,
|
|
137
139
|
text: str,
|
|
138
|
-
color:
|
|
139
|
-
)
|
|
140
|
+
color: Color = (255, 255, 255),
|
|
141
|
+
):
|
|
140
142
|
"""
|
|
141
143
|
Draw text at the given position in a default font and color.
|
|
142
144
|
|
|
@@ -153,7 +155,7 @@ class Backend(Protocol):
|
|
|
153
155
|
:type text: str
|
|
154
156
|
|
|
155
157
|
:param color: RGB color tuple.
|
|
156
|
-
:type color:
|
|
158
|
+
:type color: Color
|
|
157
159
|
"""
|
|
158
160
|
|
|
159
161
|
def capture_frame(self, path: str | None = None) -> bytes | None:
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Boundary behaviors for 2D rectangular objects.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from typing import Protocol
|
|
9
|
+
|
|
10
|
+
from .geometry2d import Bounds2D, Position2D, Size2D
|
|
11
|
+
from .physics2d import Velocity2D
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class RectKinematic(Protocol):
|
|
15
|
+
"""
|
|
16
|
+
Minimal contract for something that can bounce:
|
|
17
|
+
- position: Position2D
|
|
18
|
+
- size: Size2D
|
|
19
|
+
- velocity: Velocity2D
|
|
20
|
+
|
|
21
|
+
:ivar position (Position2D): Top-left position of the object.
|
|
22
|
+
:ivar size (Size2D): Size of the object.
|
|
23
|
+
:ivar velocity (Velocity2D): Velocity of the object.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
position: Position2D
|
|
27
|
+
size: Size2D
|
|
28
|
+
velocity: Velocity2D
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass
|
|
32
|
+
class VerticalBounce:
|
|
33
|
+
"""
|
|
34
|
+
Bounce an object off the top/bottom bounds by inverting vy
|
|
35
|
+
and clamping its position inside the bounds.
|
|
36
|
+
|
|
37
|
+
:ivar bounds (Bounds2D): The boundary rectangle.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
bounds: Bounds2D
|
|
41
|
+
|
|
42
|
+
def apply(self, obj: RectKinematic):
|
|
43
|
+
"""
|
|
44
|
+
Apply vertical bounce to the given object.
|
|
45
|
+
|
|
46
|
+
:param obj: The object to apply the bounce to.
|
|
47
|
+
:type obj: RectKinematic
|
|
48
|
+
"""
|
|
49
|
+
top = self.bounds.top
|
|
50
|
+
bottom = self.bounds.bottom
|
|
51
|
+
|
|
52
|
+
# Top collision
|
|
53
|
+
if obj.position.y <= top:
|
|
54
|
+
obj.position.y = top
|
|
55
|
+
obj.velocity.vy *= -1
|
|
56
|
+
|
|
57
|
+
# Bottom collision
|
|
58
|
+
if obj.position.y + obj.size.height >= bottom:
|
|
59
|
+
obj.position.y = bottom - obj.size.height
|
|
60
|
+
obj.velocity.vy *= -1
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class RectSprite(Protocol):
|
|
64
|
+
"""
|
|
65
|
+
Minimal contract for something that can wrap:
|
|
66
|
+
- position: Position2D
|
|
67
|
+
- size: Size2D
|
|
68
|
+
(no velocity required)
|
|
69
|
+
|
|
70
|
+
:ivar position (Position2D): Top-left position of the object.
|
|
71
|
+
:ivar size (Size2D): Size of the object.
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
position: Position2D
|
|
75
|
+
size: Size2D
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@dataclass
|
|
79
|
+
class VerticalWrap:
|
|
80
|
+
"""
|
|
81
|
+
Wrap an object top <-> bottom.
|
|
82
|
+
|
|
83
|
+
If it leaves above the top, it reappears at the bottom.
|
|
84
|
+
If it leaves below the bottom, it reappears at the top.
|
|
85
|
+
|
|
86
|
+
:ivar bounds (Bounds2D): The boundary rectangle.
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
bounds: Bounds2D
|
|
90
|
+
|
|
91
|
+
def apply(self, obj: RectSprite):
|
|
92
|
+
"""
|
|
93
|
+
Apply vertical wrap to the given object.
|
|
94
|
+
|
|
95
|
+
:param obj: The object to apply the wrap to.
|
|
96
|
+
:type obj: RectSprite
|
|
97
|
+
"""
|
|
98
|
+
top = self.bounds.top
|
|
99
|
+
bottom = self.bounds.bottom
|
|
100
|
+
|
|
101
|
+
# Completely above top → appear at bottom
|
|
102
|
+
if obj.position.y + obj.size.height < top:
|
|
103
|
+
obj.position.y = bottom
|
|
104
|
+
|
|
105
|
+
# Completely below bottom → appear at top
|
|
106
|
+
elif obj.position.y > bottom:
|
|
107
|
+
obj.position.y = top - obj.size.height
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""
|
|
2
|
+
2D collision detection helpers.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
|
|
9
|
+
from .geometry2d import Position2D, Size2D
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _rects_intersect(
|
|
13
|
+
pos_a: Position2D,
|
|
14
|
+
size_a: Size2D,
|
|
15
|
+
pos_b: Position2D,
|
|
16
|
+
size_b: Size2D,
|
|
17
|
+
) -> bool:
|
|
18
|
+
"""
|
|
19
|
+
Low-level AABB check. Internal helper.
|
|
20
|
+
|
|
21
|
+
:param pos_a: Top-left position of rectangle A.
|
|
22
|
+
:type pos_a: Position2D
|
|
23
|
+
|
|
24
|
+
:param size_a: Size of rectangle A.
|
|
25
|
+
:type size_a: Size2D
|
|
26
|
+
|
|
27
|
+
:param pos_b: Top-left position of rectangle B.
|
|
28
|
+
:type pos_b: Position2D
|
|
29
|
+
|
|
30
|
+
:param size_b: Size of rectangle B.
|
|
31
|
+
:type size_b: Size2D
|
|
32
|
+
|
|
33
|
+
:return: True if the rectangles intersect.
|
|
34
|
+
:rtype: bool
|
|
35
|
+
"""
|
|
36
|
+
return not (
|
|
37
|
+
pos_a.x + size_a.width < pos_b.x
|
|
38
|
+
or pos_a.x > pos_b.x + size_b.width
|
|
39
|
+
or pos_a.y + size_a.height < pos_b.y
|
|
40
|
+
or pos_a.y > pos_b.y + size_b.height
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass
|
|
45
|
+
class RectCollider:
|
|
46
|
+
"""
|
|
47
|
+
OOP collision helper that wraps a Position2D + Size2D pair.
|
|
48
|
+
|
|
49
|
+
It does NOT own the data – it just points to them. If the
|
|
50
|
+
entity moves (position changes), the collider “sees” it.
|
|
51
|
+
|
|
52
|
+
:ivar position (Position2D): Top-left position of the rectangle.
|
|
53
|
+
:ivar size (Size2D): Size of the rectangle.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
position: Position2D
|
|
57
|
+
size: Size2D
|
|
58
|
+
|
|
59
|
+
def intersects(self, other: "RectCollider") -> bool:
|
|
60
|
+
"""
|
|
61
|
+
High-level OOP method to check collision with another collider.
|
|
62
|
+
|
|
63
|
+
;param other: The other rectangle collider.
|
|
64
|
+
:type other: RectCollider
|
|
65
|
+
|
|
66
|
+
:return: True if the rectangles intersect.
|
|
67
|
+
:rtype: bool
|
|
68
|
+
"""
|
|
69
|
+
return _rects_intersect(
|
|
70
|
+
self.position, self.size, other.position, other.size
|
|
71
|
+
)
|
mini_arcade_core/entity.py
CHANGED
|
@@ -6,6 +6,10 @@ from __future__ import annotations
|
|
|
6
6
|
|
|
7
7
|
from typing import Any
|
|
8
8
|
|
|
9
|
+
from .collision2d import RectCollider
|
|
10
|
+
from .geometry2d import Position2D, Size2D
|
|
11
|
+
from .kinematics2d import KinematicData
|
|
12
|
+
|
|
9
13
|
|
|
10
14
|
class Entity:
|
|
11
15
|
"""Entity base class for game objects."""
|
|
@@ -30,22 +34,35 @@ class Entity:
|
|
|
30
34
|
class SpriteEntity(Entity):
|
|
31
35
|
"""Entity with position and size."""
|
|
32
36
|
|
|
33
|
-
def __init__(self,
|
|
37
|
+
def __init__(self, position: Position2D, size: Size2D):
|
|
38
|
+
"""
|
|
39
|
+
:param position: Top-left position of the entity.
|
|
40
|
+
:type position: Position2D
|
|
41
|
+
|
|
42
|
+
:param size: Size of the entity.
|
|
43
|
+
:type size: Size2D
|
|
34
44
|
"""
|
|
35
|
-
|
|
36
|
-
|
|
45
|
+
self.position = Position2D(float(position.x), float(position.y))
|
|
46
|
+
self.size = Size2D(int(size.width), int(size.height))
|
|
47
|
+
self.collider = RectCollider(self.position, self.size)
|
|
37
48
|
|
|
38
|
-
:param y: Y position.
|
|
39
|
-
:type y: float
|
|
40
49
|
|
|
41
|
-
|
|
42
|
-
|
|
50
|
+
class KinematicEntity(SpriteEntity):
|
|
51
|
+
"""SpriteEntity with velocity-based movement."""
|
|
43
52
|
|
|
44
|
-
|
|
45
|
-
|
|
53
|
+
def __init__(self, kinematic_data: KinematicData):
|
|
54
|
+
"""
|
|
55
|
+
:param kinematic_data: Kinematic data for the entity.
|
|
56
|
+
:type kinematic_data: KinematicData
|
|
46
57
|
"""
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
58
|
+
super().__init__(
|
|
59
|
+
position=kinematic_data.position,
|
|
60
|
+
size=kinematic_data.size,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
self.velocity = kinematic_data.velocity
|
|
64
|
+
|
|
65
|
+
def update(self, dt: float):
|
|
66
|
+
self.position.x, self.position.y = self.velocity.advance(
|
|
67
|
+
self.position.x, self.position.y, dt
|
|
68
|
+
)
|
mini_arcade_core/game.py
CHANGED
|
@@ -29,7 +29,7 @@ class GameConfig:
|
|
|
29
29
|
:ivar title: Title of the game window.
|
|
30
30
|
:ivar fps: Target frames per second.
|
|
31
31
|
:ivar background_color: RGB background color.
|
|
32
|
-
:ivar backend: Optional
|
|
32
|
+
:ivar backend: Optional Backend instance to use for rendering and input.
|
|
33
33
|
"""
|
|
34
34
|
|
|
35
35
|
width: int = 800
|
|
@@ -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
|
|
|
@@ -47,17 +53,28 @@ class Game:
|
|
|
47
53
|
"""
|
|
48
54
|
:param config: Game configuration options.
|
|
49
55
|
:type config: GameConfig
|
|
56
|
+
|
|
57
|
+
:raises ValueError: If the provided config does not have a valid Backend.
|
|
50
58
|
"""
|
|
51
59
|
self.config = config
|
|
52
60
|
self._current_scene: Scene | None = None
|
|
53
61
|
self._running: bool = False
|
|
54
|
-
self.backend: Backend | None = config.backend
|
|
55
62
|
|
|
56
63
|
if config.backend is None:
|
|
57
64
|
raise ValueError(
|
|
58
65
|
"GameConfig.backend must be set to a Backend instance"
|
|
59
66
|
)
|
|
60
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
|
|
61
78
|
|
|
62
79
|
def change_scene(self, scene: Scene):
|
|
63
80
|
"""
|
|
@@ -67,10 +84,59 @@ class Game:
|
|
|
67
84
|
:param scene: The new scene to activate.
|
|
68
85
|
:type scene: Scene
|
|
69
86
|
"""
|
|
70
|
-
|
|
71
|
-
self.
|
|
72
|
-
|
|
73
|
-
|
|
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:]]
|
|
74
140
|
|
|
75
141
|
def quit(self):
|
|
76
142
|
"""Request that the main loop stops."""
|
|
@@ -103,24 +169,27 @@ class Game:
|
|
|
103
169
|
dt = now - last_time
|
|
104
170
|
last_time = now
|
|
105
171
|
|
|
106
|
-
|
|
107
|
-
if
|
|
172
|
+
top = self.current_scene()
|
|
173
|
+
if top is None:
|
|
108
174
|
break
|
|
109
175
|
|
|
110
176
|
for ev in backend.poll_events():
|
|
111
|
-
|
|
177
|
+
top.handle_event(ev)
|
|
112
178
|
|
|
113
|
-
|
|
179
|
+
top.update(dt)
|
|
114
180
|
|
|
115
181
|
backend.begin_frame()
|
|
116
|
-
scene.
|
|
182
|
+
for scene in self._visible_stack():
|
|
183
|
+
scene.draw(backend)
|
|
117
184
|
backend.end_frame()
|
|
118
185
|
|
|
119
186
|
if target_dt > 0 and dt < target_dt:
|
|
120
187
|
sleep(target_dt - dt)
|
|
121
188
|
|
|
122
|
-
|
|
123
|
-
|
|
189
|
+
# exit remaining scenes
|
|
190
|
+
while self._scene_stack:
|
|
191
|
+
entry = self._scene_stack.pop()
|
|
192
|
+
entry.scene.on_exit()
|
|
124
193
|
|
|
125
194
|
@staticmethod
|
|
126
195
|
def _convert_bmp_to_image(bmp_path: str, out_path: str) -> bool:
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""
|
|
2
|
+
2D geometry data structures.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class Position2D:
|
|
12
|
+
"""
|
|
13
|
+
Simple 2D position.
|
|
14
|
+
|
|
15
|
+
:ivar x (float): X coordinate.
|
|
16
|
+
:ivar y (float): Y coordinate.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
x: float
|
|
20
|
+
y: float
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class Size2D:
|
|
25
|
+
"""
|
|
26
|
+
Simple 2D size.
|
|
27
|
+
|
|
28
|
+
:ivar width (int): Width.
|
|
29
|
+
:ivar height (int): Height.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
width: int
|
|
33
|
+
height: int
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class Bounds2D:
|
|
38
|
+
"""
|
|
39
|
+
Axis-aligned rectangular bounds in world space.
|
|
40
|
+
(left, top) .. (right, bottom)
|
|
41
|
+
|
|
42
|
+
:ivar left (float): Left boundary.
|
|
43
|
+
:ivar top (float): Top boundary.
|
|
44
|
+
:ivar right (float): Right boundary.
|
|
45
|
+
:ivar bottom (float): Bottom boundary.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
left: float
|
|
49
|
+
top: float
|
|
50
|
+
right: float
|
|
51
|
+
bottom: float
|
|
52
|
+
|
|
53
|
+
@classmethod
|
|
54
|
+
def from_size(cls, size: "Size2D") -> "Bounds2D":
|
|
55
|
+
"""
|
|
56
|
+
Convenience factory for screen/world bounds starting at (0, 0).
|
|
57
|
+
|
|
58
|
+
:param size: Size2D defining the bounds.
|
|
59
|
+
:type size: Size2D
|
|
60
|
+
|
|
61
|
+
:return: Bounds2D from (0,0) to (size.width, size.height).
|
|
62
|
+
:rtype: Bounds2D
|
|
63
|
+
"""
|
|
64
|
+
return cls(
|
|
65
|
+
left=0.0,
|
|
66
|
+
top=0.0,
|
|
67
|
+
right=float(size.width),
|
|
68
|
+
bottom=float(size.height),
|
|
69
|
+
)
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Kinematic helpers for mini_arcade_core (position + size + velocity).
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
from .backend import Color
|
|
11
|
+
from .geometry2d import Position2D, Size2D
|
|
12
|
+
from .physics2d import Velocity2D
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class KinematicData:
|
|
17
|
+
"""
|
|
18
|
+
Simple data structure to hold position, size, and velocity.
|
|
19
|
+
|
|
20
|
+
:ivar position (Position2D): Top-left position of the object.
|
|
21
|
+
:ivar size (Size2D): Size of the object.
|
|
22
|
+
:ivar velocity (Velocity2D): Velocity of the object.
|
|
23
|
+
:ivar color (Optional[Color]): Optional color for rendering.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
position: Position2D
|
|
27
|
+
size: Size2D
|
|
28
|
+
velocity: Velocity2D
|
|
29
|
+
color: Optional[Color] = None # future use
|
|
30
|
+
|
|
31
|
+
# Justification: Convenience factory with many params.
|
|
32
|
+
# pylint: disable=too-many-arguments,too-many-positional-arguments
|
|
33
|
+
@classmethod
|
|
34
|
+
def rect(
|
|
35
|
+
cls,
|
|
36
|
+
x: float,
|
|
37
|
+
y: float,
|
|
38
|
+
width: int,
|
|
39
|
+
height: int,
|
|
40
|
+
vx: float = 0.0,
|
|
41
|
+
vy: float = 0.0,
|
|
42
|
+
color: Optional[Color] = None,
|
|
43
|
+
) -> "KinematicData":
|
|
44
|
+
"""
|
|
45
|
+
Convenience factory for rectangular kinematic data.
|
|
46
|
+
|
|
47
|
+
:param x: Top-left X position.
|
|
48
|
+
:type x: float
|
|
49
|
+
|
|
50
|
+
:param y: Top-left Y position.
|
|
51
|
+
:type y: float
|
|
52
|
+
|
|
53
|
+
:param width: Width of the object.
|
|
54
|
+
:type width: int
|
|
55
|
+
|
|
56
|
+
:param height: Height of the object.
|
|
57
|
+
:type height: int
|
|
58
|
+
|
|
59
|
+
:param vx: Velocity in the X direction.
|
|
60
|
+
:type vx: float
|
|
61
|
+
|
|
62
|
+
:param vy: Velocity in the Y direction.
|
|
63
|
+
:type vy: float
|
|
64
|
+
|
|
65
|
+
:param color: Optional color for rendering.
|
|
66
|
+
:type color: Optional[Color]
|
|
67
|
+
|
|
68
|
+
:return: KinematicData instance with the specified parameters.
|
|
69
|
+
:rtype: KinematicData
|
|
70
|
+
"""
|
|
71
|
+
return cls(
|
|
72
|
+
position=Position2D(float(x), float(y)),
|
|
73
|
+
size=Size2D(int(width), int(height)),
|
|
74
|
+
velocity=Velocity2D(float(vx), float(vy)),
|
|
75
|
+
color=color,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
# pylint: enable=too-many-arguments,too-many-positional-arguments
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Simple 2D physics utilities.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class Velocity2D:
|
|
12
|
+
"""
|
|
13
|
+
Simple 2D velocity vector.
|
|
14
|
+
|
|
15
|
+
:ivar vx (float): Velocity in the X direction.
|
|
16
|
+
:ivar vy (float): Velocity in the Y direction.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
vx: float = 0.0
|
|
20
|
+
vy: float = 0.0
|
|
21
|
+
|
|
22
|
+
def advance(self, x: float, y: float, dt: float) -> tuple[float, float]:
|
|
23
|
+
"""Return new (x, y) after dt seconds."""
|
|
24
|
+
return x + self.vx * dt, y + self.vy * dt
|
|
25
|
+
|
|
26
|
+
def stop(self):
|
|
27
|
+
"""Stop movement in both axes."""
|
|
28
|
+
self.vx = 0.0
|
|
29
|
+
self.vy = 0.0
|
|
30
|
+
|
|
31
|
+
def stop_x(self):
|
|
32
|
+
"""Stop horizontal movement."""
|
|
33
|
+
self.vx = 0.0
|
|
34
|
+
|
|
35
|
+
def stop_y(self):
|
|
36
|
+
"""Stop vertical movement."""
|
|
37
|
+
self.vy = 0.0
|
|
38
|
+
|
|
39
|
+
def move_up(self, speed: float):
|
|
40
|
+
"""
|
|
41
|
+
Set vertical velocity upwards (negative Y).
|
|
42
|
+
|
|
43
|
+
:param speed: Speed to set.
|
|
44
|
+
:type speed: float
|
|
45
|
+
"""
|
|
46
|
+
self.vy = -abs(speed)
|
|
47
|
+
|
|
48
|
+
def move_down(self, speed: float):
|
|
49
|
+
"""
|
|
50
|
+
Set vertical velocity downwards (positive Y).
|
|
51
|
+
|
|
52
|
+
:param speed: Speed to set.
|
|
53
|
+
:type speed: float
|
|
54
|
+
"""
|
|
55
|
+
self.vy = abs(speed)
|
|
56
|
+
|
|
57
|
+
def move_left(self, speed: float):
|
|
58
|
+
"""
|
|
59
|
+
Set horizontal velocity to the left (negative X)."
|
|
60
|
+
|
|
61
|
+
:param speed: Speed to set.
|
|
62
|
+
:type speed: float
|
|
63
|
+
"""
|
|
64
|
+
self.vx = -abs(speed)
|
|
65
|
+
|
|
66
|
+
def move_right(self, speed: float):
|
|
67
|
+
"""
|
|
68
|
+
Set horizontal velocity to the right (positive X).
|
|
69
|
+
|
|
70
|
+
:param speed: Speed to set.
|
|
71
|
+
:type speed: float
|
|
72
|
+
"""
|
|
73
|
+
self.vx = abs(speed)
|
mini_arcade_core/scene.py
CHANGED
|
@@ -7,9 +7,10 @@ from __future__ import annotations
|
|
|
7
7
|
from abc import ABC, abstractmethod
|
|
8
8
|
from typing import Callable, List
|
|
9
9
|
|
|
10
|
-
from
|
|
11
|
-
|
|
10
|
+
from .backend import Backend, Event
|
|
11
|
+
from .entity import Entity
|
|
12
12
|
from .game import Game
|
|
13
|
+
from .geometry2d import Size2D
|
|
13
14
|
|
|
14
15
|
OverlayFunc = Callable[[Backend], None]
|
|
15
16
|
|
|
@@ -23,10 +24,55 @@ class Scene(ABC):
|
|
|
23
24
|
:type game: Game
|
|
24
25
|
"""
|
|
25
26
|
self.game = game
|
|
27
|
+
self.entities: List[Entity] = []
|
|
28
|
+
self.size: Size2D = Size2D(game.config.width, game.config.height)
|
|
26
29
|
# overlays drawn on top of the scene
|
|
27
30
|
self._overlays: List[OverlayFunc] = []
|
|
28
31
|
|
|
29
|
-
def
|
|
32
|
+
def add_entity(self, *entities: Entity):
|
|
33
|
+
"""
|
|
34
|
+
Register one or more entities in this scene.
|
|
35
|
+
|
|
36
|
+
:param entities: One or more Entity instances to add.
|
|
37
|
+
:type entities: Entity
|
|
38
|
+
"""
|
|
39
|
+
self.entities.extend(entities)
|
|
40
|
+
|
|
41
|
+
def remove_entity(self, entity: Entity):
|
|
42
|
+
"""
|
|
43
|
+
Unregister a single entity, if present.
|
|
44
|
+
|
|
45
|
+
:param entity: The Entity instance to remove.
|
|
46
|
+
:type entity: Entity
|
|
47
|
+
"""
|
|
48
|
+
if entity in self.entities:
|
|
49
|
+
self.entities.remove(entity)
|
|
50
|
+
|
|
51
|
+
def clear_entities(self):
|
|
52
|
+
"""Remove all entities from the scene."""
|
|
53
|
+
self.entities.clear()
|
|
54
|
+
|
|
55
|
+
def update_entities(self, dt: float):
|
|
56
|
+
"""
|
|
57
|
+
Default update loop for all entities.
|
|
58
|
+
|
|
59
|
+
:param dt: Time delta in seconds.
|
|
60
|
+
:type dt: float
|
|
61
|
+
"""
|
|
62
|
+
for ent in self.entities:
|
|
63
|
+
ent.update(dt)
|
|
64
|
+
|
|
65
|
+
def draw_entities(self, surface: Backend):
|
|
66
|
+
"""
|
|
67
|
+
Default draw loop for all entities.
|
|
68
|
+
|
|
69
|
+
:param surface: The backend surface to draw on.
|
|
70
|
+
:type surface: Backend
|
|
71
|
+
"""
|
|
72
|
+
for ent in self.entities:
|
|
73
|
+
ent.draw(surface)
|
|
74
|
+
|
|
75
|
+
def add_overlay(self, overlay: OverlayFunc):
|
|
30
76
|
"""
|
|
31
77
|
Register an overlay (drawn every frame, after entities).
|
|
32
78
|
|
|
@@ -35,7 +81,7 @@ class Scene(ABC):
|
|
|
35
81
|
"""
|
|
36
82
|
self._overlays.append(overlay)
|
|
37
83
|
|
|
38
|
-
def remove_overlay(self, overlay: OverlayFunc)
|
|
84
|
+
def remove_overlay(self, overlay: OverlayFunc):
|
|
39
85
|
"""
|
|
40
86
|
Unregister a previously added overlay.
|
|
41
87
|
|
|
@@ -45,11 +91,11 @@ class Scene(ABC):
|
|
|
45
91
|
if overlay in self._overlays:
|
|
46
92
|
self._overlays.remove(overlay)
|
|
47
93
|
|
|
48
|
-
def clear_overlays(self)
|
|
94
|
+
def clear_overlays(self):
|
|
49
95
|
"""Clear all registered overlays."""
|
|
50
96
|
self._overlays.clear()
|
|
51
97
|
|
|
52
|
-
def draw_overlays(self, surface: Backend)
|
|
98
|
+
def draw_overlays(self, surface: Backend):
|
|
53
99
|
"""
|
|
54
100
|
Call all overlays. Scenes should call this at the end of draw().
|
|
55
101
|
|
|
@@ -68,12 +114,12 @@ class Scene(ABC):
|
|
|
68
114
|
"""Called when the scene is replaced."""
|
|
69
115
|
|
|
70
116
|
@abstractmethod
|
|
71
|
-
def handle_event(self, event:
|
|
117
|
+
def handle_event(self, event: Event):
|
|
72
118
|
"""
|
|
73
119
|
Handle input / events (e.g. pygame.Event).
|
|
74
120
|
|
|
75
121
|
:param event: The event to handle.
|
|
76
|
-
:type event:
|
|
122
|
+
:type event: Event
|
|
77
123
|
"""
|
|
78
124
|
|
|
79
125
|
@abstractmethod
|
|
@@ -86,10 +132,16 @@ class Scene(ABC):
|
|
|
86
132
|
"""
|
|
87
133
|
|
|
88
134
|
@abstractmethod
|
|
89
|
-
def draw(self, surface:
|
|
135
|
+
def draw(self, surface: Backend):
|
|
90
136
|
"""
|
|
91
137
|
Render to the main surface.
|
|
92
138
|
|
|
93
139
|
:param surface: The backend surface to draw on.
|
|
94
|
-
:type surface:
|
|
140
|
+
:type surface: Backend
|
|
95
141
|
"""
|
|
142
|
+
|
|
143
|
+
def on_pause(self):
|
|
144
|
+
"""Called when the game is paused."""
|
|
145
|
+
|
|
146
|
+
def on_resume(self):
|
|
147
|
+
"""Called when the game is resumed."""
|
|
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
|
+
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mini-arcade-core
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.1
|
|
4
4
|
Summary: Tiny scene-based game loop core for small arcade games.
|
|
5
5
|
License: Copyright (c) 2025 Santiago Rincón
|
|
6
6
|
|
|
@@ -123,21 +123,21 @@ Represents one state of your game (menu, gameplay, pause, etc.):
|
|
|
123
123
|
from mini_arcade_core import Scene, Game
|
|
124
124
|
|
|
125
125
|
class MyScene(Scene):
|
|
126
|
-
def on_enter(self)
|
|
126
|
+
def on_enter(self):
|
|
127
127
|
print("Scene entered")
|
|
128
128
|
|
|
129
|
-
def on_exit(self)
|
|
129
|
+
def on_exit(self):
|
|
130
130
|
print("Scene exited")
|
|
131
131
|
|
|
132
|
-
def handle_event(self, event: object)
|
|
132
|
+
def handle_event(self, event: object):
|
|
133
133
|
# Handle input / events from your backend
|
|
134
134
|
pass
|
|
135
135
|
|
|
136
|
-
def update(self, dt: float)
|
|
136
|
+
def update(self, dt: float):
|
|
137
137
|
# Game logic
|
|
138
138
|
pass
|
|
139
139
|
|
|
140
|
-
def draw(self, surface: object)
|
|
140
|
+
def draw(self, surface: object):
|
|
141
141
|
# Rendering via your backend
|
|
142
142
|
pass
|
|
143
143
|
```
|
|
@@ -150,17 +150,17 @@ Lightweight game object primitives:
|
|
|
150
150
|
from mini_arcade_core import Entity, SpriteEntity
|
|
151
151
|
|
|
152
152
|
class Ball(Entity):
|
|
153
|
-
def __init__(self)
|
|
153
|
+
def __init__(self):
|
|
154
154
|
self.x = 100.0
|
|
155
155
|
self.y = 100.0
|
|
156
156
|
self.vx = 200.0
|
|
157
157
|
self.vy = 150.0
|
|
158
158
|
|
|
159
|
-
def update(self, dt: float)
|
|
159
|
+
def update(self, dt: float):
|
|
160
160
|
self.x += self.vx * dt
|
|
161
161
|
self.y += self.vy * dt
|
|
162
162
|
|
|
163
|
-
def draw(self, surface: object)
|
|
163
|
+
def draw(self, surface: object):
|
|
164
164
|
# Use your backend to draw the ball on `surface`
|
|
165
165
|
pass
|
|
166
166
|
|
|
@@ -182,7 +182,7 @@ from mini_arcade_core import Game, GameConfig, Scene
|
|
|
182
182
|
|
|
183
183
|
|
|
184
184
|
class PygameGame(Game):
|
|
185
|
-
def __init__(self, config: GameConfig)
|
|
185
|
+
def __init__(self, config: GameConfig):
|
|
186
186
|
super().__init__(config)
|
|
187
187
|
pygame.init()
|
|
188
188
|
self._screen = pygame.display.set_mode(
|
|
@@ -191,13 +191,13 @@ class PygameGame(Game):
|
|
|
191
191
|
pygame.display.set_caption(config.title)
|
|
192
192
|
self._clock = pygame.time.Clock()
|
|
193
193
|
|
|
194
|
-
def change_scene(self, scene: Scene)
|
|
194
|
+
def change_scene(self, scene: Scene):
|
|
195
195
|
if self._current_scene is not None:
|
|
196
196
|
self._current_scene.on_exit()
|
|
197
197
|
self._current_scene = scene
|
|
198
198
|
self._current_scene.on_enter()
|
|
199
199
|
|
|
200
|
-
def run(self, initial_scene: Scene)
|
|
200
|
+
def run(self, initial_scene: Scene):
|
|
201
201
|
self.change_scene(initial_scene)
|
|
202
202
|
self._running = True
|
|
203
203
|
|
|
@@ -220,7 +220,7 @@ class PygameGame(Game):
|
|
|
220
220
|
|
|
221
221
|
|
|
222
222
|
class PongScene(Scene):
|
|
223
|
-
def __init__(self, game: Game)
|
|
223
|
+
def __init__(self, game: Game):
|
|
224
224
|
super().__init__(game)
|
|
225
225
|
self.x = 100.0
|
|
226
226
|
self.y = 100.0
|
|
@@ -228,17 +228,17 @@ class PongScene(Scene):
|
|
|
228
228
|
self.vy = 150.0
|
|
229
229
|
self.radius = 10
|
|
230
230
|
|
|
231
|
-
def on_enter(self)
|
|
231
|
+
def on_enter(self):
|
|
232
232
|
print("Pong started")
|
|
233
233
|
|
|
234
|
-
def on_exit(self)
|
|
234
|
+
def on_exit(self):
|
|
235
235
|
print("Pong finished")
|
|
236
236
|
|
|
237
|
-
def handle_event(self, event: object)
|
|
237
|
+
def handle_event(self, event: object):
|
|
238
238
|
# no input yet
|
|
239
239
|
pass
|
|
240
240
|
|
|
241
|
-
def update(self, dt: float)
|
|
241
|
+
def update(self, dt: float):
|
|
242
242
|
self.x += self.vx * dt
|
|
243
243
|
self.y += self.vy * dt
|
|
244
244
|
|
|
@@ -250,7 +250,7 @@ class PongScene(Scene):
|
|
|
250
250
|
if self.y < self.radius or self.y > height - self.radius:
|
|
251
251
|
self.vy *= -1
|
|
252
252
|
|
|
253
|
-
def draw(self, surface: pygame.Surface)
|
|
253
|
+
def draw(self, surface: pygame.Surface): # type: ignore[override]
|
|
254
254
|
pygame.draw.circle(
|
|
255
255
|
surface, (255, 255, 255), (int(self.x), int(self.y)), self.radius
|
|
256
256
|
)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
mini_arcade_core/__init__.py,sha256=2PBNxk-rxjJxPvF6PvpdJ_rat0knyo6a-VRI8sROofI,2554
|
|
2
|
+
mini_arcade_core/backend.py,sha256=4PYuE_hhvLgg6acPa3JlIKCqPcYc8qJv2gIFvwRI_W8,4484
|
|
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.8.1.dist-info/METADATA,sha256=haAtt1T3L_fp4mRYnf_9GC_NRIjWy2ZMgT0mv7eFy1c,8188
|
|
14
|
+
mini_arcade_core-0.8.1.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
15
|
+
mini_arcade_core-0.8.1.dist-info/licenses/LICENSE,sha256=3lHAuV0584cVS5vAqi2uC6GcsVgxUijvwvtZckyvaZ4,1096
|
|
16
|
+
mini_arcade_core-0.8.1.dist-info/RECORD,,
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
mini_arcade_core/__init__.py,sha256=0gkloHYTV8FmEZbBLxNNy-eJge2y7pkQ_PCcrfeJ3uo,1831
|
|
2
|
-
mini_arcade_core/backend.py,sha256=SvsHDO0Fq86buTqMkOCSAidx2E4QAJ6mLsSa9ZQXEqY,4514
|
|
3
|
-
mini_arcade_core/entity.py,sha256=mAkedH0j14giBQFRpQjaym46uczbQDln6nbxy0WFAqU,1077
|
|
4
|
-
mini_arcade_core/game.py,sha256=SBhpQnaK5Fs0Y4sV6cV-kciI1hRzyLxix8Sictlc9Eo,5246
|
|
5
|
-
mini_arcade_core/scene.py,sha256=QENcn2hWUF3Ja9pU8NK67NYmC8ptnjdpUkg1qsUEl60,2435
|
|
6
|
-
mini_arcade_core-0.7.5.dist-info/METADATA,sha256=mCheuxo7siOALms4OIJDSae1fsNiFwfNNYFygLOF-o8,8324
|
|
7
|
-
mini_arcade_core-0.7.5.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
8
|
-
mini_arcade_core-0.7.5.dist-info/licenses/LICENSE,sha256=3lHAuV0584cVS5vAqi2uC6GcsVgxUijvwvtZckyvaZ4,1096
|
|
9
|
-
mini_arcade_core-0.7.5.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|