Skip to content

Commit

Permalink
cars
Browse files Browse the repository at this point in the history
  • Loading branch information
Tom Hohler committed Apr 19, 2018
1 parent d64067d commit 0682623
Show file tree
Hide file tree
Showing 8 changed files with 200 additions and 23 deletions.
45 changes: 35 additions & 10 deletions src/demos/neat/racing/car.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Network } from "../../../neural-network/model/network";
import { Map } from "./map";
import { Position } from "./position";
import { Vector2 } from "./vector2";

Expand All @@ -13,6 +14,9 @@ export class Car {
activatedSensors: number[] = [0, 0, 0];
frozen: boolean = false;
pos: Position;
checkPoints: number = 0;
lastCheckPoint: number | null = null;
distanceToLastCheckPoint: number = 0;

constructor(pos: Position, brain?: Network) {
this.pos = pos;
Expand All @@ -34,7 +38,7 @@ export class Car {
}
}

checkCollisions = (map: number[][]) => {
checkCollisions = (map: Map, nextPosX: number, nextPosY: number) => {
const sensorsActivated = [false, false, false];
for (let k = 0; k < SENSOR_DISTANCE; k += sensorDelta) {
const firstSensorPos = {
Expand All @@ -55,10 +59,15 @@ export class Car {
if (sensorsActivated[i]) {
continue;
}
if (sensors[i].x < 0 || sensors[i].x > map.length - 1 || sensors[i].y < 0 || sensors[i].y > map.length - 1) {
if (
sensors[i].x < 0
|| sensors[i].x > map.collisionMap.length - 1
|| sensors[i].y < 0
|| sensors[i].y > map.collisionMap.length - 1
) {
this.activatedSensors[i] = 1;
} else {
if (map[sensors[i].x][sensors[i].y]) {
if (map.collisionMap[sensors[i].x][sensors[i].y]) {
this.activatedSensors[i] = 0;
} else {
this.activatedSensors[i] = (SENSOR_DISTANCE - k) / SENSOR_DISTANCE;
Expand All @@ -69,40 +78,56 @@ export class Car {
}

try {
if (!map[Math.floor(this.pos.x)][Math.floor(this.pos.y)]) {
if (!map.collisionMap[Math.floor(this.pos.x)][Math.floor(this.pos.y)]) {
this.frozen = true;
}
} catch (e) {
console.log("failed to check position", Math.floor(this.pos.x), Math.floor(this.pos.y));
throw e;
}

for (let i = 0; i < map.checkPoints.length; i++) {
const checkPoint = map.checkPoints[i];
if (this.lastCheckPoint !== i && checkPoint.passed(this.pos, new Position(nextPosX, nextPosY))) {
this.lastCheckPoint = i;
this.checkPoints ++;
}
}
}

update = (map: number[][], delta: number) => {
update = (map: Map, delta: number) => {
if (!this.brain) {
throw Error("This car has no brain");
}
this.makeDecision(this.brain);
if (!this.frozen) {
this.checkCollisions(map);
const nextPosX = this.pos.x + this.speed * Math.cos(this.direction) * delta;
const nextPosY = this.pos.y + this.speed * Math.sin(this.direction) * delta;

this.checkCollisions(map, nextPosX, nextPosY);
this.speed *= 0.999;
if (this.speed < 9) {
this.speed = 0;
}
if (this.speed > MAX_SPEED) {
this.speed = MAX_SPEED;
}
this.pos.x += this.speed * Math.cos(this.direction) * delta;
this.pos.y += this.speed * Math.sin(this.direction) * delta;
this.distanceToLastCheckPoint = this.lastCheckPoint == null ? 0 : map.checkPoints[this.lastCheckPoint].distanceTo(this.pos);
this.pos.x = nextPosX;
this.pos.y = nextPosY;
}
}

makeDecision = (brain: Network) => {
const brainOutput = brain.activate(this.activatedSensors);
const directionDecision = brainOutput[0];
const speedDecision = brainOutput[1];
this.turn(directionDecision > 0.5 ? "right" : "left");
if (speedDecision > 0.5) {
if (directionDecision > 0.7) {
this.turn("right");
} else if (directionDecision < 0.3) {
this.turn("left");
}
if (speedDecision > 0.4) {
this.accelerate();
} else {
this.brake();
Expand Down
30 changes: 30 additions & 0 deletions src/demos/neat/racing/checkPoint.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Position } from "./position";
import { Vector2 } from "./vector2";

export class CheckPoint {

constructor(readonly pos1: Position, readonly pos2: Position) {}

passed(previousPos: Position, nextPos: Position) {
const AB = new Vector2(this.pos2.x - this.pos1.x, this.pos2.y - this.pos1.y);
const pAM = new Vector2(previousPos.x - this.pos1.x, previousPos.y - this.pos1.y);
const nAM = new Vector2(nextPos.x - this.pos1.x, nextPos.y - this.pos1.y);

if (nAM.norm > AB.norm) {
return false;
}
if (AB.dot(nAM) < 0) {
return false;
}

return AB.determinant(nAM) === 0 || (AB.determinant(nAM) >= 0 && AB.determinant(pAM) <= 0);
}

distanceTo(pos: Position) {
return new Position((this.pos2.x + this.pos1.x) / 2, (this.pos2.y + this.pos1.y) / 2).distanceTo(pos);
}

static fromJson(json: any) {
return new CheckPoint(new Position(json.pos1.x, json.pos1.y), new Position(json.pos2.x, json.pos2.y));
}
}
82 changes: 82 additions & 0 deletions src/demos/neat/racing/checkPoints.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
[
{
"pos1": {
"x": 300,
"y": 10
},
"pos2": {
"x": 300,
"y": 140
}
},
{
"pos1": {
"x": 1000,
"y": 50
},
"pos2": {
"x": 800,
"y": 150
}
},
{
"pos1": {
"x": 850,
"y": 300
},
"pos2": {
"x": 650,
"y": 300
}
},
{
"pos1": {
"x": 1000,
"y": 700
},
"pos2": {
"x": 800,
"y": 580
}
},
{
"pos1": {
"x": 500,
"y": 750
},
"pos2": {
"x": 500,
"y": 600
}
},
{
"pos1": {
"x": 100,
"y": 500
},
"pos2": {
"x": 300,
"y": 550
}
},
{
"pos1": {
"x": 430,
"y": 270
},
"pos2": {
"x": 600,
"y": 200
}
},
{
"pos1": {
"x": 0,
"y": 200
},
"pos2": {
"x": 150,
"y": 200
}
}
]
Binary file removed src/demos/neat/racing/map.jpg
Binary file not shown.
5 changes: 5 additions & 0 deletions src/demos/neat/racing/map.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { CheckPoint } from "./checkPoint";

export class Map {
constructor(readonly collisionMap: number[][], readonly checkPoints: CheckPoint[]) {}
}
48 changes: 35 additions & 13 deletions src/demos/neat/racing/race.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,14 @@ import { Population } from "../../../genetic/model";
import { Activation } from "../../../neural-network/methods";
import { Network } from "../../../neural-network/model/network";
import { Car } from "./car";
import { CheckPoint } from "./checkPoint";
import * as checkPoints from "./checkPoints.json";
import { Map } from "./map";
import { Position } from "./position";
import { Vector2 } from "./vector2";
import { Selection } from "../../../genetic/methods";

window.onclick = e => console.log(e.clientX - 20, e.clientY - 20);
export function race() {
const c = document.getElementById("canvas") as HTMLCanvasElement;
const ctx = c.getContext("2d")!;
Expand Down Expand Up @@ -42,33 +47,40 @@ export function race() {
throw Error("failed to load images");
}
let carsPop = createPopulation();
let cars = carsPop.candidates.map(it => new Car(new Position(200, 50), Network.fromWeights(it.genes, [3, 4, 2, 2])));
let cars = carsPop.candidates.map(it => new Car(new Position(780, 80), Network.fromWeights(it.genes, [3, 4, 2, 2])));
for (let i = 0; i < cars.length; i++) {
carsPop.candidates[i].fitness = () => {
return cars[i].pos.distanceTo(new Position(200, 50));
return cars[i].checkPoints;
};
}
let raceTime = 0;
let t0 = Date.now();
ctx.drawImage(mapImage, 0, 0);
const map = loadMap(mapImage);
const map = loadMap(mapImage, checkPoints);
let t0 = Date.now();
while (true) {
if (raceTime > 10000) {
if (raceTime > 5000) {
console.log("evolve");
raceTime = 0;
carsPop = carsPop.createNextGeneration();
cars = carsPop.candidates.map(it => new Car(new Position(200, 50), Network.fromWeights(it.genes, [3, 4, 2, 2])));
cars = carsPop.candidates.map(it => new Car(new Position(780, 80), Network.fromWeights(it.genes, [3, 4, 2, 2])));
for (let i = 0; i < cars.length; i++) {
carsPop.candidates[i].fitness = () => {
return cars[i].pos.distanceTo(new Position(200, 50));
return cars[i].checkPoints * 1000 + cars[i].distanceToLastCheckPoint;
// return cars[i].pos.distanceTo(new Position(780, 80));
// return cars[i].pos.x + cars[i].pos.y;
};
}
}
ctx.clearRect(0, 0, c.width, c.height);
drawCheckPoints(map.checkPoints);
ctx.drawImage(mapImage, 0, 0);
const t1 = Date.now();
const dt = t1 - t0;
if (dt > 50) {
t0 = t1;
raceTime = raceTime + dt;
continue;
}
cars.map(it => {
it.update(map, dt / 1000);
it.draw(ctx, carImage);
Expand All @@ -82,7 +94,7 @@ export function race() {

function createPopulation() {
return Population.generatePopulation(
40,
100,
() => Network.perceptron([3, 4, 2, 2], Activation.SIGMOID).weights,
{
mutate,
Expand All @@ -91,16 +103,26 @@ export function race() {
);
}

function loadMap(image: HTMLImageElement) {
const map: number[][] = [];
function loadMap(image: HTMLImageElement, checkPointsJson: any) {
const collisionMap: number[][] = [];
for (let x = 0; x < image.width; x ++) {
const row = [];
for (let y = 0; y < image.height; y ++) {
row.push(ctx.getImageData(x, y, 1, 1).data[3] === 0 ? 1 : 0);
}
map.push(row);
collisionMap.push(row);
}
console.log(map.length, map[0].length);
return map;
const checkPointArray: CheckPoint[] = checkPointsJson.map((it: any) => CheckPoint.fromJson(it));
return new Map(collisionMap, checkPointArray);
}

function drawCheckPoints(checkPointArray: CheckPoint[]) {
checkPointArray.map(it => {
ctx.beginPath();
ctx.strokeStyle = "green";
ctx.moveTo(it.pos1.x, it.pos1.y);
ctx.lineTo(it.pos2.x, it.pos2.y);
ctx.stroke();
});
}
}
4 changes: 4 additions & 0 deletions src/demos/neat/racing/typings.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
declare module "*.json" {
const value: any;
export default value;
}
9 changes: 9 additions & 0 deletions src/demos/neat/racing/vector2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,13 @@ export class Vector2 {
get norm(): number {
return Math.sqrt(this.x * this.x + this.y * this.y);
}

dot(v: Vector2): number {
return this.x * v.x + this.y * v.y;
}

determinant(v: Vector2): number {
return this.y * v.x - this.x * v.y;
}

}

0 comments on commit 0682623

Please sign in to comment.