feat: initial maze-ml GA visualizer

This commit is contained in:
2026-06-13 14:50:50 +02:00
commit 94b62a67d5
13 changed files with 580 additions and 0 deletions

108
maze.py Normal file
View File

@@ -0,0 +1,108 @@
import numpy as np
import random
NORTH, SOUTH, EAST, WEST = 0, 1, 2, 3
_DX = [0, 0, 1, -1] # col delta for N,S,E,W
_DY = [-1, 1, 0, 0] # row delta
_OPP = [1, 0, 3, 2] # opposite direction
def generate(rows: int, cols: int, seed: int | None = None) -> np.ndarray:
rng = random.Random(seed)
grid = np.full((rows, cols), 0b1111, dtype=np.uint8)
visited = np.zeros((rows, cols), dtype=bool)
start_r, start_c = 0, 0
visited[start_r, start_c] = True
stack = [(start_r, start_c)]
while stack:
row, col = stack[-1]
directions = [NORTH, SOUTH, EAST, WEST]
rng.shuffle(directions)
moved = False
for d in directions:
nr = row + _DY[d]
nc = col + _DX[d]
if 0 <= nr < rows and 0 <= nc < cols and not visited[nr, nc]:
# Carve passage: clear wall bit on current cell
grid[row, col] &= (~(1 << d)) & 0xFF
# Clear opposite wall bit on neighbour
grid[nr, nc] &= (~(1 << _OPP[d])) & 0xFF
visited[nr, nc] = True
stack.append((nr, nc))
moved = True
break
if not moved:
stack.pop()
return grid
def get_walls(grid: np.ndarray, row: int, col: int) -> tuple[bool, bool, bool, bool]:
cell = int(grid[row, col])
wall_n = float((cell >> NORTH) & 1)
wall_s = float((cell >> SOUTH) & 1)
wall_e = float((cell >> EAST) & 1)
wall_w = float((cell >> WEST) & 1)
return (wall_n, wall_s, wall_e, wall_w)
def is_passable(grid: np.ndarray, row: int, col: int, direction: int) -> bool:
cell = int(grid[row, col])
return not bool((cell >> direction) & 1)
def render_static(grid: np.ndarray, cfg) -> 'pygame.Surface':
import pygame
rows, cols = grid.shape
cell_size = getattr(cfg, 'cell_size', 32)
wall_thickness = getattr(cfg, 'wall_thickness', 2)
width = cols * cell_size
height = rows * cell_size
surface = pygame.Surface((width, height))
surface.fill((15, 15, 20))
wall_color = (40, 40, 50)
t = wall_thickness
for row in range(rows):
for col in range(cols):
x = col * cell_size
y = row * cell_size
cell = int(grid[row, col])
# North wall
if (cell >> NORTH) & 1:
pygame.draw.rect(surface, wall_color, (x, y, cell_size, t))
# South wall
if (cell >> SOUTH) & 1:
pygame.draw.rect(surface, wall_color, (x, y + cell_size - t, cell_size, t))
# East wall
if (cell >> EAST) & 1:
pygame.draw.rect(surface, wall_color, (x + cell_size - t, y, t, cell_size))
# West wall
if (cell >> WEST) & 1:
pygame.draw.rect(surface, wall_color, (x, y, t, cell_size))
# Start cell (0, 0): small cyan circle
start_cx = cell_size // 2
start_cy = cell_size // 2
start_radius = max(2, cell_size // 6)
pygame.draw.circle(surface, (0, 220, 220), (start_cx, start_cy), start_radius)
# Goal cell (rows-1, cols-1): green filled circle
goal_x = (cols - 1) * cell_size + cell_size // 2
goal_y = (rows - 1) * cell_size + cell_size // 2
goal_radius = max(3, cell_size // 4)
pygame.draw.circle(surface, (0, 200, 80), (goal_x, goal_y), goal_radius)
return surface