Code viewer for World: A new try
// 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();
        }
    }
}