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