Code viewer for World: poe
let cols = 19;
let rows = 13;
let w, h;
let grid = [];
let cars = [];

function setup() {
  createCanvas(900, 600);
  w = width / cols;
  h = height / rows;
  frameRate(4);

  // Initialize the grid and set walls
  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);
    }
  }

  // Initialize cars with random positions and destinations
  initializeCars();
}

function draw() {
  background(255);

  // Draw grid
  for (let i = 0; i < cols; i++) {
    for (let j = 0; j < rows; j++) {
      grid[i][j].show();
    }
  }

  // Draw cars and their paths
  for (let car of cars) {
    car.show();
    if (car.destination) {
      let path = aStar(car.i, car.j, car.destination.i, car.destination.j);
      console.log(`Car at (${car.i}, ${car.j}) to Destination (${car.destination.i}, ${car.destination.j}):`, path);
      drawPath(path, car.color);
    }
  }
}

// Define the Spot object to represent each cell in the grid
function Spot(i, j) {
  this.i = i;
  this.j = j;
  this.wall = false;

  this.show = function() {
    fill(this.wall ? 0 : 255); // Wall - Black, Path - White
    stroke(200);
    rect(this.i * w, this.j * h, w, h);
  };
}

// Car class to represent each car
class Car {
  constructor(i, j, color) {
    this.i = i; // Current grid position
    this.j = j;
    this.color = color; // Car color
    this.destination = null; // Destination grid position
  }

  setDestination(destI, destJ) {
    this.destination = { i: destI, j: destJ };
  }

  show() {
    fill(this.color);
    ellipse(this.i * w + w / 2, this.j * h + h / 2, w * 0.6); // Draw car
    if (this.destination) {
      fill(this.color);
      rect(this.destination.i * w + w / 4, this.destination.j * h + h / 4, w / 2, h / 2); // Draw destination
    }
  }
}

// Initialize cars with unique colors and random positions
function initializeCars() {
  let uniqueColors = ['red', 'blue', 'green', 'orange'];
  for (let color of uniqueColors) {
    let startPos, destPos;

    // Get valid random start position
    do {
      startPos = random(whiteGrids());
    } while (cars.some(car => car.i === startPos.i && car.j === startPos.j));

    // Get valid random destination position
    do {
      destPos = random(whiteGrids());
    } while ((destPos.i === startPos.i && destPos.j === startPos.j) || 
              cars.some(car => car.destination && car.destination.i === destPos.i && car.destination.j === destPos.j));

    let car = new Car(startPos.i, startPos.j, color);
    car.setDestination(destPos.i, destPos.j);
    cars.push(car);
  }
}

// A* Pathfinding Algorithm
function aStar(startX, startY, targetX, targetY) {
  let openSet = [];
  let closedSet = [];
  let start = grid[startX][startY];
  let target = grid[targetX][targetY];

  openSet.push(start);
  start.gScore = 0;
  start.fScore = heuristic(start, target);

  while (openSet.length > 0) {
    // Find the node with the lowest fScore
    let lowestIndex = 0;
    for (let i = 0; i < openSet.length; i++) {
      if (openSet[i].fScore < openSet[lowestIndex].fScore) {
        lowestIndex = i;
      }
    }

    let current = openSet[lowestIndex];

    // Check if we reached the target
    if (current === target) {
      return reconstructPath(current);
    }

    // Move current from openSet to closedSet
    openSet.splice(lowestIndex, 1);
    closedSet.push(current);

    // Get neighbors
    let neighbors = getNeighbors(current);
    for (let neighbor of neighbors) {
      if (closedSet.includes(neighbor)) {
        continue; // Ignore the neighbor which is already evaluated
      }

      // The distance from start to the neighbor
      let tentativeGScore = current.gScore + 1;

      if (!openSet.includes(neighbor)) {
        openSet.push(neighbor); // Discover a new node
      } else if (tentativeGScore >= neighbor.gScore) {
        continue; // This is not a better path
      }

      // This path is the best until now. Record it!
      neighbor.cameFrom = current;
      neighbor.gScore = tentativeGScore;
      neighbor.fScore = neighbor.gScore + heuristic(neighbor, target);
    }
  }

  return []; // Return an empty path if there is no path
}

// Heuristic function (Manhattan distance)
function heuristic(a, b) {
  return abs(a.i - b.i) + abs(a.j - b.j);
}

// Get neighbors of the current node
function getNeighbors(node) {
  let neighbors = [];
  let directions = [
    { i: 0, j: 1 },  // Down
    { i: 1, j: 0 },  // Right
    { i: 0, j: -1 }, // Up
    { i: -1, j: 0 }  // Left
  ];

  for (let dir of directions) {
    let newI = node.i + dir.i;
    let newJ = node.j + dir.j;
    if (newI >= 0 && newI < cols && newJ >= 0 && newJ < rows && !grid[newI][newJ].wall) {
      neighbors.push(grid[newI][newJ]);
    }
  }
  return neighbors;
}

// Reconstruct the path from the target to the start
function reconstructPath(current) {
  let totalPath = [];
  while (current.cameFrom) {
    totalPath.push({ i: current.i, j: current.j });
    current = current.cameFrom;
  }
  totalPath.reverse(); // Return the path in the correct order
  return totalPath; // Return the path
}

// Function to draw the path
function drawPath(path, color) {
  stroke(color);
  strokeWeight(3);
  for (let i = 0; i < path.length - 1; i++) {
    let startX = path[i].i * w + w / 2;
    let startY = path[i].j * h + h / 2;
    let endX = path[i + 1].i * w + w / 2;
    let endY = path[i + 1].j * h + h / 2;
    line(startX, startY, endX, endY); // Draw line between path points
  }
}

// Function to get white grid positions
function whiteGrids() {
  let validGrids = [];
  for (let i = 0; i < cols; i++) {
    for (let j = 0; j < rows; j++) {
      if (!grid[i][j].wall) {
        validGrids.push({ i, j });
      }
    }
  }
  return validGrids;
}