├── car.png ├── style.css ├── README.md ├── index.html ├── utils.js ├── controls.js ├── road.js ├── network.js ├── sensor.js ├── visualizer.js ├── car.js └── main.js /car.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mahdi-eth/Self-Driving-Car-Simulation/HEAD/car.png -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | html { 2 | font-size: 62.5%; 3 | } 4 | 5 | body { 6 | margin: 0; 7 | background: darkgray; 8 | overflow: hidden; 9 | display: flex; 10 | justify-content: center; 11 | align-items: center; 12 | gap: 1rem; 13 | } 14 | 15 | #verticalButtons { 16 | display: flex; 17 | flex-direction: column; 18 | gap: 1rem; 19 | } 20 | 21 | button { 22 | border: none; 23 | border-radius: 2rem; 24 | padding: 0.5rem 0.5rem 0.7rem; 25 | cursor: pointer; 26 | } 27 | 28 | button:hover { 29 | transition: all ease 0.25s; 30 | background: rgb(0, 195, 255); 31 | } 32 | 33 | #carCanvas { 34 | background: lightgray; 35 | } 36 | 37 | #networkCanvas { 38 | background: black; 39 | } 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Self-Driving Car Simulation 2 | 3 | A browser-based simulation training autonomous cars using a genetic algorithm. Cars equipped with neural networks learn to drive through generations of evolution. Sensors provide environmental data for decision-making. Train cars to navigate and complete driving tasks. Reinforcement learning and evolutionary computation combined in an interactive simulation. 4 | ## Features 5 | 6 | - Genetic algorithm for evolving car driving behavior 7 | - Neural networks as car "brains" to control driving 8 | - Simulation of car-environment interaction 9 | - Evaluation metrics for measuring driving performance 10 | - Visualization of the neural network 11 | - Manual saving and discarding of the best performing car's brain 12 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | SelfDriving car 16 | 17 | 18 | 19 |
20 | 21 | 22 |
23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /utils.js: -------------------------------------------------------------------------------- 1 | function lerp(A, B, t) { 2 | return A + (B - A) * t; 3 | } 4 | 5 | function getIntersection(A, B, C, D) { 6 | const tTop = (D.x - C.x) * (A.y - C.y) - (D.y - C.y) * (A.x - C.x); 7 | const uTop = (C.y - A.y) * (A.x - B.x) - (C.x - A.x) * (A.y - B.y); 8 | const bottom = (D.y - C.y) * (B.x - A.x) - (D.x - C.x) * (B.y - A.y); 9 | 10 | if (bottom != 0) { 11 | const t = tTop / bottom; 12 | const u = uTop / bottom; 13 | if (t >= 0 && t <= 1 && u >= 0 && u <= 1) { 14 | return { 15 | x: lerp(A.x, B.x, t), 16 | y: lerp(A.y, B.y, t), 17 | offset: t 18 | }; 19 | } 20 | } 21 | 22 | return null; 23 | } 24 | 25 | function polysIntersection(poly1, poly2) { 26 | for (let i = 0; i < poly1.length; i++) { 27 | for (let j = 0; j < poly2.length; j++) { 28 | const touch = getIntersection( 29 | poly1[i], 30 | poly1[(i + 1) % poly1.length], 31 | poly2[j], 32 | poly2[(j + 1) % poly2.length] 33 | ); 34 | if (touch) { 35 | return true; 36 | } 37 | } 38 | } 39 | return false; 40 | } 41 | 42 | function getRGBA(value) { 43 | const alpha = Math.abs(value); 44 | const R = value < 0 ? 0 : 255; 45 | const G = R; 46 | const B = value > 0 ? 0 : 255; 47 | return "rgba(" + R + "," + G + "," + B + "," + alpha + ")"; 48 | } 49 | 50 | 51 | function getRandomColor() { 52 | const hue = 290 + Math.random() * 260 53 | return "hsl(" + hue + ", 100%, 60%)" 54 | } -------------------------------------------------------------------------------- /controls.js: -------------------------------------------------------------------------------- 1 | class Controls { 2 | constructor(type) { 3 | this.forward = false; 4 | this.left = false; 5 | this.right = false; 6 | this.reverse = false; 7 | 8 | switch (type) { 9 | case "KEYS": 10 | this.#addKeyboardListeners(); 11 | break; 12 | case "REGULAR": 13 | this.forward = true; 14 | break; 15 | } 16 | } 17 | 18 | #addKeyboardListeners() { 19 | document.onkeydown = (e) => { 20 | switch (e.key) { 21 | case "ArrowLeft": 22 | this.left = true; 23 | break; 24 | case "ArrowRight": 25 | this.right = true; 26 | break; 27 | case "ArrowUp": 28 | this.forward = true; 29 | break; 30 | case "ArrowDown": 31 | this.reverse = true; 32 | break; 33 | } 34 | }; 35 | 36 | document.onkeyup = (e) => { 37 | switch (e.key) { 38 | case "ArrowLeft": 39 | this.left = false; 40 | break; 41 | case "ArrowRight": 42 | this.right = false; 43 | break; 44 | case "ArrowUp": 45 | this.forward = false; 46 | break; 47 | case "ArrowDown": 48 | this.reverse = false; 49 | break; 50 | } 51 | }; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /road.js: -------------------------------------------------------------------------------- 1 | class Road { 2 | constructor(x, w, laneCount = 3) { 3 | this.x = x; 4 | this.w = w; 5 | this.laneCount = laneCount; 6 | 7 | this.left = x - w / 2; 8 | this.right = x + w / 2; 9 | 10 | const infinite = 1000000; 11 | this.top = -infinite; 12 | this.bottom = infinite; 13 | 14 | const topLeft = { x: this.left, y: this.top }; 15 | const topRight = { x: this.right, y: this.top }; 16 | const bottomLeft = { x: this.left, y: this.bottom }; 17 | const bottomRight = { x: this.right, y: this.bottom }; 18 | this.borders = [ 19 | [topLeft, bottomLeft], 20 | [topRight, bottomRight] 21 | ]; 22 | } 23 | 24 | getLaneCenter(laneIndex) { 25 | const laneWidth = this.w / this.laneCount; 26 | return ( 27 | this.left + 28 | laneWidth / 2 + 29 | Math.min(laneIndex, this.laneCount - 1) * laneWidth 30 | ); 31 | } 32 | 33 | draw(ctx) { 34 | ctx.lineWidth = 5; 35 | ctx.strokeStyle = "white"; 36 | 37 | for (let i = 1; i < this.laneCount; i++) { 38 | const x = lerp(this.left, this.right, i / this.laneCount); 39 | 40 | ctx.setLineDash([20, 20]); 41 | ctx.beginPath(); 42 | ctx.moveTo(x, this.top); 43 | ctx.lineTo(x, this.bottom); 44 | ctx.stroke(); 45 | } 46 | 47 | ctx.setLineDash([]); 48 | this.borders.forEach((border) => { 49 | ctx.beginPath(); 50 | ctx.moveTo(border[0].x, border[0].y); 51 | ctx.lineTo(border[1].x, border[1].y); 52 | ctx.stroke(); 53 | }); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /network.js: -------------------------------------------------------------------------------- 1 | class NeuralNetwork { 2 | constructor(neuronCounts) { 3 | this.levels = []; 4 | for (let i = 0; i < neuronCounts.length - 1; i++) { 5 | this.levels.push(new Level(neuronCounts[i], neuronCounts[i + 1])); 6 | } 7 | } 8 | 9 | static feedForward(givenInputs, network) { 10 | let outputs = Level.feedForward(givenInputs, network.levels[0]); 11 | for (let i = 1; i < network.levels.length; i++) { 12 | outputs = Level.feedForward(outputs, network.levels[i]); 13 | } 14 | return outputs; 15 | } 16 | 17 | static mutate(network, amount) { 18 | network.levels.forEach((level) => { 19 | for (let i = 0; i < level.biases.length; i++) { 20 | level.biases[i] = lerp( 21 | level.biases[i], 22 | Math.random() * 2 - 1, 23 | amount 24 | ); 25 | } 26 | for (let i = 0; i < level.weights.length; i++) { 27 | for (let j = 0; j < level.weights[i].length; j++) { 28 | level.weights[i][j] = lerp( 29 | level.weights[i][j], 30 | Math.random() * 2 - 1, 31 | amount 32 | ); 33 | } 34 | } 35 | }); 36 | } 37 | } 38 | 39 | class Level { 40 | constructor(inputCount, outputCount) { 41 | this.inputs = new Array(inputCount); 42 | this.outputs = new Array(outputCount); 43 | this.biases = new Array(outputCount); 44 | 45 | this.weights = []; 46 | for (let i = 0; i < inputCount; i++) { 47 | this.weights[i] = new Array(outputCount); 48 | } 49 | 50 | Level.#randomize(this); 51 | } 52 | 53 | static #randomize(level) { 54 | for (let i = 0; i < level.inputs.length; i++) { 55 | for (let j = 0; j < level.outputs.length; j++) { 56 | level.weights[i][j] = Math.random() * 2 - 1; 57 | } 58 | } 59 | 60 | for (let i = 0; i < level.biases.length; i++) { 61 | level.biases[i] = Math.random() * 2 - 1; 62 | } 63 | } 64 | 65 | static feedForward(givenInputs, level) { 66 | for (let i = 0; i < level.inputs.length; i++) { 67 | level.inputs[i] = givenInputs[i]; 68 | } 69 | 70 | for (let i = 0; i < level.outputs.length; i++) { 71 | let sum = 0; 72 | for (let j = 0; j < level.inputs.length; j++) { 73 | sum += level.inputs[j] * level.weights[j][i]; 74 | } 75 | 76 | if (sum > level.biases[i]) { 77 | level.outputs[i] = 1; 78 | } else { 79 | level.outputs[i] = 0; 80 | } 81 | } 82 | 83 | return level.outputs; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /sensor.js: -------------------------------------------------------------------------------- 1 | class Sensor { 2 | constructor(car) { 3 | this.car = car; 4 | this.rayCount = 5; 5 | this.rayLength = 150; 6 | this.raySpread = Math.PI / 2; 7 | 8 | this.rays = []; 9 | this.readings = []; 10 | } 11 | 12 | update(roadBorders, traffic) { 13 | this.#castRays(); 14 | this.readings = []; 15 | for (let i = 0; i < this.rays.length; i++) { 16 | this.readings.push( 17 | this.#getReading(this.rays[i], roadBorders, traffic) 18 | ); 19 | } 20 | } 21 | 22 | #getReading(ray, roadBorders, traffic) { 23 | let touches = []; 24 | for (let i = 0; i < roadBorders.length; i++) { 25 | const touch = getIntersection( 26 | ray[0], 27 | ray[1], 28 | roadBorders[i][0], 29 | roadBorders[i][1] 30 | ); 31 | if (touch) { 32 | touches.push(touch); 33 | } 34 | } 35 | 36 | for (let i = 0; i < traffic.length; i++) { 37 | const poly = traffic[i].polygon; 38 | for (let j = 0; j < poly.length; j++) { 39 | const value = getIntersection( 40 | ray[0], 41 | ray[1], 42 | poly[j], 43 | poly[(j + 1) % poly.length] 44 | ); 45 | if (value) { 46 | touches.push(value); 47 | } 48 | } 49 | } 50 | 51 | if (touches.length == 0) { 52 | return null; 53 | } else { 54 | const offsets = touches.map((e) => e.offset); 55 | const minOffset = Math.min(...offsets); 56 | return touches.find((e) => e.offset == minOffset); 57 | } 58 | } 59 | 60 | #castRays() { 61 | this.rays = []; 62 | for (let i = 0; i < this.rayCount; i++) { 63 | const rayAngle = 64 | lerp( 65 | this.raySpread / 2, 66 | -this.raySpread / 2, 67 | this.rayCount == 1 ? 0.5 : i / (this.rayCount - 1) 68 | ) + this.car.angle; 69 | 70 | const start = { x: this.car.x, y: this.car.y }; 71 | const end = { 72 | x: this.car.x - Math.sin(rayAngle) * this.rayLength, 73 | y: this.car.y - Math.cos(rayAngle) * this.rayLength 74 | }; 75 | this.rays.push([start, end]); 76 | } 77 | } 78 | 79 | draw(ctx) { 80 | for (let i = 0; i < this.rayCount; i++) { 81 | let end = this.rays[i][1]; 82 | if (this.readings[i]) { 83 | end = this.readings[i]; 84 | } 85 | ctx.beginPath(); 86 | ctx.lineWidth = 2; 87 | ctx.strokeStyle = "yellow"; 88 | ctx.moveTo(this.rays[i][0].x, this.rays[i][0].y); 89 | ctx.lineTo(end.x, end.y); 90 | ctx.stroke(); 91 | 92 | ctx.beginPath(); 93 | ctx.lineWidth = 2; 94 | ctx.strokeStyle = "black"; 95 | ctx.moveTo(this.rays[i][1].x, this.rays[i][1].y); 96 | ctx.lineTo(end.x, end.y); 97 | ctx.stroke(); 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /visualizer.js: -------------------------------------------------------------------------------- 1 | class Visualizer { 2 | static drawNetwork(ctx, network) { 3 | const margin = 50; 4 | const left = margin; 5 | const top = margin; 6 | const width = ctx.canvas.width - margin * 2; 7 | const height = ctx.canvas.height - margin * 2; 8 | 9 | const levelHeight = height / network.levels.length; 10 | 11 | for (let i = network.levels.length - 1; i >= 0; i--) { 12 | const levelTop = 13 | top + 14 | lerp( 15 | height - levelHeight, 16 | 0, 17 | network.levels.length == 1 18 | ? 0.5 19 | : i / (network.levels.length - 1) 20 | ); 21 | 22 | ctx.setLineDash([7, 3]); 23 | Visualizer.drawLevel( 24 | ctx, 25 | network.levels[i], 26 | left, 27 | levelTop, 28 | width, 29 | levelHeight, 30 | i == network.levels.length - 1 ? ["🠉", "🠈", "🠊", "🠋"] : [] 31 | ); 32 | } 33 | } 34 | 35 | static drawLevel(ctx, level, left, top, width, height, outputLabels) { 36 | const right = left + width; 37 | const bottom = top + height; 38 | 39 | const { inputs, outputs, weights, biases } = level; 40 | 41 | for (let i = 0; i < inputs.length; i++) { 42 | for (let j = 0; j < outputs.length; j++) { 43 | ctx.beginPath(); 44 | ctx.moveTo( 45 | Visualizer.#getNodeX(inputs, i, left, right), 46 | bottom 47 | ); 48 | ctx.lineTo(Visualizer.#getNodeX(outputs, j, left, right), top); 49 | ctx.lineWidth = 2; 50 | ctx.strokeStyle = getRGBA(weights[i][j]); 51 | ctx.stroke(); 52 | } 53 | } 54 | 55 | const nodeRadius = 18; 56 | for (let i = 0; i < inputs.length; i++) { 57 | const x = Visualizer.#getNodeX(inputs, i, left, right); 58 | ctx.beginPath(); 59 | ctx.arc(x, bottom, nodeRadius, 0, Math.PI * 2); 60 | ctx.fillStyle = "black"; 61 | ctx.fill(); 62 | ctx.beginPath(); 63 | ctx.arc(x, bottom, nodeRadius * 0.6, 0, Math.PI * 2); 64 | ctx.fillStyle = getRGBA(inputs[i]); 65 | ctx.fill(); 66 | } 67 | 68 | for (let i = 0; i < outputs.length; i++) { 69 | const x = Visualizer.#getNodeX(outputs, i, left, right); 70 | ctx.beginPath(); 71 | ctx.arc(x, top, nodeRadius, 0, Math.PI * 2); 72 | ctx.fillStyle = "black"; 73 | ctx.fill(); 74 | ctx.beginPath(); 75 | ctx.arc(x, top, nodeRadius * 0.6, 0, Math.PI * 2); 76 | ctx.fillStyle = getRGBA(outputs[i]); 77 | ctx.fill(); 78 | 79 | ctx.beginPath(); 80 | ctx.lineWidth = 2; 81 | ctx.arc(x, top, nodeRadius * 0.8, 0, Math.PI * 2); 82 | ctx.strokeStyle = getRGBA(biases[i]); 83 | ctx.setLineDash([3, 3]); 84 | ctx.stroke(); 85 | ctx.setLineDash([]); 86 | 87 | if (outputLabels[i]) { 88 | ctx.beginPath(); 89 | ctx.textAlign = "center"; 90 | ctx.textBaseline = "middle"; 91 | ctx.fillStyle = "black"; 92 | ctx.strokeStyle = "white"; 93 | ctx.font = nodeRadius * 1.5 + "px Arial"; 94 | ctx.fillText(outputLabels[i], x, top + nodeRadius * 0.1); 95 | ctx.lineWidth = 0.5; 96 | ctx.strokeText(outputLabels[i], x, top + nodeRadius * 0.1); 97 | } 98 | } 99 | } 100 | 101 | static #getNodeX(nodes, index, left, right) { 102 | return lerp( 103 | left, 104 | right, 105 | nodes.length == 1 ? 0.5 : index / (nodes.length - 1) 106 | ); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /car.js: -------------------------------------------------------------------------------- 1 | class Car { 2 | constructor(x, y, w, h, controlType, maxSpeed = 3, color = "blue") { 3 | this.x = x; 4 | this.y = y; 5 | this.w = w; 6 | this.h = h; 7 | 8 | this.speed = 1.5; 9 | this.acceleration = 0.2; 10 | this.maxSpeed = maxSpeed; 11 | this.friction = 0.05; 12 | this.angle = 0; 13 | this.damaged = false; 14 | 15 | this.useBrain = controlType == "AI"; 16 | 17 | if (controlType != "REGULAR") { 18 | this.sensor = new Sensor(this); 19 | this.brain = new NeuralNetwork([this.sensor.rayCount, 6, 4]); 20 | } 21 | this.controls = new Controls(controlType); 22 | 23 | this.img = new Image(); 24 | this.img.src = "car.png"; 25 | 26 | this.mask = document.createElement("canvas"); 27 | this.mask.width = w; 28 | this.mask.height = h; 29 | 30 | const maskCtx = this.mask.getContext("2d"); 31 | 32 | this.img.onload = () => { 33 | maskCtx.fillStyle = color; 34 | maskCtx.rect(0, 0, this.w, this.h); 35 | maskCtx.fill(); 36 | 37 | maskCtx.globalCompositeOperation = "destination-atop"; 38 | maskCtx.drawImage(this.img, 0, 0, this.w, this.h); 39 | }; 40 | } 41 | 42 | update(roadBorders, traffic) { 43 | if (!this.damaged) { 44 | this.#move(); 45 | this.polygon = this.#createPolygon(); 46 | this.damaged = this.#assessDamage(roadBorders, traffic); 47 | } 48 | if (this.sensor) { 49 | this.sensor.update(roadBorders, traffic); 50 | const offsets = this.sensor.readings.map((s) => 51 | s == null ? 0 : 1 - s.offset 52 | ); 53 | const outputs = NeuralNetwork.feedForward(offsets, this.brain); 54 | 55 | if (this.useBrain) { 56 | this.controls.forward = outputs[0]; 57 | this.controls.left = outputs[1]; 58 | this.controls.right = outputs[2]; 59 | this.controls.reverse = outputs[3]; 60 | } 61 | } 62 | } 63 | 64 | #assessDamage(roadBorders, traffic) { 65 | for (let i = 0; i < roadBorders.length; i++) { 66 | if (polysIntersection(this.polygon, roadBorders[i])) { 67 | return true; 68 | } 69 | } 70 | 71 | for (let i = 0; i < traffic.length; i++) { 72 | if (polysIntersection(this.polygon, traffic[i].polygon)) { 73 | return true; 74 | } 75 | } 76 | 77 | return false; 78 | } 79 | 80 | #createPolygon() { 81 | const points = []; 82 | const rad = Math.hypot(this.w, this.h) / 2; 83 | const alpha = Math.atan2(this.w, this.h); 84 | 85 | points.push({ 86 | x: this.x - Math.sin(this.angle - alpha) * rad, 87 | y: this.y - Math.cos(this.angle - alpha) * rad 88 | }); 89 | 90 | points.push({ 91 | x: this.x - Math.sin(this.angle + alpha) * rad, 92 | y: this.y - Math.cos(this.angle + alpha) * rad 93 | }); 94 | 95 | points.push({ 96 | x: this.x - Math.sin(Math.PI + this.angle - alpha) * rad, 97 | y: this.y - Math.cos(Math.PI + this.angle - alpha) * rad 98 | }); 99 | 100 | points.push({ 101 | x: this.x - Math.sin(Math.PI + this.angle + alpha) * rad, 102 | y: this.y - Math.cos(Math.PI + this.angle + alpha) * rad 103 | }); 104 | 105 | return points; 106 | } 107 | 108 | #move() { 109 | if (this.controls.forward) { 110 | this.speed += this.acceleration; 111 | } else if (this.controls.reverse) { 112 | this.speed -= this.acceleration; 113 | } 114 | 115 | if (this.speed > this.maxSpeed) { 116 | this.speed = this.maxSpeed; 117 | } 118 | if (this.speed < -this.maxSpeed / 2) { 119 | this.speed = -this.maxSpeed / 2; 120 | } 121 | 122 | if (this.speed > 0) { 123 | this.speed -= this.friction; 124 | } 125 | if (this.speed < 0) { 126 | this.speed += this.friction; 127 | } 128 | if (Math.abs(this.speed) < this.friction) { 129 | this.speed = 0; 130 | } 131 | 132 | if (this.speed != 0) { 133 | const flip = this.speed > 0 ? 1 : -1; 134 | 135 | if (this.controls.left) { 136 | this.angle += 0.015 * flip; 137 | } 138 | if (this.controls.right) { 139 | this.angle -= 0.015 * flip; 140 | } 141 | } 142 | 143 | this.x -= Math.sin(this.angle) * this.speed; 144 | this.y -= Math.cos(this.angle) * this.speed; 145 | } 146 | 147 | draw(ctx, drawSensor = false) { 148 | ctx.save(); 149 | ctx.translate(this.x, this.y); 150 | ctx.rotate(-this.angle); 151 | if (!this.damaged) { 152 | ctx.drawImage(this.mask, -this.w / 2, -this.h / 2, this.w, this.h); 153 | ctx.globalCompositeOperation = "multiply"; 154 | } 155 | ctx.drawImage(this.img, -this.w / 2, -this.h / 2, this.w, this.h); 156 | ctx.restore(); 157 | 158 | if (this.sensor && drawSensor) { 159 | this.sensor.draw(ctx); 160 | } 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | const carCanvas = document.getElementById("carCanvas"); 2 | carCanvas.width = 300; 3 | 4 | const networkCanvas = document.getElementById("networkCanvas"); 5 | networkCanvas.width = 300; 6 | 7 | const carCtx = carCanvas.getContext("2d"); 8 | const networkCtx = networkCanvas.getContext("2d"); 9 | 10 | const nLanes = 3; 11 | const road = new Road(carCanvas.width / 2, carCanvas.width * 0.9, nLanes); 12 | 13 | const N = 100; 14 | const cars = generateCars(N); 15 | let bestCar = cars[0]; 16 | if (localStorage.getItem("bestBrain")) { 17 | cars.map((car, i) => { 18 | car.brain = JSON.parse(localStorage.getItem("bestBrain")); 19 | if (i != 0) { 20 | NeuralNetwork.mutate(car.brain, 0.6); 21 | } 22 | }); 23 | } 24 | 25 | const traffic = [ 26 | new Car( 27 | road.getLaneCenter(Math.floor(nLanes / 2)), 28 | -100, 29 | 30, 30 | 50, 31 | "REGULAR", 32 | 2 33 | ), 34 | new Car(road.getLaneCenter(0), -300, 30, 50, "REGULAR", 2, getRandomColor()), 35 | new Car(road.getLaneCenter(nLanes), -300, 30, 50, "REGULAR", 2, getRandomColor()), 36 | new Car( 37 | road.getLaneCenter(Math.floor(nLanes / 2)), 38 | -500, 39 | 30, 40 | 50, 41 | "REGULAR", 42 | 2 43 | ), 44 | new Car(road.getLaneCenter(nLanes), -700, 30, 50, "REGULAR", 2, getRandomColor()), 45 | new Car( 46 | road.getLaneCenter(Math.floor(nLanes / 2)), 47 | -900, 48 | 30, 49 | 50, 50 | "REGULAR", 51 | 2 52 | ), 53 | new Car(road.getLaneCenter(0), -1100, 30, 50, "REGULAR", 2, getRandomColor()), 54 | new Car( 55 | road.getLaneCenter(Math.floor(nLanes / 2)), 56 | -1300, 57 | 30, 58 | 50, 59 | "REGULAR", 60 | 2 61 | ), 62 | new Car(road.getLaneCenter(nLanes), -1500, 30, 50, "REGULAR", 2, getRandomColor()), 63 | new Car(road.getLaneCenter(0), -1700, 30, 50, "REGULAR", 2, getRandomColor()), 64 | new Car( 65 | road.getLaneCenter(Math.floor(nLanes / 2)), 66 | -1900, 67 | 30, 68 | 50, 69 | "REGULAR", 70 | 2 71 | ), 72 | new Car(road.getLaneCenter(Math.floor(nLanes / 2)), -2300, 30, 50, "REGULAR", 2, getRandomColor()), 73 | new Car(road.getLaneCenter(0), -2500, 30, 50, "REGULAR", 2, getRandomColor()), 74 | new Car(road.getLaneCenter(nLanes), -2700, 30, 50, "REGULAR", 2, getRandomColor()), 75 | new Car(road.getLaneCenter(Math.floor(nLanes / 2)), -2900, 30, 50, "REGULAR", 2, getRandomColor()), 76 | new Car(road.getLaneCenter(0), -3100, 30, 50, "REGULAR", 2, getRandomColor()), 77 | new Car(road.getLaneCenter(nLanes), -3300, 30, 50, "REGULAR", 2, getRandomColor()), 78 | new Car(road.getLaneCenter(nLanes), -3500, 30, 50, "REGULAR", 2, getRandomColor()), 79 | new Car(road.getLaneCenter(0), -3700, 30, 50, "REGULAR", 2, getRandomColor()), 80 | new Car(road.getLaneCenter(Math.floor(nLanes / 2)), -3900, 30, 50, "REGULAR", 2, getRandomColor()), 81 | new Car(road.getLaneCenter(nLanes), -4100, 30, 50, "REGULAR", 2, getRandomColor()), 82 | new Car(road.getLaneCenter(0), -4300, 30, 50, "REGULAR", 2, getRandomColor()), 83 | new Car(road.getLaneCenter(Math.floor(nLanes / 2)), -4500, 30, 50, "REGULAR", 2, getRandomColor()), 84 | new Car(road.getLaneCenter(0), -4700, 30, 50, "REGULAR", 2, getRandomColor()), 85 | new Car(road.getLaneCenter(0), -4900, 30, 50, "REGULAR", 2, getRandomColor()), 86 | new Car(road.getLaneCenter(nLanes), -5100, 30, 50, "REGULAR", 2, getRandomColor()), 87 | new Car(road.getLaneCenter(0), -5300, 30, 50, "REGULAR", 2, getRandomColor()), 88 | new Car(road.getLaneCenter(Math.floor(nLanes / 2)), -5500, 30, 50, "REGULAR", 2, getRandomColor()), 89 | new Car(road.getLaneCenter(0), -5700, 30, 50, "REGULAR", 2, getRandomColor()), 90 | new Car(road.getLaneCenter(Math.floor(nLanes / 2)), -5900, 30, 50, "REGULAR", 2, getRandomColor()), 91 | new Car(road.getLaneCenter(Math.floor(nLanes / 2)), -6100, 30, 50, "REGULAR", 2, getRandomColor()), 92 | new Car(road.getLaneCenter(nLanes), -6300, 30, 50, "REGULAR", 2, getRandomColor()), 93 | new Car(road.getLaneCenter(0), -6500, 30, 50, "REGULAR", 2, getRandomColor()), 94 | new Car(road.getLaneCenter(nLanes), -6700, 30, 50, "REGULAR", 2, getRandomColor()), 95 | new Car(road.getLaneCenter(Math.floor(nLanes / 2)), -6900, 30, 50, "REGULAR", 2, getRandomColor()), 96 | new Car(road.getLaneCenter(0), -7100, 30, 50, "REGULAR", 2, getRandomColor()), 97 | new Car(road.getLaneCenter(nLanes), -7300, 30, 50, "REGULAR", 2, getRandomColor()), 98 | new Car(road.getLaneCenter(Math.floor(nLanes / 2)), -7500, 30, 50, "REGULAR", 2, getRandomColor()), 99 | new Car(road.getLaneCenter(nLanes), -7700, 30, 50, "REGULAR", 2, getRandomColor()), 100 | new Car(road.getLaneCenter(Math.floor(nLanes / 2)), -7900, 30, 50, "REGULAR", 2, getRandomColor()), 101 | new Car(road.getLaneCenter(0), -8100, 30, 50, "REGULAR", 2, getRandomColor()), 102 | ]; 103 | 104 | animate(); 105 | 106 | function save() { 107 | localStorage.setItem("bestBrain", JSON.stringify(bestCar.brain)); 108 | } 109 | 110 | function discard() { 111 | localStorage.removeItem("bestBrain"); 112 | } 113 | 114 | function generateCars(N) { 115 | const cars = []; 116 | for (let i = 1; i < N; i++) { 117 | cars.push( 118 | new Car( 119 | road.getLaneCenter(Math.floor(nLanes / 2)), 120 | 100, 121 | 30, 122 | 50, 123 | "AI" 124 | ) 125 | ); 126 | } 127 | 128 | return cars; 129 | } 130 | 131 | function animate(time) { 132 | for (let i = 0; i < traffic.length; i++) { 133 | traffic[i].update(road.borders, []); 134 | } 135 | cars.map((car) => car.update(road.borders, traffic)); 136 | 137 | const bestCar = cars.find((car) => { 138 | return car.y == Math.min(...cars.map((car) => car.y)); 139 | }); 140 | 141 | carCanvas.height = window.innerHeight; 142 | networkCanvas.height = window.innerHeight; 143 | 144 | carCtx.save(); 145 | carCtx.translate(0, -bestCar.y + carCanvas.height * 0.7); 146 | 147 | road.draw(carCtx); 148 | 149 | for (let i = 0; i < traffic.length; i++) { 150 | traffic[i].draw(carCtx, "red"); 151 | } 152 | 153 | carCtx.globalAlpha = 0.2; 154 | cars.map((car) => car.draw(carCtx, "blue")); 155 | 156 | carCtx.globalAlpha = 1; 157 | bestCar.draw(carCtx, "blue", true); 158 | carCtx.restore(); 159 | 160 | networkCtx.lineDashOffset = -time / 50; 161 | Visualizer.drawNetwork(networkCtx, bestCar.brain); 162 | requestAnimationFrame(animate); 163 | } 164 | --------------------------------------------------------------------------------