Skip to content

Commit

Permalink
Live stream code
Browse files Browse the repository at this point in the history
  • Loading branch information
gniziemazity authored Nov 4, 2022
1 parent 70b48f3 commit 161916a
Show file tree
Hide file tree
Showing 11 changed files with 627 additions and 0 deletions.
147 changes: 147 additions & 0 deletions 10. Live stream variant/car.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
class Car{
constructor(x,y,width,height,controlType,maxSpeed=3){
this.x=x;
this.y=y;
this.width=width;
this.height=height;

this.speed=0;
this.acceleration=0.2;
this.maxSpeed=maxSpeed;
this.friction=0.05;
this.angle=0;

this.damaged=false;

this.useBrain=controlType=="AI";

if(controlType!="DUMMY"){
this.sensor=new Sensor();
this.brain=new NeuralNetwork(
[this.sensor.rayCount,4]
);
}
this.controls=new Controls(controlType);
}

update(roadBorders,traffic){
if(!this.damaged){
this.#move();
this.polygon=this.#createPolygon();
this.damaged=this.#assessDamage(roadBorders,traffic);
}
if(this.sensor){
this.sensor.update(this.x,this.y,this.angle,roadBorders,traffic);
const offsets=this.sensor.readings.map(
s=>s==null?0:1-s.offset
);
const outputs=NeuralNetwork.feedForward(offsets,this.brain);
if(this.useBrain){
this.controls.forward=outputs[0];
this.controls.left=outputs[1];
this.controls.right=outputs[2];
this.controls.reverse=outputs[3];
}
}
}

#assessDamage(roadBorders,traffic){
for(let i=0;i<roadBorders.length;i++){
if(polysIntersect(
[...this.polygon,this.polygon[0]],
roadBorders[i])
){
return true;
}
}
for(let i=0;i<traffic.length;i++){
const poly=traffic[i].polygon;
if(polysIntersect(
[...this.polygon,this.polygon[0]],
[...poly,poly[0]])
){
return true;
}
}
return false;
}

#createPolygon(){
const points=[];
const rad=Math.hypot(this.width,this.height)/2;
const alpha=Math.atan2(this.width,this.height);
points.push({
x:this.x-Math.sin(this.angle-alpha)*rad,
y:this.y-Math.cos(this.angle-alpha)*rad
});
points.push({
x:this.x-Math.sin(this.angle+alpha)*rad,
y:this.y-Math.cos(this.angle+alpha)*rad
});
points.push({
x:this.x-Math.sin(Math.PI+this.angle-alpha)*rad,
y:this.y-Math.cos(Math.PI+this.angle-alpha)*rad
});
points.push({
x:this.x-Math.sin(Math.PI+this.angle+alpha)*rad,
y:this.y-Math.cos(Math.PI+this.angle+alpha)*rad
});
return points;
}

#move(){
if(this.controls.forward){
this.speed+=this.acceleration;
}
if(this.controls.reverse){
this.speed-=this.acceleration;
}

if(this.speed!=0){
const flip=this.speed>0?1:-1;
if(this.controls.left){
this.angle+=0.03*flip;
}
if(this.controls.right){
this.angle-=0.03*flip;
}
}

if(this.speed>this.maxSpeed){
this.speed=this.maxSpeed;
}
if(this.speed<-this.maxSpeed/2){
this.speed=-this.maxSpeed/2;
}

if(this.speed>0){
this.speed-=this.friction;
}
if(this.speed<0){
this.speed+=this.friction;
}
if(Math.abs(this.speed)<this.friction){
this.speed=0;
}

this.x-=Math.sin(this.angle)*this.speed;
this.y-=Math.cos(this.angle)*this.speed;
}

draw(ctx,drawSensor=false){
if(this.damaged){
ctx.fillStyle="gray";
}else{
ctx.fillStyle="black";
}
ctx.beginPath();
ctx.moveTo(this.polygon[0].x,this.polygon[0].y);
for(let i=1;i<this.polygon.length;i++){
ctx.lineTo(this.polygon[i].x,this.polygon[i].y);
}
ctx.fill();
if(this.sensor && drawSensor){
this.sensor.draw(ctx);
}
}
}
52 changes: 52 additions & 0 deletions 10. Live stream variant/controls.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
class Controls{
constructor(type){
this.forward=false;
this.left=false;
this.right=false;
this.reverse=false;

switch(type){
case "KEYS":
this.#addKeyboardListeners();
break;
case "DUMMY":
this.forward=true;
break;
}
}

#addKeyboardListeners(){
document.onkeydown=(event)=>{
switch(event.key){
case "ArrowLeft":
this.left=true;
break;
case "ArrowRight":
this.right=true;
break;
case "ArrowUp":
this.forward=true;
break;
case "ArrowDown":
this.reverse=true;
break;
}
}
document.onkeyup=(event)=>{
switch(event.key){
case "ArrowLeft":
this.left=false;
break;
case "ArrowRight":
this.right=false;
break;
case "ArrowUp":
this.forward=false;
break;
case "ArrowDown":
this.reverse=false;
break;
}
}
}
}
18 changes: 18 additions & 0 deletions 10. Live stream variant/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html>
<head>
<title>JS - Self-driving Car - LIVE</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<canvas id="carCanvas"></canvas><canvas id="networkCanvas"></canvas>
<script src="visualizer.js"></script>
<script src="network.js"></script>
<script src="sensor.js"></script>
<script src="utils.js"></script>
<script src="road.js"></script>
<script src="controls.js"></script>
<script src="car.js"></script>
<script src="main.js"></script>
</body>
</html>
74 changes: 74 additions & 0 deletions 10. Live stream variant/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
carCanvas.height=window.innerHeight;
carCanvas.width=200;
networkCanvas.height=window.innerHeight;
networkCanvas.width=298;

const carCtx=carCanvas.getContext("2d");
const networkCtx=networkCanvas.getContext("2d");
const road=new Road(carCanvas.width/2,carCanvas.width*0.9);
const N=100;
const cars=generateCars(N);
const traffic=[
new Car(100,-100,30,50,"DUMMY",2)
];
let bestCar=cars[0];
if(localStorage.getItem("bestBrain")){
for(let i=0;i<cars.length;i++){
cars[i].brain=JSON.parse(
localStorage.getItem("bestBrain"));
if(i>0){
NeuralNetwork.mutate(cars[i].brain,0.4);
}
}
}

animate();

function animate(){
for(let i=0;i<traffic.length;i++){
traffic[i].update([],[]);
}
for(let i=0;i<cars.length;i++){
cars[i].update(road.borders,traffic);
}
bestCar=cars.find(
c=>c.y==Math.min(
...cars.map(c=>c.y)
));

carCanvas.height=window.innerHeight;
networkCanvas.height=window.innerHeight;

carCtx.translate(0,-bestCar.y+carCanvas.height*0.7);
road.draw(carCtx);
for(let i=0;i<traffic.length;i++){
traffic[i].draw(carCtx);
}
carCtx.globalAlpha=0.2;
for(let i=0;i<cars.length;i++){
cars[i].draw(carCtx);
}
carCtx.globalAlpha=1;
bestCar.draw(carCtx,true);

Visualizer.drawNetwork(networkCtx,bestCar.brain);

requestAnimationFrame(animate);
}

function generateCars(N){
const cars=[];
for(let i=1;i<=N;i++){
cars.push(new Car(100,100,30,50,"AI"));
}
return cars;
}

function save(){
localStorage.setItem("bestBrain",
JSON.stringify(bestCar.brain));
}

function discard(){
localStorage.removeItem("bestBrain");
}
87 changes: 87 additions & 0 deletions 10. Live stream variant/network.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
class NeuralNetwork{
constructor(neuronCounts){
this.levels=[];
for(let i=0;i<neuronCounts.length-1;i++){
this.levels.push(new Level(
neuronCounts[i],neuronCounts[i+1]
));
}
}

static feedForward(givenInputs,network){
let outputs=Level.feedForward(
givenInputs,network.levels[0]);
for(let i=1;i<network.levels.length;i++){
outputs=Level.feedForward(
outputs,network.levels[i]);
}
return outputs;
}

static mutate(network,amount=1){
network.levels.forEach(level => {
for(let i=0;i<level.biases.length;i++){
level.biases[i]=lerp(
level.biases[i],
Math.random()*2-1,
amount
)
}
for(let i=0;i<level.weights.length;i++){
for(let j=0;j<level.weights[i].length;j++){
level.weights[i][j]=lerp(
level.weights[i][j],
Math.random()*2-1,
amount
)
}
}
});
}
}

class Level{
constructor(inputCount,outputCount){
this.inputs=new Array(inputCount);
this.outputs=new Array(outputCount);
this.biases=new Array(outputCount);

this.weights=[];
for(let i=0;i<inputCount;i++){
this.weights[i]=new Array(outputCount);
}

Level.#randomize(this);
}

static #randomize(level){
for(let i=0;i<level.inputs.length;i++){
for(let j=0;j<level.outputs.length;j++){
level.weights[i][j]=Math.random()*2-1;
}
}

for(let i=0;i<level.biases.length;i++){
level.biases[i]=Math.random()*2-1;
}
}

static feedForward(givenInputs,level){
level.inputs=[...givenInputs];

for(let i=0;i<level.outputs.length;i++){
let sum=0
for(let j=0;j<level.inputs.length;j++){
sum+=level.inputs[j]*level.weights[j][i];
}

if(sum>level.biases[i]){
level.outputs[i]=1;
}else{
level.outputs[i]=0;
}
}

return level.outputs;
}
}
10 changes: 10 additions & 0 deletions 10. Live stream variant/readme.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Link to live stream:
https://youtu.be/NUjN2Mln_Gg

Instructions:

Call the save() and discard() methods in the browser console when you want to store a car's brain in local storage.

Play with the number of cars simulated in parallel N (line 9) and the mutation amount (line 0.4)

Changing properties of the neural network or the sensor requires you to call discard() in the console so that the old ones don't come from local storage.
Loading

0 comments on commit 161916a

Please sign in to comment.