-
Notifications
You must be signed in to change notification settings - Fork 1
/
Population.js
317 lines (269 loc) · 12.3 KB
/
Population.js
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
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
class Population {
constructor() {
this.players = []; //new ArrayList<Player>();
this.bestPlayer; //the best ever player
this.bestScore = 0; //the score of the best ever player
this.globalBestScore = 0;
this.gen = 1;
this.innovationHistory = []; // new ArrayList<connectionHistory>();
this.genPlayers = []; //new ArrayList<Player>();
this.species = []; //new ArrayList<Species>();
this.massExtinctionEvent = false;
this.newStage = false;
this.gensSinceNewWorld = 0;
this.batchNo = 0;
this.worldsPerBatch = 5;
for (var i = 0; i < numberOfWorlds; i++) {
for (var j = 0; j < playersPerWorld; j++) {
this.players.push(new Player());
this.players[this.players.length - 1].brain.fullyConnect(this.innovationHistory);
this.players[this.players.length - 1].brain.generateNetwork();
//
// this.players[this.players.length - 1].brain.mutate(this.innovationHistory);
// this.players[this.players.length - 1].brain.mutate(this.innovationHistory);
// if (random(1) < 0.5) {
// this.players[this.players.length - 1].brain.addConnection(this.innovationHistory);
// this.players[this.players.length - 1].brain.addNode(this.innovationHistory);
// }
this.players[this.players.length - 1].addToWorld();
this.players[this.players.length - 1].car.number = i;
}
}
}
//------------------------------------------------------------------------------------------------------------------------------------------
//update all the players which are alive
updateAlive() {
let aliveCount = 0;
for (var i = 0; i < this.players.length; i++) {
if (this.playerInBatch(this.players[i])) {
if (!this.players[i].dead) {
aliveCount++;
this.players[i].look(); //get inputs for brain
this.players[i].think(); //use outputs from neural network
this.players[i].update(); //move the player according to the outputs from the neural network
if (!showNothing && (!showBest || i == 0)) {
this.players[i].show();
}
if (this.players[i].score > this.globalBestScore) {
this.globalBestScore = this.players[i].score;
}
}
}
}
if (aliveCount == 0) {
this.batchNo++;
}
}
playerInBatch(player) {
for (var i = this.batchNo * this.worldsPerBatch; i < min((this.batchNo + 1) * this.worldsPerBatch, worlds.length); i++) {
if (player.world == worlds[i]) {
return true;
}
}
return false;
}
stepWorldsInBatch() {
for (var i = this.batchNo * this.worldsPerBatch; i < min((this.batchNo + 1) * this.worldsPerBatch, worlds.length); i++) {
worlds[i].Step(1 / 30, 10, 10);
}
}
//------------------------------------------------------------------------------------------------------------------------------------------
//returns true if all the players are dead sad
batchDead() {
for (var i = this.batchNo * this.playersPerBatch; i < min((this.batchNo + 1) * this.playersPerBatch, this.players.length); i++) {
if (!this.players[i].dead) {
return false;
}
}
return true;
}
//------------------------------------------------------------------------------------------------------------------------------------------
//returns true if all the players are dead sad
done() {
for (var i = 0; i < this.players.length; i++) {
if (!this.players[i].dead) {
return false;
}
}
clearWorlds();
return true;
}
//------------------------------------------------------------------------------------------------------------------------------------------
//sets the best player globally and for thisthis.gen
setBestPlayer() {
var tempBest = this.species[0].players[0];
tempBest.gen = this.gen;
//if best thisthis.gen is better than the global best score then set the global best as the best thisthis.gen
if (tempBest.score >= this.bestScore) {
this.genPlayers.push(tempBest.cloneForReplay());
console.log("old best: " + this.bestScore);
console.log("new best: " + tempBest.score);
this.bestScore = tempBest.score;
this.bestPlayer = tempBest.cloneForReplay();
}
}
//------------------------------------------------------------------------------------------------------------------------------------------------
//this function is called when all the players in the this.players are dead and a newthis.generation needs to be made
naturalSelection() {
// if (this.gen % 2 == 0 && playersPerWorld < 15) {
// playersPerWorld += 1;
// }
this.batchNo = 0;
var previousBest = this.players[0];
this.speciate(); //seperate the this.players varo this.species
this.calculateFitness(); //calculate the fitness of each player
this.sortSpecies(); //sort the this.species to be ranked in fitness order, best first
if (this.massExtinctionEvent) {
this.massExtinction();
this.massExtinctionEvent = false;
}
this.cullSpecies(); //kill off the bottom half of each this.species
this.setBestPlayer(); //save the best player of thisthis.gen
this.killStaleSpecies(); //remove this.species which haven't improved in the last 15(ish)this.generations
this.killBadSpecies(); //kill this.species which are so bad that they cant reproduce
if (this.gensSinceNewWorld >= 0 || this.bestScore > (grounds[0].distance - 350) / 10) {
this.gensSinceNewWorld = 0;
console.log(this.gensSinceNewWorld);
console.log(this.bestScore);
console.log(grounds[0].distance);
newWorlds();
}
// console.log("generation " + this.gen + " Number of mutations " + this.innovationHistory.length + " species: " + this.species.length + " <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<");
var averageSum = this.getAvgFitnessSum();
var children = []; //new ArrayList<Player>();//the nextthis.generation
// console.log("Species:");
for (var j = 0; j < this.species.length; j++) { //for each this.species
// // console.log("best unadjusted fitness:" + this.species[j].bestFitness);
// for (var i = 0; i < this.species[j].players.length; i++) {
// console.log("player " + i + " fitness: " + this.species[j].players[i].fitness + " score " + this.species[j].players[i].score + ' ');
// }
// console.log();
children.push(this.species[j].champ.clone()); //add champion without any mutation
var NoOfChildren = floor(this.species[j].averageFitness / averageSum * this.players.length) - 1; //the number of children this this.species is allowed, note -1 is because the champ is already added
for (var i = 0; i < NoOfChildren; i++) { //get the calculated amount of children from this this.species
children.push(this.species[j].giveMeBaby(this.innovationHistory));
}
}
if (children.length < this.players.length) {
children.push(previousBest.clone());
}
// while (children.length < this.players.length) { //if not enough babies (due to flooring the number of children to get a whole var)
// children.push(this.species[0].giveMeBaby(this.innovationHistory)); //get babies from the best this.species
// }
while (children.length < playersPerWorld * numberOfWorlds) { //if not enough babies (due to flooring the number of children to get a whole var)
children.push(this.species[0].giveMeBaby(this.innovationHistory)); //get babies from the best this.species
}
this.players = [];
arrayCopy(children, this.players); //set the children as the current this.playersulation
this.gen += 1;
this.gensSinceNewWorld++;
for (var i = 0; i < this.players.length; i++) { //generate networks for each of the children
this.players[i].brain.generateNetwork();
this.players[i].car.number = i;
}
console.log("LOOOK HERE THERE ARE " + this.players.length + " Players in this gen");
}
//------------------------------------------------------------------------------------------------------------------------------------------
//seperate this.players into this.species based on how similar they are to the leaders of each this.species in the previousthis.gen
speciate() {
for (var s of this.species) { //empty this.species
s.players = [];
}
for (var i = 0; i < this.players.length; i++) { //for each player
var speciesFound = false;
for (var s of this.species) { //for each this.species
if (s.sameSpecies(this.players[i].brain)) { //if the player is similar enough to be considered in the same this.species
s.addToSpecies(this.players[i]); //add it to the this.species
speciesFound = true;
break;
}
}
if (!speciesFound) { //if no this.species was similar enough then add a new this.species with this as its champion
this.species.push(new Species(this.players[i]));
}
}
}
//------------------------------------------------------------------------------------------------------------------------------------------
//calculates the fitness of all of the players
calculateFitness() {
for (var i = 1; i < this.players.length; i++) {
this.players[i].calculateFitness();
}
}
//------------------------------------------------------------------------------------------------------------------------------------------
//sorts the players within a this.species and the this.species by their fitnesses
sortSpecies() {
//sort the players within a this.species
for (var s of this.species) {
s.sortSpecies();
}
//sort the this.species by the fitness of its best player
//using selection sort like a loser
var temp = []; //new ArrayList<Species>();
for (var i = 0; i < this.species.length; i++) {
var max = 0;
var maxIndex = 0;
for (var j = 0; j < this.species.length; j++) {
if (this.species[j].bestFitness > max) {
max = this.species[j].bestFitness;
maxIndex = j;
}
}
temp.push(this.species[maxIndex]);
this.species.splice(maxIndex, 1);
// this.species.remove(maxIndex);
i--;
}
this.species = [];
arrayCopy(temp, this.species);
}
//------------------------------------------------------------------------------------------------------------------------------------------
//kills all this.species which haven't improved in 15this.generations
killStaleSpecies() {
for (var i = 2; i < this.species.length; i++) {
if (this.species[i].staleness >= 15) {
// .remove(i);
// splice(this.species, i)
this.species.splice(i, 1);
i--;
}
}
}
//------------------------------------------------------------------------------------------------------------------------------------------
//if a this.species sucks so much that it wont even be allocated 1 child for the nextthis.generation then kill it now
killBadSpecies() {
var averageSum = this.getAvgFitnessSum();
for (var i = 1; i < this.species.length; i++) {
if (this.species[i].averageFitness / averageSum * this.players.length < 1) { //if wont be given a single child
// this.species.remove(i); //sad
this.species.splice(i, 1);
i--;
}
}
}
//------------------------------------------------------------------------------------------------------------------------------------------
//returns the sum of each this.species average fitness
getAvgFitnessSum() {
var averageSum = 0;
for (var s of this.species) {
averageSum += s.averageFitness;
}
return averageSum;
}
//------------------------------------------------------------------------------------------------------------------------------------------
//kill the bottom half of each this.species
cullSpecies() {
for (var s of this.species) {
s.cull(); //kill bottom half
s.fitnessSharing(); //also while we're at it lets do fitness sharing
s.setAverage(); //reset averages because they will have changed
}
}
massExtinction() {
for (var i = 5; i < this.species.length; i++) {
// this.species.remove(i); //sad
this.species.splice(i, 1);
i--;
}
}
}