mirror of
https://github.com/kuhyx/praca_magisterska.git
synced 2026-07-04 21:43:07 +02:00
189 lines
6.2 KiB
Python
189 lines
6.2 KiB
Python
|
|
#!/usr/bin/env python3
|
||
|
|
"""
|
||
|
|
Pygame GUI for shortest-path algorithm visualization.
|
||
|
|
|
||
|
|
Controls:
|
||
|
|
1 / 2 / 3 — Dijkstra / Bellman-Ford / A*
|
||
|
|
SPACE — advance one step
|
||
|
|
R — reset
|
||
|
|
Q / ESC — quit
|
||
|
|
"""
|
||
|
|
|
||
|
|
import math
|
||
|
|
import sys
|
||
|
|
import pygame
|
||
|
|
|
||
|
|
from algorithms import (
|
||
|
|
NODES, EDGES, GOAL, INF, AlgoState, heuristic,
|
||
|
|
dijkstra_init, dijkstra_step,
|
||
|
|
bellman_ford_init, bellman_ford_step,
|
||
|
|
astar_init, astar_step,
|
||
|
|
)
|
||
|
|
|
||
|
|
# ─── Constants ───────────────────────────────────────────────────────────
|
||
|
|
WIDTH, HEIGHT = 1100, 700
|
||
|
|
NODE_RADIUS = 28
|
||
|
|
FONT_SIZE = 18
|
||
|
|
|
||
|
|
# Colours
|
||
|
|
WHITE = (255, 255, 255)
|
||
|
|
BLACK = (0, 0, 0)
|
||
|
|
LIGHT_GREY = (230, 230, 230)
|
||
|
|
DARK_GREY = (100, 100, 100)
|
||
|
|
BLUE = (60, 100, 220)
|
||
|
|
ORANGE = (240, 140, 40)
|
||
|
|
VISITED_COL = (180, 220, 255)
|
||
|
|
PATH_COL = (60, 200, 120)
|
||
|
|
CURRENT_COL = (255, 220, 80)
|
||
|
|
EDGE_DEFAULT= (160, 160, 160)
|
||
|
|
EDGE_RELAXED= (60, 160, 60)
|
||
|
|
EDGE_PATH = (40, 180, 100)
|
||
|
|
BG = (245, 245, 250)
|
||
|
|
|
||
|
|
ALGO_NAMES = ["Dijkstra's Algorithm", "Bellman-Ford Algorithm", "A* Algorithm"]
|
||
|
|
ALGOS = [
|
||
|
|
(dijkstra_init, dijkstra_step),
|
||
|
|
(bellman_ford_init, bellman_ford_step),
|
||
|
|
(astar_init, astar_step),
|
||
|
|
]
|
||
|
|
|
||
|
|
|
||
|
|
# ─── Drawing ─────────────────────────────────────────────────────────────
|
||
|
|
|
||
|
|
def draw_edge(screen: pygame.Surface, p1: tuple, p2: tuple,
|
||
|
|
colour: tuple, width: int = 2) -> None:
|
||
|
|
dx, dy = p2[0] - p1[0], p2[1] - p1[1]
|
||
|
|
length = math.hypot(dx, dy)
|
||
|
|
if length == 0:
|
||
|
|
return
|
||
|
|
ux, uy = dx / length, dy / length
|
||
|
|
start = (p1[0] + ux * NODE_RADIUS, p1[1] + uy * NODE_RADIUS)
|
||
|
|
end = (p2[0] - ux * NODE_RADIUS, p2[1] - uy * NODE_RADIUS)
|
||
|
|
pygame.draw.line(screen, colour, start, end, width)
|
||
|
|
|
||
|
|
|
||
|
|
def render(screen: pygame.Surface, font: pygame.font.Font,
|
||
|
|
small_font: pygame.font.Font, state: AlgoState,
|
||
|
|
algo_idx: int) -> None:
|
||
|
|
screen.fill(BG)
|
||
|
|
|
||
|
|
# Title
|
||
|
|
title_surf = font.render(ALGO_NAMES[algo_idx], True, BLACK)
|
||
|
|
screen.blit(title_surf, (WIDTH // 2 - title_surf.get_width() // 2, 10))
|
||
|
|
|
||
|
|
# Path edge set
|
||
|
|
path_edges: set[tuple[str, str]] = set()
|
||
|
|
for i in range(len(state.path) - 1):
|
||
|
|
a, b = state.path[i], state.path[i + 1]
|
||
|
|
path_edges.add((a, b))
|
||
|
|
path_edges.add((b, a))
|
||
|
|
|
||
|
|
# Edges
|
||
|
|
for u, v, w in EDGES:
|
||
|
|
p1, p2 = NODES[u], NODES[v]
|
||
|
|
if (u, v) in path_edges:
|
||
|
|
col, wd = EDGE_PATH, 5
|
||
|
|
elif (u, v) in state.relaxed_edges or (v, u) in state.relaxed_edges:
|
||
|
|
col, wd = EDGE_RELAXED, 3
|
||
|
|
else:
|
||
|
|
col, wd = EDGE_DEFAULT, 2
|
||
|
|
draw_edge(screen, p1, p2, col, wd)
|
||
|
|
|
||
|
|
# Weight label
|
||
|
|
mx = (p1[0] + p2[0]) // 2
|
||
|
|
my = (p1[1] + p2[1]) // 2
|
||
|
|
dx, dy = p2[0] - p1[0], p2[1] - p1[1]
|
||
|
|
length = math.hypot(dx, dy)
|
||
|
|
if length > 0:
|
||
|
|
mx += int(-dy / length * 14)
|
||
|
|
my += int(dx / length * 14)
|
||
|
|
wt = small_font.render(str(w), True, DARK_GREY)
|
||
|
|
screen.blit(wt, (mx - wt.get_width() // 2, my - wt.get_height() // 2))
|
||
|
|
|
||
|
|
# Nodes
|
||
|
|
for name, (x, y) in NODES.items():
|
||
|
|
if name in state.path and state.finished:
|
||
|
|
col = PATH_COL
|
||
|
|
elif name == state.current_node:
|
||
|
|
col = CURRENT_COL
|
||
|
|
elif name in state.visited:
|
||
|
|
col = VISITED_COL
|
||
|
|
else:
|
||
|
|
col = WHITE
|
||
|
|
|
||
|
|
pygame.draw.circle(screen, col, (x, y), NODE_RADIUS)
|
||
|
|
pygame.draw.circle(screen, BLACK, (x, y), NODE_RADIUS, 2)
|
||
|
|
|
||
|
|
# Name
|
||
|
|
label = font.render(name, True, BLACK)
|
||
|
|
screen.blit(label, (x - label.get_width() // 2,
|
||
|
|
y - label.get_height() // 2 - 8))
|
||
|
|
|
||
|
|
# Distance
|
||
|
|
d = state.dist.get(name, INF)
|
||
|
|
d_text = "inf" if d == INF else f"{d:.0f}"
|
||
|
|
d_surf = small_font.render(f"d={d_text}", True, BLUE)
|
||
|
|
screen.blit(d_surf, (x - d_surf.get_width() // 2, y + 8))
|
||
|
|
|
||
|
|
# h(n) for A*
|
||
|
|
if algo_idx == 2:
|
||
|
|
h_surf = small_font.render(f"h={heuristic(name):.1f}", True, ORANGE)
|
||
|
|
screen.blit(h_surf, (x + NODE_RADIUS + 4, y - 8))
|
||
|
|
|
||
|
|
# Info bar
|
||
|
|
pygame.draw.rect(screen, LIGHT_GREY, (0, HEIGHT - 80, WIDTH, 80))
|
||
|
|
pygame.draw.line(screen, DARK_GREY, (0, HEIGHT - 80), (WIDTH, HEIGHT - 80), 2)
|
||
|
|
|
||
|
|
info = font.render(state.message, True, BLACK)
|
||
|
|
screen.blit(info, (20, HEIGHT - 70))
|
||
|
|
|
||
|
|
hint = small_font.render(
|
||
|
|
"[1] Dijkstra [2] Bellman-Ford [3] A* "
|
||
|
|
"[SPACE] Step [R] Reset [Q] Quit", True, DARK_GREY)
|
||
|
|
screen.blit(hint, (20, HEIGHT - 35))
|
||
|
|
|
||
|
|
pygame.display.flip()
|
||
|
|
|
||
|
|
|
||
|
|
# ─── Main ────────────────────────────────────────────────────────────────
|
||
|
|
|
||
|
|
def main() -> None:
|
||
|
|
pygame.init()
|
||
|
|
screen = pygame.display.set_mode((WIDTH, HEIGHT))
|
||
|
|
pygame.display.set_caption("Shortest Path Visualizer — Dijkstra / Bellman-Ford / A*")
|
||
|
|
font = pygame.font.SysFont("DejaVu Sans", FONT_SIZE, bold=True)
|
||
|
|
small_font = pygame.font.SysFont("DejaVu Sans", FONT_SIZE - 4)
|
||
|
|
clock = pygame.time.Clock()
|
||
|
|
|
||
|
|
current = 0
|
||
|
|
state = ALGOS[current][0]()
|
||
|
|
|
||
|
|
running = True
|
||
|
|
while running:
|
||
|
|
for event in pygame.event.get():
|
||
|
|
if event.type == pygame.QUIT:
|
||
|
|
running = False
|
||
|
|
elif event.type == pygame.KEYDOWN:
|
||
|
|
if event.key in (pygame.K_q, pygame.K_ESCAPE):
|
||
|
|
running = False
|
||
|
|
elif event.key == pygame.K_1:
|
||
|
|
current = 0; state = ALGOS[current][0]()
|
||
|
|
elif event.key == pygame.K_2:
|
||
|
|
current = 1; state = ALGOS[current][0]()
|
||
|
|
elif event.key == pygame.K_3:
|
||
|
|
current = 2; state = ALGOS[current][0]()
|
||
|
|
elif event.key == pygame.K_r:
|
||
|
|
state = ALGOS[current][0]()
|
||
|
|
elif event.key == pygame.K_SPACE:
|
||
|
|
if not state.finished:
|
||
|
|
ALGOS[current][1](state)
|
||
|
|
|
||
|
|
render(screen, font, small_font, state, current)
|
||
|
|
clock.tick(30)
|
||
|
|
|
||
|
|
pygame.quit()
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
main()
|