Final Project

For my final project, I wanted to address something we have all been doing a lot
of during quarantine, which is impulsive online shopping. Seeing all the
devastating climate crisis we’ve experienced this year I wanted to do a little
research on how the actual cost of fast fashion and its damage to the environment. It’s easy to purchase a cute shirt when they’re 15 dollars even if
you didn’t need it, but in reality, it’s contributing to so many problems that
we are actually paying for: forest fires, unpaid labor, toxic waste, etc..
The program is very simple, you hover over with your mouse to see the environmental cost of each of these clothing items, and when you click on them it will be placed into your shopping bag. A certain number of the three symbols will pop up on the screen to show you how much you’ve spent. When you’ve used up around 50,000 litres of water (which doesn’t take a lot to achieve), the end screen will pop up. If I had more time I would make the symbols pop up in a more organized manner and add more levels of interactions.

sketchDownload
//keep track of number of runs
var count = 0;
//used for locating each item of clothing
var item;
//array for textbox, textbox is t
var t = [];
var textInfo = [
    'COTTON T-SHIRT:\n2,700 litres of water\n15kg of CO2\n200+ years to\ndecompose', 
    'JEANS:\n7000 litres of water\n33kg of CO2\n50 years to decompose',
    'SNEAKERS:\n8500 litres of water\n10kg of CO2\n40 years to decompose'
];
var textY = [30, 165, 355];
var textColor = ["lavender", "DarkSlateBlue"];

function preload(){
    rack = loadImage('https://i.imgur.com/YpMqJ3Z.png');
    shirt = loadImage('https://i.imgur.com/54EAgyC.png');
    jeans = loadImage('https://i.imgur.com/qzkFSXI.png');
    shoes = loadImage('https://i.imgur.com/D7paBLS.png');
    bag = loadImage('https://i.imgur.com/Xyv6IGe.png');
    water = loadImage('https://i.imgur.com/3ONgfhP.png');
    co2 = loadImage('https://i.imgur.com/me0Lg0A.png');
    clock = loadImage('https://i.imgur.com/gw7QVpQ.png');
}



//draw water
function drawWater() {
    image(water, this.x, this.y, 20, 20);
}

//draw co2
function drawCarbon(cx, cy) {
    image(co2, this.x, this.y, 20, 20);
}

//draw clock
function drawClock(clx, cly) {
    image(clock, this.x, this.y, 20, 20);
}

//create new water bucket object with position
function makeWater(wx, wy) {
    w = {x: wx, y: wy, 
         drawFunction: drawWater
        }
    return w;
}

//create new co2 object with position
function makeCarbon(cx, cy) {
    c = {x: cx, y: cy, 
         drawFunction: drawCarbon
        }
    return c;
}

//create new clock object with position
function makeClock(clx, cly) {
    cl = {x: clx, y: cly, 
         drawFunction: drawClock
        }
    return cl;
}

//arrays to store new symbol
var waters = [];
var carbons = [];
var clocks = [];
    
function setup() {
    createCanvas(400, 500);
    background("lightsteelblue");
    //set up the base rack
    image(rack, 200, 0, 200, 500);
    imageMode(CENTER);
    image(shirt, 300, 80, 120, 100);
    image(jeans, 300, 250, 80, 150);
    image(shoes, 300, 450, 100, 60);
    //create array of objects of the three info textboxes
    for (var i = 0; i < 3; i ++) {
        t[i] = new Object();
        t[i].info = textInfo[i];
        t[i].x = 170;
        t[i].y = textY[i];
        t[i].w = 110;
        t[i].h = 70;
        t[i].size = 10;
        t[i].c = textColor[i];
        t[i].font = 'verdana';
        t[i].stroke = "thistle"
        t[i].sWeight = 5;
    }

    //make a water bucket
    for (var i = 0; i < 20; i ++) {
        var w = makeWater(random(100), random(20, 100));
        waters.push(w);
    }
    //make a co2
    for (var i = 0; i < 20; i ++) {
        var w = makeCarbon(random(100), random(100, 200));
        carbons.push(c);
    }    
    //make a clock
    for (var i = 0; i < 20; i ++) {
        var cl = makeClock(random(50), random(200, 300));
        clocks.push(cl);
    }    
}


function draw() {
    //instruction
    textSize(20);
    fill("white");
    textFont(t[0].font);
    text('click to shop, hover to see price!', 20, 18);
    //draw textbox 
    if(mouseX >= 200 & mouseX <= 400) {
        if(mouseY >= 3 && mouseY <= 120) {
                stroke(t[0].stroke);
                strokeWeight(t[0].sWeight);
                fill(t[0].c);
                rect(t[0].x-5, t[0].y-5, t[0].w+10, t[0].h+10);
                noStroke();
                textSize(t[0].size);
                fill(t[1].c);
                text(t[0].info, t[0].x, t[0].y, t[0].w, t[0].h);
                item = 1;

        }
        else if(mouseY >= 150 & mouseY <= 300) {
            stroke(t[0].stroke);
            strokeWeight(t[0].sWeight);
            fill(t[0].c);
            rect(t[1].x-5, t[1].y-5, t[1].w+10, t[1].h+10);
            noStroke();
            textSize(t[0].size);
            fill(t[1].c);
            text(t[1].info, t[1].x, t[1].y, t[0].w, t[0].h);
            item = 2;

        }
        else if(mouseY >= 330 & mouseY <= 480) {
            stroke(t[0].stroke);
            strokeWeight(t[0].sWeight);
            fill(t[0].c);
            rect(t[2].x-5, t[2].y-5, t[2].w+10, t[2].h+10);
            noStroke();
            textSize(t[0].size);
            fill(t[1].c);
            text(t[2].info, t[2].x, t[2].y, t[2].w, t[2].h);
            item = 3;
        }
    }
    //place shopping bag
    shoppingBag();

    //place key
    drawKey(0, 400);

    //if 50 buckets of water used, display end page
    if(count >= 50) {
        endPage();
    }  
}
function mousePressed() {

    //when clicked into each area, item goes to shopping bag
    if(item == 1) {
        buyShirt(120, 450);

        //3 water buckets
        push();
        for (var i = 0; i < 3; i ++) {
            var w = waters[i];
            w.drawFunction();
            translate(random(10, 15), random(10, 15));  
        }
        pop();

        //3 co2
        push();
        for (var i = 0; i < 3; i ++) {
            var c = carbons[i];
            c.drawFunction();
            translate(random(10, 15), random(10, 15));  
        }
        pop();

        //20 clocks
        push();
        for (var i = 0; i < 20; i ++) {
            var cl = clocks[i];
            cl.drawFunction();
            translate(random(5, 10), random(5, 10));  
        }
        pop();      
        //add 3 to overall count
        count += 3;
    } 
    else if(item == 2) {
        buyJeans(120, 470);

        //7 water buckets
        push();
        for (var i = 0; i < 7; i ++) {
            var w = waters[i];
            w.drawFunction();
            translate(random(10, 15), random(10, 15));
        }
        pop();
        //6 co2
        push();
        for (var i = 0; i < 6; i ++) {
            var c = carbons[i];
            c.drawFunction();
            translate(random(10, 15), random(10, 15));  
        }
        pop();

        //5 clocks
        push();
        for (var i = 0; i < 5; i ++) {
            var cl = clocks[i];
            cl.drawFunction();
            translate(random(5, 10), random(5, 10));  
        }
        pop();   
        //add 7 to overall count
        count += 7;
    }
    else {
        buyShoes(120, 450);

        //8 water buckets
        push();
        for (var i = 0; i < 8; i ++) {
            var w = waters[i];
            w.drawFunction();
            translate(random(10, 15), random(10, 15));
        }
        pop();

        //2 co2
        push();
        for (var i = 0; i < 2; i ++) {
            var c = carbons[i];
            c.drawFunction();
            translate(random(10, 15), random(10, 15));  
        }
        pop();

        //4 clocks
        push();
        for (var i = 0; i < 4; i ++) {
            var cl = clocks[i];
            cl.drawFunction();
            translate(random(5, 10), random(5, 10));  
        }
        pop();  
        //add 8 to overall count
        count += 8;
    }

}

function shoppingBag() {
    image(bag, 120, 470, 170, 170);
    fill("white");
    textSize(20);
    text('your bag', 75, 480);
}

function drawKey(x, y) {
    //key box
    stroke("thistle");
    strokeWeight(5);
    fill("lavender");
    rect(x, y, 70, 80);
    //symbols
    image(water, x+15, y+20, 20, 20);
    image(co2, x+15, y+45, 20, 20);
    image(clock, x+15, y+65, 20, 20);
    //text 
    textSize(7);
    fill("black");
    noStroke();
    text('1000L water\n\n5kg co2\n\n10 years', x+30, y+15, 40, 80);
    stroke("thistle");
}


//clothing item goes into shopping bag
function buyShirt(x, y) {
    push();
    rotate(radians(random(-2, 2)));
    image(shirt, x, y, 120, 100);
    pop();
    }

function buyJeans(x, y) {
    push();
    rotate(radians(random(-2, 2)));
    image(jeans, x, y, 80, 150);
    pop();
}

function buyShoes(x, y) {
    push();
    rotate(radians(random(-2, 2)));
    image(shoes, x, y, 100, 60);
    pop();
}

function endPage() {
        background("steelblue");
        fill("lavender");
        noStroke();
        textSize(30);
        text('Sorry,\nYou have used up \n50 years worth of \ndrinking water.\n\nRefresh to shop again',
            50, 100)
    
}

Final Project :)

Let’s hope this plays well with WordPress…

This really might not play well with WordPress, so if not feel free to download this and run it like any other sketch. You need to have the p5.js file in the same folder as index.html and sketch.js.

Download Link

So here’s my final project. The theme is supposed to be a survival horror sort of game, but taking one look tells you that isn’t quite the case. Everything is pretty barebones and there are no real enemies, so you don’t need to “survive” anything. You were also supposed to pick up a mask on the way out of your “house,” but I decided to avoid loading textures after testing on another PC. There’s a high chance that this may run slowly for you and I didn’t want it to be even worse by adding additional complexity. So now you need to go find a purple “virus crystallization” and take it to a blue square for scientists to research. Once this happens, your screen will turn grey and that’s it. What happens? Who knows, you can decide that for yourself. 

As for controls, it’s pretty much standard PC keyboard controls. Use WASD as if they were the arrow keys, and click your mouse within the sketch to lock it so you can look around. If the game is too dark, press E to turn on the lights. If the game is too small, press F to enter fullscreen. You can’t see much if you fly around because of the render distance, but if you really want to, press G.

Oh, also, there’s no collisions. You can walk through walls if you really want to. This is mostly because I didn’t have the time to completely reinvent my movement and map generation to work with collisions. The main issue was checking multiple objects in one loop where each one would change my velocity vector, often setting it to the default value. Either way, I still think it’s kind of neat to walk around a maze and see what it looks like.

The maze will be 10×10, so go look for a purple square, then head to the blue one. The maze also changes every time you reload the page, so if you find this especially fun feel free to play again.

sketchAAAAAAAA

renderDistance = 2500;

/**
 * @file sketch.js
 * @brief Demo involving 3D movement, lighting, and map creation.
 *
 * @author Lance Yarlott <lcy@andrew.cmu.edu>
 */

/**
 * @brief creates canvas and generates a maze
 */
function setup() {
    createCanvas(600, 600, WEBGL);
    
    //noStroke();
    var cam = createCamera();
    var cam_dq = 20;
    p = makePlayer(cam, cam_dq, 5);

    locked = false;
    walking = true;
    fs = false;
    tiltAngle = 0;
    panAngle = 0;
    lightsOn = true;
    roomSize = 1000;

    ctr = 0;

    var maze = generateMaze(3, 3);
    
    mapArr = new Array();

    for (var i = 0; i < maze.length; i++) {
        mapArr[i] = new Array();
        for (var j = 0; j < maze[i].length; j++) {
            mapArr[i][j] = roomFromCell(maze[i][j], roomSize, "#990000");
            if (maze[i][j].entrance) p.setCell([i, j]);
        }
    }

    p.update();
}

/**
 * @brief aims to draw maze in 3D, currently NON-non-functional :)
 */
function draw() {
    background(0);

    if (!p.won) {
        p.update();

        enableLight(p.camX, p.camY, p.camZ);

        if (lightsOn) {
            ambientLight(20);
        } else {
            ambientLight(100);
        }

        renderMap(mapArr);

        ctr = (ctr + 1) % 360;

        if (mapArr[p.graphCell[0]][p.graphCell[1]].corrupt) {
            p.inventory.push("mask");
            mapArr[p.graphCell[0]][p.graphCell[1]].corrupt = false;
        }

        if (mapArr[p.graphCell[0]][p.graphCell[1]].exit & 
                                p.inventory.includes("mask")) {
            p.won = true;
        }
    } else {
        p.cam.setPosition(0, 5000, 0);
        background(128);
        noLoop();
    }
}

/**
 * @brief though this is a working function, it's a mess
 * 
 * The x and y assignments are absolutely backwards at times and I should fix
 * them. However, if it ain't broke, don't fix it. Which is to say, I don't want
 * to debug this for another 4 hours. Apologies to any grader that has to read
 * this.
 * 
 * @param {int} sizeX horizontal size for array/graph
 * @param {int} sizeY vertical size for array/graph
 */
function generateMaze(sizeX, sizeY) {
    var maze = new Array();

    for (var i = 0; i < sizeY; i++) {
        maze[i] = new Array();
        for (var j = 0; j < sizeX; j++) {
            maze[i][j] = makeCell(j, i, sizeX, sizeY);
        }
    }

    randX = floor(random(0, sizeY));
    if (randX == 0 || randX == sizeY - 1) {
        randY = floor(random(0, sizeX));
    } else {
        randY = floor(random(0, 2)) * (sizeX - 1);
    }

    randEX = floor(random(0, sizeY));
    randEY = floor(random(0, sizeX));

    while (dist(randX, randY, randEX, randEY) < sizeY / 2) {
        randEX = floor(random(0, sizeY));
        randEY = floor(random(0, sizeX));
    }

    randIX = floor(random(0, sizeY));
    randIY = floor(random(0, sizeX));

    maze[randX][randY].entrance = true;
    maze[randEX][randEY].exit = true;
    maze[randIX][randIY].corrupt = true;

    mazeAlgo(maze, maze[randX][randY]);

    return maze;
}

/**
 * @brief creates a graph node
 * 
 * This is a constructor for a simplified graph data structure
 * 
 * @param {int} cx array column
 * @param {int} cy array row
 * @param {int} sizeX width of graph
 * @param {int} sizeY height of graph
 */
function makeCell(cx, cy, sizeX, sizeY) {
    c = {x: cx, y: cy, sx: sizeX, sy: sizeY, 
        neighbors: new Array(), walls: [1, 1, 1, 1], visited: false,
        entrance: false, exit: false, corrupt: false,
        neighborFunction: getNeighbors};
    c.neighbors = c.neighborFunction();
    return c;
}

/**
 * @brief gets all neighbors for a graph cell in the order of NESW
 * 
 * If a neighbor is invalid, replace index with [-1, -1]
 */
function getNeighbors() {
    var x = this.x;
    var y = this.y;
    var sx = this.sx;
    var sy = this.sy;
    var neighbors = new Array();
    neighbors[0] = (y - 1 >= 0) ? [y-1, x] : [-1, -1];
    neighbors[1] = (x + 1 < sx) ? [y, x+1] : [-1, -1];
    neighbors[2] = (y + 1 < sy) ? [y+1, x] : [-1, -1];
    neighbors[3] = (x - 1 >= 0) ? [y, x-1] : [-1, -1];

    return neighbors;
}

/**
 * @brief updates cell visited and walls parameters
 * 
 * "connects" cell 1 to cell 2, setting wall between the two to 0
 * 
 * @param {object} c1 graph cell
 * @param {object} c2 graph cell
 * @param {int} direction 0-3 variable that dictates direction NESW
 */
function connectCells(c1, c2, direction) {
    c1.visited = true;
    c2.visited = true;
    c1.walls[direction] = 0;
    c2.walls[(direction + 2) % 4] = 0;
}

/**
 * @brief checks if a neighbor's array index is invalid
 * 
 * @param {object} c graph object for neighbor check
 * @param {int} n index for c's neighbor array
 */
function isValidNeighbor(c, n) {
    return c.neighbors[n][0] != -1;
}

/**
 * @brief checks if all neighbors for cell c have a visited bit set
 * 
 * @param {object[][]} mazeArr parent array for graph
 * @param {object} c graph cell object
 */
function allNeighborsVisited(mazeArr, c) {
    for (var i = 0; i < c.neighbors.length; i++) {
        if (isValidNeighbor(c, i)) {
            if (!cellFromNeighbor(mazeArr, c, i).visited) {
                return false;
           }
        }
    }
    return true;
}

/**
 * @brief retrieves a neighbor cell from its parent array, based on the cell c
 * 
 * @param {object[][]} A array of cell objects
 * @param {object} c cell object
 * @param {int} n index into c's neighbor array
 */
function cellFromNeighbor(A, c, n) {
    return A[c.neighbors[n][0]][c.neighbors[n][1]];
}

/**
 * @brief recursive function used to generate a maze, DFS algo
 * 
 * @param {object[][]} mazeArr 2d array that holds graph cells 
 * @param {object} startCell graph cell object
 */
function mazeAlgo(mazeArr, startCell) {
    if (allNeighborsVisited(mazeArr, startCell)) {
        startCell.visited = true;
        return 1;
    } else {
        var visitedCells = new Array();
        randCell = floor(random(0, 4));
        while (!isValidNeighbor(startCell, randCell) || 
                cellFromNeighbor(mazeArr, startCell, randCell).visited) {
            randCell = floor(random(0, 4));
        }

        while (!allNeighborsVisited(mazeArr, startCell) & 
                !arraysEqual(visitedCells, [1,1,1,1])) {
            visitedCells[randCell] = 1;
            connectCells(startCell, cellFromNeighbor(mazeArr, startCell, 
                randCell), randCell);

            mazeAlgo(mazeArr, cellFromNeighbor(mazeArr, startCell, randCell));

            while ((!isValidNeighbor(startCell, randCell) || 
                    cellFromNeighbor(mazeArr, startCell, randCell).visited) &
                    !arraysEqual(visitedCells, [1,1,1,1])) {
                randCell = floor(random(0, 4));
                if (visitedCells[randCell] != 1) visitedCells[randCell] = 1;
            }
        }
    }
}

/**
 * @brief Checks equality of elements in an array, type assumed to be int,
 * length assumed to be equal
 * 
 * @param {int[]} A first int array for comparison
 * @param {int[]} B second int array for comparison
 */
function arraysEqual(A, B) {
    for (var i = 0; i < B.length; i++) {
        if (A[i] != B[i]) return false;
    }
    return true;
}

// TODO: make a functional wall and corridor object

/**
 * @brief room object constructor, rooms assumed to be square
 * 
 * TODO: add check for extra walls active in other rooms
 * 
 * @param {int} x x center of room
 * @param {int} y y center, defaults to 250 for the floor
 * @param {int} z z center
 * @param {int} sx x size of room
 * @param {int} sy y size, defaults to 1 for the floor
 * @param {int} sz z size
 * @param {int[]} walls array of active walls
 * @param {p5.Color} color colors the room, passed onto walls
 */
function makeRoom(x, y=250, z, sx, sy=1, sz, walls, color, entr, exit, corrpt) {
    r = {x: x, y: y, z: z, sx: sx, sy: sy, sz: sz, walls: walls, c: color, 
        wallObjs: new Array(), entrance: entr, exit: exit, corrupt: corrpt,
        drawFunc: drawRoom, update: updateRoom};

    for (var i = 0; i < r.walls.length; i++) {
        if (r.walls[i] == 1) {
            switch(i) {
                case 0:
                    r.wallObjs[i] = makeWall(x, y, z-sx/2, sx, sx, 1, false, 
                        "#FFFFFF");
                    break;
                case 1:
                   r.wallObjs[i] = makeWall(x+sx/2, y, z, sx, sx, 1, true, 
                        "#FFFFFF");
                    break;
                case 2:
                    r.wallObjs[i] = makeWall(x, y, z+sx/2, sx, sx, 1, false, 
                        "#FFFFFF");
                    break;
                case 3:
                    r.wallObjs[i] = makeWall(x-sx/2, y, z, sx, sx, 1, true, 
                        "#FFFFFF");
                    break;
                default:
                    r.walls[i] = -1;
                    break;
            }
        }
    }
    return r;
}

/**
 * @brief draws room, starting from the floor, handles computation of wall 
 * position
 */
function drawRoom() {
    // floor
    push();
    translate(this.x, this.y+this.sx/2, this.z);
    fill(this.c);
    box(this.sx, this.sy, this.sz);
    pop();

    // ceiling
    push();
    translate(this.x, this.y-this.sx/2, this.z);
    fill(this.c);
    box(this.sx, this.sy, this.sz);
    pop();

    if (this.corrupt) {
        drawRotatingDiamond([this.x, this.y, this.z], 200, this.c);
    }

    for (var i = 0; i < this.walls.length; i++) {
        if (this.walls[i] == 1) {
            this.wallObjs[i].drawFunc();
        }
    }
}

function updateRoom() {
    for (var i = 0; i < this.walls.length; i++) {
        if (this.walls[i] == 1) {
            p.mvmtVec = handleCollision(getMovementVector(p), 
                p.panTilt[0], p.samples, this.wallObjs[i].bounds);
        }
    }
}

/**
 * @brief constructor for a wall environment object
 * 
 * depth is set to 1 by default to allow light collisions with the object
 * 
 * @param {int} x x position
 * @param {int} y y position
 * @param {int} z z position
 * @param {int} sx x size (width)
 * @param {int} sy y size (height)
 * @param {boolean} rotated dictates whether the wall is rotated 90 degress
 * @param {p5.Color} color p5.js color variable
 */
function makeWall(x, y, z, sx, sy, sz=1, rotated, color) {
    w = {x: x, y: y, z: z, sx: sx, sy: sy, sz: sz, rotated: rotated, c: color,
        bounds: new Array(), drawFunc: drawWall};

    if (!rotated) w.bounds = [[x - sx/2, z - sz], [x + sx/2, z + sz]];
    else w.bounds = [[x - sz, z - sx/2], [x + sz, z + sx/2]];

    return w;
}

/**
 * @brief wall object method to draw wall on canvas.
 */
function drawWall() {
    push();
    translate(this.x, this.y, this.z);
    if (this.rotated) rotateY(radians(90));
    fill(this.c);
    box(this.sx, this.sy, this.sz);
    pop();
}

/**
 * @brief currently creates a room object based on a graph cell
 * 
 * @param {object} c graph cell describing room information
 * @param {int} s size of room
 * @param {p5.Color} color self explanatory
 */
function roomFromCell(c, s, color) {
    r = makeRoom(c.x * s, 0, c.y * s, s, 1, s, c.walls, color,
        c.entrance, c.exit, c.corrupt);
    return r;
}

function mapUpdate(mapArr) {
    for (var i = 0; i < mapArr.length; i++) {
        for (var j = 0; j < mapArr[i].length; j++) {
            if (arraysEqual(p.graphCell, [i, j])) mapArr[i][j].update();
        }
    }
}

/**
 * @brief renders the map while trying to avoid frame drops
 * 
 * @param {object[][]} mapArr array of room objects to be rendered
 */
function renderMap(mapArr) {
    for (var i = 0; i < mapArr.length; i++) {
        for (var j = 0; j < mapArr[i].length; j++) {
            if (dist(p.xyz[0], p.xyz[1], p.xyz[2], mapArr[i][j].x, 
                    mapArr[i][j].y, mapArr[i][j].z) <= renderDistance){
                if (mapArr[i][j].entrance) {
                    mapArr[i][j].c = "#00FF00";
                } else if (mapArr[i][j].exit) {
                    mapArr[i][j].c = "#0000FF";
                } else if (mapArr[i][j].corrupt) {
                    mapArr[i][j].c = "#FF00FF";
                } else if (arraysEqual(p.graphCell, [i, j])) {
                    mapArr[i][j].c = "#FF0000";
                } else {
                    mapArr[i][j].c = "#808080";
                }
                mapArr[i][j].drawFunc();
            }
        }
    }
}

/**
 * SECTION FOR CAMERA CONTROLS
 */

/**
 * @brief enables 3D lighting with a point light at the camera's position 
 * 
 * @param {int} cx camera x position
 * @param {int} cy camera y position
 * @param {int} cz camera z position
 */
function enableLight(cx, cy, cz) {
    var local = p.cam._getLocalAxes();
    var z = local.z;

    lightFalloff(1, 0, 0);
    spotLight(255, 255, 255, cx, cy, cz, 
        -z[0], 
        -z[1], 
        -z[2], 
        Math.PI, 100);
    spotLight(255, 255, 255, cx, cy, cz, 
        -z[0], 
        -z[1], 
        -z[2], 
        Math.PI / 5, 100);
    spotLight(255, 255, 255, cx, cy, cz, 
        -z[0], 
        -z[1], 
        -z[2], 
        Math.PI / 10, 100);
    spotLight(255, 255, 255, cx, cy, cz, 
        -z[0], 
        -z[1], 
        -z[2], 
        Math.PI / 10, 100);
    spotLight(255, 255, 255, cx, cy, cz, 
            -z[0], 
            -z[1], 
            -z[2], 
            Math.PI / 10, 100);
    lightFalloff(4, 0.01, 0);
    pointLight(255, 255, 255, cx, cy, cz);

    ambientMaterial(255, 255, 255);
}

/**
 * @brief toggles between fullscreen and windowed, changing the resolution
 */
function enterFullscreen() {
    fullscreen(!fs);
    if (!fs) {
        // reduced resolution to avoid scroll wheels
        resizeCanvas(1915, 1075);
    } else {
        resizeCanvas(600, 600);
    }
    fs = !fs;
}

/**
 * @brief used to call enterFullscreen()
 */
function keyPressed() {
    if (key == "f") enterFullscreen();
    if (key == "g") walking = !walking;
    if (key == "e") lightsOn = !lightsOn;
}

/**
 * @brief used to end sprinting
 */
function keyReleased() {
    if (keyCode == SHIFT) {
        p.dq = 15;
    }
    return false;
}

function mousePressed() {
    if (!locked) {
        requestPointerLock();
        locked = !locked;
    } else {
        exitPointerLock();
        locked = !locked;
    }
}

/**
 * @brief used to calculate dot product between two vectors
 * 
 * @param {int[]} A vector
 * @param {int[]} B vector
 */
function dot(A, B){
    if (A.length == B.length) {
        var r = 0;
        for (var i = 0; i < A.length; i++) {
            r += (A[i] * B[i]);
        }
        return r;
    }

    return -1;
}

/**
 * @brief calculates the cross product between two vectors
 * 
 * @param {int[]} A vector
 * @param {int[]} B vector
 */
function cross(A, B) {
    if (A.length == B.length) {
        var r = new Array();

        r[0] = A[1] * B[2] - A[2] * B[1];
        r[1] = A[2] * B[0] - A[0] * B[2];
        r[2] = A[0] * B[1] - A[1] * B[0];

        return r;
    }

    return -1;
}

/**
 * @brief calculates norm (length) of a vector
 * 
 * @param {int[]} A vector
 */
function normalize(A) {
    var r = 0;
    for (var i = 0; i < A.length; i++) {
        r += Math.pow(A[i], 2);
    }
    return Math.sqrt(r);
}

/**
 * PLAYER OBJECT SECTION 
 */

/**
 * @brief makes a player object with several useful parameters
 * 
 * @param {p5.Camera} cam object used for looking 
 * @param {int} dq movement rate (dq is notation used in robotics)
 * @param {int} lookRate surprisingly unused, i don't think i need this
 */
function makePlayer(cam, dq, lookRate) {
    p = {cam: cam, dq: dq, lr: lookRate, xyz: new Array(), panTilt: new Array(),
        camX: 0, camY: 0, camZ: 0, graphCell: new Array(), collision: true,
        keys: new Array(), movementPath: new Array(), mvmtVec: [0, 0, 0],
        samples: 100, width: dq, depth: dq, inventory: new Array(), won: false,
        posUpdate: getPlayerPos, angleUpdate: getPlayerPanTilt, 
        cellUpdate: playerGetCell, setCell: setPosFromCell,
        move: playerMove, look: playerLook, update: updatePlayer};
    return p;
}

/**
 * @brief gets 3d position of player and returns an array of these points
 */
function getPlayerPos() {
    return [this.cam.eyeX, this.cam.eyeY, this.cam.eyeZ];
}

/**
 * @brief returns pan and tilt angle of camera for use in other calculations
 */
function getPlayerPanTilt() {
    var local = this.cam._getLocalAxes();
    var x = local.x;
    var z = local.z;
    let xUnit = [1, 0, 0];
    let yUnit = [0, 1, 0];
    let rotatedUnit = cross(x, [0, -1, 0]);

    let xCross = cross(x, xUnit);
    let zCross = cross([-z[0], -z[1], -z[2]], rotatedUnit);
    
    panAngle = acos(dot(x, xUnit));
    if (dot(yUnit, xCross) < 0) panAngle = -panAngle;

    tiltAngle = acos(dot([-z[0], -z[1], -z[2]], rotatedUnit));
    if (dot([-x[0], -x[1], -x[2]], zCross) < 0) tiltAngle = -tiltAngle;

    return [panAngle, tiltAngle];
}

 /**
 * @brief handles player movement
 * 
 * Implemented in this way to allow for use of multiple keys at a time. This is 
 * called in draw() to update camera position on every redraw.
 */
function playerMove() {
    if (keyIsDown(87)) { // w
        this.keys[0] = 1;
    } else this.keys[0] = 0;
    if (keyIsDown(65)) { // a
        this.keys[3] = 1;
    } else this.keys[3] = 0;
    if (keyIsDown(83)) { // s
        this.keys[2] = 1;
    } else this.keys[2] = 0;
    if (keyIsDown(68)) { // d
        this.keys[1] = 1;
    } else this.keys[1] = 0;
    if (keyIsDown(16)) this.dq = constrain(this.dq+0.5, 0, 30); // shift

    this.cam.move(this.mvmtVec[0], this.mvmtVec[1], this.mvmtVec[2]);
}

/**
 * @brief rotates camera based on mouse movement
 * 
 * Tilts and pans the camera, uses camera axes to calculate look angles for the
 * purpose of bounding tilts.
 */
function playerLook() {
    panAngle = this.panTilt[0];
    tiltAngle = this.panTilt[1];

    vertBound = 80;

    this.cam.pan(radians(-movedX/this.lr));
    if (tiltAngle < radians(vertBound) & tiltAngle > radians(-vertBound)) {
        this.cam.tilt(radians(movedY/this.lr));
    } else if (tiltAngle > radians(vertBound) & movedY < 0) {
        this.cam.tilt(radians(movedY/this.lr));
    } else if (tiltAngle < radians(-vertBound) & movedY > 0) {
        this.cam.tilt(radians(movedY/this.lr));
    }
}

/**
 * @brief converts player position to a cell in the graph array
 */
function playerGetCell() {
    xyz = this.posUpdate();
    x = xyz[0];
    z = xyz[2];

    x += roomSize / 2;
    z += roomSize / 4;

    return [floor(z / roomSize), floor(x / roomSize)];
}

/**
 * @brief sets the player position to a specific graph cell
 * 
 * @param {int[]} coords graph cell (x, y)
 */
function setPosFromCell(coords) {
    this.cam.setPosition(coords[1] * roomSize, this.camY, coords[0] * roomSize);
}

/**
 * @brief updates all player parameters
 */
function updatePlayer() {
    this.xyz = this.posUpdate();
    this.panTilt = this.angleUpdate();

    this.camX = this.xyz[0];
    this.camY = walking ? 0 : this.xyz[1];
    this.camZ = this.xyz[2];

    this.cam.setPosition(this.camX, this.camY, this.camZ);

    this.mvmtVec = getMovementVector(this);
    this.graphCell = this.cellUpdate();
    this.move();
    this.look();
}

// CANCELLED: collision detection

/**
 * IDEA: get four walls from around player, possibly adjacent rooms
 * check if player movement vector collides with or passes through a wall plane
 * if it does, truncate the vector at the wall's edge
 * 
 * -----|---> turns into --->|
 */

 /**
  * @brief gets player's movement vector in xyz
  * 
  * @param {object} player 
  */
function getMovementVector(player) {
    var vector = [0, 0, 0];
    if (player.collision) {
        for (var i = 0; i < player.keys.length; i++) {
            switch(i) {
                case 0:
                    if (player.keys[i] == 1) vector[2] -= player.dq;
                    break;
                case 1:
                    if (player.keys[i] == 1) vector[0] += player.dq;
                    break;
                case 2:
                    if (player.keys[i] == 1) vector[2] += player.dq;
                    break;
                case 3:
                    if (player.keys[i] == 1) vector[0] -= player.dq;
                    break;
            }
        }
        return vector;
    }
    // implies collision is off because i'm not going to account for y changes
    // in noclip mode where collision doesn't matter
    // though i might in the future because it's not hard to add
    // the issue is that there's a *small* chance it'll slow the game down
    return []; 
}

/**
 * @brief converts a movement vector plus player position to world coordinates
 * 
 * Based on math given x and z axes. Movement vector forward composed of 
 * dz*cos(a) - dx*sin(a) and side vector composed to dz*sin(z) + dx*cos(a).
 * No need for y movement because this game has no stairs or ramps.
 * 
 * @param {int[]} movementVec assumed to include x, y, and z, uses only x and z
 * @param {int} angle assumed to be panAngle
 */
function vectorToWorldCoords(playerPos, movementVec, angle, samples) {
    let pointsOverTime = new Array();
    var x = p.cam.eyeX;
    var z = p.cam.eyeZ;
    for (var i = 0; i <= samples; i++) {
        var dz = (i / samples) * (movementVec[2]);
        var dx = (i / samples) * (movementVec[0]);
        pointsOverTime[i] = [z + (dz * cos(radians(angle))) + (dx * 
            sin(radians(angle))), x + (dx * cos(radians(angle))) - (dz * 
            sin(radians(angle)))];
    }

    return pointsOverTime;
}

/**
 * @brief locates point where player intersects wall, if they do
 * 
 * @param {int[]} playerPoints 
 * @param {int[][]} boundingBox 
 */
function findIntersect(playerPoints, boundingBox) {
    for (var i = 0; i < playerPoints.length; i++) {
        if (playerPoints[i][1] >= boundingBox[0][0] - 100 &
            playerPoints[i][1] <= boundingBox[1][0] + 100 &&
            playerPoints[i][0] >= boundingBox[0][1] - 100 &&
            playerPoints[i][0] <= boundingBox[1][1] + 100) {
                return playerPoints[i];
        }
    }
    return -1;
}

/**
 * @brief partially working collision function
 * 
 * @param {object} player 
 * @param {object} wall 
 */
function checkCollisionWithWall(player, wall) {
    let mvmtVec = getMovementVector(player);
    let points = vectorToWorldCoords(player.xyz, mvmtVec, 
        player.panTilt[0], player.samples);
    var boundaryPoint = findIntersect(points, wall.bounds);
    if (boundaryPoint != -1) {
        player.cam.setPosition(boundaryPoint[1], player.xyz[1], 
            boundaryPoint[0]);
    }
}

/**
 * @brief is supposed to handle collisions by limiting x or z movement
 * 
 * @param {int[]} movementVec 
 * @param {int} angle 
 * @param {int} samples 
 * @param {int[][]} boundingBox 
 */
function handleCollision(movementVec, angle, samples, boundingBox) {
    let vel = new Array();
    var x = p.cam.eyeX;
    var z = p.cam.eyeZ;
    for (var i = 0; i <= samples; i++) {
        var dz = (i / samples) * (movementVec[2]);
        var dx = (i / samples) * (movementVec[0]);

        xOnly = [z, x + (dx * cos(radians(angle))) - (dz * 
            sin(radians(angle)))];
        yOnly = [z + (dz * cos(radians(angle))) + (dx * 
            sin(radians(angle))), x];


        var xCh = checkCollision(xOnly, boundingBox);
        if (!xCh) {
            vel[0] = dx;
        }

        var yCh = checkCollision(yOnly, boundingBox);
        if (!yCh) {
            vel[2] = dz;
        }

        vel[1] = 0;
    }

    return vel;
}

/**
 * @brief checks if point is within a bounding box
 * 
 * @param {int[]} playerPoints 
 * @param {int[][]} boundingBox 
 */
function checkCollision(playerPoints, boundingBox) {
    if (playerPoints[1] >= boundingBox[0][0] - 100 &
        playerPoints[1] <= boundingBox[1][0] + 100 &&
        playerPoints[0] >= boundingBox[0][1] - 100 &&
        playerPoints[0] <= boundingBox[1][1] + 100) {
            return true;
    }
    return false;
}

/**
 * @brief draws a rotating diamond to represent items
 * 
 * @param {int[]} pos 
 * @param {int} size 
 * @param {p5.Color or p5.Image} texture 
 */
function drawRotatingDiamond(pos, size, tex) {
    push();
    if (typeof tex == "string") {
        fill(tex);
    } else {
        texture(tex);
    }
    translate(pos[0], pos[1] + (100 * sin(radians(ctr))), pos[2]);
    rotateY(radians(ctr));
    cone(size, size, 5);
    translate(0, -size, 0);
    cone(size, -size, 5);
    pop();
}
// TODO: make cube disappear if player shows up, add to inventory
// TODO: make win state

Final Project

This is a fun little animated/interactive sequence on the death of the theater industry because of COVID. It is mostly cut off because of wordpress!!

sketchDownload
var clouds = [];
var grassLeftOn = true
var grassRightOn = true
var tiktok; //image of the tiktok logo that emerges from the death of the
			//theater industry
var rat; //image of the ratatouille musical that is saving the theater
		 //industry right now
var angle = 0 //to rotate the images of tiktok and ratatouille

function preload() {
	tiktok = loadImage("https://i.imgur.com/FymNkxO.png")
	rat = loadImage("https://i.imgur.com/70iMUtD.jpeg")
}

function setup() {
    createCanvas(1500, 700);
    background(220);
    frameRate(10);
    for (var i = 0; i < 5; i++) {
    	clouds[i] = new Object();
		clouds[i].x = random(1500);
		clouds[i].y = random(300);
		clouds[i].size = random(100);
		clouds[i].dx = random(5);
	}
}

function draw() {
	background(82, 0, 124);
	strokeWeight(1);
	fill(233, 228, 236, 190);
	circle(750, 110, 200); //this is the moon
	textSize(15);
	textAlign(CENTER, CENTER);
	fill(255, 247, 0);
	text('Click Any Grave', 750, 80);
	text('But Beware', 750, 100);
	text('Click The Center Grave Twice', 750, 120);
	text('If You Dare', 750, 140);
	fill(6, 117, 14);
	rect(0, 400, 1500, 300); //this is the horizon
	fill(48, 186, 57);
	beginShape(); //this is the path leading to the "death of theater" grave
	vertex(620, 700);
	vertex(620, 700);
	vertex(690, 450);
	vertex(810, 450);
	vertex(880, 700);
	vertex(620, 700);
	vertex(620, 700);
	endShape();
	strokeWeight(3);
	gates(); //for the gates in the background
	//clouds moving across the screen
	for (var i = 0; i < 5; i++) {
		draw_clouds(clouds[i]);
		clouds[i].x += clouds[i].dx;
		if (clouds[i].x > 1750) {
			clouds[i].x = -250
		}
	}
	//populate the landscape with graves
	strokeWeight(2);
	stroke(0);
	graves(690, 450, 240, 120); //center grave
	graves(480, 680, 260, 180); //left front
	grassLeft(472, 681, 30, 40);
	grassRight(585, 681, 40, 30);
	graves(900, 680, 190, 210); //right front
	grassLeft(892, 681, 20, 70);
	grassRight(990, 681, 20, 40);
	graves(160, 550, 80, 120); //left mid
	grassLeft(152, 551, 10, 90);
	grassRight(195, 551, 10, 90);
	graves(390, 470, 70, 20); //left back
	grassLeft(386, 471, 5, 10);
	grassRight(420, 471, 10, 5);
	graves(1080, 500, 120, 80); //right mid
	grassLeft(1072, 501, 20, 50);
	grassRight(1130, 501, 20, 35);
	graves(1420, 410, 60, 80); //right back
	grassLeft(1412, 411, 10, 30);
	grassRight(1448, 411, 10, 30);
	if (frameCount % 10 == 0) { //grass moves back and forth like the wind is blowing through it
		if (grassLeftOn == true) {
			grassLeftOn = false
		} else {
			grassLeftOn = true
		}
		if (grassRightOn == true) {
			grassRightOn = false
		} else {
			grassRightOn = true
		}
	}
	if (mouseIsPressed) {
		if (mouseX > 690 & mouseX < 810 && mouseY > 330 && mouseY < 450) {
			clickCenterGrave();
		}
		if (mouseX > 480 & mouseX < 610 && mouseY > 500 && mouseY < 680) {
			clickLeftFrontGrave();
		}
		if (mouseX > 900 & mouseX < 980 && mouseY > 470 && mouseY < 680) {
			clickRightFrontGrave();
		}
		if (mouseX > 160 & mouseX < 200 && mouseY > 430 && mouseY < 550) {
			clickLeftMidGrave();
		}
		if (mouseX > 390 & mouseX < 425 && mouseY > 450 && mouseY < 470) {
			clickLeftBackGrave();
		}
		if (mouseX > 1080 & mouseX < 1140 && mouseY > 420 && mouseY < 500) {
			clickRightMidGrave();
		}
		if (mouseX > 1420 & mouseX < 1450 && mouseY > 330 && mouseY < 410) {
			clickRightBackGrave();
		}
	}
}

function mouseReleased() {
if (mouseX > 690 & mouseX < 810 && mouseY > 330 && mouseY < 450) {
		finalSequence(); //launches the final sequence after mouseIsPressed is released for the center grave
	}
}

function draw_clouds(cl) {
	noStroke();
	fill(231, 250, 253);
	circle(cl.x, cl.y, cl.size);
	circle(cl.x - cl.size/4, cl.y, cl.size - (cl.size/5));
	circle(cl.x + cl.size/4, cl.y, cl.size - (cl.size/5));
	circle(cl.x - cl.size/2, cl.y, cl.size - (cl.size/4));
	circle(cl.x + cl.size/2, cl.y, cl.size - (cl.size/4));
}

function gates() {
	line(0, 250, 700, 250);
	line(800, 250, 1500, 250);
	for (var x = 0; x < 710; x += 10) {
		line(x, 250, x, 400);
	}
	for (var x = 800; x < 1510; x += 10) {
		line(x, 250, x, 400);
	}
}

function graves(x, y, hsize, vsize) {
	fill(165);
	beginShape();
	vertex(x, y);
	vertex(x, y);
	vertex(x, y - vsize +  vsize/6);
	curveVertex(x + hsize/8, y - vsize);
	curveVertex(x + 3*hsize/8, y - vsize);
	vertex(x + hsize/2, y - vsize + vsize/6);
	vertex(x + hsize/2, y);
	vertex(x + hsize/2, y);
	endShape();
}

function grassLeft(x, y, hsize, vsize) {
	fill(12, 182, 0);
	beginShape();
	curveVertex(x, y);
	curveVertex(x, y);
	curveVertex(x + hsize/4, y - vsize/2);
	if (grassLeftOn == true) {
		curveVertex(x, y - vsize);
	} else {
		curveVertex(x + hsize, y - vsize);
	}
	curveVertex(x + 3*hsize/4, y - vsize/2);
	curveVertex(x + hsize, y);
	curveVertex(x + hsize, y);
	endShape();
}

function grassRight(x, y, hsize, vsize) {
	fill(12, 182, 0);
	beginShape();
	curveVertex(x, y);
	curveVertex(x, y);
	curveVertex(x + hsize/4, y - vsize/2);
	if (grassRightOn == true) {
		curveVertex(x + hsize, y - vsize);
	} else {
		curveVertex(x, y - vsize);
	}
	curveVertex(x + 3*hsize/4, y - vsize/2);
	curveVertex(x + hsize, y);
	curveVertex(x + hsize, y);
	endShape();
}

function clickCenterGrave() { //text that appears on the screen if you click and hold the center grave
	fill(165);
	rect(0, 0, 1500, 700);
	textSize(100);
	textAlign(CENTER, CENTER);
	fill(0);
	text('Here Lies The Theater Industry', 750, 300);
	textSize(70);
	text('May She Be Reborn Into Something Better', 750, 400);
}

function clickLeftFrontGrave() {
	fill(165);
	rect(0, 0, 1500, 700);
	textSize(90);
	textAlign(CENTER, CENTER);
	fill(0);
	text('RIP Comm. Herschel Brawdweigh', 750, 300); //commercial broadway
	textSize(130);
	text('Father to Tony', 750, 440); //the tony's
}

function clickRightFrontGrave() {
	fill(165);
	rect(0, 0, 1500, 700);
	textSize(90);
	textAlign(CENTER, CENTER);
	fill(0);
	text('Miss Nadia Marika', 750, 300); //miss not america
	textSize(100);
	text('Outside Of US, There Is Nothing', 750, 440); //outside of us/the U.S.
}

function clickLeftMidGrave() {
	fill(165);
	rect(0, 0, 1500, 700);
	textSize(70);
	textAlign(CENTER, CENTER);
	fill(0);
	text('Together Forever: Dez Ein and Prof. Duckshun', 750, 300); //design and production
	textSize(130);
	text('They Were the Best of Us', 750, 440);
}

function clickLeftBackGrave() {
	fill(165);
	rect(0, 0, 1500, 700);
	textSize(90);
	textAlign(CENTER, CENTER);
	fill(0);
	text('Newt Hamster', 750, 300); //new amsterdam theater
	textSize(130);
	text('A Dam Beloved Pet', 750, 440);
}

function clickRightMidGrave() {
	fill(165);
	rect(0, 0, 1500, 700);
	textSize(90);
	textAlign(CENTER, CENTER);
	fill(0);
	text('To Brooks Atkinson', 750, 300); //brooks atkinson was a famous theater critic
	textSize(130);
	text('Critics Die Too', 750, 440);
}

function clickRightBackGrave() {
	fill(165);
	rect(0, 0, 1500, 700);
	textSize(90);
	textAlign(CENTER, CENTER);
	fill(0);
	text('Call Me By Your Name', 750, 300); //theater saying "call me film" because all we have is filmed productions heehee I'm hilarious
	textSize(130);
	text('Film Is All We Have Now', 750, 440);
}

function finalSequence() { //when the mouse is first released, all animated movement stops
	//when the mouse is clicked again and released, the center grave cracks
	//the tiktok logo, a picture of ratatouille appears, and a title appears
	//this is because the ratatouille musical is the only exciting thing happening in theater right now
	//if the user wants to view the other graves after this one, they have to restart the program
	//but if they keep clicking, the tiktok/ratatouille images will rotate!
	noLoop();
	strokeWeight(3);
	line(750, 330, 730, 350);
	line(730, 350, 770, 390);
	line(770, 390, 730, 430);
	line(730, 430, 770, 470);
	line(770, 470, 730, 520);
	line(730, 520, 770, 580);
	line(770, 580, 730, 640);
	line(730, 640, 770, 700);
	push();
	translate(325, 350);
	rotate(radians(angle));
	image(tiktok, -175, -200, 350, 400);
	pop();
	push();
	translate(1175, 350);
	rotate(radians(angle));
	image(rat, -175, -200, 350, 400);
	pop();
	angle += 5
	fill(255, 247, 0);
	textSize(40);
	textAlign(CENTER, CENTER);
	text('"Playbill Announces a Concert Presentation of Ratatouille: The Tiktok Musical"', 750, 30);
}

Final Project – Interactive Pandemic Visualizer

sketch
/*
 * Eric Zhao
 * ezhao2
 * 15-104D
 * Final Project: Pandemic Simulation
 */

var p;
var noiseStep = 0.005;
var noiseParam = 0;
var particles = []; //all particles
var numParticles = 50;
var infectedList = []; //all the particles that are infected
var generation = 0; //what "round" of infection the dots are on
var colorList = []; //list of colors to mark the generation of infection
var paused = false; //is the program paused?
var numCols = 8; //number of dots per row (color picker)
var menuMode = 0; //shows controls or visualizations
var masksOn = false; //are the particles wearing masks
var filterCol = 0; //color 
var filterState = false; //is the program filtering dots?
var masksOn = false; //are the dots wearing masks?
var sketchState = 0; //affects if title screen is displayed


function setup() {
    //makes array of particles and infects the first one
    createCanvas(400, 600);
    background(100);
    colorMode(HSB);
    for(var i = 0; i < numParticles; i++){
        p = makeParticle(random(0, width), random(0, height), 3, 3,
            random(0, 100), i);
        particles.push(p);
    }
    var startColor = color(0, 100, 100);
    colorList.push(startColor);

    particles[0].infectedBy = 0;
    particles[0].size = 25;
    particles[0].infectColor = colorList[0];
    infectedList.push(particles[0]);

}

function draw(){
    stroke(100);
    background(50, 20, 20);
    fill(100);
    for(var i = 0; i < numParticles; i++){
        if(particles[i].infectedBy != -1){
            drawConnections(particles[i]);
        } 
    }
    for(var i = 0; i < numParticles; i++){
        particles[i].draw();
        if(paused == false){
            particles[i].step();
        }
    }
    if(paused == false){
        noiseParam+=noiseStep;
    }
    drawUI();
}


function makeParticle(px, py, dpx, dpy, noiseOffset, index){
    p = {x: px, y: py, dx: dpx, dy: dpy,
         infectedBy: -1, //if infected, which dot infected it?
         ID: index, //the dot's position in the original array
         infectColor: color(0, 0, 100), //color of the dot
         size: 10, //size of the dot
         offset: noiseOffset,
         step: stepParticle,
         draw: drawParticle};
    return p;
}

function stepParticle(){
    //moves particle based on Perlin noise

    this.x = width * map(noise(noiseParam + this.offset), 0, 1, -0.4, 1.4);
    this.y = width * map(noise(noiseParam + this.offset + 5), 0, 1, -0.4, 1.4);
}

function drawParticle(){
    fill(255);
    push();
    noStroke();
    if(this.infectedBy > -1){
            fill(this.infectColor); 
    }
    //show dot only if not filtering or if it matches filter color
    if(filterState == true){
        var tempCLR = getRGB(this.infectColor);
        if(compareColors(tempCLR, filterCol) == false){
            noFill();
        }
    }
    circle(this.x, this.y, this.size);
    pop();
}

function drawConnections(infectedP){
    //draws a line between a dot and its infector
    push();
    var ind = infectedP.infectedBy;
    if(infectedP.infectedBy > -1){
        var tempCLR = particles[ind].infectColor;
        strokeWeight(1);
        stroke(tempCLR);
    }
    //show line only if it matches the filter color
    if(filterState == true){
        var infectedRGB = getRGB(tempCLR);
        if(compareColors(tempCLR, filterCol) == false){
            noStroke();
        }
    }
    line(infectedP.x, infectedP.y, particles[ind].x, particles[ind].y);   
    pop();
}

function infection(population, infected){
    //make empty array, push newly infected particles here

    //if particles were infected, generate a new fill color
    //for them

    //adjust size of infected particles
    var base = infected.length;
    var newCases = [];
    print("Previous number: "+infected.length);

    //the basic logic: for all infected particles, check
    //if any non-infected particles are nearby. If so,
    //infect the particle and push it to an empty array.
    //add this array to the whole list of infected particles
    //at the end.
    for(var i = 0; i < infected.length; i++){
        for(var j = 0; j < numParticles; j++){
            if(dist(population[j].x, population[j].y,
            infected[i].x, infected[i].y) < 25
            & population[j].infectedBy == -1){
                if(masksOn == true){
                    if(random(0,1) > 0.66){
                        population[j].infectedBy = infected[i].ID;
                        newCases.push(population[j]);
                    }
                } else {
                    population[j].infectedBy = infected[i].ID;
                    newCases.push(population[j]);
                }
            }
        }
    }

    if(newCases.length > 0) {
        for(var i = 0; i < newCases.length; i++){
            infected.push(newCases[i]);
        }
        generation++;
        var newC = color(random(360), random(25, 100), 100);
        colorList.push(newC);
        print(infected.length);
        print(colorList.length);
        for(i = 0; i < (infected.length-base); i++){
            //makes particle size 90 percent of the one it was infected by
            infected[base+i].size =
                0.9*(particles[infected[base+i].infectedBy].size);
            //changes color to a new random fill (per generation)
            infected[base+i].infectColor = colorList[generation]; //
        }
    }
    print("Number infected: " + infected.length);
    print("Generation: "+ generation);
    print(colorList.length);
    print("__________h_______");
}

function mousePressed(){
    if(sketchState == 0){
        sketchState = 1;
    } else {
        if(mouseY < width){
            infection(particles, infectedList);
        } else if(mouseY >= width & mouseY <= height){
            filterStatus(get(mouseX, mouseY));
        }
    }
}

function keyTyped(){
    if(key === 'p'){
        //pause function that freezes movement but allows infection
        pauseMove();
    }
    if(key === 'r'){
    //Resets dot states but not entire sketch
        resetSketch();
    }
    if(key === 'h'){
    //switches menu between guide and data visualization
        if(menuMode == 0){
        menuMode = 1;
        print('data');
        } else {
            menuMode = 0;
            print('help');
        }
    }
    if(key === 'm'){
    //toggles masks on/off
        if (masksOn){
            masksOn = false;
        } else{
            masksOn = true;
        }
    }
}

function titleScreen(){
    push();
        translate(25, 75);
        stroke(100);
        fill(50, 5, 100, 0.9);
        textSize(14);
        var rectW = width - 50;
        var rectH = 0.48*height;
        var backgroundTxt = "This is a simple simulation of a viral outbreak \
such as COVID-19 modeled with a number of colored dots.";
        var inst_1 = "1) Each time you click, all infected (colored) dots \
        will infect those nearby."
        var inst_2 = "2) Connections between the infector and newly infected \
            dot are shown."
        var inst_3 = "3) Make the dots wear masks by pressing [ W ]."
        var inst_4 = "4) Filter infected dots by pressing [ H ]."
        var inst_5 = "5) Reset all dots with [ R ]."
        var inst_6 = "(click anywhere to hide)"

        rect(0, 0, rectW, rectH, 10);
        fill(0);
        textSize(18);
        textStyle(BOLD);
        text("Visualizing a Pandemic", 20, 30);
        textSize(14);
        text("How to Use:", 20, 105);
        textSize(12);
        textStyle(NORMAL);
        text(backgroundTxt, 20, 50, 300, 150);
        text(inst_1, 20, 120, 300, 150);
        text(inst_2, 20, 160, 300, 150);
        text(inst_3, 20, 200, 300, 150);
        text(inst_4, 20, 225, 300, 150);
        text(inst_5, 20, 260);
        text(inst_6, 215, 28);
        stroke(0);
        line(20, 40, rectW-20, 40);
    pop();
}

function drawUI(){
    push();
        if(sketchState == 0)
            titleScreen();
        translate(25, width);
        fill(50, 20, 25, 0.75);
        textSize(14);
        var rectW = width - 50;
        var rectH = (height-width)-25;
        rect(0, 0, rectW, rectH, 10);
        noStroke();
        fill(100);
        textSize(14);
        textStyle(BOLD);

        if(masksOn){
            fill(120, 72, 90);
            text("Masks ON", 275, -10);
        } else{
            fill(0, 72, 90);
            text("Masks OFF", 275, -10);
        }

        fill(100);
        if(menuMode == 0){
            displayHelp();
        } else{
            displayData();
        }
    pop();
    push();
    if(paused){
        rect(width-50, 25, 8, 25);
        rect(width-34, 25, 8, 25);
    } else {
        triangle(width-50, 25, width-50, 50, width-25, 37.5);
    }
}

function displayHelp(){
        textSize(14);
        textStyle(BOLD);
        text("Guide", 20, 30);
        textStyle(NORMAL);
        textSize(12);
        text("( Press [ H ] to switch to filters )", 65, 30);
        text("[ P ] : Freeze / Unfreeze dots", 20, 100);
        text("[ R ] : Disinfect all dots (except Patient Zero)", 20, 80);
        text("[ CLICK ] to infect nearby dots", 20, 60);
        text("[ M ] : Toggle masks ON / OFF", 20, 120);
        text("[ H ] : Show / Hide controls", 20, 140);
        stroke(100);
}

function displayData(){
        noStroke();
        fill(100);
        textSize(14);
        textStyle(BOLD);
        text("Filter Data", 20, 30);
        textStyle(NORMAL);
        textSize(12);
        if(filterState){
            text("( CLICK away from squares to show all )", 90, 30);
        } else{
            text("( CLICK to isolate )", 90, 30);
        }
        drawPalette();
}

function resetSketch(){
    //code modified from setup();
    for(var i = 0; i < numParticles; i++){
            particles[i].infectedBy = -1;
            particles[i].infectColor = color(0, 0, 100);
            particles[i].size = 10;
    }
    //wipes all modifier arrays
    filterState = false;
    generation = 0;  
    infectedList = [];
    colorList = [];
    var startColor = color(0, 100, 100);
    colorList.push(startColor);

    //reinfects first dot
    particles[0].infectedBy = 0;
    particles[0].size = 25;
    particles[0].infectColor = colorList[0];
    infectedList.push(particles[0]);
}

function pauseMove(){
    if(paused === true){
        paused = false;
        print('resumed');
    } else {
        paused = true;
        print('paused');
    }
}

function drawPalette() {
    //on visualization UI, draw color palette to filter dots
    translate(20, 50);
    var count = 0;
    numRows = ceil(colorList.length / numCols);
    for(var i = 0; i < numRows; i++) {
        for(var j = 0; j < numCols; j++) {
            if(count < colorList.length) {
                fill(colorList[(i * 6) + j]);
                drawColor(40 * (j), 40*i, 30);
                count++;
            }
        }
    }
}

function drawColor(x, y, rSize) {
    rect(x, y, rSize, rSize, 0.125*rSize);
}

function filterStatus(currentColor){
    //reads color of cursor location, enables filtering
    //if the mouse is over a colored square
    if(menuMode != 0){
        for(var i = 0; i < colorList.length; i++){
            print("Infected Color "+i);
            var infectRGB = getRGB(colorList[i]);
            var cursorRGB = getRGB(currentColor);
            print("infected color: " + infectRGB);
            print("cursor color: " + cursorRGB);

            if(compareColors(infectRGB, cursorRGB) == true){
                filterCol = cursorRGB;
                filterState = true;
                return;
            }
        }
    }
    filterState = false;
}

function getRGB(inputColor){
    //converts a color into RGB
    push();
    sampledColor = inputColor;
    colorMode(RGB, 255, 255, 255, 1);
    var r = red(sampledColor);
    var g = green(sampledColor);
    var b = blue(sampledColor);
    sampledColor = color(r, g, b);
    pop();
    return sampledColor;
}

function compareColors(color1, color2){
    //approximates two colors and checks if they are identical
    var r1 = round(red(color1));
    var g1 = round(green(color1));
    var b1 = round(blue(color1));
    var r2 = round(red(color2));
    var g2 = round(green(color2));
    var b2 = round(blue(color2));

    if(r1 == r2 & g1 == g2 && b1 == b2){
        return true;
    }

    return false;

}

When quarantines first started being imposed, I found Youtube channels dedicated to data visualization and education answered all my questions about the COVID-19 pandemic and engaged me to learn further.

Inspired by channels like Primer and 3Blue1Brown, I made an interactive simulation / visualization of a pandemic. Since I had been curious about contact tracing for a while but never understood the concept, I decided to use this opportunity to learn more about it and make it a core feature in my project.

In my visualizer, a population of dots are wandering around the screen. Clicking will cause all infected dots to infect nearby ones. Colored, larger dots have been infected. Additionally, you can see which dot infected which other dot through connecting lines. Since this rough form of contact tracing still gets messy, you can also filter the dots by color (or “generation” of infection) to find “super-spreader” dots.

A good chunk of my time was spent making sure all the interactions worked together without any errors or slowdowns. With more time, I would try mimicking real-life contact tracing more closely through recursion and add animations to the dots.

Below is a short demo of my project demonstrating the difference that having masks on makes on the speed of infection.

Final Proj: Quarantine Baking Simulator

sketchDownload
//iris yip
//15-104
//deliverable week 15
//final project

var page = 1; //set up a bunch of different "scenes"
let spatula;


function setup() {
  createCanvas(500, 500);
  
}

function preload(){
  img1 = loadImage("https://i.imgur.com/cAQMiab.png");
  img2 = loadImage("https://i.imgur.com/Uab6uYu.png");
  img3 = loadImage("https://i.imgur.com/usB1pJE.png");
  img4 = loadImage("https://i.imgur.com/Imd0Dek.png");
  img5 = loadImage("https://i.imgur.com/XLEACUR.png");
  img6 = loadImage("https://i.imgur.com/iljGqZW.png");
  img7 = loadImage("https://i.imgur.com/Y2B65ub.png");
  img8 = loadImage("https://i.imgur.com/ZI2Soyy.png");
  
  day1 = loadImage("https://i.imgur.com/TPgUWUu.png");
  day3 = loadImage("https://i.imgur.com/XwD89x3.png");
  day4 = loadImage("https://i.imgur.com/i58a6SY.png");
  day6 = loadImage("https://i.imgur.com/WIZ958Z.png");
  day7 = loadImage("https://i.imgur.com/9f5exQ8.png");
  day8 = loadImage("https://i.imgur.com/fWXxfYN.png");
  
  fin = loadImage("https://i.imgur.com/IDq3zRs.png");
  die = loadImage("https://i.imgur.com/smkS7Ca.png");
  
  //spatula
  
  spatula1= loadImage("https://i.imgur.com/FMitxk1.png");
  
  //knife
  
  knife1 = loadImage("https://i.imgur.com/a9yMiM4.png");
  
}

function draw() { //scenes
  
//page 1
  if (page == 1) {
  image(img1, 0, 0);
  }

//page 2
  else if (page == 2) {
  image(img2,0,0);
  }

//page 3
  else if (page == 3) {
  image(img3,0,0);
  }
  
//page 4
  else if (page == 4){
  image(img4,0,0);
  }
  
//page 5
  else if(page ==5){
  image(img5,0,0);
    
  image(spatula1,mouseX,mouseY);
  }

//page 6
  else if(page ==6){
  image(img6,0,0);
  }

  else if(page ==7){
  image(day1,0,0);
  }
  
  else if(page ==8){
  image(day3,0,0);
  }
  
  else if(page ==9){
  image(day4,0,0);
  }
  
  else if(page ==10){
  image(day6,0,0);
  }
  
  else if(page ==11){
  image(day7,0,0);
  }

  else if(page ==12){
  image(day8,0,0);
  }
  
  else if(page ==13){
  image(img7,0,0);
  }
  
  else if(page ==14){
  image(img8,0,0);
    
  image(knife1,mouseX,mouseY);
  }


//bad end
  else if (page == 20) {
    image(die,0,0);
  }
}


function mousePressed() {
  // PAGE 1 COMMANDS
  if (page == 1) {
    if (mouseX > 90 & mouseX < 300 && mouseY > 220 && mouseY < 400) {
     page = 2; //proceed forwards into the game
    }
    else if (mouseX > 300 & mouseX < 400 && mouseY > 350 && mouseY < 400) {
     page = 1; //stays the same
    }
  }
  
  //PAGE 3 COMMANDS
  if (page ==3) {
    if(mouseX > 350 & mouseY > 200){
      page = 4; //proceed forward
    }
  }
  //page 4
  if (page ==4){
    if(mouseX>400 & mouseY> 220){
      page = 5;
    }
  }
  //page 5
  if (page ==5){
    if(mouseX>150 & mouseX<250 && mouseY>50 && mouseY<250){
      page = 6
    }
  }
  //page 6
  if(page ==6){
    if(mouseX > 400 && mouseX<480 && mouseY> 60 && mouseY<200){
      page = 7;
    }
  }
  //page 7
  if(page ==7){
    if(mouseX>400 & mouseX<480 && mouseY> 60 && mouseY<200){
      page = 8;
    }
  }
  
  if(page ==8){
    if(mouseX>400 & mouseY > 60){
      page =9;
    }
  }
  if(page ==9){
    if(mouseX>50 & mouseX<480 && mouseY>60 && mouseY<300){
      page= 13;
    }
  }
  if(page ==13){
    if(mouseX>10 & mouseY>10){
      page= 14;
    }
  }
  if(page ==14){
    if(mouseX>130 & mouseX < 250 && mouseY> 150 && mouseY<200){
      page=15;
    }
  }
}


function keyPressed(){
  //PAGE 2 COMMNADS
  if(page==2){
    if(key=='a'){
    page =3; //proceed forwards
    }
    else if(key != 'a'){
    page =20; //dies
    }
  }
  
  //day1
  if(page== 7 & 8 && 9 && 11 && 12){
    if(key=='k'){
    page = 20
    }
    else if(key != 'k'){
    page =20;
    }
  }
  
  //last step
  if(page ==15){
    if(key=='360'){
    page = fin
    }
    else if(key !='360'){
    page =20;
    }
  }
}

For my final project, I wanted to make a ‘point-and-click’ adventure style game about baking during quarantine, which would act like a little tutorial adventure of sorts. Growing up, I played a lot of Cooking Mama on the Nintendo, which is where the primary inspiration for this game came from. The instructions for each step are given on the screen, which ranges from clicking on certain parts of the screen to pressing a key and clicking and dragging.

Overall, there were a few parts that I could not get to work (certain scenes being skipped, etc.) but it was really cool to try to replicate the type of mini-game that I would play a lot when I was younger! If I had more time, I’d go back and clean up the code so that it would run more efficiently, and also add in more complex scenes and a grading scale/system rather than the instant death mechanic function.

Final Project: Self-Care Roulette

I wanted to do an interactive code that gives the user a thing to do during COVID-19. It will generate random tasks that the user can complete each day in light of COVID-19 that allows them to look forward to achieving something each day, despite how sometimes we may just want to stay in bed. These tasks can be give a call to someone you miss, water your plants, or do some skincare. For me, I feel like it has been really easy to get lost in not having a plan for the day or having the motivation to do something for myself since COVID had started. I hope this project can be a daily reminder that self-care can be something small but it is essential to maintain. It is fairly easy to navigate, with a play button to start. Then the user will be prompt to pick one category of activities. There is a next button that will display random activities from the chosen category. The user can choose to refresh to get a new activity or move on and finish the roulette. To restart the game, you just have to refresh the page. Some activities are time based, such as making breakfast or go stargazing.

sketch
//Jessie Chen
//D
//Final Project
//Self Care Roulette

//global variables
var title;
var activities = [];
//homepage
var home = true;
//boolean for all categories are not selected
var rel = false;
var soc = false;
var phy = false;
var lng = false;
var qck = false;
//buttons
var play;
var homeButton;
var refresh;
var next;
//cateogies
var relax;
var quick; 
var long;
var social;
var physical;

//load illustration

function preload() {
    //
    title = loadImage("https://i.imgur.com/Q6l9wlM.png");
    play = loadImage("https://i.imgur.com/U4YlZmG.png");


    var activitiesPage = [];
    //long
    activitiesPage[0] = "https://i.imgur.com/sNZV5FH.png"; //sewing
    activitiesPage[1] = "https://i.imgur.com/82QiPiY.png"; //read
    activitiesPage[2] = "https://i.imgur.com/Xld0YcB.png"; //instrument
    activitiesPage[3] = "https://i.imgur.com/4AubEbi.png"; //bake
    //quick
    activitiesPage[4] = "https://i.imgur.com/W1H2LbU.png"; //music
    activitiesPage[5] = "https://i.imgur.com/VJqD19E.png"; //draw
    activitiesPage[6] = "https://i.imgur.com/3wyiMdR.png"; //breakfast
    activitiesPage[7] = "https://i.imgur.com/5wUW5VD.png"; //water plants
    //social
    activitiesPage[8] = "https://i.imgur.com/jtYM2kP.png"; //call
    activitiesPage[9] = "https://i.imgur.com/jrhmmlA.png"; //game
    activitiesPage[10] = "https://i.imgur.com/W4TCa01.png"; //facemask
    activitiesPage[11] = "https://i.imgur.com/fot757c.png"; //movies
    
    //physical
    activitiesPage[12] = "https://i.imgur.com/0zokbRX.png"; //laundry
    activitiesPage[13] = "https://i.imgur.com/pO77RCB.png"; //stretch
    activitiesPage[14] = "https://i.imgur.com/PpYiJD3.png"; //swim
    activitiesPage[15] = "https://i.imgur.com/ew1Hfa5.png"; //run
    //relaxing
    activitiesPage[16] = "https://i.imgur.com/Yg5lxNt.png" //to do list
    activitiesPage[17] = "https://i.imgur.com/TtuDd51.png"; //bath
    activitiesPage[18] = "https://i.imgur.com/8bqSaWf.png"; //cloud
    activitiesPage[19] = "https://i.imgur.com/2oOvf88.png"; //stars

    for (var i = 0; i < activitiesPage.length; i++) {
        activities[i] = loadImage(activitiesPage[i]);
    }
    //buttons
    homeButton = loadImage("https://i.imgur.com/b1m90d7.png");
    categoryPage = loadImage("https://i.imgur.com/ZhCdUXE.png");
    refresh = loadImage("https://i.imgur.com/zR5a1pG.png");
    next = loadImage("https://i.imgur.com/tWvuonn.png");
    end = loadImage("https://i.imgur.com/KEkaCCC.png");

    //categories
    relax = loadImage("https://i.imgur.com/nuImFqN.png");
    quick = loadImage("https://i.imgur.com/K7NvO7p.png");
    long = loadImage("https://i.imgur.com/yWSUDxx.png");
    social = loadImage("https://i.imgur.com/1GUldUu.png");
    physical = loadImage("https://i.imgur.com/Hpf91kH.png");
}

function setup() {
    createCanvas(600, 450);
}

//set to home page, when home is false, shows categories page
function draw() {
    homePage();
    if (home == false) {
            categories();
}
}

//home page, when home is true
function homePage() {
    if (home) {
    background(215, 167, 167);
    circles();
    image(title, 0, 0, width, height);
    image(play, 0, 0, width, height);
    
    }
}

//categories page
function categories() {
    
    background(215, 167, 167);
    circles();
    push();
    imageMode(CENTER);

    //when relaxing is selected it enlarges
    if (!rel) {
        image(relax, 150, 230, 200, 80);
    } else if (rel) {
         image(relax, 150, 230, 300, 130);
    }

    //when social is selected, it enlarges
    if (!soc) {
        image(social, 300, 140, 200, 80);
    } else if (soc) {
        image(social, 300, 140, 300, 130);
    }

    //when physcial is selected, it enlarges
    if (!phy) {
        image(physical, 450, 230, 200, 80);
    } else if (phy) {
        image(physical, 450, 230, 300, 130);
    }

    //when long is selected, it enlarges
    if (!lng) {
        image(long, 200, 330, 200, 80);
    } else if (lng) {
        image(long, 200, 330, 300, 130);
    }

    //when quick is selected, it enlarges
    if (!qck) {
        image(quick, 400, 330, 200, 80);
    } else if (qck) {
        image(quick, 400, 330, 300, 130);
    }
    pop();

    //buttons
    image(homeButton, 0, 0, width, height);
    image(categoryPage, 0, 0, width, height);
}

//display activities
function activity() {
    background(random(150, 200), random(150, 200), random(150, 200));
    circles();
    var r;

    //relaxing activities based on index
    if (rel) {
        if (hour() > 6 & hour() < 18) { //mornings, excluding the stars
            r = int(random([16, 17, 18]));
        } else if (hour(0 > 20 & hour() < 4)) { //night time, excluding the clouds
            r = int(random([16, 17, 19]));
        }
    //long activities
    } else if (lng) { 
        r = int(random(0, 4));
    //physical activities
    } else if (phy) { 
        r = int(random(12, 16));
    //quick activities
    } else if (qck) { 
        if (hour() > 4 & hour() < 12) { //morning including breakfast
            r = int(random(4, 8));
        } else {
            r = int(random([4, 5, 7])); //excluding breakfast past morning
        }
    //social activities
    } else if (soc) {
        r = int(random(8, 12));
    }

    //display image
    image(activities[r], 0, 0, width, height);
    //buttons
    image(next, 0, 0, width, height);
    image(refresh, 0, 0, width, height);
    noLoop();
}

//thanks for playing
function endPage() {
    background(215, 167, 167);
    circles();
    image(end, 0, 0, width, height);
}    

//circles in the background
function circles() {
    for (var x = 0; x <= width; x += 20) {
        for (var y = 0; y<= height; y += 20) {
            noStroke();
            fill(242, 220, 203, 120);
            ellipse(x, y, 10, 10);
        }
    }
}

function mousePressed() {
    //returns to the home page
    if(mouseX >= 30 & mouseX <= 80 && mouseY >= 30 && mouseY <= 80) {
        home = true;
        homePage();
    } 
    // play button brings to categories page
    if (mouseX >= 220 & mouseX <= 380 && mouseY >= 250 && mouseY <= 320) {
        home = false;
        categories();
    //next button displays activies
    } else if(mouseX >= 510 & mouseY >= 370 && mouseX <= 560 && mouseY <= 415) {
        activity();
    //refresh button displays another activity
    } else if(mouseX <= 80 & mouseX >= 25 && mouseY <= 420 && mouseY >= 360) {
        activity();
    //end button to thanks for playing
    } else if (mouseX >= 515 & mouseY >= 15 && mouseX <= 570 && mouseY <= 70) {
        endPage();
    }

    //checks to see if boolean is selected by the user, can click to select and unselect
    if (mouseX >= 10 & mouseX <= 230 && mouseY <= 250 && mouseY >= 210) {
        rel = !rel;
    } else if(mouseX >= 230 & mouseY >= 125 && mouseX <= 360 && mouseY <= 150) {
        soc = !soc;
    } else if(mouseX >= 375 & mouseY >= 210 && mouseX <= 530 && mouseY <= 245) {
        phy = !phy;
    } else if(mouseX >= 145 & mouseY >= 310 && mouseX <= 250 && mouseY <= 340) {
        lng = !lng;
    } else if(mouseX >= 340 & mouseY >= 320 && mouseX <= 455 && mouseY <= 345) {
        qck = !qck;
    }
}

    

Week 15 – Final Project

My project is a simple interactive and informative game based on the theme of Covid-19. The setting takes place in a store with racks that display different supplies people use to prevent the spread of the disease. The main problem I realize these days is that people don’t seem to know which supplies and masks are necessary/appropriate during the pandemic. When you run my program, you should drag the objects in order from numbers 1-12 to either the shopping bag or the trash can. You should place the supply on the shopping bag if you think the object is necessary/effective during the pandemic, and place the supply on the trash can if you think the object is not necessary/effective during the pandemic. You will be able to see texts that explain whether the supplies are helpful or not in preventing Covid-19 when you place them onto the shopping bag or trash can. Also, Christmas season is coming. In celebration of the holiday, I’ve added some Christmas lights to the shop, like how real stores do during the holiday season. If I had more time, I would like to find out other ways to drag objects without having them collect multiple objects when they are overlapped in similar locations on canvas. Although it isn’t a problem in running my program and playing the game, adding codes that help objects be selected one by one despite overlapped locations would be great if I had more time to work on the project.

sketch
//Stefanie Suk
//ssuk@andrew.cmu.edu
//15-104 Section D

//Covid-19 Object Variables
var dentalMask = makedentalMask();
var n95Mask = maken95Mask();
var spongeMask = makespongeMask();
var clothMask = makeclothMask();
var homemadeMask = makehomemadeMask();
var bandanaMask = makebandanaMask();
var thermometer = makeThermometer();
var waterBottle = makewaterBottle();
var toiletPaper = maketoiletPaper();
var soap = makeSoap();
var lowSanitizer = makelowSanitizer();
var highSanitizer = makehighSanitizer();

//Shopping bag and Trash Can Variables
var shoppingBag = makeshoppingBag();
var trashCan = maketrashCan();

//Array of Covid-19 supplies
//Array of Shopping bag and Trashcan 
var covidObjects = [dentalMask, n95Mask, spongeMask, clothMask, homemadeMask, bandanaMask, 
                    thermometer, waterBottle, toiletPaper, soap, lowSanitizer, highSanitizer];
var bagandTrash = [shoppingBag, trashCan];

//offset variable for mouse press and drag supplies
var offsetX;
var offsetY;

//Objects for Covid-19 supplies, Shopping bag, and Trashcan with specific datas
function makeshoppingBag() {
    var shoppingBag = {
        imageUrl: "https://i.imgur.com/WdeIUp0.png",       //imageURL: url of object images
        positionX: 75,                                     //positionX: X position of objects on canvas
        positionY: 375,                                    //positionY: Y position of objects on canvas
    }
    return shoppingBag;
}

function maketrashCan() {
    var trashCan = {
        imageUrl: "https://i.imgur.com/5jjADwl.png",
        positionX: 330,
        positionY: 375,
    }
    return trashCan;
}

function makedentalMask() {
    var dentalMask = {
        imageUrl: "https://i.imgur.com/Lsg9xeK.png",
        imagex: 45,
        imagey: 25,
        positionX: 41,
        positionY: 167,
        informationTwo: "Dental Masks, "
                        + "Dental masks are effective in preventing Covid-19.",                                //informationTwo: texts when placed on trashcan
        information: "Dental Masks, Effective. "
                    + "Dental masks are designed to prevent the spread of droplets and splatters. "
                    + "Wearing surgical masks can reduce the spread of respiratory infection."
                    + "*Important* Surgical masks are designed to be used only once."                      //information: texts when placed on shopping bag
    }
        return dentalMask;
}

function maken95Mask() {
    var n95Mask = {
        imageUrl: "https://i.imgur.com/b5hh3hx.png",
        imagex: 45,
        imagey: 25,
        positionX: 106,
        positionY: 167,
        informationTwo: "N95 Masks, N95 masks are effective in preventing Covid-19.",
        information: "N95 Masks, Effective. N95 is the most protective mask against coronavirus and other respiratory "
                    + "diseases. It filters out 95% of particles from the air."
    }
    return n95Mask;
}

function makespongeMask() {
    var spongeMask = {
        imageUrl: "https://i.imgur.com/KhnI7An.png",
        imagex: 45,
        imagey: 25,
        positionX: 171,
        positionY: 169,
        informationTwo: "Sponge Mask, Not Effective. Sponge mask, or neoprene mask, may be designed to be comfortable, but "
                        + "is not effective in preventing the spread of Covid-19 disease. "
                        + "*Tips* Adding replaceable filer can help with the mask's effectiveness.",
        information: "Sponge Mask, Sponge masks are not effective in preventing Covid-19."
    }
    return spongeMask;
}

function makeclothMask() {
    var clothMask = {
        imageUrl: "https://i.imgur.com/SierEPM.png",
        imagex: 45,
        imagey: 25,
        positionX: 41,
        positionY: 242,
        informationTwo: "Effective, Cloth Masks bought in stores are effective in preventing Covid-19.",
        information: "Cloth Mask, Effective. A typical store-bought cloth mask is approximately 50% protective, but "
                    + "depending on its construction and quality, it can protect the virus to about 80-95%."
    }
    return clothMask;
}

function makehomemadeMask() {
    var homemadeMask = {
        imageUrl: "https://i.imgur.com/scSV8qR.png",
        imagex: 45,
        imagey: 25,
        positionX: 106,
        positionY: 242,
        informationTwo: "Handmade Mask, Not Effective. A single-layered handmade mask only filters 1% of particles "
                        + "from air. A two-layered cotton mask can filter only about 35% of particles. *Tips* More "
                        + "layers with more dense woven fabrics can increase the protection.",
        information: "Not Effective, Handmade Masks are not effective in preventing Covid-19."
    }
    return homemadeMask;
}

function makebandanaMask() {
    var bandanaMask = {
        imageUrl: "https://i.imgur.com/QX6uDBJ.png",
        imagex: 45,
        imagey: 25,
        positionX: 171,
        positionY: 242,
        informationTwo: "Bandanas, Not Effective. Covering your mouth and nose with a bandana provides little "
                        + "protection against droplets and does not filter particles from air.",
        information: "Not Effective, Bandanas are not effective in preventing Covid-19."
    }
    return bandanaMask;
}

function makeThermometer() {
    var thermometer = {
        imageUrl: "https://i.imgur.com/qbeSsFP.png",
        imagex: 30,
        imagey: 40,
        positionX: 285,
        positionY: 160,
        informationTwo: "Effective, Thermometers are effective in preventing Covid-19.",
        information: "Thermometer, Effective. Using a thermometer to check your body temperature daily is always "
                    + "important in preventing the spread of Covid-19. *Important* Make sure to check your body temperature does not go over 37.8C."
    }
    return thermometer;
}

function makewaterBottle() {
    var waterBottle = {
        imageUrl: "https://i.imgur.com/0h5YpiF.png",
        imagex: 20,
        imagey: 45,
        positionX: 359,
        positionY: 156,
        informationTwo: "Not Effective, Water. Suggestions are that drinking every 15 minutes would wash any virus down the throat, but there are at "
                        + "least thousands of viruses that we come into contact with and it’s highly unlikely you would wash all that virus down. "
                        + "Thus, the primary way the virus is transmitted is through respiratory droplets in the air.",
        information: "Not Effective, Drinking water is not effective in preventing Covid-19."
    }
    return waterBottle;
}

function maketoiletPaper() {
    var toiletPaper = {
        imageUrl: "https://i.imgur.com/pxMwAgb.png",
        imagex: 35,
        imagey: 35,
        positionX: 415,
        positionY: 161,
        informationTwo: "Toilet Papers, Not effective. There is absolutely no reason to buy toilet papers for preventing Covid-19. Stop stockpiling "
                        + "toilet papers, because they don’t help you prevent the spread of Covid-19.",
        information: "Not Effective, Toilet Papers are not effective in preventing Covid-19."
    }
    return toiletPaper;
}

function makeSoap() {
    var soap = {
        imageUrl: "https://i.imgur.com/JQBa2uV.png",
        imagex: 40,
        imagey: 30,
        positionX: 285,
        positionY: 239,
        informationTwo: "Effective, Soaps are effective in preventing Covid-19.",
        information: "Soaps, Effective. Washing your hands regularly with soap and water break germs up and remove them, which is one of the most "
                    + "effective ways to prevent Covid-19 from spreading."
    }
    return soap;
}

function makelowSanitizer() {
    var lowSanitizer = {
        imageUrl: "https://i.imgur.com/YdnSXSO.png",
        imagex: 20,
        imagey: 45,
        positionX: 359,
        positionY: 231,
        informationTwo: "40% Alcohol Sanitizers, Not Effective. Sanitizers with 40% alcohol doesn’t really help with disinfection. *Important* "
                        + "Not only sanitizers, but any alcohol solutions that contain less than 70% alcohol is ineffective.",
        information: "Not Effective, Sanitizers with 40% alcohol are not effective in preventing Covid-19."
    }
    return lowSanitizer;
}

function makehighSanitizer() {
    var highSanitizer = {
        imageUrl: "https://i.imgur.com/YZihMaD.png",
        imagex: 20,
        imagey: 45,
        positionX: 424,
        positionY: 231,
        informationTwo: "Effective, Sanitizers with 70% alcohol are effective in preventing Covid-19.",
        information: "70% Alcohol Sanitizers, Effective. Sanitizers (or any alcohol solutions) with 70% alcohol or more are "
                    + "perfect for disinfecting your hands and objects."
    }
    return highSanitizer;
}

function preload() {
    //Preload background image
    backgroundImage = loadImage("https://i.imgur.com/uRLr2HC.png");
    //Preload object images, push objects to new array
    bagandtrashImages = [];
    for (var i = 0; i < bagandTrash.length; i++) {
        bagandtrashImages.push(loadImage(bagandTrash[i].imageUrl));
    }
    covidobjectImages = [];
    for (var i = 0; i < covidObjects.length; i++) {
        covidobjectImages.push(loadImage(covidObjects[i].imageUrl));
    }
}

function setup() {
    createCanvas(500, 600);
    frameRate(10); //Frame rate for christmas lights
}

function draw() {
    //load images of background, covid objects, bag and trash 
    background(220);
    image(backgroundImage, 0, 0, 500, 600);

    for (var i = 0; i < bagandTrash.length; i++) {
        image(bagandtrashImages[i], bagandTrash[i].positionX, bagandTrash[i].positionY, 90, 100);
    }
    for (var i = 0; i < covidObjects.length; i++) {
        image(covidobjectImages[i], covidObjects[i].positionX, covidObjects[i].positionY, covidObjects[i].imagex, covidObjects[i].imagey);
        //drag object with mouse
        if (covidObjects[i].drag) {
            covidObjects[i].positionX = offsetX + mouseX;
            covidObjects[i].positionY = offsetY + mouseY;
        }
        //When objects placed in specific locations (bag and trashcan), text appears
        if ((covidObjects[i].positionX > 65) & (covidObjects[i].positionX < 145)) {
            if ((covidObjects[i].positionY > 400) && (covidObjects[i].positionY < 460)) {
                noStroke();
                fill(163, 160, 72);
                rect(3, 502, 486, 100);
                fill(0);
                text(covidObjects[i].information, 20, 520, 460, 70);                          //texts appear when placed on shopping bag
            }
        }

        if ((covidObjects[i].positionX > 310) & (covidObjects[i].positionX < 395)) {
            if ((covidObjects[i].positionY > 365) && (covidObjects[i].positionY < 465)) {
                noStroke();
                fill(163, 160, 72);
                rect(3, 502, 486, 100);
                fill(0);
                text(covidObjects[i].informationTwo, 20, 520, 460, 70);                       //texts appear when placed on trashcan
            }
        }
    }

    //lightbulbs on ceiling for Christmas holiday season
    for (var i = 5; i < 600; i = i+13) {
        push();
        translate(i, 5);
        makelightBulb();
        pop();
    }
}

function mousePressed() {
    //drag covid objects with mouse when mousepressed
    //drag when mouse pressed in specific range of object
    for (var i = 0; i < covidObjects.length; i++) {
        if (mouseX > covidObjects[i].positionX - 25 & mouseX < covidObjects[i].positionX + 25 && mouseY > covidObjects[i].positionY - 25 && mouseY < covidObjects[i].positionY + 25) {
            offsetX = covidObjects[i].positionX - mouseX;
            offsetY = covidObjects[i].positionY - mouseY;
            covidObjects[i].drag = true;
        }
    }
}

function mouseReleased() {
    for (var i = 0; i < covidObjects.length; i++) {
        covidObjects[i].drag = false;
    }
}

function makelightBulb() {
    //variables for lightbulb colors
    var lightColor;
    var lightVariations;

    //red, green, blue, yellow randomly used
    lightColor = floor(random(1, 5));
    if (lightColor === 1) {
        lightVariations = [255, 0, 0];
    }
    if (lightColor === 2) {
        lightVariations = [0, 255, 0];
    }
    if (lightColor === 3) {
     lightVariations = [0, 0, 255];
    }
    if (lightColor === 4) {
     lightVariations = [255,255,0];
    }

    //left side of canvas, square lightbulbs
    //right side of canvas, circle lightbulbs
    fill(lightVariations);
    if (mouseX > width/2) {
        ellipse(5, 5, 7, 7);
    } else {
        rect(2, 2, 7, 7);
    }
}

Final Project: Spot the Difference

My project is a spot-the-difference game with three levels. To relate the project to 2020, I based my game loosely around the concept of isolation and distance.

My game is based off of online spot-the-difference games I used to play by a developer with the screen name “Ivory.” I was inspired by the moody atmosphere that Ivory was able to create using melancholy images and ambient music.

To play the game, simply click on the differences you find. Try to click precisely when making your guess. The number of remaining differences is shown in the top right corner. If you get stuck you can use the hint button for help, which has a short cooldown of 20 seconds. Try to win without any hints though!

sketch


var count = 0; //counter for effects timing

var hint; //variable to hold the "hint" object

var hintcooldown = 0;
var hinttimer = 0;

var scene1bg;
var scene1;    //variable to hold scene images
var scene2;
var scene3;

var scene = 1; //which scene (level) the player is currently on


var margin = 5;   //tolerence for guess clicks

var scene1DifferencesLinks = ["https://i.imgur.com/ohzNdw0.jpg", "https://i.imgur.com/I33D4yq.png", "https://i.imgur.com/4NeqeQR.png", "https://i.imgur.com/awJOkKc.png", "https://i.imgur.com/qLzzqz7.png", "https://i.imgur.com/QbUGoQQ.png", "https://i.imgur.com/a1iMYvd.png"];  //the "differences" are small .jpgs of edited pieces of the image. They will be placed on top of the image to create the "difference"
var scene2DifferencesLinks = ["https://i.imgur.com/f6goqZk.png", "https://i.imgur.com/YdMfKCs.png", "https://i.imgur.com/n3I5MAl.png", "https://i.imgur.com/EYIHAZV.png", "https://i.imgur.com/uu3811U.png", "https://i.imgur.com/e7dqm0J.png", "https://i.imgur.com/V4bhJe8.png"];
var scene3DifferencesLinks = ["https://i.imgur.com/tvCjM0d.png", "https://i.imgur.com/OhlyeMv.png", "https://i.imgur.com/Cr3qy7U.png", "https://i.imgur.com/RttUtn3.png", "https://i.imgur.com/y9AJnzC.png", "https://i.imgur.com/XqaebA2.png", "https://i.imgur.com/wF5Jwgm.png"];

var scene1Differences = [];
var scene2Differences = [];
var scene3Differences = [];

var scene1DifObjs = [];
var scene2DifObjs = [];
var scene3DifObjs = [];

var bgmusic;
var successnoise;

var difsremaining = 7; //tests how many difs left

function preload() {

    scene1bg = loadImage("https://i.imgur.com/oibVoIs.jpg");

    scene1 = loadImage("https://i.imgur.com/oehMn23.png");  //main images for the 3 scenes
    scene2 = loadImage("https://i.imgur.com/PsOlXha.png");
    scene3 = loadImage("https://i.imgur.com/lVsD9Nu.png");
    
    for(i=0; i<scene1DifferencesLinks.length; i++){
        scene1Differences[i] = loadImage(scene1DifferencesLinks[i]);
    }

    for(i=0; i<scene2DifferencesLinks.length; i++){
        scene2Differences[i] = loadImage(scene2DifferencesLinks[i]);
    }

    for(i=0; i<scene3DifferencesLinks.length; i++){
        scene3Differences[i] = loadImage(scene3DifferencesLinks[i]);
    }

    scene1DifObjs.push(makeDifference(scene1Differences[0], 467, 115, 12, 9));  //make the differences. Each difference object contains the x, y, w, and h of the difference. this is the information the rest of the program functions will need to figure out where to place the difference and when the difference is discovered
    scene1DifObjs.push(makeDifference(scene1Differences[1], 213, 15, 2, 11));
    scene1DifObjs.push(makeDifference(scene1Differences[2], 81, 26, 27, 13));
    scene1DifObjs.push(makeDifference(scene1Differences[3], 12, 211, 14, 20));
    scene1DifObjs.push(makeDifference(scene1Differences[4], 281, 329, 25, 26));
    scene1DifObjs.push(makeDifference(scene1Differences[5], 491, 250, 99, 6));
    scene1DifObjs.push(makeDifference(scene1Differences[6], 317, 195, 15, 11));

    scene2DifObjs.push(makeDifference(scene2Differences[0], 527, 219, 47, 8));
    scene2DifObjs.push(makeDifference(scene2Differences[1], 319, 252, 24, 17));
    scene2DifObjs.push(makeDifference(scene2Differences[2], 388, 185, 12, 43));
    scene2DifObjs.push(makeDifference(scene2Differences[3], 47, 339, 82, 33));
    scene2DifObjs.push(makeDifference(scene2Differences[4], 573, 81, 34, 40));
    scene2DifObjs.push(makeDifference(scene2Differences[5], 211, 158, 8, 93));
    scene2DifObjs.push(makeDifference(scene2Differences[6], 413, 352, 19, 16));

    scene3DifObjs.push(makeDifference(scene3Differences[0], 328, 209, 19, 21));
    scene3DifObjs.push(makeDifference(scene3Differences[1], 475, 30, 17, 41));
    scene3DifObjs.push(makeDifference(scene3Differences[2], 262, 293, 12, 53));
    scene3DifObjs.push(makeDifference(scene3Differences[3], -16, 155, 56, 64));
    scene3DifObjs.push(makeDifference(scene3Differences[4], 284, 183, 15, 13));
    scene3DifObjs.push(makeDifference(scene3Differences[5], 172, 298, 56, 60));
    scene3DifObjs.push(makeDifference(scene3Differences[6], 73, 274, 29, 21));


    
    bgmusic = loadSound("https://courses.ideate.cmu.edu/15-104/f2020/wp-content/uploads/2020/12/104music.mp3");
    successnoise = loadSound("https://courses.ideate.cmu.edu/15-104/f2020/wp-content/uploads/2020/12/success.wav");


}


function setup() {
    createCanvas(600, 800);
    useSound();
    scene1bg.resize(600, 400);
    frameRate(12);

    hint = {x: 0, y: 0, w: 1, h: 1, alph: 0, drawit: hintDraw, stepit: hintStep};
    
}


function soundSetup() { // setup for audio generation
    bgmusic.setVolume(0.3);
    successnoise.setVolume(1);


}


function draw() {
    // you can replace any of this with your own code:
    background(255);
    bgmusic.play();
    print(difsremaining);
    print(count);

    if(count % 3000 == 0){
        bgmusic.play();
    }

    if(difsremaining == 0){
        difsremaining = 7;
        if (scene == 1) {
            scene = 2;
        } else if (scene == 2){
            scene = 3;
        } else {
            scene = 4;
            count = 1;
        }
    }

    

    if (scene == 1){
        image(scene1bg, 0, 0);
        image(scene1bg, 0, height/2);
        push();
        fill(255, 255, 255, 20);
        rect(0, 0, 600, 800);
        pop();
        image(scene1, 0, 0);
        image(scene1, 0, height/2);  //draw two base scene images

        push();
        fill(255, 255, 255, 150);
        textSize(10);
        text("Differences remaining: " + difsremaining, 470, 15);
        //text("Differences remaining: " + difsremaining, 470, 415);
        pop();

        for(i=0; i<scene1DifObjs.length; i++){
            if(scene1DifObjs[i].found == false){
                scene1DifObjs[i].drawit();
            }
        
        }
        
    }

    if (scene == 2){
        image(scene2, 0, 0);
        image(scene2, 0, height/2);

        push();
        fill(255, 255, 255, 150);
        textSize(10);
        text("Differences remaining: " + difsremaining, 470, 15);
        //text("Differences remaining: " + difsremaining, 470, 415);
        pop();

        for(i=0; i<scene2DifObjs.length; i++){
            if(scene2DifObjs[i].found == false){
                scene2DifObjs[i].drawit();
            }
        
        }

    }

    if (scene == 3){
        image(scene3, 0, 0);
        image(scene3, 0, height/2);

        push();
        fill(255, 255, 255, 150);
        textSize(10);
        text("Differences remaining: " + difsremaining, 470, 15);
        //text("Differences remaining: " + difsremaining, 470, 415);
        pop();

        for(i=0; i<scene3DifObjs.length; i++){
            if(scene3DifObjs[i].found == false){
                scene3DifObjs[i].drawit();
            }
        
        }

    }

    if (hintcooldown > 0){
        push();
        textSize(10);
        fill(255, 255, 255, 150);
        text(hintcooldown, 540, 31);
    }
    if (hinttimer % 12 == 0){
        hintcooldown --;
    }



    hint.drawit(); //hint functions
    hint.stepit();

    drawhintbutton();

    if (scene == 4){
        push();
        image(scene3, 0, 0);
        image(scene3, 0, height/2);
        
        fill(0, 0, 0, count*2.5);
        rect(0, 0, width, height);

        fill(255, 255, 255, count*2.5);
        textSize(50);
        text("END", width/2-50, height/2-10);
        textSize(15);
        text("THANK YOU FOR PLAYING", 205, 450);
        pop();

    }


    
    


    
    if(scene != 4){
        fill(0, 0, 0, 255-count*2.5); //fades in from black

        rect(0, 0, width, height);

    }
  
 
   count++;
   hinttimer++;
}

function mousePressed() {

    if (mouseX>=470 & mouseX<=532 && mouseY>=20 && mouseY<=36 && hintcooldown<0) {
        givehint();
    }

    if(scene == 1) {
        for(i=0; i<scene1DifObjs.length; i++){
            if((scene1DifObjs[i].found == false) & (mouseX >= scene1DifObjs[i].x - margin && mouseX <= scene1DifObjs[i].x + scene1DifObjs[i].w + margin) && (mouseY >= scene1DifObjs[i].y - margin && mouseY <= scene1DifObjs[i].y + scene1DifObjs[i].h + margin)) {
                scene1DifObjs[i].found = true;
                successnoise.play();
                difsremaining--;
                flashDif(scene1Differences[i], scene1DifObjs[i].x, scene1DifObjs[i].y);

            }
            if((scene1DifObjs[i].found == false) & (mouseX >= scene1DifObjs[i].x - margin && mouseX <= scene1DifObjs[i].x + scene1DifObjs[i].w + margin) && (mouseY >= scene1DifObjs[i].y + 400 - margin && mouseY <= scene1DifObjs[i].y + 400 + scene1DifObjs[i].h + margin)) {
                scene1DifObjs[i].found = true;
                successnoise.play();
                difsremaining--;
                flashDif(scene1Differences[i], scene1DifObjs[i].x, scene1DifObjs[i].y);

            }
        }

    }
    print(scene1DifObjs[0].found);

    if(scene == 2) {
        for(i=0; i<scene2DifObjs.length; i++){
            if((scene2DifObjs[i].found == false) & (mouseX >= scene2DifObjs[i].x - margin && mouseX <= scene2DifObjs[i].x + scene2DifObjs[i].w + margin) && (mouseY >= scene2DifObjs[i].y - margin && mouseY <= scene2DifObjs[i].y + scene2DifObjs[i].h + margin)) {
                scene2DifObjs[i].found = true;
                successnoise.play();
                difsremaining--;
                flashDif(scene2Differences[i], scene2DifObjs[i].x, scene2DifObjs[i].y);

            }
            if((scene2DifObjs[i].found == false) & (mouseX >= scene2DifObjs[i].x - margin && mouseX <= scene2DifObjs[i].x + scene2DifObjs[i].w + margin) && (mouseY >= scene2DifObjs[i].y + 400 - margin && mouseY <= scene2DifObjs[i].y + 400 + scene2DifObjs[i].h + margin)) {
                scene2DifObjs[i].found = true;
                successnoise.play();
                difsremaining--;
                flashDif(scene2Differences[i], scene2DifObjs[i].x, scene2DifObjs[i].y);

            }
        }

    }

    if(scene == 3) {
        for(i=0; i<scene3DifObjs.length; i++){
            if((scene3DifObjs[i].found == false) & (mouseX >= scene3DifObjs[i].x - margin && mouseX <= scene3DifObjs[i].x + scene3DifObjs[i].w + margin) && (mouseY >= scene3DifObjs[i].y - margin && mouseY <= scene3DifObjs[i].y + scene3DifObjs[i].h + margin)) {
                scene3DifObjs[i].found = true;
                successnoise.play();
                difsremaining--;
                flashDif(scene3Differences[i], scene3DifObjs[i].x, scene3DifObjs[i].y);

            }
            if((scene3DifObjs[i].found == false) & (mouseX >= scene3DifObjs[i].x - margin && mouseX <= scene3DifObjs[i].x + scene3DifObjs[i].w + margin) && (mouseY >= scene3DifObjs[i].y + 400 - margin && mouseY <= scene3DifObjs[i].y + 400 + scene3DifObjs[i].h + margin)) {
                scene3DifObjs[i].found = true;
                successnoise.play();
                difsremaining--;
                flashDif(scene3Differences[i], scene3DifObjs[i].x, scene3DifObjs[i].y);

            }
        }

    }


    

}

function flashDif(img, x, y){   //difference flashes when found
    image(img, x, y);
    image(img, x, y + 400);
  
        img.style.visibility = 'visible';
        img.style.visibility = 'hidden';

    
}


function makeDifference(img, dx, dy, dw, dh) {          //create the difference image as an object
    var difFound = false;                               //keeps track of whether or not the difference has been found
    var dif = {difimage: img, x: dx, y: dy, w: dw, h: dh, drawit: differenceDraw, found: difFound}
    return dif;

}

function differenceDraw() {       //place the difference image on the screen
    image(this.difimage, this.x, this.y);

}

function drawhintbutton() {
    push();
    noStroke();
    if (mouseX>=470 & mouseX<=532 && mouseY>=20 && mouseY<=36 && hintcooldown<0) {
        fill(255, 255, 0, 40);
    } else {
        fill(255, 255, 255, 40);
    }
    
    rect(470, 20, 62, 16);
    push();
    if (mouseX>=470 & mouseX<=532 && mouseY>=20 && mouseY<=36 && hintcooldown<0) {
        fill(255, 255, 0, 170);
    } else {
        fill(255, 255, 255, 170);
    }
    textSize(10);
    text("Hint please!", 474, 31);
    pop();
    pop();
}

function givehint(){ //gives the player a hint based on which differences are not found
    if(scene == 1){
        for(i=0; i<=scene1DifObjs.length; i++){
            if(scene1DifObjs[i].found == false){
                hint.x = scene1DifObjs[i].x + random(-60, 60);
                hint.y = scene1DifObjs[i].y + random(-60, 60);
                hint.alph = 200;
                hintcooldown = 20;
                counttimer = 0;
                return;
            }
        }
    }

}

function hintDraw(){
    
    push();
    strokeWeight(2);
    fill(255, 255, 0, 0);
    stroke(255, 255, 0, this.alph);
    ellipse(this.x, this.y, 220, 220);
    pop();
}

function hintStep(){
    this.alph-= 2;
}






Final Project: Reminders from the Room

finalproject-cb
//Caitlyn Baensch
//cbaensch@andrew.cmu.edu
//Section D

var shelf;
var bedroom;
var wind;
var steam;
var birds = [];
var fish = [];
var enter = false;
var phoneon = false;
var fadeg = 140;
var fadeb = 150;
var steamy = 266;
var steamdy = .5;

//load illustrations
function preload() {
    shelf = loadImage("https://i.imgur.com/FCmktLo.png");
    bedroom = loadImage("https://i.imgur.com/WVYB6XA.png");
    wind = loadImage("https://i.imgur.com/qjTV5Rs.png");
    steam = loadImage("https://i.imgur.com/k561c6E.png");
}

function setup() {
    createCanvas(600, 425);
    
    //setup for birds
    for(var i = 0; i < 6; i++) {
        birdsx = random(width);
        birds[i] = makeBirds(birdsx);
    }

    //setup for fish
    for (var i = 0; i < 12; i++) {
        fishx = random(325, 385);
        fish[i] = makeFish(fishx);
    }

    frameRate(12);
    angleMode(DEGREES);
}

function draw() {
    background(0);

    //show intro screen with instructions
    intro();

    //show the room when enter button is clicked
    if(enter == true) {
        room();
    }
}


//birds helper functions
//update the bird positions and display them
function displayBirds() {
    for (var i = 0; i < birds.length; i++) {
        birds[i].move();
        birds[i].draw();
    }
}

//remove birds that are off the canvas from the array
function removeBirds() {
    var birdsToKeep = [];
    for (var i = 0; i < birds.length; i++) {
        if (birds[i].x + birds[i].w > 0) {
            birdsToKeep.push(birds[i]);
        }
    }
    birds = birdsToKeep;
}

//with a small probability, add a new bird to end of the array
function addNewBirds() {
    var newBirdLikelihood = 0.12;
    if (random(0,1) < newBirdLikelihood) {
        birds.push(makeBirds(width+20));
    }
}

function moveBird() {
    this.x += this.speed;
}

function drawBird() {
    push();
    translate(this.x, this.y);
    fill(249, 228, 222);
    noStroke();
    triangle(0, 0, this.w, -this.h, this.w, 0);
    triangle(0, 0, this.w/2, -this.h, this.w/2, 0);
    pop();
}

//bird constructor (random location, size, and speed)
function makeBirds(birdsx) {
    var b = {x: birdsx, y: random(height),
            w: random(8, 16), h: random(4, 8),
            speed: random(-5, -10),
            move: moveBird,
            draw: drawBird
    }
    return b;
}


//fish helper functions
function displayFish() {
    for (var i=0; i<fish.length; i++) {
        fish[i].move();
        fish[i].draw();
    }
}

function moveFish() {
    this.x += this.dx;
    if (this.x > 385 || this.x < 325) {
        this.dx = -this.dx;
    }
}

function drawFish() {
    noStroke();
    fill(this.c);
    ellipse(this.x, this.y, this.w, this.h);
    if (this.dx < 0) {
        triangle(this.x+3, this.y, this.x+6, this.y-3, this.x+6, this.y+3);
    } else {
        triangle(this.x-3, this.y, this.x-6, this.y-3, this.x-6, this.y+3);
    }
}

//fish constructor (random location, size, speed, and color)
function makeFish(fishx) {
    var f = {x: fishx, y: random(195, 240),
        w: random(8, 12), h: random(3, 7),
        dx: random(-3, 3),
        c: color(random(100, 255), random(100, 255), random(100, 255)),
        move: moveFish,
        draw: drawFish
    }
    return f;
}


//wall clock function
function clock() {
    var hr = hour();
    var mn = minute();
    var sc = second();

    //second hand
    strokeWeight(2.15);
    var s = map(sc, 0, 60, 0, 360);
    push();
    rotate(s);
    stroke(255, 176, 148);
    line(0, 0, 16, 0);
    pop();

    //minute hand
    var m = map(mn, 0, 60, 0, 360);
    push();
    rotate(m);
    stroke(110, 160, 133);
    line(0, 0, 14, 0);
    pop();

    //hour hand
    var h = map(hr % 12, 0, 12, 0, 360);
    push();
    rotate(h);
    stroke(110, 160, 133);
    line(0, 0, 10, 0);
    pop();
}


//interactions for tip screens helper functions
//headphones: listen to music
function headphones() {
    if (mouseX > 22 & mouseX < 80 && mouseY > 150 && mouseY < 170) {
        tip('Listen to your favorite music.', 'Listening to music can help you focus, feel better, and relax.');
    }
}

//journals: reflect
function journals() {
    if (mouseX > 73 & mouseX < 102 && mouseY > 110 && mouseY < 122) {
        tip('Reflect on how you are feeling.', 'Try keeping a journal or taking some time each day to check in with yourself.');
    }
}

//tea: stay hydrated
function tea() {
    if (mouseX > 113 & mouseX < 134 && mouseY > 287 && mouseY < 308) {
        tip('Stay hydrated with water or a cup of hot tea.', 'Being well-hydrated improves sleep quality, cognition, and mood.');
    }
}

//phone: check in with friends
function phone() {
    if (mouseX > 137 & mouseX < 171 && mouseY > 303 && mouseY < 312) {
        tip('Check in with friends and family.', 'Catch up over text or phone call and see how everyone is doing.');
    }
}

//window: go for a walk
function win() {
    if (mouseX > 158.5 & mouseX < 268.5 && mouseY > 107 && mouseY < 217) {
        tip('Go outside for a 10-minute walk.', 'A walk can improve your mood and cognitive function as well as reduce stress.');
    }
}

//fish tank and cat: spend time with pets
function pets() {
    if ((mouseX > 313 & mouseX < 401 && mouseY > 187 && mouseY < 251) || (mouseX > 543 && mouseX < 594 && mouseY > 245 && mouseY < 274)) {
        tip('Spend time with pets.', 'Some quality time with your animal friends is sure to make you smile.');
    }
}

//books: read a book
function books() {
    if (mouseX > 325 & mouseX < 367 && mouseY > 278 && mouseY < 308) {
        tip('Get lost in a book.', 'Reading is the perfect way to unwind. Try it before bed instead of using your phone.');
    }
}

//yoga mat: stretch
function yoga() {
    if (mouseX > 318 & mouseX < 600 && mouseY > 378 && mouseY < 425) {
        tip('Take a break and stretch.', 'Doing yoga can help break up long periods of sitting at a desk and clear your mind.');
    }
}

//decorations: surround yourself with things that make you happy
function decoration() {
    if ((mouseX > 314 & mouseX < 378 && mouseY > 122 && mouseY < 174) || (mouseX > 468 && mouseX < 496 && mouseY > 182 && mouseY < 212)) {
        tip('Surround yourself with things you love.', 'Decorate your space with photos, art, and other meaningful items. Avoid clutter!');
    }
}

//clock: keep a daily routine
function time() {
    if (mouseX > 521 & mouseX < 572 && mouseY > 112 && mouseY < 163) {
        tip('Keep a daily routine.', 'Having a daily routine can reduce anxiety and create rhythm in your day.');
    }    
}


//text formatting for headers
function header() {
    textSize(24);
    textStyle(BOLD);
    textFont('Avenir');
}

//text formatting for body
function body() {
    textSize(14);
    textStyle(NORMAL);
    textFont('Avenir');
}


//tip screens formatting
function tip(line1, line2) {
    background(250, 227, 221, 230);
    fill(237, 122, 93);
    header();
    text(line1, 40, 250);
    body();     
    text(line2, 40, 285);
}


//intro opening screen with title and instructions
function intro() {
    background(255, 136, 108);
    fill(250, 227, 221);
    image(shelf, 40, 100, 286, 95);
    textSize(36);
    textStyle(BOLD);
    textFont('Avenir');
    text('Reminders from the Room', 40, 250);
    body();    
    text('Some quick tips for working from home during the pandemic.', 40, 285);
    text('Enter and hover over elements in the illustration to see relevant tips and info.', 40, 302);
    noStroke();
    fill(250, 227, 221);
    rect(456, 335, 100, 38, 38);
    fill(255, 136, 108);
    header();
    text('Enter', 476.5, 362);
    //if enter button is hovered over
    if (mouseX > 456 & mouseX < 555 && mouseY > 335 && mouseY < 373) {
        fill(244, 181, 161);
        rect(456, 335, 100, 38, 38);
        fill(237, 122, 93);
        header();
        text('Enter', 476.5, 362);
    }
}

//if enter button is pressed, enter the room
function mousePressed() {
    if (mouseX > 456 & mouseX < 555 && mouseY > 335 && mouseY < 373) {
        enter = true;
        room();
    }
}


//room environment
function room() {
    //landscape outside window
    image(wind, 146, 100, 125, 116);
    
    //birds flying outside window
    displayBirds();
    removeBirds();
    addNewBirds();

    //bedroom
    image(bedroom, 0, 0, width, height);

    //fish swimming in fish tank
    push();
    displayFish();
    fill(178, 190, 209, 50);
    rect(326, 187, 74, 56);
    pop();

    //wall clock by bed ticking
    push();
    translate(545, 138);
    rotate(-90);
    clock();
    pop();

    //phone screen new notifications
    push();
    noStroke();
    fill(37, 66, 52);
    quad(140, 303.5, 165, 303.5, 167, 308, 139, 308);
    if (frameCount % 90 == 0) {
        phoneon = true;
        if (phoneon == true) {
            fadeg = 240;
            fadeb = 240;
            fill(249, fadeg, fadeb);
            quad(140, 303.5, 165, 303.5, 167, 308, 139, 308);
        }
    } else {
        if (fadeg >= 140 & fadeb >= 150) {
            fadeg = fadeg - 3;
            fadeb = fadeb - 3;
            fill(249, fadeg, fadeb);
            quad(140, 303.5, 165, 303.5, 167, 308, 139, 308);
            phoneon = false;
        }
    }
    pop();

    //steam floats up over cup of tea
    image(steam, 115, steamy, 14, 24);
    steamy -= steamdy;
    if (steamy <= 257 || steamy > 267) {
        steamdy = -steamdy;
    }

    //show tip screens for user interaction
    headphones();
    journals();
    tea();
    phone();
    win();
    pets();
    books();
    yoga();
    decoration();
    time();
}

For my final project, I made an interactive illustration featuring tips for working from home during the pandemic. With hours spent locked up inside in endless Zoom meetings, I think that Zoom fatigue, a warped sense of time, and feelings of stress and anxiety are all common sentiments. I want to bring awareness to these challenges and offer simple strategies to combat fatigue and stress. I wanted my program to have a relaxing and calming feeling to it, so I chose a light, playful color palette for my illustrations as well as used an inviting typeface.

To interact with my program, follow the instructions on the intro screen. Click the “Enter” button to enter the room and explore the environment. Hover over different elements in the animated illustration to view relevant tips and information.

If I had more time, I might consider adding sound to some of the elements in the room. I would also maybe think about adding illustrations/animations to the advice screens and using p5.js to create patterns on objects or wallpaper.

Final Project

sketch
/*
Bon Bhakdibhumi
bbhakdib
Section D
*/

var particles = [];


function setup() {
    createCanvas(400, 400);
    for (var i = 0; i < 50; i ++) {
        particles[i] = new Object();
        particles[i].x = random(width);
        particles[i].y = random(height);
        particles[i].dx = random(-2,5);
        particles[i].dy = random(-2,5);
        particles[i].c = color(86, 137, 179);
        particles[i].covid = false;
    }
    frameRate(25);
}

function draw() {
    background(201, 156, 105);
    for (var j = 0; j < 50; j ++) {
        drawParticles(particles[j]);
        moveParticles(particles[j]);
    }
    if (frameCount == 50 || frameCount % 100 == 0) {
        particles[2].covid = true;
    }
// calculate distance for infection
    for (var i = 0; i < particles.length; i ++) {
        for (var j = 0; j < particles.length; j ++) {
            var d = int(dist(particles[i].x, particles[i].y, particles[j].x, particles[j].y));
            if (d < 20) {
                if (particles[i].covid == true) {
                    particles[j].covid = true;
                }
            }
        }
    }
// display counter
//if all particles have covid, then display the warning text
     var covidCounter = 0;
    for (var i = 0; i < particles.length; i ++) {
        if (particles[i].covid == true) {
            covidCounter ++;
        }
    }
    noStroke();
    fill(255);
    textAlign(LEFT);
    textSize(20);
    text("Infected People:", 10, 25);
    text(covidCounter, 160, 25);
    if (covidCounter == particles.length) {
        fill(255, 253, 131);
        textAlign(CENTER);
        textSize(30);
        text('STOP CORONA!', width/2, height/2.15);
        textSize(35)
        text('KEEP YOUR DISTANCE!', width/2, height/1.75);
    }
}

// reset canvas and cure all particles
function keyPressed() {
        for (var i = 0; i < particles.length; i ++) {
            particles[i].covid = false;
        }
}

// cure covid with mouse
function mousePressed() {
    for (var i = 0; i < particles.length; i ++) {
        var mouseDistance = int(dist(mouseX, mouseY, particles[i].x, particles[i].y));
        if (mouseDistance < 10) {
            particles[i].covid = false;
        }
    }
}

function drawParticles(p) {
    if (p.covid == true) {
        stroke(1);
        fill(204, 61, 61);
    } else {
        stroke(1);
        fill(p.c);
    }
    ellipse(p.x, p.y, 12, 12);
}

function moveParticles(p) {
    p.x += p.dx;
    p.y += p.dy;
    if (p.x > width || p.x < 0) {
        var resetX = random(100);
        if (resetX < 50) {
            p.x = 0;
        } else {
            p.x = width;
        }
    p.dx = random(-2, 5);
    }
    if (p.y > height || p.y < 0) {
        var resetY = random(100);
        if (resetY < 50) {
            p.y = 0;
        } else { 
            p.y = height;
        }
    p.dy = random(-2, 5);
    }
}

The final result of my project is a Covid-19 infection simulator using particles. The canvas represents a public space, and the particles represent people traveling around without social distancing.  Initially, all the particles are not infected. However, as they “travel” around freely–some going off canvas and reentering the canvas from somewhere else–one of the particles would contract “Covid-19, ” turning red. This infected particle will then continue to spread the virus to other particles if other particles are too close–“less than 6 feet”–to it. A counter is displayed at the top left of the canvas, showing how many particles are infected on the screen at the moment. Once every particle is infected, a text is displayed saying “STOP CORONA! KEEP YOUR DISTANCE!”This program is also interactive. Using the mousePressed() function, when the user clicks on an infected particle, he or she can cure it from the virus. However, the particles can become infected again similar to Covid reinfections we have seen on the news. By clicking on any key, the user can also reset the particles to their normal state, restarting the simulation again.

The project is inspired by the frustration I get from watching the news and seeing people not properly social distancing. Hopefully, this project illustrates the importance of social distancing to the viewers while providing them with visual elements that are captivating and interactive. 

If provided more time, I would like to add mutual repulsion to the program, illustrating what effective social distancing looks like, which was one of the main features I planned to implement initially but failed to do so since I didn’t fully understand the physics behind the example code and didn’t want a completely broken program.