Files
python-maze/web/static/js/app.js
2025-11-20 22:58:11 -05:00

386 lines
12 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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;
}
}