Code viewer for World: A star (clone by Adam)
// Canvas and Grid Settings
const cw = 900, ch = 600;
const cols = 20, rows = 15;
const numCars = 4;
const wallAmount = 0.2;
const colors = ['red', 'green', 'blue', 'orange'];

// Grid and Car Data
let grid = [];
let w, h;
let cars = [];
let frameCount = 0; // Frame counter for Ancient Brain

// Spot object represents each cell in the grid
function Spot(i, j) {
    this.i = i;
    this.j = j;
    this.f = 0;
    this.g = 0;
    this.h = 0;
    this.neighbors = [];
    this.previous = undefined;
    this.wall = (Math.random() < wallAmount);

    // Display each spot using Ancient Brain's rectangle rendering
    this.show = function(col) {
        let color = this.wall ? 'black' : col || 'white';
        addRectangle(this.i * w, this.j * h, w, h, color);
    };

    // Add neighbors for A* pathfinding
    this.addNeighbors = function(grid) {
        let i = this.i, j = this.j;
        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]);
    };
}

// Car object with pathfinding and movement
function Car(start, end, color) {
    this.start = start;
    this.end = end;
    this.position = start;
    this.path = [];
    this.color = color;
    this.completed = false;
    this.calculatePath();
}

// Calculate path for each car
Car.prototype.calculatePath = function() {
    this.path = aStar(this.position, this.end, cars.map(c => c.position));
}

// Move car one step along the path
Car.prototype.move = function() {
    if (this.path.length > 0) {
        this.position = this.path.shift();
    } else {
        this.completed = true;
    }
};

// Initialize grid and cars - runs once
function start() {
    w = cw / cols;
    h = ch / rows;

    // Initialize 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 for pathfinding
    for (let i = 0; i < cols; i++) {
        for (let j = 0; j < rows; j++) {
            grid[i][j].addNeighbors(grid);
        }
    }

    // Initialize cars with random start/end points on streets
    for (let i = 0; i < numCars; i++) {
        let start = randomStreetPosition();
        let end = randomStreetPosition();
        cars.push(new Car(start, end, colors[i % colors.length]));
    }
}

// Main update loop - runs repeatedly in Ancient Brain
function frame() {
    frameCount++;

    // Clear screen on each frame to redraw grid and cars
    clearScreen();

    // Reset paths every 120 frames to avoid collisions
    if (frameCount % 120 === 0) {
        cars.forEach(car => car.calculatePath());
    }

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

    // Move cars and update paths
    let carPositions = cars.map(car => car.position);
    cars.forEach(car => {
        if (!car.completed) {
            car.calculatePath(); // Recalculate path to avoid other cars
            car.move();          // Move one step
            carPositions[cars.indexOf(car)] = car.position; // Update car position
        }
    });

    // Draw cars and their paths
    cars.forEach(car => {
        // Draw the planned path in the color of the car
        car.path.forEach(pos => {
            addRectangle(pos.x * w, pos.y * h, w, h, car.color); // Planned path
        });

        // Draw the car itself as a circle
        addCircle(car.position.x * w + w / 2, car.position.y * h + h / 2, w * 0.6, car.color);
    });
}

// A* algorithm with obstacle avoidance
function aStar(start, goal, carPositions) {
    let openSet = [{ pos: start, cost: 0, path: [] }];
    let closedSet = new Set();

    while (openSet.length > 0) {
        openSet.sort((a, b) => a.cost - b.cost);
        let { pos, cost, path } = openSet.shift();

        // Check if goal reached
        if (pos.x === goal.x && pos.y === goal.y) {
            return path;
        }

        closedSet.add(`${pos.x}-${pos.y}`);
        for (let [dx, dy] of [[1, 0], [-1, 0], [0, 1], [0, -1]]) {
            let neighbor = { x: pos.x + dx, y: pos.y + dy };
            if (isValidPosition(neighbor, carPositions) && !closedSet.has(`${neighbor.x}-${neighbor.y}`)) {
                openSet.push({
                    pos: neighbor,
                    cost: cost + heuristic(neighbor, goal),
                    path: [...path, neighbor]
                });
            }
        }
    }
    return [];
}

// Check if a position is valid (in bounds, no wall, no other car)
function isValidPosition(pos, carPositions) {
    let inBounds = pos.x >= 0 && pos.x < cols && pos.y >= 0 && pos.y < rows;
    let isStreet = inBounds && !grid[pos.x][pos.y].wall;
    let notBlocked = !carPositions.some(p => p.x === pos.x && p.y === pos.y);
    return isStreet && notBlocked;
}

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

// Generate a random position that's not a wall
function randomStreetPosition() {
    let i, j;
    do {
        i = Math.floor(Math.random() * cols);
        j = Math.floor(Math.random() * rows);
    } while (grid[i][j].wall);
    return { x: i, y: j };
}