A* Algorithm Repository/
├── README.md # Main learning guide
├── demo.html # Interactive visualization
├── visualization.js # Core A* implementation
├── 01-graph-theory-basics.md # Foundational concepts
├── 02-search-algorithms.md # Algorithm comparisons
├── 03-heuristic-mathematics.md # Heuristic functions
├── 04-node-evaluation.md # Node scoring system
├── 05-set-management.md # Data structure management
├── 06-path-reconstruction.md # Path building techniques
├── 07-complete-implementation.md # Production code
├── 08-applications.md # Real-world uses
├── 09-optimizations.md # Performance improvements
├── 10-demo-guide.md # Interactive tutorial
└── 11-code-walkthrough.md # This file
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>A* Pathfinding Visualizer</title>
<style>
/* CSS styling for the visualization */
</style>
</head>Purpose: Standard HTML5 document structure with responsive viewport.
.grid {
display: grid;
grid-template-columns: repeat(var(--grid-width), 20px);
grid-template-rows: repeat(var(--grid-height), 20px);
gap: 1px;
background-color: #333;
padding: 10px;
border-radius: 5px;
}Key Points:
- Uses CSS Grid for perfect cell alignment
- Dynamic sizing with CSS custom properties
- Visual separation with gaps and background
.cell {
width: 20px;
height: 20px;
background-color: white;
border: 1px solid #ddd;
cursor: pointer;
transition: background-color 0.2s ease;
}
.cell.obstacle { background-color: #333; }
.cell.start { background-color: #4CAF50; }
.cell.goal { background-color: #f44336; }
.cell.open { background-color: #FFC107; }
.cell.closed { background-color: #FF9800; }
.cell.path { background-color: #2196F3; }Design Rationale:
- Distinct colors for each state
- Smooth transitions for visual feedback
- Accessible color choices
const GRID_WIDTH = 30;
const GRID_HEIGHT = 20;
let grid = [];
let startNode = null;
let goalNode = null;
let isSearching = false;Analysis:
- Constants for grid dimensions (easy to modify)
- Global state management
- Search state tracking
function initializeGrid() {
grid = [];
for (let y = 0; y < GRID_HEIGHT; y++) {
grid[y] = [];
for (let x = 0; x < GRID_WIDTH; x++) {
grid[y][x] = {
x: x,
y: y,
isObstacle: false,
gCost: Infinity,
hCost: 0,
fCost: Infinity,
parent: null,
element: null
};
}
}
}Deep Dive:
- 2D Array Structure:
grid[y][x]follows mathematical convention - Node Properties:
x, y: Coordinates for identificationisObstacle: Boolean for walkabilitygCost: Distance from start (actual cost)hCost: Heuristic distance to goal (estimated cost)fCost: Total cost (g + h)parent: For path reconstructionelement: DOM reference for visualization
function createGridDOM() {
const gridContainer = document.getElementById('grid');
gridContainer.innerHTML = '';
gridContainer.style.setProperty('--grid-width', GRID_WIDTH);
gridContainer.style.setProperty('--grid-height', GRID_HEIGHT);
for (let y = 0; y < GRID_HEIGHT; y++) {
for (let x = 0; x < GRID_WIDTH; x++) {
const cell = document.createElement('div');
cell.className = 'cell';
cell.dataset.x = x;
cell.dataset.y = y;
// Event listeners for interaction
cell.addEventListener('click', handleCellClick);
cell.addEventListener('contextmenu', handleRightClick);
gridContainer.appendChild(cell);
grid[y][x].element = cell;
}
}
}Implementation Details:
- CSS Custom Properties: Dynamic grid sizing
- Data Attributes: Store coordinates in DOM
- Event Delegation: Efficient event handling
- DOM Reference: Link between data and visualization
function handleCellClick(event) {
if (isSearching) return;
const x = parseInt(event.target.dataset.x);
const y = parseInt(event.target.dataset.y);
const node = grid[y][x];
if (event.ctrlKey || event.metaKey) {
// Set goal with Ctrl+Click
setGoal(x, y);
} else {
// Toggle obstacle with regular click
toggleObstacle(x, y);
}
}
function handleRightClick(event) {
event.preventDefault();
if (isSearching) return;
const x = parseInt(event.target.dataset.x);
const y = parseInt(event.target.dataset.y);
setStart(x, y);
}User Experience Design:
- Modifier Keys: Ctrl/Cmd for goal setting
- Right Click: Intuitive start point setting
- State Checking: Prevent interaction during search
- Event Prevention: Stop context menu on right-click
function manhattanDistance(nodeA, nodeB) {
return Math.abs(nodeA.x - nodeB.x) + Math.abs(nodeA.y - nodeB.y);
}
function euclideanDistance(nodeA, nodeB) {
const dx = nodeA.x - nodeB.x;
const dy = nodeA.y - nodeB.y;
return Math.sqrt(dx * dx + dy * dy);
}Mathematical Analysis:
-
Manhattan:
|x₁ - x₂| + |y₁ - y₂|- Grid-based movement (4-directional)
- Always admissible for grid pathfinding
- Faster computation (no square root)
-
Euclidean:
√[(x₁ - x₂)² + (y₁ - y₂)²]- Straight-line distance
- More accurate for diagonal movement
- Computationally more expensive
function getNeighbors(node) {
const neighbors = [];
const directions = [
[-1, 0], [1, 0], [0, -1], [0, 1], // Cardinal directions
[-1, -1], [-1, 1], [1, -1], [1, 1] // Diagonal directions
];
for (const [dx, dy] of directions) {
const newX = node.x + dx;
const newY = node.y + dy;
// Bounds checking
if (newX >= 0 && newX < GRID_WIDTH &&
newY >= 0 && newY < GRID_HEIGHT) {
const neighbor = grid[newY][newX];
// Skip obstacles
if (!neighbor.isObstacle) {
neighbors.push(neighbor);
}
}
}
return neighbors;
}Algorithm Considerations:
- 8-Directional Movement: Includes diagonals
- Bounds Checking: Prevents array access errors
- Obstacle Filtering: Only returns walkable cells
- Coordinate System: Consistent with grid structure
function getMovementCost(nodeA, nodeB) {
const dx = Math.abs(nodeA.x - nodeB.x);
const dy = Math.abs(nodeA.y - nodeB.y);
// Diagonal movement costs more
if (dx === 1 && dy === 1) {
return 1.414; // √2 ≈ 1.414
}
return 1; // Cardinal movement
}Cost Rationale:
- Cardinal Moves: Cost of 1 (unit distance)
- Diagonal Moves: Cost of √2 ≈ 1.414 (actual distance)
- Realistic Pathfinding: Prevents unrealistic diagonal shortcuts
async function findPath() {
if (!startNode || !goalNode || isSearching) {
return;
}
isSearching = true;
resetPathfinding();
// Initialize data structures
const openSet = [startNode];
const closedSet = new Set();
// Initialize start node
startNode.gCost = 0;
startNode.hCost = manhattanDistance(startNode, goalNode);
startNode.fCost = startNode.gCost + startNode.hCost;
while (openSet.length > 0) {
// Find node with lowest f-cost
let currentNode = openSet[0];
for (let i = 1; i < openSet.length; i++) {
if (openSet[i].fCost < currentNode.fCost ||
(openSet[i].fCost === currentNode.fCost &&
openSet[i].hCost < currentNode.hCost)) {
currentNode = openSet[i];
}
}
// Remove current from open set
const currentIndex = openSet.indexOf(currentNode);
openSet.splice(currentIndex, 1);
// Add to closed set
closedSet.add(currentNode);
// Visualization update
if (currentNode !== startNode && currentNode !== goalNode) {
currentNode.element.classList.add('closed');
}
// Check if goal reached
if (currentNode === goalNode) {
reconstructPath(currentNode);
isSearching = false;
return;
}
// Process neighbors
const neighbors = getNeighbors(currentNode);
for (const neighbor of neighbors) {
if (closedSet.has(neighbor)) {
continue; // Skip already evaluated nodes
}
const tentativeGCost = currentNode.gCost +
getMovementCost(currentNode, neighbor);
if (!openSet.includes(neighbor)) {
// New node discovered
openSet.push(neighbor);
// Visualization
if (neighbor !== goalNode) {
neighbor.element.classList.add('open');
}
} else if (tentativeGCost >= neighbor.gCost) {
continue; // Not a better path
}
// Update node with better path
neighbor.parent = currentNode;
neighbor.gCost = tentativeGCost;
neighbor.hCost = manhattanDistance(neighbor, goalNode);
neighbor.fCost = neighbor.gCost + neighbor.hCost;
}
// Delay for visualization
await sleep(50);
}
// No path found
isSearching = false;
alert('No path found!');
}Algorithm Breakdown:
-
Initialization:
- Open set starts with start node
- Closed set is empty
- Start node has g-cost of 0
-
Main Loop:
- Continue while open set has nodes
- Select node with lowest f-cost
- Tie-break with h-cost (closer to goal)
-
Node Processing:
- Move current from open to closed
- Check if goal reached
- Evaluate all neighbors
-
Neighbor Evaluation:
- Skip if in closed set
- Calculate tentative g-cost
- Update if better path found
- Add to open set if new
-
Path Reconstruction:
- Follow parent pointers from goal to start
- Visualize final path
function reconstructPath(node) {
const path = [];
let current = node;
// Follow parent chain
while (current !== null) {
path.unshift(current);
current = current.parent;
}
// Visualize path
for (let i = 1; i < path.length - 1; i++) {
path[i].element.classList.add('path');
}
console.log(`Path found! Length: ${path.length} nodes`);
return path;
}Implementation Notes:
- Reverse Traversal: Start from goal, follow parents to start
- Array Building:
unshift()maintains correct order - Visualization: Skip start/goal nodes for path coloring
- Return Value: Complete path for further analysis
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function resetPathfinding() {
// Clear all visual states
document.querySelectorAll('.cell').forEach(cell => {
cell.classList.remove('open', 'closed', 'path');
});
// Reset node properties
for (let y = 0; y < GRID_HEIGHT; y++) {
for (let x = 0; x < GRID_WIDTH; x++) {
const node = grid[y][x];
node.gCost = Infinity;
node.hCost = 0;
node.fCost = Infinity;
node.parent = null;
}
}
}
function clearGrid() {
for (let y = 0; y < GRID_HEIGHT; y++) {
for (let x = 0; x < GRID_WIDTH; x++) {
const node = grid[y][x];
node.isObstacle = false;
node.element.classList.remove('obstacle');
}
}
}Utility Purpose:
- sleep(): Async delay for visualization timing
- resetPathfinding(): Clean slate for new searches
- clearGrid(): Remove all obstacles
const node = {
x: 5, // Grid x-coordinate
y: 3, // Grid y-coordinate
isObstacle: false, // Walkability flag
gCost: 7, // Distance from start
hCost: 4, // Heuristic to goal
fCost: 11, // Total cost (g + h)
parent: nodeRef, // Previous node in path
element: domRef // DOM element reference
};Current Implementation: Array with linear search
// Finding minimum f-cost: O(n)
let currentNode = openSet[0];
for (let i = 1; i < openSet.length; i++) {
if (openSet[i].fCost < currentNode.fCost) {
currentNode = openSet[i];
}
}Optimization Opportunity: Priority queue would be O(log n)
Current Implementation: JavaScript Set
const closedSet = new Set();
closedSet.add(node); // O(1)
closedSet.has(node); // O(1)Efficiency: Optimal for membership testing
- Best Case: O(b^d) where b is branching factor, d is depth
- Average Case: Depends on heuristic quality
- Worst Case: O(b^d) when heuristic provides no guidance
- Open Set: O(b^d) in worst case
- Closed Set: O(b^d) nodes explored
- Grid Storage: O(width × height)
- Priority Queue: Replace array-based open set
- Coordinate Hashing: More efficient node lookup
- Memory Pooling: Reduce garbage collection
- SIMD Operations: Vectorized heuristic calculations
function validateInput() {
if (!startNode) {
alert('Please set a start position (right-click)');
return false;
}
if (!goalNode) {
alert('Please set a goal position (Ctrl+click)');
return false;
}
if (startNode === goalNode) {
alert('Start and goal cannot be the same!');
return false;
}
return true;
}- Grid Bounds: Checked in
getNeighbors() - Obstacle Collision: Filtered during neighbor generation
- No Path Exists: Detected when open set becomes empty
- DOM References: Cleaned up during reset
- Event Listeners: Properly attached and managed
- Large Grids: Consider chunking for very large datasets
function debugNode(node) {
console.log(`Node (${node.x}, ${node.y}):`, {
gCost: node.gCost,
hCost: node.hCost,
fCost: node.fCost,
parent: node.parent ? `(${node.parent.x}, ${node.parent.y})` : null
});
}function measurePerformance() {
const startTime = performance.now();
// Run pathfinding
findPath().then(() => {
const endTime = performance.now();
console.log(`Pathfinding took ${endTime - startTime} milliseconds`);
});
}function testManhattanDistance() {
const nodeA = { x: 0, y: 0 };
const nodeB = { x: 3, y: 4 };
const expected = 7;
const actual = manhattanDistance(nodeA, nodeB);
console.assert(actual === expected,
`Expected ${expected}, got ${actual}`);
}
function testPathReconstruction() {
// Create a simple 3-node path
const start = { x: 0, y: 0, parent: null };
const middle = { x: 1, y: 0, parent: start };
const goal = { x: 2, y: 0, parent: middle };
const path = reconstructPath(goal);
console.assert(path.length === 3,
`Expected path length 3, got ${path.length}`);
console.assert(path[0] === start, 'First node should be start');
console.assert(path[2] === goal, 'Last node should be goal');
}- Constants:
UPPER_SNAKE_CASE - Variables:
camelCase - Functions:
camelCasewith descriptive verbs - Classes:
PascalCase(if using classes)
- Single Responsibility: Each function has one clear purpose
- Pure Functions: Heuristic calculations have no side effects
- Async Handling: Proper use of async/await for visualization
/**
* Calculates the Manhattan distance between two nodes
* @param {Object} nodeA - First node with x, y properties
* @param {Object} nodeB - Second node with x, y properties
* @returns {number} Manhattan distance between nodes
*/
function manhattanDistance(nodeA, nodeB) {
return Math.abs(nodeA.x - nodeB.x) + Math.abs(nodeA.y - nodeB.y);
}- Type Checking: Validate input parameters
- Null Checks: Handle undefined/null values
- Bounds Checking: Prevent array access errors
- State Validation: Ensure valid algorithm state
Previous: Interactive Demo Guide Next: Testing and Validation
Interactive: Explore the code