fix: genome GA converges via BFS fitness + two-rate prefix-aware mutation
- Replace Manhattan distance with BFS distances (actual maze path length) - Switch from NN to direct sequence genome (what YouTube Shorts actually use) - Mutation-only GA (crossover breaks positional maze paths) - Two-rate mutation: low rate before best-step (preserve prefix), high after (explore tail) - Auto-seed selection finds maze with short BFS path - Default maze 15x15, cell_size=36, max_steps=150 - Typically converges in 5-15 generations
This commit is contained in:
39
maze.py
39
maze.py
@@ -1,5 +1,6 @@
|
||||
import numpy as np
|
||||
import random
|
||||
from collections import deque
|
||||
|
||||
NORTH, SOUTH, EAST, WEST = 0, 1, 2, 3
|
||||
_DX = [0, 0, 1, -1] # col delta for N,S,E,W
|
||||
@@ -41,6 +42,44 @@ def generate(rows: int, cols: int, seed: int | None = None) -> np.ndarray:
|
||||
return grid
|
||||
|
||||
|
||||
def find_short_seed(rows: int, cols: int, max_ratio: float = 6.5, tries: int = 200) -> tuple[int, np.ndarray, np.ndarray]:
|
||||
"""Return (seed, grid, bfs_dist) with a short goal path.
|
||||
Tries up to `tries` seeds, picks the one whose BFS path < rows*cols/max_ratio.
|
||||
Falls back to the best found if none qualifies.
|
||||
"""
|
||||
target = int(rows * cols / max_ratio)
|
||||
best = None
|
||||
for seed in range(tries):
|
||||
grid = generate(rows, cols, seed=seed)
|
||||
bfs = bfs_distances(grid, rows - 1, cols - 1)
|
||||
path_len = int(bfs[0, 0])
|
||||
if best is None or path_len < best[0]:
|
||||
best = (path_len, seed, grid, bfs)
|
||||
if path_len <= target:
|
||||
return seed, grid, bfs
|
||||
_, seed, grid, bfs = best
|
||||
return seed, grid, bfs
|
||||
|
||||
|
||||
def bfs_distances(grid: np.ndarray, goal_row: int, goal_col: int) -> np.ndarray:
|
||||
"""BFS distance from every cell to (goal_row, goal_col) through passable walls.
|
||||
Returns int array shape (rows, cols); unreachable cells = -1.
|
||||
"""
|
||||
rows, cols = grid.shape
|
||||
dist = np.full((rows, cols), -1, dtype=np.int32)
|
||||
dist[goal_row, goal_col] = 0
|
||||
q = deque([(goal_row, goal_col)])
|
||||
while q:
|
||||
r, c = q.popleft()
|
||||
for d in range(4):
|
||||
if is_passable(grid, r, c, d):
|
||||
nr, nc = r + _DY[d], c + _DX[d]
|
||||
if 0 <= nr < rows and 0 <= nc < cols and dist[nr, nc] == -1:
|
||||
dist[nr, nc] = dist[r, c] + 1
|
||||
q.append((nr, nc))
|
||||
return dist
|
||||
|
||||
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user