Toy-Neural-Network-JSでsteering

#概要
Toy-Neural-Network-JSでsteeringやってみた。

#写真

![image.png](https://qiita-image-store.s3.amazonaws.com/0/18104/4ac1283a-e415-4562-6318-7a5f1af63906.png)


#サンプルコード

```
let sigmoid = new ActivationFunction(x => 1 / (1 + Math.exp(-x)), y => y * (1 - y));
let tanh = new ActivationFunction(x => Math.tanh(x), y => 1 - (y * y));
function mutate(x) {
    if (random(1) < 0.1) 
    {
        let offset = randomGaussian() * 0.5;
        let newx = x + offset;
        return newx;
    }
    else
    {
        return x;
    }
}
class Sensor 
{
    constructor(angle) {
        this.dir = p5.Vector.fromAngle(angle);
        this.val = 0;
    }
}
class Vehicle 
{
    constructor(brain) {
        this.acceleration = createVector();
        this.velocity = createVector();
        this.position = createVector(random(width), random(height));
        this.r = 4;
        this.maxforce = 0.1;
        this.maxspeed = 4;
        this.minspeed = 0.25;
        this.maxhealth = 3;
        this.score = 0;
        this.sensors = [];
        for (let angle = 0; angle < TWO_PI; angle += sensorAngle) 
        {
            this.sensors.push(new Sensor(angle));
        }
        if (brain) 
        {
            this.brain = brain.copy();
            this.brain.mutate(mutate);
        }
        else
        {
            let inputs = this.sensors.length + 6;
            this.brain = new NeuralNetwork(inputs, 32, 2);
        }
        this.health = 1;
    }
    update() {
        this.velocity.add(this.acceleration);
        this.velocity.limit(this.maxspeed);
        if (this.velocity.mag() < this.minspeed)
        {
            this.velocity.setMag(this.minspeed);
        }
        this.position.add(this.velocity);
        this.acceleration.mult(0);
        this.health = constrain(this.health, 0, this.maxhealth);
        this.health -= 0.005;
        this.score += 1;
    }
    dead() {
        return (this.health < 0 || this.position.x > width + this.r || this.position.x < -this.r || this.position.y > height + this.r || this.position.y < -this.r);
    }
    clone(prob) {
        let r = random(1);
        if (r < prob) 
        {
            return new Vehicle(this.brain);
        }
    }
    think(food) {
        for (let j = 0; j < this.sensors.length; j++) 
        {
            this.sensors[j].val = sensorLength;
        }
        for (let i = 0; i < food.length; i++) 
        {
            let otherPosition = food[i];
            let dist = p5.Vector.dist(this.position, otherPosition);
            if (dist > sensorLength)
            {
                continue;
            }
            let toFood = p5.Vector.sub(otherPosition, this.position);
            for (let j = 0; j < this.sensors.length; j++)
            {
                let delta = this.sensors[j].dir.angleBetween(toFood);
                if (delta < sensorAngle / 2)
                {
                    this.sensors[j].val = min(this.sensors[j].val, dist);
                }
            }
        }
        let inputs = [];
        inputs[0] = constrain(map(this.position.x, foodBuffer, 0, 0, 1), 0, 1);
        inputs[1] = constrain(map(this.position.y, foodBuffer, 0, 0, 1), 0, 1);
        inputs[2] = constrain(map(this.position.x, width - foodBuffer, width, 0, 1), 0, 1);
        inputs[3] = constrain(map(this.position.y, height - foodBuffer, height, 0, 1), 0, 1);
        inputs[4] = this.velocity.x / this.maxspeed;
        inputs[5] = this.velocity.y / this.maxspeed;
        for (let j = 0; j < this.sensors.length; j++)
        {
            inputs[j + 6] = map(this.sensors[j].val, 0, sensorLength, 1, 0);
        }
        let outputs = this.brain.predict(inputs);
        let desired = createVector(2 * outputs[0] - 1, 2 * outputs[1] - 1);
        desired.mult(this.maxspeed);
        let steer = p5.Vector.sub(desired, this.velocity);
        steer.limit(this.maxforce);
        this.applyForce(steer);
    }
    eat(list) {
        for (let i = list.length - 1; i >= 0; i--) 
        {
            let d = p5.Vector.dist(list[i], this.position);
            if (d < foodRadius) 
            {
                list.splice(i, 1);
                this.health++;
            }
        }
    }
    applyForce(force) {
        this.acceleration.add(force);
    }
    display() {
        let green = color(0, 255, 255, 255);
        let red = color(255, 0, 100, 100);
        let col = lerpColor(red, green, this.health)
        push();
        translate(this.position.x, this.position.y);
        if (debug.checked()) 
        {
            for (let i = 0; i < this.sensors.length; i++) 
            {
                let val = this.sensors[i].val;
                if (val > 0)
                {
                    stroke(col);
                    strokeWeight(map(val, 0, sensorLength, 4, 0));
                    let position = this.sensors[i].dir;
                    line(0, 0, position.x * val, position.y * val);
                }
            }
            noStroke();
            fill(255, 200);
            text(int(this.score), 10, 0);
        }
        let theta = this.velocity.heading() + PI / 2;
        rotate(theta);
        fill(col);
        strokeWeight(1);
        stroke(col);
        beginShape();
        vertex(0, -this.r * 2);
        vertex(-this.r, this.r * 2);
        vertex(this.r, this.r * 2);
        endShape(CLOSE);
        pop();
    }
    highlight() {
        fill(255, 255, 255, 50);
        stroke(255);
        ellipse(this.position.x, this.position.y, 32, 32);
    }
}
let population = [];
let food = [];
let debug;
let speedSlider;
let speedSpan;
let foodRadius = 8;
let foodAmount = 25;
let foodBuffer = 50;
let totalSensors = 8;
let sensorLength = 150;
let sensorAngle = (Math.PI * 2) / totalSensors;
let best = null;
function setup() {
    let canvas = createCanvas(640, 360);
    canvas.parent('canvascontainer');
    debug = select('#debug');
    speedSlider = select('#speedSlider');
    speedSpan = select('#speed');
    for (let i = 0; i < 20; i++)
    {
        population[i] = new Vehicle();
    }
}
function draw() {
    background(0);
    let cycles = speedSlider.value();
    speedSpan.html(cycles);
    for (let n = 0; n < cycles; n++)
    {
        while (food.length < foodAmount) 
        {
            food.push(createVector(random(foodBuffer, width - foodBuffer), random(foodBuffer, height - foodBuffer)));
        }
        for (let v of population)
        {
            v.eat(food);
        }
        let record = -1;
        for (let i = population.length - 1; i >= 0; i--) 
        {
            let v = population[i];
            v.think(food);
            v.update(food);
            if (v.dead())
            {
                population.splice(i, 1);
            } 
            else
            {
                if (v.score > record) 
                {
                    record = v.score;
                    best = v;
                }
            }
        }
        if (population.length < 20) 
        {
            for (let v of population) 
            {
                let newVehicle = v.clone(0.1 * v.score / record);
                if (newVehicle != null) 
                {
                    population.push(newVehicle);
                }
            }
        }
    }
    for (let i = 0; i < food.length; i++)
    {
        fill(100, 255, 100, 200);
        stroke(100, 255, 100);
        ellipse(food[i].x, food[i].y, foodRadius * 2);
    }
    if (debug.checked()) 
    {
        best.highlight();
    }
    for (let v of population) 
    {
        v.display();
    }
}

```

#成果物

http://jsdo.it/ohisama1/Wfp0

以上。