From bb97d7121cd4cb18903b1671d81b90cf3480220f Mon Sep 17 00:00:00 2001 From: Mathom Johnson <156151500+MathomJohnson@users.noreply.github.com> Date: Mon, 23 Jun 2025 19:50:39 -0500 Subject: [PATCH 1/2] Implement arena-based movement --- classes/Car.js | 44 ++++++++++++++++--------------- classes/Obstacle.js | 10 +++++--- helpers/ObstacleManager.js | 50 +++++++++--------------------------- helpers/SimulationManager.js | 4 ++- main.js | 14 ++-------- 5 files changed, 47 insertions(+), 75 deletions(-) diff --git a/classes/Car.js b/classes/Car.js index eb3fabe..98841b8 100644 --- a/classes/Car.js +++ b/classes/Car.js @@ -2,17 +2,15 @@ import { Ray } from './Ray.js'; import { NeuralNetwork } from './NeuralNetwork.js'; export class Car { - constructor(x, width, height, canvasWidth, speed, generation = 1) { + constructor(x, y, width, height, canvasWidth, canvasHeight, speed, generation = 1) { this.x = x; - this.y = 800 * 0.8; // Will be set in draw() based on canvas height + this.y = y; // Width and height of the car this.width = width; this.height = height; this.canvasWidth = canvasWidth; - - // Penalty multiplier applied when the car strays from the road center - this.centerPenaltyMultiplier = 0.1; // Math.random() * 0.5 + 0.5; + this.canvasHeight = canvasHeight; // Penalty multiplier when the forward ray detects a nearby obstacle this.proximityPenaltyMultiplier = 0.8; @@ -20,7 +18,7 @@ export class Car { this.angle = 0; // Maximum angle change per update this.maxSteer = 0.02; - // Affects how quickly the car shifts left or right along with angle + // Affects how quickly the car moves along with angle this.speed = speed; this.generation = generation; // Whether the car has hit an obstacle or not @@ -102,20 +100,26 @@ export class Car { // 4. Predict steering using NN const steerDelta = this.brain.predict(inputs) * this.maxSteer; // output [-maxSteer, maxSteer] - // 5. Update car angle and position, with clamping - const newAngle = this.angle + steerDelta; - // Clamp angle between -π/2 and π/2 (90 degrees) - this.angle = Math.max(-Math.PI/2, Math.min(Math.PI/2, newAngle)); - this.x += Math.sin(this.angle) * this.speed; + // 5. Update car angle and position + this.angle += steerDelta; + if (this.angle > Math.PI) this.angle -= 2 * Math.PI; + if (this.angle < -Math.PI) this.angle += 2 * Math.PI; - // 6. Update fitness (distance traveled) and apply penalty for - // drifting away from the road center - this.fitness += this.speed; + this.x += Math.sin(this.angle) * this.speed; + this.y -= Math.cos(this.angle) * this.speed; + + // Keep within bounds + if ( + this.x - this.width / 2 < 0 || + this.x + this.width / 2 > this.canvasWidth || + this.y - this.height / 2 < 0 || + this.y + this.height / 2 > this.canvasHeight + ) { + this.alive = false; + } - const centerX = this.canvasWidth / 2; - const distanceFromCenter = Math.abs(this.x - centerX); - const normalized = distanceFromCenter / centerX; // 0 at center, 1 at edge - this.fitness -= normalized * this.centerPenaltyMultiplier * this.speed; + // 6. Update fitness (time survived) + this.fitness += 1; // Penalize if the forward-facing ray detects an obstacle too close const middleIndex = Math.floor(this.rays.length / 2); @@ -135,8 +139,6 @@ export class Car { draw(ctx) { if (!this.alive) return; - this.y = ctx.canvas.height * 0.8; // Update the stored y position - // Draw rays first (in world space, before car transformations) for (const ray of this.rays) { ray.draw(ctx); @@ -195,9 +197,11 @@ export class Car { clone() { const clone = new Car( this.canvasWidth / 2, + this.canvasHeight / 2, this.width, this.height, this.canvasWidth, + this.canvasHeight, this.speed, this.generation ); diff --git a/classes/Obstacle.js b/classes/Obstacle.js index d33ad9e..76ecc98 100644 --- a/classes/Obstacle.js +++ b/classes/Obstacle.js @@ -1,14 +1,16 @@ export class Obstacle { - constructor(x, y, width, height, speed) { + constructor(x, y, width, height, vx = 0, vy = 0) { this.x = x; this.y = y; this.width = width; this.height = height; - this.speed = speed; // downward speed + this.vx = vx; + this.vy = vy; } - + update() { - this.y += this.speed; + this.x += this.vx; + this.y += this.vy; } draw(ctx) { diff --git a/helpers/ObstacleManager.js b/helpers/ObstacleManager.js index 5548fdf..e2f5e11 100644 --- a/helpers/ObstacleManager.js +++ b/helpers/ObstacleManager.js @@ -1,10 +1,9 @@ import { Obstacle } from "../classes/Obstacle.js"; export class ObstacleManager { - constructor(canvas, obstacleCount = 5, speed) { + constructor(canvas, obstacleCount = 5) { this.canvas = canvas; this.count = obstacleCount; - this.speed = speed; this.width = 40; this.height = 15; this.obstacles = []; @@ -14,38 +13,25 @@ export class ObstacleManager { initializeObstacles() { this.obstacles = []; - // Add moving obstacles + for (let i = 0; i < this.count; i++) { - const x = this.getRandomX(); - const y = -i * 200; // Space them out vertically above the screen - this.obstacles.push(new Obstacle(x, y, this.width, this.height, this.speed)); + const x = Math.random() * (this.canvas.width - this.width) + this.width / 2; + const y = Math.random() * (this.canvas.height - this.height) + this.height / 2; + this.obstacles.push(new Obstacle(x, y, this.width, this.height)); } - // Manually add sides of the road as obstacles - this.obstacles.push(new Obstacle(0, this.canvas.height / 2, 10, this.canvas.height, 0)); // left wall - this.obstacles.push(new Obstacle(this.canvas.width, this.canvas.height / 2, 10, this.canvas.height, 0)); // right wall + // Arena walls + this.obstacles.push(new Obstacle(this.canvas.width / 2, 5, this.canvas.width, 10)); // top + this.obstacles.push(new Obstacle(this.canvas.width / 2, this.canvas.height - 5, this.canvas.width, 10)); // bottom + this.obstacles.push(new Obstacle(5, this.canvas.height / 2, 10, this.canvas.height)); // left + this.obstacles.push(new Obstacle(this.canvas.width - 5, this.canvas.height / 2, 10, this.canvas.height)); // right } - getRandomX() { - // Allow obstacles to spawn partially outside the road bounds so that - // "side hugging" cars cannot exploit a gap next to the walls. An - // obstacle's center can be anywhere from -width/2 to - // canvas.width + width/2 which means it may overlap the wall by up to - // half of its width but will never be completely outside the canvas. - const minX = -this.width / 2; - const maxX = this.canvas.width + this.width / 2; - return minX + Math.random() * (maxX - minX); - } updateAll(cars) { for (const obs of this.obstacles) { obs.update(); - if (obs.y - obs.height / 2 > this.canvas.height) { - obs.y = -this.height; - obs.x = this.getRandomX(); - } - for (const car of cars) { if (car.alive && obs.collidesWith(car)) { car.alive = false; @@ -64,20 +50,8 @@ export class ObstacleManager { return this.obstacles; } - // Reset all moving obstacles to starting positions + // Reset obstacles for new generation reset() { - // Keep the last two obstacles (walls) and reset the moving ones - const walls = this.obstacles.slice(-2); - this.obstacles = []; - - // Reinitialize moving obstacles - for (let i = 0; i < this.count; i++) { - const x = this.getRandomX(); - const y = -i * 200; // Space them out vertically above the screen - this.obstacles.push(new Obstacle(x, y, this.width, this.height, this.speed)); - } - - // Add back the walls - this.obstacles.push(...walls); + this.initializeObstacles(); } } diff --git a/helpers/SimulationManager.js b/helpers/SimulationManager.js index 8ba9575..2d6192a 100644 --- a/helpers/SimulationManager.js +++ b/helpers/SimulationManager.js @@ -37,9 +37,11 @@ export class SimulationManager { for (let i = 0; i < this.populationSize; i++) { this.cars.push(new Car( this.canvas.width / 2, // x + this.canvas.height / 2, // y this.CAR_WIDTH, // width this.CAR_HEIGHT, // height this.canvas.width, // canvasWidth + this.canvas.height, // canvasHeight this.speed, // speed this.generation // generation )); @@ -68,7 +70,7 @@ export class SimulationManager { // Increment overall distance if at least one car is alive if (this.cars.some(car => car.alive)) { - this.distanceTraveled += this.speed; + this.distanceTraveled += 1; } // Check if all cars are dead diff --git a/main.js b/main.js index 2ce00ca..f6e9288 100644 --- a/main.js +++ b/main.js @@ -1,10 +1,6 @@ import { SimulationManager } from './helpers/SimulationManager.js'; import { StatisticsManager } from './helpers/StatisticsManager.js'; -import { - initRoadLines, - updateRoadLines, - drawRoadLines, - } from './helpers/roadLines.js'; + import { ObstacleManager } from './helpers/ObstacleManager.js'; // Get canvas and context @@ -13,9 +9,6 @@ canvas.width = 600; canvas.height = 800; const ctx = canvas.getContext("2d"); -// Initialize road lines -initRoadLines(canvas.height); - // Define constants const SPEED = 1; const NUM_OBSTACLES = 10; @@ -23,7 +16,7 @@ const POPULATION_SIZE = 100; const PARENT_COUNT = 10; const MUTATION_RATE = 0.2; -const simManager = new SimulationManager(canvas, new ObstacleManager(canvas, NUM_OBSTACLES, SPEED), +const simManager = new SimulationManager(canvas, new ObstacleManager(canvas, NUM_OBSTACLES), POPULATION_SIZE, PARENT_COUNT, SPEED, MUTATION_RATE); // Initialize statistics manager @@ -33,9 +26,6 @@ const statsManager = new StatisticsManager(simManager); function animate() { ctx.clearRect(0, 0, canvas.width, canvas.height); - updateRoadLines(SPEED, canvas.height); - drawRoadLines(ctx, canvas.width); - simManager.update(); simManager.draw(ctx); From a923c826123e3680167a7725a750926f95f93da7 Mon Sep 17 00:00:00 2001 From: Mathom Johnson <156151500+MathomJohnson@users.noreply.github.com> Date: Mon, 23 Jun 2025 19:57:42 -0500 Subject: [PATCH 2/2] Expand arena and use square obstacles --- helpers/ObstacleManager.js | 3 ++- main.js | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/helpers/ObstacleManager.js b/helpers/ObstacleManager.js index e2f5e11..6a2cf23 100644 --- a/helpers/ObstacleManager.js +++ b/helpers/ObstacleManager.js @@ -4,8 +4,9 @@ export class ObstacleManager { constructor(canvas, obstacleCount = 5) { this.canvas = canvas; this.count = obstacleCount; + // Use square obstacles inside the arena this.width = 40; - this.height = 15; + this.height = 40; this.obstacles = []; this.initializeObstacles(); diff --git a/main.js b/main.js index f6e9288..77ab9c4 100644 --- a/main.js +++ b/main.js @@ -5,7 +5,8 @@ import { ObstacleManager } from './helpers/ObstacleManager.js'; // Get canvas and context const canvas = document.getElementById("canvas"); -canvas.width = 600; +// Make the arena a slightly larger square +canvas.width = 800; canvas.height = 800; const ctx = canvas.getContext("2d");