Initial commit
This commit is contained in:
1
api/__init__.py
Normal file
1
api/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Web API for maze generation and solving."""
|
||||
296
api/app.py
Normal file
296
api/app.py
Normal file
@@ -0,0 +1,296 @@
|
||||
"""Main Flask application for the Maze Generator API."""
|
||||
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# Add parent directory to path to import src modules
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||||
|
||||
from flask import Flask, jsonify, request, send_file, render_template
|
||||
from flask_cors import CORS
|
||||
|
||||
from src.generators import *
|
||||
from src.solvers import *
|
||||
from src.core.maze import Maze
|
||||
from src.storage.file_handler import FileHandler
|
||||
from src.visualization.image_renderer import ImageRenderer
|
||||
from src.visualization.web_renderer import WebRenderer
|
||||
from src.analysis.analyzer import MazeAnalyzer
|
||||
from src.analysis.benchmark import Benchmark
|
||||
|
||||
app = Flask(__name__,
|
||||
template_folder='../web/templates',
|
||||
static_folder='../web/static')
|
||||
CORS(app)
|
||||
|
||||
# Generator mapping
|
||||
GENERATORS = {
|
||||
'recursive_backtracking': RecursiveBacktrackingGenerator(),
|
||||
'kruskal': KruskalGenerator(),
|
||||
'prim': PrimGenerator(),
|
||||
'sidewinder': SidewinderGenerator(),
|
||||
'hunt_and_kill': HuntAndKillGenerator(),
|
||||
'eller': EllerGenerator(),
|
||||
'wilson': WilsonGenerator(),
|
||||
'aldous_broder': AldousBroderGenerator()
|
||||
}
|
||||
|
||||
# Solver mapping
|
||||
SOLVERS = {
|
||||
'dfs': DFSSolver(),
|
||||
'bfs': BFSSolver()
|
||||
}
|
||||
|
||||
# Store mazes in memory (keyed by ID)
|
||||
mazes = {}
|
||||
maze_counter = 0
|
||||
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
"""Serve the main web interface."""
|
||||
return render_template('index.html')
|
||||
|
||||
|
||||
@app.route('/api/algorithms', methods=['GET'])
|
||||
def get_algorithms():
|
||||
"""Get list of available algorithms."""
|
||||
return jsonify({
|
||||
'generators': list(GENERATORS.keys()),
|
||||
'solvers': list(SOLVERS.keys())
|
||||
})
|
||||
|
||||
|
||||
@app.route('/api/generate', methods=['POST'])
|
||||
def generate_maze():
|
||||
"""Generate a new maze."""
|
||||
global maze_counter
|
||||
|
||||
data = request.json
|
||||
|
||||
# Validate input
|
||||
algorithm = data.get('algorithm', 'recursive_backtracking')
|
||||
rows = data.get('rows', 10)
|
||||
cols = data.get('cols', 10)
|
||||
seed = data.get('seed')
|
||||
|
||||
if algorithm not in GENERATORS:
|
||||
return jsonify({'error': f'Unknown algorithm: {algorithm}'}), 400
|
||||
|
||||
if not (5 <= rows <= 50) or not (5 <= cols <= 50):
|
||||
return jsonify({'error': 'Dimensions must be between 5 and 50'}), 400
|
||||
|
||||
try:
|
||||
# Generate maze
|
||||
generator = GENERATORS[algorithm]
|
||||
maze = generator.generate(rows, cols, seed)
|
||||
|
||||
# Store maze
|
||||
maze_id = maze_counter
|
||||
mazes[maze_id] = maze
|
||||
maze_counter += 1
|
||||
|
||||
# Return maze data
|
||||
return jsonify({
|
||||
'id': maze_id,
|
||||
'maze': WebRenderer.to_json_format(maze),
|
||||
'success': True
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
@app.route('/api/maze/<int:maze_id>', methods=['GET'])
|
||||
def get_maze(maze_id):
|
||||
"""Get maze by ID."""
|
||||
if maze_id not in mazes:
|
||||
return jsonify({'error': 'Maze not found'}), 404
|
||||
|
||||
maze = mazes[maze_id]
|
||||
return jsonify({
|
||||
'id': maze_id,
|
||||
'maze': WebRenderer.to_json_format(maze)
|
||||
})
|
||||
|
||||
|
||||
@app.route('/api/solve', methods=['POST'])
|
||||
def solve_maze():
|
||||
"""Solve a maze."""
|
||||
data = request.json
|
||||
|
||||
maze_id = data.get('maze_id')
|
||||
algorithm = data.get('algorithm', 'bfs')
|
||||
|
||||
if maze_id is None or maze_id not in mazes:
|
||||
return jsonify({'error': 'Maze not found'}), 404
|
||||
|
||||
if algorithm not in SOLVERS:
|
||||
return jsonify({'error': f'Unknown solver: {algorithm}'}), 400
|
||||
|
||||
try:
|
||||
maze = mazes[maze_id]
|
||||
solver = SOLVERS[algorithm]
|
||||
result = solver.solve(maze)
|
||||
|
||||
return jsonify({
|
||||
'success': result['success'],
|
||||
'path': result['path'],
|
||||
'visited': result['visited'],
|
||||
'time_ms': result['time_ms'],
|
||||
'path_length': result['path_length'],
|
||||
'algorithm': result['algorithm']
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
@app.route('/api/analyze/<int:maze_id>', methods=['GET'])
|
||||
def analyze_maze(maze_id):
|
||||
"""Analyze a maze."""
|
||||
if maze_id not in mazes:
|
||||
return jsonify({'error': 'Maze not found'}), 404
|
||||
|
||||
try:
|
||||
maze = mazes[maze_id]
|
||||
analysis = MazeAnalyzer.analyze(maze)
|
||||
return jsonify(analysis)
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
@app.route('/api/benchmark', methods=['POST'])
|
||||
def benchmark():
|
||||
"""Run algorithm benchmarks."""
|
||||
data = request.json or {}
|
||||
|
||||
benchmark_type = data.get('type', 'quick')
|
||||
|
||||
try:
|
||||
if benchmark_type == 'quick':
|
||||
results = Benchmark.quick_benchmark()
|
||||
elif benchmark_type == 'generators':
|
||||
sizes = data.get('sizes', [(10, 10), (25, 25)])
|
||||
iterations = data.get('iterations', 3)
|
||||
results = Benchmark.benchmark_generators(sizes, iterations)
|
||||
elif benchmark_type == 'solvers':
|
||||
sizes = data.get('sizes', [(10, 10), (25, 25)])
|
||||
iterations = data.get('iterations', 3)
|
||||
results = Benchmark.benchmark_solvers(sizes, iterations)
|
||||
else:
|
||||
return jsonify({'error': 'Invalid benchmark type'}), 400
|
||||
|
||||
return jsonify(results)
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
@app.route('/api/download/<int:maze_id>', methods=['GET'])
|
||||
def download_maze_image(maze_id):
|
||||
"""Download maze as an image."""
|
||||
if maze_id not in mazes:
|
||||
return jsonify({'error': 'Maze not found'}), 404
|
||||
|
||||
try:
|
||||
maze = mazes[maze_id]
|
||||
|
||||
# Get optional parameters
|
||||
include_solution = request.args.get('solution', 'false').lower() == 'true'
|
||||
solver_algorithm = request.args.get('solver', 'bfs')
|
||||
|
||||
solution_path = None
|
||||
visited_cells = None
|
||||
|
||||
if include_solution and solver_algorithm in SOLVERS:
|
||||
solver = SOLVERS[solver_algorithm]
|
||||
result = solver.solve(maze)
|
||||
if result['success']:
|
||||
solution_path = result['path']
|
||||
visited_cells = result['visited']
|
||||
|
||||
# Render image
|
||||
renderer = ImageRenderer(cell_size=20, wall_thickness=2)
|
||||
filename = f"maze_{maze_id}_{maze.algorithm_used.replace(' ', '_')}"
|
||||
filepath = renderer.render(
|
||||
maze,
|
||||
filename,
|
||||
solution_path=solution_path,
|
||||
visited_cells=visited_cells
|
||||
)
|
||||
|
||||
return send_file(filepath, mimetype='image/png', as_attachment=True)
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
@app.route('/api/save/<int:maze_id>', methods=['POST'])
|
||||
def save_maze(maze_id):
|
||||
"""Save a maze to file."""
|
||||
if maze_id not in mazes:
|
||||
return jsonify({'error': 'Maze not found'}), 404
|
||||
|
||||
data = request.json or {}
|
||||
filename = data.get('filename', f'maze_{maze_id}')
|
||||
|
||||
try:
|
||||
maze = mazes[maze_id]
|
||||
filepath = FileHandler.save_maze(maze, filename)
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'filepath': filepath
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
@app.route('/api/load', methods=['POST'])
|
||||
def load_maze():
|
||||
"""Load a maze from file."""
|
||||
global maze_counter
|
||||
|
||||
data = request.json
|
||||
filename = data.get('filename')
|
||||
|
||||
if not filename:
|
||||
return jsonify({'error': 'Filename required'}), 400
|
||||
|
||||
try:
|
||||
maze = FileHandler.load_maze(filename)
|
||||
|
||||
# Store maze
|
||||
maze_id = maze_counter
|
||||
mazes[maze_id] = maze
|
||||
maze_counter += 1
|
||||
|
||||
return jsonify({
|
||||
'id': maze_id,
|
||||
'maze': WebRenderer.to_json_format(maze),
|
||||
'success': True
|
||||
})
|
||||
|
||||
except FileNotFoundError:
|
||||
return jsonify({'error': 'File not found'}), 404
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
@app.route('/api/saved-mazes', methods=['GET'])
|
||||
def list_saved_mazes():
|
||||
"""List all saved maze files."""
|
||||
try:
|
||||
files = FileHandler.list_saved_mazes()
|
||||
return jsonify({'files': files})
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(debug=True, host='0.0.0.0', port=5000)
|
||||
Reference in New Issue
Block a user