/* built with Studio Sketchpad:
* https://sketchpad.cc
*
* observe the evolution of this sketch:
* https://studio.sketchpad.cc/sp/pad/view/ro.KIiEIVxlhvi/rev.5037
*
* authors:
* Craig Kitchen
* license (unless otherwise specified):
* creative commons attribution-share alike 3.0 license.
* https://creativecommons.org/licenses/by-sa/3.0/
*/
//This sketch is supposed to demonstrate the way that punctuated equilibrium can work in evolution.
//The idea is that species stay relatively stagnant for long periods of time, and then make a quick shift
//to a different phenotype. I always thought this didn't make any sense, but recently reading a book I
//understood how it could work. Populations stay relatively stagnant, simply because the most dominant trait
//has more chances to reproduce. Even if other traits in the population are equally adaptive, just by sheer
//number, the dominant one tends to win out. However, given enough opportunities, unlikely things become
//likely. Given enough time, a non-dominant trait will win out and become the new dominant trait. The
//process begins anew.
//The colors represent variability within a species. Occasionally, a mutant will pop up. Usually they die
//off relatively quickly, sometimes they get a bit of a hold, but die off anyway, and rarely, they become
//the new dominant trait.
// Pressing Control-R will render this sketch.
int x = 10;//number of creatures wide
int cellsize = 20;//the size of the cell on the screen (square)
boolean pause = false;
int fr = 30;
color randomcolor(){
return color(random(255),random(255),random(255));
}
class Cell {
color col;//stores the current color of the cell
color newcol;//stores the color for the cell in the next generation
public Cell(){//this constructor will assign a random color to the new cell
col = randomcolor();
newcol = col;
}
public Cell(color coli){//this constructor will assign the specified color to the new cell.
col = coli;
newcol = col;
}
public void draw(){//whenever the cell is actually drawn, the cell updates to the color for the new generation before drawing the rectangle for the cell
col = newcol;
pushStyle();
fill (col);
rect(0,0,cellsize,cellsize);
popStyle();
}
public int getcolor(){//returns the current color
return col;
}
public void setcolor(int coli){//sets the color of the cell in the next generation. It doesn't immediately set the variable col, to allow for a delay so that the cell doesn't affect cells within its generation, only the next
newcol = coli;
}
}
color startcolor = randomcolor();//the cells all start the same color, assigned at random
Cell [][] cells = new Cell[x+2][x+2];
for (int i = 1; i < x+1; i++){
for (int j = 1; j < x+1; j++){
cells[i][j] = new Cell();
}
}
//These next few lines make the screen "wrap-around" in the torus manner (top to bottom, left to right)
cells[0] = cells[x];
cells[x+1] = cells[1];
for (i = 0; i < x+2; i++){
cells[i][0] = cells[i][x];
cells[i][x+1] = cells[i][1];
}
color nextcolor(){//was planning to make a method that returned a color far away from the existing colors, but decided a random color suffices.
//ArrayList curcolors = new ArrayList();
//for (int i = 1; i < x+1; i++){
// for (int j = 1; j < x+1; j++){
// boolean newcol = 1;
// for (int k = 0; (k > curcolors.size()) && newcol; k++){
// newcol = (cells[i][j].getcolor()==curcolors.get(k))&&newcol
// }
//
// if (newcol){
// curcolors.add (cells[i][j].getcolor());
// }
// }
//}
return randomcolor();
}
void setup() {
background(255);
size(500, 601);
//size(x*cellsize, x*cellsize+1+x*x);
smooth();
frameRate(fr);
noStroke();
pushStyle();
}
void draw() {
pushStyle();
translate ((500-x*cellsize)/2,(500-x*cellsize)/2);
//iterate through the cells, but don't include the "edges", which are really just "pointers" to the other side of the field
background(255);
ArrayList censuskey = new ArrayList(); //a list of the current colors on the screen, to be generated as we iterate
ArrayList censuspop = new ArrayList(); //a list of the population of the colors. Will always have the same size as censuskey
for (int i = 1; i < x+1; i++){
for (int j = 1; j < x+1; j++){
//this draws the cell in the appropriate place on the screen
pushMatrix();
translate((i-1)*cellsize,(j-1)*cellsize);
cells[i][j].draw();
popMatrix();
//makes a list of the current colors and the number of cells with each
boolean isnewcol = true; //this says whether the current cell's color is new
//it assumes it is a new color until it finds it in the list of colors
//iterate through the list of colors previously added
for (k = 0; (k < censuskey.size()) && isnewcol; k++){
//check if the current color matches the one in the census
if (censuskey.get(k) == cells[i][j].getcolor()){
censuspop.set(k, censuspop.get(k)+1); //increase the population of that color by 1
isnewcol = false; //updates the fact that it's not a new color
if (k != 0){//if the color is the first in the list, no need to do the next part
if (censuspop.get(k) > censuspop.get(k-1)){ //check if the color's population is more than the one before it in the list
//if it is before it, switch their places
color tempcolor = censuskey.get(k);
int temppop = censuspop.get(k);
censuskey.set(k, censuskey.get(k-1));
censuspop.set(k, censuspop.get(k-1));
censuskey.set(k-1, tempcolor);
censuspop.set(k-1, temppop);
}
}
}
}
if (isnewcol){ //if, after checking all the colors, it's different, add it to the list
censuskey.add(cells[i][j].getcolor());
censuspop.add(1);
}
//randomly choose a parent cell from the previous generation. px and py are the difference between the current cells location and their parent cell. They range from -1 to +1. Any of the 9 squares adjacent (including itself) are equally likely to be the parent
int px = pause ? 0 : (int)(random(3))-1;
int py = pause ? 0 : (int)(random(3))-1;
//set the color of the cell to be the color of the parent. This will not affect other cells figured out in the current generation. See the Cell class for how this works.
cells[i][j].setcolor(cells[i+px][j+py].getcolor());
//0.01% of the time, the cell will mutate and change color randomly
if ((random(100)<.01)&&!pause){
cells[i][j].setcolor(nextcolor());
}
}
}
//draw the graph at the bottom
barwidth = x*cellsize/censuskey.size();
for (k = 0; k < censuskey.size(); k++){
pushStyle();
fill (censuskey.get(k));
rect(k*barwidth,x*cellsize+101,barwidth,-censuspop.get(k));
//text(censuspop.get(k),10, 310+k*10);
popStyle();
}
popStyle();
}
void keyPressed() {
if (key == ' ') {
pause = !pause;
} else if (key == ']' && cellsize < 50) {
cellsize++;
//size(x*cellsize, x*cellsize+1+x*x);
} else if (key == '[' && cellsize > 1) {
cellsize--;
//size(x*cellsize, x*cellsize+1+x*x);
} else if (key == '=' && fr < 50) {
fr++;
frameRate(fr);
} else if (key == '-' && fr > 1) {
fr--;
frameRate(fr);
}
}