Final Project

sketch
/* Evan Stuhfire
 * estuhlfi@andrew.cmu.edu section B
 * Project-14: Municiple Water Management Game
*/

// map repair state variables
var rsGreen = 0;
var rsYellow = 1;
var rsOrange = 2;
var rsRed = 3;

// variable to track dam cracks
var dYellow = false;
var dOrange = false;
var dRed = false;

// map facility icons to text
var fDam = "Dam";
var fFiltration = "Filter";
var fPumping = "Pump";
var fHydro = "Hydro\nPower";

// Array to hold facility icons
var iconArray = [];

// Array to hold house objects
var houseArray = [];
var numHouses = 5;
// arrays to store house coordinates
var houseX = [35, 80, 140, 210, 190];
var houseY = [315, 335, 310, 285, 330];

// global worker object
var workerObj;

// player stats, number of fixes
var statsObj;
// control for directions page
var gameStart = true;

function drawIcon() {
    fill(this.repairColor);
    // Draw both the tray and the facility icons
    drawIconHelper(this.trayx, this.trayy, 
        this.traySize, this.facilityIcon, 13);
    drawIconHelper(this.facilityx, this.facilityy,
        this.facilitySize, this.facilityIcon, 10);
}

function drawIconHelper(x, y, size, label, txs) {
    push();
    translate(x, y);
    ellipse(0, 0, size);

    strokeWeight(1);
    fill(10);
    textAlign(CENTER);
    textSize(txs);
    text(label, 0, -3);
    pop();
}

function drawWorker() {
    fill(255);
    // draw circle to contain the worker
    ellipse(this.wx, this.wy, this.size);

    // draw stickman worker
    drawStickFigure(this.wx, this.wy, this.headSize);
    textAlign(CENTER);
    textSize(12);
    fill(255);
    strokeWeight(1);
    text("1. Click the worker", this.wx, this.wy + 50);
}

function drawStickFigure(x, y, h) {
    fill(255);
    strokeWeight(1.5);
    // body
    line(x, y - 15, x, y + 20);
    // legs
    line(x, y + 20, x - 10, y + 30);
    line(x, y + 20, x + 10, y + 30);
    // arms
    line(x, y + 10, x -10, y + 15);
    line(x, y + 10, x + 10, y + 15);

    // head
    circle(x, y - 15, h);

    // hat
    fill(255, 255, 0);
    arc(x, y - 20, 30, 33, PI, 2 * PI, OPEN);
    arc(x, y -25, 30, 15, 0, PI, CHORD);

    // eyes
    line(x - 5, y - 15, x - 5, y - 12);
    line(x + 5, y - 15, x + 5, y - 12);
    // mouth
    line(x - 5, y - 5, x + 5, y - 5);
}

function drawHouse() {
    // draw a house 
    // brick red
    fill(153, 0, 0);
    rect(this.hx, this.hy, this.hw, this.hh);

    // draw roof
    fill(50); // grey for roof
    triangle(this.hx, this.hy, this.hx + this.hw/2, this.hy - 15, 
        this.hx + this.hw, this.hy);

    // draw windows
    if(this.power == true) {
        fill(255, 255, 153); // yellow for lit windows 
    } else {
        fill(5); // black for unlit windows, no power
    }
   
    rect(this.hx + 5, this.hy + 5, 10, 12);
    rect(this.hx + 25, this.hy + 5, 10, 12);

    // draw door
    fill(153, 102, 51); // brown for door
    rect(this.hx + 15, this.hy + 15, 10, 15);
}

function houseState() {
    // change power to house based on probability of 
    // an outage, chances increae with higher repair state
    var chanceOfOutage = 0;
    var highState = 0;

    // find highest repair state of hydro power
    for(i = 0; i < iconArray.length; i++) {
        var currentIcon = iconArray[i];
        if(currentIcon.facilityIcon.slice(0, 1) == "H" &
         currentIcon.repairState > highState) {
            highState = currentIcon.repairState;
        }
    }

    if(highState >= rsOrange & this.power == false){
        return;
    }

    // green repair state all houses have power
    switch (highState) {
    case rsGreen:
        chanceOfOutage = 0;
        break;
    case rsYellow:
        // yellow repair state, probability of outage = low
        chanceOfOutage = .005;
        break;
    case rsOrange:
        // orange repair state, probability of outage = medium
        chanceOfOutage = .01;
        break;
    case rsRed:
        // red repair state, probability of outage = high
        chanceOfOutage = .5
        break;
    }
    if(random(0, 1) < chanceOfOutage) {
        this.power = false; // outage
    } else {
        this.power = true; // no outage
    }
}

function changeState() {
    if(gameStart == true) {
        return;
    }

    repairFacility(this);

    // generate a problem based on likelyhood
    if(random(0, 1) < this.problemLikely) {
        this.ageOfState++;
        // increase likelihood of problems over time
        this.problemLikely += .002;
    }


    if(this.ageOfState > 15 & this.repairState == rsYellow) {
        // set state and color to orange
        // reset ageOfState and increase likelyhood of problem

        this.repairState = rsOrange; 
        this.repairColor = color(255, 102, 0);
        this.ageOfState = 0; 
        this.problemLikely = .015;        
    } else if(this.ageOfState > 10 & this.repairState == rsOrange) {
        // set state and color to red
        // reset ageOfState and increase likelyhood of problem

        this.repairState = rsRed;
        this.repairColor = color(204, 0, 0);
        this.ageOfState = 0;
        this.problemLikely = .02; 

        if(this.facilityIcon.slice(0, 1) == "D" ||
            this.facilityIcon.slice(0, 1) == "P") {
            statsObj.floods++;
        } else if(this.facilityIcon.slice(0, 1) =="F"){
            statsObj.filterFail++;
        } else {
            statsObj.powerLoss++;
        }
    } else if(this.ageOfState > 5 & this.repairState == rsGreen) {
        // set state and color to yellow
        // reset ageOfState and increase likelyhood of problem

        this.repairState = rsYellow;
        this.repairColor = color(255, 255, 0);
        this.ageOfState = 0;
        this.problemLikely = .01;
    } 

    // Check for failing services
    if(this.facilityIcon.slice(0, 1) == "D") {
        damFailure(this);
    } else if(this.facilityIcon.slice(0, 1) == "P" &
        this.repairState == rsRed) {
        pumpFailure(this);
    } else if(this.facilityIcon.slice(0,1) == "F" &
        this.repairState == rsRed) {
        filterFailure(this);
    }
}

function makeIconObj(tx, ty, fx, fy, fac) {
    // object def for game icons
    var icon = {trayx: tx,
                trayy: ty,
                facilityx: fx,
                facilityy: fy,
                traySize: 50,
                facilitySize: 35,
                facilityIcon: fac, 
                repairState: rsGreen,
                repairColor: color(0, 200, 0), 
                ageOfState: 0,
                problemLikely: .05,
                trayClick: false,
                facillityClick: false,
                drawFunction: drawIcon,
                stateFunction: changeState
                }
    return icon;
}

function makeWorker(x, y) {
    // object def for worker
    var icon = {wx: x,
                wy: y,
                size: 80,
                headSize: 30,
                clicked: false,
                drawFunction: drawWorker
                }
    return icon;
}

function makeHouse(x, y) {
    // object def for houses
    var house = {hx: x,
                hy: y,
                hw: 40, // house width
                hh: 30, // house height
                power: true,
                drawFunction: drawHouse,
                stateFunction: houseState}

    return house;
}

function makeStats() {
    // object definition for game stats
    var stats = {maxRepairs: 10,
                maxAge: 60,
                repairs: 0,
                floods: 0,
                filterFail: 0,
                powerLoss: 0}

    return stats;
}

function createIcons() {
    // create icons for equipment tray
    iconArray.push(makeIconObj(200, 430, 295, 140, fDam));
    iconArray.push(makeIconObj(270, 430, 60, 217, fFiltration));
    iconArray.push(makeIconObj(340, 430, 397, 300, fPumping));
    iconArray.push(makeIconObj(410, 430, 305, 235, fHydro));

    // create worker icon
    workerObj = makeWorker(70, 425);

    // create game stats object
    statsObj = makeStats();
}   

function createHouses() {
    // make numHouses number of houses
    for(i = 0; i < numHouses; i++) {
        houseArray[i] = makeHouse(houseX[i], houseY[i]);
    }    
}

function setup() {
    createCanvas(480, 480);
    createIcons();
    createHouses();
}

function draw() {
    background(204, 239, 255);
    drawScene();

    // draw houses
    for(j = 0; j < houseArray.length; j++) {
        var currentHouse = houseArray[j];
        currentHouse.stateFunction();
        currentHouse.drawFunction();
    }


    // draw game icons
    var cIcon = 10;
    for(i = 0; i < iconArray.length; i++) {
        var currentIcon = iconArray[i];

        currentIcon.stateFunction();
        currentIcon.drawFunction();

        // if the tray icon was clicked show on mouse
        if(currentIcon.trayClick == true) {
            fill(currentIcon.repairColor);

            // transform to make small icon at mouse pointer
            push();
            scale(.5);
            translate(mouseX, mouseY);
            drawIconHelper(mouseX - 70, mouseY + 30, 
                currentIcon.traySize,
                currentIcon.facilityIcon);
            pop();
        }
    }

    // draw worker icon
    workerObj.drawFunction();

    // if the worker and/or icon has been clicked,
    // draw worker/icon at mouse pointer
    if(workerObj.clicked == true) {
        push();
        // transformation to make small worker at mouse pointer
        scale(.5);
        translate(mouseX, mouseY);
        drawStickFigure(mouseX - 20, mouseY + 40, workerObj.headSize);
        pop();
    }

    drawScore();

    // at the start of game show instructions
    if(gameStart == true) {
        instructions();
    }

    // Display game stats
    // if at least maxRepairs 
    if(statsObj.repairs >= statsObj.maxRepairs || 
            iconArray[0].ageOfState >= 100) {
        gameStats();
        noLoop(); 
    }
}


function mouseClicked() {
    // start game
    if(gameStart == true) {
        gameStart = false;
        return;
    }

    // check if worker was clicked
    var d = dist(mouseX, mouseY, workerObj.wx, workerObj.wy);
    if(d < workerObj.size/2) {
        if(workerObj.clicked == false) {
            workerObj.clicked = true;
        } else {
            workerObj.clicked = false;
        }
        return;
    }

    // check if worker has been clicked
    if(workerObj.clicked == true) {
        // check if tool was clicked in equipement tray
        for(var i = 0; i < iconArray.length; i++) {
            var icon = iconArray[i];
            var d = dist(mouseX, mouseY, icon.trayx, icon.trayy);
            if(d < icon.traySize/2) {
                setTrayIcon(i);
                return;
            }

            // check if facility was clicked
            if(icon.trayClick == true) {
                var d = dist(mouseX, mouseY, icon.facilityx, icon.facilityy);
                if(d < icon.facilitySize/2) {
                    icon.facilityClick = true;
                }
            }
        }
    }
}

function gameStats() {
    // draw final game stats from statsObj
    push();

    fill(255, 255, 255, 235);
    rectMode(CENTER);
    rect(width/2, height/2, 400, 400);
    fill(5);
    textSize(24);
    strokeWeight(1);
    textAlign(CENTER);
    text("You made "+statsObj.repairs+" repairs", width/2, 75);

    textSize(16);
    text("The town flooded "+statsObj.floods+" time(s)", 
            width/2, 115);
    text("The town filtration failed "+statsObj.filterFail+" time(s)", 
            width/2, 145);
    text("The town lost all power "+statsObj.powerLoss+" time(s)", 
            width/2, 175);

    textSize(24);
    text("Refresh to Play Again", width/2, 380);

    pop();
}

function instructions() {
    // Create instruction front page
    fill(255, 255, 255, 235);
    push();
    rectMode(CENTER);
    rect(width/2, height/2, 400, 400);
    fill(5);
    textSize(24);
    strokeWeight(1);
    textAlign(CENTER);
    text("Municipal Water Management Game", width/2, 75);
 
    // create a click to start message
    text("Click Anywhere to Start", width/2, 420);

    textAlign(LEFT);
    textSize(14);

    var x = 55;
    var y = 110;
    // Display instructions
    text("Welcome! You are managing the repairs for a town's water",
        x, y);
    text("supply. If the dam or pump fail the town will flood. If the",
        x, y + 20);
    text("filtration system fails the water will be dirty. If the hydro",
        x, y + 40);
    text("electric plant falters the lights in the houses will flicker and",
        x, y + 60);
    text("go out. Circle icon colors change as urgency increases.",
        x, y + 80);

    textAlign(CENTER);
    textSize(20);
    text("To Play: Make Repairs", width/2, y + 120);

    textAlign(LEFT);
    textSize(14);
    text("1. Click the worker. The icon will appear at the mouse pointer.",
        x, y + 150);
    text("2. Click a yellow, orange, or red icon from the icon tray.",
        x, y + 180);
    text("3. Click the matching facility in need of repair.",
        x, y + 210);
    pop();
}

function setTrayIcon(index) {
    // when setting icon clicked = true, set the others = false
    for(i = 0; i < iconArray.length; i++) {
        if(i == index) {
            iconArray[i].trayClick = true;
        }else {
            iconArray[i].trayClick = false;
        }
    }
}

function drawScore() {
    fill(5);
    strokeWeight(1);
    textAlign(LEFT);
    text("Repairs made: " + statsObj.repairs, 3, 15);
}

function repairFacility(cur) {
    // reset icons, player repairs the service
    if(cur.facilityClick == true) {
        if(cur.repairState == rsGreen) {
            // there was nothing to repair
            return;
        }

        // increment number of fixes for player score
        statsObj.repairs++;

        cur.trayClick = false;
        cur.facilityClick = false;
        workerObj.clicked = false;

        // update for repaird facilities repaired
        if(cur.facilityIcon.slice(0, 1) == "D") {
            damFail = false;
        } else if(cur.facilityIcon.slice(0, 1) == "P") {
            pumpFail = false;
        } else if (cur.facilityIcon.slice(0, 1) == "F") {
            filterFail = false;
        } 

         // user clicked to repair
        // reset state to green
        cur.repairState = rsGreen;
        cur.repairColor = color(0, 200, 0);
        // reset age of facility to 0
        cur.ageOfState = 0;
        // reset probability of problem
        cur.problemLikely = .007;
        return;
    }
}

// variables to track failure
var damFail = false;
var pumpFail = false;
var filterFail = false;

function filterFailure(filter) {
    if(filter.repairState == rsRed) {
        filterFail = true;
    }
}

function pumpFailure(pump) {
    if(pump.repairState == rsRed) {
        pumpFail = true;
    }
}

function damFailure(dam) {
    if(dam.repairState == rsGreen & dYellow == true){
        // reset cracks
        dYellow = false;
        dOrange = false;
        dRed = false;
    }

    if(dam.repairState == rsRed) {
        dRed = true;
        damFail = true;
    } else if(dam.repairState == rsOrange) {
        dOrange = true;
    } else if(dam.repairState == rsYellow) {
        dYellow = true;
    }
}

function drawScene() {
    // draw horizon line
    fill(0, 102, 0);
    rect(0, height/3, width, height);

    drawStaticElements();
    drawCracks();
    drawFlood();
    drawFilterFail();
}


function drawStaticElements() {
    // draw static elements for the game background
    // function are in static.js
    drawMountains();
    drawWater(r, g, b);  
    drawDam();
    drawFiltration();
    drawPumpStation();
    drawHydro();

    drawIconTray();
}

var r = 0;
var g = 0;
var b = 204;
var ft = 0; // filter transparency

function drawFilterFail() {
    // turn water brown to show it is polluted
    // when the filter fails
    if(filterFail == true) {
        r = 134;
        g = 89;
        b = 45;
        ft = min(t + 1, 255);
    } else {
        r = 0;
        g = 0;
        b = 204;
        ft = 0; // filter transparency
    }
}

var t = 0; // variable to control transparency
function drawFlood() {

    // flood the town when the dam fails
    if(damFail == true || pumpFail == true) { 
        t = min(t + 3, 255);       
    }
    else if (damFail == false & pumpFail == false) {
        // dam is repaired decrease transparency
        // to make grass green again
        t = max(t - 3, 0);
    }
    fill(r, g, b, t);
    rect(0, height/3, width, height);
    drawStaticElements();
    drawCracks();
}

function drawCracks() {
    // draw crack in dam
    if(dYellow == true) {
    // draw small cracks
        noFill();
        strokeWeight(1);

        beginShape();
        vertex(355, 170);
        vertex(365, 162);
        vertex(370, 140);
       
        endShape();
    }

    if(dOrange == true) {
    // draw more cracks
        noFill();

        beginShape();
        vertex(350, 170);
        vertex(342, 180);
        vertex(333, 164);
        vertex(325, 168);
       
        endShape();
        strokeWeight(2);

        beginShape();
        vertex(355, 170);
        vertex(380, 180);
        vertex(392, 164);
       
        endShape();
    }

        if(dRed == true) {
        // draw many cracks
        noFill();

        beginShape();
        vertex(350, 170);
        vertex(347, 175);
        vertex(355, 182);
        vertex(358, 190);
        vertex(383, 192);
        vertex(400, 194);
        vertex(425, 205);
       
        endShape();
        strokeWeight(3);

        beginShape();
        vertex(345, 170);
        vertex(340, 160);
        vertex(325, 155);
        vertex(315, 152);
       
        endShape();
    }

    strokeWeight(1);
}

// Functions to draw static game elements

function drawIconTray() {
    fill(230);
    textAlign(LEFT);
    textSize(12);
    strokeWeight(1);
    text("Icon Tray", 165, 395);
    text("2. Click icon from tray          3. Click matching facility", 
        165, 475);
    rect(165, 400, 285, 60);
}

function drawHydro() {
    // draw hydro electric power
    fill(179, 179, 204); 
    beginShape(); 
    vertex(255, 220); 
    vertex(365, 237);
    vertex(365, 254); 
    vertex(255, 237); 
    endShape(CLOSE); 

    // draw top
    beginShape();
    vertex(255, 220);
    vertex(320, 200);
    vertex(388, 208);
    vertex(365, 237);
    endShape(CLOSE);;

    // draw side
    beginShape();
    vertex(365, 237);
    vertex(388, 208);
    vertex(388, 215);
    vertex(365, 254);
    endShape();
}

function drawPumpStation() {
    // draw pumpStation
    fill(179, 179, 204);
    rect(width * .75, 275, 75, 50);

    // pump station top
    beginShape();
    vertex(width *.75, 275);
    vertex(width *.75 + 15, 265);
    vertex(width * .75 + 90, 265);
    vertex(width * .75 + 75, 275);
    endShape(CLOSE);

    // pump station side
    beginShape();
    vertex(width * .75 + 90, 265);
    vertex(width * .75 + 90, 310);
    vertex(width * .75 + 75, 325);
    vertex(width * .75 + 75, 275);
    endShape(CLOSE);

    // Draw pipe
    beginShape();
    vertex(width * .75, 285);
    vertex(width * .75 - 30, 285);
    vertex(width * .75 - 30, 325);
    vertex(width * .75 - 15, 325);
    vertex(width * .75 - 15, 300);
    vertex(width * .75, 300);
    endShape(CLOSE);
}

function drawFiltration() {
    // draw filtration faciity
    fill(179, 179, 204);
    rect(width/14, height/2.5, 50, 50);

    // Draw top
    beginShape();
    vertex(width/14, height/2.5);
    vertex(width/14 + 20, height/2.5 - 10);
    vertex(width/14 + 70, height/2.5 - 10);
    vertex(width/14 + 50, height/2.5);
    endShape(CLOSE);

    // Draw side
    beginShape();
    vertex(width/14 + 70, height/2.5 - 10);
    vertex(width/14 + 70, height/2.5 + 40);
    vertex(width/14 + 50, height/2.5 + 50);
    vertex(width/14 + 50, height/2.5);
    endShape(CLOSE);

    // Draw pipe 1
    ellipse(100, 202, 10, 10);
    beginShape();
    vertex(98, 198);
    vertex(130, 200);
    vertex(130, 240);
    vertex(125, 240);
    vertex(125, 205);
    vertex(98, 205);
    endShape(CLOSE);

    // Draw pipe 2
    ellipse(95, 222, 10, 10);
    beginShape();
    vertex(93, 218);
    vertex(113, 218);
    vertex(113, 245);
    vertex(107, 245);
    vertex(107, 225);
    vertex(93, 225);
    endShape(CLOSE);
}

function drawWater(r, g, b) {
    // draw water as a multipoint curve vertex shape
    fill(r, g, b);
    beginShape();

    curveVertex(425, 100);
    curveVertex(280, 120);
    curveVertex(320, 200);
    curveVertex(250, 240);
    curveVertex(0, 260);
    curveVertex(0, 300);
    curveVertex(270, 280);
    curveVertex(470, 150);

    endShape(CLOSE);
}

function drawDam() {
    var x = width * .55;
    var y = height/3;

    fill(179, 179, 204);

    // draw face of dam
    beginShape();
    vertex(x, y + 50);
    vertex(x + 60, y + 45);
    vertex(x + 90, y + 50);
    vertex(x + 120, y + 55);
    vertex(x + 150, y + 70);
    vertex(x + 185, y + 90);
    vertex(x + 185, y -20);
    vertex(x, y - 50);
    endShape(CLOSE);

    // draw top ledge
    beginShape();
    vertex(x, y - 50);
    vertex(x + 30, y - 55);
    vertex(x + 215, y - 25);
    vertex(x + 185, y - 20)
    endShape();

    // draw right side
    beginShape();
    vertex(x + 185 , y - 20);
    vertex(x + 215, y - 25);
    vertex(x + 215, y + 85);
    vertex(x + 185, y + 90)
    endShape();
}

function drawMountains() {
    // set origin to 0, horizon line
    push();
    translate(0, height/3);

    // draw curves for background mountains
    fill(71, 71, 107);
    beginShape();
    vertex();
    vertex(-20, 20);
    vertex(0, -125);
    vertex(45, -80);
    vertex(120, -155);
    vertex(200, -85);
    vertex(250, -135);
    vertex(290, -90);
    vertex(360, -140);
    vertex(440, -40);
    vertex(500, -120);
    vertex(510, 0);
    endShape(CLOSE);

    // draw curves for mid range mountains
    fill(153, 153, 150);
    beginShape();
    vertex(-30, 30);
    vertex(30, -65);
    vertex(75, -30);
    vertex(120, -90);
    vertex(165, -35);
    vertex(210, -65);
    vertex(255, -25);
    vertex(310, -80);
    vertex(395, 25);
    endShape(CLOSE);

    // draw curves for near mountains
    fill(0, 153, 51);
    beginShape();
    curveVertex(-30, 60);
    curveVertex(0, -5);
    curveVertex(50, -15);
    curveVertex(95, -5);
    curveVertex(135, -20);
    curveVertex(175, -5);
    curveVertex(220, -15);
    curveVertex(275, 10);
    curveVertex(300, 25)
    endShape(CLOSE);

    pop();
}


Project 11-Generative Landscape

sketch
/* Evan Stuhlfire 
 * estuhlfi@andrew.cmu.edu section B
 * project-11-generative landscape */

var hills = [];
var trees = [];
var chickArray = [];
var imgArray = [];
var farm = [];
var barnImg;
var cowImg;
var planeObj;
var flyingPlane = false;

var scrollSpeed = 5;

function preload(){
    // preload images

    // URLs to imgur walking chick
    // Chick image licensed from Adobe stock images
    var filenames = [];
    filenames[0] = "https://i.imgur.com/kjBGzBF.png";
    filenames[1] = "https://i.imgur.com/hxvbkWZ.png";
    filenames[2] = "https://i.imgur.com/53MK7g1.png";
    filenames[3] = "https://i.imgur.com/6nrYJCw.png";
    filenames[4] = "https://i.imgur.com/XmPcBDa.png";
    filenames[5] = "https://i.imgur.com/3OusOsv.png";

    for (var i = 0; i < filenames.length; i++) {
        imgArray[i] = loadImage(filenames[i]);

    barnImg = loadImage("https://i.imgur.com/inS5Xdt.png");
    cowImg = loadImage("https://i.imgur.com/8XjQyMt.png");

    }
}

// methods
function stepChick() {
    // update the chick image to animate
    if(this.imgNum < chickArray.length - 2) {
        // increment to next image
        this.imgNum++;
    } else {
        // if at end of array reset to beginning
        this.imgNum = 0; 
    }
}

function drawChick() {
    // draw image
    image(imgArray[this.imgNum], this.x, this.y, 50, 50);
}

function drawOneHill() {
    // draw hill
    fill(this.c);
    stroke(0, 150, 0);
    strokeWeight(1);
    ellipse(this.x, this.y, this.w, this.hi);

    this.x -= this.s; // decrement by speed
}

function drawTree() {
    // draw trunk
    stroke(77, 38, 0);
    strokeWeight(3);
    line(this.x, this.y, this.x, this.y + (this.h * .8));
    line(this.x, this.y + this.h * .5, this.x + this.w * .2, 
        this.y + this.h * .2);
    line(this.x, this.y + this.h * .7, this.x - this.w * .1, 
        this.y + this.h * .1);

    // draw leaves
    fill(this.c);
    stroke(0, 100, 0);
    strokeWeight(1);
    ellipse(this.x, this.y, this.w, this.h);

    this.x -= this.speed; // decrement by speed
}

// object constructor functions
function newChick(cx, cy) {
    // constructor to make a chick
    var c = {x: cx, y: cy,
            imgNum: 0,
            stepFunction: stepChick,
            drawFunction: drawChick
        }
    return c;
}

function newHill(hx, hy, hw, hhi, hc, hs) {
    // create new hill at x, y with diameter d
    var h = {x: hx, y: hy, w: hw, hi: hhi, 
            c: hc, s: hs,
            drawFunction: drawOneHill
        };
    return h;
}

function newTree(tx, ty, tw, th, tc, ts) {
    // create new tree at x, y of height th, color tc
    // tree width = tw, tree speed = ts
    var t = {x: tx, y: ty, w: tw, h: th, c: tc, 
            speed: ts,
            drawFunction: drawTree
        };
    return t;
}

function newFarm(fx, fy, fi, fw, fh, fb) {
    // create new farm img at x, y
    // use farm image fi
    var f = {x: fx, y: fy, img: fi,
            w: fw, h: fh,
            speed: scrollSpeed,
            barn: fb
        };
    return f;
}

function newPlane(px, py, pdx, pdy) {
    // create plane object
    var p = {x: px, y: py, dx: pdx, dy: pdy};
    return p;
}

function setup() {
    createCanvas(480, 480);
    frameRate(10);
    imageMode(CENTER);

    // loop to create chick images
    for(var i = 0; i < imgArray.length; i++) {
        chickArray[i] = newChick(width/2, height * .7);
    }

    var onCanvas = true;
    // generate initial hills
    var numHills = floor(random(15, 20));
    // onCanvas = true for first set of hills to draw on canvas
    generateHills(onCanvas, numHills); 

    // create a farm
    generateFarm();

    // generate initial trees
    var numTrees = floor(random(1, 10));
    // onCanvas = true trees drawn on canvas
    generateTrees(onCanvas, numTrees, 1);
    generateTrees(onCanvas, numTrees, 2);
    generateTrees(onCanvas, numTrees, 3);
}

function draw() {
    // call all the parts of the scrolling background
    background(153, 179, 255);
    drawSun();
    // draw hills
    drawHills();
    removeHills();
    createNewHills();

    foreground();
    
    // draw farm elements, barns and cows
    drawFarm();
    removeFarm();
    createNewFarm();

    // draw trees
    drawTrees();
    removeTrees();
    createNewTrees(1);
    createNewTrees(2);
    createNewTrees(3);

    // animate the little chick
    drawLittleChick();

    // draw plane
    createPlane();
    drawPlane();
}

function drawSun() {
    // draw stationary sun
    fill(255, 255, 77, 200);
    stroke(255, 255, 26);
    strokeWeight(1);
    ellipse(width * .85, height * .1, 50, 50);
}

function foreground(){
    // draw grass
    fill(0, 153, 51);
    noStroke();
    rect(0, height/3, width, height);
    // draw path
    stroke(153, 102, 51);
    strokeWeight(20);
    line(0, height * .75, width, height * .75);
    // draw pebbles
    // generate differnt shades of tan
    stroke(random(210, 250), random(125, 220), random(85, 220));
    strokeWeight(3);
    var y = floor(random(height * .75 - 10, height * .75 + 10));
    var x = floor(random(0, width));
    point(x, y);
}

function drawLittleChick() {
    // loop through chick array call step and draw methods
    for(var i = 0; i < chickArray.length; i++) {
        var currentChick = chickArray[i];
        currentChick.stepFunction();
        currentChick.drawFunction();
    }
}

function createPlane() {
    // probability of a plane
    var newPlaneLikely = .0008;
    if(random(0, 1) < newPlaneLikely & !flyingPlane) {
        var y = floor(random(40, 100));
        var x = 0;
        var dx = random(2, 5);
        var dy = random(.1, .8);
        planeObj = newPlane(x, y, dx, dy);
        flyingPlane = true;
    }
}

function drawPlane() {
    if(flyingPlane) {
        stroke(230, 255, 255);
        strokeWeight(3);
        line(planeObj.x, planeObj.y, planeObj.x + 30, planeObj.y - 2);
        // draw wings
        strokeWeight(1);
        line(planeObj.x + 18, planeObj.y - 2, planeObj.x + 3, 
            planeObj.y + 5);
        line(planeObj.x + 18, planeObj.y - 1, planeObj.x + 20, 
            planeObj.y - 10);
        // draw tail
        line(planeObj.x, planeObj.y, planeObj.x - 3, planeObj.y -8);

        planeObj.x += planeObj.dx;
        planeObj.y -= planeObj.dy; 

        if(planeObj.x > width || planeObj.y < 0) {
            flyingPlane = false;
        }
    }

}

function createNewHills() {
    // probability of a hill
    var newHillLikely = .07;
    if(random(0, 1) < newHillLikely) {
        var numHills = floor(random(5, 12));
        var onCanvas = false;
        generateHills(onCanvas, numHills);
    }
}

function removeHills() {
    // if array has more items and hills have scrolled off screen
    var keep = [];

    if(hills.length >= 0) {
        for(var i = 0; i < hills.length; i++) {
            if(hills[i].x + hills[i].w/2 > 0) {
                keep.push(hills[i]);
            }
        }
        hills = keep;
    }
}

function drawHills() {
    // draw hills on canvas
    for(var i = 0; i < hills.length; i++) {
        var h = hills[i];
        h.drawFunction();
    }
}

function generateHills(onCanvas, num) {
    // if onCanvas = true then generate first set of hills
    // on canvas, otherwise generate off canvas

    // generate num number of new hills
    for(var i = 0; i < num; i++) {
        // generate hill color
        var g = floor(random(50, 230));
        var c = color(80, g, 0);

        // generate x, y, width, and height of hill
        var y = random(height/2.6, height/2);
        var w = random(100, width);
        var h = random(150, 250);

        if(onCanvas) {
            // generate hills on canvas
            var x = random(-w, width + w * 3);
            var w = random(width/2, width);
        } else {
            // generate hills off canvas
            var x = random(width + (w * 1.5), width * 3); 
        }

        var hill = newHill(x, y, w, h, c, 5);
        hills.push(hill);
    }
}

function createNewFarm(){
    // probability of a farm
    var newFarmLikely = .01;
    if(random(0, 1) < newFarmLikely) {
        generateFarm();
    }
}

function removeFarm() {
    // if array has more items and farms have scrolled off screen
    var keep = [];

    if(farm.length >= 0) {
        for(var i = 0; i < farm.length; i++) {
            if(farm[i].x + farm[i].w > 0) {
                keep.push(farm[i]);
            }
        }
        farm = keep;
    }
}

function generateFarm() {
    // create cows on farm
    var isBarn = false
    var numCows = floor(random(10, 30));
    for(var i = 0; i < numCows; i++) {
        // create a cow object and add it to the farm
        // array for each cow
        var x = random(width, width * 4);
        var y = random(height/2.2, height/3.5);

        if(y < height/2.7) {
            // make cows smaaller, they are further away
            var w = random(10, 15);
            var h = random(10, 15);
        } else {
            // make cows bigger, they are closer
            var w = random(25, 30);
            var h = random(25, 30);
        }

        var c = newFarm(x, y, cowImg, w, h, isBarn);
        farm.push(c);
    }

    // randomly generate barn variables
    var x = random(width + w, width * 2);
    var y = random(height/2.6, height/3.7);

    // if barn is further away, draw it smaller
    if(y < height/3.2) {
        var w = random(20, 40);
        var h = random(20, 40);
    } else {
        // close and bigger
        var w = random(60, 90);
        var h = random(50, 70);
    }

    isBarn = true;
    var f = newFarm(x, y, barnImg, w, h, isBarn);
    farm.push(f);
}

function drawFarm() {
    // draw farm on canvas
    var barnArray = [];
    if(farm.length > 0) {
        for(var i = 0; i < farm.length; i++) {
            var f = farm[i];
            if(f.barn) {
                barnArray.push(f);
            } else {
                // draw cows behind barns
                image(f.img, f.x, f.y, 
                    f.w, f.h);
                f.x -= f.speed; 
            }
        }
        // draw barns in their own layer on top of cows
        for(var i = 0; i < barnArray.length; i++) {
            var b = barnArray[i];
            image(b.img, b.x, b.y, b.w, b.h);
            b.x -= b.speed;
        }
        barnArray = [];
    }
}

function createNewTrees(layer) {
    var newTreeLikely = .012;
    var numTrees = floor(random(2, 4));
    // send false to generate off canvas
    var onCanvas = false;

    if(random(0, 1) < newTreeLikely) {
        generateTrees(onCanvas, numTrees, layer);
    }
}

function removeTrees() {
    // if array has more items and trees have scrolled off screen
    var keep = [];
    if(trees.length >= 0) {
        for(var i = 0; i < trees.length; i++) {
            if(trees[i].x + trees[i].w > 0) {
                keep.push(trees[i]);
            }
        }
        trees = keep;
    }
}

function drawTrees() {
    // draw trees on canvas
    if(trees.length >= 0) {
        for(var i = 0; i < trees.length; i++) {
            var t = trees[i];
            t.drawFunction();
        }
    }
}

function generateTrees(start, num, layer) {
    // if start = true then generate first set of trees
    // on canvas, otherwise generate off canvas

    // generate num number of new trees
    for(var i = 0; i < num; i++) {
        // generate tree color
        var r = floor(random(70, 100));
        var g = floor(random(80, 255));
        var b = floor(random(80, 120));
        if(layer == 1) {
            var c = color(r, g, b, 190); 
            // create big trees in background
            bigTrees(start, c);
        } else if(layer == 2) {
            r = floor(random(0, 150));
            g = floor(random(50, 150));
            b = floor(random(250, 255));
            var c = color(r, g, b, 190);
            // create little trees in midground
            littleTrees(start, c);
        } else {
            r = floor(random(250, 255));
            g = floor(random(30, 150));
            b = floor(random(80, 255));
            var c = color(r, g, b, 200);
            // create shrubs in foreground
            shrubs(start, c);
        }
    }
}

function bigTrees(start, c) {
    // generate x, y, width, and height of trees
    var w = random(50, 70);
    var h = random(40, 70); 

    var y = random(height/2.6, height/2.8);

    if(start) {
        // generate trees on canvas
        var x = random(5, width); 
    } else {
        // generate trees off canvas
        var x = random(width + w/2, width * 3); 
    }

    // create tree and add to array
    var tree = newTree(x, y, w, h, c, 5);
    trees.push(tree);
}

function littleTrees(start, c) {
    // generate x, y, width, and height of trees
    var w = random(30, 40);
    var h = random(15, 40); 

    var y = random(height/1.7, height/2);

    if(start) {
        // generate trees on canvas
        var x = random(5, width); 
    } else {
        // generate trees off canvas
        var x = random(width + w/2, width * 2); 
    }

    // create tree and add to array
    var tree = newTree(x, y, w, h, c, 5);
    trees.push(tree);
}

function shrubs(start, c) {
    // generate x, y, width, and height of trees
    var w = random(20, 40);
    var h = random(15, 20); 

    var y = random(height/1.1, height/1.3);

    if(start) {
        // generate trees on canvas
        var x = random(5, width); 
    } else {
        // generate trees off canvas
        var x = random(width + w/2, width * 3); 
    }

    // create tree and add to array
    var tree = newTree(x, y, w, h, c, 5);
    trees.push(tree);
}

Looking Outward – 11: NFTs

Non-fungible Tokens, NFTs, gave the promise of increased income and a secure line of ownership to digital art creators. The idea was presented as a way for digital artists to reap the benefits of income and ownership that physical artists enjoy. This promised to elevate their work and to reduce copyright infringement by establishing a secure chain of ownership and proof of creation. Thus far, NFTs have failed to deliver on this promise, and in their current state, they are at best an experiment in the application of blockchain technology. At worst, they are a fad or even a fraud.

Jonathan Bailey outlines the rise and promise of NFTs in his article, “NFTs and Copyright”, which appeared in the March 16, 2021 edition of Plagiarism Times. Although NFTs have existed since 2017, they took the world by storm in 2021 with famous artists, such as Beeple, CEOs such as Twitter’s Jack Dorsey, and celebrities such as William Shatner, selling NFTs for millions of dollars. The word “non-fungible” means that something is not interchangeable, implying uniqueness. However, on the current NFT exchanges, anything can be tokenized, including URLs, images, and tweets. When something is tokenized a transaction is created on a blockchain. This makes the token unique, but only the token is unique. The original work that the token is linked to can be duplicated again and again.

Furthermore, the token does not confer ownership of the original work. When the token is sold, the original content can still be copied or owned by someone else. This creates ethical and copyright issues. These issues arise when people, or at times bots, tokenize and sell content they do not own. Some NFT exchanges attempt to prohibit unauthorized NFT sales, but many do not. This is copyright infringement and theft. A further ethical problem arises when NFTs are misrepresented to unknowing buyers. Many buyers believe they own the artwork or its copyright rather than just a transaction on a blockchain. Ultimately, it is a buyer’s responsibility to learn, however deliberate deception is ethically wrong.

NFTs could achieve their original promise and provide digital artists with income and protection; however, it would take a different implementation than what is now being pursued. In its current form, the NFT market will require artist and buyer protections or it will likely fade as another passing fad.

Project 10: Sonic Story

One Night on the Farm
One night on the farm, the farmer left the animals out. The crickets were chirping and the chickens were clucking. A whirring hum was heard and an alien space ship appeared. The alien first abducted the sheep who let out a surprised “baa” as he rose into the air and shrunk. The pig was next. He let out a startled “oink” as he was lifted away from his mud. Then it was the cows turn. He let out a shocked “moo” before being whisked away. The space ship left with its new specimens, and the crickets chirped and the chickens clucked some more.

The farm images are licensed from Adobe stock images. The sounds used are from freesound.org and edited with Audacity. The sounds are used under the Creative Commons license. The owners did not want attribution. The sounds are: crickets.wav – crickets chirping, cluckcluck.wav – chickens clucking, spaceShip.wav – space ship sounds, moo.wav – a cow, baa.wav – a sheep, oink.wav – a pig.

sketch
/* Evan Stuhlfire
 * estuhlfi@andrew.cmu.edu Section B
 * Project-10:  Sonic Story - One Night on the Farm 
Assignment-10:  Sonic Story 
One Night on the Farm 
One night on the farm, the farmer left the animals 
out. The crickets were chirping and the chickens were
clucking. A whiring hum was heard and an alien space ship appeared.
The alien first abducted the sheep who let out a surprised "baa" as he
rose into the air and shrunk. The pig was next. He let out a startled
"oink" as he was lifted away from his mud. Then it was the cows turn. 
He let out a shocked "moo" before being whisked away. The 
space ship left with its new specimens, and the crickets chirped
and the chickens clucked some more.

The farm images are liscensed from Adobe stock images. The sounds used are from freesound.org and edited with Audacity.
The sounds are used under the Creative Commons liscense. The owners did not 
want attribution. The sounds are:
crickets.wav - crickets chirping, cluckcluck.wav - chickens clucking,
spaceShip.wav - space ship sounds, moo.wav - a cow, baa.wav - a sheep,
oink.wav - a pig.
 */

var farmImg = [];   // an array to store the images
var farmObjArray = []; // array to store images

// sound variables
var crickets;
var cluck;
var baa;
var moo;
var oink;
var spaceShip;

// starting sky and foreground colors
var rNight = 50;
var gNight = 100;
var bNight = 150;
var rFore = 50;
var gFore = 160;
var bFore = 80;
var moon = 100;
// position of the moon
var xMoon;
var yMoon;

// map to array indexes
var fence = 0;
var barn = 1;
var hay = 2;
var cow = 3;
var pig = 4;
var sheep = 5;
var chicken = 6; 
var rooster = 7;
var ship = 8;

function preload(){
    // These URLs are images stored on imgur 
    // images liscensed from Adobe Stock images
    // preload farm images
    var filenames = [];
    filenames[fence] = "https://i.imgur.com/bbBOrZ7.png"; 
    filenames[barn] = "https://i.imgur.com/inS5Xdt.png"; 
    filenames[hay] = "https://i.imgur.com/IXPEVak.png"; 
    filenames[cow] = "https://i.imgur.com/8XjQyMt.png"; 
    filenames[pig] = "https://i.imgur.com/7VPvVRB.png"; 
    filenames[sheep] = "https://i.imgur.com/vIFlqDY.png"; 
    filenames[chicken] = "https://i.imgur.com/vdTxUKf.png"; 
    filenames[rooster] = "https://i.imgur.com/SCYoQoX.png"; 
    filenames[ship] = "https://i.imgur.com/lAHddxj.png"
    // load farm images
    for (var i = 0; i < filenames.length; i++) {
        farmImg[i] = loadImage(filenames[i]);
    }

    // load sounds
    crickets = loadSound("https://courses.ideate.cmu.edu/15-104/f2022/wp-content/uploads/2022/11/crickets2.wav");
    cluck = loadSound("https://courses.ideate.cmu.edu/15-104/f2022/wp-content/uploads/2022/11/cluckcluck.wav");
    baa = loadSound("https://courses.ideate.cmu.edu/15-104/f2022/wp-content/uploads/2022/11/baa.wav");
    moo = loadSound("https://courses.ideate.cmu.edu/15-104/f2022/wp-content/uploads/2022/11/moo.wav");
    oink = loadSound("https://courses.ideate.cmu.edu/15-104/f2022/wp-content/uploads/2022/11/oink.wav");
    spaceShip = loadSound("https://courses.ideate.cmu.edu/15-104/f2022/wp-content/uploads/2022/11/spaceShip.wav");

}

function stepImage() {
    // // move and grow the space ship to get the sheep
    if(this.imageNum == ship & this.moving) {
        if(this.target == sheep) {
            if(this.x < width/4) {
                this.x += this.speed;
            }
            if(this.y < height/2.5) {
                this.y += this.speed;
            }
            if(this.ySize <= 100) {
                this.ySize += this.speed;
            }
            if(this.xSize <= 200) {
                this.xSize += this.speed;
            } else {
                if(this.drawBeam) {
                    drawBeam(this.x, this.y, this.target); 
                }
            }
        } else if(this.target == pig) {
            // move the space ship to get the pig
            this.drawBeam = true; // turn the beam back on

            if(this.ySize >= 80) {
                this.ySize -= this.speed;
            }
            if(this.xSize >= 180) {
                this.xSize -= this.speed;
            }

            if(this.y > height/3) {
                this.y -= this.speed;
            }
            if(this.x <= width/2) {
                this.x += this.speed;
            } else {
                if(this.drawBeam) {
                    drawBeam(this.x, this.y, this.target); 
                }
            }
        } else if(this.target == cow) {
            // move the space ship to get the cow
            this.drawBeam = true; // turn the beam back on

            if(this.ySize <= 120) {
                this.ySize += this.speed;
            }
            if(this.xSize <= 240) {
                this.xSize += this.speed;
            }

            if(this.y < height/1.8) {
                this.y += this.speed;
            }
            if(this.x >= width/4) {
                this.x -= this.speed;
            } else {
                // the ship is in the correct location for the beam
                if(this.drawBeam) {
                    drawBeam(this.x, this.y, this.target); 
                }
            }
        } else if(this.target < cow) {
            // fly away
            if(this.ySize >= 0) {
                this.ySize -= this.speed;
            }
            if(this.xSize >= 0) {
                this.xSize -= this.speed;
            }
            if(this.y > 0) {
                this.y -= this.speed;
            }
            if(this.x <= width) {
                this.x += this.speed;
            }
        }
    } 

    // when an animal is being abducted it shrinks and rises into the ship
    if(this.imageNum == farmObjArray[ship].target & this.moving) {
        // decrease y to rise into the ship
        if(this.y >= farmObjArray[ship].y) {
            this.y -= this.speed;
        } else {
            // turn the beam off and set target to pig
            farmObjArray[ship].drawBeam = false;

            // don't decrease the target below the cow index
            if(farmObjArray[ship].target >= cow) {
                farmObjArray[ship].target--;
            }
            
            // stop drawing this image
            this.moving = false;
            this.drawOn = false;
        }
        
        // shrink
        if(this.xSize > 0) {
            this.xSize *= .95;
        }
        if (this.ySize > 0) {
            this.ySize *= .95;
        } 
        if(this.imageNum == sheep & frameCount % 15 == 0) {
            baa.play();
        } else if(this.imageNum == pig & frameCount % 15 == 0) {
            oink.play();
        } else if(this.imageNum == cow & frameCount % 12 == 0) {
            moo.play();
        }

    }
}

function drawImage() {
    // draw image
    if(this.drawOn){
       image(farmImg[this.imageNum], this.x, this.y, this.xSize, this.ySize);
    } 
}

// Constructor for each farm image
function makeObj(cx, cy, imNum, xs, ys) {
    var c = {x: cx, y: cy, xSize: xs, ySize: ys, 
             imageNum: imNum,
             moving: false,
             drawBeam: true,
             target: sheep,
             speed: 5,
             drawOn: true,
             stepFunction: stepImage,
             drawFunction: drawImage
         }
    return c;
}

function setup() {
    createCanvas(480, 480);
    // set farm colors
    background(rNight, gNight, bNight);
    foreground(rFore, gFore, bFore);
    // set initial moon position
    xMoon = width + 25;
    yMoon = height/2.25;
    xShip = width/4;
    yShip = height/2 + 50;

    imageMode(CENTER);
    // create array of farm objects
    createFarmObjs(); 

    frameRate(10);
    useSound();
}

function soundSetup() { // setup for audio generation
    crickets.setVolume(0.1);
    cluck.setVolume(0.3);
    baa.setVolume(0.3);
    moo.setVolume(0.5);
    oink.setVolume(0.3);
    spaceShip.setVolume(0.5);
}

function draw() {
    noStroke();
    background(rNight, gNight, bNight);
    foreground(rFore, gFore, bFore);
    makeSounds();

    var currentImage;
    // loop over farm images and draw them
    for(var i = 0; i < farmObjArray.length; i++) {
        currentImage = farmObjArray[i];
        // draw the farm
        currentImage.stepFunction();
        currentImage.drawFunction();
    }

    // darken to night sky
    fadeNightSky();        

}

function makeSounds() {
    // make sounds at frameCount intervals
    // play crickets
    if(frameCount >5 & frameCount < 350 && frameCount % 25 == 0) {
        crickets.play();
    } 
    // play cluck cluck
    if ((frameCount > 24 & frameCount < 350 && frameCount % 50 == 0)) {
        cluck.play();
    }
    if(frameCount > 24 & frameCount < 200 && frameCount % 25 == 0) {
        spaceShip.play();
    }

    if(frameCount > 400){
        // stop all sounds
        crickets.stop();
        cluck.stop();
        spaceShip.stop();
        baa.stop();
        moo.stop();
        oink.stop();

        // stop looping
        noLoop();
    }
}

function createFarmObjs() {
    // create objects for farm: fence, barn, hay
    // set initial positions xPos and yPos
    var xPos = width/1.25;
    var yPos = height/2;

    // set initial size
    var xs = 125;
    var ys = 125;

    // create farm objects
    farmObjArray[fence] = makeObj(width/2, yPos + 20, fence, xs * 5, 
        ys * .25);
    farmObjArray[barn] = makeObj(xPos, yPos, barn, xs, ys);
    farmObjArray[hay] = makeObj(xPos + 50, yPos + 50, hay, xs/2, ys/2);
    farmObjArray[cow] = makeObj(width/4, height/1.2, cow, 100, 70);
    farmObjArray[pig] = makeObj(width/2, height/1.68, pig, 55, 45);
    farmObjArray[sheep] = makeObj(width/4, height/1.65, sheep, 30, 50);
    farmObjArray[chicken] = makeObj(width/1.2, height/1.35, 
        chicken, 25, 35);
    farmObjArray[rooster] = makeObj(width/1.3, height/1.45, 
        rooster, 30, 40);
    farmObjArray[ship] = makeObj(0, height/8, ship, 20, 1);
}

function drawBeam(x, y, target) {
    // draw beam
    fill(240, 250, 240, 80);
    triangle(x, y, x + 65,
        farmObjArray[target].y + 40, 
        x - 65,
        farmObjArray[target].y + 40);

    // set target to moving
    farmObjArray[target].moving = true;
}

function fadeNightSky() {
    // decrease the background colors to darken the sky to night
    var offset = 5;
    rNight -= offset;
    gNight -= offset;
    bNight -= offset;
    rNight = max(0, rNight);
    gNight = max(0, gNight);
    bNight = max(0, bNight);

    // draw moon
    if(rNight == 0) {
        fill(moon);
        ellipse(xMoon, yMoon, 50);
        moon++;
        moon = min(moon, 245);  
        xMoon -= offset;
        yMoon -= offset;
        xMoon = max(width/1.35, xMoon);
        yMoon = max(height/7.75, yMoon);
    }

    // when it is dark start moving space ship
    if(bNight == 0) {
          farmObjArray[ship].moving = true;
    }
}

function foreground(r, g, b) {
    // create lawn and mud puddle for pig
    fill(r, g, b);
    rect(0, height/2, width, height/2);

    fill(90, 80, 15);
    ellipse(width/2 - 10, height/1.6, 150, 25);
}

Project 09: Computational Portrait

My process: I chose a picture of me on my 3rd birthday. I wanted to incorporate the number 3 into my custom pixel portrait. So I started there. My first custom pixels are made of 3s. Then I started to play with the other ways I could deconstruct, rebuild, and recolor play with the other ways I could deconstruct, rebuild, and recolor my portrait. I have created a 5 Portrait Series.

Portrait 1 – Made of Threes, Portrait 2 – Made of Ovals – inspired by thumbprint art, Portrait 3 – Made of Squares, Portrait 4 – Made of Legos – inspired by Legos, Portrait 5 – Made of Warhol – inspired by Andy Warhol

Press the space bar to see the various portraits I created. Click the mouse to freeze the creation of a portrait. Click again to resume.

sketch
/* Evan Stuhlfire
 * estuhlfi@andrew.cmu.edu section B
 * Project-09: Computational Portrait */

/* My process: I chose a picture of me on my 3rd 
 * birthday. I wanted to incorporate the number 3 into
 * my custom pixel portrait. So I started there. My first custom pixels
 * are made of 3s. Then I started to 
 * play with the other ways I could deconstruct, rebuild, and recolor
 * my portrait. I have created a 5 Portrait Series. */

 /* Portrait 1 - Made of Threes
  * Portrait 2 - Made of Ovals - inspired by thumbprint art
  * Portrait 3 - Made of Squares
  * Portrait 4 - Made of Legos - inspired by Legos
  * Portrait 5 - Made of Warhol - inspired by Andy Warhol */

/* Press the space bar to see the various portraits I created.
 * Click the mouse to freeze the creation of a portrait. Click again
 * to resume. This way portraits can be saved in different states. */

var img; // image variable
var scaleCanvas = 1.4; // factor to make canvas bigger than image

// booleans to determin which portrait to draw
var dThrees = true;
var dEllipses = false;
var dSquares = false;
var dLines = false;
var dOutline = false;

// used with a mouse click to freeze and resume dynamic portraits
var freeze = false; 

function preload() {
    img = loadImage("https://i.imgur.com/FRtQdiE.jpg");
}

function setup() {
    // create canvas 1.4 times the size of the original image
    createCanvas(img.width * scaleCanvas, img.height * scaleCanvas);
    background(230);

    textAlign(CENTER);
}
    
function draw() {
    // randomly generate x and y to get color from

    // use randomGaussian so image appears in center first
    var xPos = Math.floor(randomGaussian(img.width/2, 100));
    var yPos = Math.floor(randomGaussian(img.height/2, 100));

    // remap coordinates of image to the canvas size
    var xMap = map(xPos, 0, img.width, 0, width);
    var yMap = map(yPos, 0, img.height, 0, height);

    // get pixel color
    var pixCol = img.get(xPos, yPos);

    // test which syle to draw
    if(dThrees & !freeze) {
        drawThrees(xMap, yMap, pixCol);
    } else if(dEllipses & !freeze) {
        drawEllipses(xMap, yMap, pixCol);
    } else if(dSquares & !freeze) {
        rectMode(CENTER);
        drawSquares(xMap, yMap, pixCol);
    } else if(dLines & !freeze) {
        drawLines();
    } else if(dOutline & !freeze) {
        rectMode(CORNER);
        drawOutline();
    }
}

function keyPressed() {
    // switch between portait drawings when
    // the space bar is pressed
    if(key != " ") {
        // key was not space bar, return
        return;
    }

    if(dThrees){
        dThrees = false;
        dEllipses = true;
        dSquares = false;
        dLines = false;
        dOutline = false;
    } else if(dEllipses) {
        dThrees = false;
        dEllipses = false;
        dSquares = true;
        dLines = false;
        dOutline = false;
    } else if(dSquares) {
        dThrees = false;
        dEllipses = false;
        dSquares = false;
        dLines = true;
        dOutline = false;
    } else if(dLines) {
        dThrees = false;
        dEllipses = false;
        dSquares = false;
        dLines = false;
        dOutline = true;
    } else if(dOutline) {
        dThrees = true;
        dEllipses = false;
        dSquares = false;
        dLines = false; 
        dOutline = false; 
    }
    // reset background, unfreeze if frozen
    background(230);
    freeze = false;
}

function mousePressed() {
    // toggle the freeze boolean to freeze drawing
    freeze = !freeze;
}

function colorShift(pc, shiftType) {
    // read the pixel color variable and shift the color
    // based on shiftType specified
    var r, g, b, t;

    // read colors
    r = pc[0];
    g = pc[1];
    b = pc[2];
    t = pc[3];

    if(shiftType == "green") {
        // shift colors towards green
        r = constrain(r - 10, 5, 200);
        g = constrain(g + 50, 50, 230);
        b = constrain(b + 10, 10, 200);  
    } else if(shiftType == "blue") {
        // shift colors towards blue
        r = constrain(r - 10, 5, 200);
        g = constrain(g + 10, 10, 200);
        b = constrain(b + 50, 50, 230);        
    }

    // map mouse to change trasparancy as it moves vertical
    var my = map(mouseY, 0, height, 50, 180);
    t = my;

    return color(r, g, b, t);
}

function drawThrees(xMap, yMap, pixCol) {
    // draw portrait out of 3s
    var maxSize = 40;

    // green shift colors from original image
    pixCol = colorShift(pixCol, "green");
    stroke(pixCol);
    strokeWeight(1);
    fill(pixCol);
    // map mouse x to new range to control text size
    var mx = map(mouseX, 0, width, 10, maxSize);
    textSize(min(mx, maxSize));

    // draw 3s on canvas
    text("3", xMap, yMap);
}

function drawEllipses(xMap, yMap, pixCol) {
    // draw portrait out of random ellipses

    // blue shift colors from original image
    pixCol = colorShift(pixCol, "blue");
    // use color for fill
    fill(pixCol);
    stroke(pixCol);
    strokeWeight(1);
    ellipse(xMap, yMap, random(5, 35), random(5, 15));
}

function drawSquares(xMap, yMap, pixCol) {
    // create portrait from squares
    // square size is based on the darkness of the pixel color
    var maxColor = 140;
    var midColor = 100;
    var midColor2 = 70;

    // make color more or less transparent with movement of mouseY
    var transColor = colorShift(pixCol, "");
    // set colors
    stroke(transColor);
    fill(transColor);

    // Check pixel color and adjust square size based on how dark color is
    if(pixCol[0] > maxColor & pixCol[1] > maxColor && pixCol[3] > maxColor) {
        square(xMap, yMap, 30);
        fill(pixCol);
        circle(xMap, yMap, 5);
    } else if(pixCol[0] > midColor & pixCol[1] > midColor && pixCol[3] >
        midColor) {
        square(xMap, yMap, 20);
        fill(pixCol);
        circle(xMap, yMap, 5);
    } else if(pixCol[0] > midColor2 & pixCol[1] > midColor2 && pixCol[3] >
        midColor2) {
        square(xMap, yMap, 15);
        fill(pixCol);
        circle(xMap, yMap, 5);
    }else {
        square(xMap, yMap, 20);
        fill(pixCol);
        circle(xMap, yMap, 5);
    }
}

function drawLines() {
    // lego like circles and square in rows and columns
    for(var y = 5; y < img.height; y += 5) {

        for(var x = 5; x < img.width; x += 5) {
            var c = img.get(x, y);

            // map x from img to x from canvas
            var mx = map(x, 0, img.width, 0, width);
            // map y from img to y from canvas
            var my = map(y, 0, img.height, 0, height);

            // fill and draw 
            // shift to lego colors   
            if(c[0] > 180) {
                c = color(255, 255, 0);
            } else if(c[0] > 80) {
                c = color(0, 255, 0);
            } else if(c[0] > 50) {
                c = color(255, 0, 0);
            } else {
                c = color(0, 0, 255);
            }
            fill(c);
            noStroke();
            rect(mx, my, 10, 10);
            stroke(50);
            circle(mx, my, 5);
        }
    }
}

function drawOutline() {
    // draw an Andy Warhol style grid of pixel faces
    // draw four color quadrants
    fill(0, 255, 0); // green
    rect(0, 0, width/2, height/2);
    fill(255, 255, 77); // yellow
    rect(width/2, 0, width, height/2);
    fill(77, 255, 255); // blue
    rect(0, height/2, width/2, height);
    fill(255, 148, 77); // orange
    rect(width/2, height/2, width, height);

    // draw monochrome points at points of color change
    for(var y = 0; y < img.height; y += 3) {
        // get color from image
        var c = img.get(0, y);

        // look ahead to see when to change line color
        for(var x = 0; x < img.width; x += 2) {
            var dc = img.get(x, y);

            // color is different, draw this point
            if(dc[0] - c[0] > 10 || c[0] - dc[0] > 10 || 
                x == img.width - 1) {
                // map x from img to x from canvas
                var mx = map(x, 0, img.width, 0, width);
                // map y from img to y from canvas
                var my = map(y, 0, img.height, 0, height);

                if(c[0] > 60) {
                    drawPointImg(mx, my, color(0, 0, 230), 1);
                    drawPointImg(mx, my, color(230, 0, 230), 2);
                    drawPointImg(mx, my, color(255, 0, 0), 3);
                    drawPointImg(mx, my, color(255, 216, 204), 4);
                }

                // set color variable to new color
                c = dc;
            }
        }
    }
}

function drawPointImg(mx, my, pc, quad) {
    // draw set of points for specified quadrant 
    // map to specified quad
    if(quad == 1) {
        // map points to first quadrant
        qmx = map(mx, 0, width, 0, width/2);
        qmy = map(my, 0, height, 0, height/2);
    } else if (quad == 2) {
        // map points to second quadrant
        qmx = map(mx, 0, width, width/2, width);
        qmy = map(my, 0, height, 0, height/2);
    } else if (quad == 3) {
        // map points to third quadrant
        qmx = map(mx, 0, width, 0, width/2);
        qmy = map(my, 0, height, height/2, height);
    } else if (quad == 4) {
        // map points to fourth quadrant
        qmx = map(mx, 0, width, width/2, width);
        qmy = map(my, 0, height, height/2, height);
    }

    // set color of point
    stroke(pc);
    strokeWeight(.25);

    // draw point
    point(qmx, qmy);
}


Project Notes

LO 09: Focus on Women and Non-binary Practitioners

Allison Parish is a programmer, poet, and an Assistant Arts Professor at NYU. She creates projects that combine language with software and machine learning. She has written multiple books and articles, created a card game called Rewordable, and presented at Eyeo 2015. Her body of work spans writing multiple pieces of custom software that generate poetry or manipulate language. For example, she has written a Nonsense Laboratory tool which allows users to manipulate words in both how they are spelled and how they sound. The goal is to allow users to play with spelling the same way people play musical instruments or play with modeling clay. The project evokes linguistic creativity and explores machine learning.

To create her projects, Parish accesses large language datasets found in the open source Gutenberg Project and in online movie databases. The project I enjoyed most was her Semantic Similarity Chatbot. This chatbot accesses several movie dialog datasets available through Google. The project is written in Python and uses the spaCy add-on library. It builds word vectors to generate a list of possible responses to a chat. The delivered response then is randomly chosen from the possible vectors. All of the code is available on github and there is a google colab page setup to download and run the chatbot. I had several conversations with the chatbot. The responses are somewhat random and don’t seem very relevant; however, the project remains very interesting and is important as a foundation for machine learning and future chatbots. One of my chats is included below. My entries are shown next to the happy face emoji. The chatbot responses are next to the robot emoji.

Chatbot Output.

LO 08: The Creative Practice of an Individual – Mohit Bhoite

Mohit Bhoite creates free-formed circuit sculptures from a workshop in his home in Minneapolis and more recently in San Francisco. His creative practice and art form is inspired by his love of electronics and robotics. He studied at the University of Pennsylvania and received his MS in Robotics there. His primary job is as an engineer at Particle where he designs and engineers custom circuit boards for the Internet of Things, IoT.

In his spare time, to wind down, he found a love of creating brass sculptures containing circuit boards. These sculptures are elegant in their simplicity. In his day job Bhoite focuses on utility and efficiency for his boards. In his art he focuses on the joy he finds in creating what he calls “useless” projects. I admire his projects because they bring him joy. Bhoite creates for the joy of it. His brass sculptures are clocks, thermometers, light sources, and games. His aesthetic is streamlined, soldered brass rods that emulate satellites, robots, or simple objects. His circuits and displays are connected to the internet and can convey a large amount of information. He likes to put human emotion into his displays rather than raw data. For example, he has an air monitor that displays a robotic happy face when the air is safe. It gets more distressed as the quality deteriorates.

Bhoite presented his process and work at the 2019 Eyeo Festival. He presents his work mostly through photographs, circuit schematics, and source code. He is inspiring because he shares everything about his process and simply wants to share the joy of creation. My favorite project of his is a sand particle generator. It is an LED emulation of falling sand as it is tilted.

Eyeo 2019 – Mohit Bhoite from Eyeo Festival on Vimeo.

Project-07: Hypocycloid Spirograph

Sketch
sketch
/* Evan Stuhlfire
 * estuhlfi@andrew.cmu.edu Section B
 * Project-07: Composition with Curves 
 * Hypocycloid Spirograph */

var rAngle = 30; // rotation angle
var rotation = 0; // degrees of rotation
var maxShapes = 8;
var shape = []; // array of shapes

function setup() {
    createCanvas(480, 480);
    background(250);
    // fill array of objects with default shapes
    createShapes();
}

function draw() {
     // redraw background
    background(250);
    stroke(200);

    // map the mouseX position to a circle for rotation radians
    var xRot = map(mouseX, 0, width, 0, TWO_PI);
    // map the mouseY to half the height to size the shape 
    var my = map(mouseY, 0, height, 50, height/2);
    // r is based on the y position and becomes the radius of the circle
    var r = constrain(my, 100, height/2 - 75);

    // add text to canvas
    addText();

    push(); // store settings
    // translate origin to center
    translate(width/2, height/2);

    // draw the small circles and their curves
    for(var s = 0; s < shape.length; s++) {
        drawSmallShapes(s, height/2 - 25);
    }


    // rotate the canvas based on the mouseX position
    rotate(xRot);
    circle(0, 0, 2 * r);
    // loop over array of shape objects
    for(var s = 0; s < shape.length; s++) {
        // drawSmallShapes(s, maxr);
        // reset degrees of rotation for each shape
        rotation = 0; 
        if(shape[s].on == true){
            // draw the curve in spirograph, 4 times for curve rotation
            for(var i = 0; i < 4; i++) {
                // rotation canvas, start at 0
                rotate(radians(rotation));
                rotation = rAngle;
                drawCurves(shape[s].verts, r, shape[s].color);
            }            
        }
    }
    pop(); // restore settings
}

function mousePressed() {
    // when a shape is clicked it is added or removed
    // from the spirograph, toggles like a button

    // map the mouse position to the translated canvas
    var mx = map(mouseX, 0, width, -240, 240);
    var my = map(mouseY, 0, height, -240, 240);

    // loop through shapes to see if clicked
    for(var i = 0; i < shape.length; i++) {
        var d = dist(shape[i].px, shape[i].py, mx, my);

        // check distance mouse click from center of a shape
        if(d <= shape[i].radius){
            // if on, set to false
            if(shape[i].on == true) {
                shape[i].on = false;  
            } else {
                // if on = false, set true
                shape[i].on = true;
            }
        }
    }
}

function addText() {
    // Add text to canvas
    fill(shape[0].colorB);
    strokeWeight(.1);
    textAlign(CENTER, CENTER);

    // title at top
    textSize(20);
    text("Hypocycloid", width/2, 8);
    text("Spirograph", width/2, 30);
    // directions at bottom
    textSize(10);
    text("Click the circles to toggle curves on or off. Reload for different pen colors.", 
        width/2, height - 5);

    noFill();
    strokeWeight(1);
}

function createShapes() {
    var vCount = 3; // start shapes with 3 verticies
    var sOn = true; // default first shape to show in spirograph
    var angle = 30;
    var shapeRad = 35;

    // create array of shape objects
    for(var i = 0; i < maxShapes; i++) {
        shape[i] = new Object();

        // set object values
        shape[i].verts = vCount;
        // generate random color Bright and Dull for each
        var r = random(255);
        var g = random(255);
        var b = random(255);

        shape[i].colorB = color(r, g, b, 255); // Bright color
        shape[i].colorM = color(r, g, b, 80); // Muted faded color
        shape[i].color = shape[i].colorM;
        shape[i].angle = angle;
        shape[i].px = 0;
        shape[i].px = 0;
        shape[i].radius = shapeRad;
        shape[i].on = sOn;

        // default shapes to not display in spirograph
        sOn = false;      
        vCount++;
        angle += 30;
        if (angle == 90 || angle == 180 || angle == 270) {
            angle += 30;
        }
    }
}

function drawSmallShapes(s, r) {
    // calculate the parametric x and y
    var px = r * cos(radians(shape[s].angle));
    var py = r * sin(radians(shape[s].angle));
    shape[s].px = px;
    shape[s].py = py;

    // map the mouse position to the translated canvas
    var mx = map(mouseX, 0, width, -240, 240);
    var my = map(mouseY, 0, height, -240, 240);

    // check if mouse is hovering over small circle
    var d = dist(shape[s].px, shape[s].py, mx, my);
    if(d <= shape[s].radius) {
        // hovering, make bigger, make brighter
        var hover = 1.25;
        shape[s].color = shape[s].colorB;
    } else {
        // not hovering, make normal size, mute color
        var hover = 1;
        shape[s].color = shape[s].colorM;
    }

    // check if shape is appearing in spriograph
    if(shape[s].on == true) {
        var c = shape[s].colorB; // bright
    } else {
        var c = shape[s].colorM; // muted
    }

    push();
    // move origin to little circle center
    translate(px, py);
    stroke(c); // set color from object
    strokeWeight(2);

    circle(0, 0, shape[s].radius * 2 * hover); // draw little circle
    // draw the curve in the little circle
    drawCurves(shape[s].verts, shape[s].radius * hover, c)
    pop();
}

function drawCurves(n, r, c) {
    // n = number of shape verticies, r = radius, c = color
    // number of vertices for drawing with beginShape
    var nPoints = 50;

    beginShape();
    for(var i = 0; i < nPoints; i++) {
        // map the number of points to a circle
        var t = map(i, 0, nPoints, 0, TWO_PI); // t = theta
        // fit the shape to the radius of the circle
        var value = r/n;
        // calculate hypocycloid at a different number of cusps.
        var px = value * ((n - 1) * cos(t) - cos((n - 1) * t));
        var py = value * ((n - 1) * sin(t) + sin((n - 1) * t)); 
    
        vertex(px, py); // add a vertex to the shape
    }
    noFill();
    stroke(c);
    strokeWeight(1);
    endShape(CLOSE); // close shape
}

Looking Outwards 07: Information Visualization

Wind Map is a live visualization of the current direction and wind speeds across the United States. The wind vectors combine to appear almost as smoke churning at different speeds across the country. This project appeals to me visually because it captures the idea of constant change. It can at times be a peaceful flow and at other times it appears agitated and angry. It can appear both ways on the same map depending on where attention is focused.

This project is not limited to weather. It is aesthetically pleasing and useful. It is beautiful and intriguing enough to be on display at the MoMA. At the same time, diverse groups have found its data useful. Birdwatchers have used it to track migration patterns, and cyclists have used it to plan trips. Conspiracy theorists have even used it to track “chemtrails”. This visualization allows this vast, changing dataset to be useful in ways that are impossible with the raw data.

The underlying data for the Wind Map is from the National Digital Forecast Database which provides near term forecasts that are updated once per hour. The visualization was created by Fernanda Viégas and Martin Wattenberg, engineers from the visualization research lab at Google. The entire project is written in html and javascript. A live link to the Wind Map can be found here.

Project: 06 – Abstract Clock

Crop Circles

I was inspired by crop circles for my clock. Each node is one hour in a day. The crops in the background grow with the mouse.

Sketch
sketch
/* Evan Stuhlfire
** estuhlfi@andrew.cmu.edu, section B
** Project 06: Abstract Clock  */

var r = 90; // radius of center circle, noon
var deg = 360; // degrees in a circle
var nodeCount = 24; // 24 hours, each node is one hour
var c = []; // color array
var x = []; // x position of circle
var theta = []; //angle
var size = []; // diam of the circles
var defaultColor;
var tempSize = 0; // allows the nodes to grow with time
var pSize = 25;

function setup() {
    createCanvas(480, 480);
    background(127, 198, 164);
    fillArray(); // fill arrays with start values

}

function draw() {
    var thetaRotate = 40; // canvas rotation

    background(127, 198, 164);
    drawCrops();

    // build color array
    buildColorArray();

    stroke(93, 115, 126);
    // settings to draw circles (nodes)
    push(); // save settings
    translate(width/2, height/2); // reset origin to center
    rotate(radians(thetaRotate)); // rotate canvas to draw at an angle

    // call function to draw the nodes
    drawNodes();
    pop(); // restore settings

    // make crops reactive to mouse location and time
    pSize = mouseX/hour();
}

function drawCrops() {
    var rows = width/30;
    var cols = height/30;
    var incr = width/16;
    
    var s1 = width/16;
    var s2 = height/16;
    var len = pSize;
    var a = len/4; 

    // loop over double for loop to add colums and rows of crops
    for(var i = 0; i < rows; i++) {
        for(var j = 0; j < cols; j++) {
            push();
            translate(s1/2, s2/2);
            drawOne(len, a); 
            pop();
            s2 += 2 * incr;
        }
        s1 += 2 * incr;
        s2 = incr;
    }
}

function drawOne(len, a) {
    // function to draw on plant for crops
    stroke(93, 115, 126, 60);
    line(0, -a, 0, len);
    line(-a, -a, 0, len/2);
    line(a, -a, 0, len/2);
}

function buildColorArray() {
    // set the default color
    defaultColor = color(242, 245, 234);

    // fill array with colors
    for(var i = 0; i < x.length; i++){
        if(i < 3) {
            c[i] = color(93, 115, 126);    
        } else if(i < 6) {
            c[i] = color(118, 129, 179);
        } else if(i < 9) {
            c[i] = color(214, 248, 214);
        } else if(i < 11) {
            c[i] = color(163, 213, 255);
        } else if(i < 14) {
            c[i] = color(250, 243, 62);
        } else if (i < 16) {
            c[i] = color(163, 213, 255);
        } else if (i < 19) {
            c[i] = color(214, 248, 214);
        } else if (i < 22) {
            c[i] = color(118, 129, 179);
        } else if (i < 25) {
            c[i] = color(93, 115, 126)
        }
    }
}

function fillArray() {
    var angle = 30; // nodes are positioned 30 degrees apart
    var angle2 = 150; // starting point for second set of nodes
    var dist = 65; // nodes begin at dist from center
    var offset = 1.2 // distance offset 
    var offset2 = .8 
    var minSize = 15; // can be no smaller than minSize

    // fill arrays with start values
    x.push((-deg/2) + 10);
    theta.push(180);
    size.push(r * offset2); // 80% size of center

    // fill array with first 12 nodes
    for(var i = 1; i < nodeCount/2; i++) {
        x[i] = dist;
        theta[i] = angle * i;
        size[i] = max(r * i/18, minSize);

        dist = dist + i * offset;
    }

    // push center circle into array
    x.push(deg/2 - 10);
    theta.push(0);
    size.push(r);

    dist = dist - i * offset; // undo last dist calc
    // fill array with last 12 nodes
    // loop backward so that decreasing i can be used to decrease size
    for(i = x.length; i > 2; i--) {
        x.push(dist);
        theta.push(angle2);
        size.push(max(r * i/22, minSize));

        dist = dist - i * offset2;
        angle2 -= angle;
    }

    // push last circle into array, midnight
    x.push(0);
    theta.push(0);
    size.push(r * offset2); // 80% size of center
}

function drawNodes() {
    var h = hour();
 
    stroke(93, 115, 126);
    // draw line behind center node so that it is behind
    line(0, 0, deg/2, 0);

    // draw the satellite nodes
    push();

    translate(x[0], 0); // reset origin to top circle
    loopAndDraw(0, x.length/2, h);

    // draw top midnight circle
    fill(c[0]);
    circle(0, 0, r * .8);
    pop();

    push();
    translate((deg/2) -10, 0);
    loopAndDraw(int(x.length/2) + 1, x.length, h);

    pop();
}

function loopAndDraw(start, end, h) {
    var dx;
    var dy;
    var current = false; // identify the current node

    // iterate over arrays
    for(var i = start; i < end; i++) {
        dx = x[i] * cos(radians(theta[i]));
        dy = x[i] * sin(radians(theta[i]));

        // midnight
        if(h == 0) {
            h = 24;
        }
        // if the hour has passed or is the current hour use fill
        if(i < h){
            fill(c[i]);
        } else if (i == h) {
            current = true;
            fill(c[i]);
        } else {
            fill(defaultColor);
        }

        stroke(93, 115, 126);
        line(0, 0, dx, dy);
        circle(dx, dy, size[i]);

        if(current) {
            currentHour(i, dx, dy);
        }

        current = false;
    }
}

function currentHour(i, dx, dy) {
    // add time elements to nodes to track time
    // subtract to adjust for rotation of canvas
    var s = second() * 6 - 140;
    var m = minute() * 6 - 140;
    var h = hour() * 30 + minute()/2 - 140; 

    fill(c[i]); // use the color stored in the array
    push();
    translate(dx, dy);
    // make current circle bigger
    tempSize = size[i] * 1.32;
    circle(0, 0, tempSize);

    // have a second hand orbit the current hour
    if(i > 3 & i < 23) {
        fill(c[i - 3]); //use previous color.
    } else {
        fill(defaultColor);
    }
    // mark hours
    var hx = size[i] * .6 * cos(radians(h));
    var hy = size[i] * .6 * sin(radians(h));
    circle(hx, hy, tempSize * .25);

    // mark minutes
    var mx = size[i] * .3 * cos(radians(m));
    var my = size[i] * .3 * sin(radians(m));
    
    circle(mx, my, tempSize * .2);

    // mark seconds
    var armX = size[i] * .55 * cos(radians(s));
    var armY = size[i] * .55 * sin(radians(s));
    strokeWeight(1);
    circle(armX, armY, tempSize * .15);
    pop();
}