diff --git a/helpers/SimulationManager.js b/helpers/SimulationManager.js index 6744d6d..c62c84e 100644 --- a/helpers/SimulationManager.js +++ b/helpers/SimulationManager.js @@ -2,13 +2,14 @@ import { Car } from "../classes/Car.js"; import { NeuralNetwork } from "../classes/NeuralNetwork.js"; export class SimulationManager { - constructor(canvas, obstacleManager, populationSize, eliteCount, speed, mutationRate) { + constructor(canvas, obstacleManager, populationSize, eliteCount, speed, mutationRate, onGenerationComplete = () => {}) { this.canvas = canvas; this.obstacleManager = obstacleManager; this.populationSize = populationSize; this.eliteCount = eliteCount; this.speed = speed; this.mutationRate = mutationRate; + this.onGenerationComplete = onGenerationComplete; this.generation = 1; // keep track of generations this.CAR_WIDTH = 30; @@ -137,6 +138,10 @@ export class SimulationManager { console.log(`Distance: ${Math.floor(this.distanceTraveled)}`); elites[0].brain.printNetwork(); + if (this.onGenerationComplete) { + this.onGenerationComplete(this.generation, this.distanceTraveled); + } + const newCars = []; const nextGen = this.generation + 1; diff --git a/index.html b/index.html index febe057..87f04ab 100644 --- a/index.html +++ b/index.html @@ -6,7 +6,13 @@ - +
+ +
+ +
+ +
diff --git a/main.js b/main.js index 561775a..8c91975 100644 --- a/main.js +++ b/main.js @@ -12,6 +12,62 @@ canvas.width = 600; canvas.height = 800; const ctx = canvas.getContext("2d"); +const statsBtn = document.getElementById('stats-btn'); +const statsModal = document.getElementById('stats-modal'); +const statsCanvas = document.getElementById('stats-canvas'); +const statsCtx = statsCanvas.getContext('2d'); +const statsData = []; + +function drawStatsGraph() { + statsCtx.clearRect(0, 0, statsCanvas.width, statsCanvas.height); + if (statsData.length === 0) return; + + const padding = 30; + const maxGen = Math.max(...statsData.map(d => d.generation)); + const maxDist = Math.max(...statsData.map(d => d.distance)); + + statsCtx.strokeStyle = '#fff'; + statsCtx.beginPath(); + statsCtx.moveTo(padding, padding); + statsCtx.lineTo(padding, statsCanvas.height - padding); + statsCtx.lineTo(statsCanvas.width - padding, statsCanvas.height - padding); + statsCtx.stroke(); + + statsCtx.fillStyle = '#fff'; + statsCtx.font = '12px monospace'; + statsCtx.textAlign = 'center'; + statsCtx.fillText('Generation', statsCanvas.width / 2, statsCanvas.height - 10); + statsCtx.save(); + statsCtx.translate(10, statsCanvas.height / 2); + statsCtx.rotate(-Math.PI / 2); + statsCtx.fillText('Distance', 0, 0); + statsCtx.restore(); + + statsCtx.strokeStyle = 'cyan'; + statsCtx.beginPath(); + statsData.forEach((d, i) => { + const x = padding + ((d.generation - 1) / Math.max(1, maxGen - 1)) * (statsCanvas.width - 2 * padding); + const y = (statsCanvas.height - padding) - (d.distance / Math.max(1, maxDist)) * (statsCanvas.height - 2 * padding); + if (i === 0) statsCtx.moveTo(x, y); else statsCtx.lineTo(x, y); + }); + statsCtx.stroke(); +} + +function handleGenerationComplete(gen, dist) { + statsData.push({ generation: gen, distance: dist }); + drawStatsGraph(); +} + +statsBtn.addEventListener('click', () => { + const visible = statsModal.style.display === 'flex'; + statsModal.style.display = visible ? 'none' : 'flex'; + if (!visible) drawStatsGraph(); +}); + +statsModal.addEventListener('click', (e) => { + if (e.target === statsModal) statsModal.style.display = 'none'; +}); + // Initialize road lines initRoadLines(canvas.height); @@ -22,8 +78,15 @@ const POPULATION_SIZE = 75; const PARENT_COUNT = 10; const MUTATION_RATE = 0.2; -const simManager = new SimulationManager(canvas, new ObstacleManager(canvas, NUM_OBSTACLES, SPEED), - POPULATION_SIZE, PARENT_COUNT, SPEED, MUTATION_RATE); +const simManager = new SimulationManager( + canvas, + new ObstacleManager(canvas, NUM_OBSTACLES, SPEED), + POPULATION_SIZE, + PARENT_COUNT, + SPEED, + MUTATION_RATE, + handleGenerationComplete +); // Animation loop function animate() { diff --git a/style.css b/style.css index e593ee5..b066183 100644 --- a/style.css +++ b/style.css @@ -1,12 +1,46 @@ + body { - margin: 0; - background: #111; - overflow: hidden; - } + margin: 0; + background: #111; + overflow: hidden; + display: flex; + justify-content: center; +} + +#game-container { + position: relative; + width: fit-content; +} canvas { display: block; margin: 0 auto; background: #222; } - \ No newline at end of file + +#stats-btn { + position: absolute; + top: 10px; + right: 10px; + padding: 6px 12px; + z-index: 2; +} + +#stats-modal { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: none; + background: rgba(0, 0, 0, 0.6); + align-items: center; + justify-content: center; + z-index: 3; +} + +#stats-modal canvas { + background: #333; + border: 1px solid #555; +} +