// Cloned by Divya Venketasan Shanmugam on 9 Nov 2024 from World "[clone] A star project" by Rasinkton Fernando
// Please leave this clone trail here.
// reroute happening all success
// moving with one step each car
// Please leave this clone trail here.
const diagonal = false; // No diagonal movement for a street-like setup
const cw = 600; // Canvas width
const ch = 600; // Canvas height
const numCars = 4;
let cols, rows;
let w, h;
const grid = [];
const cars = [];
const wallAmount = 0.8;
const backcolor = 'black';
const roadcolor = 'lightgray';
const pathcolor = 'black'; // Color for the path to make it visible
const carcolors = ['blue', 'green', 'red', 'yellow']; // Different colors for cars
function setup() {
// Divya: Setup and initialization
//frameRate(2);
createCanvas(cw, ch);
cols = floor(width / 20);
rows = floor(height / 20);
w = width / cols;
h = height / rows;
// Create the grid
for (let i = 0; i < cols; i++) {
grid[i] = new Array(rows);
for (let j = 0; j < rows; j++) {
grid[i][j] = new Spot(i, j);
}
}
// Add neighbors to each spot
for (let i = 0; i < cols; i++) {
for (let j = 0; j < rows; j++) {
grid[i][j].addNeighbors(grid);
}
}
// Add cars with retry logic to avoid infinite loops
for (let i = 0; i < numCars; i++) {
let start, end;
let retryCount = 0; // Initialize retryCount for each car
const maxRetries = 100; // Maximum number of retries to avoid infinite loops
// Ensure the start point is valid and not a wall
do {
start = grid[floor(random(cols))][floor(random(rows))];
retryCount++;
if (retryCount > maxRetries) {
console.log("Failed to find valid start point after max retries");
break; // Exit loop if we've tried too many times
}
} while (start.wall); // Continue if the spot is a wall
retryCount = 0; // Reset retryCount for the end point
// Ensure the end point is valid, not a wall, and not the same as the start
do {
end = grid[floor(random(cols))][floor(random(rows))];
retryCount++;
if (retryCount > maxRetries) {
console.log("Failed to find valid end point after max retries");
break; // Exit loop if we've tried too many times
}
} while (end.wall || end === start); // Ensure it's not a wall or the same as the start
// Ensure start and end are not walls once we have valid points
start.wall = end.wall = false;
cars.push(new Car(start, end, carcolors[i]));
}
}
function heuristic(a, b) {
return abs(a.i - b.i) + abs(a.j - b.j);
}
class Spot {
constructor(i, j) {
this.i = i;
this.j = j;
this.f = 0;
this.g = 0;
this.h = 0;
this.neighbors = [];
this.previous = undefined;
// Random street placement logic: 50% chance to have an open street row/column
const isStreetRow = random() < 0.3;
const isStreetCol = random() < 0.7;
// Mark the spot as a wall if it's not part of the road
this.wall = !(isStreetRow || isStreetCol); // Only walls if neither row nor column is open
}
show(col) {
fill(this.wall ? 'black' : col || roadcolor); // Walls are black, roads are light gray
noStroke();
// If the spot is a wall, draw a building (pencil drawn style)
if (this.wall) {
const x = this.i * w;
const y = this.j * h;
// Draw a building (triangle roof and rectangular body)
fill(150); // Roof color (gray for pencil)
triangle(x, y, x + w, y, x + w / 2, y - h / 2); // Roof (triangle)
fill(200); // Building color (lighter gray for body)
rect(x + w / 4, y, w / 2, h / 2); // Building body (rectangle)
} else {
// If the spot is a road, just draw it as a road
fill(roadcolor);
rect(this.i * w, this.j * h, w, h);
}
}
addNeighbors(grid) {
let { i, j } = this;
if (i < cols - 1) this.neighbors.push(grid[i + 1][j]);
if (i > 0) this.neighbors.push(grid[i - 1][j]);
if (j < rows - 1) this.neighbors.push(grid[i][j + 1]);
if (j > 0) this.neighbors.push(grid[i][j - 1]);
if (diagonal) {
if (i < cols - 1 && j < rows - 1) this.neighbors.push(grid[i + 1][j + 1]);
if (i > 0 && j < rows - 1) this.neighbors.push(grid[i - 1][j + 1]);
if (i < cols - 1 && j > 0) this.neighbors.push(grid[i + 1][j - 1]);
if (i > 0 && j > 0) this.neighbors.push(grid[i - 1][j - 1]);
}
}
}
class Car {
constructor(start, end, color) {
this.start = start;
this.end = end;
this.path = [];
this.current = start;
this.recalculatePath(); // Recalculate path on initialization
this.color = color;
this.waiting = false; // Add a waiting flag for the car
this.waitingFrames = 0; // Count how many frames the car has been waiting
this.pathColor = pathcolor; // Default color of the path (black)
this.x = this.current.i * w + w / 2; // Current position for smooth movement
this.y = this.current.j * h + h / 2;
}
recalculatePath() {
let openSet = [this.current]; // Start from the current position
let closedSet = [];
this.path = [];
// Reset each cell's 'previous' property before recalculating the path
grid.flat().forEach(spot => spot.previous = undefined);
// Pathfinding loop
while (openSet.length > 0) {
let winner = openSet.reduce((best, spot, idx) => spot.f < openSet[best].f ? idx : best, 0);
let current = openSet[winner];
if (current === this.end) {
// Check if the end node has a path back to start
if (!this.end.previous) {
console.error("No valid path to the end.");
this.pathColor = 'red';
return;
}
// Path reconstruction loop
let temp = current;
this.path = [];
while (temp) {
this.path.push(temp);
// Break out if there's an issue with temp.previous
if (!temp.previous && temp !== this.current) {
console.error("Path reconstruction failed. Missing 'previous' reference.");
this.path = []; // Clear path as it failed to fully construct
break;
}
temp = temp.previous;
}
this.path.reverse();
return;
}
// Move current node from openSet to closedSet
openSet = openSet.filter((spot) => spot !== current);
closedSet.push(current);
// Evaluate neighbors
current.neighbors.forEach((neighbor) => {
if (!closedSet.includes(neighbor) && !neighbor.wall && !this.isBlocked(neighbor)) {
let tempG = current.g + 1;
if (!openSet.includes(neighbor)) {
openSet.push(neighbor);
} else if (tempG >= neighbor.g) {
return;
}
neighbor.g = tempG;
neighbor.h = heuristic(neighbor, this.end);
neighbor.f = neighbor.g + neighbor.h;
neighbor.previous = current;
}
});
}
// If no path found
this.pathColor = 'red'; // Indicating that no path was found
console.error("No path found");
}
// Check if the car's path is blocked by another car
isBlocked(nextSpot) {
return cars.some((car) => car !== this && car.path.includes(nextSpot));
}
move() {
if (this.path.length > 0) {
let nextSpot = this.path[0];
this.x = nextSpot.i * w + w / 2;
this.y = nextSpot.j * h + h / 2;
this.path.shift();
}
}
show() {
fill(this.color);
noStroke();
ellipse(this.x, this.y, w / 2); // Car is drawn as a circle
}
}
function draw() {
background(backcolor); // Black background
// Divya: Grid rendering
for (let i = 0; i < cols; i++) {
for (let j = 0; j < rows; j++) {
grid[i][j].show();
}
}
// Update each car's position and path
for (let car of cars) {
car.show();
car.move();
// Recalculate path if stuck waiting for too long
if (car.waitingFrames > 100) {
car.recalculatePath();
}
}
}