praca_magisterska/code/python/visualizer.py

189 lines
6.2 KiB
Python
Raw Normal View History

2026-03-08 21:29:46 +01:00
#!/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()