/* built with Studio Sketchpad:
* https://sketchpad.cc
*
* observe the evolution of this sketch:
* https://studio.sketchpad.cc/sp/pad/view/ro.zir$UV18FS5/rev.11965
*
* authors:
* Craig Kitchen
* license (unless otherwise specified):
* creative commons attribution-share alike 3.0 license.
* https://creativecommons.org/licenses/by-sa/3.0/
*/
// Pressing Control-R will render this sketch.
// This simulates a population of individuals with random reproductive strategies.
// Each individual has different proclivities. One might cooperate at every opportunity,
// one might defect and defend with equal probability.
// Another might have a 25% chance to cooperate, a 5% chance to defend, and a 70% chance to defect.
// Individuals are randomly assigned to encounters. These encounters can have from zero dudes, up to the total population
// Each encounter has an equal number of offspring coming out as there were dudes going in.
// Which dudes get to create offspring is based the combination of their individual strategies
// How the strategies compare to each other is based on a ruleset
// which can be chosen by changing the variable "rules" below.
// There are currently only 2 sets of rules, they're explained below.
// more rulesets may be added later
// The resulting offspring are now the current generation and compete with the same rules
// The current generation is graphed on a triangle, with the points representing an individual who only ever does 1 thing
// Most individuals are not this extreme and fall somewhere in the middle. The closer an individual is to a point
// the more likely it is to use that strategy in the next generation.
float triside = 210; //the size of the side of the triangle graph
int startdudes = 2048; //the population of dudes
int rules = 1; //which ruleset to use to resolve encounters
//all rulesets should keep a constant population, if you allow a population to grow it can cause the program
//to bog down
// 0 - rps
// 1 - ruleset 1 (complicated to explain, check below for explanation)
int start = 0; //how to generate a starting population
// 0 - totally random start
// 1 - start at extremes
// 2 - start at cooperators
// 3 - start at defectors
// 4 - start at defenders
int divides = (int)triside/2; //number of segments to divide the sides of the triangle graph
//it's also how fine the differences between individuals strategies can be.
//To choose a strategy for each step, it generates a random number up to divides and chooses that strategy
//For example, if divides is 10, then it will choose integers that add up to 10 for cooperate, defect, and defend
//Say cooperate is 2, defect is 1, and defend is 7. It generates a float up to 10, if it's less than 2, it cooperates
//if it's less than 3 (2 for cooperate, +1 for defect) it defects, if it's 3 or more, it defends.
float opaque = 10/(divides+1)/(divides+2);
//the percent of the population that needs to be of a type for that circle to be opaque
class Dude {//this defines the members of the population.
//The only properties they have are their probabilities to cooperate and defect. The probability to defend is whatever's left over
private int cooperate, defect; //The chance to cooperate is cooperate/divides. The chance to defect is defect/divides.
//the chance to defend is (divides - cooperate - defect)/divides
Dude (int cooperatei, int defecti){//You can define a dude's properties explicitly
cooperate = cooperatei;
defect = defecti;
}
Dude(){//alternatively, you can just have a guy be generated with random attributes
int temp = divides - (int)random(divides+1) - (int)random(divides+2);
cooperate = temp < 0 ? temp * -1 - 1 : temp;
defect = (int)random (divides - cooperate + 1);
}
int strategy(int groupsize){//this method selects the strategy for the dude based on his probabilities
//0 = cooperate, 1 = defect, 2 = defend
//it is possible to have the strategy depend on the group size, but I haven't done that yet
int choice = random(divides);
if (choice < cooperate){
return 0;
} else if (choice < cooperate + defect){
return 1;
} else {
return 2; /*defend is whatever is left over from the other two*/
}
}
dude offspring (){//this method generates an offspring, either identical or 1 "step" away
if (random(5) < 1){//not a very elegant way of dealing with mutation, but it works at least for now
ArrayList moves = new ArrayList();
//These PVectors will are mutations to the probabilities to cooperate and defect
//The x coordinate is the difference in cooperate rate
//The y coordinate is the difference in defect rate
//The defend rate is defined by default. Since the probabilities always add to 100%
//The three difference values always add to 0, so no explicit value is necessary for defend.
if (cooperate > 0){//If cooperate is 0, then we can't add these because they'll make cooperate negative
moves.add (new PVector(-1,0));
moves.add (new PVector(-1,1));
}
if (defect > 0){//Same applies here to defect
moves.add (new PVector(1,-1));
moves.add (new PVector(0,-1));
}
if (cooperate + defect < divides){//Though there's no negative number here, it's implied because
//the three difference values always add to 0
//The defend value is the implied third value
moves.add (new PVector(1,0));
moves.add (new PVector(0,1));
}
PVector move = moves.get((int)random(moves.size()));
return (new Dude (cooperate + move.x, defect + move.y));
} else {
return this;
}
}
void graph (int population){//this draws the individual on the graph in a place that represents their general strategy
//It draws them with an alpha value, so that they can be stacked on top of each other and you can see how
//popular a given strategy is
noStroke();
fill (255/divides*cooperate,255/divides*defect,255/divides*(divides-cooperate-defect),255/opaque/population);
float y = (cooperate - (divides/3)) * triheight / divides;
float x = cooperate == divides ? 0 : (2*defect/(divides-cooperate)-1) * ((divides-cooperate) / divides) * triside/2;
ellipse (x+width/2, height/2-y, triside/divides, triside/divides);
}
}
Dude randomDude (ArrayList dudes){//this selects a random dude from a list of dudes
return dudes.get((int)random(dudes.size()));
}
class Encounter {//encounters can contain no dudes, or as many dudes as you want.
//Which encounter a dude is in is randomly determined
//Once all the dudes have been assigned to encounters, it is resolved by the chosen ruleset
private ArrayList dudes;
Encounter (){
dudes = new ArrayList()
}
void addDude (Dude d){//add a dude to the encounter
dudes.add(d);
}
ArrayList resolve(){
//resolves the encounter, outputs the next generation of dudes
//in order to keep total population stable, the output from each encounter is the same size as its input
int size = dudes.size();
private ArrayList defectors = new ArrayList();
private ArrayList cooperators = new ArrayList();
private ArrayList defenders = new ArrayList();
//iterate through all the dudes in the encounter and have them pick a strategy
//add them to the proper list depending on their strategy
for (int i = 0; i < size; i++){
switch((dudes.get(i)).strategy(size)){
case 0: cooperators.add(dudes.get(i));
break;
case 1: defectors.add(dudes.get(i));
break;
case 2: defenders.add(dudes.get(i));
break;
default: break;
}
}
//aftermath is the next generation. dudes added to the aftermath will be put into the next generation
ArrayList aftermath = new ArrayList();
switch(rules){
case 0://rps ruleset: defectors displace cooperators, defenders displace defectors, cooperators displace defenders
for (int i = 0; i < defectors.size(); i++){
//if there are any defenders, replace all the defectors with offspring of randomly picked defenders
//otherwise, replace with offspring from each defector.
if (defenders.size() > 0){
aftermath.add(randomDude(defenders).offspring());
} else {
aftermath.add(defectors.get(i).offspring());
}
}
for (int i = 0; i < cooperators.size(); i++){
//if there are any defectors, replace all the cooperators with offspring of randomly picked defectors
//otherwise, replace with offspring from each cooperators
if (defectors.size() > 0){
aftermath.add(randomDude(defectors).offspring());
} else {
aftermath.add(cooperators.get(i).offspring());
}
}
for (int i = 0; i < defenders.size(); i++){
//if there are any cooperators, replace all the defenders with offspring of randomly picked cooperators
//otherwise, replace with offspring from each defenders
if (cooperators.size() > 0){
aftermath.add(randomDude(cooperators).offspring());
} else {
aftermath.add(defenders.get(i).offspring());
}
}
break;
case 1://ruleset 1 is more complex than rps
//the defector only wins if there are no other defectors or defenders.
//if that's the case, it replaces all the cooperators with its offspring.
//if there are only defectors, only 1 produces offspring, replacing all the old defectors
//if there are any defenders, they replace all the defectors with offspring of randomly chosen defenders
//however, if there are no defectors, defenders are replaced with randomly chosen cooperators
//unless there are no cooperators, in which case the defenders just produce 1 offspring each
//cooperators also produce 1 offspring unless there is 1 defector and 0 defenders
Dude topdefector;
if (defectors.size() > 0){
topdefector = randomDude(defectors);
}
for (int i = 0; i < defectors.size(); i++){
if (cooperators.size() == 0 && defenders.size() == 0){
aftermath.add(topdefector.offspring());
} else if (defenders.size() > 0){
aftermath.add(randomDude(defenders).offspring());
} else {
aftermath.add(randomDude(cooperators).offspring());
}
}
for (int i = 0; i < cooperators.size(); i++){
if (defectors.size() == 1 && defenders.size() == 0){
aftermath.add(topdefector.offspring());
} else {
aftermath.add(cooperators.get(i).offspring());
}
}
for (int i = 0; i < defenders.size(); i++){
if (defectors.size() > 0){
aftermath.add(defenders.get(i).offspring());
} else if (cooperators.size() > 0){
aftermath.add(randomDude(cooperators).offspring());
} else {
aftermath.add(defenders.get(i).offspring());
}
}
break;
default: break;
}
return aftermath;
}
}
ArrayList dudes = new ArrayList();
//the population of dudes. updated each step
void setup() { // this is run once.
//initialize the list of dudes, depending on selection made at top of program
for (i = 0; i < startdudes; i++){
switch(start){
case 0: dudes.add(new Dude());;
break;
case 1: int x = (int)random(3);
dudes.add(new Dude(x == 0 ? divides : 0, x == 1 ? divides : 0));
break;
case 2: dudes.add(new Dude(divides, 0));
break;
case 3: dudes.add(new Dude(0, divides));
break;
case 4: dudes.add(new Dude(0, 0));
break;
default: break;
}
}
// set the background color
background(255);
// canvas size (Variable aren't evaluated. Integers only, please.)
size(300, 300);
// smooth edges
smooth();
// limit the number of frames per second
frameRate(30);
noStroke();
fill(0);
}
float triheight = triside * sqrt(3.0) / 2.0;
void draw() { // this is run repeatedly.
//draw the background for the graph
background (255);
noFill();
stroke(0);
//draw a triangle around where the graph will be
triangle (width/2, height/2-2/3*triheight*(divides+2)/divides, width/2+1/2*triside*(divides+2)/divides, height/2+1/3*triheight*(divides+2)/divides, width/2-1/2*triside*(divides+2)/divides, height/2+1/3*triheight*(divides+2)/divides);
//label the corners of the triangle
textAlign(CENTER, BOTTOM);
fill(196,0,0);
text("cooperate",width/2, height/2-2/3*triheight*(divides+2)/divides);
textAlign(CENTER, TOP);
fill(0,196,0);
text("defect",width/2+1/2*triside*(divides+2)/divides, height/2+1/3*triheight*(divides+2)/divides);
fill(0,0,196);
text("defend",width/2-1/2*triside*(divides+2)/divides, height/2+1/3*triheight*(divides+2)/divides);
int population = dudes.size();
Encounter[] encounters = new Encounter[(int)(population/2)];; //create half as many encounters as there are dudes
//initialize these encounters
for (i = 0; i < population; i++){
encounters[i] = new Encounter();
}
//go through each dude. First, draw its current strategy, then add it to a randomly assigned encounter
for (i = 0; i < population; i++){
dudes.get(i).graph(population);
encounters[(int)random((int)(population/2))].addDude(dudes.get(i));
}
ArrayList newDudes = new ArrayList(); //the next generation of dudes, starts out empty
//go through each encounter, add its aftermath to the next generation
for (i = 0; i < encounters.length; i++){
newDudes.addAll(encounters[i].resolve());
}
//make the next generation the current generation
dudes = newDudes;
}