-
Notifications
You must be signed in to change notification settings - Fork 11
/
ship.py
260 lines (209 loc) · 7.86 KB
/
ship.py
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
"""The ship module.
Defines the ship class, and the ship-breakup class (for when
death happens)."""
import math
import random
import pyxel
from bullet import Bullet
from utils import check_bounds, rotate_around_origin, Point
import constants
import sound
class Ship:
"""The ship class.
The ship class describes the behaviour and rendering of the ships. This includes:
- initial creation
- resetting on new game
- control (rotation, acceleration and shooting)
"""
radius = constants.SHIP_RADIUS
def __init__(self, x, y, colour):
"""Set up initial variables."""
self.starting_x = x
self.starting_y = y
self.starting_colour = colour
self.reset()
self.accelerating = False
self.shooting = False
def reset(self):
"""Reset the game specific variables (position, momentum and direction)."""
self.x = self.starting_x
self.y = self.starting_y
self.colour = self.starting_colour
self.direction = 0
self.momentum_x = 0
self.momentum_y = 0
self.points = []
for point in constants.SHIP_POINTS:
self.points.append(Point(*point))
def rotate(self, direction):
"""Rotate the ship based on user input."""
if direction == "l":
multipler = 1
elif direction == "r":
multipler = -1
else:
raise ValueError("Direction must be the 'l'eft or 'r'ight")
rotation_angle = constants.ROTATION * multipler
for point in self.points:
point.rotate_point(rotation_angle)
self.direction += rotation_angle
def accelerate(self):
"""Increase the velocity (to a maximum) of the ship based on user input."""
self.accelerating = True
acc_x, acc_y = rotate_around_origin(
(0, -constants.ACCELERATION), self.direction
)
self.momentum_x += acc_x
self.momentum_y += acc_y
acceleration = math.hypot(self.momentum_x, self.momentum_y)
if acceleration > constants.MAX_ACCELERATION:
scale = constants.MAX_ACCELERATION / acceleration
self.momentum_x *= scale
self.momentum_y *= scale
assert (
round(math.hypot(self.momentum_x, self.momentum_y), 0)
== constants.MAX_ACCELERATION
)
def shoot(self):
"""Create a bullet based on the ship's direction and position."""
vel_x, vel_y = rotate_around_origin(
(0, -constants.BULLET_VELOCITY), self.direction
)
ship_tip = self.points[0]
Bullet(
self.points[0].x + self.x,
self.points[0].y + self.y,
vel_x,
vel_y,
constants.BULLET_COLOUR,
)
def yes_shoot(self):
"""Start the shoot sound."""
if not self.shooting:
sound.start_shoot()
self.shooting = True
def no_shoot(self):
"""End the shoot sound."""
if self.shooting:
sound.stop_shoot()
self.shooting = False
def destroy(self):
"""Destroy the ship (does nothing at this point)."""
pass
def update_position(self):
"""Update the position and reduce the velocity."""
self.x += self.momentum_x
self.y += self.momentum_y
self.momentum_x *= constants.DRAG
self.momentum_y *= constants.DRAG
self.x = check_bounds(self.x, pyxel.width, constants.BUFFER)
self.y = check_bounds(self.y, pyxel.height, constants.BUFFER)
def display(self):
"""Display lines between each point and display the exhaust if accelerating."""
for point1, point2 in zip(self.points, self.points[1:] + [self.points[0]]):
pyxel.line(
x1=point1.x + self.x,
y1=point1.y + self.y,
x2=point2.x + self.x,
y2=point2.y + self.y,
col=self.colour,
)
if self.accelerating:
self.display_acceleration()
def display_acceleration(self):
"""Display the exhaust if accelerating."""
x1, y1 = rotate_around_origin(
(0, constants.SHIP_ACCELERATION_POINTS[0]), self.direction
)
x2, y2 = rotate_around_origin(
(0, constants.SHIP_ACCELERATION_POINTS[1]), self.direction
)
pyxel.line(
x1=x1 + self.x,
y1=y1 + self.y,
x2=x2 + self.x,
y2=y2 + self.y,
col=constants.SHIP_ACCELERATION_COLOUR,
)
class ShipBreakup:
"""A class based on the ship on death which displays the various segements
drifting aimlessly in space."""
def __init__(self, ship):
"""Coppies key parameters from ship and constructs the lines to drift."""
self.segments = []
def random_velocity():
"""Helper function to determine a random velocity."""
direction = random.random() * math.pi * 2
velocity = rotate_around_origin(
(0, -constants.SHIP_DRIFT_VELOCITY), direction
)
return velocity
for point1, point2 in zip(ship.points, ship.points[1:] + [ship.points[0]]):
rvel_x, rvel_y = random_velocity()
line_velocity = (rvel_x + ship.momentum_x, rvel_y + ship.momentum_y)
spin = constants.SHIP_BREAKUP_ROTATION * random.choice((-1, 1))
line = Line.line_from_two_points(
point1.x + ship.x,
point1.y + ship.y,
point2.x + ship.x,
point2.y + ship.y,
velocity=line_velocity,
spin=spin,
colour=ship.colour,
)
self.segments.append(line)
def update(self):
"""Drift the ship segments."""
for segment in self.segments:
segment.update()
def display(self):
"""Display lines between each point."""
for segment in self.segments:
segment.display()
class Line:
"""Class to contain a rotating line segment."""
def __init__(self, x, y, length, direction, velocity, spin, colour):
"""Initialise variables and set ends of the line.
The provided x and y are of the center point."""
self.x = x
self.y = y
self.length = length
self.direction = direction
self.vel_x, self.vel_y = velocity
self.spin = spin
self.colour = colour
self.points = []
for end in (1, -1):
self.points.append(Point(0, end * self.length / 2))
self.rotate(direction)
@classmethod
def line_from_two_points(cls, x1, y1, x2, y2, velocity, spin, colour):
"""Construct a line from two points rather than a point, a direction, and a length."""
x = sum((x1, x2)) / 2
y = sum((y1, y2)) / 2
length = math.hypot(x1 - x2, y1 - y2)
direction = -math.atan2(y1 - y2, x1 - x2) - math.pi / 2
return cls(x, y, length, direction, velocity, spin, colour)
def rotate(self, radians):
"""Rotate both points around center."""
for point in self.points:
point.rotate_point(radians)
def update(self):
"""Update the position and rotate."""
self.x += self.vel_x
self.y += self.vel_y
self.vel_x *= constants.SHIP_BREAKUP_DRAG
self.vel_y *= constants.SHIP_BREAKUP_DRAG
self.x = check_bounds(self.x, pyxel.width, constants.BUFFER)
self.y = check_bounds(self.y, pyxel.height, constants.BUFFER)
self.rotate(self.spin)
def display(self):
"""Display lines between each point and display the exhaust if accelerating."""
point1, point2 = self.points
pyxel.line(
x1=point1.x + self.x,
y1=point1.y + self.y,
x2=point2.x + self.x,
y2=point2.y + self.y,
col=self.colour,
)