├── README.md └── TemplateNeat ├── Genome.pde ├── Node.pde ├── Player.pde ├── Population.pde ├── Species.pde ├── TemplateNeat.pde ├── connectionGene.pde └── connectionHistory.pde /README.md: -------------------------------------------------------------------------------- 1 | # NEAT_Template 2 | This is mainly for me, but if anyone wishes to use it then go ahead. 3 | 4 | This is an empty template which you can add to any game that you make for processing. 5 | this will neat it 6 | to see how i use it check out some of my projects 7 | 8 | 9 | bye 10 | -------------------------------------------------------------------------------- /TemplateNeat/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 | 10 | ArrayList network = new ArrayList();//a list of the nodes in the order that they need to be considered in the NN 11 | //--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 12 | Genome(int in, int out) { 13 | //set input number and output number 14 | inputs = in; 15 | outputs = out; 16 | 17 | //create input nodes 18 | for (int i = 0; i(); 97 | //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 98 | 99 | for (int l = 0; l< layers; l++) {//for each layer 100 | for (int i = 0; i< nodes.size(); i++) {//for each node 101 | if (nodes.get(i).layer == l) {//if that node is in that layer 102 | network.add(nodes.get(i)); 103 | } 104 | } 105 | } 106 | } 107 | //----------------------------------------------------------------------------------------------------------------------------------------- 108 | //mutate the NN by adding a new node 109 | //it does this by picking a random connection and disabling it then 2 new connections are added 110 | //1 between the input node of the disabled connection and the new node 111 | //and the other between the new node and the output of the disabled connection 112 | void addNode(ArrayList innovationHistory) { 113 | //pick a random connection to create a node between 114 | if (genes.size() ==0) { 115 | addConnection(innovationHistory); 116 | return; 117 | } 118 | int randomConnection = floor(random(genes.size())); 119 | 120 | while (genes.get(randomConnection).fromNode == nodes.get(biasNode) && genes.size() !=1 ) {//dont disconnect bias 121 | randomConnection = floor(random(genes.size())); 122 | } 123 | 124 | genes.get(randomConnection).enabled = false;//disable it 125 | 126 | int newNodeNo = nextNode; 127 | nodes.add(new Node(newNodeNo)); 128 | nextNode ++; 129 | //add a new connection to the new node with a weight of 1 130 | int connectionInnovationNumber = getInnovationNumber(innovationHistory, genes.get(randomConnection).fromNode, getNode(newNodeNo)); 131 | genes.add(new connectionGene(genes.get(randomConnection).fromNode, getNode(newNodeNo), 1, connectionInnovationNumber)); 132 | 133 | 134 | connectionInnovationNumber = getInnovationNumber(innovationHistory, getNode(newNodeNo), genes.get(randomConnection).toNode); 135 | //add a new connection from the new node with a weight the same as the disabled connection 136 | genes.add(new connectionGene(getNode(newNodeNo), genes.get(randomConnection).toNode, genes.get(randomConnection).weight, connectionInnovationNumber)); 137 | getNode(newNodeNo).layer = genes.get(randomConnection).fromNode.layer +1; 138 | 139 | 140 | connectionInnovationNumber = getInnovationNumber(innovationHistory, nodes.get(biasNode), getNode(newNodeNo)); 141 | //connect the bias to the new node with a weight of 0 142 | genes.add(new connectionGene(nodes.get(biasNode), getNode(newNodeNo), 0, connectionInnovationNumber)); 143 | 144 | //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 145 | //more accurately the layer numbers of all layers equal to or greater than this new node need to be incrimented 146 | if (getNode(newNodeNo).layer == genes.get(randomConnection).toNode.layer) { 147 | for (int i = 0; i< nodes.size() -1; i++) {//dont include this newest node 148 | if (nodes.get(i).layer >= getNode(newNodeNo).layer) { 149 | nodes.get(i).layer ++; 150 | } 151 | } 152 | layers ++; 153 | } 154 | connectNodes(); 155 | } 156 | 157 | //------------------------------------------------------------------------------------------------------------------ 158 | //adds a connection between 2 nodes which aren't currently connected 159 | void addConnection(ArrayList innovationHistory) { 160 | //cannot add a connection to a fully connected network 161 | if (fullyConnected()) { 162 | println("connection failed"); 163 | return; 164 | } 165 | 166 | 167 | //get random nodes 168 | int randomNode1 = floor(random(nodes.size())); 169 | int randomNode2 = floor(random(nodes.size())); 170 | while (randomConnectionNodesAreShit(randomNode1, randomNode2)) {//while the random nodes are no good 171 | //get new ones 172 | randomNode1 = floor(random(nodes.size())); 173 | randomNode2 = floor(random(nodes.size())); 174 | } 175 | int temp; 176 | if (nodes.get(randomNode1).layer > nodes.get(randomNode2).layer) {//if the first random node is after the second then switch 177 | temp =randomNode2 ; 178 | randomNode2 = randomNode1; 179 | randomNode1 = temp; 180 | } 181 | 182 | //get the innovation number of the connection 183 | //this will be a new number if no identical genome has mutated in the same way 184 | int connectionInnovationNumber = getInnovationNumber(innovationHistory, nodes.get(randomNode1), nodes.get(randomNode2)); 185 | //add the connection with a random array 186 | 187 | genes.add(new connectionGene(nodes.get(randomNode1), nodes.get(randomNode2), random(-1, 1), connectionInnovationNumber));//changed this so if error here 188 | connectNodes(); 189 | } 190 | //------------------------------------------------------------------------------------------------------------------------------------------- 191 | boolean randomConnectionNodesAreShit(int r1, int r2) { 192 | if (nodes.get(r1).layer == nodes.get(r2).layer) return true; // if the nodes are in the same layer 193 | if (nodes.get(r1).isConnectedTo(nodes.get(r2))) return true; //if the nodes are already connected 194 | 195 | 196 | 197 | return false; 198 | } 199 | 200 | //------------------------------------------------------------------------------------------------------------------------------------------- 201 | //returns the innovation number for the new mutation 202 | //if this mutation has never been seen before then it will be given a new unique innovation number 203 | //if this mutation matches a previous mutation then it will be given the same innovation number as the previous one 204 | int getInnovationNumber(ArrayList innovationHistory, Node from, Node to) { 205 | boolean isNew = true; 206 | int connectionInnovationNumber = nextConnectionNo; 207 | for (int i = 0; i < innovationHistory.size(); i++) {//for each previous mutation 208 | if (innovationHistory.get(i).matches(this, from, to)) {//if match found 209 | isNew = false;//its not a new mutation 210 | connectionInnovationNumber = innovationHistory.get(i).innovationNumber; //set the innovation number as the innovation number of the match 211 | break; 212 | } 213 | } 214 | 215 | if (isNew) {//if the mutation is new then create an arrayList of integers representing the current state of the genome 216 | ArrayList innoNumbers = new ArrayList(); 217 | for (int i = 0; i< genes.size(); i++) {//set the innovation numbers 218 | innoNumbers.add(genes.get(i).innovationNo); 219 | } 220 | 221 | //then add this mutation to the innovationHistory 222 | innovationHistory.add(new connectionHistory(from.number, to.number, connectionInnovationNumber, innoNumbers)); 223 | nextConnectionNo++; 224 | } 225 | return connectionInnovationNumber; 226 | } 227 | //---------------------------------------------------------------------------------------------------------------------------------------- 228 | 229 | //returns whether the network is fully connected or not 230 | boolean fullyConnected() { 231 | int maxConnections = 0; 232 | int[] nodesInLayers = new int[layers];//array which stored the amount of nodes in each layer 233 | 234 | //populate array 235 | for (int i =0; i< nodes.size(); i++) { 236 | nodesInLayers[nodes.get(i).layer] +=1; 237 | } 238 | 239 | //for each layer the maximum amount of connections is the number in this layer * the number of nodes infront of it 240 | //so lets add the max for each layer together and then we will get the maximum amount of connections in the network 241 | for (int i = 0; i < layers-1; i++) { 242 | int nodesInFront = 0; 243 | for (int j = i+1; j < layers; j++) {//for each layer infront of this layer 244 | nodesInFront += nodesInLayers[j];//add up nodes 245 | } 246 | 247 | maxConnections += nodesInLayers[i] * nodesInFront; 248 | } 249 | 250 | if (maxConnections == genes.size()) {//if the number of connections is equal to the max number of connections possible then it is full 251 | return true; 252 | } 253 | return false; 254 | } 255 | 256 | 257 | //------------------------------------------------------------------------------------------------------------------------------- 258 | //mutates the genome 259 | void mutate(ArrayList innovationHistory) { 260 | if (genes.size() ==0) { 261 | addConnection(innovationHistory); 262 | } 263 | 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.08) { 273 | addConnection(innovationHistory); 274 | } 275 | 276 | 277 | //1% of the time add a node 278 | float rand3 = random(1); 279 | if (rand3<0.02) { 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 | 409 | 410 | //split the nodes into layers 411 | for (int i = 0; i< layers; i++) { 412 | ArrayList temp = new ArrayList(); 413 | for (int j = 0; j< nodes.size(); j++) {//for each node 414 | if (nodes.get(j).layer == i ) {//check if it is in this layer 415 | temp.add(nodes.get(j)); //add it to this layer 416 | } 417 | } 418 | allNodes.add(temp);//add this layer to all nodes 419 | } 420 | 421 | //for each layer add the position of the node on the screen to the node posses arraylist 422 | for (int i = 0; i < layers; i++) { 423 | fill(255, 0, 0); 424 | float x = startX + (float)((i+1)*w)/(float)(layers+1.0); 425 | for (int j = 0; j< allNodes.get(i).size(); j++) {//for the position in the layer 426 | float y = startY + ((float)(j + 1.0) * h)/(float)(allNodes.get(i).size() + 1.0); 427 | nodePoses.add(new PVector(x, y)); 428 | nodeNumbers.add(allNodes.get(i).get(j).number); 429 | } 430 | } 431 | 432 | //draw connections 433 | stroke(0); 434 | strokeWeight(2); 435 | for (int i = 0; i< genes.size(); i++) { 436 | if (genes.get(i).enabled) { 437 | stroke(0); 438 | } else { 439 | stroke(100); 440 | } 441 | PVector from; 442 | PVector to; 443 | from = nodePoses.get(nodeNumbers.indexOf(genes.get(i).fromNode.number)); 444 | to = nodePoses.get(nodeNumbers.indexOf(genes.get(i).toNode.number)); 445 | if (genes.get(i).weight > 0) { 446 | stroke(255, 0, 0); 447 | } else { 448 | stroke(0, 0, 255); 449 | } 450 | strokeWeight(map(abs(genes.get(i).weight), 0, 1, 0, 5)); 451 | line(from.x, from.y, to.x, to.y); 452 | } 453 | 454 | //draw nodes last so they appear ontop of the connection lines 455 | for (int i = 0; i < nodePoses.size(); i++) { 456 | fill(255); 457 | stroke(0); 458 | strokeWeight(1); 459 | ellipse(nodePoses.get(i).x, nodePoses.get(i).y, 20, 20); 460 | textSize(10); 461 | fill(0); 462 | textAlign(CENTER, CENTER); 463 | 464 | 465 | text(nodeNumbers.get(i), nodePoses.get(i).x, nodePoses.get(i).y); 466 | } 467 | } 468 | } -------------------------------------------------------------------------------- /TemplateNeat/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 | } -------------------------------------------------------------------------------- /TemplateNeat/Player.pde: -------------------------------------------------------------------------------- 1 | class Player { 2 | float fitness; 3 | Genome brain; 4 | float[] vision = new float[8];//the input array fed into the neuralNet 5 | float[] decision = new float[4]; //the out put of the NN 6 | float unadjustedFitness; 7 | int lifespan = 0;//how long the player lived for fitness 8 | int bestScore =0;//stores the score achieved used for replay 9 | boolean dead; 10 | int score; 11 | int gen = 0; 12 | 13 | int genomeInputs = 13; 14 | int genomeOutputs = 4; 15 | //--------------------------------------------------------------------------------------------------------------------------------------------------------- 16 | //constructor 17 | 18 | Player() { 19 | brain = new Genome(genomeInputs,genomeOutputs); 20 | } 21 | 22 | //--------------------------------------------------------------------------------------------------------------------------------------------------------- 23 | void show() { 24 | //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< max) { 57 | max = decision[i]; 58 | maxIndex = i; 59 | } 60 | } 61 | 62 | //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< 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 | boolean massExtinctionEvent = false; 13 | boolean newStage = false; 14 | 15 | //------------------------------------------------------------------------------------------------------------------------------------------ 16 | //constructor 17 | Population(int size) { 18 | 19 | for (int i =0; i bestScore) { 61 | genPlayers.add(tempBest.cloneForReplay()); 62 | println("old best:", bestScore); 63 | println("new best:", tempBest.score); 64 | bestScore = tempBest.score; 65 | bestPlayer = tempBest.cloneForReplay(); 66 | } 67 | } 68 | 69 | //------------------------------------------------------------------------------------------------------------------------------------------------ 70 | //this function is called when all the players in the population are dead and a new generation needs to be made 71 | void naturalSelection() { 72 | speciate();//seperate the population into species 73 | calculateFitness();//calculate the fitness of each player 74 | sortSpecies();//sort the species to be ranked in fitness order, best first 75 | if (massExtinctionEvent) { 76 | massExtinction(); 77 | massExtinctionEvent = false; 78 | } 79 | cullSpecies();//kill off the bottom half of each species 80 | setBestPlayer();//save the best player of this gen 81 | killStaleSpecies();//remove species which haven't improved in the last 15(ish) generations 82 | killBadSpecies();//kill species which are so bad that they cant reproduce 83 | 84 | 85 | println("generation", gen, "Number of mutations", innovationHistory.size(), "species: " + species.size(), "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"); 86 | 87 | 88 | float averageSum = getAvgFitnessSum(); 89 | ArrayList children = new ArrayList();//the next generation 90 | println("Species:"); 91 | for (int j = 0; j < species.size(); j++) {//for each species 92 | 93 | println("best unadjusted fitness:", species.get(j).bestFitness); 94 | for (int i = 0; i < species.get(j).players.size(); i++) { 95 | print("player " + i, "fitness: " + species.get(j).players.get(i).fitness, "score " + species.get(j).players.get(i).score, ' '); 96 | } 97 | println(); 98 | children.add(species.get(j).champ.cloneForReplay());//add champion without any mutation 99 | 100 | int NoOfChildren = floor(species.get(j).averageFitness/averageSum * pop.size()) -1;//the number of children this species is allowed, note -1 is because the champ is already added 101 | for (int i = 0; i< NoOfChildren; i++) {//get the calculated amount of children from this species 102 | children.add(species.get(j).giveMeBaby(innovationHistory)); 103 | } 104 | } 105 | 106 | while (children.size() < pop.size()) {//if not enough babies (due to flooring the number of children to get a whole int) 107 | children.add(species.get(0).giveMeBaby(innovationHistory));//get babies from the best species 108 | } 109 | pop.clear(); 110 | pop = (ArrayList)children.clone(); //set the children as the current population 111 | gen+=1; 112 | for (int i = 0; i< pop.size(); i++) {//generate networks for each of the children 113 | pop.get(i).brain.generateNetwork(); 114 | } 115 | 116 | } 117 | 118 | //------------------------------------------------------------------------------------------------------------------------------------------ 119 | //seperate population into species based on how similar they are to the leaders of each species in the previous gen 120 | void speciate() { 121 | for (Species s : species) {//empty species 122 | s.players.clear(); 123 | } 124 | for (int i = 0; i< pop.size(); i++) {//for each player 125 | boolean speciesFound = false; 126 | for (Species s : species) {//for each species 127 | if (s.sameSpecies(pop.get(i).brain)) {//if the player is similar enough to be considered in the same species 128 | s.addToSpecies(pop.get(i));//add it to the species 129 | speciesFound = true; 130 | break; 131 | } 132 | } 133 | if (!speciesFound) {//if no species was similar enough then add a new species with this as its champion 134 | species.add(new Species(pop.get(i))); 135 | } 136 | } 137 | } 138 | //------------------------------------------------------------------------------------------------------------------------------------------ 139 | //calculates the fitness of all of the players 140 | void calculateFitness() { 141 | for (int i =1; i temp = new ArrayList(); 156 | for (int i = 0; i < species.size(); i ++) { 157 | float max = 0; 158 | int maxIndex = 0; 159 | for (int j = 0; j< species.size(); j++) { 160 | if (species.get(j).bestFitness > max) { 161 | max = species.get(j).bestFitness; 162 | maxIndex = j; 163 | } 164 | } 165 | temp.add(species.get(maxIndex)); 166 | species.remove(maxIndex); 167 | i--; 168 | } 169 | species = (ArrayList)temp.clone(); 170 | } 171 | //------------------------------------------------------------------------------------------------------------------------------------------ 172 | //kills all species which haven't improved in 15 generations 173 | void killStaleSpecies() { 174 | for (int i = 2; i< species.size(); i++) { 175 | if (species.get(i).staleness >= 15) { 176 | species.remove(i); 177 | i--; 178 | } 179 | } 180 | } 181 | //------------------------------------------------------------------------------------------------------------------------------------------ 182 | //if a species sucks so much that it wont even be allocated 1 child for the next generation then kill it now 183 | void killBadSpecies() { 184 | float averageSum = getAvgFitnessSum(); 185 | 186 | for (int i = 1; i< species.size(); i++) { 187 | if (species.get(i).averageFitness/averageSum * pop.size() < 1) {//if wont be given a single child 188 | species.remove(i);//sad 189 | i--; 190 | } 191 | } 192 | } 193 | //------------------------------------------------------------------------------------------------------------------------------------------ 194 | //returns the sum of each species average fitness 195 | float getAvgFitnessSum() { 196 | float averageSum = 0; 197 | for (Species s : species) { 198 | averageSum += s.averageFitness; 199 | } 200 | return averageSum; 201 | } 202 | 203 | //------------------------------------------------------------------------------------------------------------------------------------------ 204 | //kill the bottom half of each species 205 | void cullSpecies() { 206 | for (Species s : species) { 207 | s.cull(); //kill bottom half 208 | s.fitnessSharing();//also while we're at it lets do fitness sharing 209 | s.setAverage();//reset averages because they will have changed 210 | } 211 | } 212 | 213 | 214 | void massExtinction() { 215 | for (int i =5; i< species.size(); i++) { 216 | species.remove(i);//sad 217 | i--; 218 | } 219 | } 220 | } -------------------------------------------------------------------------------- /TemplateNeat/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; 12 | float weightDiffCoeff = 0.5; 13 | float compatibilityThreshold = 3; 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 | 38 | 39 | float largeGenomeNormaliser = g.genes.size() - 20; 40 | if (largeGenomeNormaliser<1) { 41 | largeGenomeNormaliser =1; 42 | } 43 | 44 | compatibility = (excessCoeff* excessAndDisjoint/largeGenomeNormaliser) + (weightDiffCoeff* averageWeightDiff);//compatablilty formula 45 | return (compatibilityThreshold > compatibility); 46 | } 47 | 48 | //------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ 49 | //add a player to the species 50 | void addToSpecies(Player p) { 51 | players.add(p); 52 | } 53 | 54 | //------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ 55 | //returns the number of excess and disjoint genes between the 2 input genomes 56 | //i.e. returns the number of genes which dont match 57 | float getExcessDisjoint(Genome brain1, Genome brain2) { 58 | float matching = 0.0; 59 | for (int i =0; i temp = new ArrayList(); 98 | 99 | //selection short 100 | for (int i = 0; i < players.size(); i ++) { 101 | float max = 0; 102 | int maxIndex = 0; 103 | for (int j = 0; j< players.size(); j++) { 104 | if (players.get(j).fitness > max) { 105 | max = players.get(j).fitness; 106 | maxIndex = j; 107 | } 108 | } 109 | temp.add(players.get(maxIndex)); 110 | players.remove(maxIndex); 111 | i--; 112 | } 113 | 114 | players = (ArrayList)temp.clone(); 115 | if (players.size() == 0) { 116 | print("fucking"); 117 | staleness = 200; 118 | return; 119 | } 120 | //if new best player 121 | if (players.get(0).fitness > bestFitness) { 122 | staleness = 0; 123 | bestFitness = players.get(0).fitness; 124 | rep = players.get(0).brain.clone(); 125 | champ = players.get(0).cloneForReplay(); 126 | } else {//if no new best player 127 | staleness ++; 128 | } 129 | } 130 | 131 | //------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 132 | //simple stuff 133 | void setAverage() { 134 | 135 | float sum = 0; 136 | for (int i = 0; i < players.size(); i ++) { 137 | sum += players.get(i).fitness; 138 | } 139 | averageFitness = sum/players.size(); 140 | } 141 | //------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 142 | 143 | //gets baby from the players in this species 144 | Player giveMeBaby(ArrayList innovationHistory) { 145 | Player baby; 146 | 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 147 | baby = selectPlayer().clone(); 148 | } else {//75% of the time do crossover 149 | 150 | //get 2 random(ish) parents 151 | Player parent1 = selectPlayer(); 152 | Player parent2 = selectPlayer(); 153 | 154 | //the crossover function expects the highest fitness parent to be the object and the lowest as the argument 155 | if (parent1.fitness < parent2.fitness) { 156 | baby = parent2.crossover(parent1); 157 | } else { 158 | baby = parent1.crossover(parent2); 159 | } 160 | } 161 | baby.brain.mutate(innovationHistory);//mutate that baby brain 162 | return baby; 163 | } 164 | 165 | //------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 166 | //selects a player based on it fitness 167 | Player selectPlayer() { 168 | float fitnessSum = 0; 169 | for (int i =0; i rand) { 179 | return players.get(i); 180 | } 181 | } 182 | //unreachable code to make the parser happy 183 | return players.get(0); 184 | } 185 | //------------------------------------------------------------------------------------------------------------------------------------------ 186 | //kills off bottom half of the species 187 | void cull() { 188 | if (players.size() > 2) { 189 | for (int i = players.size()/2; i= pop.genPlayers.size()) {//if at the end then return to the start and stop doing it 50 | upToGen= 0; 51 | showBestEachGen = false; 52 | } else {//if not at the end then get the next generation 53 | genPlayerTemp = pop.genPlayers.get(upToGen).cloneForReplay(); 54 | } 55 | } 56 | } else 57 | if (runThroughSpecies ) {//show all the species 58 | if (!speciesChamp.dead) {//if best player is not dead 59 | speciesChamp.look(); 60 | speciesChamp.think(); 61 | speciesChamp.update(); 62 | speciesChamp.show(); 63 | } else {//once dead 64 | upToSpecies++; 65 | if (upToSpecies >= pop.species.size()) { 66 | runThroughSpecies = false; 67 | } else { 68 | speciesChamp = pop.species.get(upToSpecies).champ.cloneForReplay(); 69 | } 70 | } 71 | } else { 72 | if (humanPlaying) {//if the user is controling the ship[ 73 | if (!humanPlayer.dead) {//if the player isnt dead then move and show the player based on input 74 | humanPlayer.look(); 75 | humanPlayer.update(); 76 | humanPlayer.show(); 77 | } else {//once done return to ai 78 | humanPlaying = false; 79 | } 80 | } else 81 | if (runBest) {// if replaying the best ever game 82 | if (!pop.bestPlayer.dead) {//if best player is not dead 83 | pop.bestPlayer.look(); 84 | pop.bestPlayer.think(); 85 | pop.bestPlayer.update(); 86 | pop.bestPlayer.show(); 87 | } else {//once dead 88 | runBest = false;//stop replaying it 89 | pop.bestPlayer = pop.bestPlayer.cloneForReplay();//reset the best player so it can play again 90 | } 91 | } else {//if just evolving normally 92 | if (!pop.done()) {//if any players are alive then update them 93 | pop.updateAlive(); 94 | } else {//all dead 95 | //genetic algorithm 96 | pop.naturalSelection(); 97 | } 98 | } 99 | } 100 | } 101 | 102 | 103 | //--------------------------------------------------------------------------------------------------------------------------------------------------------- 104 | //draws the display screen 105 | void drawToScreen() { 106 | if (!showNothing) { 107 | //pretty stuff 108 | //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< 10) { 180 | speed -= 10; 181 | frameRate(speed); 182 | println(speed); 183 | } 184 | break; 185 | case 'b'://run the best 186 | runBest = !runBest; 187 | break; 188 | case 's'://show species 189 | runThroughSpecies = !runThroughSpecies; 190 | upToSpecies = 0; 191 | speciesChamp = pop.species.get(upToSpecies).champ.cloneForReplay(); 192 | break; 193 | case 'g'://show generations 194 | showBestEachGen = !showBestEachGen; 195 | upToGen = 0; 196 | genPlayerTemp = pop.genPlayers.get(upToGen).clone(); 197 | break; 198 | case 'n'://show absolutely nothing in order to speed up computation 199 | showNothing = !showNothing; 200 | break; 201 | case 'p'://play 202 | humanPlaying = !humanPlaying; 203 | humanPlayer = new Player(); 204 | break; 205 | case CODED://any of the arrow keys 206 | switch(keyCode) { 207 | case UP://the only time up/ down / left is used is to control the player 208 | //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<= pop.species.size()) { 220 | runThroughSpecies = false; 221 | } else { 222 | speciesChamp = pop.species.get(upToSpecies).champ.cloneForReplay(); 223 | } 224 | } else 225 | if (showBestEachGen) {//if showing the best player each generation then move on to the next generation 226 | upToGen++; 227 | if (upToGen >= pop.genPlayers.size()) {//if reached the current generation then exit out of the showing generations mode 228 | showBestEachGen = false; 229 | } else { 230 | genPlayerTemp = pop.genPlayers.get(upToGen).cloneForReplay(); 231 | } 232 | } else if (humanPlaying) {//if the user is playing then move player right 233 | 234 | //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< 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 | } -------------------------------------------------------------------------------- /TemplateNeat/connectionHistory.pde: -------------------------------------------------------------------------------- 1 | class connectionHistory { 2 | int fromNode; 3 | int toNode; 4 | int innovationNumber; 5 | 6 | ArrayList innovationNumbers = new ArrayList();//the innovation Numbers from the connections of the genome which first had this mutation 7 | //this represents the genome and allows us to test if another genoeme is the same 8 | //this is before this connection was added 9 | 10 | //--------------------------------------------------------------------------------------------------------------------------------------------------------- 11 | //constructor 12 | connectionHistory(int from, int to, int inno, ArrayList innovationNos) { 13 | fromNode = from; 14 | toNode = to; 15 | innovationNumber = inno; 16 | innovationNumbers = (ArrayList)innovationNos.clone(); 17 | } 18 | //--------------------------------------------------------------------------------------------------------------------------------------------------------- 19 | //returns whether the genome matches the original genome and the connection is between the same nodes 20 | boolean matches(Genome genome, Node from, Node to) { 21 | if (genome.genes.size() == innovationNumbers.size()) { //if the number of connections are different then the genoemes aren't the same 22 | if (from.number == fromNode && to.number == toNode) { 23 | //next check if all the innovation numbers match from the genome 24 | for (int i = 0; i< genome.genes.size(); i++) { 25 | if (!innovationNumbers.contains(genome.genes.get(i).innovationNo)) { 26 | return false; 27 | } 28 | } 29 | 30 | //if reached this far then the innovationNumbers match the genes innovation numbers and the connection is between the same nodes 31 | //so it does match 32 | return true; 33 | } 34 | } 35 | return false; 36 | } 37 | } --------------------------------------------------------------------------------