diff --git a/boundary.js b/boundary.js index da4342a..c30a8e6 100644 --- a/boundary.js +++ b/boundary.js @@ -5,6 +5,16 @@ // 2D Ray Casting // https://editor.p5js.org/codingtrain/sketches/Nqsq3DFv- +/** + * @tutorial https://www.npmjs.com/package/@types/p5 + * @description run npm i @types/p5 + */ + +/// +/// + +/// + class Boundary { constructor(x1, y1, x2, y2) { this.a = createVector(x1, y1); diff --git a/ga.js b/ga.js index 12d565f..2e0810e 100644 --- a/ga.js +++ b/ga.js @@ -1,9 +1,11 @@ // Daniel Shiffman // Neuro-Evolution Steering +/// + function nextGeneration() { console.log('next generation'); - calculateFitness(end); + calculateFitness(); for (let i = 0; i < TOTAL; i++) { population[i] = pickOne(); } @@ -13,6 +15,9 @@ function nextGeneration() { savedParticles = []; } +/** + * @returns {Particle} + */ function pickOne() { let index = 0; let r = random(1); @@ -28,7 +33,7 @@ function pickOne() { return child; } -function calculateFitness(target) { +function calculateFitness() { for (let particle of savedParticles) { particle.calculateFitness(); } diff --git a/index.html b/index.html index e704f28..51fee56 100644 --- a/index.html +++ b/index.html @@ -1,5 +1,6 @@ + @@ -9,6 +10,7 @@ 2D Ray Casting + @@ -16,5 +18,7 @@ + - + + \ No newline at end of file diff --git a/nn.js b/nn.js index 6852496..b04a2a5 100644 --- a/nn.js +++ b/nn.js @@ -3,6 +3,16 @@ // http://thecodingtrain.com // https://youtu.be/cdUNkwXx-I4 +/** + * @tutorial https://www.npmjs.com/package/@types/p5 + * @description run npm i @types/p5 + */ +/// +/// + +/// +/// + class NeuralNetwork { constructor(a, b, c, d) { if (a instanceof tf.Sequential) { @@ -14,10 +24,13 @@ class NeuralNetwork { this.input_nodes = a; this.hidden_nodes = b; this.output_nodes = c; + /**@type {model} */ this.model = this.createModel(); } } - + /** + * copy current weights to new model + */ copy() { return tf.tidy(() => { const modelCopy = this.createModel(); @@ -30,7 +43,10 @@ class NeuralNetwork { return new NeuralNetwork(modelCopy, this.input_nodes, this.hidden_nodes, this.output_nodes); }); } - + /** + * + * @param {number} rate + */ mutate(rate) { tf.tidy(() => { const weights = this.model.getWeights(); @@ -42,6 +58,7 @@ class NeuralNetwork { for (let j = 0; j < values.length; j++) { if (random(1) < rate) { let w = values[j]; + // @ts-ignore randomGaussian is wrongly typed in the current version values[j] = w + randomGaussian(); } } @@ -65,7 +82,9 @@ class NeuralNetwork { return outputs; }); } - + /** + * @returns {model} + */ createModel() { const model = tf.sequential(); const hidden = tf.layers.dense({ diff --git a/particle.js b/particle.js index ec8fb78..bdf1199 100644 --- a/particle.js +++ b/particle.js @@ -1,3 +1,7 @@ +/// +/// +/// + function pldistance(p1, p2, x, y) { const num = abs((p2.y - p1.y) * x - (p2.x - p1.x) * y + p2.x * p1.y - p2.y * p1.x); const den = p5.Vector.dist(p1, p2); @@ -5,6 +9,10 @@ function pldistance(p1, p2, x, y) { } class Particle { + /** + * + * @param {NeuralNetwork} [brain] + */ constructor(brain) { this.fitness = 0; this.dead = false; @@ -15,7 +23,9 @@ class Particle { this.maxspeed = 5; this.maxforce = 0.2; this.sight = SIGHT; + /**@type {Ray[]} */ this.rays = []; + /**@type number */ this.index = 0; this.counter = 0; @@ -41,6 +51,10 @@ class Particle { this.acc.add(force); } + /** + * update the position/rotation/velocity of the current particle + * also sets it to dead if it reached its lifespan + */ update() { if (!this.dead && !this.finished) { this.pos.add(this.vel); @@ -57,7 +71,10 @@ class Particle { } } } - + /** + * @description checks distance to current goal + * @param {Boundary[]} checkpoints + */ check(checkpoints) { if (!this.finished) { this.goal = checkpoints[this.index]; @@ -79,6 +96,10 @@ class Particle { // } } + /** + * @description checks all the rays and executes an action + * @param {Boundary[]} walls + */ look(walls) { const inputs = []; for (let i = 0; i < this.rays.length; i++) { @@ -125,12 +146,19 @@ class Particle { // console.log(output); } + /** + * @description checks wether the particle in inside the screen + */ bounds() { if (this.pos.x > width || this.pos.x < 0 || this.pos.y > height || this.pos.y < 0) { this.dead = true; } } + + /** + * @description display the current particle + */ show() { push(); translate(this.pos.x, this.pos.y); @@ -148,6 +176,9 @@ class Particle { // } } + /** + * @description highlights the current particle and its rays in green + */ highlight() { push(); translate(this.pos.x, this.pos.y); diff --git a/ray.js b/ray.js index 026c764..1e6e029 100644 --- a/ray.js +++ b/ray.js @@ -5,19 +5,27 @@ // 2D Ray Casting // https://editor.p5js.org/codingtrain/sketches/Nqsq3DFv- +/// + class Ray { constructor(pos, angle) { this.pos = pos; this.angle = angle; this.dir = p5.Vector.fromAngle(angle); } - + /** + * + * @param {number} x + * @param {number} y + */ lookAt(x, y) { this.dir.x = x - this.pos.x; this.dir.y = y - this.pos.y; this.dir.normalize(); } - + /** + * @param {number} offset + */ rotate(offset) { this.dir = p5.Vector.fromAngle(this.angle + offset); } @@ -29,7 +37,10 @@ class Ray { line(0, 0, this.dir.x * SIGHT, this.dir.y * SIGHT); pop(); } - + /** + * @param {Boundary} wall + * @returns {import("p5").Vector|null} position of the interception or null + */ cast(wall) { const x1 = wall.a.x; const y1 = wall.a.y; diff --git a/sketch.js b/sketch.js index 440158f..4d110cf 100644 --- a/sketch.js +++ b/sketch.js @@ -5,25 +5,53 @@ // 2D Ray Casting // https://editor.p5js.org/codingtrain/sketches/Nqsq3DFv- +/// +/// +/// +/// +/** + * @tutorial https://www.npmjs.com/package/@types/p5 + * @description run npm i @types/p5 + */ +/// +/// + const TOTAL = 100; const MUTATION_RATE = 0.1; const LIFESPAN = 25; const SIGHT = 50; -let generationCount = 0; +const TRACK_CHECKPOINT_AMOUNT = 60; +let generationCount = 0; +/** + * @type {Boundary[]} + */ let walls = []; -let ray; - +//let ray; +/** + * @type {Particle[]} + */ let population = []; +/** + * @type {Particle[]} + */ let savedParticles = []; let start, end; let speedSlider; - +/** + * @type {import("p5").Vector[]} + */ let inside = []; +/** + * @type {import("p5").Vector[]} + */ let outside = []; +/** + * @type Boundary[] + */ let checkpoints = []; // around 5-6 successfully completed rounds will make the fitness of 500+ @@ -32,52 +60,16 @@ let checkpoints = []; // when any of the particle completes multiple rounds in current map // this will help to make the current generation to work on new map // and generalize to variety of maps -const maxFitness = 500; +const maxFitness = TRACK_CHECKPOINT_AMOUNT * 5; let changeMap = false; -function buildTrack() { - checkpoints = []; - inside = []; - outside = []; - - let noiseMax = 4; - const total = 60; - const pathWidth = 60; - let startX = random(1000); - let startY = random(1000); - for (let i = 0; i < total; i++) { - let a = map(i, 0, total, 0, TWO_PI); - let xoff = map(cos(a), -1, 1, 0, noiseMax) + startX; - let yoff = map(sin(a), -1, 1, 0, noiseMax) + startY; - let xr = map(noise(xoff, yoff), 0, 1, 100, width * 0.5); - let yr = map(noise(xoff, yoff), 0, 1, 100, height * 0.5); - let x1 = width / 2 + (xr - pathWidth) * cos(a); - let y1 = height / 2 + (yr - pathWidth) * sin(a); - let x2 = width / 2 + (xr + pathWidth) * cos(a); - let y2 = height / 2 + (yr + pathWidth) * sin(a); - checkpoints.push(new Boundary(x1, y1, x2, y2)); - inside.push(createVector(x1, y1)); - outside.push(createVector(x2, y2)); - } - walls = []; - for (let i = 0; i < checkpoints.length; i++) { - let a1 = inside[i]; - let b1 = inside[(i + 1) % checkpoints.length]; - walls.push(new Boundary(a1.x, a1.y, b1.x, b1.y)); - let a2 = outside[i]; - let b2 = outside[(i + 1) % checkpoints.length]; - walls.push(new Boundary(a2.x, a2.y, b2.x, b2.y)); - } - start = checkpoints[0].midpoint(); - end = checkpoints[checkpoints.length - 1].midpoint(); -} function setup() { createCanvas(1200, 800); tf.setBackend('cpu'); - buildTrack(); + buildTrack(TRACK_CHECKPOINT_AMOUNT); // let a = inside[inside.length - 1]; // let b = outside[outside.length - 1]; // walls.push(new Boundary(a.x, a.y, b.x, b.y)); @@ -124,14 +116,14 @@ function draw() { savedParticles.push(population.splice(i, 1)[0]); } - buildTrack(); + buildTrack(TRACK_CHECKPOINT_AMOUNT); nextGeneration(); generationCount++; changeMap = false; } if (population.length == 0) { - buildTrack(); + buildTrack(TRACK_CHECKPOINT_AMOUNT); nextGeneration(); generationCount++; } diff --git a/track.js b/track.js new file mode 100644 index 0000000..0642fb7 --- /dev/null +++ b/track.js @@ -0,0 +1,42 @@ +/// +/// + +/** + * @param {number} total + */ +function buildTrack(total) { + checkpoints = []; + inside = []; + outside = []; + + let noiseMax = 4; + const pathWidth = 60; + let startX = random(1000); + let startY = random(1000); + for (let i = 0; i < total; i++) { + let a = map(i, 0, total, 0, TWO_PI); + let xoff = map(cos(a), -1, 1, 0, noiseMax) + startX; + let yoff = map(sin(a), -1, 1, 0, noiseMax) + startY; + let xr = map(noise(xoff, yoff), 0, 1, 100, width * 0.5); + let yr = map(noise(xoff, yoff), 0, 1, 100, height * 0.5); + let x1 = width / 2 + (xr - pathWidth) * cos(a); + let y1 = height / 2 + (yr - pathWidth) * sin(a); + let x2 = width / 2 + (xr + pathWidth) * cos(a); + let y2 = height / 2 + (yr + pathWidth) * sin(a); + checkpoints.push(new Boundary(x1, y1, x2, y2)); + inside.push(createVector(x1, y1)); + outside.push(createVector(x2, y2)); + } + walls = []; + for (let i = 0; i < checkpoints.length; i++) { + let a1 = inside[i]; + let b1 = inside[(i + 1) % checkpoints.length]; + walls.push(new Boundary(a1.x, a1.y, b1.x, b1.y)); + let a2 = outside[i]; + let b2 = outside[(i + 1) % checkpoints.length]; + walls.push(new Boundary(a2.x, a2.y, b2.x, b2.y)); + } + + start = checkpoints[0].midpoint(); + end = checkpoints[checkpoints.length - 1].midpoint(); +} \ No newline at end of file diff --git a/type/p5.d.ts b/type/p5.d.ts new file mode 100644 index 0000000..61e8ba6 --- /dev/null +++ b/type/p5.d.ts @@ -0,0 +1,36 @@ + + +import { Vector } from 'p5'; + +declare interface Window { + setup?: Function; + + draw: Function + + p5: p5I + mousePressed: Function + + keyPressed: Function +} +interface p5I { + Vector: typeof Vector +} +export interface p5Vector extends Vector { + +} +declare let p5: p5I + +declare global { + + let p5: p5I + interface Window { + setup?: Function; + + p5: p5I + draw: Function + mousePressed: Function + + keyPressed: Function + } +} + diff --git a/type/tensorflow.d.ts b/type/tensorflow.d.ts new file mode 100644 index 0000000..04acbf3 --- /dev/null +++ b/type/tensorflow.d.ts @@ -0,0 +1,80 @@ + +interface model { + setWeights: (weights: Array) => void + getWeights: () => Array + dispose: () => void + compile: (...args) => void + predict: (t: TensorObject) => TensorObject; + + fit: (x: TensorObject, y: TensorObject, fitOptions: fitOptions) => Promise;// returns History + add: (layer: Layer) => void +} +interface fitReport { + acc: Array, + loss: Array +} +interface fitHistory { + epoch: Array + params: any + history: fitReport +} + +interface fitOptions { + epochs?: number + batchSize?: number +} + +interface TensorObject { + toFloat: () => TensorObject + dispose: () => void + shape: Array + reshape: (shape: Array) => TensorObject + dataSync: () => Array + clone: () => TensorObject + data: () => Promise> +} +interface TensorInterface { + (arr: number | + Array | + Array> | + Array>> | + Array>>>, shape?: Array): TensorObject; + +} +interface Layer { + conv2d: (...args) => Layer + + maxPooling2d: (...args) => Layer + + dense: (...args) => Layer + + flatten: (...args) => Layer +} + +interface Training { + sgd: (lr: number) => Training +} +interface Browser { + fromPixels: (canvas: HTMLCanvasElement) => TensorObject +} +interface tf { + layers: Layer + browser: Browser + setBackend: (be: "cpu") => void + train: Training, + tensor1d: TensorInterface + tensor: TensorInterface + tensor2d: TensorInterface + loadLayersModel: (any) => Promise + tidy: (fn: () => T) => T + sequential: () => model + + Sequential: class +} + +declare let tf: tf; + + +declare interface Window { + NeuralWrapper: any +} \ No newline at end of file