Skip to content

Commit

Permalink
wip car race
Browse files Browse the repository at this point in the history
  • Loading branch information
Tom Hohler committed Apr 19, 2018
1 parent 9f36652 commit db5138e
Show file tree
Hide file tree
Showing 8 changed files with 144 additions and 558 deletions.
Binary file added assets/car.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/raceMap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
71 changes: 53 additions & 18 deletions src/demos/neat/racing/car.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
import { Network } from "../../../neural-network/model/network";
import { Position } from "./position";
import { Vector2 } from "./vector2";

const SENSOR_DISTANCE = 80;
export interface Position { x: number; y: number; }

export class Car {

speed: number = 0;
direction: number = Math.PI / 4;
brain?: Network;
activatedSensors: number[] = [0, 0, 0];
frozen: boolean = false;
pos: Position;

constructor(public pos: Position) {}
constructor(pos: Position, brain?: Network) {
this.pos = pos;
this.brain = brain;
}

// get direction(): number {
// if (this.velocity.norm === 0) {
Expand Down Expand Up @@ -49,26 +53,57 @@ export class Car {
const sensors = [firstSensorPos, secondSensorPos, thirdSensorPos];

for (let i = 0; i < 3; i ++) {
if (map[sensors[i].y][sensors[i].x]) {
this.activatedSensors[i] = 0;
} else {
if (sensors[i].x < 0 || sensors[i].x > map.length - 1 || sensors[i].y < 0 || sensors[i].y > map.length - 1) {
this.activatedSensors[i] = 1;
} else {
if (map[sensors[i].x][sensors[i].y]) {
this.activatedSensors[i] = 0;
} else {
this.activatedSensors[i] = 1;
}
}
}

try {
if (!map[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;
}
}

update = (map: number[][], delta: number) => {
// if (!this.brain) {
// throw Error("This car has no brain");
// }
// this.brain.activate(this.getSensorInputs);
this.checkCollisions(map);
this.speed *= 0.999;
if (this.speed < 9) {
this.speed = 0;
if (!this.brain) {
throw Error("This car has no brain");
}
this.makeDecision(this.brain);
if (!this.frozen) {
this.checkCollisions(map);
this.speed *= 0.999;
if (this.speed < 9) {
this.speed = 0;
}
this.pos.x += this.speed * Math.cos(this.direction) * delta;
this.pos.y += this.speed * Math.sin(this.direction) * delta;
}
}

makeDecision = (brain: Network) => {
const brainOutput = brain.activate(this.activatedSensors);
const directionDecision = brainOutput[0];
const speedDecision = brainOutput[1];
if (Math.abs(directionDecision) > 0.5) {
this.turn(directionDecision > 0 ? "right" : "left");
}
if (Math.abs(speedDecision) > 0.5) {
if (speedDecision > 0) {
this.accelerate();
} else {
this.brake();
}
}
this.pos.x += this.speed * Math.cos(this.direction) * delta;
this.pos.y += this.speed * Math.sin(this.direction) * delta;
}

draw = (ctx: CanvasRenderingContext2D, image: HTMLImageElement) => {
Expand All @@ -82,7 +117,7 @@ export class Car {
ctx.beginPath();
ctx.strokeStyle = this.activatedSensors[0] ? "red" : "blue";
ctx.moveTo(0, 0);
ctx.lineTo(SENSOR_DISTANCE, -SENSOR_DISTANCE);
ctx.lineTo(SENSOR_DISTANCE / Math.sqrt(2), -SENSOR_DISTANCE / Math.sqrt(2));
ctx.stroke();

ctx.beginPath();
Expand All @@ -94,7 +129,7 @@ export class Car {
ctx.beginPath();
ctx.strokeStyle = this.activatedSensors[2] ? "red" : "blue";
ctx.moveTo(0, 0);
ctx.lineTo(SENSOR_DISTANCE, SENSOR_DISTANCE);
ctx.lineTo(SENSOR_DISTANCE / Math.sqrt(2), SENSOR_DISTANCE / Math.sqrt(2));
ctx.stroke();

ctx.restore();
Expand Down
505 changes: 0 additions & 505 deletions src/demos/neat/racing/map.ts

This file was deleted.

8 changes: 8 additions & 0 deletions src/demos/neat/racing/position.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export class Position {

constructor(public x: number, public y: number) {}

distanceTo = (other: Position) => {
return Math.sqrt((other.x - this.x) * (other.x - this.x) + (other.y - this.y) * (other.y - this.y));
}
}
110 changes: 81 additions & 29 deletions src/demos/neat/racing/race.ts
Original file line number Diff line number Diff line change
@@ -1,52 +1,104 @@
import { Population } from "../../../genetic/model";
import { Network } from "../../../neural-network/model/network";
import { Car } from "./car";
import { map } from "./map";
import { Position } from "./position";
import { Vector2 } from "./vector2";

export function race() {
const c = document.getElementById("canvas") as HTMLCanvasElement;
const ctx = c.getContext("2d")!;

let mapLoaded = false;

const image = new Image();
image.src = "http://www.clker.com/cliparts/4/W/7/2/D/5/tack-car-top-view-hi.png";
const carImage = new Image();
carImage.src = "/assets/car.png";
const mapImage = new Image();
mapImage.src = "https://image.ibb.co/c8h7Z7/New_Piskel_1.png";
mapImage.onload = () => mapLoaded = true;

const car = new Car({x: 50, y: 50});
window.onkeydown = (e: KeyboardEvent) => {
switch (e.key) {
case "ArrowDown":
car.brake();
break;
case "ArrowUp":
car.accelerate();
break;
case "ArrowLeft":
car.turn("left");
break;
case "ArrowRight":
car.turn("right");
break;
mapImage.src = "/assets/raceMap.png";

const loadImage = (image: HTMLImageElement) => new Promise<HTMLImageElement>((resolve, reject) => {
image.onload = () => {
console.log("resolved", image.src);
resolve(image);
};
if (image.complete) {
resolve(image);
}
});

const mutate = (genes: number[]) => {
const genesCopy = genes.slice(0);
const randomIndex = Math.floor(Math.random() * genes.length);
genesCopy[randomIndex] = Math.random() * 2 - 1;
return genesCopy;
};
image.onload = run;

run();

async function run() {
try {
await loadImage(carImage);
await loadImage(mapImage);
} catch {
console.log("oups");
throw Error("failed to load images");
}
let carsPop = createPopulation();
let cars = carsPop.candidates.map(it => new Car(new Position(50, 50), 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(50, 50));
};
}
let raceTime = 0;
let t0 = Date.now();
ctx.drawImage(mapImage, 0, 0);
const map = loadMap(mapImage);
while (true) {
ctx.clearRect(0, 0, c.width, c.height);
if (mapLoaded) {
ctx.drawImage(mapImage, 0, 0);
if (raceTime > 5000) {
console.log("evolve");
raceTime = 0;
carsPop = carsPop.createNextGeneration();
cars = carsPop.candidates.map(it => new Car(new Position(50, 50), 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(50, 50));
};
}
}
ctx.clearRect(0, 0, c.width, c.height);
ctx.drawImage(mapImage, 0, 0);
const t1 = Date.now();
const dt = t1 - t0;
car.update(map, dt / 1000);
car.draw(ctx, image);
cars.map(it => {
it.update(map, dt / 1000);
it.draw(ctx, carImage);
});
t0 = t1;
raceTime = raceTime + dt;
const waitTime = Math.max(0, (1000 / 60) - dt);
await new Promise(resolve => setTimeout(resolve, waitTime));
}
}

function createPopulation() {
return Population.generatePopulation(
40,
() => Network.perceptron([3, 4, 2, 2]).weights,
{
mutate,
mutationProbability: 0.4
}
);
}

function loadMap(image: HTMLImageElement) {
const map: 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);
}
console.log(map.length, map[0].length);
return map;
}
}
6 changes: 1 addition & 5 deletions src/demos/neat/xor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,7 @@ export function xor() {
};

const generateRandomWeights = () => {
const weights = [];
for (let i = 0; i < WEIGHTS_SIZE; i++) {
weights.push(Math.random() * 2 - 1);
}
return weights;
return Network.perceptron([2, 3, 4, 1]).weights;
};

let pop = Population.generatePopulation(
Expand Down
2 changes: 1 addition & 1 deletion src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
</head>
<body>
<div id="main">
<canvas style="margin: 10px; border: solid 1px rgb(0, 0, 0, 0.5);" id="canvas" width="500" height="500"></canvas>
<canvas style="margin: 10px; border: solid 1px rgb(0, 0, 0, 0.5);" id="canvas" width="1012" height="750"></canvas>
<div id="score"></div>
</div>
</body>
Expand Down

0 comments on commit db5138e

Please sign in to comment.