/* 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();
}
Author: Stuhlfire
Project 11-Generative Landscape
/* 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.
/* 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.
/* 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);
}
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.
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
/* 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.
/* 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();
}