|
| 1 | +class Creature { |
| 2 | + constructor(x, y, brain) { |
| 3 | + this.position = createVector(x, y); |
| 4 | + this.velocity = createVector(0, 0); |
| 5 | + this.acceleration = createVector(0, 0); |
| 6 | + this.fullSize = 12; |
| 7 | + this.r = this.fullSize; |
| 8 | + this.maxspeed = 2; |
| 9 | + this.sensors = []; |
| 10 | + this.health = 100; |
| 11 | + |
| 12 | + let totalSensors = 15; |
| 13 | + for (let i = 0; i < totalSensors; i++) { |
| 14 | + let a = map(i, 0, totalSensors, 0, TWO_PI); |
| 15 | + let v = p5.Vector.fromAngle(a); |
| 16 | + v.mult(this.fullSize * 1.5); |
| 17 | + this.sensors[i] = new Sensor(v); |
| 18 | + } |
| 19 | + |
| 20 | + if (brain) { |
| 21 | + this.brain = brain; |
| 22 | + } else { |
| 23 | + this.brain = ml5.neuralNetwork({ |
| 24 | + inputs: this.sensors.length, |
| 25 | + outputs: 2, |
| 26 | + task: "regression", |
| 27 | + neuroEvolution: true, |
| 28 | + }); |
| 29 | + } |
| 30 | + } |
| 31 | + |
| 32 | + reproduce() { |
| 33 | + let brain = this.brain.copy(); |
| 34 | + brain.mutate(0.1); |
| 35 | + return new Creature(this.position.x, this.position.y, brain); |
| 36 | + } |
| 37 | + |
| 38 | + eat() { |
| 39 | + for (let i = 0; i < food.length; i++) { |
| 40 | + let d = p5.Vector.dist(this.position, food[i].position); |
| 41 | + if (d < this.r + food[i].r) { |
| 42 | + this.health += 0.5; |
| 43 | + food[i].r -= 0.05; |
| 44 | + if (food[i].r < 20) { |
| 45 | + food[i] = new Food(); |
| 46 | + } |
| 47 | + } |
| 48 | + } |
| 49 | + } |
| 50 | + |
| 51 | + think() { |
| 52 | + for (let i = 0; i < this.sensors.length; i++) { |
| 53 | + this.sensors[i].value = 0; |
| 54 | + for (let j = 0; j < food.length; j++) { |
| 55 | + this.sensors[i].sense(this.position, food[j]); |
| 56 | + } |
| 57 | + } |
| 58 | + let inputs = []; |
| 59 | + for (let i = 0; i < this.sensors.length; i++) { |
| 60 | + inputs[i] = this.sensors[i].value; |
| 61 | + } |
| 62 | + |
| 63 | + // Predicting the force to apply |
| 64 | + const outputs = this.brain.predictSync(inputs); |
| 65 | + let angle = outputs[0].value * TWO_PI; |
| 66 | + let magnitude = outputs[1].value; |
| 67 | + let force = p5.Vector.fromAngle(angle).setMag(magnitude); |
| 68 | + this.applyForce(force); |
| 69 | + } |
| 70 | + |
| 71 | + // Method to update location |
| 72 | + update() { |
| 73 | + // Update velocity |
| 74 | + this.velocity.add(this.acceleration); |
| 75 | + // Limit speed |
| 76 | + this.velocity.limit(this.maxspeed); |
| 77 | + this.position.add(this.velocity); |
| 78 | + // Reset acceleration to 0 each cycle |
| 79 | + this.acceleration.mult(0); |
| 80 | + this.health -= 0.25; |
| 81 | + } |
| 82 | + |
| 83 | + // Wraparound |
| 84 | + borders() { |
| 85 | + if (this.position.x < -this.r) this.position.x = width + this.r; |
| 86 | + if (this.position.y < -this.r) this.position.y = height + this.r; |
| 87 | + if (this.position.x > width + this.r) this.position.x = -this.r; |
| 88 | + if (this.position.y > height + this.r) this.position.y = -this.r; |
| 89 | + } |
| 90 | + |
| 91 | + applyForce(force) { |
| 92 | + // We could add mass here if we want A = F / M |
| 93 | + this.acceleration.add(force); |
| 94 | + } |
| 95 | + |
| 96 | + show() { |
| 97 | + push(); |
| 98 | + translate(this.position.x, this.position.y); |
| 99 | + for (let sensor of this.sensors) { |
| 100 | + stroke(0, this.health * 2); |
| 101 | + line(0, 0, sensor.v.x, sensor.v.y); |
| 102 | + if (sensor.value > 0) { |
| 103 | + fill(255, sensor.value * 255); |
| 104 | + stroke(0, 100); |
| 105 | + circle(sensor.v.x, sensor.v.y, 4); |
| 106 | + } |
| 107 | + } |
| 108 | + noStroke(); |
| 109 | + fill(0, this.health * 2); |
| 110 | + this.r = map(this.health, 0, 100, 2, this.fullSize); |
| 111 | + this.r = constrain(this.r, 2, this.fullSize); |
| 112 | + circle(0, 0, this.r * 2); |
| 113 | + pop(); |
| 114 | + } |
| 115 | +} |
0 commit comments