system-b 1.1.4__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.
- system_b/__init__.py +0 -0
- system_b/controller/__init__.py +0 -0
- system_b/controller/controller.py +74 -0
- system_b/demo/__init__.py +0 -0
- system_b/demo/__main__.py +4 -0
- system_b/demo/demo.py +268 -0
- system_b/exceptions/__init__.py +0 -0
- system_b/exceptions/game_exception.py +4 -0
- system_b/game/__init__.py +4 -0
- system_b/game/game.py +82 -0
- system_b/images/__init__.py +5 -0
- system_b/images/images.py +100 -0
- system_b/math/__init__.py +0 -0
- system_b/math/vector_utils.py +23 -0
- system_b/physics/__init__.py +1 -0
- system_b/physics/physics.py +113 -0
- system_b/screen/__init__.py +1 -0
- system_b/screen/screen.py +46 -0
- system_b/sprites/__init__.py +4 -0
- system_b/sprites/sprites.py +110 -0
- system_b/toolkit/__init__.py +1 -0
- system_b/toolkit/__main__.py +6 -0
- system_b/toolkit/toolkit.py +19 -0
- system_b-1.1.4.dist-info/METADATA +41 -0
- system_b-1.1.4.dist-info/RECORD +28 -0
- system_b-1.1.4.dist-info/WHEEL +5 -0
- system_b-1.1.4.dist-info/licenses/LICENSE +21 -0
- system_b-1.1.4.dist-info/top_level.txt +1 -0
system_b/__init__.py
ADDED
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
from abc import abstractmethod
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
from pygame import Vector2, key, joystick
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Controller:
|
|
8
|
+
def __init__(self, directions: list[int]):
|
|
9
|
+
self._directions = directions
|
|
10
|
+
|
|
11
|
+
@abstractmethod
|
|
12
|
+
def direction(self, scalar) -> Vector2:
|
|
13
|
+
raise NotImplementedError
|
|
14
|
+
|
|
15
|
+
@abstractmethod
|
|
16
|
+
def action(self, index) -> bool:
|
|
17
|
+
raise NotImplementedError
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class KeyboardController(Controller):
|
|
21
|
+
def __init__(self, directions, actions):
|
|
22
|
+
"""Provide directions up, down, left, and right, in that order; and provide actions
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
directions (list[int]): ex. [pygame.K_w, pygame.K_s ...]
|
|
26
|
+
actions (list[int]): ex. [pygame.K_SPACE, pygame.K_ESCAPE ...]
|
|
27
|
+
"""
|
|
28
|
+
super.__init__(directions)
|
|
29
|
+
self._actions = actions
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class DefaultKeyboardController(KeyboardController):
|
|
33
|
+
def __init__(self, directions, actions):
|
|
34
|
+
super().__init__(directions, actions)
|
|
35
|
+
|
|
36
|
+
def direction(self, scalar=1):
|
|
37
|
+
u, d, l, r = (key.get_pressed()[i] for i in self._directions[:4])
|
|
38
|
+
|
|
39
|
+
return Vector2(r - l, u - d) * scalar
|
|
40
|
+
|
|
41
|
+
def action(self, index):
|
|
42
|
+
return self._actions[index]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class JoystickController(Controller):
|
|
46
|
+
def __init__(self, id: int, directions):
|
|
47
|
+
"""Provide directions up, down, left, and right, in that order; and provide actions
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
directions (list[int]): ex. [0, 1 ...]
|
|
51
|
+
actions (list[int]): ex. [0, 1 ...]
|
|
52
|
+
"""
|
|
53
|
+
super.__init__(directions)
|
|
54
|
+
if not joystick.get_init():
|
|
55
|
+
joystick.init()
|
|
56
|
+
|
|
57
|
+
self._joystick = joystick.Joystick(id)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class DefaultJoystickController(JoystickController):
|
|
61
|
+
def __init__(self, id, directions):
|
|
62
|
+
super().__init__(id, directions)
|
|
63
|
+
|
|
64
|
+
def direction(self, scalar):
|
|
65
|
+
x = self._joystick.get_axis(self._directions[0])
|
|
66
|
+
y = self._joystick.get_axis(self._directions[1])
|
|
67
|
+
|
|
68
|
+
return Vector2(x, y) * scalar
|
|
69
|
+
|
|
70
|
+
def rotation(self):
|
|
71
|
+
return self._joystick.get_axis(2)
|
|
72
|
+
|
|
73
|
+
def action(self, index):
|
|
74
|
+
return self._joystick.get_button(index)
|
|
File without changes
|
system_b/demo/demo.py
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
from pygame import Rect, Surface, Vector2
|
|
4
|
+
from pygame import display, draw, sprite, key
|
|
5
|
+
from pygame import K_DOWN, K_RIGHT, K_ESCAPE, K_UP, KEYDOWN, K_LEFT
|
|
6
|
+
from system_b.game import SpriteGame
|
|
7
|
+
from system_b.physics import PhysicsBody
|
|
8
|
+
from system_b.screen import ScreenReference
|
|
9
|
+
from system_b.sprites import PhysicsSprite
|
|
10
|
+
from system_b.math.vector_utils import apply_vector_actions, get_random_vector
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class BouncyBallPlayer(PhysicsSprite):
|
|
14
|
+
"""A PlayerSprite is a subclass of PhysicsSprite. It features
|
|
15
|
+
a PhysicsBody and also a Controller reference. Here you define logic
|
|
16
|
+
for what the controller actions do to the player sprite over time"""
|
|
17
|
+
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
image: Surface,
|
|
21
|
+
position: Vector2,
|
|
22
|
+
physics_body: PhysicsBody,
|
|
23
|
+
color: str = "indigo",
|
|
24
|
+
boundaries: Rect | None = None,
|
|
25
|
+
id: int = 0,
|
|
26
|
+
):
|
|
27
|
+
super().__init__(image, position, physics_body, boundaries, id)
|
|
28
|
+
self.color = color
|
|
29
|
+
# lock for collision logic - freezes if calculated at every loop
|
|
30
|
+
self.collided = False
|
|
31
|
+
|
|
32
|
+
def update(self, dt: float):
|
|
33
|
+
# sync position and physics body
|
|
34
|
+
self.physics_body.move(dt)
|
|
35
|
+
|
|
36
|
+
# bound position with physics body
|
|
37
|
+
self._update_pos(
|
|
38
|
+
self.physics_body.position,
|
|
39
|
+
xbounds=self._bounds_x,
|
|
40
|
+
ybounds=self._bounds_y,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
def _bounds_x(self):
|
|
44
|
+
self.physics_body.velociy = apply_vector_actions(
|
|
45
|
+
self.physics_body.velociy,
|
|
46
|
+
lambda v: Vector2(0, v.y),
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
def _bounds_y(self):
|
|
50
|
+
self.physics_body.velociy = apply_vector_actions(
|
|
51
|
+
self.physics_body.velociy,
|
|
52
|
+
lambda v: Vector2(v.x, 0),
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class BouncyBallPuck(PhysicsSprite):
|
|
57
|
+
"""This bit probably looks complicated, and it is :)
|
|
58
|
+
PhysicsSprite is subclass of GameSprite that features a PhysicsBody.
|
|
59
|
+
Here we define the logic for what the sprite will do over time"""
|
|
60
|
+
|
|
61
|
+
def __init__(
|
|
62
|
+
self,
|
|
63
|
+
image: Surface,
|
|
64
|
+
position: Vector2,
|
|
65
|
+
physics_body: PhysicsBody,
|
|
66
|
+
color: str = "blue",
|
|
67
|
+
boundaries: Rect | None = None,
|
|
68
|
+
id: int = 0,
|
|
69
|
+
speed: float = 50,
|
|
70
|
+
):
|
|
71
|
+
super().__init__(image, position, physics_body, boundaries, id)
|
|
72
|
+
self.color = color
|
|
73
|
+
self.speed = speed
|
|
74
|
+
self.physics_body.velociy = get_random_vector(speed)
|
|
75
|
+
self.start_position = Vector2.copy(position)
|
|
76
|
+
|
|
77
|
+
def update(self, dt: float):
|
|
78
|
+
# sync position and physics body
|
|
79
|
+
self.physics_body.move(dt)
|
|
80
|
+
# bound positions
|
|
81
|
+
self._update_pos(
|
|
82
|
+
self.physics_body.position,
|
|
83
|
+
xbounds=self.__bounds_x,
|
|
84
|
+
ybounds=self.__bounds_y,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
def __bounds_x(self):
|
|
88
|
+
self.physics_body.velociy = (
|
|
89
|
+
apply_vector_actions(
|
|
90
|
+
self.physics_body.velociy, lambda v: Vector2(-v.x, v.y)
|
|
91
|
+
)
|
|
92
|
+
* self.physics_body.elasticity
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
def __bounds_y(self):
|
|
96
|
+
self.physics_body.velociy = (
|
|
97
|
+
apply_vector_actions(
|
|
98
|
+
self.physics_body.velociy,
|
|
99
|
+
lambda v: Vector2(v.x, -v.y),
|
|
100
|
+
)
|
|
101
|
+
* self.physics_body.elasticity
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class BouncyBallScreen(ScreenReference):
|
|
106
|
+
"""Anything to do with screen activity"""
|
|
107
|
+
|
|
108
|
+
def __init__(
|
|
109
|
+
self,
|
|
110
|
+
player: BouncyBallPlayer,
|
|
111
|
+
puck: BouncyBallPuck,
|
|
112
|
+
width=0,
|
|
113
|
+
height=0,
|
|
114
|
+
frames_per_second=60,
|
|
115
|
+
bg_color=None,
|
|
116
|
+
bg_image=None,
|
|
117
|
+
):
|
|
118
|
+
super().__init__(
|
|
119
|
+
width=width,
|
|
120
|
+
height=height,
|
|
121
|
+
frames_per_second=frames_per_second,
|
|
122
|
+
bg_color=bg_color,
|
|
123
|
+
bg_image=bg_image,
|
|
124
|
+
)
|
|
125
|
+
self.puck = puck
|
|
126
|
+
self.player = player
|
|
127
|
+
|
|
128
|
+
def update_screen(self):
|
|
129
|
+
# DRAW THE TABLE
|
|
130
|
+
draw.rect(
|
|
131
|
+
surface=self.screen,
|
|
132
|
+
color=self.bg_color or "black",
|
|
133
|
+
rect=(0, 0, self.width, self.height),
|
|
134
|
+
border_radius=15,
|
|
135
|
+
)
|
|
136
|
+
# DRAW PLAYER
|
|
137
|
+
draw.circle(
|
|
138
|
+
self.screen,
|
|
139
|
+
self.player.color,
|
|
140
|
+
self.player.position,
|
|
141
|
+
self.player.rect.h,
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
# DRAW PUCK
|
|
145
|
+
draw.circle(
|
|
146
|
+
self.screen,
|
|
147
|
+
self.puck.color,
|
|
148
|
+
self.puck.position,
|
|
149
|
+
self.puck.rect.h,
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
class BouncyBall(SpriteGame):
|
|
154
|
+
"""This is where high-level game logic goes
|
|
155
|
+
Think of this as the game-loop layer of the
|
|
156
|
+
application
|
|
157
|
+
"""
|
|
158
|
+
|
|
159
|
+
def __init__(self, ctrl_speed, screen_ref, player_sprites, other_sprites):
|
|
160
|
+
# configure sprites
|
|
161
|
+
super().__init__(screen_ref, player_sprites, other_sprites)
|
|
162
|
+
display.set_caption("Game Utils Demo Game")
|
|
163
|
+
self.puck: BouncyBallPuck = self.other_sprites_group.sprites()[0]
|
|
164
|
+
self.player: BouncyBallPlayer = self.player_sprites[0]
|
|
165
|
+
self.ctrl_speed = ctrl_speed
|
|
166
|
+
|
|
167
|
+
def events(self, event):
|
|
168
|
+
if event.type == KEYDOWN:
|
|
169
|
+
# print(f"Key pressed: {event.key}")
|
|
170
|
+
if key.get_pressed()[K_ESCAPE]:
|
|
171
|
+
self._running = False
|
|
172
|
+
|
|
173
|
+
accel = (
|
|
174
|
+
Vector2(
|
|
175
|
+
(key.get_pressed()[K_RIGHT] - key.get_pressed()[K_LEFT]),
|
|
176
|
+
(key.get_pressed()[K_DOWN] - key.get_pressed()[K_UP]),
|
|
177
|
+
)
|
|
178
|
+
* self.ctrl_speed
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
self.player.physics_body.force(accel, self._screen_ref.get_delta_time())
|
|
182
|
+
|
|
183
|
+
def update(self):
|
|
184
|
+
"""This method is required in a Game implementation.
|
|
185
|
+
Here is where the game loop logic will go. This will
|
|
186
|
+
be called once per game loop"""
|
|
187
|
+
dt = self._screen_ref.get_delta_time()
|
|
188
|
+
|
|
189
|
+
self.player.update(dt)
|
|
190
|
+
self.puck.update(dt)
|
|
191
|
+
|
|
192
|
+
if sprite.collide_circle(self.player, self.puck):
|
|
193
|
+
# lock out of collision logic so it doesn't freeze lol
|
|
194
|
+
if not self.player.collided:
|
|
195
|
+
self.player.physics_body.on_collide(self.puck.physics_body)
|
|
196
|
+
self.puck.physics_body.on_collide(self.player.physics_body)
|
|
197
|
+
self.player.collided = True
|
|
198
|
+
else:
|
|
199
|
+
self.player.collided = False
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def main():
|
|
203
|
+
"""configurations kept in a separate file
|
|
204
|
+
can make it easy to change things at this
|
|
205
|
+
orchestration level.
|
|
206
|
+
Play around with these input paramters and see what you can get it to do!"""
|
|
207
|
+
input_params = {
|
|
208
|
+
"player": {
|
|
209
|
+
"color": "darkred",
|
|
210
|
+
"physics_body": {"mass": 4, "friction": 0.002, "elasticity": 0},
|
|
211
|
+
},
|
|
212
|
+
"puck": {
|
|
213
|
+
"color": "midnightblue",
|
|
214
|
+
"physics_body": {"mass": 0.5, "friction": 0, "elasticity": 0},
|
|
215
|
+
},
|
|
216
|
+
"controller": {"speed": 1500.0},
|
|
217
|
+
"table": {"color": "firebrick"},
|
|
218
|
+
"mode": "DEBUG",
|
|
219
|
+
"screen": {"width": 1280},
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
# properties
|
|
223
|
+
table_color = input_params["table"]["color"]
|
|
224
|
+
ctrl_speed = input_params["controller"]["speed"]
|
|
225
|
+
player_physics = input_params["player"]["physics_body"]
|
|
226
|
+
player_color = input_params["player"]["color"]
|
|
227
|
+
puck_physics = input_params["puck"]["physics_body"]
|
|
228
|
+
puck_color = input_params["puck"]["color"]
|
|
229
|
+
screen_w = input_params["screen"]["width"]
|
|
230
|
+
screen_h = screen_w / 16 * 9 # screen["height"]
|
|
231
|
+
puck_size = screen_h / 9 / 2
|
|
232
|
+
|
|
233
|
+
# configure sprites
|
|
234
|
+
player = BouncyBallPlayer(
|
|
235
|
+
image=Surface((puck_size, puck_size)),
|
|
236
|
+
position=Vector2(screen_w / 2, screen_h / 2),
|
|
237
|
+
physics_body=PhysicsBody(
|
|
238
|
+
mass=player_physics["mass"],
|
|
239
|
+
friction=player_physics["friction"],
|
|
240
|
+
elasticity=player_physics["elasticity"],
|
|
241
|
+
),
|
|
242
|
+
boundaries=Rect(screen_w, 0, screen_h, 0),
|
|
243
|
+
color=player_color,
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
puck = BouncyBallPuck(
|
|
247
|
+
image=Surface((puck_size, puck_size)),
|
|
248
|
+
position=Vector2(screen_w / 4, screen_h / 4),
|
|
249
|
+
boundaries=Rect(screen_w, 0, screen_h, 0),
|
|
250
|
+
physics_body=PhysicsBody(
|
|
251
|
+
mass=puck_physics["mass"],
|
|
252
|
+
friction=puck_physics["friction"],
|
|
253
|
+
elasticity=puck_physics["elasticity"],
|
|
254
|
+
),
|
|
255
|
+
color=puck_color,
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
# screen settings and configurations
|
|
259
|
+
settings = BouncyBallScreen(
|
|
260
|
+
player, puck, width=screen_w, height=screen_h, bg_color=table_color
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
# load sprites and configurations
|
|
264
|
+
game = BouncyBall(ctrl_speed, settings, [player], [puck])
|
|
265
|
+
|
|
266
|
+
print("Starting game...")
|
|
267
|
+
game.run()
|
|
268
|
+
print("Game quit")
|
|
File without changes
|
system_b/game/game.py
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
from abc import abstractmethod
|
|
2
|
+
|
|
3
|
+
import pygame
|
|
4
|
+
|
|
5
|
+
from system_b.screen import ScreenReference
|
|
6
|
+
from system_b.sprites import GameSprite
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Game:
|
|
10
|
+
"""Game class defines high-level game logic. This class includes a run method, which controls the game loop.
|
|
11
|
+
User is required to implement abstract methods self.update() and self.events(), which is invoked within self.run()
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
def __init__(self, screen_ref: ScreenReference | None = None):
|
|
15
|
+
"""High level game orchestrations
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
screen_ref (ScreenReference): Any screen settings
|
|
19
|
+
"""
|
|
20
|
+
self._running = False
|
|
21
|
+
self._screen_ref = screen_ref
|
|
22
|
+
|
|
23
|
+
def run(self):
|
|
24
|
+
"""Runs and maintains the game loop and clock. Updates the screen and invokes any handlers
|
|
25
|
+
Invokes pygame.init(), pygame.quit() when pygame.QUIT event is reached
|
|
26
|
+
"""
|
|
27
|
+
pygame.init()
|
|
28
|
+
self._running = True
|
|
29
|
+
while self._running:
|
|
30
|
+
self.update()
|
|
31
|
+
if self._screen_ref is not None:
|
|
32
|
+
self._screen_ref.update_screen()
|
|
33
|
+
pygame.display.flip()
|
|
34
|
+
for event in pygame.event.get():
|
|
35
|
+
if event.type == pygame.QUIT:
|
|
36
|
+
self._running = False
|
|
37
|
+
self.events(event)
|
|
38
|
+
|
|
39
|
+
pygame.quit()
|
|
40
|
+
|
|
41
|
+
@abstractmethod
|
|
42
|
+
def events(self, event: pygame.event.Event):
|
|
43
|
+
"""Define custom event handlers. Optional. Called once per game loop
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
event (pygame.event.Event): The event passed from run() method
|
|
47
|
+
*args/**kwargs
|
|
48
|
+
Raises:
|
|
49
|
+
NotImplementedError: Required implementation
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
raise NotImplementedError()
|
|
53
|
+
|
|
54
|
+
@abstractmethod
|
|
55
|
+
def update(self):
|
|
56
|
+
"""Required implentation of game logic. Called once per game loop.
|
|
57
|
+
|
|
58
|
+
Raises:
|
|
59
|
+
NotImplementedError: Required implementation
|
|
60
|
+
"""
|
|
61
|
+
raise NotImplementedError()
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class SpriteGame(Game):
|
|
65
|
+
def __init__(
|
|
66
|
+
self,
|
|
67
|
+
screen_ref: ScreenReference,
|
|
68
|
+
player_sprites: list[GameSprite],
|
|
69
|
+
other_sprites: list[GameSprite],
|
|
70
|
+
):
|
|
71
|
+
"""Create new instance of SpriteGame object. Invokes pygame.init(). Must
|
|
72
|
+
implement update_sprites()
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
settings (ScreenSettings): Information needed to update the screen
|
|
76
|
+
player_sprite (GameSprite): The sprite to be used for the player
|
|
77
|
+
*other_sprites (GameSprite): Any other sprites needed for the game
|
|
78
|
+
"""
|
|
79
|
+
super().__init__(screen_ref=screen_ref)
|
|
80
|
+
self.player_sprites: list[GameSprite] = player_sprites
|
|
81
|
+
self.player_group = pygame.sprite.Group(player_sprites)
|
|
82
|
+
self.other_sprites_group = pygame.sprite.Group(other_sprites)
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
from pygame import Rect, Surface
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class SpriteSheet:
|
|
5
|
+
"""Allows user to access segements of a sprite sheet"""
|
|
6
|
+
|
|
7
|
+
def __init__(self, spritesheet: Surface, bg_color: str | None = None) -> None:
|
|
8
|
+
self.spritesheet = spritesheet
|
|
9
|
+
self.rect = self.spritesheet.get_rect()
|
|
10
|
+
self.bg_color = bg_color
|
|
11
|
+
|
|
12
|
+
def get_spritesheet_segment(self, rect: Rect) -> Surface:
|
|
13
|
+
"""Get a segment of the spritesheet using the given coordinates
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
rect (Rect): The coordinates of the segment you want me to find in the spritesheet
|
|
17
|
+
|
|
18
|
+
Returns:
|
|
19
|
+
Surface: The sprite from the spritesheet using the given coordinates
|
|
20
|
+
"""
|
|
21
|
+
image = Surface(rect.size)
|
|
22
|
+
if self.bg_color is not None:
|
|
23
|
+
image.fill(self.bg_color)
|
|
24
|
+
image.blit(self.spritesheet, (0, 0), rect)
|
|
25
|
+
return image
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class SpriteSheetDataStruct(SpriteSheet):
|
|
29
|
+
"""Base Sprite Sheet structure for sprite sheet data structures"""
|
|
30
|
+
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
spritesheet: Surface,
|
|
34
|
+
n_sprites: int = 1,
|
|
35
|
+
n_lists: int = 1,
|
|
36
|
+
bg_color: str | None = None,
|
|
37
|
+
) -> None:
|
|
38
|
+
super().__init__(spritesheet, bg_color)
|
|
39
|
+
self.n_lists = n_lists
|
|
40
|
+
self.n_sprites = n_sprites
|
|
41
|
+
self.sprite_size = Rect((0, 0), (self.rect.height, self.rect.width / n_sprites))
|
|
42
|
+
self.sprite_rects = self._get_sprite_rects()
|
|
43
|
+
|
|
44
|
+
def _get_sprite_rects(self) -> list[Rect]:
|
|
45
|
+
return [
|
|
46
|
+
# add a new rectangle for n sprites in this sheet (by m lists)
|
|
47
|
+
Rect(
|
|
48
|
+
(self.sprite_size.x + i * self.sprite_size.w, self.sprite_size.y),
|
|
49
|
+
self.sprite_size.size,
|
|
50
|
+
)
|
|
51
|
+
for i in range(self.n_sprites)
|
|
52
|
+
for _ in range(self.n_lists)
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class SpriteSheetList(SpriteSheetDataStruct):
|
|
57
|
+
"""implements sprite sheet as a list of sprites. sprites are indexed"""
|
|
58
|
+
|
|
59
|
+
def __getitem__(self, index: int) -> Surface:
|
|
60
|
+
if index >= len(self.sprite_rects):
|
|
61
|
+
msg = f"index {index} out of bounds"
|
|
62
|
+
raise IndexError(msg)
|
|
63
|
+
|
|
64
|
+
return self.get_spritesheet_segment(self.sprite_rects[index])
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class SpriteSheetMap(SpriteSheetDataStruct):
|
|
68
|
+
"""Implements a sprite sheet as a dictionary of sprites"""
|
|
69
|
+
|
|
70
|
+
def __init__(
|
|
71
|
+
self,
|
|
72
|
+
spritesheet: Surface,
|
|
73
|
+
keys: list[str] = ["0"],
|
|
74
|
+
n_sprites: int = 1,
|
|
75
|
+
n_lists: int = 1,
|
|
76
|
+
bg_color: str | None = None,
|
|
77
|
+
) -> None:
|
|
78
|
+
super().__init__(spritesheet, n_sprites, n_lists, bg_color)
|
|
79
|
+
if len(keys) < len(self.sprite_rects):
|
|
80
|
+
raise Exception("Not enough keys for sprites determined")
|
|
81
|
+
|
|
82
|
+
self.keys = keys
|
|
83
|
+
self.sprite_map = {
|
|
84
|
+
self.keys[i]: self.sprite_rects[i] for i in range(len(self.keys))
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
def get(self, name) -> Surface | None:
|
|
88
|
+
s = self.sprite_map.get(name)
|
|
89
|
+
|
|
90
|
+
if s is not None:
|
|
91
|
+
return self.get_spritesheet_segment(s)
|
|
92
|
+
|
|
93
|
+
def __getitem__(self, name: str) -> Surface | None:
|
|
94
|
+
s = self.sprite_map.get(name)
|
|
95
|
+
|
|
96
|
+
if s is None:
|
|
97
|
+
msg = f"No key {name}"
|
|
98
|
+
raise KeyError(msg)
|
|
99
|
+
|
|
100
|
+
return self.get_spritesheet_segment(s)
|
|
File without changes
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from collections.abc import Callable
|
|
2
|
+
|
|
3
|
+
from numpy.random import choice, rand
|
|
4
|
+
from pygame import Vector2
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def get_random_vector(scalar_mag: float = 1.0, non_negative: bool = False) -> Vector2:
|
|
8
|
+
def rand_sign() -> int:
|
|
9
|
+
return choice([-1, 1]) if not non_negative else 1
|
|
10
|
+
|
|
11
|
+
return Vector2(
|
|
12
|
+
x=rand() * rand_sign() * scalar_mag,
|
|
13
|
+
y=rand() * rand_sign() * scalar_mag,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def apply_vector_actions(
|
|
18
|
+
vector: Vector2, *actions: Callable[[Vector2], Vector2]
|
|
19
|
+
) -> Vector2:
|
|
20
|
+
for action in actions:
|
|
21
|
+
vector = action(vector)
|
|
22
|
+
|
|
23
|
+
return vector
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .physics import PhysicsBody
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
from numpy.random import normal
|
|
2
|
+
from pygame import Vector2, Vector3
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class PhysicsBody:
|
|
6
|
+
"""Applies physics to a GameSprite"""
|
|
7
|
+
|
|
8
|
+
ZERO_VECTOR = Vector2(0, 0)
|
|
9
|
+
GRAVITY = 9.8
|
|
10
|
+
|
|
11
|
+
def __init__(
|
|
12
|
+
self,
|
|
13
|
+
mass: float = 1.0,
|
|
14
|
+
position: Vector2 = ZERO_VECTOR.copy(),
|
|
15
|
+
velocity: Vector2 = ZERO_VECTOR.copy(),
|
|
16
|
+
friction: float = 0.0,
|
|
17
|
+
elasticity: float = 0.0,
|
|
18
|
+
slip: float = 1.0,
|
|
19
|
+
):
|
|
20
|
+
"""Creates new instance of PhysicsBody object
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
mass (float, optional): The mass of the body. Defaults to 1.0.
|
|
24
|
+
position (Vector2, optional): The current positional vector of the body. Defaults to (0,0).
|
|
25
|
+
friction (float, optional): Static friction force. Defaults to 0.0.
|
|
26
|
+
elasticity (float, optional): Elasticity is used in caluclating loss of energy. 0 elasticity will have no effect. Defaults to 0.0.
|
|
27
|
+
slip (float, optional): Used in calculating change in static to kinetic friction. Defaults to 1.0.
|
|
28
|
+
"""
|
|
29
|
+
self.mass = mass
|
|
30
|
+
self.velociy = velocity.copy()
|
|
31
|
+
self.position = position.copy()
|
|
32
|
+
self.static_friction = friction
|
|
33
|
+
self.kinetic_friction = 0
|
|
34
|
+
self.elasticity = 1 - elasticity
|
|
35
|
+
if self.static_friction > 0:
|
|
36
|
+
self.kinetic_friction = self.static_friction * normal(loc=slip)
|
|
37
|
+
|
|
38
|
+
def force(self, acceleration: Vector2, dtime: float) -> Vector2:
|
|
39
|
+
"""Applies an acceleration vector to this body's current velocity with the given change in time.
|
|
40
|
+
If friction was given, friction is calculated into the new velocity
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
acceleration (Vector2): The acceleration to apply
|
|
44
|
+
dtime (float): The change in time
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
Vector2: The new velocity at this moment in time
|
|
48
|
+
"""
|
|
49
|
+
new_velocity = acceleration * dtime
|
|
50
|
+
force = acceleration * self.mass
|
|
51
|
+
|
|
52
|
+
if force.magnitude() > self.friction_force().magnitude():
|
|
53
|
+
self.velociy += new_velocity
|
|
54
|
+
else:
|
|
55
|
+
self.velociy -= self.velociy * self.friction_force().magnitude() * dtime
|
|
56
|
+
self.move(dtime)
|
|
57
|
+
|
|
58
|
+
return self.velociy.copy()
|
|
59
|
+
|
|
60
|
+
def move(self, dtime: float) -> Vector2:
|
|
61
|
+
"""Returns the position at this moment in time.\n
|
|
62
|
+
Applies velocity over dtime"""
|
|
63
|
+
self.position += self.velociy * dtime
|
|
64
|
+
|
|
65
|
+
return self.position.copy()
|
|
66
|
+
|
|
67
|
+
def on_collide(self, other):
|
|
68
|
+
"""New velocity is given by V`= (V(m-s) + U2s)/(m+s)
|
|
69
|
+
for initial magnitudes Vm(self) and Us(other). Calculates loss of
|
|
70
|
+
energy from this body's elasticity
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
other (object): The object with which this body has collided
|
|
74
|
+
"""
|
|
75
|
+
assert isinstance(other, PhysicsBody)
|
|
76
|
+
|
|
77
|
+
self.velociy *= self.mass - other.mass
|
|
78
|
+
self.velociy += other.velociy * 2 * other.mass
|
|
79
|
+
self.velociy /= self.mass + other.mass
|
|
80
|
+
|
|
81
|
+
# energy loss, if any
|
|
82
|
+
self.velociy *= self.elasticity
|
|
83
|
+
|
|
84
|
+
def momentum(self) -> Vector2:
|
|
85
|
+
"""This body's current momentum
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
Vector2: Vm of this body
|
|
89
|
+
"""
|
|
90
|
+
return self.velociy * self.mass
|
|
91
|
+
|
|
92
|
+
def normal_force(self) -> Vector3:
|
|
93
|
+
"""The normal force of this body
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
Vector3: (0, 0, mg)
|
|
97
|
+
"""
|
|
98
|
+
return Vector3(0, 0, self.mass * self.GRAVITY)
|
|
99
|
+
|
|
100
|
+
def friction_force(self) -> Vector3:
|
|
101
|
+
"""fs * N if not in motion, else fk * N
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
Vector3: (0, 0, fN)
|
|
105
|
+
"""
|
|
106
|
+
return (
|
|
107
|
+
self.static_friction * self.normal_force()
|
|
108
|
+
if self.velociy == self.ZERO_VECTOR
|
|
109
|
+
else self.kinetic_friction * self.normal_force()
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
def __str__(self) -> str:
|
|
113
|
+
return str(self.position)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .screen import ScreenReference
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from abc import abstractmethod
|
|
2
|
+
|
|
3
|
+
import pygame.display as display
|
|
4
|
+
from pygame import FULLSCREEN, Color, Surface
|
|
5
|
+
from pygame.time import Clock
|
|
6
|
+
|
|
7
|
+
ColorType = str | tuple[int, int, int] | Color
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ScreenReference:
|
|
11
|
+
"""defines information needed to udpate the screen"""
|
|
12
|
+
|
|
13
|
+
def __init__(
|
|
14
|
+
self,
|
|
15
|
+
width: float = 0.0,
|
|
16
|
+
height: float = 0.0,
|
|
17
|
+
frames_per_second: int = 60,
|
|
18
|
+
bg_color: ColorType | None = None,
|
|
19
|
+
bg_image: Surface | None = None,
|
|
20
|
+
):
|
|
21
|
+
self.clock = Clock()
|
|
22
|
+
self.bg_image = bg_image
|
|
23
|
+
self.bg_color = bg_color
|
|
24
|
+
self.frames_per_second = frames_per_second
|
|
25
|
+
self.width = width
|
|
26
|
+
self.height = height
|
|
27
|
+
if self.width == 0.0 and self.height == 0.0:
|
|
28
|
+
self.screen = display.set_mode((0, 0), FULLSCREEN)
|
|
29
|
+
self.width = self.screen.get_width()
|
|
30
|
+
self.height = self.screen.get_height()
|
|
31
|
+
else:
|
|
32
|
+
self.screen = display.set_mode((self.width, self.height))
|
|
33
|
+
|
|
34
|
+
def resize_screen(self, width: float, height: float):
|
|
35
|
+
"""resizes to fullscreen by default"""
|
|
36
|
+
self.width = width
|
|
37
|
+
self.height = height
|
|
38
|
+
self.screen = display.set_mode((self.width, self.height))
|
|
39
|
+
|
|
40
|
+
def get_delta_time(self, units: int = 1000) -> float:
|
|
41
|
+
return self.clock.tick(self.frames_per_second) / units
|
|
42
|
+
|
|
43
|
+
@abstractmethod
|
|
44
|
+
def update_screen(self):
|
|
45
|
+
"""Define logic for screen activity. Required"""
|
|
46
|
+
raise NotImplementedError()
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
from abc import abstractmethod
|
|
2
|
+
from collections.abc import Callable
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from pygame import Rect, Surface, Vector2
|
|
6
|
+
from pygame.sprite import Sprite
|
|
7
|
+
|
|
8
|
+
from system_b.physics import PhysicsBody
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class GameSprite(Sprite):
|
|
12
|
+
def __init__(
|
|
13
|
+
self,
|
|
14
|
+
image: Surface,
|
|
15
|
+
position: Vector2,
|
|
16
|
+
boundaries: Rect | None = None,
|
|
17
|
+
id: int = 0,
|
|
18
|
+
):
|
|
19
|
+
"""Implement a Game Sprite object. Subclass must implement GameSprite.update
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
image (Surface): The image for the sprite
|
|
23
|
+
position (Vector2): Where to put the sprite
|
|
24
|
+
boundaries (Rect | None, optional): Optional boundary vectors for this sprite (xmax, xmin, ymax, ymin). Defaults to None.
|
|
25
|
+
"""
|
|
26
|
+
super().__init__()
|
|
27
|
+
self.image = image
|
|
28
|
+
_, _, *dimensions = image.get_rect()
|
|
29
|
+
self.rect = Rect(position.x, position.y, *dimensions)
|
|
30
|
+
self.position = position
|
|
31
|
+
self.boundaries = boundaries
|
|
32
|
+
self.id = id
|
|
33
|
+
|
|
34
|
+
@abstractmethod
|
|
35
|
+
def update(self, *args: Any, **kwargs: Any):
|
|
36
|
+
"""Required. Define logic to perform on this sprite once for every game loop
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
*args, **kwargs
|
|
40
|
+
"""
|
|
41
|
+
raise NotImplementedError()
|
|
42
|
+
|
|
43
|
+
def _update_pos(
|
|
44
|
+
self,
|
|
45
|
+
position: Vector2 | None = None,
|
|
46
|
+
xbounds: Callable[[], None] | None = None,
|
|
47
|
+
ybounds: Callable[[], None] | None = None,
|
|
48
|
+
):
|
|
49
|
+
"""Updates position of this sprite. Maintains any given boundaries. Invokes callbacks given when boundaries are reached
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
position (Vector2 | None, optional): The new position of this sprite. Defaults to None.
|
|
53
|
+
xbounds (Callable[[], None] | None, optional): X boundary callable. Invoked when boundaries are reached, if given. Defaults to None.
|
|
54
|
+
ybounds (Callable[[], None] | None, optional): Y boundary callable. Invoked when boundaries are reached, if given. Defaults to None.
|
|
55
|
+
"""
|
|
56
|
+
if position is not None:
|
|
57
|
+
self.position = position
|
|
58
|
+
|
|
59
|
+
x, y, w, h = self.rect
|
|
60
|
+
|
|
61
|
+
if self.boundaries is not None:
|
|
62
|
+
bx_max, bx_min, by_max, by_min = self.boundaries
|
|
63
|
+
|
|
64
|
+
if self.position.y > by_max - h / 2:
|
|
65
|
+
if ybounds is not None:
|
|
66
|
+
ybounds()
|
|
67
|
+
|
|
68
|
+
self.position.y = by_max - h / 2
|
|
69
|
+
|
|
70
|
+
elif self.position.y < by_min + h / 2:
|
|
71
|
+
if ybounds is not None:
|
|
72
|
+
ybounds()
|
|
73
|
+
|
|
74
|
+
self.position.y = by_min + h / 2
|
|
75
|
+
|
|
76
|
+
if self.position.x > bx_max - w / 2:
|
|
77
|
+
if xbounds is not None:
|
|
78
|
+
xbounds()
|
|
79
|
+
|
|
80
|
+
self.position.x = bx_max - w / 2
|
|
81
|
+
|
|
82
|
+
elif self.position.x < bx_min + w / 2:
|
|
83
|
+
if xbounds is not None:
|
|
84
|
+
xbounds()
|
|
85
|
+
|
|
86
|
+
self.position.x = bx_min + w / 2
|
|
87
|
+
|
|
88
|
+
x = self.position.x - w / 2
|
|
89
|
+
y = self.position.y - h / 2
|
|
90
|
+
|
|
91
|
+
self.rect = Rect(x, y, w, h)
|
|
92
|
+
|
|
93
|
+
def __str__(self) -> str:
|
|
94
|
+
return str(tuple(self.rect))
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class PhysicsSprite(GameSprite):
|
|
98
|
+
"""GameSprite that uses a PhysicsBody reference to apply physics"""
|
|
99
|
+
|
|
100
|
+
def __init__(
|
|
101
|
+
self,
|
|
102
|
+
image: Surface,
|
|
103
|
+
position: Vector2,
|
|
104
|
+
physics_body: PhysicsBody,
|
|
105
|
+
boundaries: Rect | None = None,
|
|
106
|
+
id: int = 0,
|
|
107
|
+
):
|
|
108
|
+
super().__init__(image, position, boundaries, id)
|
|
109
|
+
self.physics_body = physics_body
|
|
110
|
+
self.physics_body.position = self.position
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .toolkit import *
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
import shutil
|
|
4
|
+
import os
|
|
5
|
+
|
|
6
|
+
from system_b.demo import demo
|
|
7
|
+
|
|
8
|
+
def main(args):
|
|
9
|
+
if len(args) == 1:
|
|
10
|
+
print("Thank you for using game-utils!")
|
|
11
|
+
print(" --demo to play the demo game")
|
|
12
|
+
print(" --copy to copy the demo game file into a new project")
|
|
13
|
+
elif "--demo" in args:
|
|
14
|
+
print("Running demo...")
|
|
15
|
+
demo.main()
|
|
16
|
+
print("Game over...")
|
|
17
|
+
elif "--copy" in args:
|
|
18
|
+
shutil.copy(os.path.relpath(demo.__file__), os.path.curdir)
|
|
19
|
+
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: system-b
|
|
3
|
+
Version: 1.1.4
|
|
4
|
+
Summary: A pygame engine for common game operations like vector math and maintaining events in the game loop
|
|
5
|
+
Author-email: Bee Hall <bhall1224@gmail.com>
|
|
6
|
+
Project-URL: Homepage, https://gitlab.com/bhall/game-utils
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Requires-Python: <=3.13
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Requires-Dist: pygame
|
|
14
|
+
Requires-Dist: numpy
|
|
15
|
+
Dynamic: license-file
|
|
16
|
+
|
|
17
|
+
# Game Utils
|
|
18
|
+
|
|
19
|
+
>1.1.4
|
|
20
|
+
|
|
21
|
+
## `game-utils` is a pygame engine. The engine includes modules to facilitate boilerplate game operations.
|
|
22
|
+
|
|
23
|
+
### Requirements
|
|
24
|
+
|
|
25
|
+
**Dependences:**
|
|
26
|
+
- pygame
|
|
27
|
+
- numpy
|
|
28
|
+
|
|
29
|
+
**Demo Game**
|
|
30
|
+
|
|
31
|
+
Run: `python -m game_utils.demo` or `python -m game_utils.toolkit --demo` to play the demo bouncy ball game!
|
|
32
|
+
|
|
33
|
+
Run: `python -m game_utils.toolkit --copy` to copy the sample project to your directory!
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## See Also...
|
|
38
|
+
|
|
39
|
+
<p><b>My ongoing game projects!</b></p>
|
|
40
|
+
|
|
41
|
+
- [GitLab](https://gitlab.com/bhall)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
system_b/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
system_b/controller/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
system_b/controller/controller.py,sha256=DHivIHbAiG2JzvrGZ7XZPOhlksGRUebg9wCt3KrOFVI,2073
|
|
4
|
+
system_b/demo/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
system_b/demo/__main__.py,sha256=K27WoODgmcCBgeU7h3GpOY1m9Ppe8zcdOF02TxLAJas,61
|
|
6
|
+
system_b/demo/demo.py,sha256=04aWNbA3DCRZZGQlX27_GlkZIlXO2xst7Ame87TaYc4,8481
|
|
7
|
+
system_b/exceptions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
system_b/exceptions/game_exception.py,sha256=ao_S759piEm4dSTnGNLCO_PnaCvZWkypiSONcAip30g,74
|
|
9
|
+
system_b/game/__init__.py,sha256=JloD3B5UNcRFHm_5IhgnsNSceu8ookADMdK4gi3lHwE,46
|
|
10
|
+
system_b/game/game.py,sha256=cWZHngVPPSq1XG9eFxNq9EE_3Re2W4swYy-tStE6zRk,2737
|
|
11
|
+
system_b/images/__init__.py,sha256=iGdGCB85C0jMg-nB4cdRcADi_YeP7W_3R1mwHmjRrRs,80
|
|
12
|
+
system_b/images/images.py,sha256=lht79DqUdwo4-6hfFNg6AF2D9IQoqdvGLKyF6i9_22U,3191
|
|
13
|
+
system_b/math/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
14
|
+
system_b/math/vector_utils.py,sha256=hOgKYuG2vtN1tBCP28RqQyXwUCTHjJG8ZaMh96DBtsI,575
|
|
15
|
+
system_b/physics/__init__.py,sha256=ONWX3-OpL7deXQTwaSrdZJgB0fpPfszx3Dpwn60-oAA,32
|
|
16
|
+
system_b/physics/physics.py,sha256=N6rq5MaqmQU7m1lwdOtrOLag5Bt_DCtB3rt81JHlVJ4,3775
|
|
17
|
+
system_b/screen/__init__.py,sha256=GqoqPKXE3MrB3GVSP_tSxIZtGk6nHVR6gErHLcD0ACI,35
|
|
18
|
+
system_b/screen/screen.py,sha256=3koe7IwWpPBAcNlbBX_77UG5We42h4ltrhvFKC4_nBo,1486
|
|
19
|
+
system_b/sprites/__init__.py,sha256=CF4Tzdp3yInP_4XZwt1lsE-kOFA6ogU9yi0d44_XrJ8,60
|
|
20
|
+
system_b/sprites/sprites.py,sha256=JqVLtc3xJR4KjFg9C5onJ3hDYbKaFIeqRqMyQ22KPYc,3485
|
|
21
|
+
system_b/toolkit/__init__.py,sha256=QhVERgoij_eS3lqhQ7XAd0RvXyHQKDzY5TdTEzRZp-Q,22
|
|
22
|
+
system_b/toolkit/__main__.py,sha256=svQFAMO35nJO5SLsAHl8uXJOhgMB-UcIhzCV1iaDCUQ,85
|
|
23
|
+
system_b/toolkit/toolkit.py,sha256=W3W-akfzAkeCTwFmjH_dSTazqDANj6q4CA4Tijb72WM,501
|
|
24
|
+
system_b-1.1.4.dist-info/licenses/LICENSE,sha256=8bSadSMhlfCDsSwQh3WGQZduWMqaB7BGNEc2fppP3Ok,1065
|
|
25
|
+
system_b-1.1.4.dist-info/METADATA,sha256=uCNxhrRlY3JZMIOpvkXAgzuIU0MxEywqYN2RUabYNoU,1062
|
|
26
|
+
system_b-1.1.4.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
27
|
+
system_b-1.1.4.dist-info/top_level.txt,sha256=9G5cqM_S_nA-nz2Yf1WeJTIseXX3VPNp_ZEm0sHy2LI,9
|
|
28
|
+
system_b-1.1.4.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Bee Hall
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
system_b
|