| 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,6,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); | |
| } | |
| } | |
| } |