// 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 };
}