diff --git a/Player.pde b/Player.pde index 91341a7..2087308 100644 --- a/Player.pde +++ b/Player.pde @@ -3,7 +3,7 @@ class Player{ float[] lag_coor; float[] velo; float FRICTION = 0.85; - float ACCEL = 2; + float ACCEL = 1.75; float R_ACCEL = 0.05; float THICKNESS = 11; float EPS = 0.1; diff --git a/Spider.pde b/Spider.pde index fd04f1f..cfc3ea0 100644 --- a/Spider.pde +++ b/Spider.pde @@ -1,9 +1,12 @@ -class Spider{ +import java.util.HashSet; //<>// //<>// +import java.util.ArrayList; + +class Spider { float BODY_SPAN = 4; float MAX_LEG_SPAN = 37; - float Ldist = MAX_LEG_SPAN*0.25; - int ITER_TIME = (int)random(0,SIBSC); - + float Ldist = MAX_LEG_SPAN * 0.25f; + int ITER_TIME = (int) random(0, SIBSC); + float[] genome; float[] coor; float[][] leg_coor; @@ -12,110 +15,178 @@ class Spider{ int generation; int birth_tick; Spider parent; - ArrayList swattersSeen = new ArrayList(0); - public Spider(int i, Room room){ + // Replaced ArrayList with HashSet for O(1) membership checks + HashSet swattersSeen = new HashSet<>(); + + // Class fields for performance and caching + private final float HALF_PI = PI / 2; + private final float TWO_PI = PI * 2; + private final float MAX_LEG_SPAN_SQ = MAX_LEG_SPAN * MAX_LEG_SPAN; + private color cachedColor = -1; // -1 means not calculated yet + private int lastSwatterCount = -1; // Track when we need to recalculate + + public Spider(int i, Room room) { parent = null; genome = new float[GENOME_LENGTH]; - for(int g = 0; g < GENOME_LENGTH; g++){ - genome[g] = random(0.2,0.4); + for (int g = 0; g < GENOME_LENGTH; g++) { + genome[g] = random(0.2f, 0.4f); } coor = new float[2]; - for(int d = 0; d < 2; d++){ - coor[d] = random(0,room.getMaxDim(d)); + for (int d = 0; d < 2; d++) { + coor[d] = random(0, room.getMaxDim(d)); } leg_coor = new float[LEG_COUNT][2]; - float ang = random(0,1); - for(int L = 0; L < LEG_COUNT; L++){ - float angL = (L+ang)*PI/2; - int genome_index = L*GENES_PER_LEG+1; + float ang = random(0, 1); + for (int L = 0; L < LEG_COUNT; L++) { + float angL = (L + ang) * HALF_PI; + int genome_index = L * GENES_PER_LEG + 1; float distance = genome[genome_index]; - leg_coor[L][0] = coor[0]+cos(angL)*distance; - leg_coor[L][1] = coor[1]+sin(angL)*distance; + leg_coor[L][0] = coor[0] + cos(angL) * distance; + leg_coor[L][1] = coor[1] + sin(angL) * distance; } index = i; generation = 0; increment_(); } - float clip_(float val, Room room, int dim){ + + float clip_(float val, Room room, int dim) { float min_ = 0; float max_ = room.getMaxDim(dim); - return min(max(val,min_),max_); + return min(max(val, min_), max_); } - float cursorOnSpider(){ + + float cursorOnSpider() { float[] realCoor = room.wallCoor_to_realCoor(coor); g.pushMatrix(); aTranslate(realCoor); float MAX_DIST_MOUSE = 100; float value = -99999; - float x1 = g.screenX(0,0,0); - float y1 = g.screenY(0,0,0); - float x2 = g.screenX(0,MAX_LEG_SPAN,0); - float y2 = g.screenY(0,MAX_LEG_SPAN,0); - float distFromCenter = dist(x1,y1,width/2,height/2); - if(distFromCenter < dist(x1,y1,x2,y2) && distFromCenter < MAX_DIST_MOUSE){ - value = g.screenZ(0,0,0); + float x1 = g.screenX(0, 0, 0); + float y1 = g.screenY(0, 0, 0); + float x2 = g.screenX(0, MAX_LEG_SPAN, 0); + float y2 = g.screenY(0, MAX_LEG_SPAN, 0); + float distFromCenter = dist(x1, y1, width / 2, height / 2); + if (distFromCenter < dist(x1, y1, x2, y2) && distFromCenter < MAX_DIST_MOUSE) { + value = g.screenZ(0, 0, 0); } g.popMatrix(); return value; } - color getColor(){ - int c = swattersSeen.size(); - if(c == 0 || c == 1){ - return color(0,0,0,255); - }else{ - if(c < 6){ - float fac = (c-1)/5.0; - return color(0,fac*140,255-fac*255,255); - }else{ - float fac = min(1,(c-6)/19.0); - return color(255*fac,140-fac*140,0,255); - } + + color getColor() { + int currentSwatterCount = swattersSeen.size(); + if (cachedColor != -1 && currentSwatterCount == lastSwatterCount) { + return cachedColor; } + + float logScale = currentSwatterCount > 0 ? log(currentSwatterCount + 1) / log(10000) : 0; + color newColor; + + if (currentSwatterCount == 0) { + newColor = color(0, 0, 0, 255); + } else if (logScale <= 0.08f) { + float fac = logScale / 0.08f; + newColor = color(0, 0, fac * 180, 255); + } else if (logScale <= 0.16f) { + float fac = (logScale - 0.08f) / 0.08f; + newColor = color(0, fac * 50, 180, 255); + } else if (logScale <= 0.24f) { + float fac = (logScale - 0.16f) / 0.08f; + newColor = color(0, 100, 180 - fac * 100, 255); + } else if (logScale <= 0.32f) { + float fac = (logScale - 0.24f) / 0.08f; + newColor = color(fac * 60, 100 + fac * 60, 40, 255); + } else if (logScale <= 0.40f) { + float fac = (logScale - 0.32f) / 0.08f; + newColor = color(128 + fac * 127, 220, 0, 255); + } else if (logScale <= 0.48f) { + float fac = (logScale - 0.40f) / 0.08f; + newColor = color(255, 220 - fac * 92, 0, 255); + } else if (logScale <= 0.56f) { + float fac = (logScale - 0.48f) / 0.08f; + newColor = color(255, 128 - fac * 128, 0, 255); + } else if (logScale <= 0.64f) { + float fac = (logScale - 0.56f) / 0.08f; + newColor = color(255, fac * 20, fac * 147, 255); + } else if (logScale <= 0.72f) { + float fac = (logScale - 0.64f) / 0.08f; + newColor = color(255 - fac * 127, 20 - fac * 20, 147 - fac * 19, 255); + } else if (logScale <= 0.80f) { + float fac = (logScale - 0.72f) / 0.08f; + newColor = color(128 - fac * 128, fac * 255, 128 + fac * 127, 255); + } else if (logScale <= 0.88f) { + float fac = (logScale - 0.80f) / 0.08f; + newColor = color(fac * 98, 255, 255 - fac * 111, 255); + } else if (logScale <= 0.96f) { + float fac = (logScale - 0.88f) / 0.08f; + newColor = color(98 + fac * 157, 255 - fac * 40, 144 - fac * 144, 255); + } else { + float fac = (logScale - 0.96f) / 0.04f; + newColor = color(255, 215 + fac * 40, fac * 255, 255); + } + + cachedColor = newColor; + lastSwatterCount = currentSwatterCount; + return newColor; } - color transitionColor(color a, color b, float prog){ + + color transitionColor(color a, color b, float prog) { float newR = lerp(red(a), red(b), prog); float newG = lerp(green(a), green(b), prog); float newB = lerp(blue(a), blue(b), prog); - return color(newR, newG, newB); + float newA = lerp(alpha(a), alpha(b), prog); + return color(newR, newG, newB, newA); } - void drawSpider(Room room){ + + void drawSpider(Room room) { + float[] realCoor = room.wallCoor_to_realCoor(coor); + g.pushMatrix(); + aTranslate(realCoor); + float screenX = g.screenX(0, 0, 0); + float screenY = g.screenY(0, 0, 0); + g.popMatrix(); + + float margin = MAX_LEG_SPAN * 1.5f; + if (screenX < -margin || screenX > width + margin || + screenY < -margin || screenY > height + margin) { + return; + } + color c = getColor(); - if(this == highlight_spider){ - c = color(0,255,0); + if (this == highlight_spider) { + c = color(0, 255, 0); } - float[] realCoor = room.wallCoor_to_realCoor(coor); g.pushMatrix(); aTranslate(realCoor); g.fill(c); g.pushMatrix(); g.rotateZ(realCoor[3]); g.beginShape(); - for(int i = 0; i < 12; i++){ - float angle = i*2*PI/12; - g.vertex(cos(angle)*BODY_SPAN,2,sin(angle)*BODY_SPAN); + for (int i = 0; i < 12; i++) { + float angle = i * 2 * PI / 12; + g.vertex(cos(angle) * BODY_SPAN, 2, sin(angle) * BODY_SPAN); } g.endShape(CLOSE); g.popMatrix(); g.stroke(c); g.strokeWeight(3); - for(int L = 0; L < LEG_COUNT; L++){ + for (int L = 0; L < LEG_COUNT; L++) { float[] legRealCoor = room.wallCoor_to_realCoor(leg_coor[L]); - float[] Lcoor = aSubstract(legRealCoor,realCoor); - float[] Mcoor = multi(Lcoor,0.5); - Mcoor[0] -= sin(realCoor[3])*Ldist; - Mcoor[1] += cos(realCoor[3])*Ldist; - g.line(0,0,0,Mcoor[0],Mcoor[1],Mcoor[2]); - g.line(Mcoor[0],Mcoor[1],Mcoor[2],Lcoor[0],Lcoor[1],Lcoor[2]); + float[] Lcoor = aSubstract(legRealCoor, realCoor); + float[] Mcoor = multi(Lcoor, 0.5f); + Mcoor[0] -= sin(realCoor[3]) * Ldist; + Mcoor[1] += cos(realCoor[3]) * Ldist; + g.line(0, 0, 0, Mcoor[0], Mcoor[1], Mcoor[2]); + g.line(Mcoor[0], Mcoor[1], Mcoor[2], Lcoor[0], Lcoor[1], Lcoor[2]); } g.noStroke(); g.popMatrix(); - - if(getAge() < 200 && parent != null && parent.getAge() >= getAge()){ + if (getAge() < 200 && parent != null && parent.getAge() >= getAge()) { float[] parentCoor = room.wallCoor_to_realCoor(parent.coor); - if(realCoor[0] == parentCoor[0] && realCoor[1] == parentCoor[1]){ + if (realCoor[0] == parentCoor[0] && realCoor[1] == parentCoor[1]) { return; } g.pushMatrix(); @@ -123,247 +194,288 @@ class Spider{ g.rotateZ(realCoor[3]); g.beginShape(); g.fill(255); - float WHITE_SPAN = BODY_SPAN*4; - for(int i = 0; i < 12; i++){ - float angle = i*2*PI/12; - g.vertex(cos(angle)*WHITE_SPAN,EPS*2,sin(angle)*WHITE_SPAN); + float WHITE_SPAN = BODY_SPAN * 4; + for (int i = 0; i < 12; i++) { + float angle = i * 2 * PI / 12; + g.vertex(cos(angle) * WHITE_SPAN, EPS * 2, sin(angle) * WHITE_SPAN); } g.endShape(CLOSE); g.popMatrix(); - if(dist(realCoor[0],realCoor[1],realCoor[2],parentCoor[0],parentCoor[1],parentCoor[2]) < WHITE_SPAN*10){ + if (dist(realCoor[0], realCoor[1], realCoor[2], parentCoor[0], parentCoor[1], parentCoor[2]) < WHITE_SPAN * 10) { g.stroke(255); g.strokeWeight(20); - g.line(realCoor[0],realCoor[1],realCoor[2],parentCoor[0],parentCoor[1],parentCoor[2]); + g.line(realCoor[0], realCoor[1], realCoor[2], parentCoor[0], parentCoor[1], parentCoor[2]); g.noStroke(); } } } - float[] multi(float[] a, float m){ + + float[] multi(float[] a, float m) { float[] result = new float[a.length]; - for(int i = 0; i < a.length; i++){ - result[i] = a[i]*m; + for (int i = 0; i < a.length; i++) { + result[i] = a[i] * m; } return result; } - float[] aSubstract(float[] a, float[] b){ + + float[] aSubstract(float[] a, float[] b) { float[] result = new float[a.length]; - for(int i = 0; i < a.length; i++){ - result[i] = a[i]-b[i]; + for (int i = 0; i < a.length; i++) { + result[i] = a[i] - b[i]; } return result; } - float[] getWeightedCenter(int step, Room room, float darkest_sensed_shadow){ - float[] sum_coor = {0,0}; - float sum_weight = 0; - for(int L = 0; L < LEG_COUNT; L++){ - int genome_index = L*GENES_PER_LEG+2*step; - if(darkest_sensed_shadow < genome[L*GENES_PER_LEG+12]){ // it's below the threshold, so do the dark pattern - genome_index += 6; - } - float weight = genome[genome_index]; - sum_weight += weight; - for(int d = 0; d < 2; d++){ - sum_coor[d] += leg_coor[L][d]*weight; - } + + float[] getWeightedCenter(int step, Room room, float darkest_sensed_shadow) { + float sumX = 0, sumY = 0; + float sumWeight = 0; + boolean isDarkPattern = darkest_sensed_shadow < genome[12]; + int baseIndex = isDarkPattern ? 6 : 0; + + for (int L = 0; L < LEG_COUNT; L++) { + int genomeIndex = L * GENES_PER_LEG + 2 * step + baseIndex; + float weight = genome[genomeIndex]; + sumWeight += weight; + float legX = leg_coor[L][0]; + float legY = leg_coor[L][1]; + sumX += legX * weight; + sumY += legY * weight; } - float rx = sum_coor[0]/sum_weight; - float ry = sum_coor[1]/sum_weight; - float[] result = {rx, ry}; - return result; + + float invWeight = 1.0f / sumWeight; + return new float[] { sumX * invWeight, sumY * invWeight }; } - void placeLegs(float[] center, int step, Room room, float darkest_sensed_shadow, ArrayList spiders){ - float force_to_right_angles = 0.001; // how strongly should the spider's legs be dragged back into right angles? + + void placeLegs(float[] center, int step, Room room, float darkest_sensed_shadow, ArrayList spiders) { + final float force_to_right_angles = 0.001f; float first_angle = 0; - for(int L = 0; L < LEG_COUNT; L++){ - int genome_index = L*GENES_PER_LEG+2*step+1; - if(darkest_sensed_shadow < genome[L*GENES_PER_LEG+12]){ // it's below the threshold, so do the dark pattern - genome_index += 6; - } - float distance = genome[genome_index]*MAX_LEG_SPAN; - float delta_x = leg_coor[L][0]-center[0]; - float delta_y = leg_coor[L][1]-center[1]; - float angle = atan2(delta_y,delta_x); - if(L == 0){ + boolean isDarkPattern = darkest_sensed_shadow < genome[12]; + int baseIndex = isDarkPattern ? 6 : 0; + + for (int L = 0; L < LEG_COUNT; L++) { + int genomeIndex = L * GENES_PER_LEG + 2 * step + baseIndex + 1; + float distance = genome[genomeIndex] * MAX_LEG_SPAN; + float dx = leg_coor[L][0] - center[0]; + float dy = leg_coor[L][1] - center[1]; + float angle = atan2(dy, dx); + + if (L == 0) { first_angle = angle; - }else{ - float desired_angle = first_angle+PI/2*L; - float move = (desired_angle-angle); - while(move > PI){ - move -= 2*PI; - } - while(move < -PI){ - move += 2*PI; - } - angle += force_to_right_angles*move; - } - leg_coor[L][0] = center[0]+cos(angle)*distance; - leg_coor[L][1] = center[1]+sin(angle)*distance; - } - coor = getWeightedCenter(step,room,darkest_sensed_shadow); - for(int d = 0; d < 2; d++){ - if(coor[d] < 0){ - shiftAllBy(d,room.getMaxDim(d)); - }else if(coor[d] >= room.getMaxDim(d)){ - shiftAllBy(d,-room.getMaxDim(d)); + } else { + float desired_angle = first_angle + L * HALF_PI; + float move = desired_angle - angle; + if (move > PI) move -= TWO_PI; + else if (move < -PI) move += TWO_PI; + angle += force_to_right_angles * move; } + + leg_coor[L][0] = center[0] + cos(angle) * distance; + leg_coor[L][1] = center[1] + sin(angle) * distance; } + + coor = center; + checkBoundaries(room); + } + + private void checkBoundaries(Room room) { + float maxX = room.getMaxDim(0); + float maxY = room.getMaxDim(1); + if (coor[0] < 0) shiftAllBy(0, maxX); + else if (coor[0] >= maxX) shiftAllBy(0, -maxX); + if (coor[1] < 0) shiftAllBy(1, maxY); + else if (coor[1] >= maxY) shiftAllBy(1, -maxY); } - void shiftAllBy(int dim, float amt){ + + void shiftAllBy(int dim, float amt) { coor[dim] += amt; - for(int L = 0; L < LEG_COUNT; L++){ + for (int L = 0; L < LEG_COUNT; L++) { leg_coor[L][dim] += amt; } } - void iterate(Room room, ArrayList swatters, ArrayList spiders){ + + void iterate(Room room, ArrayList swatters, ArrayList spiders) { int cycle = whereInCycle(0); - if(cycle%SPIDER_ITER_BUCKETS == 0){ + if (cycle % SPIDER_ITER_BUCKETS == 0) { move(room, cycle, swatters, spiders); } } - float getDarkestShadow(){ - float darkest_sensed_shadow = 1.0; - for(int s = 0; s < swatters.size(); s++){ - Swatter sw = swatters.get(s); - float x = sw.coor[0]; - float y1 = sw.coor[1]-R; - float y2 = sw.coor[1]+R; - for(int L = 0; L < LEG_COUNT; L++){ - if(abs(leg_coor[L][0]-sw.coor[0]) < R && abs(leg_coor[L][1]-sw.coor[1]) < R){ // it's under the shadow! - if(!swattersSeen.contains(sw.visIndex)){ - swattersSeen.add(sw.visIndex); - swattersSeenTotal++; + + private float lastDarkestShadow = 1.0f; + private int lastCheckTick = -1; + + float getDarkestShadow() { + if (lastCheckTick == ticks) { + return lastDarkestShadow; + } + lastCheckTick = ticks; + float darkest_sensed_shadow = 1.0f; + float R2 = R * R; + + for (Swatter sw : swatters) { + float swX = sw.coor[0]; + float swY = sw.coor[1]; + float minX = swX - R; + float maxX = swX + R; + float minY = swY - R; + float maxY = swY + R; + + boolean legFound = false; + for (int L = 0; L < LEG_COUNT && !legFound; L++) { + float legX = leg_coor[L][0]; + float legY = leg_coor[L][1]; + if (legX >= minX && legX <= maxX && legY >= minY && legY <= maxY) { + float dx = legX - swX; + float dy = legY - swY; + float distSquared = dx * dx + dy * dy; + if (distSquared < R2) { + if (!swattersSeen.contains(sw.visIndex)) { + swattersSeen.add(sw.visIndex); + swattersSeenTotal++; + } + darkest_sensed_shadow = Math.min(darkest_sensed_shadow, Math.max(sw.percentage, 0)); + legFound = true; } - darkest_sensed_shadow = min(darkest_sensed_shadow,max(sw.percentage,0)); } } } return darkest_sensed_shadow; } - void move(Room room, int cycle, ArrayList swatters, ArrayList spiders){ + + void move(Room room, int cycle, ArrayList swatters, ArrayList spiders) { float darkest_sensed_shadow = getDarkestShadow(); - int step = cycle/SPIDER_ITER_BUCKETS; + int step = cycle / SPIDER_ITER_BUCKETS; + boolean isDarkPattern = darkest_sensed_shadow < genome[12]; + int baseIndex = isDarkPattern ? 6 : 0; float[] weightedCenter = getWeightedCenter(step, room, darkest_sensed_shadow); placeLegs(weightedCenter, step, room, darkest_sensed_shadow, spiders); } - int whereInCycle(int offset){ - return (ticks+offset-ITER_TIME+SIBSC)%SIBSC; + + int whereInCycle(int offset) { + return (ticks + offset - ITER_TIME + SIBSC) % SIBSC; } - PGraphics drawGenome(){ - PGraphics panel = createGraphics(400,540); + + PGraphics drawGenome() { + PGraphics panel = createGraphics(400, 540); panel.beginDraw(); - panel.background(40,80,120); + panel.background(40, 80, 120); boolean[] shouldBeGreen = new boolean[GENOME_LENGTH]; - for(int g = 0; g < GENOME_LENGTH; g++){ + for (int g = 0; g < GENOME_LENGTH; g++) { shouldBeGreen[g] = false; } - int step = whereInCycle(0)/SPIDER_ITER_BUCKETS; - int step2 = whereInCycle(SPIDER_ITER_BUCKETS/2)/SPIDER_ITER_BUCKETS; + int step = whereInCycle(0) / SPIDER_ITER_BUCKETS; + int step2 = whereInCycle(SPIDER_ITER_BUCKETS / 2) / SPIDER_ITER_BUCKETS; float darkest = getDarkestShadow(); - for(int L = 0; L < LEG_COUNT; L++){ - int index = L*13+step*2+1; - int index2 = L*13+step2*2; - float threshold = genome[L*13+12]; - if(darkest < threshold){ - shouldBeGreen[L*13+12] = true; + for (int L = 0; L < LEG_COUNT; L++) { + int index = L * 13 + step * 2 + 1; + int index2 = L * 13 + step2 * 2; + float threshold = genome[L * 13 + 12]; + if (darkest < threshold) { + shouldBeGreen[L * 13 + 12] = true; index += 6; index2 += 6; } shouldBeGreen[index] = true; shouldBeGreen[index2] = true; } - for(int g = 0; g < GENOME_LENGTH; g++){ - panel.fill(genome[g]*255); + for (int g = 0; g < GENOME_LENGTH; g++) { + panel.fill(genome[g] * 255); panel.stroke(0); panel.strokeWeight(1); - if(shouldBeGreen[g]){ - panel.stroke(0,255,0); + if (shouldBeGreen[g]) { + panel.stroke(0, 255, 0); panel.strokeWeight(2); } - float g1 = g%GENES_PER_LEG; - float g2 = g/GENES_PER_LEG; + float g1 = g % GENES_PER_LEG; + float g2 = g / GENES_PER_LEG; float x; float y; - if(g1 == 12){ + if (g1 == 12) { x = 185; y = 170; - }else{ - x = 20*g1+10; - y = 180-(g1%2)*40+10; - if(g1 >= 6){ + } else { + x = 20 * g1 + 10; + y = 180 - (g1 % 2) * 40 + 10; + if (g1 >= 6) { x += 130; } } - y += 100*g2; - panel.rect(x,y,30,30); + y += 100 * g2; + panel.rect(x, y, 30, 30); } panel.fill(255); panel.textAlign(LEFT); panel.textSize(30); - panel.text("Spider #"+commafy(visIndex+1),20,40); - panel.text("Generation #"+commafy(generation+1),20,70); - panel.text("Age: "+nf(ticksToDays(getAge()),0,2)+" days",20,100); + panel.text("Spider #" + commafy(visIndex + 1), 20, 40); + panel.text("Generation #" + commafy(generation + 1), 20, 70); + panel.text("Age: " + nf(ticksToDays(getAge()), 0, 2) + " days", 20, 100); String p = (howManySwattersSeen() == 1) ? "" : "s"; - panel.text(commafy(howManySwattersSeen())+" swatter"+p+" seen",80,130); + panel.text(commafy(howManySwattersSeen()) + " swatter" + p + " seen", 80, 130); panel.fill(getColor()); panel.stroke(0); - panel.rect(20,109,50,25); + panel.rect(20, 109, 50, 25); panel.endDraw(); return panel; } - int howManySwattersSeen(){ + + int howManySwattersSeen() { return swattersSeen.size(); } - void randomShift(){ - float dsx = random(-MAX_LEG_SPAN/2,MAX_LEG_SPAN/2); - float dsy = random(-MAX_LEG_SPAN/2,MAX_LEG_SPAN/2); - - float dangle = random(0.1*PI,1.9*PI); - for(int L = 0; L < LEG_COUNT; L++){ - float dx = leg_coor[L][0]-coor[0]; - float dy = leg_coor[L][1]-coor[1]; - float dist_ = dist(0,0,dx,dy); - float angle = atan2(dy,dx); - float newAngle = angle+dangle; - float newDX = cos(newAngle)*dist_; - float newDY = sin(newAngle)*dist_; - leg_coor[L][0] = coor[0]+newDX+dsx; - leg_coor[L][1] = coor[1]+newDY+dsy; + + void randomShift() { + float dsx = random(-MAX_LEG_SPAN / 2, MAX_LEG_SPAN / 2); + float dsy = random(-MAX_LEG_SPAN / 2, MAX_LEG_SPAN / 2); + float dangle = random(0.1f * PI, 1.9f * PI); + for (int L = 0; L < LEG_COUNT; L++) { + float dx = leg_coor[L][0] - coor[0]; + float dy = leg_coor[L][1] - coor[1]; + float dist_ = dist(0, 0, dx, dy); + float angle = atan2(dy, dx); + float newAngle = angle + dangle; + float newDX = cos(newAngle) * dist_; + float newDY = sin(newAngle) * dist_; + leg_coor[L][0] = coor[0] + newDX + dsx; + leg_coor[L][1] = coor[1] + newDY + dsy; } coor[0] += dsx; coor[1] += dsy; } - void reincarnate(ArrayList spiders){ + + void reincarnate(ArrayList spiders) { dailyDeaths++; - parent = spiders.get((int)random(0,spiders.size())); - float MUTATION_FACTOR = 0.2; + parent = spiders.get((int) random(0, spiders.size())); + float MUTATION_FACTOR = 0.2f; genome = mutate(parent.genome, MUTATION_FACTOR); coor = deepCopy(parent.coor); leg_coor = deepCopy(parent.leg_coor); randomShift(); - generation = parent.generation+1; + generation = parent.generation + 1; increment_(); } - int getAge(){ - return ticks-birth_tick; + + int getAge() { + return ticks - birth_tick; } - void increment_(){ + + void increment_() { visIndex = totalIndex; totalIndex++; birth_tick = ticks; - swattersSeen = new ArrayList(0); + swattersSeen = new HashSet<>(); } - float getSensitivity(){ + + float getSensitivity() { float sensitivity = 0; - for(int L = 0; L < LEG_COUNT; L++){ - for(int k = 0; k < 6; k++){ - sensitivity += abs(genome[L*13+k]-genome[L*13+6+k]); + for (int L = 0; L < LEG_COUNT; L++) { + for (int k = 0; k < 6; k++) { + sensitivity += abs(genome[L * 13 + k] - genome[L * 13 + 6 + k]); } } - return sensitivity/LEG_COUNT/6*100; + return sensitivity / LEG_COUNT / 6 * 100; } - void writeData(Float[] datum){ + + void writeData(Float[] datum) { datum[0] += ticksToDays(getAge()); datum[3] += getSensitivity(); datum[5] += swattersSeen.size(); diff --git a/SpiderEvoSim.pde b/SpiderEvoSim.pde index c0b8850..a4cce88 100644 --- a/SpiderEvoSim.pde +++ b/SpiderEvoSim.pde @@ -1,6 +1,7 @@ import com.jogamp.newt.opengl.GLWindow; import processing.sound.*; +//Working branch + FPS counter int CENTER_X = 960; // try setting this to 960 or 961 if there is horizontal camera-pan-drifting String[] soundFileNames = {"slap0.wav","slap1.wav","slap2.wav","splat0.wav","splat1.wav","splat2.wav","boop1.wav","boop2.wav","jump.wav","news.wav"}; SoundFile[] sfx; @@ -9,7 +10,7 @@ Player player; KeyHandler keyHandler; int DIM_COUNT = 3; int SPIDER_ITER_BUCKETS = 4; -float SWAT_SPEED = 0.001; +float SWAT_SPEED = 0.001f; int R = 40; int STEPS_CYCLE = 3; int SIBSC = SPIDER_ITER_BUCKETS*STEPS_CYCLE; @@ -21,14 +22,16 @@ int ticks = 0; int totalIndex = 0; int totalSwatterIndex = 0; float[] camera = {0,0}; -float EPS = 0.04; +float EPS = 0.04f; PGraphics g; int playback_speed = 1; int dailyDeaths = 0; int swattersSeenTotal = 0; -float TICKS_PER_DAY = 10000; +float TICKS_PER_DAY = 10000.0f; // Keep as float but use explicit f suffix +float PER_DAY = 10000.0f; boolean TRAP_MOUSE = true; boolean lock_highlight = false; + GLWindow r; int LEG_COUNT = 4; @@ -58,8 +61,8 @@ float sig(float a){ float sig_inv(float a){ return log(a/(1-a)); } -float ticksToDays(float age){ - return age/TICKS_PER_DAY; +float ticksToDays(long age) { + return age / TICKS_PER_DAY; } color darken(color c, float perc){ float newR = red(c)*perc; @@ -92,7 +95,15 @@ float[][] deepCopy(float[][] input){ return result; } - +// Helper function to safely handle large numbers +double safeAdd(double a, double b) { + double result = a + b; + if (result == Double.POSITIVE_INFINITY || result == Double.NEGATIVE_INFINITY) { + println("Warning: Arithmetic overflow in calculations"); + return Double.MAX_VALUE; + } + return result; +} ArrayList createSpiders(Room room){ int START_SPIDER_COUNT = 300; ArrayList result = new ArrayList(0); @@ -105,13 +116,14 @@ ArrayList createSpiders(Room room){ void createSwatters(Room room, int START_SPIDER_COUNT){ swatters = new ArrayList(0); for(int s = 0; s < START_SPIDER_COUNT; s++){ - float perc = (s+0.5)/START_SPIDER_COUNT*1.4-0.4; + float perc = (s+0.5f)/START_SPIDER_COUNT*1.4f-0.4f; Swatter newSwatter = new Swatter(s, perc, room, swatters); swatters.add(newSwatter); } } void setup(){ + frameRate(60); //Frame rate limit for physics windowImages = new PImage[WINDOW_COUNT]; for(int w = 0; w < WINDOW_COUNT; w++){ windowImages[w] = loadImage("windows/w"+nf(w+1,4,0)+".png"); @@ -151,13 +163,61 @@ void setup(){ r.setPointerVisible(false); g = createGraphics(1920,1080,P3D); } -void draw(){ - doMouse(); - doPhysics(); - drawVisuals(); - image(g,0,0); - drawUI(); - frames++; +//FPS counter +ArrayList frameTimestamps = new ArrayList<>(); // To store frame timestamps +float averageFps = 0; // Average FPS over the last 4 seconds + +// Global timing variables +long physicsTiming = 0; +long renderTiming = 0; +String physicsStats = ""; +String renderStats = ""; + +void draw() { + // FPS calculation stays the same + long currentTime = millis(); + frameTimestamps.add(currentTime); + while (frameTimestamps.size() > 0 && frameTimestamps.get(0) < currentTime - 4000) { + frameTimestamps.remove(0); + } + if (frameTimestamps.size() > 1) { + float elapsedSeconds = (frameTimestamps.get(frameTimestamps.size() - 1) - frameTimestamps.get(0)) / 1000.0f; + averageFps = (frameTimestamps.size() - 1) / elapsedSeconds; + } + + doMouse(); + + long startPhysics = System.nanoTime(); + doPhysics(); + physicsTiming = System.nanoTime() - startPhysics; + + long startRender = System.nanoTime(); + drawVisuals(); + renderTiming = System.nanoTime() - startRender; + + image(g, 0, 0); + drawUI(); + + // Update stats every 30 frames + if (frames % 30 == 0) { + physicsStats = "Physics: " + (physicsTiming / 1_000_000) + "ms"; + renderStats = "Render: " + (renderTiming / 1_000_000) + "ms"; + } + + // Display stats + fill(255); + textAlign(LEFT); + textSize(24); + text(physicsStats, 10, 140); + text(renderStats, 10, 170); + + frames++; + if (camera[1] < -1) { + camera[1] = -1; + } + if (camera[1] > 1) { + camera[1] = 1; + } } void checkHighlight(){ if(!lock_highlight){ @@ -167,57 +227,96 @@ void checkHighlight(){ Spider checkHighlightHelper(){ Spider answer = null; float recordLowest = 1; - for(int s = 0; s < spiders.size(); s++){ - float score = spiders.get(s).cursorOnSpider(); - if(score > 0 && score < recordLowest){ - recordLowest = score; - answer = spiders.get(s); + if (mousePressed) { + for (Spider s : spiders) { + float score = s.cursorOnSpider(); + if (score > 0 && score < recordLowest) { + recordLowest = score; + answer = s; + } } } return answer; } -void drawUI(){ - noStroke(); - fill(0); - float M = 1; - float W = 20; - rect(width/2-M,height/2-W,M*2,W*2); - rect(width/2-W,height/2-M,W*2,M*2); - if(highlight_spider != null){ - PGraphics genomePanel = highlight_spider.drawGenome(); - image(genomePanel,width-genomePanel.width-30,height-genomePanel.height-30); - } - textAlign(LEFT); - textSize(50); - text(ticksToDate(ticks),20,65); + +void drawUI() { + // Draw crosshair + noStroke(); + fill(0); + float M = 1; // Move constants outside of method if possible + float W = 20; + rect(width/2 - M, height/2 - W, M * 2, W * 2); + rect(width/2 - W, height/2 - M, W * 2, M * 2); + + // Draw genome panel if spider is highlighted + if (highlight_spider != null) { + PGraphics genomePanel = highlight_spider.drawGenome(); + image(genomePanel, width - genomePanel.width - 30, height - genomePanel.height - 30); + } + + // Cache text settings to reduce state changes + fill(255); + + // Draw date + textAlign(LEFT); + textSize(50); + text(ticksToDate(ticks), 20, 65); + + // Draw FPS with less frequent updates + if (frames % 5 == 0) { // Update FPS display every 10 frames + cachedFpsString = "FPS: " + nf(averageFps, 0, 2); + } + textAlign(RIGHT); + textSize(25); + text(cachedFpsString, width - 20, 30); } -String dateNumToMonthString(int d){ - String[] monthNames = {"January","February","March","April","May","June","July","August","September","October","November","December"}; - int[] monthDays = {31,28,31,30,31,30,31,31,30,31,30,31}; - for(int m = 0; m < 12; m++){ - if(d < monthDays[m]){ - return monthNames[m]+" "+(d+1); +// Cache FPS string +String cachedFpsString = "FPS: 0.00"; + +String dateNumToMonthString(int d) { + String[] monthNames = {"January","February","March","April","May","June", + "July","August","September","October","November","December"}; + int[] monthDays = {31,28,31,30,31,30,31,31,30,31,30,31}; + + // Bounds check + if (d < 0) { + println("Warning: Negative date value"); + return monthNames[0] + " 1"; } - d -= monthDays[m]; - } - return monthNames[11]+monthDays[11]; -} -String ticksToDate(int t){ - float daysTotalFloat = ticksToDays(t)+0.5; - int daysTotalInt = (int)daysTotalFloat; - float timeOfDay = daysTotalFloat%1.0; - int years = daysTotalInt/365; - int days = daysTotalInt%365; - String[] TOD_LIST = {"Night","Sunrise","Morning","Afternoon","Sunset","Evening"}; - String TOD = TOD_LIST[(int)(timeOfDay*6)]; - return "Year "+(years+1)+", "+dateNumToMonthString(days)+" - "+TOD; + + for (int m = 0; m < 12; m++) { + if (d < monthDays[m]) { + return monthNames[m] + " " + (d + 1); + } + d -= monthDays[m]; + } + return monthNames[11] + " " + monthDays[11]; +} +String ticksToDate(long t) { + float daysTotalFloat = ticksToDays(t) + 0.5f; + int daysTotalInt = (int)daysTotalFloat; + float timeOfDay = daysTotalFloat % 1.0f; + + // Add bounds checking + if (daysTotalInt > 365_000_000) { + println("Warning: Date calculation overflow"); + return "Year MAX"; + } + + int years = daysTotalInt / 365; + int days = daysTotalInt % 365; + + String[] TOD_LIST = {"Night","Sunrise","Morning","Afternoon","Sunset","Evening"}; + String TOD = TOD_LIST[(int)(timeOfDay * 6)]; + + return "Year " + (years + 1) + ", " + dateNumToMonthString(days) + " - " + TOD; } void doMouse(){ if(TRAP_MOUSE){ if(frames >= 2){ - camera[0] += (mouseX-CENTER_X)*0.005; - camera[1] += (mouseY-height/2)*0.005; + camera[0] += (mouseX-CENTER_X)*0.005f; + camera[1] += (mouseY-height/2)*0.005f; } r.warpPointer(width/2,height/2); } @@ -241,20 +340,38 @@ void mousePressed() { } g.popMatrix(); } -void doPhysics(){ - iterateButtons(player); - for(int p = 0; p < playback_speed; p++){ - iterateSpiders(room); - iterateSwatters(room); - collectData(); - ticks++; - } - player.takeInputs(keyHandler); - player.doPhysics(room); +void doPhysics(){ //<>// + iterateButtons(player); + + // Process multiple ticks in batches + int batchSize = 100; + int remainingTicks = playback_speed; + + while(remainingTicks > 0) { + int currentBatch = Math.min(batchSize, remainingTicks); + processPhysicsBatch(currentBatch); + remainingTicks -= currentBatch; + } + + player.takeInputs(keyHandler); + player.doPhysics(room); +} + +void processPhysicsBatch(int batchSize) { + for(int p = 0; p < batchSize; p++) { + iterateSpiders(room); + iterateSwatters(room); + if(ticks % (long)TICKS_PER_DAY == 0) { + collectData(); + } + ticks++; + } } float getBiodiversity(){ + int spiderCount = spiders.size(); float[] meanGenome = new float[GENOME_LENGTH]; + java.util.Arrays.fill(meanGenome, 0); for(int g = 0; g < GENOME_LENGTH; g++){ meanGenome[g] = 0; } @@ -264,9 +381,10 @@ float getBiodiversity(){ } } for(int g = 0; g < GENOME_LENGTH; g++){ - meanGenome[g] /= spiders.size(); + meanGenome[g] /= spiderCount; } float[] variances = new float[GENOME_LENGTH]; + java.util.Arrays.fill(variances, 0); for(int g = 0; g < GENOME_LENGTH; g++){ variances[g] = 0; } @@ -277,47 +395,65 @@ float getBiodiversity(){ } float total_diversity = 0; for(int g = 0; g < GENOME_LENGTH; g++){ - total_diversity += sqrt(variances[g]/spiders.size()); + total_diversity += sqrt(variances[g]/spiderCount); } return total_diversity/GENOME_LENGTH*100; } -void collectData(){ - if(ticks%CHANGE_WINDOWS_EVERY == 0){ - for(int w = 0; w < windows.size(); w++){ - windows.get(w).updateShow(); - } - } - if(ticks%TICKS_PER_DAY == 0){ - Float[] datum = new Float[STAT_COUNT]; - for(int d = 0; d < STAT_COUNT; d++){ - datum[d] = new Float(0); - } - for(int s = 0; s < spiders.size(); s++){ - spiders.get(s).writeData(datum); - } - for(int d = 0; d < STAT_COUNT; d++){ - datum[d] /= spiders.size(); +void collectData() { + if (ticks % CHANGE_WINDOWS_EVERY == 0) { + for (int w = 0; w < windows.size(); w++) { + windows.get(w).updateShow(); + } } - datum[1] = (float)dailyDeaths; - datum[2] = (float)(swattersSeenTotal-dailyDeaths); - datum[4] = getBiodiversity(); - dailyDeaths = 0; - swattersSeenTotal = 0; - stats.add(datum); - statNotes.add(""); - float[] graph_dim = {100,120,575,400}; - String[] titles = {"Average Age (days)", "Daily Deaths", - "Daily Swatter Escapes", "Sensitivity (out of 100)", "Total Biodiversity (out of 100)", "Average Swatters Seen"}; - for(int d = 0; d < STAT_COUNT; d++){ - float[] graphData = new float[stats.size()]; - for(int i = 0; i < stats.size(); i++){ - graphData[i] = stats.get(i)[d]; - } - drawGraphOn(statImages[d],graphData,titles[d],graph_dim, color(128,0,0), d); + if (ticks % (long)TICKS_PER_DAY == 0) { + // Keep using Float[] to match Spider.writeData() method + Float[] datum = new Float[STAT_COUNT]; + for (int d = 0; d < STAT_COUNT; d++) { + datum[d] = 0.0f; // Use 0.0f for Float + } + + // Get spider count before division + float spiderCount = spiders.size(); + + for (int s = 0; s < spiderCount; s++) { + spiders.get(s).writeData(datum); + } + + // Prevent division by zero and ensure precise division + for (int d = 0; d < STAT_COUNT; d++) { + datum[d] = spiderCount > 0 ? datum[d] / spiderCount : 0.0f; + } + + datum[1] = (float)dailyDeaths; + datum[2] = (float)(swattersSeenTotal - dailyDeaths); + datum[4] = getBiodiversity(); + + dailyDeaths = 0; + swattersSeenTotal = 0; + stats.add(datum); + statNotes.add(""); + + float[] graph_dim = {100, 120, 575, 400}; + String[] titles = { + "Average Age (days)", + "Daily Deaths", + "Daily Swatter Escapes", + "Sensitivity (out of 100)", + "Total Biodiversity (out of 100)", + "Average Swatters Seen" + }; + + for (int d = 0; d < STAT_COUNT; d++) { + float[] graphData = new float[stats.size()]; + for (int i = 0; i < stats.size(); i++) { + graphData[i] = stats.get(i)[d]; + } + drawGraphOn(statImages[d], graphData, titles[d], graph_dim, color(128,0,0), d); + } + + sfx[9].play(); + sfx[9].amp(1.0 - min(0.8, (playback_speed-1)/200.0)); } - sfx[9].play(); - sfx[9].amp(1.0-min(0.8,(playback_speed-1)/200.0)); - } } float getUnit(float a, float b){ float diff = b-a; @@ -421,8 +557,13 @@ void drawGraphOn(PGraphics s, float[] data, String title, float[] graph_dim, col s.text(title,graph_dim[0]+graph_dim[2]*0.5,graph_dim[1]-50); s.endDraw(); } -float daylight(){ - return 0.5+0.5*cos(ticksToDays(ticks)*(2*PI)); +float daylight() { + float days = ticksToDays(ticks); + if (Float.isInfinite(days) || Float.isNaN(days)) { + println("Warning: Day calculation overflow in daylight()"); + return 0.5f; + } + return 0.5f + 0.5f * cos(days * (2 * PI)); } void drawVisuals(){ g.beginDraw(); @@ -457,15 +598,16 @@ void drawButtons(){ void aTranslate(float[] coor){ g.translate(coor[0],coor[1],coor[2]); } -void iterateSpiders(Room room){ - for(int s = 0; s < spiders.size(); s++){ - spiders.get(s).iterate(room, swatters, spiders); - } +void iterateSpiders(Room room) { + for(int s = 0; s < spiders.size(); s++) { + spiders.get(s).iterate(room, swatters, spiders); + } } -void iterateSwatters(Room room){ - for(int s = 0; s < swatters.size(); s++){ - swatters.get(s).iterate(room, spiders, swatters); - } + +void iterateSwatters(Room room) { + for(int s = 0; s < swatters.size(); s++) { //<>// + swatters.get(s).iterate(room, spiders, swatters); + } } void iterateButtons(Player player){ for(int b = 0; b < buttons.size(); b++){ diff --git a/sketch.properties b/sketch.properties new file mode 100644 index 0000000..0537529 --- /dev/null +++ b/sketch.properties @@ -0,0 +1 @@ +main=SpiderEvoSim.pde