0
# Mathematical Utilities
1
2
Mathematical functions and vector operations for game calculations. Pygame's math module provides essential mathematical tools including 2D and 3D vectors, utility functions, and geometric calculations commonly needed in game development.
3
4
## Capabilities
5
6
### Utility Functions
7
8
General mathematical functions for common game calculations.
9
10
```python { .api }
11
def clamp(value: float, min_value: float, max_value: float) -> float:
12
"""
13
Constrain value to range [min_value, max_value].
14
15
Args:
16
value (float): Value to clamp
17
min_value (float): Minimum allowed value
18
max_value (float): Maximum allowed value
19
20
Returns:
21
float: Clamped value
22
"""
23
24
def lerp(a: float, b: float, weight: float) -> float:
25
"""
26
Linear interpolation between two values.
27
28
Args:
29
a (float): Start value
30
b (float): End value
31
weight (float): Interpolation factor (0.0 to 1.0)
32
33
Returns:
34
float: Interpolated value
35
"""
36
```
37
38
### Vector2 Class
39
40
2D vector class for position, velocity, direction calculations.
41
42
```python { .api }
43
class Vector2:
44
def __init__(self, x: float = 0, y: float = 0):
45
"""
46
Create 2D vector.
47
48
Args:
49
x (float): X component
50
y (float): Y component
51
"""
52
53
# Properties
54
x: float # X component
55
y: float # Y component
56
57
# Vector Operations
58
def dot(self, vector: 'Vector2') -> float:
59
"""
60
Calculate dot product with another vector.
61
62
Args:
63
vector (Vector2): Other vector
64
65
Returns:
66
float: Dot product (v1.x * v2.x + v1.y * v2.y)
67
"""
68
69
def cross(self, vector: 'Vector2') -> float:
70
"""
71
Calculate 2D cross product (scalar).
72
73
Args:
74
vector (Vector2): Other vector
75
76
Returns:
77
float: Cross product magnitude
78
"""
79
80
def magnitude(self) -> float:
81
"""
82
Get vector length/magnitude.
83
84
Returns:
85
float: Vector magnitude
86
"""
87
88
def magnitude_squared(self) -> float:
89
"""
90
Get squared magnitude (faster than magnitude()).
91
92
Returns:
93
float: Squared magnitude
94
"""
95
96
def length(self) -> float:
97
"""
98
Alias for magnitude().
99
100
Returns:
101
float: Vector length
102
"""
103
104
def length_squared(self) -> float:
105
"""
106
Alias for magnitude_squared().
107
108
Returns:
109
float: Squared length
110
"""
111
112
def normalize(self) -> 'Vector2':
113
"""
114
Get normalized vector (length = 1).
115
116
Returns:
117
Vector2: Unit vector in same direction
118
"""
119
120
def normalize_ip(self) -> None:
121
"""Normalize this vector in place."""
122
123
def is_normalized(self) -> bool:
124
"""
125
Check if vector is normalized.
126
127
Returns:
128
bool: True if length is approximately 1
129
"""
130
131
def safe_normalize(self) -> 'Vector2':
132
"""
133
Normalize vector, returns zero vector if length is zero.
134
135
Returns:
136
Vector2: Normalized vector or zero vector
137
"""
138
139
def scale_to_length(self, length: float) -> None:
140
"""
141
Scale vector to specific length.
142
143
Args:
144
length (float): Desired length
145
"""
146
147
# Distance Functions
148
def distance_to(self, vector: 'Vector2') -> float:
149
"""
150
Calculate distance to another vector.
151
152
Args:
153
vector (Vector2): Target vector
154
155
Returns:
156
float: Distance between vectors
157
"""
158
159
def distance_squared_to(self, vector: 'Vector2') -> float:
160
"""
161
Calculate squared distance (faster than distance_to()).
162
163
Args:
164
vector (Vector2): Target vector
165
166
Returns:
167
float: Squared distance
168
"""
169
170
# Interpolation
171
def lerp(self, vector: 'Vector2', t: float) -> 'Vector2':
172
"""
173
Linear interpolation to another vector.
174
175
Args:
176
vector (Vector2): Target vector
177
t (float): Interpolation factor (0.0 to 1.0)
178
179
Returns:
180
Vector2: Interpolated vector
181
"""
182
183
def slerp(self, vector: 'Vector2', t: float) -> 'Vector2':
184
"""
185
Spherical linear interpolation (smoother rotation).
186
187
Args:
188
vector (Vector2): Target vector
189
t (float): Interpolation factor (0.0 to 1.0)
190
191
Returns:
192
Vector2: Interpolated vector
193
"""
194
195
def move_towards(self, vector: 'Vector2', max_distance: float) -> 'Vector2':
196
"""
197
Move towards target by maximum distance.
198
199
Args:
200
vector (Vector2): Target vector
201
max_distance (float): Maximum distance to move
202
203
Returns:
204
Vector2: New position
205
"""
206
207
# Rotation and Reflection
208
def rotate(self, angle: float) -> 'Vector2':
209
"""
210
Rotate vector by angle (degrees).
211
212
Args:
213
angle (float): Rotation angle in degrees
214
215
Returns:
216
Vector2: Rotated vector
217
"""
218
219
def rotate_ip(self, angle: float) -> None:
220
"""
221
Rotate vector in place.
222
223
Args:
224
angle (float): Rotation angle in degrees
225
"""
226
227
def rotate_rad(self, angle: float) -> 'Vector2':
228
"""
229
Rotate vector by angle (radians).
230
231
Args:
232
angle (float): Rotation angle in radians
233
234
Returns:
235
Vector2: Rotated vector
236
"""
237
238
def rotate_rad_ip(self, angle: float) -> None:
239
"""
240
Rotate vector in place (radians).
241
242
Args:
243
angle (float): Rotation angle in radians
244
"""
245
246
def reflect(self, normal: 'Vector2') -> 'Vector2':
247
"""
248
Reflect vector across surface normal.
249
250
Args:
251
normal (Vector2): Surface normal vector
252
253
Returns:
254
Vector2: Reflected vector
255
"""
256
257
def reflect_ip(self, normal: 'Vector2') -> None:
258
"""
259
Reflect vector in place.
260
261
Args:
262
normal (Vector2): Surface normal vector
263
"""
264
265
# Polar Coordinates
266
def as_polar(self) -> tuple[float, float]:
267
"""
268
Convert to polar coordinates.
269
270
Returns:
271
tuple[float, float]: (radius, angle_in_degrees)
272
"""
273
274
@classmethod
275
def from_polar(cls, polar: tuple[float, float]) -> 'Vector2':
276
"""
277
Create vector from polar coordinates.
278
279
Args:
280
polar (tuple[float, float]): (radius, angle_in_degrees)
281
282
Returns:
283
Vector2: New vector from polar coordinates
284
"""
285
286
# Angles
287
def angle_to(self, vector: 'Vector2') -> float:
288
"""
289
Calculate angle to another vector.
290
291
Args:
292
vector (Vector2): Target vector
293
294
Returns:
295
float: Angle in degrees (-180 to 180)
296
"""
297
298
# Utility
299
def copy(self) -> 'Vector2':
300
"""
301
Create copy of vector.
302
303
Returns:
304
Vector2: Copy of this vector
305
"""
306
307
def update(self, x: float, y: float) -> None:
308
"""
309
Update vector components.
310
311
Args:
312
x (float): New X component
313
y (float): New Y component
314
"""
315
316
def clamp_magnitude(self, min_length: float, max_length: float) -> 'Vector2':
317
"""
318
Clamp vector magnitude to range.
319
320
Args:
321
min_length (float): Minimum magnitude
322
max_length (float): Maximum magnitude
323
324
Returns:
325
Vector2: Vector with clamped magnitude
326
"""
327
328
def clamp_magnitude_ip(self, min_length: float, max_length: float) -> None:
329
"""
330
Clamp magnitude in place.
331
332
Args:
333
min_length (float): Minimum magnitude
334
max_length (float): Maximum magnitude
335
"""
336
337
# Arithmetic Operations (supports +, -, *, /, //, %, **)
338
def __add__(self, other) -> 'Vector2': ...
339
def __sub__(self, other) -> 'Vector2': ...
340
def __mul__(self, scalar: float) -> 'Vector2': ...
341
def __truediv__(self, scalar: float) -> 'Vector2': ...
342
def __floordiv__(self, scalar: float) -> 'Vector2': ...
343
def __mod__(self, scalar: float) -> 'Vector2': ...
344
def __pow__(self, exponent: float) -> 'Vector2': ...
345
346
# Comparison Operations
347
def __eq__(self, other) -> bool: ...
348
def __ne__(self, other) -> bool: ...
349
350
# Sequence Operations
351
def __getitem__(self, index: int) -> float: ...
352
def __setitem__(self, index: int, value: float) -> None: ...
353
def __len__(self) -> int: ... # Always returns 2
354
def __iter__(self): ...
355
```
356
357
### Vector3 Class
358
359
3D vector class extending Vector2 functionality with Z component.
360
361
```python { .api }
362
class Vector3:
363
def __init__(self, x: float = 0, y: float = 0, z: float = 0):
364
"""
365
Create 3D vector.
366
367
Args:
368
x (float): X component
369
y (float): Y component
370
z (float): Z component
371
"""
372
373
# Properties
374
x: float # X component
375
y: float # Y component
376
z: float # Z component
377
378
# All Vector2 methods plus 3D-specific operations:
379
380
def cross(self, vector: 'Vector3') -> 'Vector3':
381
"""
382
Calculate 3D cross product.
383
384
Args:
385
vector (Vector3): Other vector
386
387
Returns:
388
Vector3: Cross product vector (perpendicular to both inputs)
389
"""
390
391
def dot(self, vector: 'Vector3') -> float:
392
"""
393
Calculate dot product.
394
395
Args:
396
vector (Vector3): Other vector
397
398
Returns:
399
float: Dot product
400
"""
401
402
# 3D Rotation
403
def rotate_x(self, angle: float) -> 'Vector3':
404
"""
405
Rotate around X axis.
406
407
Args:
408
angle (float): Rotation angle in degrees
409
410
Returns:
411
Vector3: Rotated vector
412
"""
413
414
def rotate_x_ip(self, angle: float) -> None:
415
"""Rotate around X axis in place."""
416
417
def rotate_y(self, angle: float) -> 'Vector3':
418
"""
419
Rotate around Y axis.
420
421
Args:
422
angle (float): Rotation angle in degrees
423
424
Returns:
425
Vector3: Rotated vector
426
"""
427
428
def rotate_y_ip(self, angle: float) -> None:
429
"""Rotate around Y axis in place."""
430
431
def rotate_z(self, angle: float) -> 'Vector3':
432
"""
433
Rotate around Z axis.
434
435
Args:
436
angle (float): Rotation angle in degrees
437
438
Returns:
439
Vector3: Rotated vector
440
"""
441
442
def rotate_z_ip(self, angle: float) -> None:
443
"""Rotate around Z axis in place."""
444
445
def rotate(self, axis: 'Vector3', angle: float) -> 'Vector3':
446
"""
447
Rotate around arbitrary axis.
448
449
Args:
450
axis (Vector3): Rotation axis (should be normalized)
451
angle (float): Rotation angle in degrees
452
453
Returns:
454
Vector3: Rotated vector
455
"""
456
457
def rotate_ip(self, axis: 'Vector3', angle: float) -> None:
458
"""
459
Rotate around arbitrary axis in place.
460
461
Args:
462
axis (Vector3): Rotation axis (should be normalized)
463
angle (float): Rotation angle in degrees
464
"""
465
466
# Spherical Coordinates
467
def as_spherical(self) -> tuple[float, float, float]:
468
"""
469
Convert to spherical coordinates.
470
471
Returns:
472
tuple[float, float, float]: (radius, theta, phi) in degrees
473
"""
474
475
@classmethod
476
def from_spherical(cls, spherical: tuple[float, float, float]) -> 'Vector3':
477
"""
478
Create vector from spherical coordinates.
479
480
Args:
481
spherical (tuple[float, float, float]): (radius, theta, phi) in degrees
482
483
Returns:
484
Vector3: New vector from spherical coordinates
485
"""
486
487
def update(self, x: float, y: float, z: float) -> None:
488
"""
489
Update vector components.
490
491
Args:
492
x (float): New X component
493
y (float): New Y component
494
z (float): New Z component
495
"""
496
497
# All arithmetic and comparison operations from Vector2
498
def __len__(self) -> int: ... # Always returns 3
499
```
500
501
## Usage Examples
502
503
### Basic Vector Operations
504
505
```python
506
import pygame
507
import pygame.math
508
509
# Create vectors
510
pos = pygame.math.Vector2(100, 50)
511
velocity = pygame.math.Vector2(5, -3)
512
target = pygame.math.Vector2(400, 300)
513
514
print(f"Position: {pos}")
515
print(f"Velocity: {velocity}")
516
517
# Vector arithmetic
518
new_pos = pos + velocity
519
print(f"New position: {new_pos}")
520
521
# Vector magnitude
522
speed = velocity.magnitude()
523
print(f"Speed: {speed}")
524
525
# Distance calculation
526
distance = pos.distance_to(target)
527
print(f"Distance to target: {distance}")
528
529
# Normalize vector
530
direction = (target - pos).normalize()
531
print(f"Direction to target: {direction}")
532
```
533
534
### Movement and Steering
535
536
```python
537
import pygame
538
import pygame.math
539
import random
540
541
class Entity:
542
def __init__(self, x, y):
543
self.position = pygame.math.Vector2(x, y)
544
self.velocity = pygame.math.Vector2(0, 0)
545
self.max_speed = 3.0
546
self.acceleration = pygame.math.Vector2(0, 0)
547
548
def seek(self, target):
549
"""Steer towards target"""
550
desired = (target - self.position).normalize() * self.max_speed
551
steer = desired - self.velocity
552
return steer
553
554
def flee(self, target):
555
"""Steer away from target"""
556
return -self.seek(target)
557
558
def wander(self):
559
"""Random wandering behavior"""
560
random_angle = random.uniform(-30, 30)
561
forward = self.velocity.normalize() if self.velocity.magnitude() > 0 else pygame.math.Vector2(1, 0)
562
wander_direction = forward.rotate(random_angle)
563
return wander_direction * 0.5
564
565
def update(self):
566
# Apply acceleration
567
self.velocity += self.acceleration
568
569
# Limit speed
570
if self.velocity.magnitude() > self.max_speed:
571
self.velocity.scale_to_length(self.max_speed)
572
573
# Update position
574
self.position += self.velocity
575
576
# Reset acceleration
577
self.acceleration = pygame.math.Vector2(0, 0)
578
579
def apply_force(self, force):
580
self.acceleration += force
581
582
pygame.init()
583
screen = pygame.display.set_mode((800, 600))
584
clock = pygame.time.Clock()
585
586
# Create entities
587
entities = [Entity(random.randint(50, 750), random.randint(50, 550)) for _ in range(10)]
588
mouse_pos = pygame.math.Vector2(400, 300)
589
590
running = True
591
while running:
592
for event in pygame.event.get():
593
if event.type == pygame.QUIT:
594
running = False
595
elif event.type == pygame.MOUSEMOTION:
596
mouse_pos = pygame.math.Vector2(event.pos)
597
598
screen.fill((0, 0, 0))
599
600
for entity in entities:
601
# Calculate steering forces
602
seek_force = entity.seek(mouse_pos) * 0.1
603
wander_force = entity.wander() * 0.5
604
605
# Combine forces
606
total_force = seek_force + wander_force
607
entity.apply_force(total_force)
608
entity.update()
609
610
# Wrap around screen edges
611
if entity.position.x < 0:
612
entity.position.x = 800
613
elif entity.position.x > 800:
614
entity.position.x = 0
615
if entity.position.y < 0:
616
entity.position.y = 600
617
elif entity.position.y > 600:
618
entity.position.y = 0
619
620
# Draw entity
621
pygame.draw.circle(screen, (255, 255, 255), (int(entity.position.x), int(entity.position.y)), 5)
622
623
# Draw velocity vector
624
end_pos = entity.position + entity.velocity * 10
625
pygame.draw.line(screen, (255, 0, 0), entity.position, end_pos, 2)
626
627
# Draw mouse cursor
628
pygame.draw.circle(screen, (0, 255, 0), (int(mouse_pos.x), int(mouse_pos.y)), 8)
629
630
pygame.display.flip()
631
clock.tick(60)
632
633
pygame.quit()
634
```
635
636
### 3D Vector Math
637
638
```python
639
import pygame.math
640
import math
641
642
# Create 3D vectors
643
position = pygame.math.Vector3(1, 2, 3)
644
forward = pygame.math.Vector3(0, 0, -1) # Looking down negative Z
645
up = pygame.math.Vector3(0, 1, 0)
646
647
print(f"Position: {position}")
648
print(f"Forward: {forward}")
649
print(f"Up: {up}")
650
651
# Calculate right vector using cross product
652
right = forward.cross(up)
653
print(f"Right: {right}")
654
655
# Rotate around Y axis (yaw)
656
rotated_forward = forward.rotate_y(45)
657
print(f"Rotated 45° around Y: {rotated_forward}")
658
659
# Convert to spherical coordinates
660
spherical = position.as_spherical()
661
print(f"Spherical (r, theta, phi): {spherical}")
662
663
# Create vector from spherical coordinates
664
from_spherical = pygame.math.Vector3.from_spherical((5, 45, 30))
665
print(f"From spherical: {from_spherical}")
666
667
# 3D distance and interpolation
668
target = pygame.math.Vector3(5, 8, 2)
669
distance_3d = position.distance_to(target)
670
print(f"3D distance: {distance_3d}")
671
672
# Interpolate between positions
673
interpolated = position.lerp(target, 0.5)
674
print(f"Halfway point: {interpolated}")
675
```
676
677
### Collision Detection with Vectors
678
679
```python
680
import pygame
681
import pygame.math
682
683
def circle_collision(pos1, radius1, pos2, radius2):
684
"""Check collision between two circles using vectors"""
685
distance = pos1.distance_to(pos2)
686
return distance <= (radius1 + radius2)
687
688
def line_intersection(line1_start, line1_end, line2_start, line2_end):
689
"""Find intersection point of two lines"""
690
# Convert to vector form
691
d1 = line1_end - line1_start
692
d2 = line2_end - line2_start
693
d3 = line1_start - line2_start
694
695
# Calculate cross products
696
cross_d2_d3 = d2.cross(d3)
697
cross_d1_d2 = d1.cross(d2)
698
699
if abs(cross_d1_d2) < 1e-10: # Lines are parallel
700
return None
701
702
t1 = cross_d2_d3 / cross_d1_d2
703
if 0 <= t1 <= 1:
704
intersection = line1_start + d1 * t1
705
return intersection
706
return None
707
708
def point_in_triangle(point, a, b, c):
709
"""Check if point is inside triangle using barycentric coordinates"""
710
v0 = c - a
711
v1 = b - a
712
v2 = point - a
713
714
dot00 = v0.dot(v0)
715
dot01 = v0.dot(v1)
716
dot02 = v0.dot(v2)
717
dot11 = v1.dot(v1)
718
dot12 = v1.dot(v2)
719
720
inv_denom = 1 / (dot00 * dot11 - dot01 * dot01)
721
u = (dot11 * dot02 - dot01 * dot12) * inv_denom
722
v = (dot00 * dot12 - dot01 * dot02) * inv_denom
723
724
return (u >= 0) and (v >= 0) and (u + v <= 1)
725
726
# Example usage
727
pygame.init()
728
screen = pygame.display.set_mode((800, 600))
729
clock = pygame.time.Clock()
730
731
# Test objects
732
circle1 = {"pos": pygame.math.Vector2(200, 200), "radius": 30}
733
circle2 = {"pos": pygame.math.Vector2(400, 300), "radius": 40}
734
735
triangle_points = [
736
pygame.math.Vector2(500, 100),
737
pygame.math.Vector2(600, 250),
738
pygame.math.Vector2(450, 200)
739
]
740
741
mouse_pos = pygame.math.Vector2(0, 0)
742
743
running = True
744
while running:
745
for event in pygame.event.get():
746
if event.type == pygame.QUIT:
747
running = False
748
elif event.type == pygame.MOUSEMOTION:
749
mouse_pos = pygame.math.Vector2(event.pos)
750
751
screen.fill((0, 0, 0))
752
753
# Check circle collision
754
circles_colliding = circle_collision(
755
circle1["pos"], circle1["radius"],
756
circle2["pos"], circle2["radius"]
757
)
758
759
# Draw circles
760
color1 = (255, 100, 100) if circles_colliding else (100, 100, 255)
761
color2 = (255, 100, 100) if circles_colliding else (100, 255, 100)
762
763
pygame.draw.circle(screen, color1, (int(circle1["pos"].x), int(circle1["pos"].y)), circle1["radius"])
764
pygame.draw.circle(screen, color2, (int(circle2["pos"].x), int(circle2["pos"].y)), circle2["radius"])
765
766
# Check point in triangle
767
mouse_in_triangle = point_in_triangle(mouse_pos, *triangle_points)
768
triangle_color = (255, 255, 100) if mouse_in_triangle else (100, 100, 100)
769
770
# Draw triangle
771
triangle_coords = [(int(p.x), int(p.y)) for p in triangle_points]
772
pygame.draw.polygon(screen, triangle_color, triangle_coords)
773
774
# Draw mouse position
775
pygame.draw.circle(screen, (255, 255, 255), (int(mouse_pos.x), int(mouse_pos.y)), 5)
776
777
pygame.display.flip()
778
clock.tick(60)
779
780
pygame.quit()
781
```
782
783
### Vector Field Visualization
784
785
```python
786
import pygame
787
import pygame.math
788
import math
789
790
def vector_field(pos):
791
"""Generate vector field - spiral pattern"""
792
center = pygame.math.Vector2(400, 300)
793
to_center = center - pos
794
795
if to_center.magnitude() > 0:
796
# Spiral field
797
perpendicular = pygame.math.Vector2(-to_center.y, to_center.x).normalize()
798
radial = to_center.normalize()
799
800
spiral_strength = 0.3
801
radial_strength = 0.1
802
803
field_vector = perpendicular * spiral_strength + radial * radial_strength
804
return field_vector
805
else:
806
return pygame.math.Vector2(0, 0)
807
808
pygame.init()
809
screen = pygame.display.set_mode((800, 600))
810
clock = pygame.time.Clock()
811
812
# Create grid of vectors
813
grid_size = 40
814
vectors = []
815
816
for x in range(0, 800, grid_size):
817
for y in range(0, 600, grid_size):
818
pos = pygame.math.Vector2(x, y)
819
field_vec = vector_field(pos)
820
vectors.append((pos, field_vec))
821
822
# Particles
823
particles = []
824
for _ in range(50):
825
particle = {
826
'pos': pygame.math.Vector2(
827
pygame.math.Vector2(400, 300).x + (random.uniform(-100, 100)),
828
pygame.math.Vector2(400, 300).y + (random.uniform(-100, 100))
829
),
830
'vel': pygame.math.Vector2(0, 0),
831
'trail': []
832
}
833
particles.append(particle)
834
835
import random
836
837
running = True
838
while running:
839
for event in pygame.event.get():
840
if event.type == pygame.QUIT:
841
running = False
842
843
screen.fill((0, 0, 0))
844
845
# Draw vector field
846
for pos, field_vec in vectors:
847
if field_vec.magnitude() > 0.01:
848
end_pos = pos + field_vec * 30
849
pygame.draw.line(screen, (50, 50, 50), pos, end_pos)
850
# Draw arrowhead
851
arrow_size = 5
852
if field_vec.magnitude() > 0:
853
arrow_dir = field_vec.normalize()
854
left_arrow = end_pos - arrow_dir.rotate(150) * arrow_size
855
right_arrow = end_pos - arrow_dir.rotate(-150) * arrow_size
856
pygame.draw.polygon(screen, (50, 50, 50), [end_pos, left_arrow, right_arrow])
857
858
# Update and draw particles
859
for particle in particles:
860
# Apply field force
861
field_force = vector_field(particle['pos'])
862
particle['vel'] += field_force * 0.5
863
864
# Apply damping
865
particle['vel'] *= 0.98
866
867
# Update position
868
particle['pos'] += particle['vel']
869
870
# Add to trail
871
particle['trail'].append(particle['pos'].copy())
872
if len(particle['trail']) > 20:
873
particle['trail'].pop(0)
874
875
# Wrap around screen
876
if particle['pos'].x < 0: particle['pos'].x = 800
877
if particle['pos'].x > 800: particle['pos'].x = 0
878
if particle['pos'].y < 0: particle['pos'].y = 600
879
if particle['pos'].y > 600: particle['pos'].y = 0
880
881
# Draw trail
882
for i, trail_pos in enumerate(particle['trail']):
883
alpha = i / len(particle['trail'])
884
color_intensity = int(255 * alpha)
885
pygame.draw.circle(screen, (color_intensity, color_intensity, 255),
886
(int(trail_pos.x), int(trail_pos.y)), 2)
887
888
pygame.display.flip()
889
clock.tick(60)
890
891
pygame.quit()
892
```