> show canvas only <


/* 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;
}