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
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
| # 使用 Pygame 的 A* 算法迷宫寻路动画(自动进行和效果添加/修正版)
import pygame
import random
import heapq
import math
import time # 为计算 dt 添加(即使没有,clock.tick 也可以)
# --- 常量 ---
# 网格设置
GRID_WIDTH = 31
GRID_HEIGHT = 25
CELL_SIZE = 15
MARGIN = 1
# 窗口大小
WINDOW_WIDTH = GRID_WIDTH * (CELL_SIZE + MARGIN) + MARGIN
WINDOW_HEIGHT = GRID_HEIGHT * (CELL_SIZE + MARGIN) + MARGIN
# 颜色 (RGB) - 更新为现代配色方案
WHITE = (245, 245, 245)
BLACK = (20, 20, 30)
GREY = (180, 180, 180)
GREEN = (76, 187, 23)
RED = (235, 64, 52)
BLUE = (66, 135, 245)
YELLOW = (250, 204, 21)
CYAN = (28, 186, 210)
ORANGE = (255, 126, 28)
LIGHT_ORANGE = (255, 183, 77) # 用于闪烁
PATH_HIGHLIGHT = (130, 210, 240) # 浅蓝色(路径显示动画用)
PATH_HIGHLIGHT_PULSE = (180, 230, 250) # 用于脉冲效果
GOAL_FLASH = (255, 255, 255) # 目标到达时效果用
HOVER_COLOR = (220, 220, 220) # 用于悬停效果
PURPLE = (180, 120, 240) # 新颜色
PINK = (255, 105, 180) # 新颜色
# 动画速度 (Frame Per Second)
FPS = 60
# 自动重置前的等待时间(秒)
RESET_DELAY_SECONDS = 2.0
# 路径高亮动画速度(每帧移动的格子数,越小越慢)
PATH_HIGHLIGHT_SPEED = 0.3
# --- 辅助函数(无更改) ---
def heuristic(a, b):
(r1, c1) = a
(r2, c2) = b
return abs(r1 - r2) + abs(c1 - c2)
def get_valid_neighbors(node, grid):
neighbors = []
row, col = node
rows = len(grid)
cols = len(grid[0])
directions = [(0, 1), (0, -1), (1, 0), (-1, 0)]
for dr, dc in directions:
nr, nc = row + dr, col + dc
if 0 <= nr < rows and 0 <= nc < cols and grid[nr][nc] == 0:
neighbors.append((nr, nc))
return neighbors
def reconstruct_path(came_from, current):
path = []
while current in came_from:
path.append(current)
current = came_from[current]
path.reverse()
return path
def generate_maze(width, height):
grid = [[1 for _ in range(width)] for _ in range(height)]
start_r, start_c = random.randrange(1, height, 2), random.randrange(1, width, 2)
grid[start_r][start_c] = 0
stack = [(start_r, start_c)]
visited = {(start_r, start_c)}
while stack:
cr, cc = stack[-1]
neighbors = []
for dr, dc in [(0, 2), (0, -2), (2, 0), (-2, 0)]:
nr, nc = cr + dr, cc + dc
if 0 < nr < height - 1 and 0 < nc < width - 1 and (nr, nc) not in visited:
neighbors.append((nr, nc))
if neighbors:
nr, nc = random.choice(neighbors)
grid[(cr + nr) // 2][(cc + nc) // 2] = 0
grid[nr][nc] = 0
visited.add((nr, nc))
stack.append((nr, nc))
else:
stack.pop()
passages = [(r, c) for r in range(height) for c in range(width) if grid[r][c] == 0]
if len(passages) < 2:
start_node = (1, 1) if height > 1 and width > 1 else (0, 0)
end_node = (height - 2, width - 2) if height > 2 and width > 2 else start_node
if grid[start_node[0]][start_node[1]] == 1:
grid[start_node[0]][start_node[1]] = 0
if grid[end_node[0]][end_node[1]] == 1:
grid[end_node[0]][end_node[1]] = 0
else:
start_node = random.choice(passages)
end_node = random.choice(passages)
while end_node == start_node:
end_node = random.choice(passages)
return grid, start_node, end_node
# 粒子类定义 - 改进以支持更多样化的效果
class Particle:
def __init__(self, x, y, color, particle_type="normal"):
self.x = x
self.y = y
self.base_color = color # 保留原始颜色
self.color = color
self.particle_type = particle_type
self.size = (
random.randint(2, 6)
if particle_type == "normal"
else random.randint(3, 8)
)
self.speed = (
random.uniform(1, 5) * 50 # 速度调整(基于 dt)
if particle_type == "normal"
else random.uniform(0.5, 3) * 50 # 速度调整(基于 dt)
)
self.angle = random.uniform(0, math.pi * 2)
self.lifespan = (
random.uniform(0.5, 1.5)
if particle_type == "normal"
else random.uniform(1.0, 2.5)
)
self.age = 0
self.pulse_rate = random.uniform(3.0, 6.0) # 用于脉冲效果
self.original_size = self.size # 用于尺寸变化
self.fade_in_duration = 0.3 # 淡入时间
self.fade_out_start_ratio = 0.7 # 从寿命的百分之多少开始淡出
# 星形粒子的顶点数
self.vertices = random.randint(4, 6) if particle_type == "star" else 0
# 用于轨迹粒子
self.trail = []
self.trail_length = 5 if particle_type == "trail" else 0
# 用于波纹效果
if particle_type == "ripple":
self.size = 1
self.max_size = random.randint(15, 25)
self.expand_speed = random.uniform(0.8, 1.2) * 30 # 速度调整(基于 dt)
self.lifespan = random.uniform(1.0, 1.5)
self.speed = 0 # 波纹不移动
def update(self, dt):
self.x += math.cos(self.angle) * self.speed * dt
self.y += math.sin(self.angle) * self.speed * dt
self.age += dt
# 根据粒子类型进行更新处理
size_decay_rate = self.original_size / (self.lifespan * (1.0 - self.fade_out_start_ratio)) if self.lifespan > 0 else 1
if self.particle_type == "normal":
if self.age >= self.lifespan * self.fade_out_start_ratio:
self.size = max(0, self.size - size_decay_rate * dt)
elif self.particle_type == "pulse":
pulse = math.sin(self.age * self.pulse_rate) * 0.5 + 0.5
current_size_factor = 1.0
if self.age >= self.lifespan * self.fade_out_start_ratio:
current_size_factor = max(0, 1 - (self.age - self.lifespan * self.fade_out_start_ratio) / (self.lifespan * (1.0 - self.fade_out_start_ratio)))
self.size = self.original_size * (0.5 + pulse * 0.5) * current_size_factor
elif self.particle_type == "fade_in":
if self.age < self.fade_in_duration:
self.size = self.original_size * (self.age / self.fade_in_duration)
elif self.age >= self.lifespan * self.fade_out_start_ratio:
fade_out_duration = self.lifespan * (1.0 - self.fade_out_start_ratio)
self.size = max(0, self.original_size * (1 - (self.age - self.lifespan * self.fade_out_start_ratio) / fade_out_duration))
else:
self.size = self.original_size # 淡入后、淡出前为最大尺寸
elif self.particle_type == "trail":
self.trail.append((self.x, self.y))
if len(self.trail) > self.trail_length:
self.trail.pop(0)
if self.age >= self.lifespan * self.fade_out_start_ratio:
self.size = max(0, self.size - size_decay_rate * dt * 0.5) # Trail 消失得稍慢一些
elif self.particle_type == "ripple":
self.size = min(self.size + self.expand_speed * dt, self.max_size)
elif self.particle_type == "star":
if self.age >= self.lifespan * self.fade_out_start_ratio:
self.size = max(0, self.size - size_decay_rate * dt)
else: # default or rainbow etc.
if self.age >= self.lifespan * self.fade_out_start_ratio:
self.size = max(0, self.size - size_decay_rate * dt)
# 颜色变化(色相随时间变化 - rainbow 类型)
if self.particle_type == "rainbow":
hue_shift = (self.age * 100) % 360
# HSV -> RGB 转换(简化版)
r_val, g_val, b_val = 0, 0, 0
i = int(hue_shift / 60) % 6
f = hue_shift / 60 - i
v = 1.0 # 亮度
s = 1.0 # 饱和度
p = v * (1 - s)
q = v * (1 - f * s)
t = v * (1 - (1 - f) * s)
if i == 0: r_val, g_val, b_val = v, t, p
elif i == 1: r_val, g_val, b_val = q, v, p
elif i == 2: r_val, g_val, b_val = p, v, t
elif i == 3: r_val, g_val, b_val = p, q, v
elif i == 4: r_val, g_val, b_val = t, p, v
elif i == 5: r_val, g_val, b_val = v, p, q
self.color = (int(r_val*255), int(g_val*255), int(b_val*255))
def draw(self, surface):
if self.size <= 0: # 如果尺寸小于等于 0 则不绘制
return
# 淡入/淡出效果的透明度计算
alpha = 255
if self.particle_type == "ripple":
# 波纹效果的透明度计算(逐渐变淡)
progress = self.age / self.lifespan if self.lifespan > 0 else 1
alpha = max(0, min(255, int(255 * (1 - progress) * 0.8))) # 结尾时更加透明
elif self.particle_type == "fade_in":
if self.age < self.fade_in_duration:
alpha = int(255 * (self.age / self.fade_in_duration))
elif self.age >= self.lifespan * self.fade_out_start_ratio:
fade_out_duration = self.lifespan * (1.0 - self.fade_out_start_ratio)
if fade_out_duration > 0:
alpha = max(0, min(255, int(255 * (1 - (self.age - self.lifespan * self.fade_out_start_ratio) / fade_out_duration))))
else:
alpha = 0 # 以防万一
else:
alpha = 255
else: # Normal, Pulse, Star, Trail, Rainbow etc.
# 通用的淡出处理
if self.age >= self.lifespan * self.fade_out_start_ratio:
fade_out_duration = self.lifespan * (1.0 - self.fade_out_start_ratio)
if fade_out_duration > 0:
alpha = max(0, min(255, int(255 * (1 - (self.age - self.lifespan * self.fade_out_start_ratio) / fade_out_duration))))
else:
alpha = 0
else:
alpha = 255
# 颜色验证和设置
try:
current_color = self.color if self.particle_type == "rainbow" else self.base_color
if isinstance(current_color, tuple) and len(current_color) == 3:
r = max(0, min(255, int(current_color[0])))
g = max(0, min(255, int(current_color[1])))
b = max(0, min(255, int(current_color[2])))
final_color = (r, g, b, alpha)
else:
final_color = (255, 255, 255, alpha) # 默认颜色
# 根据粒子类型绘制
if self.particle_type == "ripple":
# 波纹效果(绘制轮廓)
line_width = max(1, int(self.max_size / 15 * (1 - self.age / self.lifespan))) # 逐渐变细的轮廓
if self.size >= 1: # 最小半径大于等于 1
pygame.draw.circle(surface, final_color, (int(self.x), int(self.y)), int(self.size), width=line_width)
elif self.particle_type == "star" and self.vertices > 0:
# 星形粒子
points = []
outer_radius = self.size
inner_radius = self.size * 0.4
for i in range(self.vertices * 2):
angle = math.pi / self.vertices * i - math.pi / 2 # 调整使顶点朝上
radius = outer_radius if i % 2 == 0 else inner_radius
x_p = self.x + math.cos(angle) * radius
y_p = self.y + math.sin(angle) * radius
points.append((x_p, y_p))
if len(points) >= 3: # 至少需要 3 个点
pygame.draw.polygon(surface, final_color, points)
elif self.particle_type == "trail" and len(self.trail) > 1:
# 轨迹粒子
for i in range(len(self.trail) - 1):
start_pos = self.trail[i]
end_pos = self.trail[i + 1]
# 调整轨迹的 alpha 和宽度
trail_alpha = alpha * ((i + 1) / len(self.trail))**2 # 越往后越淡
trail_width = max(1, int(self.size * ((i + 1) / len(self.trail))))
trail_color_tuple = (final_color[0], final_color[1], final_color[2], int(trail_alpha))
pygame.draw.line(surface, trail_color_tuple, start_pos, end_pos, trail_width)
# 也绘制前端的圆
pygame.draw.circle(surface, final_color, (int(self.x), int(self.y)), int(self.size))
else:
# 普通圆形粒子 (Normal, Pulse, Fade_in, Rainbow)
pygame.draw.circle(surface, final_color, (int(self.x), int(self.y)), int(self.size))
except (ValueError, TypeError) as e:
# 如果发生错误,则使用默认颜色
print(f"Error drawing particle: {e}, color={self.color}, alpha={alpha}, size={self.size}")
try:
safe_color = (255, 255, 255, alpha)
if self.size >= 1:
pygame.draw.circle(surface, safe_color, (int(self.x), int(self.y)), int(max(1, self.size))) # 保证最小尺寸为 1
except Exception as final_e:
print(f"Final fallback drawing failed: {final_e}")
# --- Pygame 初始化 ---
pygame.init()
screen = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))
pygame.display.set_caption("A* Maze Solver Animation (Auto-Repeat, ESC: Quit)")
clock = pygame.time.Clock()
font = pygame.font.Font(None, 24)
# --- 状态变量 ---
grid = []
start_node = None
end_node = None
open_set_heap = []
open_set_map = {}
closed_set = set()
came_from = {}
g_score = {}
path = []
current_node = None
solving = False
maze_generated = False
message = ""
particles = [] # 粒子列表(ripples 也合并到这里)
# ripples = [] # 因为不再需要而删除
node_pulses = [] # 用于节点搜索时的脉冲效果(目前可能未使用?)
# --- 自动重置用变量 ---
reset_timer = 0 # 等待帧计数器
RESET_DELAY_FRAMES = int(RESET_DELAY_SECONDS * FPS) # 将秒转换为帧数
start_reset_timer_after_highlight = False # 高亮完成后启动计时器的标志
# --- 路径高亮动画用变量 ---
path_highlight_index = 0.0 # 使用 float 缓慢前进
highlighting_path = False
# --- 目标到达效果用 ---
goal_reached_flash = False # 是否是到达目标后的那一帧
# --- 主循环 ---
running = True
frame_count = 0 # 用于闪烁动画
hover_cell = None # 悬停中的单元格
while running:
# --- Delta Time 计算 ---
dt = clock.tick(FPS) / 1000.0 # 以秒为单位的 delta time(避免除以 0)
if dt == 0: dt = 1 / FPS # 保证最小时间步长
# --- 事件处理 ---
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
running = False
# 从鼠标坐标获取悬停中的单元格
mouse_pos = pygame.mouse.get_pos()
mouse_col = mouse_pos[0] // (CELL_SIZE + MARGIN)
mouse_row = mouse_pos[1] // (CELL_SIZE + MARGIN)
if 0 <= mouse_row < GRID_HEIGHT and 0 <= mouse_col < GRID_WIDTH:
hover_cell = (mouse_row, mouse_col)
else:
hover_cell = None
# --- 状态更新 ---
if not maze_generated:
# 重置等待计时器
reset_timer = 0
start_reset_timer_after_highlight = False
highlighting_path = False
path_highlight_index = 0.0
goal_reached_flash = False
hover_cell = None
particles = [] # 清除现有粒子
message = "正在生成新迷宫..."
screen.fill(BLACK)
msg_render = font.render(message, True, WHITE)
screen.blit(msg_render, (10, WINDOW_HEIGHT // 2 - 10))
pygame.display.flip()
grid, start_node, end_node = generate_maze(GRID_WIDTH, GRID_HEIGHT)
open_set_heap = []
open_set_map = {}
closed_set = set()
came_from = {}
path = []
current_node = None
g_score = {
(r, c): float("inf") for r in range(GRID_HEIGHT) for c in range(GRID_WIDTH)
}
if start_node: # 确认 start_node 不为 None
g_score[start_node] = 0
h_start = heuristic(start_node, end_node) if end_node else 0
f_start = g_score[start_node] + h_start
heapq.heappush(open_set_heap, (f_start, h_start, start_node))
open_set_map[start_node] = (f_start, h_start)
maze_generated = True
solving = True if start_node and end_node else False # 如果没有开始/结束节点则不求解
message = "正在求解..." if solving else "迷宫已生成(没有起点/终点?)"
# --- A* 算法的步进执行 ---
if solving and open_set_heap:
current_f, current_h, current_node_popped = heapq.heappop(open_set_heap)
# 如果已从 open_set_map 中删除,或者之后找到了更好的路径,则跳过
if current_node_popped not in open_set_map or open_set_map[current_node_popped] > (current_f, current_h):
pass # 忽略并进入下一个循环
else:
# 因为要处理,所以从 open_set_map 中删除(可能再次添加)
# 因为在从 heapq 弹出时就作为处理对象,所以 del 可能不需要。重复检查在上面的 if 中进行。
# del open_set_map[current_node_popped] # 此处的删除可能不需要
current_node = current_node_popped
if current_node == end_node:
path = reconstruct_path(came_from, current_node)
solving = False
message = "已到达目标!正在高亮路径..."
current_node = None
highlighting_path = True
path_highlight_index = 0.0
goal_reached_flash = True # 打开效果生成标志
start_reset_timer_after_highlight = True
else:
closed_set.add(current_node)
# 确认从 open_set_map 中删除(因为它已进入 closed)
if current_node in open_set_map:
del open_set_map[current_node]
# 为正在搜索的节点添加波纹效果
node_x = (current_node[1] * (CELL_SIZE + MARGIN)) + MARGIN + CELL_SIZE // 2
node_y = (current_node[0] * (CELL_SIZE + MARGIN)) + MARGIN + CELL_SIZE // 2
# 生成波纹效果(使用 Particle 类)
particles.append(Particle(node_x, node_y, YELLOW, "ripple")) # 将颜色更改为 YELLOW
# 少量生成小粒子(搜索时)
if random.random() < 0.1: # 稍微降低概率
for _ in range(1): # 减少数量
color = random.choice([YELLOW, ORANGE]) # 使颜色与搜索颜色匹配
particles.append(Particle(node_x, node_y, color, "fade_in"))
for neighbor in get_valid_neighbors(current_node, grid):
if neighbor in closed_set:
continue
tentative_g_score = g_score[current_node] + 1
# 如果此路径不如现有路径,或者 open set 中已有更好的路径,则忽略
# 注意:open_set_map 中存储的是 (f, h)
neighbor_in_open = open_set_map.get(neighbor)
if neighbor_in_open and tentative_g_score >= g_score.get(neighbor, float('inf')):
continue
# 如果找到更好的路径,或者首次访问
came_from[neighbor] = current_node
g_score[neighbor] = tentative_g_score
h_neighbor = heuristic(neighbor, end_node)
f_neighbor = tentative_g_score + h_neighbor
# 如果不在 open_set 中则添加,如果存在则更新(heapq 不直接支持更新,因此添加新元素)
heapq.heappush(open_set_heap, (f_neighbor, h_neighbor, neighbor))
open_set_map[neighbor] = (f_neighbor, h_neighbor) # 保存 f, h
elif solving and not open_set_heap:
solving = False
message = f"未找到路径!将在 {RESET_DELAY_SECONDS:.1f} 秒后重置..."
current_node = None
reset_timer = RESET_DELAY_FRAMES # 搜索失败时立即启动计时器
# --- 路径高亮处理 ---
if highlighting_path and path:
if path_highlight_index < len(path):
path_highlight_index += PATH_HIGHLIGHT_SPEED * FPS * dt # 使用 dt 调整速度
# 完成瞬间的处理
if path_highlight_index >= len(path):
path_highlight_index = len(path)
if start_reset_timer_after_highlight:
reset_timer = RESET_DELAY_FRAMES
message = f"路径完成!将在 {RESET_DELAY_SECONDS:.1f} 秒后重置..."
start_reset_timer_after_highlight = False
# --- 自动重置计时器处理 ---
if reset_timer > 0:
reset_timer -= 1 # 基于帧进行倒计时
remaining_time = reset_timer / FPS # 转换为秒并显示
if not solving and not path:
message = f"未找到路径!将在 {remaining_time:.1f} 秒后重置..."
elif not solving and path and path_highlight_index >= len(path):
message = f"路径完成!将在 {remaining_time:.1f} 秒后重置..."
if reset_timer <= 0:
maze_generated = False
# --- 绘制处理 ---
# 渐变背景
for y in range(WINDOW_HEIGHT):
time_factor = math.sin(frame_count * 0.005) * 0.2
r_base = 30 + int(10 * time_factor)
g_base = 40 + int(15 * time_factor)
b_base = 60 + int(20 * time_factor)
gradient_factor = math.sin(math.pi * y / WINDOW_HEIGHT)
r = int(r_base + (50 - r_base) * gradient_factor) # 稍微调暗
g = int(g_base + (70 - g_base) * gradient_factor) # 稍微调暗
b = int(b_base + (90 - b_base) * gradient_factor) # 稍微调暗
pygame.draw.line(screen, (max(0,r), max(0,g), max(0,b)), (0, y), (WINDOW_WIDTH, y))
# 提升单元格质感(阴影和光泽)- 这部分保持原样也可以
shadow_surface = pygame.Surface((WINDOW_WIDTH, WINDOW_HEIGHT), pygame.SRCALPHA)
for row in range(GRID_HEIGHT):
for col in range(GRID_WIDTH):
rect = pygame.Rect(
(MARGIN + CELL_SIZE) * col + MARGIN,
(MARGIN + CELL_SIZE) * row + MARGIN,
CELL_SIZE,
CELL_SIZE,
)
if grid[row][col] == 0: # 通道
pygame.draw.rect(shadow_surface, (0, 0, 0, 30), rect.inflate(1, 1), border_radius=3) # 稍微浅一点的阴影
light_rect = rect.inflate(-3, -3).move(-1, -1)
pygame.draw.rect(shadow_surface, (255, 255, 255, 50), light_rect, border_radius=2) # 光泽也稍微低调一点
else: # 墙壁
pygame.draw.rect(shadow_surface, (0, 0, 0, 20), rect.inflate(1, 1), border_radius=2)
pygame.draw.rect(shadow_surface, (0, 0, 0, 30), rect.inflate(-2, -2), border_radius=1, width=1) # 内部阴影
screen.blit(shadow_surface, (0, 0))
# 到达目标时的粒子生成
if goal_reached_flash:
goal_x = (end_node[1] * (CELL_SIZE + MARGIN)) + MARGIN + CELL_SIZE // 2
goal_y = (end_node[0] * (CELL_SIZE + MARGIN)) + MARGIN + CELL_SIZE // 2
# 生成多种粒子类型
for _ in range(40): # 增加普通粒子数量
color = random.choice([RED, YELLOW, ORANGE, BLUE, GREEN, PURPLE, PINK, WHITE])
particles.append(Particle(goal_x, goal_y, color, "normal"))
for _ in range(15): # 增加星形粒子数量
color = random.choice([YELLOW, WHITE, ORANGE, CYAN])
particles.append(Particle(goal_x, goal_y, color, "star"))
for _ in range(10): # 脉冲效果
color = random.choice([CYAN, PURPLE, PINK, BLUE])
particles.append(Particle(goal_x, goal_y, color, "pulse"))
for _ in range(8): # 轨迹粒子
color = random.choice([BLUE, CYAN, WHITE, GREEN])
particles.append(Particle(goal_x, goal_y, color, "trail"))
for _ in range(10): # 彩虹粒子
particles.append(Particle(goal_x, goal_y, WHITE, "rainbow")) # 初始颜色为白色即可
for _ in range(6): # 波纹效果也作为 Particle 生成
color = random.choice([WHITE, CYAN, BLUE, YELLOW]) # 波纹颜色
particles.append(Particle(goal_x, goal_y, color, "ripple")) # 以 ripple 类型生成
goal_reached_flash = False # ★★★ 粒子生成后立即重置标志 ★★★
# 单元格绘制循环
for row in range(GRID_HEIGHT):
for col in range(GRID_WIDTH):
color = WHITE
if grid[row][col] == 1:
color = BLACK
node = (row, col)
is_path_node = False # 是否为路径高亮对象的标志
# --- 根据单元格状态设置颜色 ---
if node in closed_set:
# closed 列表的颜色(已搜索)- 稍暗的 CYAN
color = (20, 140, 160)
# 存在于 open_set_map 中的节点(搜索候选)- 稍暗的 YELLOW
# 即使 heapq 中有多个相同节点,open_set_map 中也应该包含最新的 (f, h)
if node in open_set_map:
color = (200, 160, 10) # 稍暗的 YELLOW
# --- 路径高亮 ---
if highlighting_path and path:
current_path_segment_index = int(path_highlight_index)
if node in path[:current_path_segment_index]:
is_path_node = True
pulse_factor = math.sin(frame_count * 0.15 + path.index(node) * 0.1) * 0.5 + 0.5 # 根据节点位置进行相位偏移
r = int(PATH_HIGHLIGHT[0] + (PATH_HIGHLIGHT_PULSE[0] - PATH_HIGHLIGHT[0]) * pulse_factor)
g = int(PATH_HIGHLIGHT[1] + (PATH_HIGHLIGHT_PULSE[1] - PATH_HIGHLIGHT[1]) * pulse_factor)
b = int(PATH_HIGHLIGHT[2] + (PATH_HIGHLIGHT_PULSE[2] - PATH_HIGHLIGHT[2]) * pulse_factor)
color = (r, g, b)
# 前端节点的效果
if current_path_segment_index < len(path) and node == path[current_path_segment_index - 1]:
if (frame_count // 4) % 2 == 0: # 调整闪烁速度
color = PATH_HIGHLIGHT_PULSE
# 在前端生成粒子(低概率)
if random.random() < 0.15: # 稍微提高概率
x = (node[1] * (CELL_SIZE + MARGIN)) + MARGIN + CELL_SIZE // 2
y = (node[0] * (CELL_SIZE + MARGIN)) + MARGIN + CELL_SIZE // 2
particles.append(Particle(x, y, PATH_HIGHLIGHT_PULSE, "fade_in")) # 匹配颜色
# --- 当前搜索中的节点 ---
if solving and node == current_node:
# 闪烁效果
if (frame_count // 8) % 2 == 0: # 调整闪烁速度
color = LIGHT_ORANGE
else:
color = ORANGE
# --- 开始和目标 ---
if node == start_node:
color = GREEN
elif node == end_node:
# 到达目标后的闪光不由 goal_reached_flash 标志管理,
# 也可以考虑其他方法,例如仅在 highlighting_path 变为 True 的最初几帧调亮
# 目前保持简单的 RED
color = RED
# --- 单元格绘制 ---
rect = pygame.Rect(
(MARGIN + CELL_SIZE) * col + MARGIN,
(MARGIN + CELL_SIZE) * row + MARGIN,
CELL_SIZE,
CELL_SIZE,
)
pygame.draw.rect(screen, color, rect, border_radius=3)
# --- 光泽和悬停效果 ---
is_floor_like = (grid[row][col] == 0 or node == start_node or node == end_node or node in open_set_map or node in closed_set or is_path_node)
if is_floor_like:
# 光泽
highlight_rect = rect.copy()
highlight_rect.height = max(1, CELL_SIZE // 4) # 稍微小一点
highlight_color = (min(255, color[0] + 40), min(255, color[1] + 40), min(255, color[2] + 40))
pygame.draw.rect(screen, highlight_color, highlight_rect, border_top_left_radius=3, border_top_right_radius=3)
# 悬停
if hover_cell == node:
hover_rect = rect.inflate(-1, -1) # 避免与边框重叠
hover_color = HOVER_COLOR # 固定颜色可能更容易理解
# pygame.draw.rect(screen, hover_color, hover_rect, border_radius=2) # 填充
pygame.draw.rect(screen, hover_color, hover_rect, width=1, border_radius=2) # 用边框显示
# --- 边界线 ---
border_color = (max(0, color[0] - 50), max(0, color[1] - 50), max(0, color[2] - 50)) # 更暗
pygame.draw.rect(screen, border_color, rect, 1, border_radius=3)
frame_count += 1 # 在这里递增 frame_count
# --- 粒子更新和绘制 ---
active_particles = []
for p in particles:
p.update(dt) # 传递 dt 进行更新
# 根据寿命和尺寸(或波纹达到最大尺寸)判断生存
is_alive = p.age < p.lifespan
if p.particle_type == "ripple":
# 波纹寿命结束后消失(即使达到 max_size 也会继续移动)
pass
else:
# 普通粒子尺寸变为 0 后消失
is_alive = is_alive and p.size > 0
if is_alive:
active_particles.append(p)
particles = active_particles # 只保留有效的粒子
# 创建用于粒子绘制的透明 Surface
# 使用 SRCALPHA 可以正确处理每个粒子的 alpha 值(透明度)
particle_surface = pygame.Surface((WINDOW_WIDTH, WINDOW_HEIGHT), pygame.SRCALPHA)
for p in particles:
p.draw(particle_surface) # 在透明 Surface 上绘制
# 在 screen(已绘制背景和单元格)上叠加绘制 particle_surface
screen.blit(particle_surface, (0, 0))
# --- 消息显示 ---
if message:
text_color = WHITE
stroke_color = BLACK
msg_render = font.render(message, True, text_color)
# 描边绘制
for dx, dy in [(-1,-1), (-1,1), (1,-1), (1,1), (-1,0), (1,0), (0,-1), (0,1)]:
stroke_render = font.render(message, True, stroke_color)
screen.blit(stroke_render, (10 + dx, WINDOW_HEIGHT - 25 + dy))
# 主体文本绘制
screen.blit(msg_render, (10, WINDOW_HEIGHT - 25))
# --- 画面更新 ---
pygame.display.flip()
# goal_reached_flash 的重置移至粒子生成后立即进行
# --- 结束处理 ---
pygame.quit()
|