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

My project was inspired by a silly idea I had for a flip book of my personal failures revolving around the bouncing ball problem from early on in the class. That evolved into slides containing the ball that you had to click or else the game would end. Eventually I figured with all the problems the year had I could make it America and the ball representing the horrible things popping up all over the country. The user has to keep clicking on the ball as it bounces around the map until they win. Every time the map goes off the canvas, the ball gets faster and more red and the background gets darker. The screen will also begin to crack. Encouraging and frightened dialogue will also appear at the bottom and top respectively, and I’ve included instructions and a brief introduction in the console at the start of the game. I wish I had more time to flesh out the theme of the multiple problems of the year and could’ve made multiple minigames rather than just one, or a more involved animation, but after how poorly my proposal turned out I’m very happy with what I have.

sketch

var slides = []
var threat = 0
var prevent = 0
var incomingX = -100
var incomingY = -100
var incomingSize = 20
var u
var t
var rectY = -200
var flag;
var pic;
var hasDrawn = false
var lineX = []
var lineY= []
var gameStart = false
var hasWritten = false


function makeSlide(sX, sY, sDX, slideTime, ballX, ballY, bounceX, bounceY){ 
  slide = {x: sX, y: sY, dx: sDX, timing: slideTime, drawFunction: drawSlide, stepFunction: slideSlide, extra: drawBall, extraMove: ballMove, extraX: ballX, extraY: ballY, moveX: bounceX, moveY: bounceY}
  return slide
}


function drawSlide(){ 
	imageMode(CENTER)
	image(pic, this.x, this.y, 350, 350)
}


function slideSlide(){
	this.x += this.dx
	if(this.x >= 600 || this.x <= -200){
		this.dx *= -1
	} if (this.x == 600){
    		this.dx -= 2
    	} else if (this.x == -200){
    		this.dx += 2
    	} 
}
 

function drawBall(){
	stroke(0, 0, 0)
	fill(256 - threat, (256 -(threat * 13)), 256 -(threat * 13))
	circle(constrain(this.extraX, this.x - 155, this.x + 175), constrain(this.extraY, 30, 350), 40)
}


function ballMove(){
	this.extraX += this.dx 
	this.extraX += this.moveX
	this.extraY += this.moveY
    if (this.extraX >= this.x + 175 || this.extraX <= this.x - 155){
    	this.moveX *= -1
    } if (this.extraY >= 330 || this.extraY <= 30){
    	this.moveY *= -1
    }
    if (mouseIsPressed & dist(mouseX, mouseY, this.extraX, this.extraY) <= 20){
    	prevent += 1	
    }
     else if (this.x == 600 || this.x == -20){
    	threat += 1
    }
}


function preload(){ 
  pic = loadImage("https://i.imgur.com/VD23NPD.png")
  flag = loadImage("https://i.imgur.com/xFTfp2y.jpg")
}


function setup() { 
	angleMode(DEGREES)
	frameRate(15)
    createCanvas(400, 400);

Final Project!

For my final project, I wanted to create a game that presents a picture at the end with hidden images that represent events of 2020. The user is able to click two images to switch the locations of the images, and therefore unscramble the original scrambled image that loads. In order not to pressure the user or make the user create one “right answer”, I chose not to tell the user when the image is “correctly” unscrambled. Therefore, the game acts more like a pressure-free artistic adaptation of an image if they’d like.

sketchDownload
// Susie Kim
// Final Project
// susiek@andrew.cmu.edu
// Section A

// set global variables
 var imgLinks = [
 "https://i.imgur.com/vGhpX7m.jpg", //1
 "https://i.imgur.com/bNtGvLH.jpg", //2
 "https://i.imgur.com/cWHfsai.jpg", //3
 "https://i.imgur.com/hnFyXIy.jpg", //4
 "https://i.imgur.com/06xo9FF.jpg", //5
 "https://i.imgur.com/FJbeTn2.jpg", //6
 "https://i.imgur.com/HZduFcW.jpg", //7
 "https://i.imgur.com/fU2goSM.jpg", //8
 "https://i.imgur.com/i4BVpfk.jpg", //9
 "https://i.imgur.com/dvCAEzm.jpg", //10
 "https://i.imgur.com/t7FzJVW.jpg", //11
 "https://i.imgur.com/MVcrDnK.jpg", //12
 "https://i.imgur.com/QUBppYU.jpg", //13
 "https://i.imgur.com/IPIsgqD.jpg", //14
 "https://i.imgur.com/Inkq9Nc.jpg", //15
 "https://i.imgur.com/FJotQ09.jpg"]; //16

gridRows = 4;
gridCols = 4;
sqSize = 100;

images = [];
puzzles = [];
ids = [];
shuffledIds = [];

clickedImgs = [];
numClicks = 0;

// preload image links into images array
function preload() {
	for (var i=0; i < 16; i++) {
	    images[i] = loadImage(imgLinks[i]);
	}
}

function setup() {
    createCanvas(500, 500);
    background(145, 168, 209);

    // write instructions text ontop and bottom
    fill(255);
    text('complete the puzzle to see the image :)', 150, 35);
    text('click 2 pictures to switch their location!', 150, 475);

    push();

    // write instructions text on left and right
    translate(250, 250);
    rotate(radians(90));
    text('2020 in review', -35, 225);

    rotate(radians(180));
    text('2020 in review', -35, 225);
    pop();


    // create a set of shuffled ids:
    for (var i = 0; i < 16; i++) {
    	ids[i] = i;
    }
    shuffledIds = shuffle(ids);

    // make 16 objects with object properties
    for (var i = 0; i < gridRows; i++) {
    	for (var j = 0; j < gridCols; j++) {
    		puzzles[j+i*4] = makePuzzle(50+j*100, 50+i*100, shuffledIds[j+i*4], j+i*4);
    	}
    }
}

function draw() {
	// draw the grid of shuffled images
	for (var i = 0; i < gridRows; i++) {
    	for (var j = 0; j < gridCols; j++) {
    		puzzles[j+i*4].display();
    	}
    }
}

// create an object that holds the images' properties
function makePuzzle(xlocation, ylocation, idNum, idNum2) {
	var makeImg = { x1: xlocation, 
	                y1: ylocation,
	                shuffId: idNum,
	                origId: idNum2,
	                display: makeImage }
	return makeImg;
}

// function that creates the image
function makeImage() {
	image(images[this.shuffId], this.x1, this.y1, 100, 100);
}

// if mouse is clicked on two images, switch them
function mouseClicked() {
	for (var i = 0; i < 16; i++) {
		if (mouseX > puzzles[i].x1 & mouseX < puzzles[i].x1+100 && // boundaries of any image i
			mouseY > puzzles[i].y1 && mouseY < puzzles[i].y1+100) {
			clickedImgs.push(puzzles[i].origId); // push original Id value of clicked image into clickedImgs array
		    numClicks += 1;
		}
	}
	// if 2 images are clicked, swap, and reset clicknum to 0
	if (numClicks == 2) {
		swapImages();
		numClicks = 0;
	}
}

// function that swaps the two images
function swapImages() {
	// store the x and y values in a temporary variable
	var tempX = puzzles[clickedImgs[0]].x1;
	var tempY = puzzles[clickedImgs[0]].y1

	// switch the x and y values
	puzzles[clickedImgs[0]].x1 = puzzles[clickedImgs[1]].x1;
	puzzles[clickedImgs[0]].y1 = puzzles[clickedImgs[1]].y1;

	puzzles[clickedImgs[1]].x1 = tempX;
	puzzles[clickedImgs[1]].y1 = tempY;

	// clear the clickedImgs array
	clickedImgs.pop();
	clickedImgs.pop();
}

Some examples of imagery I used was a basketball for Kobe Bryant’s death, a fire printed bag for the California and Australia wildfires, a tiger poster for Tiger King, and an Oscar for Parasite winning Best Picture.

Final Project

hcFinal
var picfiles = ["S0UUF6k.png", "QRebAPt.png", "kPzEtew.png", "GDfUrs9.png", 
"RdaK8Db.png", "rJua0RK.png", "OFcSHV3.png", "MOsS3A0.png", "gqBXY2d.png",
"O6M0opw.png", "dMPlHtH.png", "KQPXYro.png", "0k3Synd.png", "lXtNJ7L.png",
"046RWzZ.png", "gybarRF.png"];
var soundfiles = ["a.mp3", "b.mp3", "c.mp3", "d.mp3", "e.mp3", "f.mp3","g.mp3",
"h.mp3", "i.mp3", "j.mp3", "k.mp3", "l.mp3", "m.mp3", "n.mp3", "o.mp3", "p.mp3"]; //Will add more sound files later
var pics = [];
var sounds = [];
var channelobj = [];
var oldindex = -1;
var letters = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p"];
var colors = [];
var titles = ["Australia fire", "BLM protest", "Chadwick Bosewman death", "Death of Chadwick Boseman", "Explosion in Beirut",
"Florida man saves his dog from an alligator", "Gas explosion in Nigeria", "Harvey Weinstein convicted",
"Impeachment trial of Trump", "Joe Biden wins", "Kobe Bryant death", "Locust swarm", "Murder hornets in US",
"NASA's Mars 2020", "Olympics postponed", "Presidential election"]
var relkey=-1;

function preload(){
    //sound files
    for (var i = 0; i < soundfiles.length; i++){
       sounds[i] = createAudio(concat ("https://courses.ideate.cmu.edu/15-104/f2020/wp-content/uploads/2020/12/", soundfiles[i])); //stop() doesn't work with loadSound
        //image files
       pics[i] = loadImage(concat ("https://i.imgur.com/", picfiles[i]));
    }
}

function makeChannel(letter, letterColor){ //Using object to combine letters and their color
    ch = {l: letter, lc: letterColor
         }
    return ch;
}

function setup(){
    createCanvas(500, 500);
    rectMode(CENTER);
    imageMode(CENTER);
    //Giving all letters the same gray color in the beginning
    for (var i = 0; i < letters.length; i++){
        channelobj[i] = makeChannel(letters[i], color(222, 217, 209));
    }
}

function draw(){
    background(240, 255, 254);
    TV();
    drawTitle(relkey);
    drawText(relkey);
}

function drawText(nSelect){
    if (nSelect > -1 & nSelect < letters.length){ //Making selected letter to turn black
        channelobj[nSelect].lc = color(0);
    }
    for (var i = 0; i < channelobj.length; i++){ //Giving characteristics and placement to the letters
        textFont('Baskerville')
        textAlign(CENTER);
        fill(channelobj[i].lc);
        textSize(25);
        text(channelobj[i].l, 20 * i + 100, 450);
    }
    if (nSelect > -1 & nSelect < letters.length){ //Making the letters to turn back to gray
        channelobj[nSelect].lc = color(222, 217, 209);
    }
}

function drawTitle(nSelect){
    noStroke();
    fill(240, 255, 254);
    rect(250, 60, 500, 120); //drawing rect to cover previous titles
    if (nSelect > -1 & nSelect < titles.length){
        textFont('Baskerville')
        textAlign(CENTER);
        fill(0);
        textSize(25);
        text(titles[nSelect], 250, 90);
    }
}

function keyPressed(){
    loop();
    relkey = keyCode - 65; //65 = 'a' keycode, found on p5js.org
    if (relkey > channelobj.length - 1 || relkey < 0){ //Checking the pressed key is in between a~p
        return; //returning any key that is not a~p
    }

    for(var i = 0; i < sounds.length; i++){ //Stopping the audio from previous channel (isPlaying found on p5js.org)
          sounds[i].stop(0);
    }
    sounds[relkey].play(); //Calling the correct audio
    started = true;
}

function TV(){
    //Drawing TV
    stroke(200);
    fill(168, 237, 170);
    rect(250, 270, 370, 270, 35);
    fill(209, 196, 186);
    circle(390, 200, 30);
    circle(390, 250, 30);
    for (var i = 300; i < 360; i += 10){
        rect(390, i, 20, 5);
    }
    fill(100);
       if (relkey > - 1 & relkey < channelobj.length ) {
     image(pics[relkey], 220, 270); //Calling the correct image
    } else {
    rect(220, 270, 260, 220);
    loading();
    for(var i = 0; i < sounds.length; i++){ //Stopping the audio from previous channel
          sounds[i].stop(0);
        }
    }
}

function loading(){ //drawing loading screen
    fill(255);
    push();
    translate(220, 270);
    for (var i = 0; i < 6; i++){
        push();
        rotate(radians(frameCount + 60 * i));
        ellipse(30, 0, 20, 10);
        pop();
    }
    pop();
}

For this project, I wanted to create a television that shows the events that happened in 2020. I was inspired to work on this project since so many people talked about how they will never speak of 2020 in the future. I thought it would be fun to create a program that sums up this year and show it to people in the future to bring their memories back. 

I tried to find a somewhat cute way to show the events since a lot of them are disturbing news. It felt like 2020 was a disastrous year and it was hard to pin down just one main event to show. So I decided to find a way to show all the “key” events of 2020. 

You can watch the channels by pressing the keyboard from “a” to “p.” If you press any keyboard that is not one of those alphabets, the television will stop playing.

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 – social distance

sketch

I created a interactive game about Covid-19. When the program runs, it first gives the instruction of the game for the first few seconds, You have to click on the heads of the humans to remove them, so that they keep social distance. If you successfully keep the number of the people under 4, you succeed. If you fail to do so and there are more than 12 people, game over. I was inspired by how the social distance is crucial to keep us safe during this time of pandemic. We just have to keep ourselves not too crammed in one place! I wish I could add more interactive/intuitive features including click to start or click to restart the game.

//Jae Son , Section C

var humans = []; 

function setup() {
    createCanvas(500, 400);
    rectMode(CENTER);
    frameRate(20);
}

function draw() {
    background(210,237,178);
    
    for (var i = 0; i < humans.length; i++){
      var human = humans[i];
      humans[i].display();
    }
    //game instruction in the beginning
    if (frameCount <80) {
      noStroke();
      fill(152,183,108);
      rect(250,200,300,200);
      fill(255);
      textSize(20);
      textStyle(BOLD);
      text('click the head and',140,180);
      text('remove the humans to',140,200);
      text('keep the social distance',140,220);
    }
    //game starts after the instruction
    if (frameCount > 80 & frameCount % 10 == 0){
      var newHuman = createHuman(random(25,476),random(10,278),0,0);
      humans.push(newHuman);
    } 
    //if there is more than 12 people, game over
    if (humans.length > 12) {
      fill(242,158,158);
      rect(250,200,350,250);
      fill(255);
      textSize(20);
      textStyle(BOLD);
      text('No one bothered to keep',130,140);
      text('the social distance',130,160);
      text('and Now we are at the',130,180);
      text('highest risk of COVID 19.',130,200);
      text('Try better!',130,240);
      frameRate(0);
    } 
    //if there is less than 4 people, game clear
    if (humans.length < 4 & frameCount > 200){
      fill(157,209,244);
      rect(250,200,300,250);
      fill(255);
      textSize(20);
      textStyle(BOLD);
      text('Everyone kept the',140,140);
      text('social distance and',140,160);
      text('and we are at',140,180);
      text('lower risk of COVID 19.',140,200);
      text('Good job!',140,240);
      frameRate(0);
    }
}


//humans

//creating Human objects and drawing them
function createHuman(humanx,humany,humandx,humandy){
  var human = {x: humanx, y: humany,
               dx: humandx, dy: humandy,
               display: drawHuman,
               humancolor: color(random(220,255),random(80,100),random(100,200))
              }
  return human;
}

function drawHuman(){
    noStroke();
    fill(this.humancolor);
    push();
    ellipse(this.x,this.y,15,15);
    rect(this.x,this.y+22,16,27);
    rect(this.x-12,this.y+22,4,22);
    rect(this.x+12,this.y+22,4,22);
    rect(this.x-3,this.y+52,5,33);
    rect(this.x+3,this.y+52,5,33);
    pop();
}

//remove humans when their head is clicked
function mousePressed() {
  for (var i = humans.length - 1; i >= 0; i--) {
    if (dist(humans[i].x, humans[i].y, mouseX, mouseY) < 20) {
      humans.splice(i, 1);
    }
  }
}


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

    

Final Project

Welcome to Covid Retreat!

For this project I was inspired to create a virtual escape due to the restrictions Covid-19 has created. I experimented with sound, location design, a game-like set up.

The program includes 4 locations and you can select your location of choice. Clicking on the icon allows you to enter that location, while pressing the back arrow on your keyboard returns you to the game page. Each location has a unique sound that is supposed to be ambient and relaxing so you can meditate to it. The city backdrop has stars that spin with the mouse X location and the village backdrop has snow falling. These act as additional visual aids.

The poppy field has sounds of birds and how a relaxing field would sound. The beach has the calming noise of waves and seagulls in the distance. The city has street noise as well as ambient jazz street music. The village has noise of wind chimes fluttering in the wind. The pictures of the locations act as aids to visualize the location you’re in, and then you can close your eyes and listen to the sounds to meditate.

graanak-15
//This is supposed to be a meditation simulation where you can escape to a location because 
// of the restrictions covid has placed on us. Welcome to Covid Retreat!

var homePage = true;
var gamePage = false;
var locationPoppy = false;
var locationBeach = false;
var locationCity = false;
var locationVillage = false;

//image variables
var gpage;
var poppyImg;
var cityImg;
var beachImg;
var villageImg;

//sounds variables
var field;
var beach;
var seagull;
var city;
var jazz;
var chimes;

//snow variables 
var xpos = [];
var ypos = [];
var dir = [];


function preload() {
	gpage = loadImage("https://i.imgur.com/kWIbADG.png");
	poppyImg = loadImage("https://i.imgur.com/gX6nVv0.png");
	villageImg = loadImage("https://i.imgur.com/ncTFXtG.png");
	cityImg = loadImage("https://i.imgur.com/JzdvYPw.png");
	beachImg = loadImage("https://i.imgur.com/l9hqxRW.png");

	field = loadSound("https://courses.ideate.cmu.edu/15-104/f2020/wp-content/uploads/2020/12/135472__kvgarlic__summeropenfielddusk.wav");
	beach = loadSound("https://courses.ideate.cmu.edu/15-104/f2020/wp-content/uploads/2020/12/320306__sophronsinesounddesign__beach-9.wav");
	seagull = loadSound("https://courses.ideate.cmu.edu/15-104/f2020/wp-content/uploads/2020/12/353416__squashy555__seagull-on-beach.mp3");
	city = loadSound("https://courses.ideate.cmu.edu/15-104/f2020/wp-content/uploads/2020/12/182872__klankbeeld__nightcity-hum-01-130212.mp3");
	jazz = loadSound("https://courses.ideate.cmu.edu/15-104/f2020/wp-content/uploads/2020/12/54066__reinsamba__jazz-street-musicians.wav");
	chimes = loadSound("https://courses.ideate.cmu.edu/15-104/f2020/wp-content/uploads/2020/12/324325__opmartin__wind-chimes.mp3");
}

function soundSetup() { 
	field.rate(0.75);
	field.setVolume(0.5);
	beach.rate(0.5);
	beach.setVolume(0.15);
	seagull.setVolume(0.1);
	city.setVolume(0.5);
	jazz.setVolume(1);
	chimes.setVolume(1.5);
}


function setup() {
    createCanvas(600, 500);
    useSound();
    //snow setup
    for(var i = 0; i < 100; i++){
    	xpos[i] = random(0, width);
    	ypos[i] = random(0, height);
    	dir[i] = random(1, 3);
    }
}

function draw() {

	//drawing the homepage using p5js commands

	if(homePage==true){
		background(230);
		noStroke();

		cube();

		fill(255);
		textSize(15);
		text('Welcome to Covid Retreat. Let us begin your relaxation journey.', 100, 350);

		fill(168, 255, 28, 200);
		rect(209, 420, 188, 58);
		fill(255);
		rect(209, 420, 181, 50);

		fill(0);
		text('Click to start.', 250, 450);

		fill(168, 255, 28);
		circle(mouseX, mouseY, 5);
		fill(168, 255, 28, 200);
		circle(mouseX, mouseY, 15);
		fill(168, 255, 28, 100);
		circle(mouseX, mouseY, 20);

//setting the backdrops for each of the locations
//setting the mouse pointer for the cursor the gamepage

	} else if(gamePage==true){
		image(gpage, 0, 0, 600, 500);
		fill(168, 255, 28);
		circle(mouseX, mouseY, 5);
		fill(168, 255, 28, 200);
		circle(mouseX, mouseY, 15);
		fill(168, 255, 28, 100);
		circle(mouseX, mouseY, 20);
		fill(0);
		textSize(10);
		text('If you would like to return to this page, press the back arrow on your keyboard.', 75, 100);

	} else if(locationPoppy==true){
		image(poppyImg, 0, 0, 600, 500);
		
	}else if(locationCity==true){
		//creating the stars that rotate with mouseX position for the city backdrop
		image(cityImg, 0, 0, 600, 500);
		stars(17, 13);
		stars(32, 36);
		stars(214, 10);
		stars(293, 30);
		stars(306, 16);
		stars(364, 59);
		stars(438, 13);
		stars(569, 21);

	}else if(locationBeach==true){
		image(beachImg, 0, 0, 600, 500);

	}else if(locationVillage==true){
		image(villageImg, 0, 0, 600, 500);
		fill(255);
		noStroke();
		//implementing the snow command
		snowing();
	}

}

//creating the logo on the homepage
function cube(){
	fill(255);
	beginShape();
	vertex(300, 70);
	vertex(390, 145);
	vertex(390, 248);
	vertex(300, 320);
	vertex(209, 248);
	vertex(209, 145);
	endShape();

	fill(220);
	beginShape();
	vertex(300, 70);
	vertex(390, 145);
	vertex(300, 210);
	vertex(209, 145);
	endShape();
}

//causing the screen changes and sound changes for each screen
function mousePressed(){
	var my = mouseY;
	var mx = mouseX;

	if(homePage){
		if(mx > 209 & mx < 390 & my > 420 & my < 470){
			gamePage = true;
			homePage = false;
		}
	}else if(gamePage){
		if(mx > 54 & mx < 139 & my > 132 & my < 217){
			locationPoppy = true;
			gamePage = false;
			field.play();
			field.setLoop(true);
		} else if(mx > 54 & mx < 139 & my > 336 & my < 421){
			locationCity = true;
			gamePage = false;
			city.play();
			city.setLoop(true);
			jazz.play();
			jazz.setLoop(true);
		} else if(mx > 338 & mx < 423 & my > 132 & my < 217){
			locationBeach = true;
			gamePage = false;
			beach.play();
			beach.setLoop(true);
			seagull.play();
			seagull.setLoop(true);
		} else if(mx > 338 & mx < 423 & my > 336 & my < 421){
			locationVillage = true;
			gamePage = false;
			chimes.play();
			chimes.setLoop(true);
		}
	}


}	

//function to return to the gamepage 

function keyPressed(){
	if (keyCode === LEFT_ARROW){
		gamePage = true;
		locationPoppy = false;
		locationBeach = false;
		locationCity = false;
		locationVillage = false;
		field.stop();
		beach.stop();
		seagull.stop();
		city.stop();
		jazz.stop();
		chimes.stop();
	}
}

function snowing(){
	for (var i=0; i < xpos.length; i++){
		circle(xpos[i], ypos[i], 2);
		ypos[i] += dir[i];

		if(ypos[i] >= height){
			ypos[i] = 0;
		}
	}
}

function stars(xp, yp){
	stroke(255, 239, 143);
	push();
	translate(xp, yp);
	rotate(radians(mouseX));
	line(-3, 0, 3, 0);
	line(0, -3, 0, 3);
	pop();
}