Initial commit

This commit is contained in:
2025-11-20 22:58:11 -05:00
commit 6d75c8e94e
51 changed files with 5141 additions and 0 deletions

385
web/static/js/app.js Normal file
View File

@@ -0,0 +1,385 @@
// Main application logic
const API_BASE = '/api';
let currentMazeId = null;
let currentMazeData = null;
// DOM Elements
const generateBtn = document.getElementById('generateBtn');
const visualizeBtn = document.getElementById('visualizeBtn');
const downloadBtn = document.getElementById('downloadBtn');
const saveBtn = document.getElementById('saveBtn');
const loadBtn = document.getElementById('loadBtn');
const solveDfsBtn = document.getElementById('solveDfsBtn');
const solveBfsBtn = document.getElementById('solveBfsBtn');
const analyzeBtn = document.getElementById('analyzeBtn');
const benchmarkBtn = document.getElementById('benchmarkBtn');
const algorithmSelect = document.getElementById('algorithm');
const rowsInput = document.getElementById('rows');
const colsInput = document.getElementById('cols');
const seedInput = document.getElementById('seed');
const solverSelect = document.getElementById('solver');
const resultsContent = document.getElementById('resultsContent');
const mazeInfo = document.getElementById('mazeInfo');
// Disable buttons initially
function disableActionButtons() {
visualizeBtn.disabled = true;
downloadBtn.disabled = true;
saveBtn.disabled = true;
solveDfsBtn.disabled = true;
solveBfsBtn.disabled = true;
analyzeBtn.disabled = true;
}
function enableActionButtons() {
visualizeBtn.disabled = false;
downloadBtn.disabled = false;
saveBtn.disabled = false;
solveDfsBtn.disabled = false;
solveBfsBtn.disabled = false;
analyzeBtn.disabled = false;
}
disableActionButtons();
// Event Listeners
generateBtn.addEventListener('click', generateMaze);
visualizeBtn.addEventListener('click', visualizeMaze);
downloadBtn.addEventListener('click', downloadMaze);
saveBtn.addEventListener('click', saveMaze);
loadBtn.addEventListener('click', showLoadModal);
solveDfsBtn.addEventListener('click', () => solveMaze('dfs'));
solveBfsBtn.addEventListener('click', () => solveMaze('bfs'));
analyzeBtn.addEventListener('click', analyzeMaze);
benchmarkBtn.addEventListener('click', runBenchmark);
// Generate Maze
async function generateMaze() {
const algorithm = algorithmSelect.value;
const rows = parseInt(rowsInput.value);
const cols = parseInt(colsInput.value);
const seed = seedInput.value ? parseInt(seedInput.value) : null;
if (rows < 5 || rows > 50 || cols < 5 || cols > 50) {
showError('Dimensions must be between 5 and 50');
return;
}
setLoading(generateBtn, true);
try {
const response = await fetch(`${API_BASE}/generate`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ algorithm, rows, cols, seed })
});
const data = await response.json();
if (data.success) {
currentMazeId = data.id;
currentMazeData = data.maze;
enableActionButtons();
showSuccess('Maze generated successfully!');
displayMazeInfo(data.maze);
visualizeMaze();
} else {
showError(data.error || 'Failed to generate maze');
}
} catch (error) {
showError('Network error: ' + error.message);
} finally {
setLoading(generateBtn, false);
}
}
// Visualize Maze
function visualizeMaze() {
if (!currentMazeData) {
showError('No maze to visualize');
return;
}
renderMaze(currentMazeData);
showInfo('Maze visualized');
}
// Download Maze
async function downloadMaze() {
if (currentMazeId === null) {
showError('No maze to download');
return;
}
try {
window.open(`${API_BASE}/download/${currentMazeId}?solution=false`, '_blank');
showSuccess('Downloading maze image...');
} catch (error) {
showError('Failed to download: ' + error.message);
}
}
// Save Maze
async function saveMaze() {
if (currentMazeId === null) {
showError('No maze to save');
return;
}
const filename = prompt('Enter filename:', `maze_${currentMazeId}`);
if (!filename) return;
try {
const response = await fetch(`${API_BASE}/save/${currentMazeId}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ filename })
});
const data = await response.json();
if (data.success) {
showSuccess(`Maze saved to: ${data.filepath}`);
} else {
showError(data.error || 'Failed to save maze');
}
} catch (error) {
showError('Network error: ' + error.message);
}
}
// Load Maze Modal
async function showLoadModal() {
const modal = document.getElementById('loadModal');
const fileList = document.getElementById('fileList');
const closeBtn = document.getElementById('closeModal');
try {
const response = await fetch(`${API_BASE}/saved-mazes`);
const data = await response.json();
if (data.files && data.files.length > 0) {
fileList.innerHTML = data.files.map(file =>
`<div class="file-item" onclick="loadMazeFile('${file}')">${file}</div>`
).join('');
} else {
fileList.innerHTML = '<p class="placeholder-text">No saved mazes found</p>';
}
modal.classList.add('active');
closeBtn.onclick = () => modal.classList.remove('active');
modal.onclick = (e) => {
if (e.target === modal) modal.classList.remove('active');
};
} catch (error) {
showError('Failed to load file list: ' + error.message);
}
}
async function loadMazeFile(filename) {
const modal = document.getElementById('loadModal');
modal.classList.remove('active');
try {
const response = await fetch(`${API_BASE}/load`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ filename })
});
const data = await response.json();
if (data.success) {
currentMazeId = data.id;
currentMazeData = data.maze;
enableActionButtons();
showSuccess(`Loaded: ${filename}`);
displayMazeInfo(data.maze);
visualizeMaze();
} else {
showError(data.error || 'Failed to load maze');
}
} catch (error) {
showError('Network error: ' + error.message);
}
}
// Solve Maze
async function solveMaze(algorithm) {
if (currentMazeId === null) {
showError('No maze to solve');
return;
}
const btn = algorithm === 'dfs' ? solveDfsBtn : solveBfsBtn;
setLoading(btn, true);
try {
const response = await fetch(`${API_BASE}/solve`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ maze_id: currentMazeId, algorithm })
});
const data = await response.json();
if (data.success) {
renderMaze(currentMazeData, data.visited, data.path);
displaySolutionInfo(data);
showSuccess(`Maze solved using ${data.algorithm}!`);
} else {
showError('Failed to solve maze');
}
} catch (error) {
showError('Network error: ' + error.message);
} finally {
setLoading(btn, false);
}
}
// Analyze Maze
async function analyzeMaze() {
if (currentMazeId === null) {
showError('No maze to analyze');
return;
}
setLoading(analyzeBtn, true);
try {
const response = await fetch(`${API_BASE}/analyze/${currentMazeId}`);
const data = await response.json();
displayAnalysisResults(data);
showSuccess('Analysis complete!');
} catch (error) {
showError('Network error: ' + error.message);
} finally {
setLoading(analyzeBtn, false);
}
}
// Run Benchmark
async function runBenchmark() {
setLoading(benchmarkBtn, true);
showInfo('Running benchmarks... This may take a moment.');
try {
const response = await fetch(`${API_BASE}/benchmark`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ type: 'quick' })
});
const data = await response.json();
displayBenchmarkResults(data);
showSuccess('Benchmark complete!');
} catch (error) {
showError('Network error: ' + error.message);
} finally {
setLoading(benchmarkBtn, false);
}
}
// Display Functions
function displayMazeInfo(maze) {
mazeInfo.innerHTML = `
<strong>ALGORITHM:</strong> ${maze.algorithm}<br>
<strong>SIZE:</strong> ${maze.rows} × ${maze.cols}<br>
<strong>GENERATION TIME:</strong> ${maze.generationTime.toFixed(2)} ms
`;
}
function displaySolutionInfo(solution) {
const section = document.createElement('div');
section.className = 'result-section';
section.innerHTML = `
<h3>SOLUTION: ${solution.algorithm}</h3>
<div class="result-item"><strong>Path Length:</strong> ${solution.path_length} cells</div>
<div class="result-item"><strong>Cells Visited:</strong> ${solution.visited.length}</div>
<div class="result-item"><strong>Solve Time:</strong> ${solution.time_ms.toFixed(2)} ms</div>
`;
resultsContent.innerHTML = '';
resultsContent.appendChild(section);
}
function displayAnalysisResults(analysis) {
const section = document.createElement('div');
section.className = 'result-section';
section.innerHTML = `
<h3>MAZE ANALYSIS</h3>
<div class="result-item"><strong>Dimensions:</strong> ${analysis.dimensions}</div>
<div class="result-item"><strong>Total Cells:</strong> ${analysis.total_cells}</div>
<div class="result-item"><strong>Algorithm:</strong> ${analysis.algorithm}</div>
<div class="result-item"><strong>Dead Ends:</strong> ${analysis.dead_ends} (${analysis.dead_end_percentage.toFixed(1)}%)</div>
<div class="result-item"><strong>Longest Path:</strong> ${analysis.longest_path_length} cells</div>
<div class="result-item"><strong>Avg Branching:</strong> ${analysis.average_branching_factor.toFixed(2)}</div>
`;
resultsContent.innerHTML = '';
resultsContent.appendChild(section);
}
function displayBenchmarkResults(data) {
let html = '<div class="result-section"><h3>GENERATOR BENCHMARK</h3>';
html += '<table class="result-table"><tr><th>Algorithm</th><th>Size</th><th>Avg Time (ms)</th></tr>';
data.generators.results.forEach(r => {
html += `<tr><td>${r.algorithm}</td><td>${r.size}</td><td>${r.avg_time_ms}</td></tr>`;
});
html += '</table></div>';
html += '<div class="result-section"><h3>SOLVER BENCHMARK</h3>';
html += '<table class="result-table"><tr><th>Algorithm</th><th>Size</th><th>Avg Time (ms)</th><th>Path Length</th></tr>';
data.solvers.results.forEach(r => {
html += `<tr><td>${r.algorithm}</td><td>${r.size}</td><td>${r.avg_time_ms}</td><td>${r.avg_path_length}</td></tr>`;
});
html += '</table></div>';
resultsContent.innerHTML = html;
}
// Utility Functions
function showSuccess(message) {
showStatus(message, 'success');
}
function showError(message) {
showStatus(message, 'error');
}
function showInfo(message) {
showStatus(message, 'info');
}
function showStatus(message, type) {
const existing = document.querySelector('.status-message');
if (existing) existing.remove();
const div = document.createElement('div');
div.className = `status-message status-${type}`;
div.textContent = message;
resultsContent.insertBefore(div, resultsContent.firstChild);
setTimeout(() => div.remove(), 5000);
}
function setLoading(button, isLoading) {
if (isLoading) {
button.dataset.originalText = button.textContent;
button.textContent = 'LOADING...';
button.disabled = true;
} else {
button.textContent = button.dataset.originalText || button.textContent;
button.disabled = false;
}
}

125
web/static/js/visualizer.js Normal file
View File

@@ -0,0 +1,125 @@
// Canvas visualization for mazes
const canvas = document.getElementById('mazeCanvas');
const ctx = canvas.getContext('2d');
const COLORS = {
wall: '#000000',
background: '#FFFFFF',
start: '#FFE500', // Neon yellow
end: '#FF10F0', // Neon pink
solution: '#39FF14', // Neon green
visited: '#E0E0E0' // Light gray
};
function renderMaze(mazeData, visitedCells = null, solutionPath = null) {
const rows = mazeData.rows;
const cols = mazeData.cols;
const cellSize = Math.min(Math.floor(600 / Math.max(rows, cols)), 40);
const wallThickness = Math.max(2, Math.floor(cellSize / 10));
// Set canvas size
canvas.width = cols * cellSize + wallThickness;
canvas.height = rows * cellSize + wallThickness;
// Clear canvas
ctx.fillStyle = COLORS.background;
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Create sets for quick lookup
const visitedSet = new Set();
if (visitedCells) {
visitedCells.forEach(([r, c]) => visitedSet.add(`${r},${c}`));
}
const solutionSet = new Set();
if (solutionPath) {
solutionPath.forEach(([r, c]) => solutionSet.add(`${r},${c}`));
}
// Draw cell backgrounds
for (let row = 0; row < rows; row++) {
for (let col = 0; col < cols; col++) {
const x = col * cellSize + wallThickness;
const y = row * cellSize + wallThickness;
const key = `${row},${col}`;
// Determine cell color
let color = COLORS.background;
if (row === mazeData.start[0] && col === mazeData.start[1]) {
color = COLORS.start;
} else if (row === mazeData.end[0] && col === mazeData.end[1]) {
color = COLORS.end;
} else if (solutionSet.has(key)) {
color = COLORS.solution;
} else if (visitedSet.has(key)) {
color = COLORS.visited;
}
ctx.fillStyle = color;
ctx.fillRect(x, y, cellSize - 1, cellSize - 1);
}
}
// Draw walls
ctx.strokeStyle = COLORS.wall;
ctx.lineWidth = wallThickness;
ctx.lineCap = 'square';
for (let row = 0; row < rows; row++) {
for (let col = 0; col < cols; col++) {
const cellWalls = mazeData.walls[row][col];
const x = col * cellSize + wallThickness / 2;
const y = row * cellSize + wallThickness / 2;
// Draw north wall
if (cellWalls.north) {
ctx.beginPath();
ctx.moveTo(x, y);
ctx.lineTo(x + cellSize, y);
ctx.stroke();
}
// Draw south wall
if (cellWalls.south) {
ctx.beginPath();
ctx.moveTo(x, y + cellSize);
ctx.lineTo(x + cellSize, y + cellSize);
ctx.stroke();
}
// Draw west wall
if (cellWalls.west) {
ctx.beginPath();
ctx.moveTo(x, y);
ctx.lineTo(x, y + cellSize);
ctx.stroke();
}
// Draw east wall
if (cellWalls.east) {
ctx.beginPath();
ctx.moveTo(x + cellSize, y);
ctx.lineTo(x + cellSize, y + cellSize);
ctx.stroke();
}
}
}
// Draw start and end markers with text
drawMarker(mazeData.start[0], mazeData.start[1], 'S', cellSize, wallThickness);
drawMarker(mazeData.end[0], mazeData.end[1], 'E', cellSize, wallThickness);
}
function drawMarker(row, col, text, cellSize, wallThickness) {
const x = col * cellSize + wallThickness + cellSize / 2;
const y = row * cellSize + wallThickness + cellSize / 2;
ctx.fillStyle = '#000000';
ctx.font = `bold ${Math.max(12, cellSize / 2)}px Space Grotesk, monospace`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(text, x, y);
}