├── 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 |
--------------------------------------------------------------------------------