-
Notifications
You must be signed in to change notification settings - Fork 242
Expand file tree
/
Copy pathvehicle.js
More file actions
252 lines (226 loc) · 7.01 KB
/
Copy pathvehicle.js
File metadata and controls
252 lines (226 loc) · 7.01 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
// Daniel Shiffman
// Nature of Code 2018
// https://github.com/shiffman/NOC-S18
// Evolutionary "Steering Behavior" Simulation
// Neural Network parameters
// Mutation rate is set quite high because there is no crossover
const mutationRate = 0.25;
// This is a class for an individual sensor
// Each vehicle will have N sensors
class Sensor {
constructor(angle) {
// The vector describes the sensor's direction
this.dir = p5.Vector.fromAngle(angle);
// This is the sensor's reading
this.val = 0;
}
}
// This is the class for each Vehicle
class Vehicle {
// A vehicle can be from a "brain" (Neural Network)
constructor(brain) {
// All the physics stuff
this.acceleration = createVector();
this.velocity = createVector();
this.position = createVector(random(width), random(height));
this.r = 4;
this.maxforce = 0.2;
this.maxspeed = 4;
this.minspeed = 0.25;
this.maxhealth = 3;
// This indicates how well it is doing
this.score = 0;
// Create an array of sensors
this.sensors = [];
for (let angle = 0; angle < TWO_PI; angle += sensorAngle) {
this.sensors.push(new Sensor(angle));
}
// If a brain is passed via constructor copy it
if (brain) {
this.brain = brain.copy();
this.mutate(mutationRate);
// Otherwise make a new brain
} else {
// inputs are all the sensors plus position and velocity info
let inputs = this.sensors.length + 6;
// Arbitrary hidden layer
// 2 outputs for x and y desired velocity
this.brain = new NeuralNetwork(inputs, 32, 2);
}
// Health keeps vehicle alive
this.health = 1;
}
mutate(rate) {
// Check if this should be mutated at all
if (Math.random() < rate) {
// This is how we adjust weights ever so slightly
function mutate(x) {
// Mutate only so much of the values
if (Math.random() < rate) {
var offset = randomGaussian() * 0.5;
// var offset = random(-0.1, 0.1);
var newx = x + offset;
return newx;
} else {
return x;
}
}
this.brain.mutate(mutate);
}
}
// Called each time step
update() {
// Update velocity
this.velocity.add(this.acceleration);
// Limit speed to max
this.velocity.limit(this.maxspeed);
// Keep speed at a minimum
if (this.velocity.mag() < this.minspeed) {
this.velocity.setMag(this.minspeed);
}
// Update position
this.position.add(this.velocity);
// Reset acceleration to 0 each cycle
this.acceleration.mult(0);
// Decrease health
this.health = constrain(this.health, 0, this.maxhealth);
this.health -= 0.005;
// Increase score
this.score += 1;
}
// Return true if health is less than zero
// or if vehicle leaves the canvas
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
);
}
// Make a copy of this vehicle according to probability
clone(prob) {
// Pick a random number
let r = random(1);
if (r < prob) {
// New vehicle with brain copy
return new Vehicle(this.brain);
}
// otherwise will return undefined
}
// Function to calculate all sensor readings
// And predict a "desired velocity"
think(food) {
// All sensors start with maximum length
for (let j = 0; j < this.sensors.length; j++) {
this.sensors[j].val = sensorLength;
}
for (let i = 0; i < food.length; i++) {
// Where is the food
let otherPosition = food[i];
// How far away?
let dist = p5.Vector.dist(this.position, otherPosition);
// Skip if it's too far away
if (dist > sensorLength) {
continue;
}
// What is vector pointint to food
let toFood = p5.Vector.sub(otherPosition, this.position);
// Check all the sensors
for (let j = 0; j < this.sensors.length; j++) {
// If the relative angle of the food is in between the range
let delta = this.sensors[j].dir.angleBetween(toFood);
if (delta < sensorAngle / 2) {
// Sensor value is the closest food
this.sensors[j].val = min(this.sensors[j].val, dist);
}
}
}
// Create inputs
let inputs = [];
// These inputs are the location of the vehicle
inputs[0] = this.position.x / width;
inputs[1] = this.position.y / height;
// These inputs are the distance of the vehicle to east- and west borders
inputs[2] = 1 - inputs[0];
inputs[3] = 1 - inputs[1];
// These inputs are the current velocity vector
inputs[4] = this.velocity.x / this.maxspeed;
inputs[5] = this.velocity.y / this.maxspeed;
// All the sensor readings
for (let j = 0; j < this.sensors.length; j++) {
inputs[j + 6] = map(this.sensors[j].val, 0, sensorLength, 1, 0);
}
// Get two outputs
let outputs = this.brain.predict(inputs);
// Turn it into a desired velocity and apply steering formula
let desired = createVector(2 * outputs[0] - 1, 2 * outputs[1] - 1);
desired.mult(this.maxspeed);
// Craig Reynolds steering formula
let steer = p5.Vector.sub(desired, this.velocity);
steer.limit(this.maxforce);
// Apply the force
this.applyForce(steer);
}
// Check against array of food
eat(list) {
for (let i = list.length - 1; i >= 0; i--) {
// Calculate distance
let d = p5.Vector.dist(list[i], this.position);
// If vehicle is within food radius, eat it!
if (d < foodRadius) {
list.splice(i, 1);
// Add health when it eats food
this.health++;
}
}
}
// Add force to acceleration
applyForce(force) {
this.acceleration.add(force);
}
display() {
// Color based on health
let green = color(0, 255, 255, 255);
let red = color(255, 0, 100, 100);
let col = lerpColor(red, green, this.health)
push();
// Translate to vehicle position
translate(this.position.x, this.position.y);
// Draw lines for all the activated sensors
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);
}
}
// Display score next to each vehicle
noStroke();
fill(255, 200);
text(int(this.score), 10, 0);
}
// Draw a triangle rotated in the direction of velocity
let theta = this.velocity.heading() + PI / 2;
rotate(theta);
// Draw the vehicle itself
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 with a grey bubble
highlight() {
fill(255, 255, 255, 50);
stroke(255);
ellipse(this.position.x, this.position.y, 32, 32);
}
}