149 lines
4.4 KiB
Python
149 lines
4.4 KiB
Python
"""Maze analysis tools for calculating statistics and metrics."""
|
|
|
|
from typing import Dict, List
|
|
|
|
from ..core.cell import Cell
|
|
from ..core.maze import Maze
|
|
|
|
|
|
class MazeAnalyzer:
|
|
"""Analyzes mazes to compute various metrics and statistics."""
|
|
|
|
@staticmethod
|
|
def analyze(maze: Maze) -> Dict:
|
|
"""Perform comprehensive analysis of a maze.
|
|
|
|
Args:
|
|
maze: Maze to analyze
|
|
|
|
Returns:
|
|
Dictionary with analysis results
|
|
"""
|
|
dead_ends = MazeAnalyzer.count_dead_ends(maze)
|
|
longest_path_info = MazeAnalyzer.find_longest_path(maze)
|
|
branching_factor = MazeAnalyzer.calculate_branching_factor(maze)
|
|
|
|
return {
|
|
'dimensions': f"{maze.rows}x{maze.cols}",
|
|
'total_cells': maze.rows * maze.cols,
|
|
'algorithm': maze.algorithm_used,
|
|
'generation_time_ms': maze.generation_time_ms,
|
|
'seed': maze.seed,
|
|
'dead_ends': dead_ends,
|
|
'dead_end_percentage': (dead_ends / (maze.rows * maze.cols)) * 100,
|
|
'longest_path_length': longest_path_info['length'],
|
|
'longest_path_start': longest_path_info['start'],
|
|
'longest_path_end': longest_path_info['end'],
|
|
'average_branching_factor': branching_factor
|
|
}
|
|
|
|
@staticmethod
|
|
def count_dead_ends(maze: Maze) -> int:
|
|
"""Count the number of dead ends in the maze.
|
|
|
|
A dead end is a cell with only one open passage.
|
|
|
|
Args:
|
|
maze: Maze to analyze
|
|
|
|
Returns:
|
|
Number of dead ends
|
|
"""
|
|
dead_end_count = 0
|
|
|
|
for row in maze.grid:
|
|
for cell in row:
|
|
# Count open passages
|
|
open_passages = sum(1 for wall in cell.walls.values() if not wall)
|
|
|
|
if open_passages == 1:
|
|
dead_end_count += 1
|
|
|
|
return dead_end_count
|
|
|
|
@staticmethod
|
|
def find_longest_path(maze: Maze) -> Dict:
|
|
"""Find the longest path in the maze.
|
|
|
|
Args:
|
|
maze: Maze to analyze
|
|
|
|
Returns:
|
|
Dictionary with longest path info
|
|
"""
|
|
max_length = 0
|
|
max_start = (0, 0)
|
|
max_end = (0, 0)
|
|
|
|
# Try BFS from each cell to find longest path
|
|
for start_row in maze.grid:
|
|
for start_cell in start_row:
|
|
maze.reset_visited()
|
|
distances = MazeAnalyzer._bfs_distances(maze, start_cell)
|
|
|
|
for end_cell, distance in distances.items():
|
|
if distance > max_length:
|
|
max_length = distance
|
|
max_start = (start_cell.row, start_cell.col)
|
|
max_end = (end_cell.row, end_cell.col)
|
|
|
|
maze.reset_visited()
|
|
return {
|
|
'length': max_length,
|
|
'start': max_start,
|
|
'end': max_end
|
|
}
|
|
|
|
@staticmethod
|
|
def _bfs_distances(maze: Maze, start: Cell) -> Dict[Cell, int]:
|
|
"""Calculate distances from start cell using BFS.
|
|
|
|
Args:
|
|
maze: The maze
|
|
start: Starting cell
|
|
|
|
Returns:
|
|
Dictionary mapping cells to their distance from start
|
|
"""
|
|
from collections import deque
|
|
|
|
queue = deque([(start, 0)])
|
|
distances = {start: 0}
|
|
start.visited = True
|
|
|
|
while queue:
|
|
current, dist = queue.popleft()
|
|
|
|
neighbors = maze.get_neighbors(current)
|
|
for neighbor, direction in neighbors:
|
|
if not neighbor.visited and not current.has_wall(direction):
|
|
neighbor.visited = True
|
|
distances[neighbor] = dist + 1
|
|
queue.append((neighbor, dist + 1))
|
|
|
|
return distances
|
|
|
|
@staticmethod
|
|
def calculate_branching_factor(maze: Maze) -> float:
|
|
"""Calculate the average branching factor of the maze.
|
|
|
|
Branching factor is the average number of passages per cell.
|
|
|
|
Args:
|
|
maze: Maze to analyze
|
|
|
|
Returns:
|
|
Average branching factor
|
|
"""
|
|
total_passages = 0
|
|
cell_count = 0
|
|
|
|
for row in maze.grid:
|
|
for cell in row:
|
|
# Count open passages
|
|
open_passages = sum(1 for wall in cell.walls.values() if not wall)
|
|
total_passages += open_passages
|
|
cell_count += 1
|
|
|
|
return total_passages / cell_count if cell_count > 0 else 0
|