forked from assamidanov/python_programming_class
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtargets.py
More file actions
428 lines (360 loc) · 14.2 KB
/
targets.py
File metadata and controls
428 lines (360 loc) · 14.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
from abstract import Drawable, Killable, Moveable
from color import Color
from artist import Artist
from bombs import BombMaster
from pygame import Surface
import random
class TargetMaster:
"""
Implements methods for game-wide target checking
Introduces methods for creating random targets and maintaining existing
targets (drawing them and moving them)
Attributes
----------
target_list : list[Target]
A list of all the targets created by this TargetMaster
moving_target_type : list
A list of the moveable types of targets
static_target_type : list
A list of the static types of targets
"""
def __init__(self) -> None:
"""Initializes the empty target list"""
self.target_list: list[Target] = []
# The types of targets available
self.moving_target_type = [
MovingSquare,
MovingTriangle,
MovingCircle
]
self.static_target_type = [
StaticSquare,
StaticTriangle,
StaticCircle
]
def create_random_target(
self,
screen_size: tuple,
target_size: int,
x: int = None,
y: int = None,
is_moving: bool = None) -> None:
"""
Creates a random target given its parameters (or lack thereof)
This function checks if the input parameters have been predetermined,
or generates them randomly.
The generated target is added to the target list, not returned
Parameters
----------
screen_size : tuple
A tuple representing the (X, Y) size of the screen
target_size : int
An int representing the target size to create
x : int
The x position of the target. If not provided, it will be generated
according to the screen size
y : int
The y position of the target. If not provided, it will be generated
according to the screen size
is_moving : bool
A bool denoting whether or not the target should be a moving target
If not provided, it will be a 50% chance
"""
# Determine whether target should be moving (if it wasn't provided)
is_moving = is_moving if is_moving is not None else bool(random.randint(0, 1))
chosen_type: Target = None
# The parameters provided or generated
params: dict = {
'x': x or random.randint(target_size, screen_size[0] - target_size),
'y': y or random.randint(target_size, screen_size[1] - target_size),
'size': target_size
}
# If the target should be moving, choose from the list of moving target
# types. Otherwise, choose a static target type
if is_moving:
chosen_type = random.choice(self.moving_target_type)
else:
chosen_type = random.choice(self.static_target_type)
# Create and store the target
created_target = chosen_type(**params)
self.target_list.append(created_target)
def calculate_target_size(self, score: int) -> int:
"""
Determines the target size based on the score
Should be a number between 10 and 30, with higher sizes being favored for
lower scores and vice versa (the higher the score, the harder it is to
hit the targets)
Parameters
----------
score : int
The score to calculate the target size based off of
Returns
-------
size : int
The size of the target calculated
"""
score = max(0, score)
weight = 1/(score + 1)
return int(random.uniform(10, min(30, 30 + weight * 20)))
def draw_all(self, surface: Surface) -> None:
"""
Simply loops through all the targets and draws them to the surface
Simply calls the target.draw function on each target
Parameters
----------
surface : pygame.Surface
The surface to draw the target to
"""
[target.draw(surface) for target in self.target_list]
def move_all(self, screen_size: tuple) -> None:
"""
Simply loops through all the targets and moves them based on their velocity
Simply calls the target.move function on each target if it's a moving
target
Parameters
----------
screen_size : tuple
The size of the screen
"""
[
target.move(screen_size)
for target in self.target_list
if isinstance(target, MovingTarget)
]
class Target(Drawable, Killable):
"""
A class representing a target
A target can be hit by projectiles, and can be a circle, triangle, or square.
Only projectiles of the same shape as the target can deal damage.
A Target is a Drawable (meaning it can be drawn to the screen) and is Killable
(meaning it can take damage and die). This means we need to pass in all the
attributes necessary for both a Drawable and Killable object, and an attribute
for the shape.
Attributes
----------
x : int
The x coordinate of the object
y : int
The y coordinate of the object
color : tuple
A tuple representing the (R, G, B) values of the object's color
size : int
An int representing the size of the object
For a square, it will be the length of a side
For a circle, it will be its radius
For a triangle, it will be the length of a side
health : int
An int denoting the object's health. A health value of 1 means the object
is killed after a single hit.
shape : str
A string of characters 's', 't', or 'c' denoting whether the object is a
square, triangle, or circle.
bomb_master : BombMaster
The controller of all bombs created by this target
"""
def __init__(
self,
x: int,
y: int,
color: tuple = None,
size: int = 5,
health: int = 1,
shape: str = 'c') -> None:
"""
Intiailizes the necessary values for a Drawable, Killable, object using the
init functions of both abstract classes, respectively
While the default value for color, size, health, and shape looks to be None,
the function defaults those to a random color, 5, 1, and 'c' for circle.
This is due to behavior in Python with passing attributes through super
functions.
"""
color = color or Color.rand_color()
# Parent class initialization
Drawable.__init__(self, x=x, y=y, color=color, size=size)
Killable.__init__(self, health=health)
# Shape initialization
self.shape = shape
self.bomb_master = BombMaster()
def draw(self, surface: Surface) -> None:
"""
Uses a static Artist draw function to draw the object to the given surface
Parameters
----------
surface : pygame.Surface
A surface object to draw the Drawable onto
"""
Artist.draw(
surface,
self.x, self.y,
self.color, self.size, self.shape)
def __str__(self) -> str:
"""Returns a string representation of the object"""
return f"Static Target of Shape({self.shape}), " \
f"Pos({self.x}, {self.y}), " \
f"Color({self.color}), Size({self.size}), Health({self.health})"
def __repr__(self) -> str:
"""Delegates to __str__"""
return self.__str__()
class MovingTarget(Moveable, Target):
"""
A class representing a moving target
A target can be hit by projectiles, and can be a circle, triangle, or square.
Only projectiles of the same shape as the target can deal damage.
A Target is a Drawable (meaning it can be drawn to the screen) and is Killable
(meaning it can take damage and die). This means we need to pass in all the
attributes necessary for both a Drawable and Killable object, and an attribute
for the shape. In addition, this type of target can be moved around the screen
(making it a Moveable as well).
MovingTarget simply inherits from the abstract Moveable and the concrete Target
Attributes
----------
x : int
The x coordinate of the object
y : int
The y coordinate of the object
v_x : int
The object's velocity in the x direction
v_y : int
The object's velocity in the y direction
color : tuple
A tuple representing the (R, G, B) values of the object's color
size : int
An int representing the size of the object
For a square, it will be the length of a side
For a circle, it will be its radius
For a triangle, it will be the length of a side
health : int
An int denoting the object's health. A health value of 1 means the object
is killed after a single hit.
shape : str
A string of characters 's', 't', or 'c' denoting whether the object is a
square, triangle, or circle.
"""
def __init__(
self,
x: int,
y: int,
v_x: int = None,
v_y: int = None,
color: tuple = None,
size: int = 30,
health: int = 1,
shape: str = None) -> None:
"""
Intiailizes the necessary values for a Moveable, Target, object using
both classes' init functions
While the default value for velocities, color, size, health, and shape
looks to be None, the function defaults those to random numbers
between -2 and 2, a random color, 30, 1, and 'c' for circle.
This is due to behavior in Python with passing attributes through super
functions.
"""
v_x = v_x or random.randint(-2, 2)
v_y = v_y or random.randint(-2, 2)
color = color or Color.rand_color()
# Parent class initialization
Moveable.__init__(self, v_x, v_y)
Target.__init__(self, x, y, color, size, health, shape)
def move(self, screen_size: tuple) -> None:
"""
Changes the x and y position of the object depending on the velocities
and delgates to checking if we hit the edge of the screen
Parameters
----------
screen_size : tuple
A tuple representing the (X, Y) size of the screen
"""
self.x += self.v_x
self.y += self.v_y
self.check_corners(screen_size)
def check_corners(self, screen_size: tuple) -> None:
"""
Implements rebound when the target hits the screen's edge
Parameters
----------
screen_size : tuple
A tuple representing the (X, Y) size of the screen
"""
# If the target hits the left edge of the screen
if self.x < self.size:
# Make sure we don't go off-screen
self.x = self.size
# Reverse the x velocity and calculate the new velocities (decreased)
# based on the reflection parameters
self.v_x = -int(self.v_x)
self.v_y = int(self.v_y)
# If the target hits the right edge of the scrteen
elif self.x > screen_size[0] - self.size:
# Make sure we don't go off-screen
self.x = screen_size[0] - self.size
# Reverse the x velocity and calculate the new velocities (decreased)
# based on the reflection parameters
self.v_x = -int(self.v_x)
self.v_y = int(self.v_y)
# If the target hits the top of the screen
if self.y < self.size:
# Make sure we don't go off-screen
self.y = self.size
# Reverse the y velocity and calculate the new velocities (decreased)
# based on the reflection parameters
self.v_x = int(self.v_x)
self.v_y = -int(self.v_y)
# If the target hits the bottom of the screen
elif self.y > screen_size[1] - self.size:
# Make sure we don't go off-screen
self.y = screen_size[1] - self.size
# Reverse the y velocity and calculate the new velocities (decreased)
# based on the reflection parameters
self.v_x = int(self.v_x)
self.v_y = -int(self.v_y)
def __str__(self):
"""Returns a string representation of the object"""
return f"Moving Target of Shape({self.shape}), " \
f"Pos({self.x}, {self.y}), " \
f"Color({self.color}), Size({self.size}), Health({self.health}), " \
f"Speed({self.v_x}, {self.v_y})"
def __repr__(self):
"""Returns a string representation of the object"""
return self.__str__()
class MovingSquare(MovingTarget):
"""A MovingTarget of shape Square. Refer to `MovingTarget`"""
def __init__(self, *args, **kwargs) -> None:
super().__init__(
*args,
**kwargs,
shape = 's')
class MovingTriangle(MovingTarget):
"""A MovingTarget of shape Triangle. Refer to `MovingTarget`"""
def __init__(self, *args, **kwargs) -> None:
super().__init__(
*args,
**kwargs,
shape = 't')
class MovingCircle(MovingTarget):
"""A MovingTarget of shape Circle. Refer to `MovingTarget`"""
def __init__(self, *args, **kwargs) -> None:
super().__init__(
*args,
**kwargs,
shape = 'c')
class StaticSquare(Target):
"""A StaticTarget of shape Square. Refer to `Target`"""
def __init__(self, *args, **kwargs) -> None:
super().__init__(
*args,
**kwargs,
shape = 's')
class StaticTriangle(Target):
"""A StaticTarget of shape Triangle. Refer to `Target`"""
def __init__(self, *args, **kwargs) -> None:
super().__init__(
*args,
**kwargs,
shape = 't')
class StaticCircle(Target):
"""A StaticTarget of shape Circle. Refer to `Target`"""
def __init__(self, *args, **kwargs) -> None:
super().__init__(
*args,
**kwargs,
shape = 'c')