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
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);
}