Code viewer for World: befor subm check (clone by...
let cols = 10;
let rows = 7;
let w, h;
let grid = [];
let cars = [];
const colors = ["red", "blue", "green", "yellow"];
let currentCarIndex = 0;

function setup() {
  createCanvas(900, 600);
  w = width / cols;
  h = height / rows;
  frameRate(7); // Slow down for testing

  for (let i = 0; i < cols; i++) {
    grid[i] = [];
    for (let j = 0; j < rows; j++) {
      grid[i][j] = new Spot(i, j);
      grid[i][j].wall = !(i % 3 === 0 || j % 3 === 0);
    }
  }

  grid[0][0].wall = true;
  grid[0][rows - 1].wall = true;
  grid[cols - 1][0].wall = true;
  grid[cols - 1][rows - 1].wall = true;

  for (let i = 0; i < colors.length; i++) {
    let car = createCar(i);
    let destination = createDestination(car);
    car.destination = destination;

    // Calculate the original path (A* for valid path)
    let originalPath = findValidPath(car, destination);
    console.log(`Car ${i} original path:`, originalPath); // Log the path

    car.originalPath = originalPath;
    car.hasReachedDestination = false;
    cars.push(car);
  }
}

function draw() {
  background(255);
  for (let i = 0; i < cols; i++) {
    for (let j = 0; j < rows; j++) {
      grid[i][j].show();
    }
  }

  for (let car of cars) {
    displayPath(car.originalPath, car.color);
    car.show();
    displayDestination(car.destination, car.color);
  }

  if (cars.length > 0) {
    moveCar(currentCarIndex);
  }
}

function Spot(i, j) {
  this.i = i;
  this.j = j;
  this.wall = false;

  this.show = function() {
    fill(this.wall ? 0 : 255);
    stroke(0);
    rect(this.i * w, this.j * h, w, h);
  };
}

function createCar(carIndex) {
  let i, j;
  do {
    i = floor(random(cols));
    j = floor(random(rows));
  } while (grid[i][j].wall || isOccupied(i, j));

  return new Car(i, j, colors[carIndex]);
}

function isOccupied(i, j) {
  return cars.some(car => car.i === i && car.j === j);
}

function createDestination(car) {
  let i, j;
  do {
    i = floor(random(cols));
    j = floor(random(rows));
  } while (grid[i][j].wall || (i === car.i && j === car.j) || isDestinationOccupied(i, j));

  return { i, j };
}

function isDestinationOccupied(i, j) {
  return cars.some(car => car.destination && car.destination.i === i && car.destination.j === j);
}

function Car(startI, startJ, carColor) {
  this.i = startI;
  this.j = startJ;
  this.color = carColor;
  this.destination = null;
  this.originalPath = [];
  this.allowedGridsAround = [];
  this.blockedGridsAround = [];
  this.hasReachedDestination = false;

  this.show = function() {
    fill(this.color);
    noStroke();
    ellipse((this.i + 0.5) * w, (this.j + 0.5) * h, w / 2, h / 2);
  };
}

function moveCar(index) {
  let car = cars[index];

  if (!car.hasReachedDestination) {
    let { allowedGridsAround, blockedGridsAround } = senseGridAroundCar(car);
    car.allowedGridsAround = allowedGridsAround;
    car.blockedGridsAround = blockedGridsAround;

    let action = planMovement(car);

    if (action === "Proceed as per the original plan.") {
      let nextStep = car.originalPath[1];
      if (nextStep) {
        car.i = nextStep.i;
        car.j = nextStep.j;
        car.originalPath.shift();
        
        console.log(`Car ${car.color} moved to (${car.i}, ${car.j})`);
      }

      // Check if the car has reached its destination
      if (car.originalPath.length === 0 || 
          (car.i === car.destination.i && car.j === car.destination.j)) {
        car.hasReachedDestination = true;
        console.log(`Car ${car.color} reached its destination.`);
        
        // Mark the grid position as empty when the car reaches its destination
        grid[car.i][car.j].wall = false;
        // Remove the car from the game (from the cars array)
        cars.splice(index, 1);
      }
    } else if (action === "Re-routing and revising the original plan.") {
      console.log(`Car ${car.color} is re-routing...`);
      car.originalPath = findValidPath(car, car.destination, true);
      logPath(car, "Revised path");
    } else {
      console.log(`Car ${car.color}: ${action}`);
    }
  }

  currentCarIndex = (currentCarIndex + 1) % cars.length;

  if (cars.every(car => car.hasReachedDestination)) {
    noLoop();
  } else if (cars[currentCarIndex].hasReachedDestination) {
    currentCarIndex = (currentCarIndex + 1) % cars.length;
  }
}

function senseGridAroundCar(car) {
  let allowedGridsAround = [];
  let blockedGridsAround = [];
  let carsAround = [];

  let directions = [
    { di: -1, dj: 0 },
    { di: 1, dj: 0 },
    { di: 0, dj: -1 },
    { di: 0, dj: 1 },
  ];

  for (let dir of directions) {
    let ni = car.i + dir.di;
    let nj = car.j + dir.dj;

    if (ni >= 0 && ni < cols && nj >= 0 && nj < rows) {
      if (grid[ni][nj].wall) {
        blockedGridsAround.push({ i: ni, j: nj });
      } else if (cars.some(c => c.i === ni && c.j === nj)) {
        blockedGridsAround.push({ i: ni, j: nj });
        carsAround.push({ i: ni, j: nj });
      } else {
        allowedGridsAround.push({ i: ni, j: nj });
      }
    }
  }

  return { allowedGridsAround, blockedGridsAround, carsAround };
}

function planMovement(car) {
  let nextStep = car.originalPath[1];
  if (!nextStep) return "Car has reached its destination.";

  let inAllowedGrids = car.allowedGridsAround.some(grid => grid.i === nextStep.i && grid.j === nextStep.j);
  if (inAllowedGrids) return "Proceed as per the original plan.";

  let inBlockedGrids = car.blockedGridsAround.some(grid => grid.i === nextStep.i && grid.j === nextStep.j);
  if (inBlockedGrids) {
    let isIntersection = checkIntersection(nextStep.i, nextStep.j);
    if (isIntersection) {
      return "The car will wait and pass the turn.";
    } else {
      return "Re-routing and revising the original plan.";
    }
  }

  return "Error: Grid is neither allowed nor blocked.";
}

function checkIntersection(i, j) {
  let pathCount = 0;
  let directions = [
    { di: -1, dj: 0 },
    { di: 1, dj: 0 },
    { di: 0, dj: -1 },
    { di: 0, dj: 1 },
  ];

  for (let dir of directions) {
    let ni = i + dir.di;
    let nj = j + dir.dj;
    if (ni >= 0 && ni < cols && nj >= 0 && nj < rows && !grid[ni][nj].wall) {
      pathCount++;
    }
  }
  return pathCount > 2;
}

function findValidPath(car, destination, secondShortest = false) {
  let start = grid[car.i][car.j];
  let end = grid[destination.i][destination.j];
  if (start.wall || end.wall) return [];

  let openSet = [];
  let closedSet = new Set();
  let cameFrom = {};

  openSet.push({ node: start, g: 0, h: manhattanDistance(start.i, start.j, end.i, end.j), f: 0 });
  cameFrom[`${start.i},${start.j}`] = null;

  let directions = [[0, 1], [0, -1], [1, 0], [-1, 0]];

  while (openSet.length > 0) {
    openSet.sort((a, b) => a.f - b.f);
    let current = openSet.shift();

    if (current.node === end) {
      let path = [];
      let temp = current.node;
      while (temp) {
        path.push(temp);
        temp = cameFrom[`${temp.i},${temp.j}`];
      }
      return path.reverse();
    }

    closedSet.add(current.node);

    for (let [di, dj] of directions) {
      let ni = current.node.i + di;
      let nj = current.node.j + dj;
      if (ni < 0 || nj < 0 || ni >= cols || nj >= rows || grid[ni][nj].wall) continue;

      let neighbor = grid[ni][nj];
      if (closedSet.has(neighbor)) continue;

      if (isOccupied(ni, nj)) continue;

      let tentativeG = current.g + 1;
      let existingNode = openSet.find(node => node.node === neighbor);

      if (secondShortest) {
        let additionalPenalty = existingNode ? 1 : 0;
        tentativeG += additionalPenalty;
      }

      if (!existingNode || tentativeG < existingNode.g) {
        cameFrom[`${ni},${nj}`] = current.node;
        let h = manhattanDistance(ni, nj, end.i, end.j);
        let f = tentativeG + h;
        if (!existingNode) openSet.push({ node: neighbor, g: tentativeG, h, f });
      }
    }
  }
  return [];
}

function manhattanDistance(i1, j1, i2, j2) {
  return abs(i1 - i2) + abs(j1 - j2);
}

function logPath(car, label) {
  console.log(`${label} for ${car.color}:`);
  for (let spot of car.originalPath) {
    console.log(`(${spot.i}, ${spot.j})`);
  }
}

function displayPath(path, color) {
  stroke(color);
  noFill();
  beginShape();
  for (let spot of path) {
    vertex(spot.i * w + w / 2, spot.j * h + h / 2);
  }
  endShape();
}

function displayDestination(destination, color) {
  fill(color);
  noStroke();
  ellipse((destination.i + 0.5) * w, (destination.j + 0.5) * h, w / 3, h / 3);
}