diff --git a/ANIMATION_FEATURE.md b/ANIMATION_FEATURE.md new file mode 100644 index 0000000..8189c03 --- /dev/null +++ b/ANIMATION_FEATURE.md @@ -0,0 +1,138 @@ +# Animation Feature - Technical Documentation + +## Overview +The maze solving animation feature provides real-time visualization of pathfinding algorithms (DFS and BFS) with interactive controls. + +## Features Implemented + +### 1. Animated Solving Process +When a user clicks either "SOLVE (DFS)" or "SOLVE (BFS)" button: +- The algorithm executes on the server and returns the complete solution +- The visualization then **animates** the solving process step-by-step: + 1. **Phase 1**: Shows cells being visited in order (gray cells) + 2. **Phase 2**: Highlights the final solution path (green cells) + +### 2. Animation Controls +A control panel appears during animation with: + +#### **PAUSE/RESUME Button** +- Click to pause the animation at any point +- Button text changes to "RESUME" when paused +- Click again to continue from where it was paused + +#### **STOP Button** +- Immediately stops the animation +- Shows the complete final result (all visited cells + solution path) +- Hides the animation controls +- Re-enables solve buttons + +#### **SPEED Slider** +- Range: 1 (SLOW) to 100 (FAST) +- Dynamically adjusts animation speed +- Changes take effect immediately, even during animation +- SLOW = 200ms per step +- FAST = 10ms per step + +### 3. Neo-Brutalism Styling +The animation controls follow the same design language: +- **White background** with **6px black border** +- **Pink drop shadow** (6px offset) +- **Yellow PAUSE/STOP buttons** with hard shadows +- **Pink slider thumb** with black border +- **Bold uppercase typography** + +## Technical Implementation + +### Animation State Management +```javascript +animationState = { + isAnimating: boolean, // Whether animation is running + isPaused: boolean, // Whether animation is paused + currentStep: number, // Current animation step + visitedCells: array, // All cells visited during solving + solutionPath: array, // Final solution path + animationId: timeout, // setTimeout ID for cancellation + speed: number // Delay between steps in ms +} +``` + +### Animation Flow +1. User clicks solve button +2. API call fetches solution data +3. `startSolvingAnimation()` initializes state and shows controls +4. `animateStep()` recursively renders each step: + - First loop: Animate visited cells + - Second loop: Animate solution path (slower) +5. `finishAnimation()` completes and cleans up + +### Button States +- **During Animation**: Solve buttons disabled, controls visible +- **When Paused**: Animation frozen, can resume or stop +- **After Stop/Finish**: Controls hidden, solve buttons re-enabled + +## User Experience + +### Visual Feedback +- **Gray cells**: Cells being explored by the algorithm +- **Green cells**: Final solution path +- **Yellow cell (S)**: Start position +- **Pink cell (E)**: End position + +### Animation Speed +- **Default**: Medium speed (50/100 = 110ms per step) +- **Visited cells**: Uses slider speed +- **Solution path**: 2x slower for clarity + +### Control Visibility +- Controls **appear** only when animation starts +- Controls **hide** when animation completes or is stopped +- Prevents UI clutter when not needed + +## Code Changes + +### Files Modified +1. **web/static/js/app.js** + - Added animation state management + - Implemented `startSolvingAnimation()` + - Implemented `animateStep()` with recursive timeout + - Added `togglePause()`, `stopAnimation()`, `finishAnimation()` + - Added `updateSpeed()` for slider + +2. **web/templates/index.html** + - Added animation controls section + - Added pause/stop buttons + - Added speed slider with labels + +3. **web/static/css/styles.css** + - Styled `.animation-controls` with Neo-Brutalism theme + - Styled control buttons + - Styled speed slider (custom thumb styling) + - Added speed labels + +4. **README.md** + - Documented new animation feature + - Added usage instructions for controls + +## Benefits + +### Educational Value +- Users can **see** how DFS vs BFS explores the maze differently +- DFS tends to go deep before backtracking +- BFS explores level-by-level (visible in animation) + +### Engagement +- Makes the solving process **interactive** and **fun** +- Users can pause to study specific steps +- Speed control allows customization for different maze sizes + +### Debugging/Analysis +- Developers can observe algorithm behavior +- Useful for understanding performance characteristics +- Can see exactly which cells each algorithm visits + +## Future Enhancements (Not Implemented) +- Step-by-step forward/backward controls +- Export animation as GIF/video +- Comparison mode (run DFS and BFS side-by-side) +- Generation algorithm animation +- Custom color themes for visited/solution cells diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..b948cbd --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,46 @@ +# Changelog + +## [1.1.0] - 2025-01-20 + +### Added +- **Animated Solving Visualization** + - Real-time step-by-step animation of DFS and BFS algorithms + - Two-phase animation: visited cells exploration, then solution path highlighting + - Animation controls panel with Neo-Brutalism styling + - Pause/Resume button to control animation flow + - Stop button to immediately halt and show final result + - Speed slider (SLOW to FAST) for dynamic speed adjustment + - Automatic control visibility management + +- **Footer** + - Copyleft notice with reversed copyright symbol (🄯) + - "Made with ❤️ and Python in New Jersey" message + - Black background with white text and neon pink accent + - Cyan drop shadow with negative offset (Neo-Brutalism style) + +### Removed +- **Solver Dropdown** - Redundant UI element removed for cleaner interface + - Users now interact directly with "SOLVE (DFS)" and "SOLVE (BFS)" buttons + +### Changed +- Solve buttons now trigger animated solving instead of instant results +- Solve buttons disabled during active animation to prevent conflicts +- Results display updated to show during animation + +### Technical Details +- Added animation state management in `app.js` +- Implemented recursive timeout-based animation loop +- Added event listeners for pause, stop, and speed controls +- Enhanced Neo-Brutalism CSS with footer and animation control styles + +## [1.0.0] - 2025-01-20 + +### Initial Release +- 8 maze generation algorithms +- 2 solving algorithms (DFS, BFS) +- Neo-Brutalism web interface +- Image export functionality +- Save/load maze files +- Analysis and benchmarking tools +- Docker containerization +- Comprehensive test suite (>90% coverage) diff --git a/README.md b/README.md index 00c4c2f..a48e2e0 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ Built with **Neo-Brutalism** design aesthetics featuring: - Hard drop shadows - Chunky typography (Space Grotesk font) - Asymmetric layouts +- **Animated solving** with pause/stop controls and adjustable speed ### 📊 Analysis Tools - Dead end counting and percentage @@ -103,11 +104,17 @@ The web interface provides 9 main operations: 3. **Download Image** - Save maze as PNG file 4. **Save to File** - Persist maze as JSON 5. **Load from File** - Restore saved maze -6. **Solve (DFS)** - Find path using Depth-First Search -7. **Solve (BFS)** - Find shortest path using Breadth-First Search +6. **Solve (DFS)** - Find path using Depth-First Search with animated visualization +7. **Solve (BFS)** - Find shortest path using Breadth-First Search with animated visualization 8. **Analyze** - Compute maze statistics and metrics 9. **Benchmark** - Compare algorithm performance +#### Animation Controls +When solving a maze, you can: +- **Pause/Resume** - Pause the animation and resume where you left off +- **Stop** - Stop the animation and show the final result +- **Adjust Speed** - Use the slider to control animation speed (slow to fast) + ### API Endpoints #### Generate Maze diff --git a/run.bat b/run.bat new file mode 100644 index 0000000..cd9ed48 --- /dev/null +++ b/run.bat @@ -0,0 +1,26 @@ +@echo off +echo ======================================== +echo MAZE GENERATOR - Starting Server +echo ======================================== +echo. + +REM Check if virtual environment exists +if not exist "venv" ( + echo Creating virtual environment... + python -m venv venv +) + +REM Activate virtual environment +call venv\Scripts\activate.bat + +REM Install requirements if needed +echo Installing/updating dependencies... +pip install -q -r requirements.txt + +REM Start the Flask application +echo. +echo Starting Flask server on http://localhost:5000 +echo. +echo Press Ctrl+C to stop the server +echo. +python api\app.py diff --git a/run.sh b/run.sh new file mode 100644 index 0000000..5c40228 --- /dev/null +++ b/run.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +echo "========================================" +echo " MAZE GENERATOR - Starting Server" +echo "========================================" +echo "" + +# Check if virtual environment exists +if [ ! -d "venv" ]; then + echo "Creating virtual environment..." + python3 -m venv venv +fi + +# Activate virtual environment +source venv/bin/activate + +# Install requirements if needed +echo "Installing/updating dependencies..." +pip install -q -r requirements.txt + +# Start the Flask application +echo "" +echo "Starting Flask server on http://localhost:5000" +echo "" +echo "Press Ctrl+C to stop the server" +echo "" +python api/app.py diff --git a/web/static/css/styles.css b/web/static/css/styles.css index 65c0797..6e3c9ed 100644 --- a/web/static/css/styles.css +++ b/web/static/css/styles.css @@ -356,6 +356,75 @@ body { background-color: var(--neon-cyan); } +/* Animation Controls */ +.animation-controls { + background-color: var(--white); + border: var(--border-thick) solid var(--black); + padding: var(--spacing-md); + margin-bottom: var(--spacing-md); + box-shadow: 6px 6px 0 var(--neon-pink); +} + +.control-title { + font-size: 1.2rem; + font-weight: 700; + text-transform: uppercase; + margin-bottom: var(--spacing-md); + letter-spacing: 2px; +} + +.control-buttons { + display: grid; + grid-template-columns: 1fr 1fr; + gap: var(--spacing-sm); + margin-bottom: var(--spacing-md); +} + +.btn-control { + background-color: var(--neon-yellow); + box-shadow: 4px 4px 0 var(--black); + padding: 12px; +} + +.speed-slider { + width: 100%; + height: 8px; + background: var(--white); + border: var(--border-medium) solid var(--black); + outline: none; + -webkit-appearance: none; + margin-bottom: 8px; +} + +.speed-slider::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 24px; + height: 24px; + background: var(--neon-pink); + border: var(--border-medium) solid var(--black); + cursor: pointer; + box-shadow: 2px 2px 0 var(--black); +} + +.speed-slider::-moz-range-thumb { + width: 24px; + height: 24px; + background: var(--neon-pink); + border: var(--border-medium) solid var(--black); + cursor: pointer; + box-shadow: 2px 2px 0 var(--black); + border-radius: 0; +} + +.speed-labels { + display: flex; + justify-content: space-between; + font-size: 0.75rem; + font-weight: 700; + letter-spacing: 1px; +} + /* Loading Spinner */ .loading { display: inline-block; @@ -370,3 +439,41 @@ body { @keyframes spin { to { transform: rotate(360deg); } } + +/* Footer */ +.footer { + margin-top: var(--spacing-lg); + background-color: var(--black); + color: var(--white); + border: var(--border-thick) solid var(--black); + padding: var(--spacing-lg); + box-shadow: calc(var(--shadow-offset) * -1) calc(var(--shadow-offset) * -1) 0 var(--neon-cyan); +} + +.footer-content { + text-align: center; +} + +.copyleft { + font-size: 0.9rem; + font-weight: 700; + letter-spacing: 2px; + margin-bottom: var(--spacing-sm); + display: flex; + align-items: center; + justify-content: center; + gap: 8px; +} + +.copyleft-symbol { + font-size: 1.5rem; + display: inline-block; + transform: scaleX(-1); +} + +.footer-message { + font-size: 1.1rem; + font-weight: 700; + letter-spacing: 1px; + color: var(--neon-pink); +} diff --git a/web/static/js/app.js b/web/static/js/app.js index 8fb47d9..341a828 100644 --- a/web/static/js/app.js +++ b/web/static/js/app.js @@ -4,6 +4,15 @@ 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'); @@ -15,15 +24,18 @@ 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 solverSelect = document.getElementById('solver'); const resultsContent = document.getElementById('resultsContent'); const mazeInfo = document.getElementById('mazeInfo'); +const animationControls = document.getElementById('animationControls'); // Disable buttons initially function disableActionButtons() { @@ -56,6 +68,9 @@ 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() { @@ -209,13 +224,16 @@ async function loadMazeFile(filename) { } } -// Solve Maze +// 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); @@ -227,21 +245,141 @@ async function solveMaze(algorithm) { }); const data = await response.json(); + setLoading(btn, false); if (data.success) { - renderMaze(currentMazeData, data.visited, data.path); + // Start animated solving + startSolvingAnimation(data); 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); } } +// 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) { diff --git a/web/templates/index.html b/web/templates/index.html index cf98470..49912af 100644 --- a/web/templates/index.html +++ b/web/templates/index.html @@ -57,14 +57,6 @@ -
- - -
- @@ -72,6 +64,23 @@ 7. SOLVE (BFS) + + +