-
Notifications
You must be signed in to change notification settings - Fork 0
/
logic.py
184 lines (146 loc) · 7.01 KB
/
logic.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
import numpy.typing as npt
from constants import *
import physics
from random import shuffle, randint, choice
import pygame
def safe_access_grid(grid: npt.NDArray[int], seg_x: int, seg_y: int, decrement: bool = False) -> int:
"""Safely access given segment of given grid.
Returns the value at the segment if valid segment is specified.
Returns -1 if the specified segment is out of bounds, and part of the top, left or right wall.
Returns 0 if the specified segment is out of bounds, and part of the baseline."""
if 0 <= seg_x < DIM_X and 0 <= seg_y < DIM_Y:
if decrement:
assert grid[seg_y][seg_x] > 0
grid[seg_y][seg_x] -= 1
return grid[seg_y][seg_x]
else:
if seg_y < DIM_Y:
return -1
else:
return 0
def get_surroundings(grid: npt.NDArray[int], seg_x: int, seg_y: int, x_flip: bool, y_flip: bool)\
-> tuple[int, int, int]:
"""Check the left, top, and diagonal segments and return grid values.
X and Y axes may be flipped."""
dx = 1 if x_flip else -1
dy = 1 if y_flip else -1
left = safe_access_grid(grid, seg_x + dx, seg_y)
diag = safe_access_grid(grid, seg_x + dx, seg_y + dy)
top = safe_access_grid(grid, seg_x, seg_y + dy)
return left, diag, top
def decrement_bricks(grid: npt.NDArray[int], seg_x: int, seg_y: int, x_flip: bool, y_flip: bool,
dec_left: bool, dec_diag: bool, dec_top: bool) -> None:
"""Decrement the left, top, and diagonal segments by 1 or 0."""
dx = 1 if x_flip else -1
dy = 1 if y_flip else -1
safe_access_grid(grid, seg_x + dx, seg_y, dec_left)
safe_access_grid(grid, seg_x + dx, seg_y + dy, dec_diag)
safe_access_grid(grid, seg_x, seg_y + dy, dec_top)
def get_rel_values(position: npt.NDArray[float], vector: npt.NDArray[float], x_flip: bool, y_flip: bool) \
-> tuple[npt.NDArray[float], npt.NDArray[float]]:
"""Flip the position and velocity of ball within its segment."""
if x_flip:
if y_flip:
rel_pos = RELPOS_XY + position * FLIP_XY
rel_vec = vector * FLIP_XY
else:
rel_pos = RELPOS_X + position * FLIP_X
rel_vec = vector * FLIP_X
else:
if y_flip:
rel_pos = RELPOS_Y + position * FLIP_Y
rel_vec = vector * FLIP_Y
else:
rel_pos = position
rel_vec = vector
return rel_pos, rel_vec
def brick_color(value: int) -> npt.NDArray[int]:
"""Given the value of a brick, return its RGB value."""
_full_red = 30
_full_blue = 40
assert _full_blue > _full_red
redness = min(value / _full_red, 1)
blueness = min((value - _full_red) / _full_blue, 1)
return np.clip(np.array([230 - blueness * 200, 230 - redness * 230, blueness * 70]).astype(int), 0, 255)
def gridpos_to_coordinates(i: int, j: int) -> tuple[int, int]:
"""Given the coordinate of a grid position, return the pixel coordinates."""
x, y = j * WIDTH, i * HEIGHT
return x, y
def draw_bricks(screen: pygame.Surface, grid: npt.NDArray[int], font: pygame.font.Font) -> None:
"""Draw the bricks onto the screen."""
for i in range(len(grid)):
line = grid[i]
for j in range(len(line)):
value = line[j]
if value:
x, y = gridpos_to_coordinates(i, j)
pygame.draw.rect(screen, brick_color(value), (x, y, WIDTH, HEIGHT))
pygame.draw.rect(screen, WHITE, (x, y, WIDTH, HEIGHT), width=BORDER)
value_text = font.render(str(value), True, WHITE)
text_rect = value_text.get_rect(center=(x + WIDTH // 2, y + HEIGHT // 2))
screen.blit(value_text, text_rect)
def draw_points(screen: pygame.Surface, points: npt.NDArray[int]) -> None:
"""Draw the points onto the screen."""
for i in range(len(points)):
line = points[i]
for j in range(len(line)):
if line[j]:
x, y = gridpos_to_coordinates(i, j)
pygame.draw.circle(screen, GREEN, (x + WIDTH // 2, y + HEIGHT // 2), RADIUS)
def rand_gen(grid: npt.NDArray[int], points: npt.NDArray[int], n: int) -> None:
"""Generate the bricks and points, given parameter n."""
max_bricks = min(n // 10 + 2, DIM_X - 1)
min_bricks = max(max_bricks - 3, 1)
brick_count = randint(min_bricks, max_bricks)
assert all(grid[0][i] == 0 for i in range(DIM_X))
assert all(points[0][i] == 0 for i in range(DIM_X))
placement = [n] * brick_count + [0] * (DIM_X - brick_count)
shuffle(placement)
grid[0] = placement
empties = [i for i in range(len(placement)) if placement[i] == 0]
point_idx = choice(empties)
points[0][point_idx] = 1
def shift_down(grid: npt.NDArray[int], points: npt.NDArray[int]) -> tuple[bool, int]:
"""Shift down the grid.
Returns a tuple, where the first value is whether a brick has reached the floor,
and the second value is the number of points that have reached the floor.
"""
assert all(grid[DIM_Y - 1][i] == 0 for i in range(DIM_X))
for i in range(DIM_Y - 1, 0, -1):
grid[i] = grid[i - 1]
grid[0] = [0] * DIM_X
for i in range(DIM_Y - 1, 0, -1):
points[i] = points[i - 1]
points[0] = [0] * DIM_X
taken_points = sum(points[DIM_Y - 1])
points[DIM_Y - 1] = [0] * DIM_X
return any(grid[DIM_Y - 1]), taken_points
def draw_arrow(screen: pygame.Surface, color: tuple[int, int, int],
start: npt.NDArray[float], end: npt.NDArray[float],
trirad: int = ARROW_HEAD_RADIUS, thickness: int = ARROW_THICKNESS) -> None:
"""Draw an arrow from given start to given end."""
lcolor = color
tricolor = color
rad = np.pi / 180
pygame.draw.line(screen, lcolor, start, end, thickness)
rotation = np.arctan2(start[1] - end[1], end[0] - start[0]) + np.pi / 2
pygame.draw.polygon(screen, tricolor,
((end[0] + trirad * np.sin(rotation),
end[1] + trirad * np.cos(rotation)),
(end[0] + trirad * np.sin(rotation - 120 * rad),
end[1] + trirad * np.cos(rotation - 120 * rad)),
(end[0] + trirad * np.sin(rotation + 120 * rad),
end[1] + trirad * np.cos(rotation + 120 * rad))))
def draw_arrow_modified(screen: pygame.Surface, color: tuple[int, int, int],
start: npt.NDArray[float], vector: npt.NDArray[float], max_length: int) -> None:
"""Draw an arrow starting from start, clipped by length, in vector direction."""
if not any(vector):
return
vector *= min(max_length / physics.length(vector), 1)
end = start + vector
draw_arrow(screen, color, start, end)
def clipped_direction(vector: npt.NDArray[float]) -> npt.NDArray[float]:
"""Given a vector, clip the angle it makes with the x-axis."""
angle = np.arctan2(vector[1], vector[0]) + 2 * np.pi # [pi, 2pi], increasing clockwise
target_angle = min(ANGLE_MAX_RAD, max(ANGLE_MIN_RAD, angle))
return physics.rotate(vector, target_angle - angle)