import React, { useState, useEffect } from 'react';
import { ChevronRight } from 'lucide-react';
// Utility function to calculate Manhattan distance (heuristic for A*)
const manhattanDistance = (a, b) => {
return Math.abs(a.x - b.x) + Math.abs(a.y - b.y);
};
// A* Pathfinding algorithm
const aStar = (grid, start, goal, occupiedCells) => {
const openSet = [start];
const cameFrom = new Map();
const gScore = new Map();
const fScore = new Map();
gScore.set(`${start.x},${start.y}`, 0);
fScore.set(`${start.x},${start.y}`, manhattanDistance(start, goal));
while (openSet.length > 0) {
// Find the node with the lowest fScore
const current = openSet.reduce((lowest, node) =>
(fScore.get(`${node.x},${node.y}`) ?? Infinity) <
(fScore.get(`${lowest.x},${lowest.y}`) ?? Infinity)
? node : lowest
);
// Check if we've reached the goal
if (current.x === goal.x && current.y === goal.y) {
const path = [];
let curr = current;
while (curr) {
path.unshift(curr);
curr = cameFrom.get(`${curr.x},${curr.y}`);
}
return path;
}
openSet.splice(openSet.indexOf(current), 1);
// Check neighboring cells (up, right, down, left)
const neighbors = [
{x: current.x, y: current.y - 1}, // Up
{x: current.x + 1, y: current.y}, // Right
{x: current.x, y: current.y + 1}, // Down
{x: current.x - 1, y: current.y} // Left
];
for (const neighbor of neighbors) {
// Skip if out of grid or occupied
if (
neighbor.x < 0 || neighbor.x >= grid[0].length ||
neighbor.y < 0 || neighbor.y >= grid.length ||
grid[neighbor.y][neighbor.x] === 1 ||
occupiedCells.some(cell =>
cell.x === neighbor.x && cell.y === neighbor.y)
) continue;
const tentativeGScore =
(gScore.get(`${current.x},${current.y}`) ?? Infinity) + 1;
const neighborKey = `${neighbor.x},${neighbor.y}`;
if (tentativeGScore < (gScore.get(neighborKey) ?? Infinity)) {
cameFrom.set(neighborKey, current);
gScore.set(neighborKey, tentativeGScore);
fScore.set(neighborKey,
tentativeGScore + manhattanDistance(neighbor, goal)
);
if (!openSet.some(n => n.x === neighbor.x && n.y === neighbor.y)) {
openSet.push(neighbor);
}
}
}
}
return []; // No path found
};
const AutonomousCarSimulation = () => {
// Create a grid that resembles city streets
const [grid, setGrid] = useState(() => {
const gridSize = 15;
const newGrid = Array(gridSize).fill().map(() =>
Array(gridSize).fill(0)
);
// Create street-like pattern with some blocked areas
for (let i = 0; i < gridSize; i++) {
for (let j = 0; j < gridSize; j++) {
// Create street-like blocks
if (i % 3 === 2 || j % 3 === 2) {
newGrid[i][j] = 0; // Street
} else {
newGrid[i][j] = 1; // Building block
}
}
}
return newGrid;
});
// Initialize cars with random start and goal positions
const [cars, setCars] = useState(() => {
const carCount = 4;
const gridSize = grid.length;
return Array(carCount).fill().map(() => {
let start, goal;
do {
start = {
x: Math.floor(Math.random() * gridSize),
y: Math.floor(Math.random() * gridSize)
};
goal = {
x: Math.floor(Math.random() * gridSize),
y: Math.floor(Math.random() * gridSize)
};
} while (
grid[start.y][start.x] === 1 ||
grid[goal.y][goal.x] === 1 ||
start.x === goal.x && start.y === goal.y
);
return {
id: Math.random().toString(36).substr(2, 9),
position: start,
goal: goal,
path: []
};
});
});
// Simulation step
const simulationStep = () => {
setCars(prevCars => {
// Calculate occupied cells (excluding current car's position)
const occupiedCells = prevCars.flatMap(car =>
car.path.length > 1 ? [car.path[1]] : []
);
// Recalculate paths for each car considering other cars
return prevCars.map(car => {
// If no path or reached first step, calculate new path
if (car.path.length <= 1) {
const newPath = aStar(
grid,
car.position,
car.goal,
occupiedCells.filter(
cell => cell.x !== car.position.x || cell.y !== car.position.y
)
);
return {
...car,
path: newPath
};
}
// Move along the path
const [, nextStep, ...remainingPath] = car.path;
return {
...car,
position: nextStep || car.position,
path: remainingPath
};
});
});
};
// Auto-step simulation
useEffect(() => {
const intervalId = setInterval(simulationStep, 500);
return () => clearInterval(intervalId);
}, []);
// Render the grid
return (
<div className="flex flex-col items-center p-4 bg-gray-100">
<h2 className="text-xl font-bold mb-4">
Autonomous Cars A* Pathfinding Simulation
</h2>
<div
className="grid gap-1"
style={{
gridTemplateColumns: `repeat(${grid[0].length}, 30px)`,
gridTemplateRows: `repeat(${grid.length}, 30px)`
}}
>
{grid.map((row, y) =>
row.map((cell, x) => (
<div
key={`${x},${y}`}
className={`
w-[30px] h-[30px] border
${cell === 1 ? 'bg-gray-300' : 'bg-white'}
relative
`}
>
{cars.map(car =>
car.position.x === x && car.position.y === y ? (
<div
key={car.id}
className="absolute inset-0 flex items-center justify-center"
>
<ChevronRight
className={`
w-5 h-5
${['text-red-500', 'text-blue-500', 'text-green-500', 'text-purple-500'][cars.indexOf(car)]}
`}
/>
</div>
) : null
)}
</div>
))
)}
</div>
</div>
);
};
export default AutonomousCarSimulation;