//Made by Abhishek Malaviya
//Leave this trail here if you clone it
// Update v1.3 features:
// 1) Pathfinding A* Algorithm (Cars will find a fastest route and/or reroute if collision is detected )
// 2) Collision Avoidance and Incremental Waiting (Cars will wait for traffic to clear and if it doesnt, it will reroute after a set frame[partially works])
// 3) Error Prevention: Range Error Handling (Only partially works)
// 4) Makes sure when cars load into the world, it has a start point, end point and a path (only partially works, might be a cause of RangeError)
// 5) Added cars with retry logic to avoid infinite loops if stuck at a point
// Update v1.5:
// Solved Range Error (Cars spawned before and outside the grid, when grid was loaded, cars couldn't find a way inside the grid)
// Cars Reroute when stuck in traffic (works partially)
// Added wait times for car being stuck in traffic and retrying after a set frames(works partially)
// Added debugging tools (console logs) to check proper working of code.
// Will need more debugging and trial methods to make it work properly
// To fix these issues, I am thinking of making each car as a separate class, havings its own wait periods and rerouting logic which could fix these issues.
// Will try to update it in V2.0 where it will be a complete redesign of the code, grid(graphically), and the cars(graphically).
// I have mentioned the resources I have used and referred them in my documentation.
// Disclaimer: I have used Inspect Element's AI(Gemini I think) for debugging and understanding the core of a problem but the entire code is NOT made from GeminiAI or ChatGPT
const diagonal = true;
const cw = 1000;
const ch = 700;
const numCars = 4;
let cols = 20, rows = 15;
let w, h;
const grid = [];
const cars = [];
const wallAmount = 0.4;
const backcolor = 'white';
const wallcolor = 'black';
const carColors = ['red', 'green', 'blue', 'yellow'];
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;
const isStreetRow = random() < 0.5;
const isStreetCol = random() < 0.5;
this.wall = !(isStreetRow || isStreetCol);
}
show(col) {
fill(this.wall ? wallcolor : col || backcolor);
noStroke();
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]);
}
}
class Car {
constructor(start, end, color) {
this.start = start;
this.end = end;
this.path = [];
this.current = start;
this.color = color;
this.recalculatePath();
this.waitingForPath = false;
this.waitTurns = 0;
this.maxWaitTurns = Math.floor(Math.random() * 3) + 1;
this.visited = new Set();
this.waitTimer = 0;
this.maxWaitTime = Math.max(1, Math.floor(this.waitTimer / 2)); // Decrease wait time based on waiting duration
}
recalculatePath() {
console.log(`Car starting at (${this.start.i}, ${this.start.j}) trying to reach (${this.end.i}, ${this.end.j})`);
if (!this.visited) {
this.visited = new Set();
}
if (this.start.i === this.end.i && this.start.j === this.end.j) {
console.error("Invalid path: Start and end positions are the same.");
this.path = [];
this.waitingForPath = true;
return;
}
let openSet = [this.start];
let closedSet = new Set();
this.path = [];
this.start.g = 0;
this.start.f = heuristic(this.start, this.end);
let pathFound = false;
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) {
let temp = current;
this.path = [];
let loopCounter = 0;
while (temp) {
if (!temp.previous || temp.previous === this.start) {
this.path.push(temp);
break;
}
this.path.push(temp);
temp = temp.previous;
loopCounter++;
if (loopCounter > 1000) {
console.error("Possible infinite loop detected while constructing path!");
break;
}
}
this.path.reverse();
pathFound = true;
console.log(`Path found for car! Path length: ${this.path.length}`);
return;
}
openSet = openSet.filter((spot) => spot !== current);
closedSet.add(current);
for (let neighbor of current.neighbors) {
let isCarBlocking = cars.some(car => car.current === neighbor && car !== this);
if (neighbor && !closedSet.has(neighbor) && !neighbor.wall && !isCarBlocking && !this.visited.has(neighbor)) {
let tempG = current.g + 1;
if (!openSet.includes(neighbor)) {
openSet.push(neighbor);
} else if (tempG >= neighbor.g) {
continue;
}
neighbor.g = tempG;
neighbor.h = heuristic(neighbor, this.end);
neighbor.f = neighbor.g + neighbor.h;
neighbor.previous = current;
this.visited.add(neighbor);
}
}
}
if (!pathFound) {
console.log(`No path found for car from (${this.start.i}, ${this.start.j}) to (${this.end.i}, ${this.end.j}). Car is waiting.`);
this.waitingForPath = true;
this.path = [];
}
}
move() {
if (this.waitingForPath) {
this.waitTurns++;
this.waitTimer++;
if (this.waitTurns >= this.maxWaitTurns || this.waitTimer >= this.maxWaitTime) {
console.log(`Car at (${this.current.i}, ${this.current.j}) finished waiting. Recalculating path.`);
this.recalculatePath();
let next = this.path[0];
let isNextPositionOccupied = cars.some(car => car.current === next && car !== this);
if (!isNextPositionOccupied && this.path.length > 0) {
this.waitingForPath = false;
this.waitTurns = 0;
this.waitTimer = 0;
} else {
console.log("Car cannot proceed, still blocked.");
}
}
return;
}
if (this.path.length > 0) {
let next = this.path[0];
let isNextPositionOccupied = cars.some(car => car.current === next && car !== this);
if (!isNextPositionOccupied) {
this.current = next;
this.path.shift();
this.waitTurns = 0;
} else {
console.log(`Car at (${this.current.i}, ${this.current.j}) is waiting for the path to clear.`);
this.waitingForPath = true;
this.waitTurns = 0;
this.waitTimer = 0;
}
}
}
show() {
fill(this.color);
noStroke();
rect(this.current.i * w, this.current.j * h, w, h);
}
drawPath() {
noFill();
stroke(this.color);
beginShape();
for (let spot of this.path) {
vertex(spot.i * w + w / 2, spot.j * h + h / 2);
}
endShape();
}
}
function initializeGrid() {
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);
}
}
for (let i = 0; i < cols; i++) {
for (let j = 0; j < rows; j++) {
grid[i][j].addNeighbors(grid);
}
}
}
function initializeCars() {
let start = grid[0][0];
let end = grid[cols - 1][rows - 1];
for (let i = 0; i < numCars; i++) {
let car = new Car(start, end, carColors[i]);
cars.push(car);
}
}
function setup() {
createCanvas(cw, ch);
frameRate(60);
w = width / cols;
h = height / rows;
initializeGrid();
initializeCars();
}
function draw() {
background(backcolor);
for (let i = 0; i < cols; i++) {
for (let j = 0; j < rows; j++) {
grid[i][j].show();
}
}
for (let car of cars) {
car.move();
car.show();
car.drawPath();
}
}
function heuristic(a, b) {
return dist(a.i, a.j, b.i, b.j);
}
function setup() {
frameRate(2);
createCanvas(cw, ch);
cols = 20;
rows = 15;
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 or occupied by another car
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 || cars.some(car => car.current === start)); // Check if spot is a wall or occupied
retryCount = 0; // Reset retryCount for the end point
// Ensure the end point is valid, not a wall, and not the same as the start or occupied by another car
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 || cars.some(car => car.end === end)); // Check for wall, start, or occupied
// Set start and end to be walkable for this car
start.wall = end.wall = false;
// Assign a unique car color
let color = carColors[i % carColors.length];
cars.push(new Car(start, end, color));
}
}
function draw() {
try {
background(backcolor);
// Draw all grid spots
grid.flat().forEach((spot) => spot.show());
cars.forEach((car, idx) => {
car.drawPath();
let nextPosition = car.path.length > 1 ? car.path[1] : null;
if (nextPosition) {
let blockingCar = cars.find((otherCar, otherIdx) =>
otherIdx !== idx && otherCar.current === nextPosition
);
if (blockingCar) {
car.waiting = true;
} else {
car.move();
}
}
car.show();
});
} catch (error) {
if (error instanceof RangeError) {
console.error("RangeError detected. Refreshing the page.");
alert("RangeError occurred. Refreshing the page to fix the issue.");
location.reload();
} else {
console.error(error);
throw error; // re-throw other errors
}
}
}