Code viewer for World: New World
const rows = 20;
const cols = 30;
const cellSize = 40;
let grid = [];
let cars = [];
const numCars = 4;
let grass_image, road_image, house_image;
let car_images = [];
let carColors = [];

function preload() {
  grass_image = loadImage('/uploads/dash1127/house.jpeg'); // Load grass image for non-road
  road_image = loadImage('/uploads/dash1127/2.jpeg'); // Load road image
  house_image = loadImage('/uploads/nivedithavudayagiri/New-york-buildings-4.png'); // Load house image (destination for cars)

  // Load car images
  for (let i = 1; i <= numCars; i++) {
    car_images.push(loadImage(`/uploads/dash1127/car${i}.png`));
    carColors.push(color(random(255), random(255), random(255))); // Assign random colors for each car trail
  }
}

function setup() {
  createCanvas(cols * cellSize, rows * cellSize);
  
  // Initialize NYC Grid
  grid = createNYCGrid(rows, cols);
  
  // Generate random houses in non-road locations
  let housePositions = generateHouses(numCars); 
  
  // Initialize 4 cars
  cars = createCars(numCars, housePositions);
  
  // Control speed of simulation (2 steps per second)
  frameRate(2); 
}

function draw() {
  background(255);
  drawGrid();
  drawCars();

  // Move cars step-by-step
  moveCars();
}

// Set up the NYC grid layout
function createNYCGrid(rows, cols) {
  let grid = [];
  for (let r = 0; r < rows; r++) {
    let row = [];
    for (let c = 0; c < cols; c++) {
      // Define streets every 3rd row and every 5th column for avenues
      if (r % 3 === 0 || c % 5 === 0) {
        row.push(0); // Road
      } else {
        row.push(1); // Building block
      }
    }
    grid.push(row);
  }
  return grid;
}

// Generate positions for houses within the grid
function generateHouses(n) {
  let houses = [];
  while (houses.length < n) {
    let r = floor(random(1, rows));
    let c = floor(random(1, cols));
    
    if (grid[r][c] === 1) { // Ensure it's a non-road building block
      houses.push({ x: c, y: r });
      grid[r][c] = 2; // Mark cell as house
    }
  }
  return houses;
}

function createCars(n, housePositions) {
  let cars = [];
  for (let i = 0; i < n; i++) {
    let start = randomRoad();
    let end = housePositions[i];
    cars.push({
      image: car_images[i], // Assign car image
      color: carColors[i], // Assign unique color
      position: start,
      initial: start,
      destination: end,
      path: aStar(start, end), // Track the final path of the car
      pathIndex: 0, // Track the current index in the path
      hasReached: false, // Track if the car has reached its destination
      angle: 0 // Initial angle
    });
  }
  return cars;
}

// Get position of a random road location to set as start point for cars
function randomRoad() {
  let r, c;
  do {
    r = floor(random(rows));
    c = floor(random(cols));
  } while (grid[r][c] !== 0); // Ensure it's a road
  return { x: c, y: r };
}

function drawGrid() {
  for (let r = 0; r < rows; r++) {
    for (let c = 0; c < cols; c++) {
      stroke(0);
      noFill();
      rect(c * cellSize, r * cellSize, cellSize, cellSize); // Draw grid

      if (grid[r][c] === 1) {
        image(grass_image, c * cellSize, r * cellSize, cellSize, cellSize); // Draw building block
      } else if (grid[r][c] === 0) {
        image(road_image, c * cellSize, r * cellSize, cellSize, cellSize); // Draw road
      } else if (grid[r][c] === 2) {
        image(house_image, c * cellSize, r * cellSize, cellSize, cellSize); // Draw house
      }
    }
  }
}

function drawCars() {
  cars.forEach(car => {
    push();
    translate(car.position.x * cellSize + cellSize / 2, car.position.y * cellSize + cellSize / 2);
    rotate(car.angle);
    image(car.image, -cellSize / 2 + 5, -cellSize / 2 + 5, cellSize - 10, cellSize - 10);
    pop();

    // Draw car's path
    if (car.path.length > 0) {
      stroke(car.color);
      strokeWeight(2);
      for (let i = 0; i < car.path.length - 1; i++) {
        let startPos = car.path[i];
        let endPos = car.path[i + 1];
        line(startPos.x * cellSize + cellSize / 2, startPos.y * cellSize + cellSize / 2,
             endPos.x * cellSize + cellSize / 2, endPos.y * cellSize + cellSize / 2);
      }
    }

    // Draw destination square
    fill(car.color);
    rect(car.destination.x * cellSize + 10, car.destination.y * cellSize + 10, cellSize - 20, cellSize - 20);
  });
}

// A* Pathfinding Algorithm
function aStar(start, end) {
  let openSet = [];
  let closedSet = [];
  openSet.push({ ...start, g: 0, f: heuristic(start, end), parent: null });

  while (openSet.length > 0) {
    let current = openSet.sort((a, b) => a.f - b.f)[0]; // Get node with lowest f

    // If the current position is the destination
    if (current.x === end.x && current.y === end.y) {
      console.log(`Path found from ${start.x},${start.y} to ${end.x},${end.y}`);
      return reconstructPath(current);
    }

    openSet = openSet.filter(node => node !== current);
    closedSet.push(current);

    let neighbors = getNeighbors(current);

    neighbors.forEach(neighbor => {
      if (closedSet.find(n => n.x === neighbor.x && n.y === neighbor.y)) {
        return;
      }

      let tentative_g = current.g + 1;
      let inOpenSet = openSet.find(n => n.x === neighbor.x && n.y === neighbor.y);

      if (!inOpenSet || tentative_g < neighbor.g) {
        neighbor.g = tentative_g;
        neighbor.f = neighbor.g + heuristic(neighbor, end);
        neighbor.parent = current;

        if (!inOpenSet) {
          openSet.push(neighbor);
        }
      }
    });
  }

  console.log(`No path found from ${start.x},${start.y} to ${end.x},${end.y}`);
  return []; // No path found
}

// Get neighbors function to get valid roads or houses 
function getNeighbors(node) {
  let neighbors = [];
  let { x, y } = node;

  // Check the cells adjacent to the current node
  if (x > 0 && (grid[y][x - 1] === 0 || grid[y][x - 1] === 2)) neighbors.push({ x: x - 1, y }); // Left
  if (x < cols - 1 && (grid[y][x + 1] === 0 || grid[y][x + 1] === 2)) neighbors.push({ x: x + 1, y }); // Right
  if (y > 0 && (grid[y - 1][x] === 0 || grid[y - 1][x] === 2)) neighbors.push({ x, y: y - 1 }); // Up
  if (y < rows - 1 && (grid[y + 1][x] === 0 || grid[y + 1][x] === 2)) neighbors.push({ x, y: y + 1 }); // Down

  return neighbors;
}

function heuristic(a, b) {
  return abs(a.x - b.x) + abs(a.y - b.y); // Manhattan distance
}

function reconstructPath(node) {
  let path = [];
  while (node) {
    path.push({ x: node.x, y: node.y });
    node = node.parent;
  }
  return path.reverse();
}

function moveCars() {
  let occupiedCells = {};

  // Mark current positions of all cars as occupied
  cars.forEach(car => {
    if (!car.hasReached) {
      occupiedCells[`${car.position.x},${car.position.y}`] = true;
    }
  });

  cars.forEach(car => {
    if (car.path.length > 0 && !car.hasReached) {
      // Get the next position in the path
      let nextPos = car.path[car.pathIndex];

      // Check if the next position is occupied
      if (!occupiedCells[`${nextPos.x},${nextPos.y}`]) {
        // Move car to the next position
        car.position = nextPos;
        car.pathIndex++;
      } else {
        // If the next position is occupied, attempt to find an alternative route
        let alternativePath = aStar(car.position, car.destination);
        if (alternativePath.length > 0) {
          car.path = alternativePath;
          car.pathIndex = 0; // Reset index to follow new path
        }
      }

      // Check if car has reached the destination
      if (car.position.x === car.destination.x && car.position.y === car.destination.y) {
        car.hasReached = true;
      }

      // Update car angle based on movement direction
      if (car.pathIndex > 0) {
        let current = car.path[car.pathIndex - 1];
        let next = car.path[car.pathIndex];
        car.angle = atan2(next.y - current.y, next.x - current.x); // Update angle for rotation
      }
    }
  });
}
function draw() {
  background(255);
  drawGrid();
  drawCars();
  
  // Move cars step-by-step
  moveCars();
}