Final Project – Polarization & Authoritarianism

I interpreted the 2020 theme as part of our ongoing political crisis. The Republican party has begun to show their true colors as authoritarians, so I tried to explain what that process means in this project using simple particle visualizations.

Users can learn some basic political science, and have fun screwing over democracy in the process.

For the most part, the project acts as a sort of slideshow, moving between animations. The last slide, however, allows users to manipulate the system directly for themselves. The particle simulation code we used extensively in class was used & heavily modified for this simulation. The program also relies heavily on for-loops, objects, and if-statements.

-Robert

sketch
var slide = 0;  //variable that keeps track of which slide is being displayed. 0 corresponds to the title screen.

var democrat    //these four are color variables, used to keep track of each type of voter
var rep
var auth    //authoritarians
var orange  //orange represents 'the strongman leader'.
var voters  //'neutral', or persuadable voters

var vPart = []; //particle array representing "generic" voters
var dPart = []; //particle array representing democrat voters
var rPart = []; //particle array representing republican voters
var aPart = []; //particle array representing authoritarian voters
var dict = [];

//hard boundaries are on by default for all particles so that they can be put in boxes.

function createVParticles(n, x, y, w, h, v) {   //creates voters given a quantity n, position range defined by x, y, w, & h, and a velocity range -v to v
    for(i=0; i<n; i++) {
        newP = makeParticle(random(x, x+w), random(y, y+h), random(-v, v), random(-v, v));
        newP.bHardBoundaries = true;
        append(vPart, newP);
    }
}

function createDParticles(n, x, y, w, h, v) {   //creates democrat particles within a given rectangle & velocity range
    for(i=0; i<n; i++) {
        newP = makeParticle(random(x, x+w), random(y, y+h), random(-v, v), random(-v, v));
        newP.bHardBoundaries = true;
        append(dPart, newP);
    }
}

function createRParticles(n, x, y, w, h, v) {   //creates republican particles within a given rectangle & velocity range
    for(i=0; i<n; i++) {
        newP = makeParticle(random(x, x+w), random(y, y+h), random(-v, v), random(-v, v));
        newP.bHardBoundaries = true;
        append(rPart, newP);
    }
}

function createAParticles(n, x, y, w, h, v) {   //creates authoritarian particles within a given rectangle & velocity range
    for(i=0; i<n; i++) {
        newP = makeParticle(random(x, x+w), random(y, y+h), random(-v, v), random(-v, v));
        newP.bHardBoundaries = true;
        append(aPart, newP);
    }
}

function makeDictator(x, y, d) {    //makes a dynamic orange circle
    var d = {tx: x, ty: y, diameter: d, force: 1, drawFunction: drawDictator};

    dict[0] = d;
}

function drawDictator() {   //draws the circle, maps it to the mouse
    var left = this.tx - this.diameter/2;
    var right = this.tx + this.diameter/2;
    var top = this.ty - this.diameter/2;
    var bottom = this.ty + this.diameter/2;

        this.tx = mouseX;
        this.ty = mouseY;

    push();
    noStroke();
    fill(orange);
    circle(this.tx, this.ty, this.diameter);
    pop();
}

function setup() {
    createCanvas(950, 500);   //multiplication is used to keep the canvas the same w/h ratio as the american flag. This setup is 950 x 500
    background(255);+
    text("p5.js vers 0.9.0 test.", 10, 15);
    frameRate(60);

    dem = color(70, 70, 240);
    rep = color(240, 70, 70);
    auth = color(25);   
    voter = color(200);
    orange = color(255, 140, 25);

    noStroke();
}

var count = 0;  //used to make stuff move

function draw() {
    if (slide == 0) {   //title screen. Each slide is coded separately here, and simply clicking will advance the slide.
        push();
            background(255);
            if (count < width/64) { //displays the moving title screen intro, then the interactive title screen
                titleIntro();
            } else {
                titleScreen();
            }
        pop();
    }

    if (slide == 1) {   //Explainer screen, explains the diagrams, what the particles mean, and gives context
        push();
            background(65);

            push();
            rectMode(CENTER);
            textAlign(CENTER, CENTER);
            textSize(30);
            fill(255);
            text('These particles represent voters. Each color represents a political leaning, and each box represents a grouping.',width/2, 100, width/2, 300);
            
            textStyle(BOLD);    //labels for each voter box
            textSize(25);
            fill(dem);
            text('Liberal', 157.5, 400);

            fill(rep)
            text('Conservative', 367.5, 400);

            fill(voter);
            text('Moderate', 577.5, 400);

            fill(auth);
            text('Authoritarian', 787.5, 400);
            pop();            

            for(i=0; i < dPart.length; i++) {   //these for loops update the position of each particle & draws them
                dPart[i].draw(dem, 15);         //dems
                dPart[i].update(105*1, 250, 105, 105);
            }  
            for(i=0; i < rPart.length; i++) {   //reps
                rPart[i].draw(rep, 15);
                rPart[i].update(105*3, 250, 105, 105);
            }
            for(i=0; i < vPart.length; i++) {   //swing voters
                vPart[i].draw(voter, 15);
                vPart[i].update(105*5, 250, 105, 105);
            }
            for(i=0; i < aPart.length; i++) {   //authoritarians
                aPart[i].draw(auth, 15);
                aPart[i].update(105*7, 250, 105, 105);
            }

            noStroke();
            stroke(255);
            strokeWeight(10);
            noFill();
            for(i=1; i<=7; i+=2) {
                rect(105*i-10, 250-10, 105+20, 105+20);
            }
        pop();
        slideOut(); //slideOut creates a fade-in transition for the slide. It needs to be put last so that it can fade in ON TOP of everything else.
    }

    if (slide == 2) {   //Geographic polarization, civil war thru WWII
        push();
            background(65);
            push();
                fill(255);
                textAlign(CENTER, CENTER);
                textSize(30);
                text('Since before the civil war through the 60s, Americans are divided sharply by geography & slavery. Regional loyalties are strong, and the glacial pace of information keeps regions separate.', 50, 50, 850, 150);
            pop();

            push();
                textStyle(BOLD);

                fill(255);
                textAlign(RIGHT, TOP);
                textSize(25);
                text('Republican (North)', 50, 235, 150, 200);

                textAlign(LEFT, TOP);
                textSize(25);
                text('Democrat (South)', 750, 235, 150, 200);
            pop();

            for(i=0; i < dPart.length; i++) {   //these for loops update the position of each particle & draws them
                dPart[i].draw(dem, 15);         //dems
                if(i < dPart.length/2) {
                    dPart[i].update(225, 250, 200, 200);    //because there are two boxes with the same kind of voter
                } else {                                    //doing updates has to be divided in half
                    dPart[i].update(525, 250, 200, 200);
                }
            }  
            for(i=0; i < rPart.length; i++) {   //reps
                rPart[i].draw(rep, 15);
                if(i < rPart.length/2) {
                    rPart[i].update(225, 250, 200, 200);
                } else {
                    rPart[i].update(525, 250, 200, 200);
                }
            }
            for(i=0; i < vPart.length; i++) {   //swing voters
                vPart[i].draw(voter, 15);
                if(i < vPart.length/2) {
                    vPart[i].update(225, 250, 200, 200);
                } else {
                    vPart[i].update(525, 250, 200, 200);
                }
            }

            push();
                noFill();
                stroke(255);
                strokeWeight(10);
                rect(525-10, 250-10, 200+20, 200+20);   //right rect
                rect(225-10, 250-10, 200+20, 200+20);   //left rect
            pop();
        pop();
        slideOut();
    }

    if (slide == 3) {   //ideological consolidation, WWII thru 1990s.
        push();
            background(65);

            push();
                fill(255);
                textAlign(CENTER, CENTER);
                textSize(30);
                text('Later, mass media allowed disparate people to connect. Conservative Republicans & liberal Democrats consolidated, creating idealogically distinct parties - with fewer swing voters.', 50, 50, 850, 150);
            pop();

            push();
                textStyle(BOLD);

                fill(255);
                textAlign(RIGHT, TOP);
                textSize(25);
                text('Democrat', 50, 235, 150, 200);

                textAlign(LEFT, TOP);
                textSize(25);
                text('Republican', 750, 235, 150, 200);
            pop();

            for(i=0; i < rPart.length; i++) {   //these for loops update the position of each particle & draws them
                rPart[i].draw(rep, 15);         //reps (now on the right)
                rPart[i].update(225, 250, 500, 200);
                rPart[i].vx += .015                              //slowly alters particle velocties so they fall to the right
                rPart[i].vx = constrain(rPart[i].vx, -2, 5)     //constrains their velocities so they stay generally on the right
            }  
            for(i=0; i < dPart.length; i++) {   //dems (now on the left)
                dPart[i].draw(dem, 15);
                dPart[i].update(225, 250, 500, 200);
                dPart[i].vx -= .015                              //slowly fall to the left
                dPart[i].vx = constrain(dPart[i].vx, -5, 2)     //constrains to the left
            }
            for(i=0; i < vPart.length; i++) {   //swing voters
                vPart[i].draw(voter, 15);       //swing voters float free
                vPart[i].update(225, 250, 500, 200);
            }
            push();
                noFill();
                stroke(255);
                strokeWeight(10);
                
                beginShape();   //left box
                    vertex(435, 240);
                    vertex(215, 240);
                    vertex(215, 460);
                    vertex(435, 460);
                endShape();

                beginShape();   //left box
                    vertex(515, 240);
                    vertex(735, 240);
                    vertex(735, 460);
                    vertex(515, 460);
                endShape();
            pop();
        pop();
        
        slideOut();
    }

    if (slide == 4) {   //Something strange has happened recently
        background(65);
        push();
            for (i=0; i<9; i++) {
                for (j=0; j<9; j++) {
                    fill(auth);
                    circle(375+20*(j+1) + random(-2, 2), 250+20*(i+1) + random(-2, 2), 15); //Because these 'voters' are supposed to be in lockstep, I presented them here as circles instead of particles
                }   //it makes the code simpler
            }

            push();
                fill(255);
                textAlign(CENTER, TOP);
                textSize(30);
                text('Recently, something strange has happened: the emergence of a new kind of voter. A minority of voters, mostly conservative, became afraid of losing power - and looked to strongman leaders.', 50, 50, 850, 150);
            pop();

            noFill();   //the authoritarian box (appropriately orange)
            stroke(255);
            strokeWeight(10);
            rect(375-10, 250-10, 200+20, 200+20);

            push();
                fill(orange);   //the 'leader' circle, whose location will act as an attractor point
                noStroke();
                ellipse(width/2, 350, 40, 40);

                fill(255);
                textAlign(LEFT, TOP);
                textSize(22);
                text('Authoritarians dont act like normal voters. What is most important to them is group loyalty. They value safety & order, and look for leaders that make them feel strong.', 650, 235, 250, 220);
            pop();
        pop();

        slideOut();
    }

    if (slide == 5) { //the authoritarian consolidation
        background(65);
        push();
            push();
                fill(255);
                textAlign(CENTER, TOP);
                textSize(30);
                text('In 2016, authoritarian voters became activated in response to demographic change. They consolidated around a leader in the republican party, pushing out or converting other conservatives.', 50, 50, 850, 150);
            pop();

            push();
                textStyle(BOLD);

                fill(255);
                textAlign(RIGHT, TOP);
                textSize(25);
                text('Democrat', 50, 235, 150, 200);

                textAlign(LEFT, TOP);
                textSize(25);
                text('Republican', 750, 235, 150, 200);
            pop();

            for(i=0; i < rPart.length; i++) {   //these for loops update the position of each particle & draws them
                rPart[i].draw(rep, 15);         //reps (now on the right)
                rPart[i].update(225, 250, 500, 200);
                rPart[i].vx += .01              //slowly alters particle velocties so they fall to the right
                if (rPart[i].px > 585) {        //if they go TOO FAR to the right, then they get pushed back
                    rPart[i].vx -= .5
                }                                               
                rPart[i].vx = constrain(rPart[i].vx, -2, 5)     //constrains their velocities so they stay generally on the right
            }  
            for(i=0; i < dPart.length; i++) {   //dems (now on the left)
                dPart[i].draw(dem, 15);
                dPart[i].update(225, 250, 500, 200);
                dPart[i].vx -= .02                              //slowly fall to the left
                dPart[i].vx = constrain(dPart[i].vx, -5, 2)     //constrains to the left
            }
            for(i=0; i < vPart.length; i++) {   //swing voters
                vPart[i].draw(voter, 15);       //swing voters float free
                vPart[i].update(225, 250, 300, 200);

                if (vPart[i].px >= 585) {        //if they go TOO FAR to the right, then they get pushed back
                    vPart[i].vx -= .5
                }
                vPart[i].vx = constrain(rPart[i].vx, -2, 2)     //constrains their velocities so they don't go too fast
            }
            for(i=0; i < aPart.length; i++) {   //dems (now on the left)
                aPart[i].draw(auth, 15);
                aPart[i].update(600, 250, 125, 200);
            }

            push();
                noFill();
                stroke(255);
                strokeWeight(10);
                
                beginShape();   //left box
                    vertex(435, 240);
                    vertex(215, 240);
                    vertex(215, 460);
                    vertex(435, 460);
                endShape();

                beginShape();   //left box
                    vertex(515, 240);
                    vertex(735, 240);
                    vertex(735, 460);
                    vertex(515, 460);
                endShape();
            pop();
        pop();
        slideOut();
    }

    if (slide == 6) { //authoritarian simulation
        background(65);
        push();
            push();
                fill(255);
                textAlign(CENTER, TOP);
                textSize(30);
                text('Even an otherwise stable political system can be disrupted by strongman demagogues.', 50, 50, 850, 150);

                //textStyle(BOLD);
                textSize(20);
                text('Move the mouse to disrupt democracy.', 50, 175, 850, 150);
            pop();

            for(i=0; i < rPart.length; i++) {   //these for loops update the position of each particle & draws them
                rPart[i].draw(rep, 15);         //reps are attracted to the dictator weakly
                rPart[i].update(225, 250, 500, 200);

                if (rPart[i].px > dict[0].tx) {rPart[i].vx -= .05}
                if (rPart[i].px < dict[0].tx) {rPart[i].vx += .05}
                if (rPart[i].py > dict[0].ty) {rPart[i].vy -= .05}
                if (rPart[i].py < dict[0].ty) {rPart[i].vy += .05}

                rPart[i].vx = constrain(rPart[i].vx, -5, 5);
                rPart[i].vy = constrain(rPart[i].vy, -5, 5);
            }  
            for(i=0; i < dPart.length; i++) {   //dems get pushed away by the circle strongly
                dPart[i].draw(dem, 15);
                dPart[i].update(225, 250, 500, 200);

                if (dPart[i].px > dict[0].tx) {dPart[i].vx += .2}
                if (dPart[i].px < dict[0].tx) {dPart[i].vx -= .2}
                if (dPart[i].py > dict[0].ty) {dPart[i].vy += .2}
                if (dPart[i].py < dict[0].ty) {dPart[i].vy -= .2}

                dPart[i].vx = constrain(dPart[i].vx, -5, 5);
                dPart[i].vy = constrain(dPart[i].vy, -5, 5);
            }
            for(i=0; i < vPart.length; i++) {   //swing voters get pushed away by the circle weakly
                vPart[i].draw(voter, 15);
                vPart[i].update(225, 250, 500, 200);

                if (vPart[i].px > dict[0].tx) {vPart[i].vx += .1}
                if (vPart[i].px < dict[0].tx) {vPart[i].vx -= .1}
                if (vPart[i].py > dict[0].ty) {vPart[i].vy += .1}
                if (vPart[i].py < dict[0].ty) {vPart[i].vy -= .1}

                vPart[i].vx = constrain(vPart[i].vx, -5, 5);
                vPart[i].vy = constrain(vPart[i].vy, -5, 5);
            }
            for(i=0; i < aPart.length; i++) {   //authoritarians move towards the circle strongly
                aPart[i].draw(auth, 15);
                aPart[i].update(225, 250, 500, 200);

                if (aPart[i].px > dict[0].tx) {aPart[i].vx -= .2}
                if (aPart[i].px < dict[0].tx) {aPart[i].vx += .2}
                if (aPart[i].py > dict[0].ty) {aPart[i].vy -= .2}
                if (aPart[i].py < dict[0].ty) {aPart[i].vy += .2}

                aPart[i].vx = constrain(aPart[i].vx, -5, 5);
                aPart[i].vy = constrain(aPart[i].vy, -5, 5);
            }

            push();
                noFill();
                stroke(255);
                strokeWeight(10);
                
                beginShape();   //left box
                    vertex(435, 240);
                    vertex(215, 240);
                    vertex(215, 460);
                    vertex(435, 460);
                endShape();

                beginShape();   //left box
                    vertex(515, 240);
                    vertex(735, 240);
                    vertex(735, 460);
                    vertex(515, 460);
                endShape();
            pop();
            dict[0].drawFunction();
        pop();
        slideOut();
    }

    if (slide == 7) {
        background(65);
        push();
                fill(255);
                textAlign(CENTER, TOP);
                textSize(20);
                text('- Benjamin Franklin', 50, 175, 850, 150);

                textSize(30);
                text('Thank You for Playing', 50, 350, 850, 150);


                textStyle(BOLD);
                textSize(30);
                text('“Those who would give up essential liberty to purchase a little temporary safety, deserve neither liberty nor safety.”', 50, 50, 850, 150);

        pop();
        slideOut();
    }

}

function titleIntro() { //Provides the introduction to the title screen, where the rectangles fly in
        push();
            fill(dem);
            rect(0, 0, count*32, height);
        pop();

        push();
            fill(rep);
            rect(width, 0, -count*32, height);
        pop();

        if(count < width/64) {
                count++
        }
}

function titleScreen() {    //Displays the title screen
    push();
        noStroke();
        fill(dem);
        rect(0, 0, width/2, height);
    pop();

    push();
        noStroke();
        fill(rep);
        rect(width/2, 0, width, height);
    pop();

    if (mouseX > width/2 & mouseY > 0 && mouseY < height) {
        push();
            fill(auth);
            rect(width/2, 0, constrain(mouseX-width/2-25, 0, width/2-25), height);
        pop();
        

        push();
            noStroke();
            fill(rep);
            textAlign(RIGHT, CENTER);
            textSize(40);
            text('POLARIZATION & AUTHORITARIANISM', (width/2 + constrain(mouseX-width/2-50, 0, width/2-25))/2, 0, constrain(mouseX, 0, width/2-25), height);
            textSize(10);
            text('CLICK TO PROCEED', (width/2 + constrain(mouseX-width/2-50, 0, width/2-45))/2, 60, constrain(mouseX, 0, width/2-25), height);
        pop();

        push();
            noStroke();
            fill(dem);
            rect(0, 0, width/2, height);
        pop();
    }   
}

function slideOut() {   //Runs on top of each slide, creating the illusion of a smooth fade-in
    push();
        fill(65, 255-count);
        rect(0, 0, width, height);
    pop();
    if (count <= 255) {
        count+=3
    }
}

function mousePressed() {   //advances which simulation is shown. Also used to instance functions which need to run exactly once for a slide.
    slide++


    vPart = []; //empties all the arrays whenever the slide changes, giving a fresh slate.
    dPart = [];
    rPart = [];
    aPart = [];
    dictator = [];
    count = 0;

    if (slide == 1) {
        createDParticles(1, 105*1, 250, 105, 105, 2);
        createRParticles(1, 105*3, 250, 105, 105, 2);
        createVParticles(1, 105*5, 250, 105, 105, 2);
        createAParticles(1, 105*7, 250, 105, 105, 2);
    }

    if (slide == 2) {
        createDParticles(5, 225, 250, 200, 200, 2);
        createRParticles(5, 225, 250, 200, 200, 2);
        createVParticles(5, 225, 250, 200, 200, 2);

        createDParticles(5, 525, 250, 200, 200, 2);
        createRParticles(5, 525, 250, 200, 200, 2);
        createVParticles(5, 525, 250, 200, 200, 2);
    }

    if (slide == 3) {
        createDParticles(6, 225, 250, 200, 200, 2);
        createRParticles(6, 225, 250, 200, 200, 2);
        createVParticles(3, 225, 250, 200, 200, 2);

        createDParticles(6, 525, 250, 200, 200, 2);
        createRParticles(6, 525, 250, 200, 200, 2);
        createVParticles(3, 525, 250, 200, 200, 2);
    }

    if (slide == 4) {
        //just a placeholder in case I wanted to put stuff here later
    }

    if (slide == 5) {
        createDParticles(10, 225, 250, 200, 200, 2);
        createRParticles(6, 585, 250, 140, 200, 2);

        createVParticles(4, 365, 250, 200, 200, 2);

        createAParticles(14, 585, 250, 140, 200, 5);
    }

    if (slide == 6) {
        createDParticles(10, 225, 250, 200, 200, 0);
        createRParticles(10, 525, 250, 200, 200, 0);

        createVParticles(10, 225, 250, 200, 200, 0);
        createAParticles(10, 525, 250, 200, 200, 0);

        makeDictator(width/2, height/2, 40);
    }


    if (slide > 7) {    //wraps the whole program around if the last slide is reached
        slide = 0;
    }
}

//start particle code [CODE BELOW IS MODIFIED FROM EARLIER EXAMPLES TO MAKE IT MORE GENERAL & FLEXIBLE]
function makeParticle(x, y, dx, dy) {
    var p = {px: x, py: y, vx: dx, vy: dy,
             bFixed: false,
             bLimitVelocities: false,
             bPeriodicBoundaries: false,
             bHardBoundaries: false,
             update: particleUpdate,
             handleBoundaries: particleHandleBoundaries,
             draw: particleDraw
            }
    return p;
}

// Update the position based on force and velocity. x, y, w, h define a rectangle within which the particle bounces around.
function particleUpdate(x, y, w, h) {
        this.handleBoundaries(x, y, w, h);
        this.px += this.vx;
        this.py += this.vy;
}

// do boundary processing if enabled. Modified to process bounds within a given rectangle instead of the canvas. x, y, w, h are passed off from te particleUpdate function
function particleHandleBoundaries(x, y, w, h) {
    if (this.bPeriodicBoundaries) {
        if (this.px > x + w) this.px -= width;
        if (this.px < x) this.px += width;
        if (this.py > y + h) this.py -= height;
        if (this.py < y) this.py += height;
    } else if (this.bHardBoundaries) {
        if (this.px >= x + w) {
            this.vx = -abs(this.vx);
        }
        if (this.px <= x) {
            this.vx = abs(this.vx);
        }
        if (this.py >= y + h) {
            this.vy = -abs(this.vy);
        }
        if (this.py <= y) {
            this.vy = abs(this.vy);
        }
    }
}

//Draws the particle, given a color & size
function particleDraw(c, s) {
    fill(c);
    ellipse(this.px, this.py, s, s);
}

Leave a Reply