diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..343642e --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ ++*.pde text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2 \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..00de0bd --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ + +data/autosave\.gz +data/*\.gz + +error\.log + +data/tempfile\.gz diff --git a/Axon.pde b/Axon.pde index 7ca19b4..91363c0 100644 --- a/Axon.pde +++ b/Axon.pde @@ -1,4 +1,4 @@ -class Axon{ +class Axon { final double MUTABILITY_MUTABILITY = 0.7; final int mutatePower = 9; final double MUTATE_MULTI; @@ -8,9 +8,12 @@ class Axon{ public Axon(double w, double m){ weight = w; mutability = m; - MUTATE_MULTI = Math.pow(0.5,mutatePower); + MUTATE_MULTI = Math.pow(0.8,mutatePower); } + public Axon copyAxon(){ + return new Axon(this.weight,this.mutability); + } public Axon mutateAxon(){ double mutabilityMutate = Math.pow(0.5,pmRan()*MUTABILITY_MUTABILITY); return new Axon(weight+r()*mutability/MUTATE_MULTI,mutability*mutabilityMutate); @@ -21,4 +24,26 @@ class Axon{ public double pmRan(){ return (Math.random()*2-1); } -} + + public void saveToJson(JsonGenerator g){ + try { + g.writeNumberField("weight", weight); + g.writeNumberField("mutability", mutability); + } catch(Exception e){ + writeToErrorLog(e); + } + } + + public void loadFromJson(JsonParser p){ + try{ + while(p.nextToken() != JsonToken.END_OBJECT){ + String fieldName = p.getCurrentName(); + p.nextToken(); + if(fieldName.equals("weight")){ this.weight = p.getDoubleValue(); } + else if(fieldName.equals("mutability")){ this.mutability = p.getDoubleValue(); } + } + } catch(Exception e){ + writeToErrorLog(e); + } + } +} \ No newline at end of file diff --git a/Brain.pde b/Brain.pde index 0598d20..a46a81a 100644 --- a/Brain.pde +++ b/Brain.pde @@ -1,15 +1,17 @@ class Brain { + float heartbeatInterval = (float)(Math.PI/12); + int timesUsed; float[][] neurons; Axon[][][] axons; int BRAIN_WIDTH = 0; int BRAIN_HEIGHT = 0; Brain(int bw, int bh, Axon[][][] templateAxons, Boolean haveNeurons, Boolean mutate){ //This is to copy a brain EXACTLY. setUpBasics(bw,bh,haveNeurons); - axons = new Axon[BRAIN_WIDTH-1][BRAIN_HEIGHT][BRAIN_HEIGHT-1]; + axons = new Axon[BRAIN_WIDTH-1][BRAIN_HEIGHT][BRAIN_HEIGHT]; if(mutate){ for(int x = 0; x < BRAIN_WIDTH-1; x++){ for(int y = 0; y < BRAIN_HEIGHT; y++){ - for(int z = 0; z < BRAIN_HEIGHT-1; z++){ + for(int z = 0; z < BRAIN_HEIGHT; z++){ axons[x][y][z] = templateAxons[x][y][z].mutateAxon(); } } @@ -17,8 +19,8 @@ class Brain { }else{ for(int x = 0; x < BRAIN_WIDTH-1; x++){ for(int y = 0; y < BRAIN_HEIGHT; y++){ - for(int z = 0; z < BRAIN_HEIGHT-1; z++){ - axons[x][y][z] = new Axon(templateAxons[x][y][z].weight,templateAxons[x][y][z].mutability); + for(int z = 0; z < BRAIN_HEIGHT; z++){ + axons[x][y][z] = templateAxons[x][y][z].copyAxon(); } } } @@ -26,12 +28,12 @@ class Brain { } Brain(int bw, int bh){ setUpBasics(bw,bh,false); - axons = new Axon[BRAIN_WIDTH-1][BRAIN_HEIGHT][BRAIN_HEIGHT-1]; + axons = new Axon[BRAIN_WIDTH-1][BRAIN_HEIGHT][BRAIN_HEIGHT]; for(int x = 0; x < BRAIN_WIDTH-1; x++){ for(int y = 0; y < BRAIN_HEIGHT; y++){ - for(int z = 0; z < BRAIN_HEIGHT-1; z++){ + for(int z = 0; z < BRAIN_HEIGHT; z++){ double startingWeight = 0; - if(y == BRAIN_HEIGHT-1){ + if(y == BRAIN_HEIGHT - 1){ startingWeight = (Math.random()*2-1)*STARTING_AXON_VARIABILITY; } axons[x][y][z] = new Axon(startingWeight,AXON_START_MUTABILITY); @@ -42,10 +44,10 @@ class Brain { void changeBrainStructure(int bw, int bh, int rowInsertionIndex, int rowRemovalIndex){ setUpBasics(bw,bh,false); Axon[][][] oldAxons = axons; - axons = new Axon[BRAIN_WIDTH-1][BRAIN_HEIGHT][BRAIN_HEIGHT-1]; + axons = new Axon[BRAIN_WIDTH-1][BRAIN_HEIGHT][BRAIN_HEIGHT]; for(int x = 0; x < BRAIN_WIDTH-1; x++){ for(int y = 0; y < BRAIN_HEIGHT; y++){ - for(int z = 0; z < BRAIN_HEIGHT-1; z++){ + for(int z = 0; z < BRAIN_HEIGHT; z++){ if(y == rowInsertionIndex || z == rowInsertionIndex){ double startingWeight = 0; if(y == BRAIN_HEIGHT-1 || true){ @@ -66,6 +68,7 @@ class Brain { } } void setUpBasics(int bw, int bh, Boolean haveNeurons){ + timesUsed = 0; BRAIN_WIDTH = bw; BRAIN_HEIGHT = bh; if(haveNeurons){ @@ -80,50 +83,106 @@ class Brain { } } }else{ - neurons = null; + neurons = new float[BRAIN_WIDTH][BRAIN_HEIGHT];//null; } } public void useBrain(Creature owner){ - ArrayList n = owner.n; - ArrayList m = owner.m; + final ArrayList n = owner.n; + final ArrayList m = owner.m; for(int i = 0; i < n.size(); i++){ Node ni = n.get(i); - neurons[0][i] = dist(ni.x, ni.y, ni.z, foodX, foodY, foodZ); + neurons[0][i] = dist(ni.x, ni.y, ni.z, owner.foodX, owner.foodY, owner.foodZ); } + for(int i = 0; i < m.size(); i++){ Muscle am = m.get(i); Node ni1 = n.get(am.c1); Node ni2 = n.get(am.c2); neurons[0][n.size()+i] = dist(ni1.x, ni1.y, ni1.z, ni2.x, ni2.y, ni2.z)/am.len; } + + neurons[0][n.size()+m.size()] = sin(heartbeatInterval*timesUsed); + neurons[0][n.size()+m.size()+1] = cos(2*heartbeatInterval*timesUsed); + timesUsed += 1; + for(int x = 1; x < BRAIN_WIDTH; x++){ - for(int y = 0; y < BRAIN_HEIGHT-1; y++){ + for(int y = 0; y < BRAIN_HEIGHT; y++){ float total = 0; for(int input = 0; input < BRAIN_HEIGHT; input++){ total += neurons[x-1][input]*axons[x-1][input][y].weight; } - if(x == BRAIN_WIDTH-1){ - neurons[x][y] = total; - }else{ - neurons[x][y] = sigmoid(total); + if(x == BRAIN_WIDTH - 1){ + neurons[x][y] = sigmoidapprox(total); + } else { + neurons[x][y] = linear(total); } } } + for(int i = 0; i < n.size(); i++){ + n.get(i).brainOutput = neurons[BRAIN_WIDTH-1][i]; + } for(int i = 0; i < m.size(); i++){ m.get(i).brainOutput = neurons[BRAIN_WIDTH-1][n.size()+i]; } } + public float linear(float input){ + if(input >= -5 || input <= 5){ + return input/5; + } + else if(input < -5){ + return -1; + } + else{ + return 1; + } + } public float sigmoid(float input){ - return 1.0/(1.0+pow(2.71828182846,-input)); + return -1.0+(2.0/(1.0+pow(2.71828182846,-input))); + // Sigmoid centered on 0 and giving response between -1 and +1 + } + public float sigmoidapprox(float input){ + if(input >= -1.1779 && input <= 1.1779){ + return 0.5*input; + } else if(input >= -2.7537 && input < -1.1779){ + return 0.210*input-0.3416; + } else if(input > 1.1779 && input <= 2.7537){ + return 0.210*input+0.3416; + } else if(input >= -5 && input < -2.7537){ + return 0.0354*input-0.8224; + } else if(input > 3 && input <= 5){ + return 0.0354*input+0.8224; + } else if(input < -5){ + return 0; + } else { + return 1; + } } Brain getUsableCopyOfBrain(){ - return new Brain(BRAIN_WIDTH,BRAIN_HEIGHT,axons,true,false); + return new Brain(BRAIN_WIDTH,BRAIN_HEIGHT,axons.clone(),true,false); } Brain copyBrain(){ - return new Brain(BRAIN_WIDTH,BRAIN_HEIGHT,axons,false,false); + return new Brain(BRAIN_WIDTH,BRAIN_HEIGHT,axons.clone(),false,false); } Brain copyMutatedBrain(){ - return new Brain(BRAIN_WIDTH,BRAIN_HEIGHT,axons,false,true); + return new Brain(BRAIN_WIDTH,BRAIN_HEIGHT,axons.clone(),false,true); + } + Brain copyExpandedBrain(){ + Axon[][][] extaxons = new Axon[BRAIN_WIDTH][BRAIN_HEIGHT][BRAIN_HEIGHT]; + for(int x = 0; x < BRAIN_WIDTH; x++){ + for(int y = 0; y < BRAIN_HEIGHT; y++){ + for(int z = 0; z < BRAIN_HEIGHT; z++){ + if(x == 0){ + if(y == z){ + extaxons[x][y][z] = new Axon(5.0,AXON_START_MUTABILITY); + } else { + extaxons[x][y][z] = new Axon(0.0,AXON_START_MUTABILITY); + } + } + else{ extaxons[x][y][z] = axons[x-1][y][z].copyAxon(); } + } + } + } + return new Brain(BRAIN_WIDTH+1,BRAIN_HEIGHT,extaxons.clone(),false,false); } public void drawBrain(float scaleUp, Creature owner){ ArrayList n = owner.n; @@ -153,7 +212,7 @@ class Brain { } for(int x = 0; x < BRAIN_WIDTH-1; x++){ for(int y = 0; y < BRAIN_HEIGHT; y++){ - for(int z = 0; z < BRAIN_HEIGHT-1; z++){ + for(int z = 0; z < BRAIN_HEIGHT; z++){ drawAxon(x,y,x+1,z,scaleUp); } } @@ -177,4 +236,91 @@ class Brain { return color(255,255,255); } } -} + + public void saveToJson(JsonGenerator g){ + try{ + g.writeNumberField("width", BRAIN_WIDTH); + g.writeNumberField("height", BRAIN_HEIGHT); + if(axons != null){ + g.writeArrayFieldStart("axons"); + for (int i = 0; i < axons.length; i++){ + g.writeStartArray(); + for(int j = 0; j < axons[i].length; j++){ + g.writeStartArray(); + for(int k = 0; k < axons[i][j].length; k++){ + g.writeStartObject(); axons[i][j][k].saveToJson(g); g.writeEndObject(); + } + g.writeEndArray(); + } + g.writeEndArray(); + } + g.writeEndArray(); + } + if(neurons != null){ + g.writeArrayFieldStart("neurons"); + for(int i = 0; i < neurons.length; i++){ + g.writeStartArray(); + for(int j = 0; j < neurons[i].length; j++){ + g.writeNumber(neurons[i][j]); + } + g.writeEndArray(); + } + g.writeEndArray(); + } + } catch(Exception e){ + writeToErrorLog(e); + } + } + + public void loadFromJson(JsonParser p){ + try{ + while(p.nextToken() != JsonToken.END_OBJECT){ + String fieldName = p.getCurrentName(); + JsonToken token = p.nextToken(); + if(fieldName.equals("width")){ this.BRAIN_WIDTH = p.getIntValue(); } + else if(fieldName.equals("height")){ this.BRAIN_HEIGHT = p.getIntValue(); } + else if(fieldName.equals("axons")){ + if (token != JsonToken.START_ARRAY) { throw new IOException("Expected Array"); } + int i = 0; + axons = new Axon[BRAIN_WIDTH-1][BRAIN_HEIGHT][BRAIN_HEIGHT]; + while((token = p.nextToken()) != JsonToken.END_ARRAY){ + if (token == JsonToken.START_ARRAY){ + int j = 0; + while((token = p.nextToken()) != JsonToken.END_ARRAY){ + if (token == JsonToken.START_ARRAY){ + int k = 0; + while((token = p.nextToken()) != JsonToken.END_ARRAY){ + if (token != JsonToken.START_OBJECT) { throw new Exception("Expected Object"); } + axons[i][j][k] = new Axon(0,0); + axons[i][j][k].loadFromJson(p); + k += 1; + } + } + j += 1; + } + } + i += 1; + } + } + else if(fieldName.equals("neurons")){ + if (token != JsonToken.START_ARRAY) { throw new IOException("Expected Array"); } + int i = 0; + neurons = new float[BRAIN_WIDTH][BRAIN_HEIGHT]; + while((token = p.nextToken()) != JsonToken.END_ARRAY){ + if (token == JsonToken.START_ARRAY){ + int j = 0; + while(p.nextToken() != JsonToken.END_ARRAY){ + neurons[i][j] = p.getFloatValue(); + j += 1; + } + } + i += 1; + } + } + } + } catch(Exception e){ + writeToErrorLog(e); + } + } + +} \ No newline at end of file diff --git a/Creature.pde b/Creature.pde index 8857a87..73d48b5 100644 --- a/Creature.pde +++ b/Creature.pde @@ -4,79 +4,124 @@ class Creature { float d; int id; boolean alive; - float creatureTimer; float mutability; Brain brain; int[] name; - float[][] foodPositions = new float[100][3]; - Creature(int[] tname, int tid, ArrayList tn, ArrayList tm, float td, boolean talive, float tct, float tmut, Brain newBrain, float[][] tfoodpos) { - id = tid; - m = tm; - n = tn; - d = td; - alive = talive; - creatureTimer = tct; - mutability = tmut; + float[][] foodPositions = new float[maxChomp][3]; + float foodAngle = 0.0; + float foodX = 0; + float foodY = 0; + float foodZ = 0; + int chomps = 0; + float timePerChomp = 0; + float averageX = 0; + float averageY = 0; + float averageZ = 0; + float energy = baselineEnergy; + float startingFoodDistance = 9999; + int lastChompTime = 0; + + Creature(int[] tname, int tid, ArrayList tn, ArrayList tm, float tmut, Brain newBrain, float[][] tfoodpos) { + this.id = tid; + this.m = tm; + this.n = tn; + this.d = 0; + this.alive = true; + this.mutability = tmut; + this.initParameters(); if(newBrain != null){ - brain = newBrain; + this.brain = newBrain; }else{ - brain = new Brain(BRAIN_WIDTH, getBrainHeight()); + this.brain = new Brain(BRAIN_WIDTH, this.getBrainHeight()); } if(tname == null){ - name = getNewCreatureName(); + this.name = getNewCreatureName(); }else{ - name = new int[2]; - name[0] = tname[0]; - name[1] = tname[1]; + this.name = new int[2]; + this.name[0] = tname[0]; + this.name[1] = tname[1]; } if(tfoodpos == null){ - for(int i = 0; i < 100; i++){ - foodPositions[i][0] = random(-foodAngleChange,foodAngleChange); - foodPositions[i][1] = random(-1.2,-0.55); - foodPositions[i][2] = random(0,1); - } - }else{ - for(int i = 0; i < 100; i++){ - foodPositions[i][0] = tfoodpos[i][0]; - foodPositions[i][1] = tfoodpos[i][1]; - foodPositions[i][2] = tfoodpos[i][2]; + for(int i = 0; i < maxChomp; i++){ + if(i < angleMultiplier.length){ + this.foodPositions[i][0] = angleMultiplier[i]*random(-foodAngleChange,foodAngleChange); + } else { + this.foodPositions[i][0] = random(-foodAngleChange,foodAngleChange); + } + this.foodPositions[i][1] = random(-1.2,-0.55); + this.foodPositions[i][2] = random(0,1); } + } else { + this.foodPositions = tfoodpos.clone(); } } + void initParameters(){ + this.chomps = 0; + this.lastChompTime = 0; + this.timePerChomp = 0; + } int getBrainHeight(){ - return n.size()+m.size()+1; + return this.n.size()+this.m.size()+3; } void changeBrainStructure(int rowInsertionIndex, int rowRemovalIndex){ - brain.changeBrainStructure(BRAIN_WIDTH, getBrainHeight(), rowInsertionIndex,rowRemovalIndex); - } - public float sigmoid(float input){ - return 1.0/(1.0+pow(2.71828182846,-input)); + this.brain.changeBrainStructure(this.brain.BRAIN_WIDTH, this.getBrainHeight(), rowInsertionIndex, rowRemovalIndex); } - Creature modified(int id) { + Creature modified(int id, float mutationFactor) { + float modMut; + if(mutationFactor == 1.0){ mutationFactor = mutability; modMut = mutability; } + else{ modMut = mutability; } ArrayList newN = new ArrayList(0); ArrayList newM = new ArrayList(0); - for (int i = 0; i < n.size(); i++) { - newN.add(n.get(i).modifyNode(mutability,n.size())); + for (int i = 0; i < this.n.size(); i++) { + newN.add(this.n.get(i).modifyNode(modMut,this.n.size())); } - for (int i = 0; i < m.size(); i++) { - newM.add(m.get(i).modifyMuscle(n.size(), mutability)); + for (int i = 0; i < this.m.size(); i++) { + newM.add(this.m.get(i).modifyMuscle(this.n.size(), modMut)); + } + + boolean bigMutAddNode = false, bigMutRemoveNode = false, bigMutAddMuscle = false, bigMutRemoveMuscle = false; + boolean bigMutExpandBrain = false; + if (random(0, 1) < bigMutationChance*mutationFactor || this.n.size() <= 2){ bigMutAddNode = true; } + if (random(0, 1) < bigMutationChance*mutationFactor) { bigMutAddMuscle = true; } + if (random(0, 1) < bigMutationChance*mutationFactor && this.n.size() >= 5) { bigMutRemoveNode = true; } + if (random(0, 1) < bigMutationChance*mutationFactor && this.m.size() >= 2) { bigMutRemoveMuscle = true; } + if (random(0, 1) < bigMutationChance*mutationFactor*(5-this.brain.BRAIN_WIDTH)/2 && this.brain.BRAIN_WIDTH < 5) { + bigMutExpandBrain = true; + bigMutAddNode = false; bigMutRemoveNode = false; bigMutAddMuscle = false; bigMutRemoveMuscle = false; } + int[] newName = new int[2]; - newName[0] = name[0]; - newName[1] = CREATURES_PER_PATRON[name[0]]; - CREATURES_PER_PATRON[name[0]]++; - Creature modifiedCreature = new Creature(newName, id, - newN, newM, 0, true, creatureTimer+r()*16*mutability, min(mutability*random(0.8, 1.25), 2), brain.copyMutatedBrain(),null); - if (random(0, 1) < bigMutationChance*mutability || n.size() <= 2) { //Add a node + if(bigMutAddNode || bigMutRemoveNode || bigMutAddMuscle || bigMutRemoveMuscle || bigMutExpandBrain){ + newName = getNewCreatureName(); + } else { + newName[0] = name[0]; + newName[1] = CREATURES_PER_PATRON[name[0]]; + CREATURES_PER_PATRON[name[0]]++; + } + + Brain tmpBrain; + if(bigMutExpandBrain){ + tmpBrain = this.brain.copyExpandedBrain(); + } else { + tmpBrain = this.brain.copyMutatedBrain(); + } + float newMut; + if(mutationFactor > 1){ + newMut = max(min(mutability*random((float)0.8, (float)1.25), 2), (float)0.2); + } else { + newMut = min(mutability*random((float)0.8, (float)1.25), 2); + } + Creature modifiedCreature = new Creature(newName, id, newN, newM, newMut, tmpBrain, null); + if (bigMutAddNode) { //Add a node modifiedCreature.addRandomNode(); } - if (random(0, 1) < bigMutationChance*mutability) { //Add a muscle + if (bigMutAddMuscle) { //Add a muscle modifiedCreature.addRandomMuscle(-1, -1); } - if (random(0, 1) < bigMutationChance*mutability && modifiedCreature.n.size() >= 5) { //Remove a node + if (bigMutRemoveNode) { //Remove a node modifiedCreature.removeRandomNode(); } - if (random(0, 1) < bigMutationChance*mutability && modifiedCreature.m.size() >= 2) { //Remove a muscle + if (bigMutRemoveMuscle) { //Remove a muscle modifiedCreature.removeRandomMuscle(); } modifiedCreature.checkForOverlap(); @@ -88,15 +133,15 @@ class Creature { void moveToCenter(){ float avX = 0; float avZ = 0; - for(int i = 0; i < n.size(); i++) { - avX += n.get(i).x; - avZ += n.get(i).z; + for(int i = 0; i < this.n.size(); i++) { + avX += this.n.get(i).x; + avZ += this.n.get(i).z; } - avX /= n.size(); - avZ /= n.size(); - for(int i = 0; i < n.size(); i++) { - n.get(i).x -= avX; - n.get(i).z -= avZ; + avX /= this.n.size(); + avZ /= this.n.size(); + for(int i = 0; i < this.n.size(); i++) { + this.n.get(i).x -= avX; + this.n.get(i).z -= avZ; } } void checkForOverlap() { @@ -118,25 +163,25 @@ class Creature { int b = bads.get(i)+0; if (b < m.size()) { m.remove(b); - changeBrainStructure(-1,n.size()+b); + changeBrainStructure(-1,this.n.size()+b); } } } void checkForLoneNodes() { - if (n.size() >= 3) { - for (int i = 0; i < n.size(); i++) { + if (this.n.size() >= 3) { + for (int i = 0; i < this.n.size(); i++) { int connections = 0; int connectedTo = -1; - for (int j = 0; j < m.size(); j++) { - if (m.get(j).c1 == i || m.get(j).c2 == i) { + for (int j = 0; j < this.m.size(); j++) { + if (this.m.get(j).c1 == i || this.m.get(j).c2 == i) { connections++; connectedTo = j; } } if (connections <= 1) { - int newConnectionNode = floor(random(0, n.size())); + int newConnectionNode = floor(random(0, this.n.size())); while (newConnectionNode == i || newConnectionNode == connectedTo) { - newConnectionNode = floor(random(0, n.size())); + newConnectionNode = floor(random(0, this.n.size())); } addRandomMuscle(i, newConnectionNode); } @@ -144,96 +189,101 @@ class Creature { } } void addRandomNode() { - int parentNode = floor(random(0, n.size())); + int parentNode = floor(random(0, this.n.size())); float ang1 = random(0, 2*PI); float distance = sqrt(random(0, 1)); float vertical = random(-1,1); - float x = n.get(parentNode).x+cos(ang1)*0.5*distance; - float y = n.get(parentNode).y+vertical*0.5*distance; - float z = n.get(parentNode).y+sin(ang1)*0.5*distance; + float x = this.n.get(parentNode).x+cos(ang1)*0.5*distance; + float y = this.n.get(parentNode).y+vertical*0.5*distance; + float z = this.n.get(parentNode).y+sin(ang1)*0.5*distance; int newNodeCount = n.size()+1; - n.add(new Node(x, y, z, 0, 0, 0, 0.4, random(0, 1))); - changeBrainStructure(n.size()-1,-1); + this.n.add(new Node(x, y, z, 0, 0, 0, 0.4, random(0, 1))); + changeBrainStructure(this.n.size()-1,-1); int nextClosestNode = 0; float record = 100000; - for (int i = 0; i < n.size()-1; i++) { + for (int i = 0; i < this.n.size()-1; i++) { if (i != parentNode) { - float dx = n.get(i).x-x; - float dy = n.get(i).y-y; + float dx = this.n.get(i).x-x; + float dy = this.n.get(i).y-y; if (sqrt(dx*dx+dy*dy) < record) { record = sqrt(dx*dx+dy*dy); nextClosestNode = i; } } } - addRandomMuscle(parentNode, n.size()-1); - addRandomMuscle(nextClosestNode, n.size()-1); + addRandomMuscle(parentNode, this.n.size()-1); + addRandomMuscle(nextClosestNode, this.n.size()-1); } void addRandomMuscle(int tc1, int tc2) { if (tc1 == -1) { - tc1 = int(random(0, n.size())); + tc1 = int(random(0, this.n.size())); tc2 = tc1; - while (tc2 == tc1 && n.size () >= 2) { - tc2 = int(random(0, n.size())); + while (tc2 == tc1 && this.n.size () >= 2) { + tc2 = int(random(0, this.n.size())); } } float len = random(0.5, 1.5); if (tc1 != -1) { - len = dist(n.get(tc1).x, n.get(tc1).y, n.get(tc2).x, n.get(tc2).y); + len = dist(this.n.get(tc1).x, this.n.get(tc1).y, this.n.get(tc2).x, this.n.get(tc2).y); } m.add(new Muscle(tc1, tc2, len, random(0.02, 0.08))); changeBrainStructure(getBrainHeight()-2,-1); } void removeRandomNode() { - int choice = floor(random(0, n.size())); - n.remove(choice); + int choice = floor(random(0, this.n.size())); + this.n.remove(choice); changeBrainStructure(-1,choice); int i = 0; - while (i < m.size ()) { - if (m.get(i).c1 == choice || m.get(i).c2 == choice) { - m.remove(i); - changeBrainStructure(-1,n.size()+i); + while (i < this.m.size ()) { + if (this.m.get(i).c1 == choice || this.m.get(i).c2 == choice) { + this.m.remove(i); + changeBrainStructure(-1,this.n.size()+i); }else{ i++; } } for (int j = 0; j < m.size(); j++) { - if (m.get(j).c1 >= choice) { - m.get(j).c1--; + if (this.m.get(j).c1 >= choice) { + this.m.get(j).c1--; } if (m.get(j).c2 >= choice) { - m.get(j).c2--; + this.m.get(j).c2--; } } } void removeRandomMuscle() { int choice = floor(random(0, m.size())); m.remove(choice); - changeBrainStructure(-1,n.size()+choice); + changeBrainStructure(-1,this.n.size()+choice); } Creature copyCreature(int newID, Boolean changeFood, Boolean withUsableBrain) { - ArrayList n2 = new ArrayList(0); - ArrayList m2 = new ArrayList(0); - for (int i = 0; i < n.size(); i++) { + final ArrayList n2 = new ArrayList(0); + final ArrayList m2 = new ArrayList(0); + for (int i = 0; i < this.n.size(); i++) { n2.add(this.n.get(i).copyNode()); } - for (int i = 0; i < m.size(); i++) { + for (int i = 0; i < this.m.size(); i++) { m2.add(this.m.get(i).copyMuscle()); } if (newID == -1) { - newID = id; + newID = this.id; } float[][] newFoodPositions = null; if(!changeFood){ - newFoodPositions = foodPositions; + newFoodPositions = foodPositions.clone(); } - Brain newBrain = brain.copyBrain(); + Brain newBrain = this.brain.copyBrain(); if(withUsableBrain){ - newBrain = brain.getUsableCopyOfBrain(); - } - return new Creature(name, newID, n2, m2, d, alive, creatureTimer, mutability,newBrain,newFoodPositions); + newBrain = this.brain.getUsableCopyOfBrain(); + } + Creature copiedCreature = new Creature(this.name, newID, n2, m2, this.mutability, newBrain, newFoodPositions); + copiedCreature.d = this.d; + copiedCreature.chomps = this.chomps; + copiedCreature.timePerChomp = this.timePerChomp; + copiedCreature.alive = this.alive; + return copiedCreature; } void drawCreature(PGraphics img, Boolean putInFrontOfBack) { if(putInFrontOfBack && false){ @@ -247,45 +297,208 @@ class Creature { img.pushMatrix(); img.translate(0,0,-minZ*scaleToFixBug); } - for (int i = 0; i < m.size(); i++) { - m.get(i).drawMuscle(n, img); + for (int i = 0; i < this.m.size(); i++) { + this.m.get(i).drawMuscle(this.n, img); } - for (int i = 0; i < n.size(); i++) { - n.get(i).drawNode(img); + for (int i = 0; i < this.n.size(); i++) { + this.n.get(i).drawNode(img); } if(putInFrontOfBack && false){ img.popMatrix(); } } + void setAverages() { + this.averageX = 0; + this.averageY = 0; + this.averageZ = 0; + for (int i = 0; i < this.n.size(); i++) { + Node ni = this.n.get(i); + this.averageX += ni.x; + this.averageY += ni.y; + this.averageZ += ni.z; + } + this.averageX = this.averageX/this.n.size(); + this.averageY = this.averageY/this.n.size(); + this.averageZ = this.averageZ/this.n.size(); + } + void calculateNextFoodLocation() { + if(this.chomps >= maxChomp){ return; } + this.setAverages(); + this.foodAngle += this.foodPositions[chomps][0]; + float sinA = sin(this.foodAngle); + float cosA = cos(this.foodAngle); + float furthestNodeForward = 0; + for(int i = 0; i < this.n.size(); i++){ + Node ni = this.n.get(i); + float newX = (ni.x-this.averageX)*cosA-(ni.z-this.averageZ)*sinA; + if(newX >= furthestNodeForward){ + furthestNodeForward = newX; + } + } + this.d = MIN_FOOD_DISTANCE+(MAX_FOOD_DISTANCE-MIN_FOOD_DISTANCE)*this.foodPositions[chomps][2]; + this.foodX = this.foodX+cos(foodAngle)*(furthestNodeForward+d); + this.foodZ = this.foodZ+sin(foodAngle)*(furthestNodeForward+d); + this.foodY = this.foodPositions[chomps][1]; + this.startingFoodDistance = this.getCurrentFoodDistance(); + } + float getCurrentFoodDistance() { + float closestDist = 9999; + for(int i = 0; i < this.n.size(); i++){ + Node N = this.n.get(i); + float distFromFood = dist(N.x,N.y,N.z,this.foodX,this.foodY,this.foodZ)-0.4; + if(distFromFood < closestDist){ + closestDist = distFromFood; + } + } + return closestDist; + } + float getFitness(){ + Boolean hasNodeOffGround = false; + for(int i = 0; i < this.n.size(); i++){ + if(this.n.get(i).y <= -0.2001){ + hasNodeOffGround = true; + } + } + if(hasNodeOffGround){ + float withinChomp = max(1.0-this.getCurrentFoodDistance()/this.startingFoodDistance,0); + if(withinChomp >= 1){ withinChomp = 0.99; } + float chompfit; + if(this.chomps >= maxChomp){ chompfit = this.chomps; } + else { chompfit = this.chomps+withinChomp; } + + float speedfit = 0; + if(this.chomps > 0){ + timePerChomp = float(lastChompTime/frames)/float(this.chomps); + if(this.chomps > angleMultiplier.length){ + if(lastChompTime/frames <= 1){ speedfit = 0; } + else if(timePerChomp < 1 && lastChompTime > 0){ speedfit = timePerChompWeight; } + else if(timePerChomp > timePerChompWeight/timePerChompSlope+1){ speedfit = 0; } + else if(lastChompTime == 0 || timePerChomp == 0){ speedfit = 0; } + else{ speedfit = -timePerChompSlope*(timePerChomp-(timePerChompWeight/timePerChompSlope)-1); } + } + } + + float loss = (this.brain.BRAIN_WIDTH - 2)*lossPerLayer; // loss function for brain width + if(this.chomps >= maxChomp/2){ loss = 0; } + + return 100*(chompfit+speedfit-loss)/(maxChomp+timePerChompWeight); + }else{ + return 0; + } + } void toStableConfiguration() { for (int j = 0; j < 200; j++) { - for (int i = 0; i < m.size(); i++) { - m.get(i).applyForce(i, n); + for (int i = 0; i < this.m.size(); i++) { + this.m.get(i).applyForce(i, this.n, this); } - for (int i = 0; i < n.size(); i++) { - n.get(i).applyForces(); + for (int i = 0; i < this.n.size(); i++) { + this.n.get(i).applyForces(); } } - for (int i = 0; i < n.size(); i++) { - n.get(i).vx = 0; - n.get(i).vy = 0; + for (int i = 0; i < this.n.size(); i++) { + this.n.get(i).vx = 0; + this.n.get(i).vy = 0; } } - void simulate() { - brain.useBrain(this); - for (int i = 0; i < m.size(); i++) { - m.get(i).applyForce(i, n); - } - for (int i = 0; i < n.size(); i++) { - Node ni = n.get(i); + boolean simulate() { + boolean hasEaten = false; + this.brain.useBrain(this); + for (int i = 0; i < this.m.size(); i++) { + this.m.get(i).applyForce(i, this.n, this); + } + for (int i = 0; i < this.n.size(); i++) { + Node ni = this.n.get(i); ni.applyGravity(); ni.applyForces(); ni.hitWalls((i >= 2)); - float distFromFood = dist(ni.x,ni.y,ni.z,foodX,foodY,foodZ); + float distFromFood = dist(ni.x,ni.y,ni.z,this.foodX,this.foodY,this.foodZ); if(distFromFood <= 0.4){ - chomps++; - setFoodLocation(); + this.chomps++; + lastChompTime = this.brain.timesUsed; + hasEaten = true; + this.calculateNextFoodLocation(); } } + return hasEaten; + } + + public void saveToJson(JsonGenerator g, int overwriteId){ + try{ + g.writeNumberField("d", d); + if(overwriteId == -1) { g.writeNumberField("id", id); } + else { g.writeNumberField("id", overwriteId); } + g.writeBooleanField("alive", alive); + g.writeNumberField("mutability", mutability); + g.writeNumberField("chomps", chomps); + g.writeNumberField("timePerChomp", timePerChomp); + g.writeArrayFieldStart("name");g.writeNumber(name[0]);g.writeNumber(name[1]);g.writeEndArray(); + if(n != null){ + g.writeArrayFieldStart("nodes"); + for(int i = 0 ; i < this.n.size(); i++){ + g.writeStartObject(); this.n.get(i).saveToJson(g); g.writeEndObject(); + } + g.writeEndArray(); + } + if(m != null){ + g.writeArrayFieldStart("muscles"); + for(int i = 0 ; i < this.m.size(); i++){ + g.writeStartObject(); this.m.get(i).saveToJson(g); g.writeEndObject(); + } + g.writeEndArray(); + } + g.writeObjectFieldStart("brain"); this.brain.saveToJson(g); g.writeEndObject(); + } catch(Exception e){ + writeToErrorLog(e); + } + + } + public void loadFromJson(JsonParser p){ + try{ + while(p.nextToken() != JsonToken.END_OBJECT){ + String fieldName = p.getCurrentName(); + JsonToken token = p.nextToken(); + if(fieldName.equals("d")){ d = p.getFloatValue(); } + if(fieldName.equals("chomps")){ chomps = p.getIntValue(); } + if(fieldName.equals("timePerChomp")){ timePerChomp = p.getFloatValue(); } + else if(fieldName.equals("id")){ id = p.getIntValue(); } + else if(fieldName.equals("alive")){ alive = p.getBooleanValue(); } + else if(fieldName.equals("mutability")){ mutability = p.getFloatValue(); } + else if(fieldName.equals("name")){ + if (token != JsonToken.START_ARRAY) { throw new IOException("Expected Array"); } + int i = 0; + while(p.nextToken() != JsonToken.END_ARRAY){ + name[i] = p.getIntValue(); + i += 1; + } + } + else if(fieldName.equals("nodes")){ + this.n = new ArrayList(); + if (token != JsonToken.START_ARRAY) { throw new IOException("Expected Array"); } + while((token = p.nextToken()) != JsonToken.END_ARRAY){ + if (token == JsonToken.START_OBJECT){ + Node node = new Node(0, 0, 0, 0, 0, 0, 0, 0); + node.loadFromJson(p); + this.n.add(node); + } + } + } + else if(fieldName.equals("muscles")){ + this.m = new ArrayList(); + if (token != JsonToken.START_ARRAY) { throw new IOException("Expected Array"); } + while((token = p.nextToken()) != JsonToken.END_ARRAY){ + if (token == JsonToken.START_OBJECT){ + Muscle muscle = new Muscle(0, 0, 0, 0); + muscle.loadFromJson(p); + this.m.add(muscle); + } + } + } + else if(fieldName.equals("brain")){ + if (token != JsonToken.START_OBJECT) { throw new IOException("Expected Object"); } + brain = new Brain(1, 1); + brain.loadFromJson(p); + } + } + } catch(Exception e) { writeToErrorLog(e); } } -} +} \ No newline at end of file diff --git a/Muscle.pde b/Muscle.pde index 476e0e2..2d16f59 100644 --- a/Muscle.pde +++ b/Muscle.pde @@ -5,43 +5,43 @@ class Muscle { float previousTarget; float brainOutput; Muscle(int tc1, int tc2, float tlen, float trigidity) { - previousTarget = len = tlen; - c1 = tc1; - c2 = tc2; - rigidity = trigidity; - brainOutput = 1; + this.previousTarget = len = tlen; + this.c1 = tc1; + this.c2 = tc2; + this.rigidity = trigidity; + this.brainOutput = 0; } - void applyForce(int i, ArrayList n) { + void applyForce(int i, ArrayList n, Creature owner) { float target = previousTarget; - if(energyDirection == 1 || energy >= 0.0001){ - target = len*toMuscleUsable(brainOutput); + if(energyDirection == 1 || owner.energy >= 0.0001){ + target = this.len*toMuscleUsable(); }else{ - target = len; + target = this.len; } - Node ni1 = n.get(c1); - Node ni2 = n.get(c2); + Node ni1 = n.get(this.c1); + Node ni2 = n.get(this.c2); float distance = dist(ni1.x, ni1.y, ni1.z, ni2.x, ni2.y, ni2.z); if(distance >= 0.0001){ float normX = (ni1.x-ni2.x)/distance; float normY = (ni1.y-ni2.y)/distance; float normZ = (ni1.z-ni2.z)/distance; - force = min(max(1-(distance/target), -1.7), 1.7); + float force = min(max(1-(distance/target), -1.7), 1.7); ni1.vx += normX*force*rigidity/ni1.m; ni1.vy += normY*force*rigidity/ni1.m; ni1.vz += normZ*force*rigidity/ni1.m; ni2.vx -= normX*force*rigidity/ni2.m; ni2.vy -= normY*force*rigidity/ni2.m; ni2.vz -= normZ*force*rigidity/ni2.m; - energy = max(energy+energyDirection*abs(previousTarget-target)*rigidity*energyUnit,0); - previousTarget = target; + owner.energy = max(owner.energy+energyDirection*abs(previousTarget-target)*rigidity*energyUnit,0); + this.previousTarget = target; } } Muscle copyMuscle() { - return new Muscle(c1, c2, len, rigidity); + return new Muscle(this.c1, this.c2, this.len, this.rigidity); } Muscle modifyMuscle(int nodeNum, float mutability) { - int newc1 = c1; - int newc2 = c2; + int newc1 = this.c1; + int newc2 = this.c2; if(random(0,1) n, PGraphics img) { - Node ni1 = n.get(c1); - Node ni2 = n.get(c2); - float w = toMuscleUsable(brainOutput)*0.15; + Node ni1 = n.get(this.c1); + Node ni2 = n.get(this.c2); + float w = toMuscleUsable()*0.15; img.strokeWeight(w*scaleToFixBug); float brownness = rigidity*13; img.stroke(255-180*brownness, 255-210*brownness, 255-255*brownness, 255); @@ -65,4 +68,31 @@ class Muscle { ni2.x*scaleToFixBug, ni2.y*scaleToFixBug, ni2.z*scaleToFixBug); } -} + + public void saveToJson(JsonGenerator g){ + try{ + g.writeNumberField("c1", this.c1); + g.writeNumberField("c2", this.c2); + g.writeNumberField("len", this.len); + g.writeNumberField("rigidity", this.rigidity); + } catch(Exception e){ + writeToErrorLog(e); + } + } + + public void loadFromJson(JsonParser p){ + try{ + while(p.nextToken() != JsonToken.END_OBJECT){ + String fieldName = p.getCurrentName(); + p.nextToken(); + if(fieldName.equals("c1")){ this.c1 = p.getIntValue(); } + else if(fieldName.equals("c2")){ this.c2 = p.getIntValue(); } + else if(fieldName.equals("len")){ this.len = p.getFloatValue(); } + else if(fieldName.equals("rigidity")){ this.rigidity = p.getFloatValue(); } + } + } catch(Exception e){ + writeToErrorLog(e); + } + } + +} \ No newline at end of file diff --git a/Node.pde b/Node.pde index b617c45..aa2b9e8 100644 --- a/Node.pde +++ b/Node.pde @@ -1,73 +1,78 @@ class Node { - float x, y, z, vx, vy, vz, prevX, prevY, prevZ, pvx, pvy, pvz, m, f; - boolean safeInput; - float pressure; + float x, y, z, vx, vy, vz, prevX, prevY, prevZ, pvx, pvy, pvz, m, initf, f; + float pressure, brainOutput; Node(float tx, float ty, float tz, float tvx, float tvy, float tvz, float tm, float tf) { - prevX = x = tx; - prevY = y = ty; - prevZ = z = tz; - pvx = vx = tvx; - pvy = vy = tvy; - pvz = vz = tvz; - m = tm; - f = tf; - pressure = 0; + this.prevX = x = tx; + this.prevY = y = ty; + this.prevZ = z = tz; + this.pvx = vx = tvx; + this.pvy = vy = tvy; + this.pvz = vz = tvz; + this.m = tm; + this.initf = tf; + this.f = this.initf; + this.pressure = 0; + this.brainOutput = (2*this.initf)-1; } void applyForces() { - vx *= airFriction; - vy *= airFriction; - vz *= airFriction; - y += vy; - x += vx; - z += vz; + this.f = toNodeUsable(); + this.vx *= airFriction; + this.vy *= airFriction; + this.vz *= airFriction; + this.y += vy; + this.x += vx; + this.z += vz; float acc = dist(vx,vy,vz,pvx,pvy,pvz); totalNodeNausea += acc*acc*nauseaUnit; - pvx = vx; - pvy = vy; - pvz = vz; + this.pvx = vx; + this.pvy = vy; + this.pvz = vz; + } + float toNodeUsable(){ + return (this.brainOutput+1)/2; } void applyGravity() { - vy += gravity; + this.vy += gravity; } void pressAgainstGround(float groundY){ float dif = y-(groundY-m/2); - pressure += dif*pressureUnit; - y = (groundY-m/2); - vy = 0; - x -= vx*f; - z -= vz*f; - if (vx > 0) { - vx -= f*dif*FRICTION; - if (vx < 0) { - vx = 0; + this.pressure += dif*pressureUnit; + this.y = (groundY-m/2); + this.vy = 0; + this.x -= vx*f; + this.z -= vz*f; + if (this.vx > 0) { + this.vx -= this.f*dif*FRICTION; + if (this.vx < 0) { + this.vx = 0; } } else { - vx += f*dif*FRICTION; - if (vx > 0) { - vx = 0; + this.vx += this.f*dif*FRICTION; + if (this.vx > 0) { + this.vx = 0; } } - if (vz > 0) { - vz -= f*dif*FRICTION; - if (vz < 0) { - vz = 0; + if (this.vz > 0) { + this.vz -= this.f*dif*FRICTION; + if (this.vz < 0) { + this.vz = 0; } } else { - vz += f*dif*FRICTION; + this.vz += this.f*dif*FRICTION; if (vz > 0) { vz = 0; } } } void hitWalls(Boolean addToAngular) { - pressure = 0; + this.pressure = 0; float dif = y+m/2; if (dif >= 0 && haveGround) { pressAgainstGround(0); } - if(y > prevY && hazelStairs >= 0){ + if(this.y > this.prevY && hazelStairs >= 0){ float bottomPointNow = y+m/2; float bottomPointPrev = prevY+m/2; int levelNow = (int)(ceil(bottomPointNow/hazelStairs)); @@ -141,36 +146,36 @@ class Node { } } }*/ - prevY = y; - prevX = x; + this.prevY = this.y; + this.prevX = this.x; } Node copyNode() { - return (new Node(x, y, z, 0, 0, 0, m, f)); + return (new Node(this.x, this.y, this.z, 0, 0, 0, this.m, this.f)); } Node modifyNode(float mutability, int nodeNum) { - float newX = x+r()*0.5*mutability; - float newY = y+r()*0.5*mutability; - float newZ = z+r()*0.5*mutability; + float newX = this.x+r()*0.5*mutability; + float newY = this.y+r()*0.5*mutability; + float newZ = this.z+r()*0.5*mutability; //float newM = m+r()*0.1*mutability; //newM = min(max(newM, 0.3), 0.5); float newM = 0.4; - float newF = min(max(f+r()*0.1*mutability, 0), 1); + float newF = min(max(this.initf+r()*0.1*mutability, 0), 1); Node newNode = new Node(newX, newY, newZ, 0, 0, 0, newM, newF); return newNode;//max(m+r()*0.1,0.2),min(max(f+r()*0.1,0),1) } void drawNode(PGraphics img) { color c = color(0,0,0); - if (f <= 0.5) { - c = colorLerp(color(255,255,255),color(180,0,255),f*2); + if (this.f <= 0.5) { + c = colorLerp(color(255,255,255),color(180,0,255),this.f*2); }else{ - c = colorLerp(color(180,0,255),color(0,0,0),f*2-1); + c = colorLerp(color(180,0,255),color(0,0,0),this.f*2-1); } img.fill(c); img.noStroke(); img.lights(); img.pushMatrix(); - img.translate(x*scaleToFixBug, y*scaleToFixBug,z*scaleToFixBug); - img.sphere(m*scaleToFixBug*0.5); + img.translate(this.x*scaleToFixBug, this.y*scaleToFixBug,this.z*scaleToFixBug); + img.sphere(this.m*scaleToFixBug*0.5); img.popMatrix(); //img.ellipse((ni.x+x)*scaleToFixBug, (ni.y+y)*scaleToFixBug, ni.m*scaleToFixBug, ni.m*scaleToFixBug); /*if(ni.f >= 0.5){ @@ -186,4 +191,43 @@ class Node { color colorLerp(color a, color b, float x){ return color(red(a)+(red(b)-red(a))*x, green(a)+(green(b)-green(a))*x, blue(a)+(blue(b)-blue(a))*x); } -} + + public void saveToJson(JsonGenerator g){ + try{ + g.writeNumberField("x", this.x); + g.writeNumberField("y", this.y); + g.writeNumberField("z", this.z); + g.writeNumberField("vx", this.vx); + g.writeNumberField("vy", this.vy); + g.writeNumberField("vz", this.vz); + g.writeNumberField("m", this.m); + g.writeNumberField("f", this.initf); + } catch(Exception e){ + writeToErrorLog(e); + } + } + + public void loadFromJson(JsonParser p){ + try{ + while(p.nextToken() != JsonToken.END_OBJECT){ + String fieldName = p.getCurrentName(); + p.nextToken(); + if(fieldName.equals("x")){ this.x = p.getFloatValue(); } + else if(fieldName.equals("y")){ this.y = p.getFloatValue(); } + else if(fieldName.equals("z")){ this.z = p.getFloatValue(); } + else if(fieldName.equals("vx")){ this.vx = p.getFloatValue(); } + else if(fieldName.equals("vy")){ this.vy = p.getFloatValue(); } + else if(fieldName.equals("vz")){ this.vz = p.getFloatValue(); } + else if(fieldName.equals("m")){ this.m = p.getFloatValue(); } + else if(fieldName.equals("f")){ + this.f = this.initf = p.getFloatValue(); + this.brainOutput = (2*this.initf)-1; + } + else if(fieldName.equals("pressure")){ this.pressure = p.getFloatValue(); } + } + } catch(Exception e){ + writeToErrorLog(e); + } + } + +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..808f4a3 --- /dev/null +++ b/README.md @@ -0,0 +1,58 @@ +# evolutionSteer +Evolution simulator of creatures learning how to steer towards food pellets better and better. + +# INSTALL + +1. **Dependencies** : +Please install Processing 2.2.1 by following the link on this website : https://processing.org/download/ + +2. **Download evolutionSteer** : +You can either download zip file or typing the following command into your terminal: `git clone https://github.com/carykh/evolutionSteer.git` + +3. **Launch evolutionSteer** : +You can then open evolutionSteer.pde and press the *RUN* button. That's it ! + +4. Installing JacksonCore : +If the program doesn’t launch because of dependancies trouble, please drag and drop the two .jar files in the *code* subdirectory onto the Processing window. + +# Configuration + +Most parameters are set at the beginning of the *evolutionSteer.pde* file. Noticeable parameters are: + +* **windowSizeMultiplier** : set to 1 if window is too big +* **nbCreatures** : define the number of creatures created at each generation. please verify that the value is even, and that the parameters *gridX* and *gridY* are set accordingly so that `gridX * gridY = nbCreatures` +* **thresholdName** : the name of the creature will be shown in the bottom graph if the number of creatures in this species is greater than this value +* **autoSave** : in ALAP mode, this option will automatically save your work. The number set in the *autoSave* variable will define the frequency of these saves +* **autoSaveTimecode** : if set to *true*, the periodical autosave will have a different name each time, with timecode included (data/autosave-2017-7-26_7-58-5.gz for example). If set to *false*, the autosave will have a fixed name (data/autosave.gz). As the files can be pretty large, please set this value to *false* if you don't have enough disk space. +* **autoPause** : because each man needs its pause, you can ask **evolutionSteer** to stop working after a given number of generations +* **simDuration** : the base duration (in seconds) of the simulation +* **jumperDuration** : if the first food blob is found too early (before this value, in seconds), the creature wil be defined as a jumper and thus destroyed +* **giftForChompSec** : gift (in simulation overtime) given to the creature when it found a food blob. +* **radioactiveNumber** : when Radioactive mode is enabled, this number of creatures will see its mutation rate increase by a given factor (**radioactiveMutator**) +* **freshBloodNumber** : when Radioactive mode is enabled, this number of creatures will be created from scratch each generation +* **THREAD_COUNT** : set accordingly to the number of cores in your computer. Multithreading will be activated if you set **activateMultiThreading** to *true* + +# Features + +First have a look to green commands in the upper right panel : + +* Click **Do 1 step-by-step generation** button to launch the details of the generation process of new creatures. This will explain you how the creatures are generated and show you your creatures trying and failing to eat. +* Click **Do 1 quick generation** in order to follow the same steps but avoiding to see each of your creatures pathetically dying +* Click **Do 1 gen ASAP** in order to quickly generate *one* generation +* Click **Do gens ALAP** in order to continuously generate creatures. This will stop only when (your computer will crash or) you click anywhere else in the window. + +You can press certain commands during execution + +* Press **"r"** to activate *Radioactive mode*. This mode will increase the mutation rate of your creature and hopefully lead to new species +* Press **'"t"** to increase the variability of the food generation process. The food blob will be created relatively to the creature given a random angle which is contained between + and - the angle shown in the interface. You can reduce the variability by pressing **"g"** +* Press **"y"** to reduce the gift (in seconds of supplemental simulation) given to the creature each time it encounters a food blob. Press **"h"** to increase this gift. +* Press **left** and **right** arrow to select previous/next generation. Alternatively, you can use the slider in the interface. +* Press **"k"** to instantly kill half your population. It is fun. +* Press **"b"** to activate **BRAINIAC** mode. This will instantly add a neutron layer to each of your creatures. Be cautious as it is computationally intensive, irreversible and can lead to numerous deaths in your population. +* Press **"p"** to instantly double the number of creatures in you batch. This will reset the timeline and hopefully double the computation time for each generation. Be careful for what you wish ! + +At last you can save you creatures : + +* If you press the **Save** button, everything contained in the window ill be saved, including past (sometimes *ancient*) creatures (3 per generation). However this can lead to very big files according to : the number of creatures simulated, the size of their brain and the number of generations. +* If you press the **Light save**, you will save only the two last generations. This has the advantage to create lighter files +* NB the files are gzipped files, and the format is a binary json file format named [Smile](https://en.wikipedia.org/wiki/Smile_(data_interchange_format)). \ No newline at end of file diff --git a/code/jackson-core-2.9.0.pr4.jar b/code/jackson-core-2.9.0.pr4.jar new file mode 100644 index 0000000..dbf541d Binary files /dev/null and b/code/jackson-core-2.9.0.pr4.jar differ diff --git a/code/jackson-dataformat-smile-2.9.0.pr4.jar b/code/jackson-dataformat-smile-2.9.0.pr4.jar new file mode 100644 index 0000000..293d16c Binary files /dev/null and b/code/jackson-dataformat-smile-2.9.0.pr4.jar differ diff --git a/computingThread.pde b/computingThread.pde new file mode 100644 index 0000000..27e536e --- /dev/null +++ b/computingThread.pde @@ -0,0 +1,36 @@ +public class ComputingThread implements Runnable{ + private int beginIndex; + private int endIndex; + private int framePerChomp; + private Creature myCreature; + public ComputingThread(int bi, int ei){ + this.beginIndex = bi; + this.endIndex = ei; + } + @Override + public void run() { + for(int k = this.beginIndex; k < this.endIndex; k++) { + myCreature = c[k].copyCreature(-1,false,true); + myCreature.initParameters(); + myCreature.calculateNextFoodLocation(); + int myMaxFrames = maxFrames; + int currentChomp = 0; + boolean isJumper = false; + for (int sim = 0; sim < myMaxFrames; sim++) { + if(myCreature.simulate()){ // activated when chomped + myMaxFrames += giftForChompFrames; + currentChomp++; + if(currentChomp >= maxChomp){ break; } + } + } + if(isJumper){ + c[k].d = 0; + } else { + myCreature.setAverages(); + c[k].d = myCreature.getFitness(); + c[k].chomps = myCreature.chomps; + c[k].timePerChomp = myCreature.timePerChomp; + } + } + } +} \ No newline at end of file diff --git a/evolutionSteer.pde b/evolutionSteer.pde index ea1723a..fe909ec 100644 --- a/evolutionSteer.pde +++ b/evolutionSteer.pde @@ -1,5 +1,20 @@ -final float windowSizeMultiplier = 1.4; -final int SEED = 31; //7; ;( +import java.io.*; +import java.io.BufferedReader; +import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonEncoding; +import com.fasterxml.jackson.core.JsonToken; +//import com.fasterxml.jackson.dataformat.smile.SmileGenerator; +//import com.fasterxml.jackson.dataformat.smile.SmileFactory; + + +float windowSizeMultiplier = 1; +int SEED = 314; //7; ;( PFont font; ArrayList percentile = new ArrayList(0); @@ -26,12 +41,21 @@ float hazelStairs = -1; float cumulativeAngularVelocity = 0; boolean saveFramesPerGeneration = true; color gridBGColor = color(220, 253, 102, 255); -float foodAngleChange = 0.0; -float foodX = 0; -float foodY = 0; -float foodZ = 0; -float foodAngle = 0; -int chomps = 0; +float foodAngleChange = PI; +float[] angleMultiplier = { // angle increases progressively according to the values in this array + 0.0326, 0.0526, 0.0839, 0.1312, 0.1993, 0.291, 0.4036, 0.5273, 0.6478, + 0.752, 0.8333, 0.8918, 0.9315, 0.9573, 0.9736, 0.9838, 0.9901, 0.994 +}; + +static int nbCreatures = 250; // please set even number +int gridX = 25; // X * Y must be equal to nbCreatures ! +int gridY = 10; +int thresholdName = 25; // name of species is showed over this threshold + +int autoSave = 200; // autosave every x generation in ALAP mode +boolean autoSaveTimecode = false; // set to false is disk space limited +boolean hasAutosaveWorked = false; +int autoPause = 10000; // pauses ALAP each x generation int lastImageSaved = -1; float pressureUnit = 500.0/2.37; @@ -53,6 +77,7 @@ float lineY2 = 0.35; int windowWidth = 1280; int windowHeight = 720; +int gridHeightCrop = 100; int timer = 0; float camX = 0; float camY = 0; @@ -60,6 +85,14 @@ float camZ = 0; float camHA = 0; float camVA = -0.5; int frames = 60; +int simDuration = 15; // in seconds +int maxFrames = simDuration*frames; +int maxSimulationFrames = maxFrames; +float giftForChompSec = 15; +int giftForChompFrames = ceil(giftForChompSec*frames); +int maxChomp = 50; // maximum number of chomps before simulation ends +int timePerChompWeight = 20; // weight for creature speed in fitness calculation +int timePerChompSlope = 2; // slope for speed in fitness calculation int menu = 0; int gen = -1; float sliderX = 1170; @@ -78,7 +111,8 @@ int overallTimer = 0; boolean miniSimulation = false; int creatureWatching = 0; int simulationTimer = 0; -int[] creaturesInPosition = new int[1000]; +int[] creaturesInPosition = new int[nbCreatures]; +Creature[] c = new Creature[nbCreatures]; float camZoom = 0.015; float gravity = 0.006;//0.007; @@ -86,28 +120,37 @@ float airFriction = 0.95; float MIN_FOOD_DISTANCE = 1; float MAX_FOOD_DISTANCE = 2.5; -float target; -float force; -float averageX; -float averageY; -float averageZ; int speed; boolean stepbystep; boolean stepbystepslow; boolean slowDies; -int[] p = { +int[] pPercentages = { 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 200, 300, 400, 500, 600, 700, 800, 900, 910, 920, 930, 940, 950, 960, 970, 980, 990, 999 }; -final int BRAIN_WIDTH = 3; +int[] p = new int[29]; +final int BRAIN_WIDTH = 2; float STARTING_AXON_VARIABILITY = 1.0; float AXON_START_MUTABILITY = 0.0005; +float lossPerLayer = 0.5; //chomp lost by layer of neurons added + +boolean enableRadioactivity = true; +int radioactiveNumber = 20; // number of highly mutated creatures +int freshBloodNumber = 10; // number of brand new creatures +float radioactiveMutator = 1.5; + +boolean massExtinction = false; +float massExtinctionPercentage = 0.99; + String[] patronData; int PATRON_COUNT = 75; float TOTAL_PLEDGED = 183.39; int[] CREATURES_PER_PATRON = new int[PATRON_COUNT]; float startingFoodDistance = 0; +int THREAD_COUNT = 14; +boolean activateMultiThreading = true; + float inter(int a, int b, float offset) { return float(a)+(float(b)-float(a))*offset; } @@ -118,6 +161,14 @@ int rInt() { return int(random(-0.01, 1.01)); } void drawGround(PGraphics img) { + float averageX = 0; + float averageY = 0; + float averageZ = 0; + if(currentCreature != null) { + averageX = currentCreature.averageX; + averageY = currentCreature.averageY; + averageZ = currentCreature.averageZ; + } int stairDrawStart = max(1,(int)(-averageY/hazelStairs)-10); img.noStroke(); if (haveGround){ @@ -153,10 +204,23 @@ void drawGround(PGraphics img) { } }*/ } -float toMuscleUsable(float f){ - return min(max(f,0.8),1.2); -} void drawPosts(PGraphics img) { + float averageX = 0; + float averageY = 0; + float averageZ = 0; + if(currentCreature != null) { + averageX = currentCreature.averageX; + averageY = currentCreature.averageY; + averageZ = currentCreature.averageZ; + } + float foodX = 0; + float foodY = 0; + float foodZ = 0; + if(currentCreature != null) { + foodX = currentCreature.foodX; + foodY = currentCreature.foodY; + foodZ = currentCreature.foodZ; + } int startPostY = min(-8,(int)(averageY/4)*4-4); img.noStroke(); img.textAlign(CENTER); @@ -224,7 +288,7 @@ void drawArrow(float x, float y, float z, PGraphics img) { img.vertex(-0.5*scaleToFixBug, -2.7*scaleToFixBug); img.vertex(0.5*scaleToFixBug, -2.7*scaleToFixBug); img.endShape(CLOSE); - String fitnessString = nf(getFitness(),0,2)+" "+fitnessUnit; + String fitnessString = nf(currentCreature.getFitness(),0,2)+" "+fitnessUnit; img.fill(255); img.text(fitnessString, 0, -2.91*scaleToFixBug,0.1*scaleToFixBug); img.popMatrix(); @@ -244,8 +308,8 @@ void drawGraphImage() { noStroke(); for (int i = 1; i < 101; i++) { int c = s[i]-s[i-1]; - if (c >= 25) { - float y = ((s[i]+s[i-1])/2)/1000.0*100+573; + if (c >= thresholdName) { + float y = ((s[i]+s[i-1])/2)/float(nbCreatures)*100+573; if (i-1 == topSpeciesCounts.get(genSelected)) { stroke(0); strokeWeight(2); @@ -284,11 +348,10 @@ color getColor(int i, boolean adjust) { } void drawGraph(int graphWidth, int graphHeight) { graphImage.beginDraw(); - graphImage.smooth(); graphImage.background(220); if (gen >= 1) { - drawLines(130, int(graphHeight*0.05), graphWidth-130, int(graphHeight*0.9)); - drawSegBars(130, 0, graphWidth-130, 150); + drawLines(80, int(graphHeight*0.05), graphWidth-80, int(graphHeight*0.9)); + drawSegBars(80, 0, graphWidth-80, 150); } graphImage.endDraw(); } @@ -308,7 +371,7 @@ void drawLines(int x, int y, int graphWidth, int graphHeight) { for (float i = ceil((worst-(best-worst)/18.0)/unit)*unit; i < best+(best-worst)/18.0;i+=unit) { float lineY = y-i*meterHeight+zero; graphImage.line(x, lineY, graphWidth+x, lineY); - graphImage.text(showUnit(i, unit)+" "+fitnessUnit, x-5, lineY+4); + graphImage.text(showUnit(i, unit)+" %", x-5, lineY+4); } graphImage.stroke(0); for (int i = 0; i < 29; i++) { @@ -343,7 +406,6 @@ void drawLines(int x, int y, int graphWidth, int graphHeight) { } void drawSegBars(int x, int y, int graphWidth, int graphHeight) { segBarImage.beginDraw(); - segBarImage.smooth(); segBarImage.noStroke(); segBarImage.colorMode(HSB, 1); segBarImage.background(0, 0, 0.5); @@ -357,10 +419,10 @@ void drawSegBars(int x, int y, int graphWidth, int graphHeight) { for (int j = 0; j < 100; j++) { segBarImage.fill(getColor(j, false)); segBarImage.beginShape(); - segBarImage.vertex(barX1, y+speciesCounts.get(i)[j]/1000.0*graphHeight); - segBarImage.vertex(barX1, y+speciesCounts.get(i)[j+1]/1000.0*graphHeight); - segBarImage.vertex(barX2, y+speciesCounts.get(i2)[j+1]/1000.0*graphHeight); - segBarImage.vertex(barX2, y+speciesCounts.get(i2)[j]/1000.0*graphHeight); + segBarImage.vertex(barX1, y+speciesCounts.get(i)[j]/float(nbCreatures)*graphHeight); + segBarImage.vertex(barX1, y+speciesCounts.get(i)[j+1]/float(nbCreatures)*graphHeight); + segBarImage.vertex(barX2, y+speciesCounts.get(i2)[j+1]/float(nbCreatures)*graphHeight); + segBarImage.vertex(barX2, y+speciesCounts.get(i2)[j]/float(nbCreatures)*graphHeight); segBarImage.endShape(); } } @@ -441,21 +503,7 @@ void adjustToCenter(int nodeNum) { ni.y -= lowY; } } -void setAverages() { - averageX = 0; - averageY = 0; - averageZ = 0; - for (int i = 0; i < currentCreature.n.size(); i++) { - Node ni = currentCreature.n.get(i); - averageX += ni.x; - averageY += ni.y; - averageZ += ni.z; - } - averageX = averageX/currentCreature.n.size(); - averageY = averageY/currentCreature.n.size(); - averageZ = averageZ/currentCreature.n.size(); -} -Creature[] c = new Creature[1000]; +//Creature[] c = new Creature[nbCreatures]; ArrayList c2 = new ArrayList(); void mouseWheel(MouseEvent event) { @@ -490,6 +538,7 @@ void mousePressed() { void openMiniSimulation() { simulationTimer = 0; + maxSimulationFrames = simDuration*frames; if (gensToDo == 0) { miniSimulation = true; int id; @@ -532,6 +581,9 @@ void mouseReleased() { float mY = mouseY/windowSizeMultiplier; if (menu == 0 && abs(mX-windowWidth/2) <= 200 && abs(mY-400) <= 100) { setMenu(1); + }else if(menu == 0 && abs(mX -windowWidth/2) <=150 && abs(mY - 600) <=50){ + setMenu(14); + selectInput("Select a file to load", "fileSelected"); }else if (menu == 1 && gen == -1 && abs(mX-120) <= 100 && abs(mY-300) <= 50) { setMenu(2); }else if (menu == 1 && gen >= 0 && abs(mX-990) <= 230) { @@ -556,6 +608,12 @@ void mouseReleased() { } startASAP(); } + }else if(menu == 1 && gen != -1 && abs(mX - 650) <= 50 && abs(mY - 48) <= 20){ + setMenu(15); + selectOutput("Select file to save simulation to", "saveSelected"); + }else if(menu == 1 && gen != -1 && abs(mX - 505) <= 85 && abs(mY - 48) <= 20){ + setMenu(15); + selectOutput("Select file to save simulation to", "saveSelectedLight"); }else if (menu == 3 && abs(mX-1030) <= 130 && abs(mY-684) <= 20) { gen = 0; setMenu(1); @@ -563,26 +621,28 @@ void mouseReleased() { setMenu(8); } else if((menu == 5 || menu == 4) && mY >= windowHeight-40){ if(mX < 90){ - for (int s = timer; s < 900; s++) { - simulateCurrentCreature(); + maxFrames = simDuration*frames; + for (int s = timer; s < maxFrames; s++) { + if(simulateCurrentCreature()){ maxFrames += simDuration*frames; } } timer = 1021; }else if(mX >= 120 && mX < 360){ speed *= 2; - if(speed == 1024) speed = 900; + if(speed == 1024) speed = simDuration*frames; if(speed >= 1800) speed = 1; }else if(mX >= windowWidth-120){ - for (int s = timer; s < 900; s++) { - simulateCurrentCreature(); + maxFrames = simDuration*frames; + for (int s = timer; s < maxFrames; s++) { + if(simulateCurrentCreature()){ maxFrames += simDuration*frames; } } timer = 0; creaturesTested++; - for (int i = creaturesTested; i < 1000; i++) { - setGlobalVariables(c[i]); - for (int s = 0; s < 900; s++) { - simulateCurrentCreature(); + for (int i = creaturesTested; i < nbCreatures; i++) { + maxFrames = simDuration*frames; + for (int s = 0; s < maxFrames; s++) { + if(simulateCurrentCreature()){ maxFrames += simDuration*frames; } } - setAverages(); + currentCreature.setAverages(); setFitness(i); } setMenu(6); @@ -597,32 +657,34 @@ void mouseReleased() { setMenu(1); } } -void simulateCurrentCreature(){ - currentCreature.simulate(); +boolean simulateCurrentCreature(){ + boolean hasEaten = currentCreature.simulate(); averageNodeNausea = totalNodeNausea/currentCreature.n.size(); simulationTimer++; timer++; + return hasEaten; } void drawScreenImage(int stage) { screenImage.beginDraw(); screenImage.pushMatrix(); screenImage.scale(10.0*windowSizeMultiplier/scaleToFixBug); - screenImage.smooth(); screenImage.background(gridBGColor); screenImage.noStroke(); - for (int j = 0; j < 1000; j++) { + for (int j = 0; j < nbCreatures; j++) { Creature cj = c2.get(j); - if (stage == 3) cj = c[cj.id-(gen*1000)-1001]; + if (stage == 3) cj = c[cj.id%nbCreatures]; int j2 = j; if (stage == 0) { - j2 = cj.id-(gen*1000)-1; + j2 = cj.id-(gen*nbCreatures)-1; creaturesInPosition[j2] = j; } - int x = j2%40; - int y = floor(j2/40); - if (stage >= 1) y++; + int x = j2%gridX; + int y = floor(j2/gridX); + float xWidth = windowWidth / (gridX+1) / 10.0; + float yHeight = (windowHeight - gridHeightCrop) / (gridY+1) / 10.0; + //if (stage >= 1) y++; screenImage.pushMatrix(); - screenImage.translate((x*3+5.5)*scaleToFixBug, (y*2.5+3)*scaleToFixBug, 0); + screenImage.translate(((x+1)*xWidth)*scaleToFixBug, ((y+0.5)*yHeight+(gridHeightCrop/20.0))*scaleToFixBug, 0); cj.drawCreature(screenImage,true); screenImage.popMatrix(); } @@ -640,24 +702,26 @@ void drawScreenImage(int stage) { if (stage == 0) { screenImage.rect(900, 664, 260, 40); screenImage.fill(0); - screenImage.text("All 1,000 creatures have been tested. Now let's sort them!", windowWidth/2-200, 690); + screenImage.text("All "+nbCreatures+" creatures have been tested. Now let's sort them!", windowWidth/2-200, 690); screenImage.text("Sort", windowWidth-250, 690); } else if (stage == 1) { screenImage.rect(900, 670, 260, 40); screenImage.fill(0); screenImage.text("Fastest creatures at the top!", windowWidth/2, 30); screenImage.text("Slowest creatures at the bottom. (Going backward = slow)", windowWidth/2-200, 700); - screenImage.text("Kill 500", windowWidth-250, 700); + screenImage.text("Kill "+(nbCreatures/2), windowWidth-250, 700); } else if (stage == 2) { screenImage.rect(1050, 670, 160, 40); screenImage.fill(0); screenImage.text("Faster creatures are more likely to survive because they can outrun their predators. Slow creatures get eaten.", windowWidth/2, 30); screenImage.text("Because of random chance, a few fast ones get eaten, while a few slow ones survive.", windowWidth/2-130, 700); screenImage.text("Reproduce", windowWidth-150, 700); - for (int j = 0; j < 1000; j++) { + for (int j = 0; j < nbCreatures; j++) { Creature cj = c2.get(j); - int x = j%40; - int y = floor(j/40)+1; + int x = j%gridX; + int y = floor(j/gridX);//+1; + float xWidth = windowWidth / (gridX+1); + float yHeight = (windowHeight - gridHeightCrop) / (gridY+1); if (cj.alive) { /*screenImage.pushMatrix(); screenImage.scale(10.0*windowSizeMultiplier/scaleToFixBug); @@ -669,17 +733,17 @@ void drawScreenImage(int stage) { } else { screenImage.fill(0); screenImage.beginShape(); - screenImage.vertex(x*30+40, y*25+17,0.01); - screenImage.vertex(x*30+70, y*25+17,0.01); - screenImage.vertex(x*30+70, y*25+42,0.01); - screenImage.vertex(x*30+40, y*25+42,0.01); + screenImage.vertex((x+1)*xWidth-15, (y+0.5)*yHeight+(gridHeightCrop/2)-12,0.01); + screenImage.vertex((x+1)*xWidth+15, (y+0.5)*yHeight+(gridHeightCrop/2)-12,0.01); + screenImage.vertex((x+1)*xWidth+15, (y+0.5)*yHeight+(gridHeightCrop/2)+12,0.01); + screenImage.vertex((x+1)*xWidth-15, (y+0.5)*yHeight+(gridHeightCrop/2)+12,0.01); screenImage.endShape(); } } } else if (stage == 3) { screenImage.rect(1050, 670, 160, 40); screenImage.fill(0); - screenImage.text("These are the 1000 creatures of generation #"+(gen+2)+".", windowWidth/2, 30); + screenImage.text("These are the "+nbCreatures+" creatures of generation #"+(gen+2)+".", windowWidth/2, 30); screenImage.text("What perils will they face? Find out next time!", windowWidth/2-130, 700); screenImage.text("Back", windowWidth-150, 700); } @@ -687,10 +751,9 @@ void drawScreenImage(int stage) { screenImage.endDraw(); } void drawpopUpImage() { - setAverages(); + currentCreature.setAverages(); moveCamera(); popUpImage.beginDraw(); - popUpImage.smooth(); float camDist = (450/2.0) / tan(PI*30.0 / 180.0); popUpImage.pushMatrix(); @@ -701,7 +764,7 @@ void drawpopUpImage() { popUpImage.scale(1.0/camZoom/scaleToFixBug); - if (simulationTimer < 900) { + if (simulationTimer < maxSimulationFrames) { popUpImage.background(120, 200, 255); } else { popUpImage.background(60, 100, 128); @@ -709,12 +772,20 @@ void drawpopUpImage() { drawPosts(popUpImage); drawGround(popUpImage); currentCreature.drawCreature(popUpImage,false); - drawArrow(averageX,averageY,averageZ,popUpImage); + drawArrow(currentCreature.averageX,currentCreature.averageY,currentCreature.averageZ,popUpImage); popUpImage.noStroke(); popUpImage.endDraw(); popUpImage.popMatrix(); } void moveCamera(){ + float averageX = 0; + float averageY = 0; + float averageZ = 0; + if(currentCreature != null) { + averageX = currentCreature.averageX; + averageY = currentCreature.averageY; + averageZ = currentCreature.averageZ; + } camX += (averageX-camX)*0.2; camY += (averageY-camY)*0.2; camZ += (averageZ-camZ)*0.2; @@ -773,6 +844,8 @@ void drawHistogram(int x, int y, int hw, int hh) { void drawStatusWindow(boolean isFirstFrame) { int x, y, px, py; int rank = (statusWindow+1); + float xWidth = windowWidth / (gridX+1); + float yHeight = (windowHeight - gridHeightCrop) / (gridY+1); Creature cj; stroke(abs(overallTimer%30-15)*17); strokeWeight(3); @@ -780,21 +853,21 @@ void drawStatusWindow(boolean isFirstFrame) { if (statusWindow >= 0) { cj = c2.get(statusWindow); if (menu == 7) { - int id = ((cj.id-1)%1000); - x = id%40; - y = floor(id/40); + int id = ((cj.id-1)%nbCreatures); + x = id%gridX; + y = floor(id/gridX); } else { - x = statusWindow%40; - y = floor(statusWindow/40)+1; + x = statusWindow%gridX; + y = floor(statusWindow/gridX);//+1; } - px = x*30+55; - py = y*25+10; + px = floor((x+1)*xWidth); + py = floor((y+0.5)*yHeight+(gridHeightCrop/2)-19); if (px <= 1140) { px += 80; } else { px -= 80; } - rect(x*30+40, y*25+17, 30, 25); + rect((x+1)*xWidth-15, (y+0.5)*yHeight+(gridHeightCrop/2)-12, 30, 25); } else { cj = creatureDatabase.get((genSelected-1)*3+statusWindow+3); x = 760+(statusWindow+3)*160; @@ -803,23 +876,25 @@ void drawStatusWindow(boolean isFirstFrame) { py = y; rect(x, y, 140, 140); int[] ranks = { - 1000, 500, 1 + nbCreatures, nbCreatures/2, 1 }; rank = ranks[statusWindow+3]; } noStroke(); fill(255); - rect(px-60, py, 120, 52); + rect(px-60, py, 120, 58); fill(0); textFont(font, 12); textAlign(CENTER); - text("#"+rank, px, py+12); - text("ID: "+cj.id, px, py+24); - text("Fitness: "+nf(cj.d, 0, 3), px, py+36); + text("#"+rank, px, py+10); + text("ID: "+cj.id, px, py+19); + text("Fitness: "+nf(cj.d, 0, 3), px, py+28); + text("Chomps: "+cj.chomps, px, py+37); + text("Time/Chomp: "+nf(cj.timePerChomp, 0, 3), px, py+46); colorMode(HSB, 1); int sp = (cj.n.size()%10)*10+(cj.m.size()%10); fill(getColor(sp, true)); - text("Species: S"+(cj.n.size()%10)+""+(cj.m.size()%10), px, py+48); + text("Species: S"+(cj.n.size()%10)+""+(cj.m.size()%10), px, py+55); colorMode(RGB, 255); if (miniSimulation) { keysToMoveCamera(); @@ -839,7 +914,7 @@ void drawStatusWindow(boolean isFirstFrame) { drawBrain(px2-130, py2, 1,5, cj); drawStats(px2+355, py2+239, 1, 0.45); - simulateCurrentCreature(); + if(simulateCurrentCreature()){ maxSimulationFrames += giftForChompFrames; } int shouldBeWatching = statusWindow; if (statusWindow <= -1) { cj = creatureDatabase.get((genSelected-1)*3+statusWindow+3); @@ -850,6 +925,15 @@ void drawStatusWindow(boolean isFirstFrame) { } } } +void settings(){ + size(int(windowWidth*windowSizeMultiplier), int(windowHeight*windowSizeMultiplier),P3D); + smooth(); +} +void initPercentiles(){ + for (int i = 1; i < 29; i++) { + p[i] = int(floor(float(pPercentages[i])*float(nbCreatures)/1000.0)); + }; +} void setup() { String[] prePatronData = loadStrings("PatronReport_2017-06-12.csv"); patronData = new String[PATRON_COUNT]; @@ -863,10 +947,9 @@ void setup() { for(int i = 0; i < PATRON_COUNT; i++){ CREATURES_PER_PATRON[i] = 0; } + initPercentiles(); frameRate(60); randomSeed(SEED); - noSmooth(); - size((int)(windowWidth*windowSizeMultiplier), (int)(windowHeight*windowSizeMultiplier),P3D); ellipseMode(CENTER); Float[] beginPercentile = new Float[29]; Integer[] beginBar = new Integer[barLen]; @@ -878,7 +961,7 @@ void setup() { beginBar[i] = 0; } for (int i = 0; i < 101; i++) { - beginSpecies[i] = 500; + beginSpecies[i] = nbCreatures/2; } percentile.add(beginPercentile); @@ -892,11 +975,9 @@ void setup() { popUpImage = createGraphics(450, 450, P3D); segBarImage = createGraphics(975, 150); segBarImage.beginDraw(); - segBarImage.smooth(); segBarImage.background(220); segBarImage.endDraw(); popUpImage.beginDraw(); - popUpImage.smooth(); popUpImage.background(220); popUpImage.endDraw(); @@ -938,23 +1019,27 @@ void draw() { fill(100, 200, 100); noStroke(); rect(windowWidth/2-200, 300, 400, 200); + rect(windowWidth/2-150, 550, 300, 100); fill(0); + textSize(60); text("EVOLUTION!", windowWidth/2, 200); text("START", windowWidth/2, 430); + textSize(26); + text("Load simulation", windowWidth/2, 610); }else if (menu == 1) { noStroke(); fill(0); background(255, 200, 130); textFont(font, 32); textAlign(LEFT); - textFont(font, 96); + textFont(font, 72); text("GEN "+max(genSelected, 0), 20, 100); textFont(font, 28); if (gen == -1) { fill(100, 200, 100); rect(20, 250, 200, 100); fill(0); - text("Since there are no creatures yet, create 1000 creatures!", 20, 160); + text("Since there are no creatures yet, create "+nbCreatures+" creatures!", 20, 160); text("They will be randomly created, and also very simple.", 20, 200); text("CREATE", 56, 312); } else { @@ -962,6 +1047,8 @@ void draw() { rect(760, 20, 460, 40); rect(760, 70, 460, 40); rect(760, 120, 230, 40); + rect(600, 20, 100, 40); + rect(420, 20, 170, 40); if (gensToDo >= 2) { fill(128, 255, 128); } else { @@ -970,21 +1057,33 @@ void draw() { rect(990, 120, 230, 40); fill(0); //text("Survivor Bias: "+percentify(getSB(genSelected)), 437, 50); - text("Curve: ±"+nf(foodAngleChange/(2*PI)*360,0,2)+" degrees", 420, 50); + textAlign(RIGHT); + if(foodAngleChange < PI){ + text("Curve: ±"+nf(foodAngleChange/(2*PI)*360,0,2)+" degrees", 700, 120); + } + if(giftForChompSec < 15){ + text("Gift: +"+nf(giftForChompSec)+" seconds", 700, 145); + } + if(enableRadioactivity){ + text("Radioactive mode",700, 95); + } + textAlign(LEFT); text("Do 1 step-by-step generation.", 770, 50); text("Do 1 quick generation.", 770, 100); text("Do 1 gen ASAP.", 770, 150); text("Do gens ALAP.", 1000, 150); - text("Median "+fitnessName, 50, 160); - textAlign(CENTER); - textAlign(RIGHT); - text(float(round(percentile.get(min(genSelected, percentile.size()-1))[14]*1000))/1000+" "+fitnessUnit, 700, 160); + text("Save", 620, 48); + text("Light save", 435, 48); + text("Median fit : "+float(round(percentile.get(min(genSelected, percentile.size()-1))[14]*nbCreatures))/nbCreatures+"%", 50, 160); drawHistogram(760, 410, 460, 280); drawGraphImage(); //if(saveFramesPerGeneration && gen > lastImageSaved){ // saveFrame("imgs//"+zeros(gen,5)+".png"); // lastImageSaved = gen; //} + if(massExtinction){ + text("MASS EXTINCTION", 400, 210); + } } if (gensToDo >= 1) { gensToDo--; @@ -994,42 +1093,13 @@ void draw() { } }else if (menu == 2) { creatures = 0; - for (int y = 0; y < 25; y++) { - for (int x = 0; x < 40; x++) { - int nodeNum = int(random(4, 8)); - int muscleNum = int(random(nodeNum, nodeNum*3)); - ArrayList n = new ArrayList(nodeNum); - ArrayList m = new ArrayList(muscleNum); - for (int i = 0; i < nodeNum; i++) { - n.add(new Node(random(-1, 1), random(-1, 1), random(-1, 1), - 0, 0, 0, 0.4, random(0, 1))); //replaced all nodes' sizes with 0.4, used to be random(0.1,1), random(0,1) - } - for (int i = 0; i < muscleNum; i++) { - int tc1 = 0; - int tc2 = 0; - if (i < nodeNum-1) { - tc1 = i; - tc2 = i+1; - } else { - tc1 = int(random(0, nodeNum)); - tc2 = tc1; - while (tc2 == tc1) { - tc2 = int(random(0, nodeNum)); - } - } - float s = 0.8; - if (i >= 10) { - s *= 1.414; - } - float len = random(0.5,1.5); - m.add(new Muscle(tc1, tc2, len, random(0.015, 0.06))); - } - float heartbeat = random(40, 80); - c[y*40+x] = new Creature(null, y*40+x+1, new ArrayList(n), new ArrayList(m), 0, true, heartbeat, 1.0, null, null); - c[y*40+x].checkForOverlap(); - c[y*40+x].checkForLoneNodes(); - c[y*40+x].toStableConfiguration(); - c[y*40+x].moveToCenter(); + for (int y = 0; y < gridY; y++) { + for (int x = 0; x < gridX; x++) { + c[y*gridX+x] = createNewCreature(y*gridX+x); + c[y*gridX+x].checkForOverlap(); + c[y*gridX+x].checkForLoneNodes(); + c[y*gridX+x].toStableConfiguration(); + c[y*gridX+x].moveToCenter(); } } creatures = 0; @@ -1038,11 +1108,13 @@ void draw() { screenImage.scale(windowSizeMultiplier); screenImage.pushMatrix(); screenImage.scale(10.0/scaleToFixBug); - for (int y = 0; y < 25; y++) { - for (int x = 0; x < 40; x++) { + float xWidth = windowWidth / (gridX+1) / 10.0; + float yHeight = (windowHeight - gridHeightCrop) / (gridY+1) / 10.0; + for (int y = 0; y < gridY; y++) { + for (int x = 0; x < gridX; x++) { screenImage.pushMatrix(); - screenImage.translate((x*3+5.5)*scaleToFixBug, (y*2.5+3)*scaleToFixBug, 0); - c[y*40+x].drawCreature(screenImage,true); + screenImage.translate(((x+1)*xWidth)*scaleToFixBug, ((y+1)*yHeight+gridHeightCrop/20.0)*scaleToFixBug, 0); + c[y*gridX+x].drawCreature(screenImage,true); screenImage.popMatrix(); } } @@ -1054,7 +1126,7 @@ void draw() { screenImage.fill(0); screenImage.textAlign(CENTER); screenImage.textFont(font, 24); - screenImage.text("Here are your 1000 randomly generated creatures!!!", windowWidth/2-200, 690); + screenImage.text("Here are your "+nbCreatures+" randomly generated creatures!!!", windowWidth/2-200, 690); screenImage.text("Back", windowWidth-250, 690); screenImage.endDraw(); setMenu(3); @@ -1065,36 +1137,61 @@ void draw() { setGlobalVariables(c[creaturesTested]); setMenu(5); if (!stepbystepslow) { - for (int i = 0; i < 1000; i++) { - setGlobalVariables(c[i]); - for (int s = 0; s < 900; s++) { - simulateCurrentCreature(); + long start = System.nanoTime(); + Thread[] threads = new Thread[THREAD_COUNT]; + int previousLastIndex = 0; + for(int i = 0; i < threads.length; i++) { + int firstIndex = previousLastIndex; + int lastIndex; + if(i == threads.length - 1) { + lastIndex = nbCreatures; + } else { + lastIndex = (int)((i+1) * float(nbCreatures) / threads.length); + } + threads[i] = new Thread(new ComputingThread(firstIndex, lastIndex)); + if(activateMultiThreading){ + threads[i].start(); + } + previousLastIndex = lastIndex; + } + for(int i = 0; i < threads.length; i++) { + try { + if(activateMultiThreading){ + threads[i].join(); + } else { + threads[i].run(); + } + } catch (InterruptedException ie) { + ie.printStackTrace(); // :( } - setAverages(); - setFitness(i); } + double simulationTime = Math.round((System.nanoTime() - start) / 100000D) / 10; + surface.setTitle("evolutionSteer | simulationTime: " + simulationTime + " ms"); + //println(simulationTime); setMenu(6); } } if (menu == 5) { //simulate running - if (timer <= 900) { + maxFrames = simDuration*frames; + if (timer <= maxFrames) { + background(255); keysToMoveCamera(); simulationImage.beginDraw(); simulationImage.background(120, 200, 255); for (int s = 0; s < speed; s++) { - if (timer < 900) { - simulateCurrentCreature(); + if (timer < simDuration*frames) { + if(simulateCurrentCreature()){ maxFrames += simDuration*frames; } } } - setAverages(); + currentCreature.setAverages(); if (speed < 30) { for (int s = 0; s < speed; s++) { moveCamera(); } } else { - camX = averageX; - camY = averageY; - camZ = averageZ; + camX = currentCreature.averageX; + camY = currentCreature.averageY; + camZ = currentCreature.averageZ; } float camDist = (height/2.0) / tan(PI*30.0 / 180.0); simulationImage.pushMatrix(); @@ -1107,7 +1204,7 @@ void draw() { drawPosts(simulationImage); drawGround(simulationImage); currentCreature.drawCreature(simulationImage,false); - drawArrow(averageX,averageY,averageZ,simulationImage); + drawArrow(currentCreature.averageX,currentCreature.averageY,currentCreature.averageZ,simulationImage); simulationImage.popMatrix(); simulationImage.endDraw(); image(simulationImage,0,0,width/windowSizeMultiplier, @@ -1117,7 +1214,7 @@ void draw() { drawSkipButton(); drawOtherButtons(); } - if (timer == 900) { + if (timer == maxFrames) { if (speed < 30) { noStroke(); fill(0, 0, 0, 130); @@ -1128,28 +1225,28 @@ void draw() { textAlign(CENTER); textFont(font, 96); text("Creature's "+fitnessName+":", windowWidth/2, 300); - text(nf(getFitness(),0,2) + " "+fitnessUnit, windowWidth/2, 400); + text(nf(currentCreature.getFitness(),0,2) + " "+fitnessUnit, windowWidth/2, 400); } else { - timer = 1020; + timer = maxFrames+(2*frames); } setFitness(creaturesTested); } - if (timer >= 1020) { + if (timer >= maxFrames+(2*frames)) { setMenu(4); creaturesTested++; - if (creaturesTested == 1000) { + if (creaturesTested == nbCreatures) { setMenu(6); } camX = 0; } - if (timer >= 900) { + if (timer >= simDuration*frames) { timer += speed; } } if (menu == 6) { //sort c2 = new ArrayList(0); - for(int i = 0; i < 1000; i++){ + for(int i = 0; i < nbCreatures; i++){ c2.add(c[i]); } c2 = quickSort(c2); @@ -1157,8 +1254,8 @@ void draw() { for (int i = 0; i < 29; i++) { percentile.get(gen+1)[i] = c2.get(p[i]).d; } - creatureDatabase.add(c2.get(999).copyCreature(-1,false,false)); - creatureDatabase.add(c2.get(499).copyCreature(-1,false,false)); + creatureDatabase.add(c2.get(nbCreatures-1).copyCreature(-1,false,false)); + creatureDatabase.add(c2.get(nbCreatures/2-1).copyCreature(-1,false,false)); creatureDatabase.add(c2.get(0).copyCreature(-1,false,false)); Integer[] beginBar = new Integer[barLen]; @@ -1170,7 +1267,7 @@ void draw() { for (int i = 0; i < 101; i++) { beginSpecies[i] = 0; } - for (int i = 0; i < 1000; i++) { + for (int i = 0; i < nbCreatures; i++) { int bar = floor(c2.get(i).d*histBarsPerMeter-minBar); if (bar >= 0 && bar < barLen) { barCounts.get(gen+1)[bar]++; @@ -1207,16 +1304,18 @@ void draw() { screenImage.pushMatrix(); screenImage.scale(10.0/scaleToFixBug*windowSizeMultiplier); float transition = 0.5-0.5*cos(min(float(timer)/60, PI)); - for (int j = 0; j < 1000; j++) { + float xWidth = windowWidth / (gridX+1) / 10.0; + float yHeight = (windowHeight - gridHeightCrop) / (gridY+1) / 10.0; + for (int j = 0; j < nbCreatures; j++) { Creature cj = c2.get(j); - int j2 = cj.id-(gen*1000)-1; - int x1 = j2%40; - int y1 = floor(j2/40); - int x2 = j%40; - int y2 = floor(j/40)+1; + int j2 = cj.id-(gen*nbCreatures)-1; + int x1 = j2%gridX; + int y1 = floor(j2/gridX); + int x2 = j%gridX; + int y2 = floor(j/gridX)+1; float x3 = inter(x1, x2, transition); float y3 = inter(y1, y2, transition); - screenImage.translate((x3*3+5.5)*scaleToFixBug, (y3*2.5+4)*scaleToFixBug, 0); + screenImage.translate(((x3+1)*xWidth)*scaleToFixBug, ((y3+0.5)*yHeight-(gridHeightCrop/2))*scaleToFixBug, 0); cj.drawCreature(screenImage,true); } screenImage.popMatrix(); @@ -1232,23 +1331,32 @@ void draw() { drawScreenImage(1); setMenu(9); } + } else if(menu == 14){ + fill(0); + background(255, 200, 130); + textSize(60); + text("Please wait while loading...", windowWidth/2, 200); + } else if(menu == 15){ + fill(0); + background(255, 200, 130); + textSize(60); + text("Please wait while saving...", windowWidth/2, 200); } float mX = mouseX/windowSizeMultiplier; float mY = mouseY/windowSizeMultiplier; + float xWidth = windowWidth / (gridX+1); + float yHeight = (windowHeight - gridHeightCrop) / (gridY+1); prevStatusWindow = statusWindow; if (abs(menu-9) <= 2 && gensToDo == 0 && !drag) { - if (abs(mX-639.5) <= 599.5) { - if (menu == 7 && abs(mY-329) <= 312) { - statusWindow = creaturesInPosition[floor((mX-40)/30)+floor((mY-17)/25)*40]; - } - else if (menu >= 9 && abs(mY-354) <= 312) { - statusWindow = floor((mX-40)/30)+floor((mY-42)/25)*40; - } - else { - statusWindow = -4; - } - } - else { + int mXI = floor((mX-(xWidth/2))/xWidth); + int mYI = floor((mY-(gridHeightCrop/2))/yHeight); + if(mXI < 0 || mXI >= gridX){ mXI = -1; } + if(mYI < 0 || mYI >= gridY){ mYI = -1; } + if (menu == 7 && mXI >= 0 && mYI >= 0) { + statusWindow = creaturesInPosition[mXI+mYI*gridX]; + } else if (menu >= 9 && mXI >= 0 && mYI >= 0) { + statusWindow = mXI+mYI*gridX; + } else { statusWindow = -4; } } else if (menu == 1 && genSelected >= 1 && gensToDo == 0 && !drag) { @@ -1266,9 +1374,9 @@ void draw() { } if (menu == 10) { //Kill! - for (int j = 0; j < 500; j++) { + for (int j = 0; j < nbCreatures/2; j++) { if(random(0,1) < getSB(gen)){ - float f = float(j)/1000; + float f = float(j)/nbCreatures; float rand = (pow(random(-1, 1), 3)+1)/2; //cube function slowDies = (f <= rand); }else{ @@ -1278,15 +1386,13 @@ void draw() { int j3; if (slowDies) { j2 = j; - j3 = 999-j; + j3 = nbCreatures-1-j; } else { - j2 = 999-j; + j2 = nbCreatures-1-j; j3 = j; } - Creature cj = c2.get(j2); - cj.alive = true; - Creature ck = c2.get(j3); - ck.alive = false; + c2.get(j2).alive = true; + c2.get(j3).alive = false; } if (stepbystep) { drawScreenImage(2); @@ -1297,28 +1403,71 @@ void draw() { } if (menu == 12) { //Reproduce and mutate justGotBack = true; - for (int j = 0; j < 500; j++) { + for (int j = 0; j < nbCreatures/2; j++) { int j2 = j; - if (!c2.get(j).alive) j2 = 999-j; + if (!c2.get(j).alive) j2 = nbCreatures-1-j; Creature cj = c2.get(j2); - Creature cj2 = c2.get(999-j2); + Creature cj2 = c2.get(nbCreatures-1-j2); - c2.set(j2, cj.copyCreature(cj.id+1000,true,false)); //duplicate - c2.set(999-j2, cj.modified(cj2.id+1000)); //mutated offspring 1 + if(massExtinction && random(0,1) < massExtinctionPercentage){ // mass extinction + c2.set(j2, createNewCreature(cj.id+nbCreatures-1)); // new creatures arises ! + c2.set(nbCreatures-1-j2, createNewCreature(cj2.id+nbCreatures-1)); + } else { + c2.set(j2, cj.copyCreature(cj.id+nbCreatures,true,false)); //duplicate + if(enableRadioactivity && j >= nbCreatures/2 - freshBloodNumber) { + c2.set(nbCreatures-1-j2, createNewCreature(cj2.id+nbCreatures-1)); //brand new creatures + } else if(enableRadioactivity && j >= nbCreatures/2 - radioactiveNumber - freshBloodNumber){ + c2.set(nbCreatures-1-j2, cj.modified(cj2.id+nbCreatures, radioactiveMutator)); //radioactive offspring + } else { + c2.set(nbCreatures-1-j2, cj.modified(cj2.id+nbCreatures, 1.0)); //mutated offspring 1 + } + } } - for (int j = 0; j < 1000; j++) { + for (int j = 0; j < nbCreatures; j++) { Creature cj = c2.get(j); - c[cj.id-(gen*1000)-1001] = cj.copyCreature(-1,false,false); + c[cj.id%nbCreatures] = cj.copyCreature(-1,false,false); } drawScreenImage(3); gen++; + genSelected = gen; recalcSlider(); + massExtinction = false; if (stepbystep) { setMenu(13); } else { + if(autoSave > 0 && gen > 0){ + if(gen%autoSave == 0){ + hasAutosaveWorked = false; + saveSelected(new File(dataPath("")+"/autosave-tmp.gz")); + if(hasAutosaveWorked){ + String finalfilename = ""; + if(autoSaveTimecode){ + finalfilename = dataPath("")+"/autosave-"+year()+"-"+month()+"-"+day()+"_"+hour()+"-"+minute()+"-"+second()+".gz"; + } else { + finalfilename = dataPath("")+"/autosave.gz"; + } + try{ + Path source = Paths.get(dataPath("")+"/autosave-tmp.gz"); + File autosaveGenuine = new File(finalfilename); + if(autosaveGenuine.isFile()) { + autosaveGenuine.delete(); + } + Files.move(source, Paths.get(finalfilename)); + } catch(Exception e){ + writeToErrorLog(e); + } + } + } + } + if(autoPause > 0){ + if(gen%autoPause == 0){ + gensToDo = 0; + } + } setMenu(1); } } if(menu%2 == 1 && abs(menu-10) <= 3){ + background(gridBGColor); image(screenImage, 0, 0, 1280, 720); } if (menu == 1 || gensToDo >= 1) { @@ -1327,12 +1476,14 @@ void draw() { noStroke(); if (gen >= 1) { textAlign(CENTER); - if (gen >= 5) { - genSelected = round((sliderX-760)*(gen-1)/410)+1; - } else { - genSelected = round((sliderX-760)*gen/410); + if(drag){ + if (gen >= 5) { + genSelected = round((sliderX-760)*(gen-1)/410)+1; + } else { + genSelected = round((sliderX-760)*gen/410); + } + sliderX = min(max(sliderX+(mX-25-sliderX)*0.2, 760), 1170); } - if (drag) sliderX = min(max(sliderX+(mX-25-sliderX)*0.2, 760), 1170); fill(100); rect(760, 340, 460, 50); fill(220); @@ -1399,6 +1550,9 @@ float getSB(int g){ return 1.0; //return 0.7+0.3*cos(g*(2*PI)/50.0); } +void recalcSlider() { + sliderX = 760+(genSelected*410/gen); +} void keysToMoveCamera(){ if(keyPressed){ if(key == 'w'){ @@ -1437,13 +1591,48 @@ void keysToMoveCamera(){ camVA = min(max(camVA,-PI*0.499),-PI*0.001); } void keyPressed(){ - if(key == 't'){ - foodAngleChange += 5.0/360.0*(2*PI); - setMenu(1); - } - if(key == 'g'){ - foodAngleChange -= 5.0/360.0*(2*PI); - setMenu(1); + if (key == CODED) { + if (keyCode == LEFT) { + genSelected -= 1; + if(genSelected < 0) { genSelected = 0; } + } else if (keyCode == RIGHT) { + genSelected += 1; + if(genSelected > gen) { genSelected = gen; } + } + } else { + if(key == 'b'){ + brainiac(); + } + if(key == 't'){ + foodAngleChange += 5.0/360.0*(2*PI); + setMenu(1); + } + if(key == 'g'){ + foodAngleChange -= 5.0/360.0*(2*PI); + setMenu(1); + } + if(key == 'r'){ + enableRadioactivity = !enableRadioactivity; + setMenu(1); + } + if(key == 'y'){ + giftForChompSec -= 0.5; + if(giftForChompSec < 0.5) { giftForChompSec = (float)0.5; } + giftForChompFrames = ceil(giftForChompSec*frames); + setMenu(1); + } + if(key == 'h'){ + giftForChompSec += 0.5; + giftForChompFrames = ceil(giftForChompSec*frames); + setMenu(1); + } + if(key == 'k'){ + massExtinction = true; + setMenu(1); + } + if(key == 'p'){ + powerDouble(); + } } } void drawStats(float x, float y, float z, float size){ @@ -1455,15 +1644,15 @@ void drawStats(float x, float y, float z, float size){ scale(size); text(toRealName(currentCreature.name), 0, 32); text("Creature ID: "+currentCreature.id, 0, 64); - text("Time: "+nf(timer/60.0,0,2)+" / 15 sec.", 0, 96); + text("Time: "+nf(float(timer)/float(frames),0,2)+" / "+simDuration+" sec.", 0, 96); text("Playback Speed: x"+max(1,speed), 0, 128); String extraWord = "used"; if(energyDirection == -1){ extraWord = "left"; } - text("X: "+nf(averageX/5.0,0,2)+"", 0, 160); - text("Y: "+nf(-averageY/5.0,0,2)+"", 0, 192); - text("Z: "+nf(-averageZ/5.0,0,2)+"", 0, 224); + text("X: "+nf(currentCreature.averageX/5.0,0,2)+"", 0, 160); + text("Y: "+nf(-currentCreature.averageY/5.0,0,2)+"", 0, 192); + text("Z: "+nf(-currentCreature.averageZ/5.0,0,2)+"", 0, 224); //text("Energy "+extraWord+": "+nf(energy,0,2)+" yums", 0, 256); //text("A.N.Nausea: "+nf(averageNodeNausea,0,2)+" blehs", 0, 256); @@ -1523,46 +1712,11 @@ void setGlobalVariables(Creature thisCreature) { camVA = -0.5; camHA = 0.0; simulationTimer = 0; - energy = baselineEnergy; totalNodeNausea = 0; averageNodeNausea = 0; cumulativeAngularVelocity = 0; - foodAngle = 0.0; - chomps = 0; - foodX = 0; - foodY = 0; - foodZ = 0; - setFoodLocation(); -} -void setFoodLocation(){ - setAverages(); - foodAngle += currentCreature.foodPositions[chomps][0]; - float sinA = sin(foodAngle); - float cosA = cos(foodAngle); - float furthestNodeForward = 0; - for(int i = 0; i < currentCreature.n.size(); i++){ - Node ni = currentCreature.n.get(i); - float newX = (ni.x-averageX)*cosA-(ni.z-averageZ)*sinA; - if(newX >= furthestNodeForward){ - furthestNodeForward = newX; - } - } - float d = MIN_FOOD_DISTANCE+(MAX_FOOD_DISTANCE-MIN_FOOD_DISTANCE)*currentCreature.foodPositions[chomps][2]; - foodX = foodX+cos(foodAngle)*(furthestNodeForward+d); - foodZ = foodZ+sin(foodAngle)*(furthestNodeForward+d); - foodY = currentCreature.foodPositions[chomps][1]; - startingFoodDistance = getCurrentFoodDistance(); -} -float getCurrentFoodDistance(){ - float closestDist = 9999; - for(int i = 0; i < currentCreature.n.size(); i++){ - Node n = currentCreature.n.get(i); - float distFromFood = dist(n.x,n.y,n.z,foodX,foodY,foodZ)-0.4; - if(distFromFood < closestDist){ - closestDist = distFromFood; - } - } - return closestDist; + currentCreature.initParameters(); + currentCreature.calculateNextFoodLocation(); } int[] getNewCreatureName(){ float indexOfChoice = random(0,TOTAL_PLEDGED); @@ -1593,20 +1747,313 @@ String rankify(int s){ return s+"th"; } } -float getFitness(){ - Boolean hasNodeOffGround = false; - for(int i = 0; i < currentCreature.n.size(); i++){ - if(currentCreature.n.get(i).y <= -0.2001){ - hasNodeOffGround = true; +void setFitness(int i){ + c[i].d = currentCreature.getFitness(); + c[i].chomps = currentCreature.chomps; + c[i].timePerChomp = currentCreature.timePerChomp; +} + +void brainiac(){ + for(int i = 0; i < c.length; i++){ + c[i] = c[i].copyCreature(-1,true,false); + c[i].brain = c[i].brain.copyExpandedBrain(); + } + setMenu(4); +} +void powerDouble(){ + Creature firstC = creatureDatabase.get((gen-1)*3); + Creature middleC = creatureDatabase.get((gen-1)*3+1); + Creature lastC = creatureDatabase.get((gen-1)*3+2); + Integer[] lastBarcount = barCounts.get(barCounts.size()-1); + Float[] lastPercentile = percentile.get(percentile.size()-1); + Integer[] lastSpeciesCounts = speciesCounts.get(speciesCounts.size()-1); + Integer lastTopSpeciesCounts = topSpeciesCounts.get(topSpeciesCounts.size()-1); + + gridY = gridY*2; + nbCreatures = nbCreatures*2; initPercentiles(); + gen = 0; + genSelected = 0; + creaturesInPosition = new int[nbCreatures]; + c2 = new ArrayList(); + for(int i = 0; i < c.length; i++){ + c2.add(c[i]); + } + c = new Creature[nbCreatures]; + for(int i = 0; i < c2.size(); i++){ + c[i] = c[i+nbCreatures/2] = c2.get(i); + } + + creatureDatabase.clear(); + barCounts.clear(); barCounts.add(lastBarcount); + percentile.clear(); percentile.add(lastPercentile); + speciesCounts.clear(); speciesCounts.add(lastSpeciesCounts); + topSpeciesCounts.clear(); topSpeciesCounts.add(lastTopSpeciesCounts); + setMenu(4); +} + +Creature createNewCreature(int index){ + int nodeNum = int(random(4, 8)); + int muscleNum = int(random(nodeNum, nodeNum*3)); + ArrayList n = new ArrayList(nodeNum); + ArrayList m = new ArrayList(muscleNum); + for (int i = 0; i < nodeNum; i++) { + n.add(new Node(random(-1, 1), random(-1, 1), random(-1, 1), + 0, 0, 0, 0.4, random(0, 1))); //replaced all nodes' sizes with 0.4, used to be random(0.1,1), random(0,1) + } + for (int i = 0; i < muscleNum; i++) { + int tc1 = 0; + int tc2 = 0; + if (i < nodeNum-1) { + tc1 = i; + tc2 = i+1; + } else { + tc1 = int(random(0, nodeNum)); + tc2 = tc1; + while (tc2 == tc1) { + tc2 = int(random(0, nodeNum)); + } + } + float s = 0.8; + if (i >= 10) { + s *= 1.414; } + float len = random(0.5,1.5); + m.add(new Muscle(tc1, tc2, len, random(0.015, 0.06))); } - if(hasNodeOffGround){ - float withinChomp = max(1.0-getCurrentFoodDistance()/startingFoodDistance,0); - return chomps+withinChomp;//cumulativeAngularVelocity/(n.size()-2)/pow(averageNodeNausea,0.3);// /(2*PI)/(n.size()-2); //dist(0,0,averageX,averageZ)*0.2; // Multiply by 0.2 because a meter is 5 units for some weird reason. - }else{ - return 0; + return new Creature(null, index+1, new ArrayList(n), new ArrayList(m), 1.0, null, null); +} +public void writeToErrorLog(Exception e){ + String[] error = new String[100]; + error[0] = e.toString(); + for(int i = 0; i < e.getStackTrace().length; i++){ + error[i+1] = e.getStackTrace()[i].toString(); + } + saveStrings("error.log", error); +} + +public void fileSelected(File file){ + if(file != null){ + try{ + JsonFactory factory = new JsonFactory(); + JsonParser p = factory.createParser(new GZIPInputStream(new FileInputStream(file.getAbsolutePath()))); + loadFromJson(p); + setMenu(1); + randomSeed(SEED); + }catch(Exception e){ + writeToErrorLog(e); + } + } else { + setMenu(1); } } -void setFitness(int i){ - c[i].d = getFitness(); -} + +public void saveSelected(File file){ + saveFunc(file, false); +} +public void saveSelectedLight(File file){ + saveFunc(file, true); +} +public void saveFunc(File file, boolean light){ + if(file != null){ + try{ + JsonFactory factory = new JsonFactory(); + GZIPOutputStream out = new GZIPOutputStream(new FileOutputStream(file.getAbsolutePath())); + JsonGenerator generator = factory.createGenerator(out, JsonEncoding.UTF8); + generator.writeStartObject(); + saveToJson(generator, light); + generator.writeEndObject(); + generator.close(); + out.close(); + hasAutosaveWorked = true; + setMenu(1); + }catch(Exception e){ + writeToErrorLog(e); + } + } else { + setMenu(1); + } +} + +public void saveToJson(JsonGenerator g, boolean light){ + try{ + g.writeNumberField("version", 6); + g.writeNumberField("seed", SEED); + g.writeNumberField("foodChange", foodAngleChange); + g.writeNumberField("giftForChompSec", giftForChompSec); + if(light) { g.writeNumberField("gen", 1); } + else { g.writeNumberField("gen", gen); } + g.writeNumberField("nbcreatures", nbCreatures); + g.writeNumberField("gridX", gridX); + g.writeNumberField("gridY", gridY); + + int l = 0, l2 = -1; + if(light) { l = creatureDatabase.size() - 6; } + g.writeArrayFieldStart("creatureDatabase"); + for(int i = l; i < creatureDatabase.size(); i++){ + if(creatureDatabase.get(i) != null){ + if(light) { l2 = i - creatureDatabase.size() + 6; } + g.writeStartObject(); creatureDatabase.get(i).saveToJson(g, l2); g.writeEndObject(); + } + } + g.writeEndArray(); + + l = 0; + if(light) { l = barCounts.size() - 2; } + g.writeArrayFieldStart("barCounts"); + for(int i = l; i < barCounts.size(); i++){ + g.writeStartArray(); + for(int j = 0; j < barCounts.get(i).length; j++){ + g.writeNumber(barCounts.get(i)[j]); + } + g.writeEndArray(); + } + g.writeEndArray(); + + l = 0; + if(light) { l = percentile.size() - 2; } + g.writeArrayFieldStart("percentiles"); + for(int i = l; i < percentile.size(); i++){ + g.writeStartArray(); + for(int j = 0; j < percentile.get(i).length; j++){ + g.writeNumber(percentile.get(i)[j]); + } + g.writeEndArray(); + } + g.writeEndArray(); + + l = 0; + if(light) { l = speciesCounts.size() - 2; } + g.writeArrayFieldStart("species"); + for(int i = l; i < speciesCounts.size(); i++){ + g.writeStartArray(); + for(int j = 0; j < speciesCounts.get(i).length; j++){ + g.writeNumber(speciesCounts.get(i)[j]); + } + g.writeEndArray(); + } + g.writeEndArray(); + + l2 = -1; + g.writeArrayFieldStart("creatureArray"); + for(int i = 0; i < c.length; i++){ + if(light) { l2 = i + nbCreatures + 1; } + g.writeStartObject(); c[i].saveToJson(g, l2); g.writeEndObject(); + } + g.writeEndArray(); + + l = 0; + if(light) { l = topSpeciesCounts.size() - 2; } + g.writeArrayFieldStart("topSpecies"); + for(int i = l; i < topSpeciesCounts.size(); i++){ + g.writeNumber(topSpeciesCounts.get(i)); + } + g.writeEndArray(); + } catch(Exception e){ + writeToErrorLog(e); + } +} + +public void loadFromJson(JsonParser p){ + try{ + if (p.nextToken() != JsonToken.START_OBJECT) { + throw new IOException("Expected data to start with an Object"); + } + while(p.nextToken() != JsonToken.END_OBJECT){ + String fieldName = p.getCurrentName(); + JsonToken token = p.nextToken(); + if(fieldName.equals("seed")){ SEED = p.getIntValue(); } + else if(fieldName.equals("version")){ + if(p.getFloatValue() < 6){ println("WARNING file may be incompatible"); } + } + else if(fieldName.equals("foodChange")){ foodAngleChange = p.getFloatValue(); } + else if(fieldName.equals("gen")){ gen = p.getIntValue(); genSelected = gen; } + else if(fieldName.equals("nbcreatures")){ nbCreatures = p.getIntValue(); initPercentiles(); } + else if(fieldName.equals("gridX")){ gridX = p.getIntValue(); } + else if(fieldName.equals("gridY")){ gridX = p.getIntValue(); } + else if(fieldName.equals("giftForChompSec")){ + giftForChompSec = p.getFloatValue(); giftForChompFrames = ceil(giftForChompSec*frames); + } + else if(fieldName.equals("creatureDatabase")){ + creatureDatabase.clear(); + if (token != JsonToken.START_ARRAY) { throw new IOException("Expected Array"); } + while((token = p.nextToken()) != JsonToken.END_ARRAY){ + if (token == JsonToken.START_OBJECT){ + Creature creature = new Creature(new int[2], 0, new ArrayList(), new ArrayList(), 1.0, null, null); + creature.loadFromJson(p); + creatureDatabase.add(creature); + } + } + } + else if(fieldName.equals("barCounts")){ + barCounts.clear(); + if (token != JsonToken.START_ARRAY) { throw new IOException("Expected Array"); } + int i = 0; + while((token = p.nextToken()) != JsonToken.END_ARRAY){ + if (token == JsonToken.START_ARRAY){ + int j = 0; + Integer[] tmpBar = new Integer[barLen]; + while(p.nextToken() != JsonToken.END_ARRAY){ + tmpBar[j] = p.getIntValue(); + j += 1; + } + barCounts.add(tmpBar); + i += 1; + } + } + } + else if(fieldName.equals("percentiles")){ + percentile.clear(); + if (token != JsonToken.START_ARRAY) { throw new IOException("Expected Array"); } + while((token = p.nextToken()) != JsonToken.END_ARRAY){ + if (token == JsonToken.START_ARRAY){ + int j = 0; + Float[] tmpPercentile = new Float[29]; + while(p.nextToken() != JsonToken.END_ARRAY){ + tmpPercentile[j] = p.getFloatValue(); + j += 1; + } + percentile.add(tmpPercentile); + } + } + } + else if(fieldName.equals("species")){ + speciesCounts.clear(); + if (token != JsonToken.START_ARRAY) { throw new IOException("Expected Array"); } + while((token = p.nextToken()) != JsonToken.END_ARRAY){ + if (token == JsonToken.START_ARRAY){ + int j = 0; + Integer[] tmpSpecies = new Integer[101]; + while(p.nextToken() != JsonToken.END_ARRAY){ + tmpSpecies[j] = p.getIntValue(); + j += 1; + } + speciesCounts.add(tmpSpecies); + } + } + } + else if(fieldName.equals("topSpecies")){ + topSpeciesCounts.clear(); + if (token != JsonToken.START_ARRAY) { throw new IOException("Expected Array"); } + while(p.nextToken() != JsonToken.END_ARRAY){ + topSpeciesCounts.add(p.getIntValue()); + } + } + else if(fieldName.equals("creatureArray")){ + c = new Creature[nbCreatures]; + if (token != JsonToken.START_ARRAY) { throw new IOException("Expected Array"); } + int i = 0; + while((token = p.nextToken()) != JsonToken.END_ARRAY){ + if (token == JsonToken.START_OBJECT){ + Creature creature = new Creature(new int[2], 0, new ArrayList(), new ArrayList(), 1.0, null, null); + creature.loadFromJson(p); + c[i] = creature; + c2.add(c[i]); + i += 1; + } + } + } + } + } catch(Exception e){ + writeToErrorLog(e); + } +} \ No newline at end of file