// Main application logic const API_BASE = '/api'; let currentMazeId = null; let currentMazeData = null; let animationState = { isAnimating: false, isPaused: false, currentStep: 0, visitedCells: [], solutionPath: [], animationId: null, speed: 50 // milliseconds per step }; // 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 pauseBtn = document.getElementById('pauseBtn'); const stopBtn = document.getElementById('stopBtn'); const speedSlider = document.getElementById('speedSlider'); const algorithmSelect = document.getElementById('algorithm'); const rowsInput = document.getElementById('rows'); const colsInput = document.getElementById('cols'); const seedInput = document.getElementById('seed'); const resultsContent = document.getElementById('resultsContent'); const mazeInfo = document.getElementById('mazeInfo'); const animationControls = document.getElementById('animationControls'); // 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); pauseBtn.addEventListener('click', togglePause); stopBtn.addEventListener('click', stopAnimation); speedSlider.addEventListener('input', updateSpeed); // 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 { // Create a temporary link and trigger download const downloadUrl = `${API_BASE}/download/${currentMazeId}?solution=false&format=png`; const response = await fetch(downloadUrl); if (!response.ok) { const errorData = await response.json().catch(() => ({ error: 'Download failed' })); throw new Error(errorData.error || `Server returned ${response.status}`); } const blob = await response.blob(); // Verify we got an image if (!blob.type.startsWith('image/')) { throw new Error('Invalid response: not an image'); } const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `maze_${currentMazeId}_${currentMazeData.algorithm.replace(/[^a-z0-9]/gi, '_')}.png`; document.body.appendChild(a); a.click(); // Clean up after a short delay setTimeout(() => { document.body.removeChild(a); window.URL.revokeObjectURL(url); }, 100); showSuccess('Maze image downloaded!'); } catch (error) { console.error('Download error:', 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 => `
${file}
` ).join(''); } else { fileList.innerHTML = '

No saved mazes found

'; } 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 with Animation async function solveMaze(algorithm) { if (currentMazeId === null) { showError('No maze to solve'); return; } // Stop any ongoing animation stopAnimation(); 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(); setLoading(btn, false); if (data.success) { // Start animated solving startSolvingAnimation(data); displaySolutionInfo(data); } else { showError('Failed to solve maze'); } } catch (error) { showError('Network error: ' + error.message); setLoading(btn, false); } } // Animation Control Functions function startSolvingAnimation(solutionData) { animationState.isAnimating = true; animationState.isPaused = false; animationState.currentStep = 0; animationState.visitedCells = solutionData.visited; animationState.solutionPath = solutionData.path; // Show animation controls animationControls.style.display = 'block'; pauseBtn.textContent = 'PAUSE'; // Disable solve buttons during animation solveDfsBtn.disabled = true; solveBfsBtn.disabled = true; // Start animation animateStep(); } function animateStep() { if (!animationState.isAnimating || animationState.isPaused) { return; } const { currentStep, visitedCells, solutionPath } = animationState; if (currentStep < visitedCells.length) { // Show visited cells up to current step const visibleVisited = visitedCells.slice(0, currentStep + 1); // Don't show solution path yet, only when all cells are visited renderMaze(currentMazeData, visibleVisited, null); animationState.currentStep++; // Schedule next step animationState.animationId = setTimeout(animateStep, animationState.speed); } else if (currentStep < visitedCells.length + solutionPath.length) { // Now animate the solution path const pathStep = currentStep - visitedCells.length; const visiblePath = solutionPath.slice(0, pathStep + 1); renderMaze(currentMazeData, visitedCells, visiblePath); animationState.currentStep++; // Schedule next step (slower for solution path) animationState.animationId = setTimeout(animateStep, animationState.speed * 2); } else { // Animation complete finishAnimation(); } } function togglePause() { if (!animationState.isAnimating) return; animationState.isPaused = !animationState.isPaused; if (animationState.isPaused) { pauseBtn.textContent = 'RESUME'; if (animationState.animationId) { clearTimeout(animationState.animationId); } } else { pauseBtn.textContent = 'PAUSE'; animateStep(); } } function stopAnimation() { if (!animationState.isAnimating) return; animationState.isAnimating = false; animationState.isPaused = false; if (animationState.animationId) { clearTimeout(animationState.animationId); animationState.animationId = null; } // Hide animation controls animationControls.style.display = 'none'; // Re-enable solve buttons solveDfsBtn.disabled = false; solveBfsBtn.disabled = false; // Show final result if we have solution data if (animationState.visitedCells.length > 0 && animationState.solutionPath.length > 0) { renderMaze(currentMazeData, animationState.visitedCells, animationState.solutionPath); } else { renderMaze(currentMazeData); } } function finishAnimation() { animationState.isAnimating = false; // Hide animation controls animationControls.style.display = 'none'; // Re-enable solve buttons solveDfsBtn.disabled = false; solveBfsBtn.disabled = false; // Show final result renderMaze(currentMazeData, animationState.visitedCells, animationState.solutionPath); showSuccess('Solving animation complete!'); } function updateSpeed(event) { // Speed slider: 1 (slow) to 100 (fast) // Convert to delay: 200ms (slow) to 10ms (fast) const sliderValue = parseInt(event.target.value); animationState.speed = 210 - (sliderValue * 2); } // 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 = ` ALGORITHM: ${maze.algorithm}
SIZE: ${maze.rows} × ${maze.cols}
GENERATION TIME: ${maze.generationTime.toFixed(2)} ms `; } function displaySolutionInfo(solution) { const section = document.createElement('div'); section.className = 'result-section'; section.innerHTML = `

SOLUTION: ${solution.algorithm}

Path Length: ${solution.path_length} cells
Cells Visited: ${solution.visited.length}
Solve Time: ${solution.time_ms.toFixed(2)} ms
`; resultsContent.innerHTML = ''; resultsContent.appendChild(section); } function displayAnalysisResults(analysis) { const section = document.createElement('div'); section.className = 'result-section'; section.innerHTML = `

MAZE ANALYSIS

Dimensions: ${analysis.dimensions}
Total Cells: ${analysis.total_cells}
Algorithm: ${analysis.algorithm}
Dead Ends: ${analysis.dead_ends} (${analysis.dead_end_percentage.toFixed(1)}%)
Longest Path: ${analysis.longest_path_length} cells
Avg Branching: ${analysis.average_branching_factor.toFixed(2)}
`; resultsContent.innerHTML = ''; resultsContent.appendChild(section); } function displayBenchmarkResults(data) { let html = '

GENERATOR BENCHMARK

'; html += ''; data.generators.results.forEach(r => { html += ``; }); html += '
AlgorithmSizeAvg Time (ms)
${r.algorithm}${r.size}${r.avg_time_ms}
'; html += '

SOLVER BENCHMARK

'; html += ''; data.solvers.results.forEach(r => { html += ``; }); html += '
AlgorithmSizeAvg Time (ms)Path Length
${r.algorithm}${r.size}${r.avg_time_ms}${r.avg_path_length}
'; 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; } }