├── README.md └── asteroidsGameNeat ├── Asteroid.pde ├── Bullet.pde ├── Genome.pde ├── Node.pde ├── Player.pde ├── Population.pde ├── Species.pde ├── asteroidsGameNeat.pde ├── connectionGene.pde ├── connectionHistory.pde └── data └── AgencyFB-Reg-48.vlw /README.md: -------------------------------------------------------------------------------- 1 | # Asteroids-with-NEAT 2 | my implementation of NEAT which I tested on asteroids 3 | -------------------------------------------------------------------------------- /asteroidsGameNeat/Asteroid.pde: -------------------------------------------------------------------------------- 1 | class Asteroid { 2 | PVector pos; 3 | PVector vel; 4 | int size = 3;//3 = large 2 = medium and 1 = small 5 | float radius; 6 | ArrayList chunks = new ArrayList();//each asteroid contains 2 smaller asteroids which are released when shot 7 | boolean split = false;//whether the asteroid has been hit and split into to 2 8 | int sizeHit; 9 | //------------------------------------------------------------------------------------------------------------------------------------------ 10 | //constructor 11 | Asteroid(float posX, float posY, float velX, float velY, int sizeNo) { 12 | pos = new PVector(posX, posY); 13 | size = sizeNo; 14 | vel = new PVector(velX, velY); 15 | 16 | switch(sizeNo) {//set the velocity and radius depending on size 17 | case 1: 18 | radius = 15; 19 | vel.normalize(); 20 | vel.mult(1.25); 21 | break; 22 | case 2: 23 | radius = 30; 24 | vel.normalize(); 25 | vel.mult(1); 26 | break; 27 | case 3: 28 | radius = 60; 29 | vel.normalize(); 30 | vel.mult(0.75); 31 | break; 32 | } 33 | } 34 | 35 | //------------------------------------------------------------------------------------------------------------------------------------------ 36 | //draw the asteroid 37 | void show() { 38 | if (split) {//if split show the 2 chunks 39 | for (Asteroid a : chunks) { 40 | a.show(); 41 | } 42 | } else {// if still whole 43 | noFill(); 44 | stroke(255); 45 | polygon(pos.x, pos.y, radius, 12);//draw the dodecahedrons 46 | } 47 | } 48 | //-------------------------------------------------------------------------------------------------------------------------- 49 | //draws a polygon 50 | //not gonna lie, I copied this from https://processing.org/examples/regularpolygon.html 51 | void polygon(float x, float y, float radius, int npoints) { 52 | float angle = TWO_PI / npoints;//set the angle between vertexes 53 | beginShape(); 54 | for (float a = 0; a < TWO_PI; a += angle) {//draw each vertex of the polygon 55 | float sx = x + cos(a) * radius;//math 56 | float sy = y + sin(a) * radius;//math 57 | vertex(sx, sy); 58 | } 59 | endShape(CLOSE); 60 | } 61 | //------------------------------------------------------------------------------------------------------------------------------------------ 62 | //adds the velocity to the position 63 | void move() { 64 | if (split) {//if split move the chunks 65 | for (Asteroid a : chunks) { 66 | a.move(); 67 | } 68 | } else {//if not split 69 | pos.add(vel);//move it 70 | if (isOut(pos)) {//if out of the playing area wrap (loop) it to the other side 71 | loopy(); 72 | } 73 | } 74 | } 75 | //------------------------------------------------------------------------------------------------------------------------------------------ 76 | //if out moves it to the other side of the screen 77 | void loopy() { 78 | if (pos.y < -50) { 79 | pos.y = height + 50; 80 | } else 81 | if (pos.y > height + 50) { 82 | pos.y = -50; 83 | } 84 | if (pos.x< -50) { 85 | pos.x = width +50; 86 | } else if (pos.x > width + 50) { 87 | pos.x = -50; 88 | } 89 | } 90 | //------------------------------------------------------------------------------------------------------------------------------------------ 91 | //checks if a bullet hit the asteroid 92 | boolean checkIfHit(PVector bulletPos) { 93 | if (split) {//if split check if the bullet hit one of the chunks 94 | for (Asteroid a : chunks) { 95 | if (a.checkIfHit(bulletPos)) { 96 | return true; 97 | } 98 | } 99 | return false; 100 | } else { 101 | if (dist(pos.x, pos.y, bulletPos.x, bulletPos.y)< radius) {//if it did hit 102 | isHit();//boom 103 | return true; 104 | } 105 | if (pos.x< -50 +radius || pos.x > width+50 - radius || pos.y< -50 + radius || pos.y > height+50 -radius ) {//if ateroid is overlapping edge 106 | if (pos.x< -50 +radius || pos.x > width+50 - radius || pos.y< -50 + radius || pos.y > height+50 -radius ) {//if bullet is near the edge 107 | PVector overlapPos = new PVector(pos.x, pos.y); 108 | if (pos.x< -50 +radius) { 109 | overlapPos.x += width+100; 110 | } 111 | if ( pos.x > width+50 - radius ) { 112 | overlapPos.x -= width+100; 113 | } 114 | 115 | if ( pos.y< -50 + radius) { 116 | overlapPos.y +=height + 100; 117 | } 118 | 119 | if (pos.y > height+50 -radius) { 120 | 121 | overlapPos.y -= height + 100; 122 | } 123 | if (dist(overlapPos.x, overlapPos.y, bulletPos.x, bulletPos.y)< radius) { 124 | isHit();//boom 125 | return true; 126 | } 127 | } 128 | } 129 | return false; 130 | } 131 | } 132 | //------------------------------------------------------------------------------------------------------------------------------------------ 133 | //probs could have made these 3 functions into 1 but whatever 134 | //this one checks if the player hit the asteroid 135 | boolean checkIfHitPlayer(PVector playerPos) { 136 | if (split) {//if split check if the player hit one of the chunks 137 | for (Asteroid a : chunks) { 138 | if (a.checkIfHitPlayer(playerPos)) { 139 | return true; 140 | } 141 | } 142 | return false; 143 | } else { 144 | if (dist(pos.x, pos.y, playerPos.x, playerPos.y)< radius + 15) {//if hit player 145 | isHit();//boom 146 | 147 | return true; 148 | } 149 | 150 | if (pos.x< -50 +radius || pos.x > width+50 - radius || pos.y< -50 + radius || pos.y > height+50 -radius ) {//if ateroid is overlapping edge 151 | if (pos.x< -50 +radius || pos.x > width+50 - radius || pos.y< -50 + radius || pos.y > height+50 -radius ) {//if bullet is near the edge 152 | PVector overlapPos = new PVector(pos.x, pos.y); 153 | if (pos.x< -50 +radius) { 154 | overlapPos.x += width+100; 155 | } 156 | if ( pos.x > width+50 - radius ) { 157 | overlapPos.x -= width+100; 158 | } 159 | 160 | if ( pos.y< -50 + radius) { 161 | overlapPos.y +=height + 100; 162 | } 163 | 164 | if (pos.y > height+50 -radius) { 165 | 166 | overlapPos.y -= height + 100; 167 | } 168 | if (dist(overlapPos.x, overlapPos.y, playerPos.x, playerPos.y)< radius) { 169 | isHit();//boom 170 | return true; 171 | } 172 | } 173 | } 174 | return false; 175 | } 176 | } 177 | 178 | 179 | 180 | 181 | //------------------------------------------------------------------------------------------------------------------------------------------ 182 | //same as checkIfHit but it doesnt destroy the asteroid used by the look function 183 | boolean lookForHit(PVector bulletPos) { 184 | if (split) { 185 | for (Asteroid a : chunks) { 186 | if (a.lookForHit(bulletPos)) { 187 | return true; 188 | } 189 | } 190 | return false; 191 | } else { 192 | if (dist(pos.x, pos.y, bulletPos.x, bulletPos.y)< radius) { 193 | sizeHit = size; 194 | 195 | return true; 196 | } 197 | if (pos.x< -50 +radius || pos.x > width+50 - radius || pos.y< -50 + radius || pos.y > height+50 -radius ) {//if ateroid is overlapping edge 198 | if (pos.x< -50 +radius || pos.x > width+50 - radius || pos.y< -50 + radius || pos.y > height+50 -radius ) {//if bullet is near the edge 199 | PVector overlapPos = new PVector(pos.x, pos.y); 200 | if (pos.x< -50 +radius) { 201 | overlapPos.x += width+100; 202 | } 203 | if ( pos.x > width+50 - radius ) { 204 | overlapPos.x -= width+100; 205 | } 206 | 207 | if ( pos.y< -50 + radius) { 208 | overlapPos.y +=height + 100; 209 | } 210 | 211 | if (pos.y > height+50 -radius) { 212 | 213 | overlapPos.y -= height + 100; 214 | } 215 | if (dist(overlapPos.x, overlapPos.y, bulletPos.x, bulletPos.y)< radius) { 216 | return true; 217 | } 218 | } 219 | } 220 | return false; 221 | } 222 | } 223 | //------------------------------------------------------------------------------------------------------------------------------------------ 224 | 225 | //destroys/splits asteroid 226 | void isHit() { 227 | split = true; 228 | if (size == 1) {//can't split the smallest asteroids 229 | return; 230 | } else { 231 | //add 2 smaller asteroids to the chunks array with slightly different velocities 232 | PVector velocity = new PVector(vel.x, vel.y); 233 | velocity.rotate(-0.3); 234 | chunks.add(new Asteroid(pos.x, pos.y, velocity.x, velocity.y, size-1)); 235 | velocity.rotate(0.5); 236 | chunks.add(new Asteroid(pos.x, pos.y, velocity.x, velocity.y, size-1)); 237 | } 238 | } 239 | 240 | Asteroid getAsteroid(PVector bulletPos) { 241 | 242 | if (split) { 243 | for (Asteroid a : chunks) { 244 | if (a.getAsteroid(bulletPos)!= null) { 245 | return a.getAsteroid(bulletPos); 246 | } 247 | } 248 | return null; 249 | } else { 250 | 251 | if (dist(pos.x, pos.y, bulletPos.x, bulletPos.y)< radius) { 252 | return this; 253 | } 254 | if (pos.x< -50 +radius || pos.x > width+50 - radius || pos.y< -50 + radius || pos.y > height+50 -radius ) {//if ateroid is overlapping edge 255 | if (pos.x< -50 +radius || pos.x > width+50 - radius || pos.y< -50 + radius || pos.y > height+50 -radius ) {//if bullet is near the edge 256 | PVector overlapPos = new PVector(pos.x, pos.y); 257 | if (pos.x< -50 +radius) { 258 | overlapPos.x += width+100; 259 | } 260 | if ( pos.x > width+50 - radius ) { 261 | overlapPos.x -= width+100; 262 | } 263 | 264 | if ( pos.y< -50 + radius) { 265 | overlapPos.y +=height + 100; 266 | } 267 | 268 | if (pos.y > height+50 -radius) { 269 | 270 | overlapPos.y -= height + 100; 271 | } 272 | if (dist(overlapPos.x, overlapPos.y, bulletPos.x, bulletPos.y)< radius) { 273 | return this; 274 | } 275 | } 276 | } 277 | return null; 278 | } 279 | } 280 | } -------------------------------------------------------------------------------- /asteroidsGameNeat/Bullet.pde: -------------------------------------------------------------------------------- 1 | class Bullet { 2 | PVector pos; 3 | PVector vel; 4 | float speed = 10; 5 | boolean off = false; 6 | int lifespan = 60; 7 | //------------------------------------------------------------------------------------------------------------------------------------------ 8 | 9 | Bullet(float x, float y, float r, float playerSpeed) { 10 | 11 | pos = new PVector(x, y); 12 | vel = PVector.fromAngle(r); 13 | vel.mult(speed + playerSpeed);//bullet speed = 10 + the speed of the player 14 | } 15 | 16 | //------------------------------------------------------------------------------------------------------------------------------------------ 17 | //move the bullet 18 | void move() { 19 | lifespan --; 20 | if (lifespan<0) {//if lifespan is up then destroy the bullet 21 | off = true; 22 | } else { 23 | pos.add(vel); 24 | if (isOut(pos)) {//wrap bullet 25 | loopy(); 26 | } 27 | } 28 | } 29 | 30 | //------------------------------------------------------------------------------------------------------------------------------------------ 31 | //show a dot representing the bullet 32 | void show() { 33 | if (!off) { 34 | fill(255); 35 | ellipse(pos.x, pos.y, 3, 3); 36 | } 37 | } 38 | 39 | //------------------------------------------------------------------------------------------------------------------------------------------ 40 | //if out moves it to the other side of the screen 41 | 42 | void loopy() { 43 | if (pos.y < -50) { 44 | pos.y = height + 50; 45 | } else 46 | if (pos.y > height + 50) { 47 | pos.y = -50; 48 | } 49 | if (pos.x< -50) { 50 | pos.x = width +50; 51 | } else if (pos.x > width + 50) { 52 | pos.x = -50; 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /asteroidsGameNeat/Genome.pde: -------------------------------------------------------------------------------- 1 | class Genome { 2 | ArrayList genes = new ArrayList();//a list of connections between nodes which represent the NN 3 | ArrayList nodes = new ArrayList();//list of nodes 4 | int inputs; 5 | int outputs; 6 | int layers =2; 7 | int nextNode = 0; 8 | int biasNode; 9 | ArrayList network = new ArrayList();//a list of the nodes in the order that they need to be considered in the NN 10 | //--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 11 | Genome(int in, int out) { 12 | int localNextConnectionNumber = 0; 13 | //set input number and output number 14 | inputs = in; 15 | outputs = out; 16 | 17 | //create input nodes 18 | for (int i = 0; i(); 113 | //for each layer add the node in that layer, since layers cannot connect to themselves there is no need to order the nodes within a layer 114 | 115 | for (int l = 0; l< layers; l++) {//for each layer 116 | for (int i = 0; i< nodes.size(); i++) {//for each node 117 | if (nodes.get(i).layer == l) {//if that node is in that layer 118 | network.add(nodes.get(i)); 119 | } 120 | } 121 | } 122 | } 123 | //----------------------------------------------------------------------------------------------------------------------------------------- 124 | //mutate the NN by adding a new node 125 | //it does this by picking a random connection and disabling it then 2 new connections are added 126 | //1 between the input node of the disabled connection and the new node 127 | //and the other between the new node and the output of the disabled connection 128 | void addNode(ArrayList innovationHistory) { 129 | //pick a random connection to create a node between 130 | int randomConnection = floor(random(genes.size())); 131 | 132 | while (genes.get(randomConnection).fromNode == nodes.get(biasNode)) {//dont disconnect bias 133 | randomConnection = floor(random(genes.size())); 134 | } 135 | 136 | genes.get(randomConnection).enabled = false;//disable it 137 | 138 | int newNodeNo = nextNode; 139 | nodes.add(new Node(newNodeNo)); 140 | nextNode ++; 141 | //add a new connection to the new node with a weight of 1 142 | int connectionInnovationNumber = getInnovationNumber(innovationHistory, genes.get(randomConnection).fromNode, getNode(newNodeNo)); 143 | genes.add(new connectionGene(genes.get(randomConnection).fromNode, getNode(newNodeNo), 1, connectionInnovationNumber)); 144 | 145 | 146 | connectionInnovationNumber = getInnovationNumber(innovationHistory, getNode(newNodeNo), genes.get(randomConnection).toNode); 147 | //add a new connection from the new node with a weight the same as the disabled connection 148 | genes.add(new connectionGene(getNode(newNodeNo), genes.get(randomConnection).toNode, genes.get(randomConnection).weight, connectionInnovationNumber)); 149 | getNode(newNodeNo).layer = genes.get(randomConnection).fromNode.layer +1; 150 | 151 | 152 | connectionInnovationNumber = getInnovationNumber(innovationHistory, nodes.get(biasNode), getNode(newNodeNo)); 153 | //connect the bias to the new node with a weight of 0 154 | genes.add(new connectionGene(nodes.get(biasNode), getNode(newNodeNo), 0, connectionInnovationNumber)); 155 | 156 | //if the layer of the new node is equal to the layer of the output node of the old connection then a new layer needs to be created 157 | //more accurately the layer numbers of all layers equal to or greater than this new node need to be incrimented 158 | if (getNode(newNodeNo).layer == genes.get(randomConnection).toNode.layer) { 159 | for (int i = 0; i< nodes.size() -1; i++) {//dont include this newest node 160 | if (nodes.get(i).layer >= getNode(newNodeNo).layer) { 161 | nodes.get(i).layer ++; 162 | } 163 | } 164 | layers ++; 165 | } 166 | connectNodes(); 167 | } 168 | 169 | //------------------------------------------------------------------------------------------------------------------ 170 | //adds a connection between 2 nodes which aren't currently connected 171 | void addConnection(ArrayList innovationHistory) { 172 | //cannot add a connection to a fully connected network 173 | if (fullyConnected()) { 174 | println("connection failed"); 175 | return; 176 | } 177 | 178 | 179 | //get random nodes 180 | int randomNode1 = floor(random(nodes.size())); 181 | int randomNode2 = floor(random(nodes.size())); 182 | while (nodes.get(randomNode1).layer == nodes.get(randomNode2).layer 183 | || nodes.get(randomNode1).isConnectedTo(nodes.get(randomNode2))) { //while the random nodes are no good 184 | //get new ones 185 | randomNode1 = floor(random(nodes.size())); 186 | randomNode2 = floor(random(nodes.size())); 187 | } 188 | int temp; 189 | if (nodes.get(randomNode1).layer > nodes.get(randomNode2).layer) {//if the first random node is after the second then switch 190 | temp =randomNode2 ; 191 | randomNode2 = randomNode1; 192 | randomNode1 = temp; 193 | } 194 | 195 | //get the innovation number of the connection 196 | //this will be a new number if no identical genome has mutated in the same way 197 | int connectionInnovationNumber = getInnovationNumber(innovationHistory, nodes.get(randomNode1), nodes.get(randomNode2)); 198 | //add the connection with a random array 199 | 200 | genes.add(new connectionGene(nodes.get(randomNode1), nodes.get(randomNode2), random(-1, 1), connectionInnovationNumber));//changed this so if error here 201 | connectNodes(); 202 | } 203 | 204 | //------------------------------------------------------------------------------------------------------------------------------------------- 205 | //returns the innovation number for the new mutation 206 | //if this mutation has never been seen before then it will be given a new unique innovation number 207 | //if this mutation matches a previous mutation then it will be given the same innovation number as the previous one 208 | int getInnovationNumber(ArrayList innovationHistory, Node from, Node to) { 209 | boolean isNew = true; 210 | int connectionInnovationNumber = nextConnectionNo; 211 | for (int i = 0; i < innovationHistory.size(); i++) {//for each previous mutation 212 | if (innovationHistory.get(i).matches(this, from, to)) {//if match found 213 | isNew = false;//its not a new mutation 214 | connectionInnovationNumber = innovationHistory.get(i).innovationNumber; //set the innovation number as the innovation number of the match 215 | break; 216 | } 217 | } 218 | 219 | if (isNew) {//if the mutation is new then create an arrayList of integers representing the current state of the genome 220 | ArrayList innoNumbers = new ArrayList(); 221 | for (int i = 0; i< genes.size(); i++) {//set the innovation numbers 222 | innoNumbers.add(genes.get(i).innovationNo); 223 | } 224 | 225 | //then add this mutation to the innovationHistory 226 | innovationHistory.add(new connectionHistory(from.number, to.number, connectionInnovationNumber, innoNumbers)); 227 | nextConnectionNo++; 228 | } 229 | return connectionInnovationNumber; 230 | } 231 | //---------------------------------------------------------------------------------------------------------------------------------------- 232 | 233 | //returns whether the network is fully connected or not 234 | boolean fullyConnected() { 235 | int maxConnections = 0; 236 | int[] nodesInLayers = new int[layers];//array which stored the amount of nodes in each layer 237 | 238 | //populate array 239 | for (int i =0; i< nodes.size(); i++) { 240 | nodesInLayers[nodes.get(i).layer] +=1; 241 | } 242 | 243 | //for each layer the maximum amount of connections is the number in this layer * the number of nodes infront of it 244 | //so lets add the max for each layer together and then we will get the maximum amount of connections in the network 245 | for (int i = 0; i < layers-1; i++) { 246 | int nodesInFront = 0; 247 | for (int j = i+1; j < layers; j++) {//for each layer infront of this layer 248 | nodesInFront += nodesInLayers[j];//add up nodes 249 | } 250 | 251 | maxConnections += nodesInLayers[i] * nodesInFront; 252 | } 253 | 254 | if (maxConnections == genes.size()) {//if the number of connections is equal to the max number of connections possible then it is full 255 | return true; 256 | } 257 | return false; 258 | } 259 | 260 | 261 | //------------------------------------------------------------------------------------------------------------------------------- 262 | //mutates the genome 263 | void mutate(ArrayList innovationHistory) { 264 | float rand1 = random(1); 265 | if (rand1<0.8) { // 80% of the time mutate weights 266 | for (int i = 0; i< genes.size(); i++) { 267 | genes.get(i).mutateWeight(); 268 | } 269 | } 270 | //5% of the time add a new connection 271 | float rand2 = random(1); 272 | if (rand2<0.05) { 273 | addConnection(innovationHistory); 274 | } 275 | 276 | 277 | //3% of the time add a node 278 | float rand3 = random(1); 279 | if (rand3<0.03) { 280 | addNode(innovationHistory); 281 | } 282 | } 283 | 284 | //--------------------------------------------------------------------------------------------------------------------------------- 285 | //called when this Genome is better that the other parent 286 | Genome crossover(Genome parent2) { 287 | Genome child = new Genome(inputs, outputs, true); 288 | child.genes.clear(); 289 | child.nodes.clear(); 290 | child.layers = layers; 291 | child.nextNode = nextNode; 292 | child.biasNode = biasNode; 293 | ArrayList childGenes = new ArrayList();//list of genes to be inherrited form the parents 294 | ArrayList isEnabled = new ArrayList(); 295 | //all inherrited genes 296 | for (int i = 0; i< genes.size(); i++) { 297 | boolean setEnabled = true;//is this node in the chlid going to be enabled 298 | 299 | int parent2gene = matchingGene(parent2, genes.get(i).innovationNo); 300 | if (parent2gene != -1) {//if the genes match 301 | if (!genes.get(i).enabled || !parent2.genes.get(parent2gene).enabled) {//if either of the matching genes are disabled 302 | 303 | if (random(1) < 0.75) {//75% of the time disabel the childs gene 304 | setEnabled = false; 305 | } 306 | } 307 | float rand = random(1); 308 | if (rand<0.5) { 309 | childGenes.add(genes.get(i)); 310 | 311 | //get gene from this fucker 312 | } else { 313 | //get gene from parent2 314 | childGenes.add(parent2.genes.get(parent2gene)); 315 | } 316 | } else {//disjoint or excess gene 317 | childGenes.add(genes.get(i)); 318 | setEnabled = genes.get(i).enabled; 319 | } 320 | isEnabled.add(setEnabled); 321 | } 322 | 323 | 324 | //since all excess and disjoint genes are inherrited from the more fit parent (this Genome) the childs structure is no different from this parent | with exception of dormant connections being enabled but this wont effect nodes 325 | //so all the nodes can be inherrited from this parent 326 | for (int i = 0; i < nodes.size(); i++) { 327 | child.nodes.add(nodes.get(i).clone()); 328 | } 329 | 330 | //clone all the connections so that they connect the childs new nodes 331 | 332 | for ( int i =0; i> allNodes = new ArrayList>(); 404 | ArrayList nodePoses = new ArrayList(); 405 | ArrayList nodeNumbers= new ArrayList(); 406 | 407 | //get the positions on the screen that each node is supposed to be in 408 | for (int i = 0; i< layers; i++) { 409 | ArrayList temp = new ArrayList(); 410 | for (int j = 0; j< nodes.size(); j++) {//for each node 411 | if (nodes.get(j).layer == i ) {//check if it is in this layer 412 | temp.add(nodes.get(j)); //add it to this layer 413 | } 414 | } 415 | allNodes.add(temp);//add this layer to all nodes 416 | } 417 | 418 | for (int i = 0; i < layers; i++) { 419 | fill(255, 0, 0); 420 | float x = (float)((i+1)*width)/(float)(layers+1.0); 421 | for (int j = 0; j< allNodes.get(i).size(); j++) { 422 | float y = ((float)(j + 1.0) * height)/(float)(allNodes.get(i).size() + 1.0); 423 | nodePoses.add(new PVector(x, y)); 424 | nodeNumbers.add(allNodes.get(i).get(j).number); 425 | } 426 | } 427 | 428 | //draw connections 429 | stroke(0); 430 | strokeWeight(2); 431 | for (int i = 0; i< genes.size(); i++) { 432 | if (genes.get(i).enabled) { 433 | stroke(0); 434 | } else { 435 | stroke(100); 436 | } 437 | PVector from; 438 | PVector to; 439 | from = nodePoses.get(nodeNumbers.indexOf(genes.get(i).fromNode.number)); 440 | to = nodePoses.get(nodeNumbers.indexOf(genes.get(i).toNode.number)); 441 | if (genes.get(i).weight > 0) { 442 | stroke(255, 0, 0); 443 | } else { 444 | stroke(0, 0, 255); 445 | } 446 | strokeWeight(map(abs(genes.get(i).weight), 0, 1, 0, 5)); 447 | line(from.x, from.y, to.x, to.y); 448 | } 449 | 450 | //draw nodes last so they appear ontop of the connection lines 451 | for (int i = 0; i < nodePoses.size(); i++) { 452 | fill(255); 453 | stroke(0); 454 | strokeWeight(1); 455 | ellipse(nodePoses.get(i).x, nodePoses.get(i).y, 20, 20); 456 | textSize(10); 457 | fill(0); 458 | textAlign(CENTER, CENTER); 459 | 460 | 461 | text(nodeNumbers.get(i), nodePoses.get(i).x, nodePoses.get(i).y); 462 | } 463 | } 464 | } -------------------------------------------------------------------------------- /asteroidsGameNeat/Node.pde: -------------------------------------------------------------------------------- 1 | class Node { 2 | int number; 3 | float inputSum = 0;//current sum i.e. before activation 4 | float outputValue = 0; //after activation function is applied 5 | ArrayList outputConnections = new ArrayList(); 6 | int layer = 0; 7 | PVector drawPos = new PVector(); 8 | //--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 9 | //constructor 10 | Node(int no) { 11 | number = no; 12 | } 13 | 14 | //--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 15 | //the node sends its output to the inputs of the nodes its connected to 16 | void engage() { 17 | if (layer!=0) {//no sigmoid for the inputs and bias 18 | outputValue = sigmoid(inputSum); 19 | } 20 | 21 | for (int i = 0; i< outputConnections.size(); i++) {//for each connection 22 | if (outputConnections.get(i).enabled) {//dont do shit if not enabled 23 | outputConnections.get(i).toNode.inputSum += outputConnections.get(i).weight * outputValue;//add the weighted output to the sum of the inputs of whatever node this node is connected to 24 | } 25 | } 26 | } 27 | //---------------------------------------------------------------------------------------------------------------------------------------- 28 | //not used 29 | float stepFunction(float x) { 30 | if (x < 0) { 31 | return 0; 32 | } else { 33 | return 1; 34 | } 35 | } 36 | //--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 37 | //sigmoid activation function 38 | float sigmoid(float x) { 39 | float y = 1 / (1 + pow((float)Math.E, -4.9*x)); 40 | return y; 41 | } 42 | //---------------------------------------------------------------------------------------------------------------------------------------------------------- 43 | //returns whether this node connected to the parameter node 44 | //used when adding a new connection 45 | boolean isConnectedTo(Node node) { 46 | if (node.layer == layer) {//nodes in the same layer cannot be connected 47 | return false; 48 | } 49 | 50 | //you get it 51 | if (node.layer < layer) { 52 | for (int i = 0; i < node.outputConnections.size(); i++) { 53 | if (node.outputConnections.get(i).toNode == this) { 54 | return true; 55 | } 56 | } 57 | } else { 58 | for (int i = 0; i < outputConnections.size(); i++) { 59 | if (outputConnections.get(i).toNode == node) { 60 | return true; 61 | } 62 | } 63 | } 64 | 65 | return false; 66 | } 67 | //--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 68 | //returns a copy of this node 69 | Node clone() { 70 | Node clone = new Node(number); 71 | clone.layer = layer; 72 | return clone; 73 | } 74 | } -------------------------------------------------------------------------------- /asteroidsGameNeat/Player.pde: -------------------------------------------------------------------------------- 1 | class Player { 2 | PVector pos; 3 | PVector vel; 4 | PVector acc; 5 | 6 | int score = 0;//how many asteroids have been shot 7 | int shootCount = 0;//stops the player from shooting too quickly 8 | float rotation;//the ships current rotation 9 | float spin;//the amount the ship is to spin next update 10 | float maxSpeed = 10;//limit the players speed at 10 11 | boolean boosting = false;//whether the booster is on or not 12 | ArrayList bullets = new ArrayList(); //the bullets currently on screen 13 | ArrayList asteroids = new ArrayList(); // all the asteroids 14 | int asteroidCount = 1000;//the time until the next asteroid spawns 15 | int lives = 0;//no lives 16 | boolean dead = false;//is it dead 17 | int immortalCount = 0; //when the player looses a life and respawns it is immortal for a small amount of time 18 | int boostCount = 10;//makes the booster flash 19 | //--------AI stuff 20 | Genome brain; 21 | float[] vision = new float[8];//the input array fed into the neuralNet 22 | float[] decision = new float[4]; //the out put of the NN 23 | boolean replay = false;//whether the player is being raplayed 24 | //since asteroids are spawned randomly when replaying the player we need to use a random seed to repeat the same randomness 25 | long SeedUsed; //the random seed used to intiate the asteroids 26 | ArrayList seedsUsed = new ArrayList();//seeds used for all the spawned asteroids 27 | int upToSeedNo = 0;//which position in the arrayList 28 | float fitness; 29 | float unadjustedFitness; 30 | 31 | int shotsFired =4;//initiated at 4 to encourage shooting 32 | int shotsHit = 1; //initiated at 1 so players dont get a fitness of 1 33 | 34 | int lifespan = 0;//how long the player lived for fitness 35 | 36 | boolean canShoot = true;//whether the player can shoot or not 37 | 38 | int bestScore =0;//stores the score achieved used for replay 39 | //------------------------------------------------------------------------------------------------------------------------------------------ 40 | //constructor 41 | Player() { 42 | pos = new PVector(width/2, height/2); 43 | vel = new PVector(); 44 | acc = new PVector(); 45 | rotation = 0; 46 | SeedUsed = floor(random(1000000000));//create and store a seed 47 | randomSeed(SeedUsed); 48 | 49 | //generate asteroids 50 | asteroids.add(new Asteroid(random(width), 0, random(-1, 1), random (-1, 1), 3)); 51 | asteroids.add(new Asteroid(random(width), 0, random(-1, 1), random (-1, 1), 3)); 52 | asteroids.add(new Asteroid(0, random(height), random(-1, 1), random (-1, 1), 3)); 53 | asteroids.add(new Asteroid(random(width), random(height), random(-1, 1), random (-1, 1), 3)); 54 | //aim the fifth one at the player 55 | float randX = random(width); 56 | float randY = -50 +floor(random(2))* (height+100); 57 | asteroids.add(new Asteroid(randX, randY, pos.x- randX, pos.y - randY, 3)); 58 | brain = new Genome(33, 4); 59 | } 60 | //------------------------------------------------------------------------------------------------------------------------------------------ 61 | //constructor used for replaying players 62 | Player(long seed) { 63 | replay = true;//is replaying 64 | pos = new PVector(width/2, height/2); 65 | vel = new PVector(); 66 | acc = new PVector(); 67 | rotation = 0; 68 | SeedUsed = seed;//use the parameter seed to set the asteroids at the same position as the last one 69 | randomSeed(SeedUsed); 70 | //generate asteroids 71 | asteroids.add(new Asteroid(random(width), 0, random(-1, 1), random (-1, 1), 3)); 72 | asteroids.add(new Asteroid(random(width), 0, random(-1, 1), random (-1, 1), 3)); 73 | asteroids.add(new Asteroid(0, random(height), random(-1, 1), random (-1, 1), 3)); 74 | asteroids.add(new Asteroid(random(width), random(height), random(-1, 1), random (-1, 1), 3)); 75 | //aim the fifth one at the player 76 | float randX = random(width); 77 | float randY = -50 +floor(random(2))* (height+100); 78 | asteroids.add(new Asteroid(randX, randY, pos.x- randX, pos.y - randY, 3)); 79 | } 80 | 81 | //------------------------------------------------------------------------------------------------------------------------------------------ 82 | //Move player 83 | void move() { 84 | if (!dead) { 85 | checkTimers(); 86 | rotatePlayer(); 87 | if (boosting) {//are thrusters on 88 | boost(); 89 | } else { 90 | boostOff(); 91 | } 92 | 93 | vel.add(acc);//velocity += acceleration 94 | vel.limit(maxSpeed); 95 | vel.mult(0.99); 96 | pos.add(vel);//position += velocity 97 | 98 | for (int i = 0; i < bullets.size(); i++) {//move all the bullets 99 | bullets.get(i).move(); 100 | } 101 | 102 | for (int i = 0; i < asteroids.size(); i++) {//move all the asteroids 103 | asteroids.get(i).move(); 104 | } 105 | if (isOut(pos)) {//wrap the player around the gaming area 106 | loopy(); 107 | } 108 | } 109 | } 110 | //------------------------------------------------------------------------------------------------------------------------------------------ 111 | //move through time and check if anything should happen at this instance 112 | void checkTimers() { 113 | lifespan +=1; 114 | shootCount --; 115 | asteroidCount--; 116 | if (asteroidCount<=0) {//spawn asteorid 117 | 118 | if (replay) {//if replaying use the seeds from the arrayList 119 | randomSeed(seedsUsed.get(upToSeedNo)); 120 | upToSeedNo ++; 121 | } else {//if not generate the seeds and then save them 122 | long seed = floor(random(1000000)); 123 | seedsUsed.add(seed); 124 | randomSeed(seed); 125 | } 126 | //aim the asteroid at the player to encourage movement 127 | float randX = random(width); 128 | float randY = -50 +floor(random(2))* (height+100); 129 | asteroids.add(new Asteroid(randX, randY, pos.x- randX, pos.y - randY, 3)); 130 | asteroidCount = 1000; 131 | } 132 | 133 | if (shootCount <=0) { 134 | canShoot = true; 135 | } 136 | } 137 | 138 | 139 | 140 | //------------------------------------------------------------------------------------------------------------------------------------------ 141 | //booster 142 | void boost() { 143 | acc = PVector.fromAngle(rotation); 144 | acc.setMag(0.5); 145 | } 146 | 147 | //------------------------------------------------------------------------------------------------------------------------------------------ 148 | //boostless 149 | void boostOff() { 150 | acc.setMag(0); 151 | } 152 | //------------------------------------------------------------------------------------------------------------------------------------------ 153 | //spin that player 154 | void rotatePlayer() { 155 | rotation += spin; 156 | } 157 | //------------------------------------------------------------------------------------------------------------------------------------------ 158 | //draw the player, bullets and asteroids 159 | void show() { 160 | if (!dead) { 161 | for (int i = 0; i < bullets.size(); i++) {//show bullets 162 | bullets.get(i).show(); 163 | } 164 | if (immortalCount >0) {//no need to decrease immortalCOunt if its already 0 165 | immortalCount --; 166 | } 167 | if (immortalCount >0 && floor(((float)immortalCount)/5)%2 ==0) {//needs to appear to be flashing so only show half of the time 168 | } else { 169 | pushMatrix(); 170 | translate(pos.x, pos.y); 171 | rotate(rotation); 172 | //actually draw the player 173 | fill(0); 174 | noStroke(); 175 | beginShape(); 176 | int size = 12; 177 | //black triangle 178 | vertex(-size-2, -size); 179 | vertex(-size-2, size); 180 | vertex(2* size -2, 0); 181 | endShape(CLOSE); 182 | stroke(255); 183 | //white out lines 184 | line(-size-2, -size, -size-2, size); 185 | line(2* size -2, 0, -22, 15); 186 | line(2* size -2, 0, -22, -15); 187 | if (boosting ) {//when boosting draw "flames" its just a little triangle 188 | boostCount --; 189 | if (floor(((float)boostCount)/3)%2 ==0) {//only show it half of the time to appear like its flashing 190 | line(-size-2, 6, -size-2-12, 0); 191 | line(-size-2, -6, -size-2-12, 0); 192 | } 193 | } 194 | popMatrix(); 195 | } 196 | } 197 | for (int i = 0; i < asteroids.size(); i++) {//show asteroids 198 | asteroids.get(i).show(); 199 | } 200 | } 201 | //------------------------------------------------------------------------------------------------------------------------------------------ 202 | //shoot a bullet 203 | void shoot() { 204 | if (shootCount <=0) {//if can shoot 205 | bullets.add(new Bullet(pos.x, pos.y, rotation, vel.mag()));//create bullet 206 | shootCount = 50;//reset shoot count 207 | canShoot = false; 208 | shotsFired ++; 209 | } 210 | } 211 | //------------------------------------------------------------------------------------------------------------------------------------------ 212 | //in charge or moving everything and also checking if anything has been shot or hit 213 | void update() { 214 | for (int i = 0; i < bullets.size(); i++) {//if any bullets expires remove it 215 | if (bullets.get(i).off) { 216 | bullets.remove(i); 217 | i--; 218 | } 219 | } 220 | move();//move everything 221 | checkPositions();//check if anything has been shot or hit 222 | } 223 | //------------------------------------------------------------------------------------------------------------------------------------------ 224 | //check if anything has been shot or hit 225 | void checkPositions() { 226 | //check if any bullets have hit any asteroids 227 | for (int i = 0; i < bullets.size(); i++) { 228 | for (int j = 0; j < asteroids.size(); j++) { 229 | if (asteroids.get(j).checkIfHit(bullets.get(i).pos)) { 230 | shotsHit ++; 231 | bullets.remove(i);//remove bullet 232 | score +=1; 233 | break; 234 | } 235 | } 236 | } 237 | //check if player has been hit 238 | if (immortalCount <=0) { 239 | for (int j = 0; j < asteroids.size(); j++) { 240 | if (asteroids.get(j).checkIfHitPlayer(pos)) { 241 | playerHit(); 242 | } 243 | } 244 | } 245 | } 246 | //------------------------------------------------------------------------------------------------------------------------------------------ 247 | //called when player is hit by an asteroid 248 | void playerHit() { 249 | if (lives == 0) {//if no lives left 250 | dead = true; 251 | } else {//remove a life and reset positions 252 | lives -=1; 253 | immortalCount = 100; 254 | resetPositions(); 255 | } 256 | } 257 | //------------------------------------------------------------------------------------------------------------------------------------------ 258 | //returns player to center 259 | void resetPositions() { 260 | pos = new PVector(width/2, height/2); 261 | vel = new PVector(); 262 | acc = new PVector(); 263 | bullets = new ArrayList(); 264 | rotation = 0; 265 | } 266 | //------------------------------------------------------------------------------------------------------------------------------------------ 267 | //wraps the player around the playing area 268 | void loopy() { 269 | if (pos.y < -50) { 270 | pos.y = height + 50; 271 | } else 272 | if (pos.y > height + 50) { 273 | pos.y = -50; 274 | } 275 | if (pos.x< -50) { 276 | pos.x = width +50; 277 | } else if (pos.x > width + 50) { 278 | pos.x = -50; 279 | } 280 | } 281 | 282 | //---------------------------------------------------------------------------------------------------------------------------------------------------------<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< 283 | //for genetic algorithm 284 | void calculateFitness() { 285 | float hitRate = (float)shotsHit/(float)shotsFired; 286 | fitness = (score+1)*10; 287 | fitness *= lifespan; 288 | fitness *= hitRate*hitRate;//includes hitrate to encourage aiming 289 | unadjustedFitness = fitness; 290 | } 291 | 292 | //--------------------------------------------------------------------------------------------------------------------------------------------------------- 293 | //returns a clone of this player with the same brian 294 | Player clone() { 295 | Player clone = new Player(); 296 | clone.brain = brain.clone(); 297 | clone.fitness = fitness; 298 | clone.brain.generateNetwork(); 299 | return clone; 300 | } 301 | //returns a clone of this player with the same brian and same random seeds used so all of the asteroids will be in the same positions 302 | Player cloneForReplay() { 303 | Player clone = new Player(SeedUsed); 304 | clone.brain = brain.clone(); 305 | clone.fitness = fitness; 306 | clone.bestScore = score; 307 | clone.seedsUsed = (ArrayList)seedsUsed.clone(); 308 | clone.brain.generateNetwork(); 309 | return clone; 310 | } 311 | //--------------------------------------------------------------------------------------------------------------------------------------------------------- 312 | Player crossover(Player parent2) { 313 | Player child = new Player(); 314 | child.brain = brain.crossover(parent2.brain); 315 | child.brain.generateNetwork(); 316 | return child; 317 | } 318 | //--------------------------------------------------------------------------------------------------------------------------------------------------------- 319 | 320 | //looks in 8 directions to find asteroids 321 | void look() { 322 | vision = new float[33]; 323 | //look left 324 | PVector direction; 325 | for (int i = 0; i< vision.length - 1; i+=2) { 326 | direction = PVector.fromAngle(rotation + i*(PI/8)); 327 | direction.mult(10); 328 | lookInDirection(direction, i); 329 | } 330 | 331 | 332 | if (canShoot && vision[0] != 0) { 333 | vision[32] = 1; 334 | } else { 335 | vision[32] =0; 336 | } 337 | } 338 | //--------------------------------------------------------------------------------------------------------------------------------------------------------- 339 | 340 | 341 | float lookInDirection(PVector direction, int visionPos) { 342 | //set up a temp array to hold the values that are going to be passed to the main vision array 343 | 344 | PVector position = new PVector(pos.x, pos.y);//the position where we are currently looking for food or tail or wall 345 | float distance = 0; 346 | //move once in the desired direction before starting 347 | position.add(direction); 348 | distance +=1; 349 | PVector looped = new PVector(0, 0); 350 | //look in the direction until you reach a wall 351 | while (distance< 60) {//!(position.x < 400 || position.y < 0 || position.x >= 800 || position.y >= 400)) { 352 | 353 | 354 | for (Asteroid a : asteroids) { 355 | if (a.lookForHit(position) ) { 356 | 357 | vision[visionPos] = 1/distance; 358 | Asteroid asteroidHit = a.getAsteroid(position); 359 | //vision[visionPos+1] = map(asteroidHit.size, 1, 3, 0, 1); 360 | 361 | 362 | 363 | PVector towardsPlayer = new PVector(pos.x - asteroidHit.pos.x - looped.x, pos.y - asteroidHit.pos.y - looped.x); 364 | towardsPlayer.normalize(); 365 | float redShift = asteroidHit.vel.dot(towardsPlayer); 366 | vision[visionPos+1] =redShift; 367 | } 368 | } 369 | 370 | //look further in the direction 371 | position.add(direction); 372 | 373 | //loop it 374 | if (position.y < -50) { 375 | position.y += height + 100; 376 | looped.y += height + 100; 377 | } else 378 | if (position.y > height + 50) { 379 | position.y -= height -100; 380 | looped.y -= height + 100; 381 | } 382 | if (position.x< -50) { 383 | position.x += width +100; 384 | looped.x += width + 100; 385 | } else if (position.x > width + 50) { 386 | position.x -= width +100; 387 | looped.x -= width + 100; 388 | } 389 | 390 | 391 | distance +=1; 392 | } 393 | return 0; 394 | } 395 | 396 | 397 | //--------------------------------------------------------------------------------------------------------------------------------------------------------- 398 | //convert the output of the neural network to actions 399 | void think() { 400 | //get the output of the neural network 401 | decision = brain.feedForward(vision); 402 | 403 | if (decision[0] > 0.8) {//output 0 is boosting 404 | boosting = true; 405 | } else { 406 | boosting = false; 407 | } 408 | if (decision[1] > 0.8) {//output 1 is turn left 409 | spin = -0.08; 410 | 411 | } else {//cant turn right and left at the same time 412 | if (decision[2] > 0.8) {//output 2 is turn right 413 | spin = 0.08; 414 | 415 | } else {//if neither then dont turn 416 | spin = 0; 417 | } 418 | } 419 | //shooting 420 | if (decision[3] > 0.8) {//output 3 is shooting 421 | shoot(); 422 | } 423 | } 424 | } -------------------------------------------------------------------------------- /asteroidsGameNeat/Population.pde: -------------------------------------------------------------------------------- 1 | class Population { 2 | ArrayList pop = new ArrayList(); 3 | Player bestPlayer;//the best ever player 4 | int bestScore =0;//the score of the best ever player 5 | //int species = 0; 6 | int gen; 7 | //int nextConnectionNumber; 8 | ArrayList innovationHistory = new ArrayList(); 9 | ArrayList genPlayers = new ArrayList(); 10 | ArrayList species = new ArrayList(); 11 | 12 | 13 | 14 | //------------------------------------------------------------------------------------------------------------------------------------------ 15 | //constructor 16 | Population(int size) { 17 | 18 | for (int i =0; i bestScore) { 59 | println("old best:", bestScore); 60 | println("new best:", tempBest.score); 61 | bestScore = tempBest.score; 62 | bestPlayer = tempBest.cloneForReplay(); 63 | } 64 | } 65 | 66 | //------------------------------------------------------------------------------------------------------------------------------------------------ 67 | //this function is called when all the players in the population are dead and a new generation needs to be made 68 | void naturalSelection() { 69 | speciate();//seperate the population into species 70 | calculateFitness();//calculate the fitness of each player 71 | sortSpecies();//sort the species to be ranked in fitness order, best first 72 | cullSpecies();//kill off the bottom half of each species 73 | setBestPlayer();//save the best player of this gen 74 | killStaleSpecies();//remove species which haven't improved in the last 15(ish) generations 75 | killBadSpecies();//kill species which are so bad that they cant reproduce 76 | 77 | 78 | float averageSum = getAvgFitnessSum(); 79 | ArrayList children = new ArrayList();//the next generation 80 | for (Species s : species) {//for each species 81 | children.add(s.players.get(0).clone());//add champion without any mutation 82 | int NoOfChildren = floor(s.averageFitness/averageSum * pop.size()) -1;//the number of children this species is allowed, note -1 is because the champ is already added 83 | for (int i = 0; i< NoOfChildren; i++) {//get the calculated amount of children from this species 84 | children.add(s.giveMeBaby(innovationHistory)); 85 | } 86 | } 87 | 88 | while (children.size() < pop.size()) {//if not enough babies (due to flooring the number of children to get a whole int) 89 | children.add(species.get(0).giveMeBaby(innovationHistory));//get babies from the best species 90 | } 91 | 92 | pop.clear(); 93 | pop = (ArrayList)children.clone(); //set the children as the current population 94 | 95 | gen+=1; 96 | println("generation", gen,"Number of mutations", innovationHistory.size(), "species: " + species.size()); 97 | for (int i = 0; i< pop.size(); i++) {//generate networks for each of the children 98 | pop.get(i).brain.generateNetwork(); 99 | } 100 | } 101 | 102 | //------------------------------------------------------------------------------------------------------------------------------------------ 103 | //seperate population into species based on how similar they are to the leaders of each species in the previous gen 104 | void speciate() { 105 | for (Species s : species) {//empty species 106 | s.players.clear(); 107 | } 108 | for (int i = 0; i< pop.size(); i++) {//for each player 109 | boolean speciesFound = false; 110 | for (Species s : species) {//for each species 111 | if (s.sameSpecies(pop.get(i).brain)) {//if the player is similar enough to be considered in the same species 112 | s.addToSpecies(pop.get(i));//add it to the species 113 | speciesFound = true; 114 | break; 115 | } 116 | } 117 | if (!speciesFound) {//if no species was similar enough then add a new species with this as its champion 118 | species.add(new Species(pop.get(i))); 119 | } 120 | } 121 | } 122 | //------------------------------------------------------------------------------------------------------------------------------------------ 123 | //calculates the fitness of all of the players 124 | void calculateFitness() { 125 | 126 | for (int i =1; i temp = new ArrayList(); 142 | for (int i = 0; i < species.size(); i ++) { 143 | float max = 0; 144 | int maxIndex = 0; 145 | for (int j = 0; j< species.size(); j++) { 146 | if (species.get(j).bestFitness > max) { 147 | max = species.get(j).bestFitness; 148 | maxIndex = j; 149 | } 150 | } 151 | temp.add(species.get(maxIndex)); 152 | species.remove(maxIndex); 153 | i--; 154 | } 155 | species = (ArrayList)temp.clone(); 156 | } 157 | //------------------------------------------------------------------------------------------------------------------------------------------ 158 | //kills all species which haven't improved in 15 generations 159 | void killStaleSpecies() { 160 | for (int i = 1; i< species.size(); i++) { 161 | if (species.get(i).staleness >= 15) { 162 | species.remove(i); 163 | i--; 164 | } 165 | } 166 | } 167 | //------------------------------------------------------------------------------------------------------------------------------------------ 168 | //if a species sucks so much that it wont even be allocated 1 child for the next generation then kill it now 169 | void killBadSpecies() { 170 | float averageSum = getAvgFitnessSum(); 171 | 172 | for (int i = 1; i< species.size(); i++) { 173 | if (species.get(i).averageFitness/averageSum * pop.size() < 1) {//if wont be given a single child 174 | species.remove(i);//sad 175 | i--; 176 | } 177 | } 178 | } 179 | //------------------------------------------------------------------------------------------------------------------------------------------ 180 | //returns the sum of each species average fitness 181 | float getAvgFitnessSum() { 182 | float averageSum = 0; 183 | for (Species s : species) { 184 | averageSum += s.averageFitness; 185 | } 186 | return averageSum; 187 | } 188 | 189 | //------------------------------------------------------------------------------------------------------------------------------------------ 190 | //kill the bottom half of each species 191 | void cullSpecies() { 192 | for (Species s : species) { 193 | s.cull(); //kill bottom half 194 | s.fitnessSharing();//also while we're at it lets do fitness sharing 195 | s.setAverage();//reset averages because they will have changed 196 | } 197 | } 198 | } -------------------------------------------------------------------------------- /asteroidsGameNeat/Species.pde: -------------------------------------------------------------------------------- 1 | class Species { 2 | ArrayList players = new ArrayList(); 3 | float bestFitness = 0; 4 | Player champ; 5 | float averageFitness = 0; 6 | int staleness = 0;//how many generations the species has gone without an improvement 7 | Genome rep; 8 | 9 | //-------------------------------------------- 10 | //coefficients for testing compatibility 11 | float excessCoeff = 1.5; 12 | float weightDiffCoeff = 0.8; 13 | float compatibilityThreshold = 1; 14 | //------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ 15 | //empty constructor 16 | 17 | Species() { 18 | } 19 | 20 | 21 | //------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ 22 | //constructor which takes in the player which belongs to the species 23 | Species(Player p) { 24 | players.add(p); 25 | //since it is the only one in the species it is by default the best 26 | bestFitness = p.fitness; 27 | rep = p.brain.clone(); 28 | champ = p.cloneForReplay(); 29 | } 30 | 31 | //------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ 32 | //returns whether the parameter genome is in this species 33 | boolean sameSpecies(Genome g) { 34 | float compatibility; 35 | float excessAndDisjoint = getExcessDisjoint(g, rep);//get the number of excess and disjoint genes between this player and the current species rep 36 | float averageWeightDiff = averageWeightDiff(g, rep);//get the average weight difference between matching genes 37 | float largeGenomeNormaliser = 1;//g.genes.size(); 38 | 39 | compatibility = (excessCoeff* excessAndDisjoint/largeGenomeNormaliser) + (weightDiffCoeff* averageWeightDiff);//compatablilty formula 40 | return (compatibilityThreshold > compatibility); 41 | } 42 | 43 | //------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ 44 | //add a player to the species 45 | void addToSpecies(Player p) { 46 | players.add(p); 47 | } 48 | 49 | //------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ 50 | //returns the number of excess and disjoint genes between the 2 input genomes 51 | //i.e. returns the number of genes which dont match 52 | float getExcessDisjoint(Genome brain1, Genome brain2) { 53 | float matching = 0.0; 54 | for (int i =0; i temp = new ArrayList(); 88 | 89 | //selection short 90 | for (int i = 0; i < players.size(); i ++) { 91 | float max = 0; 92 | int maxIndex = 0; 93 | for (int j = 0; j< players.size(); j++) { 94 | if (players.get(j).fitness > max) { 95 | max = players.get(j).fitness; 96 | maxIndex = j; 97 | } 98 | } 99 | temp.add(players.get(maxIndex)); 100 | players.remove(maxIndex); 101 | i--; 102 | } 103 | 104 | players = (ArrayList)temp.clone(); 105 | 106 | //if new best player 107 | if (players.get(0).fitness > bestFitness) { 108 | staleness = 0; 109 | bestFitness = players.get(0).fitness; 110 | rep = players.get(0).brain.clone(); 111 | champ = players.get(0).cloneForReplay(); 112 | } else {//if no new best player 113 | staleness ++; 114 | } 115 | } 116 | 117 | //------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 118 | //simple stuff 119 | void setAverage() { 120 | 121 | float sum = 0; 122 | for (int i = 0; i < players.size(); i ++) { 123 | sum += players.get(i).fitness; 124 | } 125 | averageFitness = sum/players.size(); 126 | } 127 | //------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 128 | 129 | //gets baby from the players in this species 130 | Player giveMeBaby(ArrayList innovationHistory) { 131 | Player baby; 132 | if (random(1) < 0.25) {//25% of the time there is no crossover and the child is simply a clone of a random(ish) player 133 | baby = selectPlayer().clone(); 134 | } else {//75% of the time do crossover 135 | 136 | //get 2 random(ish) parents 137 | Player parent1 = selectPlayer(); 138 | Player parent2 = selectPlayer(); 139 | 140 | //the crossover function expects the highest fitness parent to be the object and the lowest as the argument 141 | if (parent1.fitness < parent2.fitness) { 142 | baby = parent2.crossover(parent1); 143 | } else { 144 | baby = parent1.crossover(parent2); 145 | } 146 | } 147 | baby.brain.mutate(innovationHistory);//mutate that baby brain 148 | return baby; 149 | } 150 | 151 | //------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 152 | //selects a player based on it fitness 153 | Player selectPlayer() { 154 | float fitnessSum = 0; 155 | for (int i =0; i rand) { 165 | return players.get(i); 166 | } 167 | } 168 | //unreachable code to make the parser happy 169 | return players.get(0); 170 | } 171 | //------------------------------------------------------------------------------------------------------------------------------------------ 172 | //kills off bottom half of the species 173 | void cull() { 174 | if (players.size() > 2) { 175 | for (int i = players.size()/2; i= pop.genPlayers.size()) {//if at the end then return to the start and stop doing it 61 | upToGen= 0; 62 | showBestEachGen = false; 63 | } else {//if not at the end then get the next generation 64 | genPlayerTemp = pop.genPlayers.get(upToGen).clone(); 65 | println(genPlayerTemp.bestScore); 66 | } 67 | } 68 | } else 69 | if (runThroughSpecies ) {//show all the species 70 | if (!speciesChamp.dead) {//if best player is not dead 71 | speciesChamp.look(); 72 | speciesChamp.think(); 73 | speciesChamp.update(); 74 | speciesChamp.show(); 75 | } else {//once dead 76 | upToSpecies++; 77 | if (upToSpecies >= pop.species.size()) { 78 | runThroughSpecies = false; 79 | } else { 80 | speciesChamp = pop.species.get(upToSpecies).champ.cloneForReplay(); 81 | } 82 | } 83 | } else { 84 | if (humanPlaying) {//if the user is controling the ship[ 85 | if (!humanPlayer.dead) {//if the player isnt dead then move and show the player based on input 86 | humanPlayer.look(); 87 | humanPlayer.update(); 88 | humanPlayer.show(); 89 | println(humanPlayer.vision[1]); 90 | } else {//once done return to ai 91 | humanPlaying = false; 92 | } 93 | } else 94 | if (runBest) {// if replaying the best ever game 95 | if (!pop.bestPlayer.dead) {//if best player is not dead 96 | pop.bestPlayer.look(); 97 | pop.bestPlayer.think(); 98 | pop.bestPlayer.update(); 99 | pop.bestPlayer.show(); 100 | } else {//once dead 101 | runBest = false;//stop replaying it 102 | pop.bestPlayer = pop.bestPlayer.cloneForReplay();//reset the best player so it can play again 103 | } 104 | } else {//if just evolving normally 105 | if (!pop.done()) {//if any players are alive then update them 106 | pop.updateAlive(); 107 | } else {//all dead 108 | //genetic algorithm 109 | pop.naturalSelection(); 110 | } 111 | } 112 | } 113 | showScore();//display the score 114 | } 115 | //------------------------------------------------------------------------------------------------------------------------------------------ 116 | 117 | void keyPressed() { 118 | switch(key) { 119 | case ' ': 120 | if (humanPlaying) {//if the user is controlling a ship shoot 121 | humanPlayer.shoot(); 122 | } else {//if not toggle showBest 123 | showBest = !showBest; 124 | } 125 | break; 126 | case 'p'://play 127 | humanPlaying = !humanPlaying; 128 | humanPlayer = new Player(); 129 | break; 130 | case '+'://speed up frame rate 131 | speed += 10; 132 | frameRate(speed); 133 | println(speed); 134 | 135 | break; 136 | case '-'://slow down frame rate 137 | if (speed > 10) { 138 | speed -= 10; 139 | frameRate(speed); 140 | println(speed); 141 | } 142 | break; 143 | case 'h'://halve the mutation rate 144 | globalMutationRate /=2; 145 | println(globalMutationRate); 146 | break; 147 | case 'd'://double the mutation rate 148 | globalMutationRate *= 2; 149 | println(globalMutationRate); 150 | break; 151 | case 'b'://run the best 152 | runBest = true; 153 | break; 154 | case 's': 155 | runThroughSpecies = !runThroughSpecies; 156 | upToSpecies = 0; 157 | speciesChamp = pop.species.get(upToSpecies).champ.cloneForReplay(); 158 | break; 159 | case 'g'://show genome 160 | showBestEachGen = !showBestEachGen; 161 | upToGen = 0; 162 | genPlayerTemp = pop.genPlayers.get(upToGen).clone(); 163 | break; 164 | case 'n': 165 | showBrain = !showBrain; 166 | break; 167 | } 168 | 169 | //player controls 170 | if (key == CODED) { 171 | if (keyCode == UP) { 172 | humanPlayer.boosting = true; 173 | } 174 | if (keyCode == LEFT) { 175 | humanPlayer.spin = -0.08; 176 | } else if (keyCode == RIGHT) { 177 | if (runThroughSpecies) { 178 | upToSpecies++; 179 | if (upToSpecies >= pop.species.size()) { 180 | runThroughSpecies = false; 181 | } else { 182 | speciesChamp = pop.species.get(upToSpecies).champ.cloneForReplay(); 183 | } 184 | } else 185 | if (showBestEachGen) { 186 | upToGen++; 187 | if (upToGen >= pop.gen) { 188 | showBestEachGen = false; 189 | } else { 190 | genPlayerTemp = pop.genPlayers.get(upToGen).cloneForReplay(); 191 | } 192 | } else { 193 | humanPlayer.spin = 0.08; 194 | } 195 | } 196 | } 197 | } 198 | 199 | //---------------------------------------------------------------------------------------------------------------------------------------- 200 | void keyReleased() { 201 | //once key released 202 | if (key == CODED) { 203 | if (keyCode == UP) {//stop boosting 204 | humanPlayer.boosting = false; 205 | } 206 | if (keyCode == LEFT) {// stop turning 207 | humanPlayer.spin = 0; 208 | } else if (keyCode == RIGHT) { 209 | humanPlayer.spin = 0; 210 | } 211 | } 212 | } 213 | 214 | //------------------------------------------------------------------------------------------------------------------------------------------ 215 | //function which returns whether a vector is out of the play area 216 | boolean isOut(PVector pos) { 217 | if (pos.x < -50 || pos.y < -50 || pos.x > width+ 50 || pos.y > 50+height) { 218 | return true; 219 | } 220 | return false; 221 | } 222 | 223 | //------------------------------------------------------------------------------------------------------------------------------------------ 224 | //shows the score and the generation on the screen 225 | void showScore() { 226 | if (showBestEachGen) { 227 | textFont(font); 228 | fill(255); 229 | textAlign(LEFT); 230 | text("Score: " + genPlayerTemp.score, 80, 60); 231 | text("Gen: " + (upToGen +1), width-250, 60); 232 | } else 233 | if (runThroughSpecies) { 234 | textFont(font); 235 | fill(255); 236 | textAlign(LEFT); 237 | text("Score: " + speciesChamp.score, 80, 60); 238 | text("Species: " + (upToSpecies +1), width-250, 60); 239 | } else 240 | if (humanPlaying) { 241 | textFont(font); 242 | fill(255); 243 | textAlign(LEFT); 244 | text("Score: " + humanPlayer.score, 80, 60); 245 | } else 246 | if (runBest) { 247 | textFont(font); 248 | fill(255); 249 | textAlign(LEFT); 250 | text("Score: " + pop.bestPlayer.score, 80, 60); 251 | text("Gen: " + pop.gen, width-200, 60); 252 | } else { 253 | if (showBest) { 254 | textFont(font); 255 | fill(255); 256 | textAlign(LEFT); 257 | text("Score: " + pop.pop.get(0).score, 80, 60); 258 | text("Gen: " + pop.gen, width-200, 60); 259 | } 260 | } 261 | } -------------------------------------------------------------------------------- /asteroidsGameNeat/connectionGene.pde: -------------------------------------------------------------------------------- 1 | //a connection between 2 nodes 2 | class connectionGene { 3 | Node fromNode; 4 | Node toNode; 5 | float weight; 6 | boolean enabled = true; 7 | int innovationNo;//each connection is given a innovation number to compare genomes 8 | //--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 9 | //constructor 10 | connectionGene(Node from, Node to, float w, int inno) { 11 | fromNode = from; 12 | toNode = to; 13 | weight = w; 14 | innovationNo = inno; 15 | } 16 | 17 | //--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 18 | //changes the weight 19 | void mutateWeight() { 20 | float rand2 = random(1); 21 | if (rand2 < 0.1) {//10% of the time completely change the weight 22 | weight = random(-1, 1); 23 | } else {//otherwise slightly change it 24 | weight += randomGaussian()/50; 25 | //keep weight between bounds 26 | if(weight > 1){ 27 | weight = 1; 28 | } 29 | if(weight < -1){ 30 | weight = -1; 31 | 32 | } 33 | } 34 | } 35 | 36 | //---------------------------------------------------------------------------------------------------------- 37 | //returns a copy of this connectionGene 38 | connectionGene clone(Node from, Node to) { 39 | connectionGene clone = new connectionGene(from, to, weight, innovationNo); 40 | clone.enabled = enabled; 41 | 42 | return clone; 43 | } 44 | } -------------------------------------------------------------------------------- /asteroidsGameNeat/connectionHistory.pde: -------------------------------------------------------------------------------- 1 | class connectionHistory { 2 | int fromNode; 3 | int toNode; 4 | int innovationNumber; 5 | 6 | 7 | 8 | 9 | 10 | ArrayList innovationNumbers = new ArrayList();//the innovation Numbers from the connections of the genome which first had this mutation 11 | //this represents the genome and allows us to test if another genoeme is the same 12 | //this is before this connection was added 13 | 14 | //--------------------------------------------------------------------------------------------------------------------------------------------------------- 15 | //constructor 16 | connectionHistory(int from, int to, int inno, ArrayList innovationNos) { 17 | fromNode = from; 18 | toNode = to; 19 | innovationNumber = inno; 20 | innovationNumbers = (ArrayList)innovationNos.clone(); 21 | } 22 | //--------------------------------------------------------------------------------------------------------------------------------------------------------- 23 | //returns whether the genome matches the original genome and the connection is between the same nodes 24 | boolean matches(Genome genome, Node from, Node to) { 25 | if (genome.genes.size() == innovationNumbers.size()) { //if the number of connections are different then the genoemes aren't the same 26 | if (from.number == fromNode && to.number == toNode) { 27 | //next check if all the innovation numbers match from the genome 28 | for (int i = 0; i< genome.genes.size(); i++) { 29 | if (!innovationNumbers.contains(genome.genes.get(i).innovationNo)) { 30 | return false; 31 | } 32 | } 33 | 34 | //if reached this far then the innovationNumbers match the genes innovation numbers and the connection is between the same nodes 35 | //so it does match 36 | return true; 37 | } 38 | } 39 | return false; 40 | } 41 | } -------------------------------------------------------------------------------- /asteroidsGameNeat/data/AgencyFB-Reg-48.vlw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Code-Bullet/Asteroids-with-NEAT/abe1e9af30d0392270324c44fa9b3056bd4b222e/asteroidsGameNeat/data/AgencyFB-Reg-48.vlw --------------------------------------------------------------------------------