Initial commit
This commit is contained in:
372
web/static/css/styles.css
Normal file
372
web/static/css/styles.css
Normal file
@@ -0,0 +1,372 @@
|
||||
/* Neo-Brutalism Maze Generator Styles */
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
:root {
|
||||
/* Neo-Brutalism Color Palette */
|
||||
--black: #000000;
|
||||
--white: #FFFFFF;
|
||||
--neon-yellow: #FFE500;
|
||||
--neon-pink: #FF10F0;
|
||||
--neon-cyan: #00F0FF;
|
||||
--neon-green: #39FF14;
|
||||
--gray-bg: #F5F5F5;
|
||||
--gray-dark: #333333;
|
||||
|
||||
/* Spacing */
|
||||
--border-thick: 6px;
|
||||
--border-medium: 4px;
|
||||
--shadow-offset: 8px;
|
||||
--spacing-sm: 12px;
|
||||
--spacing-md: 20px;
|
||||
--spacing-lg: 32px;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Space Grotesk', monospace, sans-serif;
|
||||
background-color: var(--gray-bg);
|
||||
color: var(--black);
|
||||
line-height: 1.4;
|
||||
padding: var(--spacing-md);
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.header {
|
||||
background-color: var(--neon-yellow);
|
||||
border: var(--border-thick) solid var(--black);
|
||||
padding: var(--spacing-lg);
|
||||
margin-bottom: var(--spacing-lg);
|
||||
box-shadow: var(--shadow-offset) var(--shadow-offset) 0 var(--black);
|
||||
transform: rotate(-1deg);
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 4rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: -2px;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 1.2rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
|
||||
/* Main Grid Layout */
|
||||
.main-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 350px 1fr 400px;
|
||||
gap: var(--spacing-lg);
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.main-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
/* Panels */
|
||||
.panel {
|
||||
background-color: var(--white);
|
||||
border: var(--border-thick) solid var(--black);
|
||||
padding: var(--spacing-md);
|
||||
}
|
||||
|
||||
.control-panel {
|
||||
background-color: var(--neon-cyan);
|
||||
box-shadow: var(--shadow-offset) var(--shadow-offset) 0 var(--black);
|
||||
}
|
||||
|
||||
.viz-panel {
|
||||
background-color: var(--white);
|
||||
box-shadow: calc(var(--shadow-offset) * -1) var(--shadow-offset) 0 var(--neon-pink);
|
||||
min-height: 600px;
|
||||
}
|
||||
|
||||
.results-panel {
|
||||
background-color: var(--neon-green);
|
||||
box-shadow: var(--shadow-offset) calc(var(--shadow-offset) * -1) 0 var(--black);
|
||||
max-height: 800px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.panel-title {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: var(--spacing-md);
|
||||
padding-bottom: var(--spacing-sm);
|
||||
border-bottom: var(--border-medium) solid var(--black);
|
||||
}
|
||||
|
||||
/* Form Controls */
|
||||
.control-group {
|
||||
margin-bottom: var(--spacing-md);
|
||||
}
|
||||
|
||||
.control-row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: var(--spacing-sm);
|
||||
margin-bottom: var(--spacing-md);
|
||||
}
|
||||
|
||||
.label {
|
||||
display: block;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
margin-bottom: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.text-input,
|
||||
.select-input {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
font-family: 'Space Grotesk', monospace, sans-serif;
|
||||
font-size: 1rem;
|
||||
font-weight: 700;
|
||||
background-color: var(--white);
|
||||
border: var(--border-medium) solid var(--black);
|
||||
box-shadow: 4px 4px 0 var(--black);
|
||||
transition: transform 0.1s, box-shadow 0.1s;
|
||||
}
|
||||
|
||||
.text-input:focus,
|
||||
.select-input:focus {
|
||||
outline: none;
|
||||
transform: translate(2px, 2px);
|
||||
box-shadow: 2px 2px 0 var(--black);
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
.btn {
|
||||
width: 100%;
|
||||
padding: 16px;
|
||||
font-family: 'Space Grotesk', monospace, sans-serif;
|
||||
font-size: 1rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
border: var(--border-medium) solid var(--black);
|
||||
cursor: pointer;
|
||||
transition: transform 0.1s, box-shadow 0.1s;
|
||||
margin-bottom: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
transform: translate(4px, 4px);
|
||||
}
|
||||
|
||||
.btn:active {
|
||||
transform: translate(6px, 6px);
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: var(--neon-pink);
|
||||
box-shadow: 6px 6px 0 var(--black);
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background-color: var(--neon-yellow);
|
||||
box-shadow: 4px 4px 0 var(--black);
|
||||
}
|
||||
|
||||
.btn-accent {
|
||||
background-color: var(--white);
|
||||
box-shadow: 4px 4px 0 var(--black);
|
||||
}
|
||||
|
||||
.btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Canvas Container */
|
||||
.canvas-container {
|
||||
background-color: var(--white);
|
||||
border: var(--border-medium) solid var(--black);
|
||||
padding: var(--spacing-md);
|
||||
margin-bottom: var(--spacing-md);
|
||||
min-height: 400px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#mazeCanvas {
|
||||
border: 2px solid var(--black);
|
||||
}
|
||||
|
||||
/* Info Box */
|
||||
.info-box {
|
||||
background-color: var(--neon-yellow);
|
||||
border: var(--border-medium) solid var(--black);
|
||||
padding: var(--spacing-md);
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.info-box strong {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* Results Content */
|
||||
.results-content {
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.placeholder-text {
|
||||
color: var(--gray-dark);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.result-section {
|
||||
background-color: var(--white);
|
||||
border: var(--border-medium) solid var(--black);
|
||||
padding: var(--spacing-md);
|
||||
margin-bottom: var(--spacing-md);
|
||||
}
|
||||
|
||||
.result-section h3 {
|
||||
font-size: 1.2rem;
|
||||
margin-bottom: var(--spacing-sm);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.result-item {
|
||||
padding: 8px;
|
||||
border-bottom: 2px solid var(--black);
|
||||
}
|
||||
|
||||
.result-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.result-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.result-table th,
|
||||
.result-table td {
|
||||
padding: 8px;
|
||||
text-align: left;
|
||||
border: 2px solid var(--black);
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.result-table th {
|
||||
background-color: var(--neon-yellow);
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.result-table tr:nth-child(even) {
|
||||
background-color: var(--gray-bg);
|
||||
}
|
||||
|
||||
/* Modal */
|
||||
.modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.8);
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.modal.active {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background-color: var(--white);
|
||||
border: var(--border-thick) solid var(--black);
|
||||
box-shadow: 12px 12px 0 var(--neon-pink);
|
||||
padding: var(--spacing-lg);
|
||||
max-width: 500px;
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
font-size: 1.8rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: var(--spacing-md);
|
||||
}
|
||||
|
||||
.file-list {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
margin-bottom: var(--spacing-md);
|
||||
}
|
||||
|
||||
.file-item {
|
||||
padding: 12px;
|
||||
background-color: var(--gray-bg);
|
||||
border: var(--border-medium) solid var(--black);
|
||||
margin-bottom: 8px;
|
||||
cursor: pointer;
|
||||
font-weight: 700;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.file-item:hover {
|
||||
background-color: var(--neon-cyan);
|
||||
}
|
||||
|
||||
/* Status Messages */
|
||||
.status-message {
|
||||
padding: var(--spacing-md);
|
||||
border: var(--border-medium) solid var(--black);
|
||||
margin-bottom: var(--spacing-md);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.status-success {
|
||||
background-color: var(--neon-green);
|
||||
}
|
||||
|
||||
.status-error {
|
||||
background-color: var(--neon-pink);
|
||||
}
|
||||
|
||||
.status-info {
|
||||
background-color: var(--neon-cyan);
|
||||
}
|
||||
|
||||
/* Loading Spinner */
|
||||
.loading {
|
||||
display: inline-block;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 3px solid var(--black);
|
||||
border-radius: 0;
|
||||
border-top-color: transparent;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
385
web/static/js/app.js
Normal file
385
web/static/js/app.js
Normal 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
125
web/static/js/visualizer.js
Normal 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);
|
||||
}
|
||||
Reference in New Issue
Block a user