Code viewer for World: Practical Trial 2 - A star...
//Trial Car

let cols, rows;
let grid = [];
let cars = [];
let cellSize = 50;
let canvas = 700;

//Initialising for Car figures
let carColor;
let posX, posY;
let carWidth = 50;
let carHeight = carWidth/2.5;
let wheelWidth = carWidth * 0.2;
let wheelHeight = wheelWidth/2.5;
let velocity=0;
let carFigs = [];

 
function setup() {
    frameRate (2);
    createCanvas(700, 700);
    cols = width / cellSize; //14
    rows = height / cellSize;//14
    
    background(220);
    drawGrid();
    
    // Initialize the grid
    for (let i = 0; i < cols; i++) {
        grid[i] = [];
        for (let j = 0; j < rows; j++) {
            grid[i][j] = 0; // 0 means empty cell
        }
    }

    // Create multiple cars with random start and goal positions
    for (let i = 0; i < 4; i++) {
        let start = randomPosition();
        //console.log("start "+start);
        let goal;
        do {
            goal = randomPosition();
            //console.log("end "+goal);
        } while (arraysEqual(start, goal));
        
        carColor = color(random(255), random(255), random(255));
        console.log("color and start "+start +" "+ carColor);
        cars.push(new Car(i, start, goal, carColor));
        
        //setup the car
        posX = start[0];//width/2;
        posY = start[1];//height/2;
        carFigs.push(new CarFigure(i,posX,posY,carColor));
    }
    for(let fig of carFigs){
        console.log("fig");
        fig.drawCarFigure();
    }
}

function draw() {
    
    let positions = cars.map(car => `${car.position}`);//current position of all cars, eg positions=["2,3", "5,4", "1,1"]
    for (let car of cars) {
        if (!car.path.length || arraysEqual(car.position, car.goal)) {
            //car.recalculatePath();
        } else {
            if (!positions.includes(`${car.path[1]}`)) {// if car's next move doesn't have another car there, then no problem move
                //car.move();
            } else {
                //car.recalculatePath();// if car's next move has another car there, then recalculate
            }
        }
        car.draw();
    }
}

function drawGrid() { //Set
    
    //Black Grids and Border
    stroke("black");
    strokeWeight(3);
    for (let i = 0; i < cols; i++) {
        for (let j = 0; j < rows; j++) { 
            noFill();
            rect(i * cellSize, j * cellSize, cellSize, cellSize); //cellsize=50
        }
    }
    
    //White Dotted Lines
    stroke("white");
    strokeWeight(3);
    
    for (let i = 0; i <= cellSize*cols; i+=cellSize) {//cellsize=50
        var interval=25;
        var interval2 = 50;
        for (let j = 0; j <= cellSize*rows; j+=cellSize) { 
            noFill();
            line(i, interval, j,interval);
            line(i, interval2, j,interval2);
            setLineDash([5, 5]);
            interval+=50;
            interval2+=50;
        }
    }
}

// Utility function to draw dotted lines
function setLineDash(list) {
  drawingContext.setLineDash(list);
}

// Utility function to generate a random position
function randomPosition() {
    return [floor(random(cols)), floor(random(rows))];
}

class Car {
    constructor(id, start, goal, col) {
        this.id = id;
        this.position = start;
        this.goal = goal;
        this.color = col;
        this.path = [];
        this.drawDestinationGrid();
        this.recalculatePath();
    }

    recalculatePath() {
        this.path = aStar(this.position, this.goal);
    }

    move() {
        if (this.path.length > 1) {
            this.position = this.path[1]; // Move to the next step
            this.path.shift();
        }
    }

    draw() {
        //fill(this.color);
        //rect(this.position[0] * cellSize, this.position[1] * cellSize, cellSize, cellSize);
    }
    
    drawDestinationGrid() { //Not working
        //To highlight the destination of each car
        //stroke(this.color);
        //strokeWeight(3);
        //rect(this.position[0] * cellSize, this.position[1] * cellSize, cellSize/2, cellSize/2); //cellsize=50
    }
}

class CarFigure {
    constructor(id, posX, posY, carColor) {
        this.id = id;
        this.posX = posX;
        this.posY = posY;
        this.carColor = carColor;
        
        this.drawCarFigure();
        
    }

    drawCarFigure(){
        //car body
  fill(this.carColor);
  noStroke();
  console.log("pos x and y: "+this.posX + " "+ this.posY );
  rect(this.posX * cellSize, this.posY * cellSize, cellSize, cellSize);
  //rect(this.posX, this.posY, this.posX+1, this.posY+1);
  
  
  fill(0);
  //left top wheel
  rect(this.posX-carWidth/2 + wheelWidth/2, this.posY - carHeight/2-wheelHeight/2, wheelWidth, wheelHeight);
  //right top wheel
  rect(this.posX+carWidth/2 - wheelWidth/2, this.posY - carHeight/2-wheelHeight/2, wheelWidth, wheelHeight);
  //left bottom wheel
  rect(this.posX-carWidth/2 + wheelWidth/2, this.posY + carHeight/2+wheelHeight/2, wheelWidth, wheelHeight);
  //right bottom wheel
  rect(this.posX+carWidth/2 - wheelWidth/2, this.posY + carHeight/2+wheelHeight/2, wheelWidth, wheelHeight);
  
    }

    /*move() {
        if (this.path.length > 1) {
            this.position = this.path[1]; // Move to the next step
            this.path.shift();
        }
    }

    draw() {
        fill(this.color);
        rect(this.position[0] * cellSize, this.position[1] * cellSize, cellSize, cellSize);
    }
    
    drawDestinationGrid() { //Not working
        //To highlight the destination of each car
        stroke(this.color);
        strokeWeight(3);
        rect(this.position[0] * cellSize, this.position[1] * cellSize, cellSize, cellSize); //cellsize=50
    }*/
}

function aStar(start, goal) {
    let openList = [];
    let closedList = new Set();
    let gScore = {};
    let fScore = {};
    let cameFrom = {};

    gScore[`${start}`] = 0;
    fScore[`${start}`] = heuristic(start, goal);
    openList.push(start);

    while (openList.length > 0) {
        let current = openList.reduce((a, b) => (fScore[a] < fScore[b] ? a : b));

        if (arraysEqual(current, goal)) {
            return reconstructPath(cameFrom, current);
        }

        openList = openList.filter(node => !arraysEqual(node, current));
        closedList.add(`${current}`);

        for (let neighbor of getNeighbors(current)) {
            if (closedList.has(`${neighbor}`) || cars.some(car => arraysEqual(car.position, neighbor))) continue;

            let tentativeGScore = gScore[`${current}`] + 1;
            if (tentativeGScore < (gScore[`${neighbor}`] || Infinity)) {
                cameFrom[`${neighbor}`] = current;
                gScore[`${neighbor}`] = tentativeGScore;
                fScore[`${neighbor}`] = tentativeGScore + heuristic(neighbor, goal);

                if (!openList.some(n => arraysEqual(n, neighbor))) {
                    openList.push(neighbor);
                }
            }
        }
    }

    return [];

    function heuristic([x1, y1], [x2, y2]) {
        return abs(x1 - x2) + abs(y1 - y2);
    }

    function reconstructPath(cameFrom, current) {
        let path = [current];
        while (cameFrom[`${current}`]) {
            current = cameFrom[`${current}`];
            path.unshift(current);
        }
        return path;
    }

    function getNeighbors([x, y]) {
        let neighbors = [
            [x + 1, y], [x - 1, y],
            [x, y + 1], [x, y - 1]
        ];
        return neighbors.filter(([nx, ny]) => nx >= 0 && nx < cols && ny >= 0 && ny < rows);
    }
}

function arraysEqual(a, b) {
    return a[0] === b[0] && a[1] === b[1];
}