mini-arcade-core 0.7.4__py3-none-any.whl → 0.8.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.
@@ -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 .entity import Entity, SpriteEntity
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
- game = Game(config or GameConfig())
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
@@ -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) -> None:
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) -> None:
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) -> None:
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) -> None:
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: tuple[int, int, int] = (255, 255, 255),
110
- ) -> None:
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: tuple[int, int, int]
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: tuple[int, int, int] = (255, 255, 255),
139
- ) -> None:
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: tuple[int, int, int]
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
+ )
@@ -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, x: float, y: float, width: int, height: int):
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
- :param x: X position.
36
- :type x: float
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
- :param width: Width of the entity.
42
- :type width: int
50
+ class KinematicEntity(SpriteEntity):
51
+ """SpriteEntity with velocity-based movement."""
43
52
 
44
- :param height: Height of the entity.
45
- :type height: int
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
- self.x = x
48
- self.y = y
49
- self.width = width
50
- self.height = height
51
- # TODO: velocity, color, etc.
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 backend class to use for rendering and input.
32
+ :ivar backend: Optional Backend instance to use for rendering and input.
33
33
  """
34
34
 
35
35
  width: int = 800
@@ -47,11 +47,12 @@ class Game:
47
47
  """
48
48
  :param config: Game configuration options.
49
49
  :type config: GameConfig
50
+
51
+ :raises ValueError: If the provided config does not have a valid Backend.
50
52
  """
51
53
  self.config = config
52
54
  self._current_scene: Scene | None = None
53
55
  self._running: bool = False
54
- self.backend: Backend | None = config.backend
55
56
 
56
57
  if config.backend is None:
57
58
  raise ValueError(
@@ -140,8 +141,11 @@ class Game:
140
141
  img = Image.open(bmp_path)
141
142
  img.save(out_path) # Pillow chooses format from extension
142
143
  return True
144
+ # Justification: Pillow can raise various exceptions on failure
145
+ # pylint: disable=broad-exception-caught
143
146
  except Exception:
144
147
  return False
148
+ # pylint: enable=broad-exception-caught
145
149
 
146
150
  def screenshot(
147
151
  self, label: str | None = None, directory: str = "screenshots"
@@ -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 mini_arcade_core.backend import Backend
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 add_overlay(self, overlay: OverlayFunc) -> None:
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) -> None:
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) -> None:
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) -> None:
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: object):
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: object
122
+ :type event: Event
77
123
  """
78
124
 
79
125
  @abstractmethod
@@ -86,10 +132,10 @@ class Scene(ABC):
86
132
  """
87
133
 
88
134
  @abstractmethod
89
- def draw(self, surface: object):
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: object
140
+ :type surface: Backend
95
141
  """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mini-arcade-core
3
- Version: 0.7.4
3
+ Version: 0.8.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
 
@@ -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) -> None:
126
+ def on_enter(self):
127
127
  print("Scene entered")
128
128
 
129
- def on_exit(self) -> None:
129
+ def on_exit(self):
130
130
  print("Scene exited")
131
131
 
132
- def handle_event(self, event: object) -> None:
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) -> None:
136
+ def update(self, dt: float):
137
137
  # Game logic
138
138
  pass
139
139
 
140
- def draw(self, surface: object) -> None:
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) -> None:
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) -> None:
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) -> None:
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) -> None:
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) -> None:
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) -> None:
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) -> None:
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) -> None:
231
+ def on_enter(self):
232
232
  print("Pong started")
233
233
 
234
- def on_exit(self) -> None:
234
+ def on_exit(self):
235
235
  print("Pong finished")
236
236
 
237
- def handle_event(self, event: object) -> None:
237
+ def handle_event(self, event: object):
238
238
  # no input yet
239
239
  pass
240
240
 
241
- def update(self, dt: float) -> None:
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) -> None: # type: ignore[override]
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,14 @@
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=DM3pbALCihTKfhPgKSftqOYXVEmMdiy-qvnk6hA4tvE,5278
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=OBfDe3JJt598itj2qtsChvacQM9i8o_g9TRznWw4_z0,3721
11
+ mini_arcade_core-0.8.0.dist-info/METADATA,sha256=pjaAUFvFK5665leA2g3lmTgDNoN06nVQlRtx14dgO0I,8188
12
+ mini_arcade_core-0.8.0.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
13
+ mini_arcade_core-0.8.0.dist-info/licenses/LICENSE,sha256=3lHAuV0584cVS5vAqi2uC6GcsVgxUijvwvtZckyvaZ4,1096
14
+ mini_arcade_core-0.8.0.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=qfJ4UiAA2Mu-iAkj5SvxWxteGO9-s79IjlJk9ORHliw,5077
5
- mini_arcade_core/scene.py,sha256=QENcn2hWUF3Ja9pU8NK67NYmC8ptnjdpUkg1qsUEl60,2435
6
- mini_arcade_core-0.7.4.dist-info/METADATA,sha256=3-lnmTd16f89QKre5WegNgd8xQ2fR4MoY5ZrCB1A2n4,8324
7
- mini_arcade_core-0.7.4.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
8
- mini_arcade_core-0.7.4.dist-info/licenses/LICENSE,sha256=3lHAuV0584cVS5vAqi2uC6GcsVgxUijvwvtZckyvaZ4,1096
9
- mini_arcade_core-0.7.4.dist-info/RECORD,,