166 lines
4.8 KiB
Python
166 lines
4.8 KiB
Python
|
|
import csv
|
||
|
|
import os
|
||
|
|
import sys
|
||
|
|
|
||
|
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "codes"))
|
||
|
|
|
||
|
|
from maze import TextFileMazeBuilder, MazeSolver, BFSStrategy, DFSStrategy, AStarStrategy
|
||
|
|
from maze_generator import generate_all
|
||
|
|
|
||
|
|
try:
|
||
|
|
import matplotlib.pyplot as plt
|
||
|
|
import matplotlib
|
||
|
|
matplotlib.use("Agg")
|
||
|
|
HAS_PLT = True
|
||
|
|
except ImportError:
|
||
|
|
HAS_PLT = False
|
||
|
|
|
||
|
|
BASE_DIR = os.path.dirname(__file__)
|
||
|
|
MAZES_DIR = os.path.join(BASE_DIR, "mazes")
|
||
|
|
RESULTS_DIR = os.path.join(BASE_DIR, "results")
|
||
|
|
RUNS = 7
|
||
|
|
|
||
|
|
MAZE_FILES = [
|
||
|
|
("small", "small.txt"),
|
||
|
|
("medium", "medium.txt"),
|
||
|
|
("large", "large.txt"),
|
||
|
|
("empty", "empty.txt"),
|
||
|
|
("no_exit", "no_exit.txt"),
|
||
|
|
]
|
||
|
|
|
||
|
|
|
||
|
|
def run():
|
||
|
|
os.makedirs(RESULTS_DIR, exist_ok=True)
|
||
|
|
|
||
|
|
if not os.path.exists(MAZES_DIR) or not os.listdir(MAZES_DIR):
|
||
|
|
generate_all(MAZES_DIR)
|
||
|
|
|
||
|
|
strategies = [BFSStrategy(), DFSStrategy(), AStarStrategy()]
|
||
|
|
builder = TextFileMazeBuilder()
|
||
|
|
all_results = []
|
||
|
|
|
||
|
|
for label, filename in MAZE_FILES:
|
||
|
|
path = os.path.join(MAZES_DIR, filename)
|
||
|
|
if not os.path.exists(path):
|
||
|
|
continue
|
||
|
|
|
||
|
|
maze = builder.build_from_file(path)
|
||
|
|
print(f"\nMaze: {label} ({maze.width}x{maze.height})")
|
||
|
|
|
||
|
|
solver = MazeSolver(maze, strategies[0])
|
||
|
|
|
||
|
|
for strategy in strategies:
|
||
|
|
solver.set_strategy(strategy)
|
||
|
|
times, visited_list, lengths = [], [], []
|
||
|
|
|
||
|
|
for _ in range(RUNS):
|
||
|
|
stats = solver.solve()
|
||
|
|
times.append(stats.time_ms)
|
||
|
|
visited_list.append(stats.visited)
|
||
|
|
lengths.append(stats.path_length)
|
||
|
|
|
||
|
|
avg_time = sum(times) / RUNS
|
||
|
|
avg_visited = sum(visited_list) / RUNS
|
||
|
|
avg_len = sum(lengths) / RUNS
|
||
|
|
|
||
|
|
found = f"length={avg_len:.0f}" if avg_len > 0 else "not found"
|
||
|
|
print(f" {strategy.name:<6} time={avg_time:.4f} ms visited={avg_visited:.0f} {found}")
|
||
|
|
|
||
|
|
all_results.append({
|
||
|
|
"maze": label,
|
||
|
|
"strategy": strategy.name,
|
||
|
|
"time_ms": round(avg_time, 4),
|
||
|
|
"visited_cells": round(avg_visited, 1),
|
||
|
|
"path_length": round(avg_len, 1),
|
||
|
|
})
|
||
|
|
|
||
|
|
save_csv(all_results)
|
||
|
|
save_plots(all_results)
|
||
|
|
show_sample()
|
||
|
|
print("\nDone. See results/ and docs/")
|
||
|
|
|
||
|
|
|
||
|
|
def save_csv(results):
|
||
|
|
path = os.path.join(RESULTS_DIR, "results_maze.csv")
|
||
|
|
with open(path, "w", newline="", encoding="utf-8") as f:
|
||
|
|
writer = csv.DictWriter(
|
||
|
|
f, fieldnames=["maze", "strategy", "time_ms", "visited_cells", "path_length"]
|
||
|
|
)
|
||
|
|
writer.writeheader()
|
||
|
|
writer.writerows(results)
|
||
|
|
print(f"\nCSV saved: {path}")
|
||
|
|
|
||
|
|
|
||
|
|
def save_plots(results):
|
||
|
|
if not HAS_PLT:
|
||
|
|
return
|
||
|
|
|
||
|
|
mazes = list(dict.fromkeys(r["maze"] for r in results))
|
||
|
|
strategies = list(dict.fromkeys(r["strategy"] for r in results))
|
||
|
|
colors = ["#2196F3", "#FF5722", "#4CAF50"]
|
||
|
|
|
||
|
|
def val(maze, strat, key):
|
||
|
|
for r in results:
|
||
|
|
if r["maze"] == maze and r["strategy"] == strat:
|
||
|
|
return float(r[key])
|
||
|
|
return 0.0
|
||
|
|
|
||
|
|
metrics = [
|
||
|
|
("time_ms", "Time (ms)"),
|
||
|
|
("visited_cells", "Visited cells"),
|
||
|
|
("path_length", "Path length"),
|
||
|
|
]
|
||
|
|
|
||
|
|
fig, axes = plt.subplots(
|
||
|
|
len(metrics), len(mazes),
|
||
|
|
figsize=(3.5 * len(mazes), 4 * len(metrics))
|
||
|
|
)
|
||
|
|
|
||
|
|
def fmt(v):
|
||
|
|
if v == 0:
|
||
|
|
return "0"
|
||
|
|
if v >= 100:
|
||
|
|
return f"{v:.0f}"
|
||
|
|
if v >= 1:
|
||
|
|
return f"{v:.2f}"
|
||
|
|
return f"{v:.3f}"
|
||
|
|
|
||
|
|
for row_i, (key, ylabel) in enumerate(metrics):
|
||
|
|
for col_i, maze in enumerate(mazes):
|
||
|
|
ax = axes[row_i][col_i]
|
||
|
|
vals = [val(maze, s, key) for s in strategies]
|
||
|
|
bars = ax.bar(strategies, vals, color=colors[:len(strategies)])
|
||
|
|
if row_i == 0:
|
||
|
|
ax.set_title(maze, fontsize=9)
|
||
|
|
if col_i == 0:
|
||
|
|
ax.set_ylabel(ylabel)
|
||
|
|
for bar, v in zip(bars, vals):
|
||
|
|
ax.text(
|
||
|
|
bar.get_x() + bar.get_width() / 2,
|
||
|
|
bar.get_height() * 1.02,
|
||
|
|
fmt(v), ha="center", va="bottom", fontsize=7
|
||
|
|
)
|
||
|
|
ax.tick_params(axis="x", labelsize=8)
|
||
|
|
|
||
|
|
plt.tight_layout()
|
||
|
|
out = os.path.join(RESULTS_DIR, "benchmark_plot.png")
|
||
|
|
plt.savefig(out, dpi=120)
|
||
|
|
plt.close()
|
||
|
|
print(f"Chart saved: {out}")
|
||
|
|
|
||
|
|
|
||
|
|
def show_sample():
|
||
|
|
path = os.path.join(MAZES_DIR, "sample.txt")
|
||
|
|
if not os.path.exists(path):
|
||
|
|
return
|
||
|
|
builder = TextFileMazeBuilder()
|
||
|
|
maze = builder.build_from_file(path)
|
||
|
|
solver = MazeSolver(maze, BFSStrategy())
|
||
|
|
stats = solver.solve()
|
||
|
|
print("\nSample maze with BFS path:")
|
||
|
|
print(maze.render(path=stats.path))
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
run()
|