Angela Lee – Final Project

Instructions: Please expand your browser window as much as possible since my canvas is at the width of 600! Thank you 🙂
To interact with my speculative comic panel, here are a couple of interactions you can try.
To speed up the subway car, press the “f” key.
To slow down the subway car, press the “s” key. (Note: you cannot make the subway go backwards. If you press this key too many times, it will reset to its original speed.)
To reset the subway car speed, press the “r” key.
In frames 1 & 2, click your mouse around to see some falling stars.

sketch

/*
 * Angela Lee
 * Section E
 * ahl2@andrew.cmu.edu
 * Final Project
 */

// FRAME 1 VARIABLES
var f1height = 270; // height of first frame
var f1width = 275; // width of first frame
var f1x = 22.5; // upper left x-pos of first frame
var f1y = 12.5; // upper left y-pos of first frame
var f1spacing = 9; // spacing to the right of the first frame
var accelerate = 0; // how much the subway car speeds up/slows down
var skyBlue; // top of the sky color
var skyYellow; // bottom of the sky color
var waterYellow; // top of the water color
var waterBlue; // bottom of the water color
var shootingStarArray = []; // array for shooting stars
var ripples = []; // array for the ripples

// FRAME 2 VARIABLES
var f2width = 270; // width of second frame
var f2height = 130; // height of second frame
var f2spacing = 11.5; // spacing below the second frame
var planetx = 400; // x position of the big planet
var subwayStarArray = []; // array for subway stars


function setup(){
	createCanvas (600, 300);
    background(27, 34, 45);

    // gradient for the sky in frame 1
    skyBlue = color(2, 76, 98); // top of the sky color
    skyYellow = color(233, 255, 191); // bottom of the sky color

    // gradient for the water in frame 1
    waterYellow = color(252, 239, 194); // top of the water color
    waterBlue = color(59, 98, 115); // bottom of the water color

    sub = makeSubway(); // creates a subway object in frame 1

    // RIPPLES IN FRAME 1
    // boundaries for ripples in frame 1
    var top = 212; // top boundary
    var bottom = height; // bottom boundary
    // first ripples that come into frame 1
    for (var i = 0; i < 10; i++) {
        var rippleX = random(width);
        var rippleY = random(top, bottom);
        ripples[i] = makeRipples(rippleX, rippleY);
    }
}

function draw(){
    // FRAME 1
    f1scene(); // the sky
    // moving the subway
    sub.move();
    sub.draw();
    // moving the ripples
    for (var i = 0; i < ripples.length; i++) {
        ripples[i].move();
        ripples[i].draw();
    }
    removeRipple();
    addRipple(); 


    // FRAME 2
    f2scene(); // background in frame 2
    // moving the shooting stars
    for (var i = 0; i < shootingStarArray.length; i++) {
        shootingStarArray[i].move();
        shootingStarArray[i].draw();
    }
    removeShootingStar(); // keeps shooting stars that are in frame
    addShootingStar(); // adds shooting stars when mouse is clicked
    f2car(); // subway car features in frame 2
    astroboy(); // astronaut boy in frame 2


    // FRAME 3
    f3scene(); // background in frame 3


    // TEXT
    // words in frame 1
    var f1text = "every night i take the subway home..."
    // words in frame 2
    var f2text = "i wonder..."
    // words in frame 3
    var f3text = "if our worlds will ever collide again."
    noStroke();
    fill("white");
    textSize(14);
    textStyle(ITALIC);
    text(f1text, f1x + 10, 279); // frame 1 text
    text(f2text, 490, 87); // frame 2 text
    text(f3text, f1x + f1width + 25, 279); // frame 3 text


    // BORDERS AROUND THE FRAMES
    noStroke();
    fill(27, 34, 45);
    rect(0, 0, width, f1y); // top border
    rect(0, 0, f1x, height); // border left of frame 1
    rect(f1x + f1width, 0, 17, height); // border right of frame 1
    rect(0, 0, f1x + f1width, f1y); // border above frame 1
    // border below frame 1
    rect(0, f1y + f1height, width, width - f1y - f1height);
    // border below frame 2
    rect(f1x + f1width, f1y + f2height, width - f1x - f1width, f2spacing);
    // border right of frame 2
    rect(f1x + f1width + f1spacing + f2width, 0, 
        width - f1x + f1width + f1spacing + f2width, height);
}

// ---------------- FUNCTIONS & OBJECTS FOR FRAME 1 ------------------

// FRAME 1 BACKGROUND
function f1scene(){
    // GRADIENT SKY
    noFill();
    for (var s = f1y; s < f1y + f1height; s++) {
        var skyInter = map(s, f1y, f1y + f1height, 0, 1);
        var skyStroke = lerpColor(skyBlue, skyYellow, skyInter);
        stroke(skyStroke);
        line(f1x, s, f1x + f1width, s);
    } 
    planets(); // planets in the sky
    surface(); // surface of the moon
    clouds(); // clouds in the sky
    constellations(); // constellations in the sky 

    // TWINKLING STARS
    fill(242, 242, 158);
    for (var st = 0; st < 8; st++) {
        // x positions for the twinkling stars
        starX = [32, 77, 129, 177, 190, 205, 242, 276];
        // y positions for the twinkling stars
        starY = [91, 66, 63, 27, 79, 53, 25, 90];
        // sizes for the twinkling stars
        starSize = [5, 4, 4, 5, 4, 5, 4, 5];
        star(starX[st], starY[st], 
             starSize[st] - random(1, 3), starSize[st], 5);
    }

    // SUBWAY TRACK
    stroke(79, 94, 94);
    fill(130, 142, 134);
    // pillars holding the track
    for (var i = 0; i < 6; i++) {
        rect(34 + i * 48, 148, 14, 67);
    }
    rect(0, 148, f1x + f1width, 13); // bridge above the water

    // GRADIENT WATER
    noFill();
    for (var w = 212; w < f1y + f1height; w++) {
        var waterInter = map(w, 212, f1y + f1height, 0, 1);
        var waterStroke = lerpColor(waterYellow, waterBlue, waterInter);
        stroke(waterStroke);
        line(f1x - 1, w, f1x + f1width, w);
    }
}

// CONSTELLATIONS iN FRAME 1
function constellations() {
    stroke(131, 178, 177); 
    strokeWeight(1);

    // LIBRA CONSTELLATION
    // the following lines join together to create libra
    line(98.5, 90.5, 98.5, 84.5);
    line(98.5, 84.5, 89.5, 70.5);
    line(89.5, 70.5, 111.5, 60.5);
    line(111.5, 60.5, 112.5, 74.5);
    line(89.5, 70.5, 96.5, 56.5);
    line(96.5, 56.5, 111.5, 60.5);

    // LEO CONSTELLATION
    // the following lines join together to create leo
    line(146, 84, 142, 75);
    line(142, 75, 154, 79);
    line(154, 79, 146, 84);
    line(146, 84, 148, 97);
    line(148, 97, 156, 95);
    line(156, 95, 156, 89);
    line(156, 89, 161, 88);
    line(161, 88, 172, 95);
    line(172, 95, 169, 100);

    // AQUARIUS CONSTELLATION
    // the following lines join together to create aquarius
    line(258, 49, 241, 63);
    line(241, 63, 246, 65);
    line(246, 65, 254, 63);
    line(241, 63, 241, 67);
    line(241, 67, 239, 69);
    line(239, 69, 247, 83);
    line(247, 83, 249.5, 77.5);
    line(249.5, 77.5, 258, 76);
    line(258, 76, 268, 82);

}

// CLOUDS IN FRAME 1
function clouds() {
    noStroke();

    // YELLOW CLOUDS
    fill(210, 216, 130, 70);
    var ycloudx = [92, 67, 103, 117, 122, 159]; // x coordinates
    var ycloudy = [92, 88, 53, 56, 52, 70]; // y coordinates
    var ycloudw = [56, 48, 27, 35, 19, 25]; // cloud width
    var ycloudh = [9, 8, 5, 4, 3.5, 3]; // cloud height
    // for loop generates 6 yellow clouds
    for (var y = 0; y < 6; y++) {
        ellipse(ycloudx[y], ycloudy[y], ycloudw[y], ycloudh[y]);
    }

    // PINK CLOUDS
    fill(242, 136, 158, 150);
    var pcloudx = [251, 65, 48, 183, 196, 212]; // x coordinates
    var pcloudy = [109, 58, 55, 43, 46, 41]; // y coordinates
    var pcloudw = [80, 30, 25, 35, 31, 29]; // cloud width
    var pcloudh = [12, 7, 3, 4, 7, 9]; // cloud height
    // for loop generates 6 pink clouds
    for (var p = 0; p < 6; p++) {
        ellipse(pcloudx[p], pcloudy[p], pcloudw[p], pcloudh[p]);
    }
}

// SURFACE OF THE MOON IN FRAME 1
function surface() {
    strokeWeight(1);
    stroke(31, 73, 72);
    fill(38, 104, 119);

    // x positions of the moon surface's shape
    var moonx = [f1x, 61, 107, 152, 202, 248, f1x + f1width, 
                 f1x + f1width, f1x];
    // y positions of the moon surface's shape
    var moony = [187, 185, 191, 180, 188, 190, 195, 237, 237];

    // SURFACE OF THE MOON
    beginShape(); 
    vertex(moonx[0], moony[0]);
    // for loop for curved parts of the shape
    for (var m = 0; m < 7; m++) {
        curveVertex(moonx[m], moony[m]);
    }
    // straight parts of the moon surface shape
    // these points can't be seen because they are
    // blocked by the water
    vertex(moonx[7], moony[7]);
    vertex(moonx[8], moony[8]);
    vertex(moonx[0], moony[0]);
    endShape();

    // MOON CRATERS
    noStroke();
    fill(22, 81, 91);
    ellipse(58, 192, 29, 6);
    ellipse(96, 202, 19, 3);
    ellipse(146, 188, 19, 4);
    ellipse(201, 201, 20, 3);
    ellipse(241, 200, 34, 5);
}

// PLANETS IN THE SKY OF FRAME 1
function planets () {
    // LARGEST PLANET
    strokeWeight(2);
    stroke(30, 76, 114);
    fill(32, 111, 142);
    ellipse(217, 164, 142, 142);

    // SECOND LARGEST PLANET
    strokeWeight(1);
    stroke(24, 76, 119);
    fill(18, 114, 130, 95);
    ellipse(95, 119, 75, 75);

    // RINGED PLANET
    noStroke();
    fill(234, 106, 124);
    ellipse(63.5, 45, 51, 12); // rings
    strokeWeight(0.5);
    stroke(140, 65, 83);
    fill(242, 136, 158);
    ellipse(64, 44, 25, 25); // planet itself

    // CRESCENT MOON
    strokeWeight(1);
    stroke(234, 96, 119);
    fill(242, 242, 158);
    ellipse(144.8, 40, 30, 30); // crescent part
    noStroke();
    fill(31, 94, 104);
    ellipse(143, 37, 24, 24); // shadow part

}

// MAKING THE STARS IN FRAME 1 + 3
// called in f1scene() and f3scene()
function star(x, y, radius1, radius2, npoints) {
    var angle = TWO_PI / npoints;
    var halfAngle = angle / 2.0;
    beginShape();
    for (let a = 0; a < TWO_PI; a += angle) {
        var sx = x + cos(a) * radius2;
        var sy = y + sin(a) * radius2;
        vertex(sx, sy);
        sx = x + cos(a + halfAngle) * radius1;
        sy = y + sin(a + halfAngle) * radius1;
        vertex(sx, sy);
    }
  endShape(CLOSE);
}

// ------------- FUNCTIONS & OBJECTS FOR FRAME 2 -------------------

// FRAME 2 BACKGROUND
function f2scene() {
    // WINDOW BEHIND THE RIDER
    strokeWeight(4);
    stroke(255);
    fill(9, 74, 89);
    rect(302, 29, 291, 85);

    // NIGHTTIME SCENERY
    // stars that come into frame when the subway is "moving"
    noStroke();
    fill(242, 242, 158);
    for (var i = 0; i < subwayStarArray.length; i++) {
        subwayStarArray[i].move();
        subwayStarArray[i].draw();
    }
    removeSubwayStar(); // keeps stars in the keepSubwayStar array
    addSubwayStar(); // based on a small probability, adds new stars

    // big planet
    strokeWeight(2);
    stroke(30, 76, 114);
    fill(32, 111, 142);
    ellipse(planetx, 100, 130, 130); 
    planetx += 0.05 // planet slowly moves right as time passes

    // MORE WINDOW FEATURES
    // glass dividers on the subway windows
    strokeWeight(3);
    stroke(255);
    var divideh = 24; // height of vertical dividers
    var dividex = 335; // initial x position of vertical dividers
    var dividey = 32; // y position of all vertical dividers
    var dividespace = 70; // horizontal spacing in between
    // 4 vertical dividers are created through lines
    for (var d = 0; d < 4; d++) {
        line(dividex + dividespace * d, dividey,
             dividex + dividespace * d, dividey + divideh);
    }
    // horizontal line beneath the vertical dividers
    line(302, 32 + divideh, 302 + f2width, 32 + divideh);

    // window reflections
    noStroke();
    fill(253, 255, 255, 40);
    // first reflection shape
    beginShape();
    vertex(330, 112);
    vertex(326, 95); 
    vertex(381, 31);
    vertex(399, 31);
    vertex(330, 112);
    endShape();
    // second reflection shape
    beginShape();
    vertex(355, 95);
    vertex(409, 31);
    vertex(418, 31);
    vertex(364, 95);
    vertex(355, 95);
    endShape();
    // third reflection shape
    beginShape(); 
    vertex(423, 98);
    vertex(472, 30);
    vertex(509, 30);
    vertex(465, 98);
    vertex(423, 98);
    endShape();
    // fourth reflection shpae
    beginShape();
    vertex(512, 102);
    vertex(555, 31);
    vertex(572, 31);
    vertex(531, 102);
    vertex(512, 102);
    endShape();
}

// FEATURES OF THE SUBWAY CAR IN FRONT OF THE WINDOW
function f2car() {
    // subway car wall above the window
    noStroke();
    fill(143, 169, 183);
    rect(f1x + f1width + 10, 0, f2width, 29);
    // subway car wall below the window
    rect(f1x + f1width + 10, 114, f2width, 40);

    // subway chairs
    // back of the chair that the rider leans against
    stroke(73, 122, 121);
    strokeWeight(1);
    fill(174, 199, 206);
    rect(320, 92, 247, 77, 15); 

    // indents in the chair 
    noStroke();
    fill(153, 181, 188); 
    var indentx = 333; // initial x positions of indents
    var indenty = 103; // y positions of indents
    var indentw = 66; // width of indents
    var indenth = 65; // height of indents
    var indentround = 15; // roundness of indents
    var indentspacing = 79; // spacing between each indent
    // for loop creates 3 evenly spaced indents in the chair 
    for (var c = 0; c < 3; c++) {
        rect(indentx + indentspacing * c, indenty, indentw, 
             indenth, indentround);
    }

    // handles above the window
    noFill();
    strokeWeight(3);
    stroke(200, 214, 219);
    var handlespacing = 82; // horizontal spacing between handles
    // for loop creates 3 evenly spaced handles
    for (var h = 0; h < 3; h++) {
        ellipse(360 + handlespacing * h, 20, 10, 40);
    }
}

// ASTRONAUT BOY SITTING IN THE SUBWAY
function astroboy() {
    // arms
    noStroke();
    fill(160, 116, 87);
    rect(397, 137, 13, 15); // right arm

    // shirt
    fill(194, 160, 224);
    ellipse(379, 118.5, 66, 19); // shoulders
    rect(356, 117, 47, 35); // body
    rect(346, 119, 15, 19); // left sleeve
    ellipse(354, 137.5, 16, 5); // bottom of left sleeve
    rect(400, 119, 12, 19); // right sleeve
    ellipse(404, 137.5, 16, 5); // bottom of right sleeve
    // pocket on shirt
    fill(234, 132, 132); 
    rect(385, 130, 11, 13); 
    ellipse(390, 143, 11, 3);

    // right arm
    noStroke();
    fill(160, 116, 87);
    quad(346, 138, 360, 138, 366, 150, 352, 150); // left arm

    // astronaut helmet
    // neck piece
    noStroke();
    fill(100);
    ellipse(377, 111, 27, 6);
    rect(364, 100, 27, 11);
    // helmet
    strokeWeight(1);
    stroke(184, 209, 208);
    fill("white");
    ellipse(377, 83, 48, 48);
    // glass piece
    noStroke();
    fill(33, 51, 68);
    ellipse(376, 95.5, 34, 11);
    quad(357, 85, 395, 85, 393, 96, 359, 96);
    ellipse(376, 85, 38, 11);
    // highlights on glass piece
    fill(100, 107, 109);
    ellipse(371, 84, 22, 2); // top highlight
    ellipse(376, 98, 18, 1); // bottom highlight
}

// ------------------ FUNCTIONS/OBJECTS IN FRAME 3 ---------------------

// FRAME 3 BACKGROUND
function f3scene() {
    // the dark night sky
    noStroke();
    fill(18, 38, 76);
    rect(307, 12 + f2height + f2spacing, f2width, f2height); 

    // clouds
    // darkest clouds
    fill(39, 49, 117, 170);
    ellipse(382, 193, 69, 48);
    ellipse(534, 201, 95, 47);
    // purple clouds
    fill(118, 86, 153, 120);
    ellipse(348, 230, 107, 71);
    ellipse(445, 220, 142, 77);
    ellipse(531, 225, 98, 52);
    // darker pink clouds
    fill(219, 127, 154, 130);
    ellipse(374, 247, 76, 50);
    ellipse(520, 251, 135, 50);

    // glow behind the star in figure's hand
    noStroke();
    var glowx = 491; // x position of the glow center
    var glowy = 216; // y position of the glow center 
    // controls how the glowing portion of the star 
    // grows and shrinks, so it looks like it's flickering
    var osc = 10 + sin(millis()) * 1;
    // outer glow
    fill(239, 125, 172, 100);
    ellipse(glowx, glowy, 20 + osc, 20 + osc);
    // middle glow
    fill(249, 155, 189, 120);
    ellipse(glowx, glowy, 10 + osc, 10 + osc);
    // inner glow
    fill(237, 102, 138);
    ellipse(glowx, glowy, 4 + osc, 4 + osc);

    starfigure(); // mysterious figure in the clouds

    // light pink clouds
    noStroke();
    fill(242, 165, 165, 200);
    ellipse(337, 273, 76, 50);
    ellipse(535, 276, 94, 50);
    fill(242, 165, 165); // cloud covering the figure is opaque
    ellipse(430, 278, 135, 50);


    // twinkling stars
    fill(242, 242, 158);
    for (var st = 0; st < 5; st++) {
        // x positions for the twinkling stars
        starX = [366, 413, 491, 493, 514];
        // y positions for the twinkling stars
        starY = [240, 213, 216, 245, 178];
        // sizes for the twinkling stars
        starSize = [8, 9, 6, 8, 6];
        star(starX[st], starY[st], 
             starSize[st] - random(4, 5), starSize[st], 5);
    }
}

function starfigure() {
    // body
    fill(224, 184, 140);
    ellipse(466, 195.5, 2, 5); // right ear
    ellipse(443, 194.5, 2, 5); // left ear
    rect(450, 203, 8, 9); // neck
    quad(470, 235, 485, 219, 490, 220, 475, 240); // arm
    ellipse(488.5, 219.5, 7, 3); // palm
    //forefinger
    beginShape();
    vertex(491, 220);
    vertex(491, 218);
    vertex(494.2, 217.6);
    vertex(494.5, 218.5);
    vertex(492, 220);
    vertex(491, 220);
    endShape();
    ellipse(494.5, 216.5, 1, 4);// upper finger

    //hair
    fill(248, 252, 195);
    ellipse(454.5, 194, 23, 24);
    hairx = [443, 466, 464, 463, 462, 452, 450, 449, 445, 445, 
             444, 441, 443, 443]; // x positions of hair coordinates
    hairy = [194, 194, 208, 205, 208, 208, 206, 208, 207, 205, 
             207, 207, 199, 194]; // y positions of hair coordinates
    beginShape();
    for (var h = 0; h < 14; h++) {
        vertex(hairx[h], hairy[h]);
    }
    endShape();

    // cape
    fill(30, 30, 124);
    ellipse(454, 217, 35, 13);
    quad(426, 266, 437, 218, 472, 218, 482, 266);
    // folds in the cape
    fill(16, 16, 94);
    triangle(438, 259, 443, 221, 443, 259);
    triangle(467, 260, 465, 222, 472, 260);
    // constellations of the cape
    strokeWeight(1);
    stroke(113, 108, 183);
    // first constellation
    line(443, 216, 448, 224);
    line(448, 224, 452, 225);
    line(452, 225, 445, 235);
    // second constellation
    line(455, 236, 453, 244);
    line(453, 244, 459, 246);
    line(459, 246, 461, 242);
    line(461, 242, 465, 250);
    // constellation on the edge of the cape
    line(432, 243, 434, 245);
    line(434, 245, 429, 251);
}

// ------------- FUNCTIONS & OBJECTS FOR THE MOVING SUBWAY IN FRAME 1 -------------

//  PRESSING KEYS TO CONTROL SUBWAY CAR SPEED
function keyPressed() {
    // everytime the "f" key is pressed, the subway car's 
    // speed will increase by 1
    if (key == "f") {
        accelerate += 1;
    }

    // likewise, everytime "s" is pressed, the subway car's
    // speed will reduce by 1
    if (key == "s") {
        accelerate -= 1; 
    }

    // upon pressing the r key, it resets the accelerate to 0
    // helpful in case you make the train way too fast and don't
    // want to click s multiple times to get back to the original speed
    if (key === "r") {
        accelerate = 0; 
    }
}

// SUBWAY OBJECT
function makeSubway() {
    var subwaycar = {x: -200,
                     y: 119,
                     speed: 4,
                     move: moveSubway,
                     draw: drawSubway}
    return subwaycar; 
}

function drawSubway() {
    // subway cars
    var sublength = 75; // length of subway car
    var subheight = 28; // height of subway car
    var subx = -200; // x position of subway car
    var suby = 119; // y position of subway car
    var round = 7.5; // rounded edges of rect

    // subway car windows
    var windowY = 125; // y position of subway window
    var windowW = sublength / 4 // length of subway window
    var windowH = 10; // height of subway window
    var wspace = 4.5; // spacing between subway windows

    // creating the subway car
    for (var s = 0; s < 5; s++) {
        stroke(125, 121, 132);
        fill(230, 230, 252); 
        rect(this.x + sublength * s, suby, sublength, subheight, round);
        for (var i = 0; i < 3; i++) {
            fill(21, 68, 76);
            rect(this.x + wspace * (i + 1) + windowW * i + sublength * s, 
                windowY, windowW, windowH);
        }
    }
}

function moveSubway() {
    this.x += (this.speed + accelerate); 
    // to prevent the subway car from moving backwards, 
    // if accelerate becomes too big that its absolute value
    // is greater than the speed, it resets to 0, which means
    // the subway train will pause right before and upon pressing
    // the s key again, it will reset to the original speed
    if (accelerate < this.speed * -1) {
        accelerate = 0;  
    }
    // reset the x position of the last subway car to -500
    // if the last subway car exits the frame
    if (this.x > f1x + f1width) {
        this.x = -500; 
    }
}

// ----------- FUNCTIONS & OBJECTS FOR FALLING STARS IN FRAME 1 & 2 ----------

// FALLING STARS IN FRAME 1 & 2 WHEN MOUSE IS PRESSED
function mousePressed() {
    // when mouse is pressed, the function will 
    // to add a shooting star will be called with 
    // the x and y inputs of the mouse position
    if (mouseX < width && mouseX > f1x &&
        mouseY < f1y + f1height && mouseY > f1y) {
        addShootingStar(mouseX, mouseY);
    }
}

// SHOOTING STAR OBJECT 
function makeShootingStar(xPos, yPos) {
    var shootingStar = {x: xPos,
                        y: 0, 
                        size: random(7, 20),
                        speed: random(3, 8),
                        move: moveShootingStar,
                        draw: drawShootingStar}
    return shootingStar; 
}

// DRAWING THE SHOOTING STAR
function drawShootingStar() {
    // controls how the glowing portion of the star 
    // grows and shrinks, so it looks like it's flickering
    var osc = 10 + sin(millis()) * 1;

    noStroke();

    // glowing part of the star
    fill(244, 242, 168, 75);
    ellipse(this.x, this.y, this.size + osc, this.size + osc);

    // inner part of the star
    fill(239, 235, 91);
    ellipse(this.x, this.y, this.size, this.size);
    
}

// MOVING THE SHOOTING STAR
function moveShootingStar() {
    this.y += this.speed; 
}

// ADDING SHOOTING STARS
function addShootingStar(xPos, yPos) {
    shootingStarArray.push(makeShootingStar(xPos, yPos));
}

// REMOVING SHOOTING STARS 
function removeShootingStar() {
    // array for keeping the stars
    var keepStar = [];
    // as long as the shooting stars are in bound of frame 1
    // they will be pushed into the array for keeping the stars
    for (var i = 0; i < shootingStarArray.length; i++) {
        if (shootingStarArray[i].y < f1y + f1height + 
            shootingStarArray[i].size) {
            keepStar.push(shootingStarArray[i]);
        }
    }
    shootingStarArray = keepStar;  
}

// ------------- FUNCTIONS & OBJECTS FOR THE RIPPLES IN FRAME 1 --------------

// RIPPLE OBJECT
function makeRipples(xPos, yPos) {
    var makeRipple = {x: xPos,
                      y: yPos,
                      // longer ripples are in the front, shorter ones in the back
                      length: map(yPos, 212, height, 5, 75),
                      // thinner ripples in the back, thicker ones in the front
                      weight: map(yPos, 212, height, 1, 4),
                      // faster ripples in the front, slower ripples in the back
                      speed: map(yPos, 212, height, 0.5, 1),
                      move: moveRipple,
                      draw: drawRipple}
                    return makeRipple; 
}

// MOVING THE RIPPLE
function moveRipple() {
    // x position changes by speed
    this.x += this.speed; 
    // if the ripple leaves the frame, reset x position
    // to the left side of the frame
    if (this.x > width + this.length) {
        this.x === -this.length;
    }
}

// ADDING RIPPLES
// using a tiny probability, add ripples
function addRipple() {
    if (random(0, 1) < 0.025) {
        ripples.push(makeRipples(-75, random(212, height)));
    }
}

// REMOVING RIPPLES
function removeRipple() {
    // an array for ripples to keep
    var keepRipples = [];
    // as long as ripples are within the bounds of frame 1, 
    // keep them in the keepRipples array
    for (var i = 0; i < ripples.length; i++) {
        if (ripples[i].x < f1x + f1width) {
            keepRipples.push(ripples[i]);
        }
    }
    ripples = keepRipples;
}

// DRAWING THE RIPPLE
function drawRipple() {
    strokeWeight(this.weight);
    stroke(255, 255, 255, 75);
    line(this.x, this.y, this.x + this.length, this.y);
}

// ------------- FUNCTIONS & OBJECTS FOR THE STARS IN FRAME 2 -------------

// SUBWAY STAR OBJECT
function makeStar(xPos, yPos) {
    var subwayStar = {x: xPos,
                      y: yPos,
                      radius1: random(5, 10),
                      npoints: 5,
                      speed: random(0.5, 2),
                      move: moveSubwayStar,
                      draw: drawSubwayStar}
                    return subwayStar; 
}

// MOVING THE SUBWAY STARS
function moveSubwayStar() {
    // x position changes by speed
    this.x += this.speed; 
    // if the star leaves the frame, reset x position
    // to the left side of the frame
    if (this.x > width) {
        this.x === f1x + f1width + 10;
    }
}

// ADDING SUBWAY STARS
// using a tiny probability, add subway stars
function addSubwayStar() {
    if (random(0, 1) < 0.02) {
        subwayStarArray.push(makeStar(f1x + f1width + 10, 
                             random(30, 100)));
    }
}

// REMOVING SUBWAY STARS
function removeSubwayStar() {
    // an array for subway stars to keep
    var keepSubwayStar = [];
    // as long as the stars are within the width, 
    // keep them in a separate array
    for (var i = 0; i < subwayStarArray.length; i++) {
        if (subwayStarArray[i].x < width) {
            keepSubwayStar.push(subwayStarArray[i]);
        }
    }
    subwayStar = keepSubwayStar;
}

// DRAWING THE SUBWAY STARS
function drawSubwayStar() {
    fill(242, 242, 158);
    var angle = TWO_PI / this.npoints;
    var halfAngle = angle / 2.0;
    beginShape();
    for (let a = 0; a < TWO_PI; a += angle) {
        var sx = this.x + cos(a) * (this.radius1 - 5);
        var sy = this.y + sin(a) * (this.radius1 - 5);
        vertex(sx, sy);
        sx = this.x + cos(a + halfAngle) * this.radius1;
        sy = this.y + sin(a + halfAngle) * this.radius1;
        vertex(sx, sy);
    }
  endShape(CLOSE);
}



For my final project, I created a speculative cartoon panel. It’s speculative in the sense that the it forces the viewer to use their imagination to weave a story of what might have happened–the possibilities are endless. In this project, I enjoyed exploring my interests in illustration, concept art, and storytelling. To convey this dreamy, mysterious, magical atmosphere, I worked within a range of cool/teal tones, with pink and yellow highlights.

To begin, I created a moodboard of images/animations on Pinterest. Then, I sketched out/digitized my idea using simple shapes on Illustrator. Not only did I enjoy creating the static images, I think programming the interactions made the comic scene much more fun and engaging.

Moodboarding: I drew a lot of inspiration from space/water concept art. What I love about concept art is its ability to inspire your imagination as you create stories in your head about the piece.
My sketch on Illustrator, which helped save time when visually styling the scene.

Lanna Lang – Final Project

lannal-project-12

Download the zip file and run the index HTML. I could not upload my sketch onto WordPress because my canvas size is 1000 x 1000. Read below to see how to access my project.

Instructions on accessing the project: To view this project, you must download the zip file attached above. Once the file is downloaded, a zip file called “lannal-project-12” should appear. Open the folder and then open “sketch.js” to access the code behind it.

Because this is outside of WordPress and my code has implemented sound, you have to create a local server to have the sound file playback to successfully view the project, so follow the instructions below (taken from Lab Week 10).

  1. Open a Terminal in OS X or a command window (cmd) in Windows.
  2. Change your current directory to the directory you want to serve: Type cd path-to-your-directory (ex. cd Desktop/104final )
  3. Type in Terminal:
    python -m SimpleHTTPServerOr if you are using Python 3, type:
    python -m http.server
  4. Visit the URL http://localhost:8000 in your browser to test your sketch.

My project is a rendition of Patatap, but with global warming and the climate crisis as the concept.

How it works:

Press the enter key to change between color schemes – there are 5 different schemes which go from cool colors to warm colors – symbolizing global warming and rising temperatures.

Press any arrow key to play corresponding graphics and sounds:

The up arrow key = the decrease and destruction of coral reefs

The down arrow key = the transformation of ocean bubbles to pollution smoke

The left arrow key = the exponential increase in the consumption of plastic water bottles and the amount of plastic in the oceans and our waste.

The right arrow key = the rising ocean water level due to the climate crisis.

In my body of work as an artist, I explore many concepts, but environmental awareness is one of the concepts I revisit pretty often. I was very intrigued by the Patatap project when we were introduced to it in a lecture, and I wanted to create my own version of it, combining with a discussion of the climate crisis. Coding this project in these past 2 weeks was a good challenge for me as it combined all the topics we learned throughout the semester: arrays, objects, for loops, if statements, images, sounds, noise, etc. I really want to expand on this project later and continue working on this, such as making it its own website, maybe as another project for another class.

As global warming progresses there is more consumption of plastic water bottles and more plastic in the ocean (depicted as the increase in bottles form a wave).

As global warming progresses, these ocean bubbles transform into pollution smoke from industrial technology.

As global warming progresses, the coral reefs in the oceans are disappearing and their numbers are decreasing.

As global warming progresses, the water level of the oceans are rising.

A screen recording of me interacting with the sketch. There was supposed to be sound but I think something went wrong with the screen recording, so this is just to show how the interaction works.

Nawon Choi— Final Project

INSTRUCTIONS: Blocks will begin to appear on the screen and approach you! When blocks turn yellow, type the letter displayed on the block to earn a point. You have 5 lives.

sketch

// Nawon Choi
// Section C
// nawonc@andrew.cmu.edu
// Final Project

var numOfStars = 700;
var starSize = 1.2;
var sx = [];
var sy = [];
var sz = [];

var newBlockChance = 100;
var blocks = [];
var blockSize = 50;
var speed = 5;
var lastBlk = 0;
var alphabet = ["a", "b", "c", "d", "e", "f", "g",
                 "h", "i", "j", "k", "l", "m", "n", 
                 "o", "p", "q", "r", "s", "t", "u", 
                 "v", "w", "x", "y", "z"];
var txt;
var ripeBlocks = [];

var level = 1;
var points = 0;
var showPoints;
var lives = 5;
var heart;
var gameOver;
var resetTxt;
var yourScore;
var lvl;

function preload() {
    heart = loadImage("https://i.imgur.com/YvJC0vj.png");
}

function setup() {
    createCanvas(480, 500, WEBGL);
        
    // generate a bunch of randomly placed stars
    for (var i = 0; i < numOfStars; i++) {
        sx.push(random(-width, width));
        sy.push(random(-height, height));
        sz.push(random(-height, height));
    }

    // used for drawing letter on block
    txt = createGraphics(200, 200);
    txt.textSize(75);

    // show points/level at the corner
    showPoints = createGraphics(width, 100);
    showPoints.textSize(60);
    showPoints.fill(255);
    showPoints.textStyle(BOLD);
    lvl = createGraphics(width, 100);
    lvl.textSize(50);
    lvl.fill(255);

    // game over screen text
    gameOver = createGraphics(width + 150, 100);
    gameOver.textSize(100);
    gameOver.fill(255);
    gameOver.textStyle(BOLD);
    resetTxt = createGraphics(width + 150, 100);
    resetTxt.textSize(40);
    resetTxt.fill(255);
    yourScore = createGraphics(width + 150, 100);
    yourScore.textSize(40);
    yourScore.fill(255);


}

function draw() {
    background("#1c2736");
    drawStars();

    if (lives > 0) {
        // draw hearts to represent lives
        for (var i = 0; i < lives; i++) {
            push();
            translate(width / 2 - (30 * (i + 1)), -height / 2 + 25);
            texture(heart);
            plane(30, 30);
            pop();
        }

        // show points/level at the top corner
        push();
        showPoints.background("#1c2736");
        showPoints.text("SCORE = " + points, 10, 60);
        texture(showPoints);
        translate(-width / 2 + 85, -height / 2 + 25);
        plane(150, 30);

        lvl.background("#1c2736");
        lvl.text("LEVEL = " + level, 10, 60);
        texture(lvl);
        translate(0, 25);
        plane(150, 30);
        pop();


        // long road
        push();
        fill("#104670");
        translate(0, 30, -600);
        rotateX(blockSize);
        box(width, 10, 8000);
        pop();

        // highlight zone
        push();
        fill("#497291");
        rotateX(blockSize);
        translate(0, 150);
        box(400, 10, 330); 
        pop();

        // increase level based on points
        var determineLvl = floor(points / 10) + 1;
        if (determineLvl > 3) {
            // only up to 3 levels
            level = 3;
        } else {
            level = determineLvl;
        }

        if (level < 3) {
            // make sure blocks don't overlap
            if (frameCount > (lastBlk + 30)) {
                var newBlock = floor(random(newBlockChance));
                if (newBlock == 1) {
                    lastBlk = frameCount;
                    blocks.push(makeNewBlock());
                }
            }
        } else {
            // blocks can overlap
            var newBlock = floor(random(newBlockChance));
            if (newBlock == 1) {
                lastBlk = frameCount;
                blocks.push(makeNewBlock());
            }
        }

        // draw blocks
        for (var i = 0; i < blocks.length; i++) {
            if (blocks[i].z < 330) {
                blocks[i].draw();
                if (blocks[i].ripe()) {
                    ripeBlocks.push(blocks[i]);
                }
            } else {
                // remove blocks that have gone off the page
                if (blocks[i].scored == 0) {
                    lives--;
                }
                blocks.shift();
            }
        }
    } else {
        // game over screen
        push();
        translate(0, -25);
        gameOver.text("GAME OVER", 0, 80);
        texture(gameOver);
        plane(300, 50);

        resetTxt.text("Press 'R' to restart", 0, 100);
        translate(70, 40);
        texture(resetTxt);
        plane(300, 50);

        yourScore.text("Your score = " + points, 0, 100);
        translate(0, 50);
        texture(yourScore);
        plane(300, 50);
        pop();
    }
}

function resetGame() {
    lives = 5;
    points = 0;
    level = 1;
    blocks = [];
    newBlockChance = 100;
}

function levelUp () {
    // increase difficulty
    newBlockChance -= 5;
}

function blockIsScored() {
    this.scored = 1;
    ripeBlocks.shift();
}

function blockIsRipe() {
    // if block is on the highlighted zone
    if (this.z == 70) {
        return true;
    }
    return false;
}

function drawBlock() {
    push();
    // block changes color based on nearness/if scored
    if (this.scored == 0) {
        if (this.z >= 70) {
            txt.background("yellow");
        } else {
            txt.background("red");
        }
    } else {
        txt.background("green");
    }
        
    // displays letter on the block
    var upper = this.letter.toUpperCase();
    txt.text(upper, 80, 120);
    stroke("#104670");
    texture(txt);
    rotateX(blockSize);
    translate(this.x, this.y, this.z);
    box(blockSize);
    pop();

    // move forward
    this.z += speed;
}

function makeNewBlock() {
    var block = {
        x: floor(random(-100, 100)),
        y: floor(random(-150, 25)),
        z: -2000,
        letter: alphabet[floor(random(26))],
        ripe: blockIsRipe,
        score: blockIsScored,
        scored: 0,
        draw: drawBlock
    }
    return block;
}

function drawStars() {
    fill(200);
    noStroke();
    for (var i = 0; i < numOfStars; i++) {
        push();
        translate(sx[i], sy[i], sz[i]);
        sphere(starSize);
        pop();
    }
}

function keyTyped() {
    if (lives == 0) {
        // reset game
        if (key == "r") {
            resetGame();
        }
    } else {
        // check if a block was pressed correctly/timely
        for (var i = 0; i < ripeBlocks.length; i++) {
            if (key == ripeBlocks[i].letter) {
                points++;
                ripeBlocks[i].score();
                if (points % 10 == 0) {
                    levelUp();
                }   
            }
        }
    }   
}

For my final project I created a 3D typing game. I tried to recreate Blade Saber, a similar VR game where players slash through the blocks using a VR “saber”. For a long time, I tried to have the player score points by dragging their mouse through the block, but due to complexities of 3D in p5js (translations, rotations, etc) it was way out-of-scope for this project. I decided to simplify this to be a keyboard-based game instead. Enjoy!

Nawon Choi— Project Proposal

For my project, I want to create a game inspired by a VR game called Blade Saber. This game will use p5.js’s WEBGL library and have 3D blocks that can be slashed using the player’s mouse.

Blocks will start at a far distance from the player and slowly get closer and closer. The player must slash the blocks before they reach the player. As the game advances, more blocks will come at an increasingly faster rate. Moreover, each block will have an arrow indicating which direction the player must “slash” in order to get the point. If they do not slash in the right direction, they will lose a life.

Joanne Chui – Final Project

TILES

sketch

// Joanne Chui
// Section C
// Final Project

var gridLength = 400;

var myLBlue = 0
var myBlue = 0
var myOrange = 0

var tileCount = 0; //amount of columns and rows
var crtPattern = 0; //amount of tiles highlighted
var counter = 0;
var roundNum = 1;

let ready = false;
let wrongAnswer = false;

myGrid = [];
userGrid = [];

function setup(){
  createCanvas(550, 405);
  background(80, 20, 10);
  textSize(32);
  fill('white');
  text("T I L E S", 415, 40);
  textSize(10);
  text("MEMORIZE THE PATTERN", 415, 70);
  text("PRESS R WHEN READY", 420, 90);
  text("CLICK TO CHANGE TILE", 418, 110);
  textSize(20);
  text("ROUND", 440, 180);

  tileCount = int(random(3, 6));
  crtPattern = int(.4 * (tileCount * tileCount));

  myLBlue = color(25, 120, 145);
  myBlue = color(15, 60, 100);
  myOrange = color(200, 80, 50);

  stroke(90, 100, 90);
  for(i = 0; i < tileCount; i++){
    myGrid.push([]);
    userGrid.push([]);
    for(j = 0; j < tileCount; j++){
      myGrid[i].push(myBlue);
      userGrid[i].push(myBlue);
    }
  }
}

function draw(){
  strokeWeight(5);
  rect(440, 185, 70, 70);
  fill('white');
  noStroke();
  textSize(50);
  if(roundNum < 10){
    text(roundNum, 462, 237);
  }else{
    text(roundNum, 447, 237);
  }

  if(ready){
    drawGrid();
  }else{
    drawPatternGrid();
  }

  //GAME OVER
  if(wrongAnswer){
    //have solution pop up
    drawPatternGrid();
    //game over text
    noStroke();
    fill(80, 20, 10)
    rect(0, 170, 410, 60);
    fill("white");
    textSize(34);
    text("GAME OVER", 115, 210);
  }

  //border
  fill(myBlue);
  strokeWeight(10);
  line(4, 4, gridLength, 4);
  line(gridLength, 4, gridLength, gridLength);
  line(gridLength, gridLength, 4, gridLength);
  line(4, gridLength, 4, 4);

}


function drawPatternGrid(){
  stroke(10, 35, 60);
  strokeWeight(7);
  for(i = 0; i < tileCount; i++){
    for(j = 0; j < tileCount; j++){
      patternGenerator();
      fill(myGrid[i][j])
      rect(i * (gridLength/tileCount), j * (gridLength/tileCount), gridLength/tileCount, gridLength/tileCount);
    }
  }

}

//creates the solution pattern of tiles
var keyI = 0;
var keyJ = 0;
var x = 0;
function patternGenerator(){
  while(x < crtPattern){
    keyI = int(random(tileCount));
    keyJ = int(random(tileCount));
    if(myGrid[keyI][keyJ] != myOrange){
      myGrid[keyI][keyJ] = myOrange;
      x += 1;
    }
  }
}

//base grid for guessing
function drawGrid(){
  stroke(10, 35, 60);
  strokeWeight(7);
  for(i = 0; i < tileCount; i++){
    for(j = 0; j < tileCount; j++){
      mouseHover();
      fill(userGrid[i][j]);
      rect(i * (gridLength/tileCount), j * (gridLength/tileCount), gridLength/tileCount, gridLength/tileCount);
    }
  }
}

//highlights tiles if mouse hovers over them
function mouseHover(){
  if(mouseX < (i+1) * gridLength/tileCount & 
     mouseX > i * gridLength/tileCount && 
     mouseY < (j+1) * gridLength/tileCount &&
     mouseY > j * gridLength/tileCount){
    if(userGrid[i][j] != myOrange){
      userGrid[i][j] = myLBlue;
    }
 
  }else{
    if(userGrid[i][j] != myOrange){
      userGrid[i][j] = myBlue;
    }
  }
}


//selecting tiles
function mousePressed(){
 for(i = 0; i < tileCount; i++){
    for(j = 0; j < tileCount; j++){
      if(mouseX < (i+1) * gridLength/tileCount & 
       mouseX > i * gridLength/tileCount && 
       mouseY < (j+1) * gridLength/tileCount &&
       mouseY > j * gridLength/tileCount){
        if(counter < crtPattern - 1){
          if(userGrid[i][j] != myOrange){
            if(myGrid[i][j] == myOrange){
              userGrid[i][j] = myOrange;
              counter += 1;
              print(counter);
              print(crtPattern);
              }else{
              wrongAnswer = true;
              }
            }
          }else{
            ready = false;
            myGrid = [];
            userGrid = [];
            roundNum += 1;
            for(i = 0; i < tileCount; i++){
              myGrid.push([]);
              userGrid.push([]);
              for(j = 0; j < tileCount; j++){
                myGrid[i].push(myBlue);
                userGrid[i].push(myBlue);
              }
            }
            counter = 0;
            x = 0;
        }
      }
    }
  }
}

//switch grid
function keyTyped(){
  if(key === "r" || key === "R"){
    ready = true;
    }else{
      ready = false;
  }
}











This is a memorization game to test a user’s spatial recall ability. A pattern of tiles is first shown, and once the user has committed the pattern to memory, pressing the “r” key will render the grid blank, and the user must recreate the pattern by clicking on the tiles. If the pattern matches the original pattern, a new pattern is generated. Once the user clicks an incorrect tile, the game is over. The division of the grid is randomized.

Aaron Lee – Final Project

sketch

/*
Aaron Lee
Section C
sangwon2@andrew.cmu.edu
Final Project
*/

var canvaswidth = 640;
var canvasheight = 480;

var myCaptureDevice;
var brightnessThreshold = 50;
var darknessThreshold = 45;
var theBrightnessOfTheColorAtPxPy = 100;
var px = 30;
var py = 30;

var gamelogo;
var spacefighter;
var invader = [];
var missile = [];
var score = 0;

var img_Invader, img_spacefighter, img_Missile,img_background;


function setup() {//creating camera for the background
   createCanvas(canvaswidth, canvasheight);
   myCaptureDevice = createCapture(VIDEO);
   myCaptureDevice.size(canvaswidth, canvasheight); // attempt to size the camera. 
   myCaptureDevice.hide(); // this hides an unnecessary extra view.
   
}

function preload(){ //preloading the reference images
    img_Invader = loadImage("https://i.imgur.com/hBZOUxR.png");
    img_spacefighter = loadImage("https://i.imgur.com/wJPVqdo.png");
    img_Missile = loadImage("https://i.imgur.com/0O0UGN4.png");
    gamelogo = loadImage("https://i.imgur.com/hBZOUxR.png");
}

spacefighter = {
    x : 140,
    width : 30,
    y : 400,
    height: 30,
    draw : function() {
        image(img_spacefighter, this.x, this.y, this.width, this.height);
    }
}

//--------------------------------------//invader
function Invader(I) {
    I.active = true;
    I.x =random(640);
    I.y = 0;
    I.width = 50;
    I.height = 50;
    I.speed = 2;
    I.inBounds = function(){
        return I.x >= 0 & I.y >= 0 && I.x < canvaswidth - I.width && I.y < canvasheight - I.height;
    }
    I.draw = function(){
        image(img_Invader, I.x, I.y, I.width, I.height);
    }
    I.update = function(){
        I.active = I.active & I.inBounds();
        I.y += I.speed;
    }
    return I;

    function trlUpdate() { //this is the part where the code unfortunately doesn't work
   //fetch the color of the pixel at the (px,py)
   var theColorAtPxPy = myCaptureDevice.get(this.px, this.py);
   //compute its birghtness
   theBrightnessOfTheColorAtPxPy = brightness(theColorAtPxPy);
   //if the textrainletter is in a bright area, move downwards
   if (theBrightnessOfTheColorAtPxPy > brightnessThreshold) {
   this.py++;
   }
   //else if it's in a dark area, move up until we're in a light area
   while (theBrightnessOfTheColorAtPxPy < darknessThreshold & py > 0) {
     theColorAtPxPy = myCaptureDevice.get(px, py)
     theBrightnessOfTheColorAtPxPy = brightness(theColorAtPxPy);
     this.py--;  
   }
   //reset to initial place if falls off the screen
   if (this.py >= height) {
   this.py = 0;
   }
}
}

//--------------------------------------//missile
function Missile(I){
    I.active = true;
    I.x = spacefighter.x;//because both the missile and spacefighter share same origin
    I.y = spacefighter.y;
    I.width = 30;
    I.height = 30;
    I.speed = 10;
    I.inBounds = function(){
        return I.x >= 0 & I.y >= 0 && I.x < canvaswidth - I.width && I.y < canvasheight -  I.height;
    }
    I.update = function(){
        I.active  = I.active & I.inBounds();
        I.y -= I.speed;
    }
    I.draw = function(){
        image(img_Missile, I.x, I.y, I.width, I.height);
    }
    return I;
}


//--------------------------------------//shooting
function shooting(Invader, Missile){
    return Missile.x + Missile.width >= Invader.x & Missile.x < Invader.x + Invader.width &&
            Missile.y + Missile.height >= Invader.y && Missile.y < Invader.y + Invader.height;
}

function draw(){
    clear();
    myCaptureDevice.loadPixels();
   image(myCaptureDevice, 0,0);
   image(gamelogo, 30, 30);
    fill("red");
    textStyle(BOLD);
    textFont('Sprint2');
    textSize(20);
    text("HIGH SCORE : " + score, canvaswidth / 2 - 80, 20);
    if(keyIsDown(LEFT_ARROW)){              //left key configuration
        if(spacefighter.x - 3 >= 0)
            spacefighter.x -= 3;
        else
            spacefighter.x = 0;
    }
    if(keyIsDown(RIGHT_ARROW)){              //right key configuration
        if(spacefighter.x + 3 <= canvaswidth-spacefighter.width)
            spacefighter.x += 3;
        else
            spacefighter.x = canvaswidth - spacefighter.width;
    }
    if(keyIsDown(UP_ARROW)){              //up key configuration
        if(spacefighter.y - 5 >= 0)
            spacefighter.y -= 5;
        else
            spacefighter.y = 0;
    }
    if(keyIsDown(DOWN_ARROW)){              //down key configuration
        if(spacefighter.y + 5 <= canvasheight-spacefighter.height)
            spacefighter.y += 5;
        else
            spacefighter.y = canvasheight - spacefighter.height;
    }
    if(keyIsDown(32)){
        missile.push(Missile({}));
    }
    spacefighter.draw();              //draw spacefighter
    missile.forEach(function(Missile){//draw and update the missiles
        Missile.update();
        Missile.draw();
    });

    if(Math.random()<0.07){
        invader.push(Invader({}));
    }
    invader = invader.filter(function(Invader){
        return Invader.active;
    });
    invader.forEach(function(Invader){
        Invader.update();
        Invader.draw();
    });

    missile.forEach(function(Missile){
        invader.forEach(function(Invader){
            if(shooting(Invader, Missile)){
                Invader.active = false;
                Missile.active = false;
                score++;
            }
        });
    });

    invader.forEach(function(Invader){
        if(shooting(Invader, spacefighter)){
            Invader.active = false;
            noLoop();
            textStyle(BOLD);
            textFont('Sprint2');
            textSize(40);
            fill("red");
            text("continue? press f5", width / 2 - 160, height /2);
        }
    });
}

Instruction: 1) please allow camera access 2) you are a space fighter on an important mission. Use arrows keys for maneuver, and space bar to terminate space invaders 3) player who gets the higher score wins

*what didn’t work: 1)the pixels of the player in the background were supposed to buffer the velocity of the space invaders 2)the camera notification setting distracts the player in the beginning when the page is refreshed

I personally had lot of fun making this game as it really provoked my child experience with retro arcade games. I wanted to be selective in use of color, font and art in order to mimic the retro feelings.

ammar hassonjee and lee chu – final project

sketch

// Ammar Hassonjee | lee chu
// section c
// ahassonj@andrew.cmu.edu | lrchu@andrew.cmu.edu
// final project


var mode = 0; // variable for cycling through frames

// variables for the beachBall ball scene
// choosing an initial point to launch the ball from
var ballx = 240;
var bally = 100;
var dir1 = 1; // direction of ball
var dir2 = 1; // direction of ball
var speedx = 1; // speed of the ball
var speedy = 0.5;
var size = 100; // size of the ball

// variables for rain scene
var raindrops = []; // array to store rain objects
var rainSpeed = 5; // tracking the speed of the rain
var increment = 0.01; //incrementing the rain values
var tone = 255; // the color of the lightning
var strweight = 30; // stroke weight of the lightning bolt
var cloudNumber; //
var cloudwidth;
var cloudheight;
var frame = 8; // keeping track of the frame count

var myCaptureDevice;
var px = [];
var py = [];
var theColorAtPxPy;
var theBrightnessOfTheColorAtPxPy;
var brightnessThreshold;
var darknessThreshold = 10;

// variables for determining silhouette
var borderX = [];
var borderY = [];
// variables for particles
var parties = [];
var ex = 20;
var why = 20;


function setup() {
    createCanvas(480, 360);
    myCaptureDevice = createCapture(VIDEO);
    myCaptureDevice.size(480, 360); // attempt to size the camera. 
    myCaptureDevice.hide(); // this hides an unnecessary extra view.


    // three variables declared to initialize the lighting function
    var xi = random(40, 440);
    var xii = random(30, 450);
    var xiii = random(20, 460);

    //bImage.resize(480, 360);
}


function isColor(c) {
    return (c instanceof Array);
}


//--------------------------------------------------------------------
function draw() {
    background(220);
    myCaptureDevice.loadPixels();

    image(myCaptureDevice, 0, -0, width, height);  // draw the camera at 1:1 resolution

    // ammar's code
    if (mode === 0) {
        initialScene();
    }
    if (mode === 1) {
        // varying the clouds for each time the rain scene is called
        // done before rainFall is called to ensure this code doesn't loop again
        cloudNumber = random(3, 8);
        cloudwidth = random(105, 140);
        cloudheight = random(60, 95);
        //calling the beach ball scene
        ballSlap();
    }
    if (mode === 2) {
        rainFall();
    }

    // lee's code
    findBorder(); // finding silhouette of person
    if (mode === 3) {
    	// particle effects
    	addParti();
	    updateAndDisplayParti();
	    removeParti();
    }
    if (mode === 4) {
    	// censoring effect
    	censor();
    }
}


function mousePressed() {
    mode = (mode + 1) % 5; // code to swtich between modes
}


//-------AMMAR'S CODE----------------------------------------------------------------------
// NOTE: the code works best if users are wearing dark clothing against a light background
function initialScene() { // initial scene for webcam text
    textAlign(CENTER, CENTER);
    textSize(15);
    fill(100);
    text("WELCOME TO AMMAR AND LEE'S WEBCAM MACHINE", width / 2, height / 2);
    text("Press the mouse to cycle between scenes", width / 2, height * .8);
    text("Press 'O' to increase darkness threshold", width / 2, height * .85);
    text("Press 'P' to decrease darkness threshold", width / 2, height * .9);
    text("Press 'R' to detect threshold automatically", width / 2, height * .95);
}

function ballSlap() {
  // Creating the ball and setting its movement by changing
        //  ballx and bally variables
    beachBall(ballx, bally, size);
    ballx += dir1 * speedx;
    bally += dir2 * speedy;

    // Making ball bounce off the right
    if (ballx >= width - size / 2 || ballx <= size / 2) {
        dir1 = -dir1;
      }

      // Making the ball bounce off the top and bottom
    if (bally < size / 2 || bally > height - size / 2) {
        dir2 = -dir2;
      }

    var colorAtBall = myCaptureDevice.get(ballx, (bally + size / 2));
    if (isColor(colorAtBall)) {
            // setting the brightness to a variable and testing if it meets the
            //    the threshold values
            brightnessAtBall = brightness(colorAtBall);
            // if the ball hits the user's head (darker pixel) the ball will bounce up
            if (brightnessAtBall < darknessThreshold & dir2 > 0) {
                dir2 = -dir2;
                speedy = random(0.5, 5);
                speedyx = random(-4, 2);
              }
    }
}

function beachBall(x, y, diameter) { // function for creating rendering the beach ballx
    var ballcolors = ["RED", "BLUE", "YELLOW"];
    noStroke();
    fill(255);
    ellipse(x, y, diameter, diameter);
    fill(ballcolors[0]);
    arc(x, y, diameter, diameter, radians(30), radians(90));
    fill(ballcolors[1]);
    arc(x, y, diameter, diameter, radians(150), radians(210));
    fill(ballcolors[2]);
    arc(x, y, diameter, diameter, radians(270), radians(330));
    fill(255);
    ellipse(x, y, diameter / 5, diameter / 5);
}

function keyPressed() { // allowing the user to alter the beachball size with keys
    // increasing the ball size if the a key is pressed
    if (key === 'a' || key === 'A') {
        if (size < 200) {
            size += 10;
        }
    }

    // decreasing the ball size if the d key is pressed
    if (key === 'd' || key === 'D') {
        if (size > 50) {
            size -= 5;
        }
    }

    // this code allows the user to edit the darkness threshold in order to fix bugs in the webcam
    if (key === 'o' || key === 'O') {
        if (darknessThreshold < 100) {
            darknessThreshold += 2;
        }
    }

    // decreasing the darkness threshold
    if (key === 'p' || key === 'P') {
        if (darknessThreshold > 10) {
            darknessThreshold -= 2;
        }
    }

    // finding threshold automatically
    if (key ==='r' || key === 'R') {
    	findThreshold();
    }

}

function rainFall() { // function for creating the rain scene
    clouds(cloudNumber, cloudwidth, cloudheight); // calling the cloud function
    // these lines of called call the function for the rain objects to make ensure
    //    the rain continuously falls
    updateRain();
    removeRain();
    addRain();

    // these if statements remap the speed of the rain to change as time passes
    rainSpeed += increment;
    if (rainSpeed >= 15) {
        increment = -0.01;
    }
    if (rainSpeed <= 5) {
        increment = 0.01;
    }

    // code to implement the lighting function at a random timeout
    // this first if statement sets the lighting variables to an initial value
    //      and increments the frame variable
    if (frameCount % 200 === 0) {
        frame++;
        tone = 255;
        xi = random(40, 440);
        xii = random(30, 450);
        xiii = random(20, 460);
        strweight = 30;
    }

    // a separate frame variable is used to make sure the lighning stays for multiple frames
    if (frame % 3 === 0) {
        // a random variable is used to make sure the lightning is called unpredictably
        var chance = random(0, 2);
        lightning(strweight, tone, xi, xii, xiii);
        tone -= 4; // altering the lighting render values for aesthetic effect
        strweight -= 1;
    }

}

function makeRain(xpos, ypos) { // function for making the rain objects
    var rain = {x: xpos, y: ypos, move: rainMove, display: rainDraw};
    return rain;
}

function rainDraw() { // function for drawing the rain particles
    noStroke();
    fill(0, 0, 240);
    push();
    translate(this.x, this.y);
    rotate(radians(15));
    ellipse(0, 0, 2, 10);
    pop();
}

function rainMove() { // moving the rain by the rain speed global variable
    this.y += rainSpeed;
}

function updateRain() {
  // for loop that calls train track objects and moves/displays them
    for (var i = 0; i < raindrops.length; i++) {
        var colorAtRain = myCaptureDevice.get(raindrops[i].x, raindrops[i].y);
        raindrops[i].move();
        if (isColor(colorAtRain)) {
              // setting the brightness to a variable and testing if it meets the
              //    the threshold values
              brightnessAtRain = brightness(colorAtRain);

              // this ensures the rain stops falls on top of the figure in the WEBCAM
              //    by comparing to the pixel brightness
              if (brightnessAtRain >= darknessThreshold) {
                  raindrops[i].display();
                }
              }

      }
}

function addRain() { // function for adding rain objects to raindrops array
    for (var i = 0; i < 6; i++) {
       raindrops.push(makeRain(random(0, width), 0));
    }
}

function removeRain() { // function for removing objects from the array
    var rainKeep = [];
    for (var i = 0; i < raindrops.length; i++) {
        if (raindrops[i].y < 380) {
            rainKeep.push(raindrops[i]);
        }

      }
    raindrops = rainKeep; // reassigning the raindrops array to the duplicate array
}

// this function calls the lighting from a list of values that create polylines
function lightning(weight, brightness, xpos, xpos2, xpos3) {
    noFill();
    strokeWeight(weight);
    stroke(brightness);
    var x2 = xpos + random(-10, 10);
    var x3 = xpos2 + random(-20, 25);
    var x4 = xpos3 + random (-30, 40);
    var y1 = random(height / 4, height / 3);
    var y2 = random(height / 8 * 5, height * 2 / 3);
    line(xpos, 0, x2, y1);
    line(x2, y1, x3, y2);
    line(x3, y2, x4, height);
}

// function that calls the clouds from a list of variables
function clouds(number, length, cheight) {
    noStroke();
    for (var i = 0; i < number + 1; i++) {
        fill( map(i, 0, number, 100, 150));
        var cloudx = map(i, 0, number, 0, width);
        ellipse(cloudx, 0, length, cheight + 15 * (i % 3));
    }
}


//-------LEE'S CODE----------------------------------------------------------------------

function findBorder() {
	borderX = [];
	borderY = [];
	// finds the silhouette of person based on darkness threshold in intervals of 5
	for (var i = 0; i < width; i += 5) {
		for (var j = 0; j < height; j += 5) {
			if (brightness(myCaptureDevice.get(i, j)) < darknessThreshold & 
				borderX.length < i / 5) {
					borderX.push(i);
					borderY.push(j);
			}
		}
	}
}

function findThreshold() {
	var brightest = 50;
    var darkest = 50;
    var xColors = [];
    var xx = [];
    // stores brightnesses of all colors along a line in the image displayed
    for (var j = 0; j < width; j += 5) {
        xColors.push(brightness(myCaptureDevice.get(j, height / 2)));
        xx.push(j);
    }
    // finds brightest and darkest pixels
    for (var k = 0; k < xColors.length; k ++) {
        if (xColors[k] > xColors[brightest]) {
            brightest = k;
        }
        if (xColors[k] < xColors[darkest]) {
            darkest = k;
        }
    }
    // elementary ratio to determine thresholds
    brightnessThreshold = (xColors[brightest] - xColors[darkest]) / 3 + xColors[darkest];
    darknessThreshold = (xColors[brightest] - xColors[darkest]) / 5 + xColors[darkest] - 10;
}

function addParti() {
	// creating particles along top of person
	var likelihood = 0.45;
	for (var i = 0; i < borderX.length; i ++) {
		if (random(1) < likelihood) {
			parties.push(createParticle(borderX[i], borderY[i]));
		}
	}
}

function removeParti() {
	// removes particle when size decreases below 1
	var partiesToKeep = [];
	for (var i = 0; i < parties.length; i ++) {
		if (parties[i].size > 1) {
			partiesToKeep.push(parties[i]);
		}
	}
	parties = partiesToKeep;
}

function updateAndDisplayParti() {
	for (var i = 0; i < parties.length; i ++) {
		parties[i].move();
		parties[i].display();
	}
}

function createParticle(birthX, birthY) {
	var parti = {x: birthX, y: birthY, size: round(random(5, 15)),
				speed: -5, move: partiMove, display: partiDisplay,
				color: myCaptureDevice.get(birthX, birthY)}
	return parti;
}

function partiMove() {
	// move particle and decrease size
	this.y += this.speed;
	this.size -= 1;
}

function partiDisplay() {
	strokeWeight(0);
	fill(this.color);
	push();
	translate(this.x, this.y);
	rotate(millis() / 1000 * 2 * PI);
	rectMode(CENTER);
	rect(0, 0, this.size, this.size);
	pop();
}

function censor() {
	// divides area below silhouette into 25 by 25 squares and extracts colors
	for (var i = 0; i < borderX.length; i += 5) {
		for (var j = borderY[i]; j < height; j += 25) {
			push();
			translate(borderX[i], j);
			rectMode(CENTER)
			fill(myCaptureDevice.get(borderX[i], j));
			rect(0, 0, 25, 25);
			pop();
		}
	}
}

Ammar Hassonjee and Lee Chu collaborated on this webcam machine. It consists of four scenes: a beachball headbutting game, a rainy day, snapped by Thanos, and an unnecessary censorship. Click the mouse to cycle through the scenes. Press the keys O and P to increase or decrease the darkness threshold depending on the lighting of your setting. Press the key R to automatically detect the darkness threshold, although this process may not be perfect.

Final Project – Austin Garcia – Group C

Click, hold and drag on the white circle to move it, but don’t move it too quickly or it will reset. Move it to the black circle to complete and randomize the box placement again. I intended to have collision on the white rectangles but have so-far been unsuccessful in the implementation of that.

My goal with this project was to make a quick and easy game where you moved the ball through a series of obstacles and, upon reaching the goal, the map re-set and you could do it again. I have tried to tune the boxes so that there is always a path through them to the goal, but RNG is often a difficult thing to work with and a more careful approach of creating different ‘levels’ to cycle through may have been a more effective method.

sketch

/*		Austin Garcia
		Section C
		aegarcia@andrew.cmu.edu
		Assignment or Project
*/

var ballX = 10;
var ballY = 10;
var diameter = 10;
var goalX = 380;
var goalY = 380;
var boxNum = 15;


function drawSquares() {
    noStroke();
    fill(255);
    rect(this.x, this.y, this.w, this.h);
};

function makeSquares() {
    var squares = {
      x: random(0, 200),
      y: random(15, 380),
      w: 10,
      h: random(100),
      draw: drawSquares,

    }
    return squares;
};



function drawBoxes() {
    noStroke();
    fill(255);
    rect(this.x, this.y, this.w, this.h);
};

function makeBoxes() {
    var boxes = {
      x: random(0, 350),
      y: random(15, 380),
      w: random(100, 150),
      h: 10,
      draw: drawBoxes,
    }
    return boxes;
};

var boxes = [];
var squares = [];


function setup() {
    createCanvas(400, 400);

    for(var i = 0; i < boxNum; i++) {
    var b = makeBoxes();
    var s = makeSquares();
    squares.push(s);
    boxes.push(b);
  }
}

function draw() {
    background(20, 180, 0);

    for(var i = 0; i < boxNum; i++) {
      var b = boxes[i];
      var s = squares[i];
      s.draw();
      b.draw();
   }
// mouse circle
    fill(255);
    if(dist(ballX, ballY, mouseX, mouseY)< diameter & mouseIsPressed)  {
      ballX = mouseX;
      ballY = mouseY;
  }
    //ball
    ellipse(ballX, ballY, diameter,diameter);
    //goal circle
    fill(0);
    ellipse(goalX, goalY, diameter*2, diameter*2);

    //goal circle effect
    if(dist(ballX, ballY, goalX, goalY) < 18)  {
      ballX = 10;
      ballY = 10;
      for (var i = 0; i < boxNum; i++) {
          boxes[i].x = random(0, 350);
          boxes[i].y = random(15, 380);
          boxes[i].w = random(100, 150);
          boxes[i].h = 10;

          squares[i].x = random(0, 200);
          squares[i].y = random(15, 380);
          squares[i].w = 10;
          squares[i].h = random(100);
        }
      }
      //Attempt at getting collision to work
/*    for (var i = 0; i < boxNum; i++) {
      if(dist(mouseX+5, mouseY+5, boxes[i].x, boxes[i].y) > boxes[i].w & boxes[i].h) {
        ballX = 10;
        ballY = 10;
      }
    }
      */

    }
function mouseReleased() {
   ballX = 10;
   ballY = 10;
 };

Monica Chang – Final Project

Monica Chang – Final Project

I had to upload through a zip file because my canvas size was 650 in width(a little over the max size for WordPress). Thus, continue reading to learn how to access the game.

Instructions on accessing the game: To begin this game, you must download the zip file attached above. Once the file is downloaded, a zip file called “Monica-Chang-Final-Project” should appear. Open the file and a folder called “104final” will appear; then, open “sketch.js” to access the code behind it.

Because this is outside of WordPress and my code has implemented sound, it is crucial to trigger the sound file playback and open the game by following the instructions below(taken from Lab Week 10).

  1. Open a Terminal in OS X or a command window (cmd) in Windows.
  2. Change your current directory to the directory you want to serve:Type cd path-to-your-directory (ex. cd Desktop/104final )
  3. Type in Terminal:
    python -m SimpleHTTPServerOr if you are using Python 3, type:
    python -m http.server
  4. Visit the URL http://localhost:8000 in your browser to test your sketch.

Description: For this final project, I developed a game in which the player or virtual “photographer” will be responsible for capturing pictures of the flying birds with the virtual camera by pressing the SPACEBAR. The problem is the birds fly by way too fast but the players are required to capture enough pictures to reach “captured” score of 100. There are no losing points in this game.

Another element that I implemented is changing the weather. Since the initial weather is gloomy and raining, the player may press his/her mouse to change the screen to a happy, blue sky. The field audio/sounds will change accordingly.

Default Screen – gloomy, rainy sky
When mouse pressed, screen changes to blue sky!
Bird – the player must press SPACEBAR to capture these.

Lauren Park – Final Project

sketch

//Lauren Park
//ljpark@andrew.cmu.edu
//Section D
//Final Project

//initialize sound variables
var love;

//load sound file
function preload(){
love = loadSound("https://courses.ideate.cmu.edu/15-104/f2019/wp-content/uploads/2019/11/jasmine.wav");
}

function setup() {
  createCanvas(500, 400);
  love.loop();
  
  beat = new p5.Amplitude();
  beat.setInput(love);
}

function draw() {
  
  //abstract curves
  frameRate(1.65);
  background(50, 155, random(255), 140);
  //create the vertices curves
  var numCurvePoints = 10;
  beginShape();
  for (var i = 0; i < numCurvePoints; i++) {
    curveVertex(random(30, width - 20), random(100, height - 10));
  }
  endShape();

  stroke(random(215));
  strokeWeight(0);
  fill(254, 193, 255, 140); 
  var numCurvePoints = 12;
  beginShape();
  for (var i = 0; i < numCurvePoints; i++) {
    curveVertex(random(30, width - 20), random(95, height - 20));
  }
  endShape();

  //background lines
  for (var row = 0; row <= width; row = row + 70) {
    for (var col = 0; col <= height; col = col + 60) {
      fill(80);
      rect(random(row), random(col), 5, 45);
      fill(200);
      rect(random(row), random(col), 4, 30);
    }
  }
  
  //hoodie fill colors
  fill('#4BA680');
  ellipse(231, 300, 90, 200);
  ellipse(300, 305, 90, 200);
  ellipse(270, 390, 195, 180);
  ellipse(280, 265, 90, 100);

  //back hair
  fill('#8A4B21');
  ellipse(223, 147, 30, 30);
  ellipse(218, 160, 20, 30);
  ellipse(220, 205, 25, 30);
  ellipse(209, 227, 30, 31);
  ellipse(210, 221, 20, 30);
  strokeWeight(2);
  stroke('#4D2A12');
  fill('#8A4B21');
  curve(230, 100, 210, 137, 209, 170, 230, 150); 
  curve(230, 100, 213, 190, 187, 270, 300, 400); 

  //hoodie collar
  strokeWeight(3);
  stroke('#316E55');
  fill('#4BA680');
  beginShape();
  curveVertex(230, 215);
  curveVertex(256, 222);
  curveVertex(225, 209);
  curveVertex(245, 231);
  curveVertex(258, 191);
  endShape();
  
  //rightcollar
  beginShape();
  curveVertex(258, 171);
  curveVertex(252, 221);
  curveVertex(300, 205);
  curveVertex(371, 215);
  endShape();

  //hoodie left arm
  beginShape();
  curveVertex(271, 215);
  curveVertex(230, 218);
  curveVertex(193, 250);
  curveVertex(170, 400);
  curveVertex(193, 250);
  endShape();

  //right arm
  strokeWeight(3);
  stroke('#316E55');
  fill('#4BA680');
  beginShape();
  curveVertex(313, 223);
  curveVertex(300, 230);
  curveVertex(340, 250);
  curveVertex(370, 400);
  curveVertex(340, 250);
  endShape();

  //hood
  beginShape();
  curveVertex(205, 230);
  curveVertex(300, 205);
  curveVertex(333, 213);
  curveVertex(335, 241);
  curveVertex(300, 210);
  endShape();
  
  //inner arms shadow
  curve(230, 280, 215, 275, 205, 400, 150, 540); 
  curve(320, 250, 310, 280, 325, 400, 280, 200); 

  //face
  fill('#DAA67B');
  strokeWeight(1.5);
  stroke('#AD8361');
  ellipse(250, 175, 80, 88);

  //outer hair
  noStroke();
  fill('#8A4B21');
  ellipse(285, 200, 30, 175);
  ellipse(265, 167, 20, 40);
  ellipse(270, 190, 20, 60);
  ellipse(282, 280, 10, 30);
  strokeWeight(2);
  stroke('#4D2A12');
  fill('#8A4B21');
  
  beginShape();
  curveVertex(250, 463);
  curveVertex(290, 163);
  curveVertex(305, 270);
  curveVertex(280, 300);
  curveVertex(303, 300);
  endShape();

  beginShape();
  curveVertex(280, 300);
  curveVertex(280, 300);
  curveVertex(250, 146);
  curveVertex(200, 346);
  endShape();

  //hat
  fill(70);
  stroke(0);
  beginShape();
  curveVertex(210, 137);
  curveVertex(295, 163);
  curveVertex(210, 137);
  curveVertex(215, 125);
  curveVertex(295, 163);
  curveVertex(290, 147);
  curveVertex(215, 125);
  curveVertex(290, 147);
  endShape();

  beginShape();
  curveVertex(220, 214);
  curveVertex(215, 124);
  curveVertex(290, 110);
  curveVertex(296, 150);
  curveVertex(296, 150);
  endShape();

  //heart beat to song
  mood = beat.getLevel();
  fill(0);
  ellipse(260,300,25+mood*100, 30+mood*100);
  ellipse(280,300,25+mood*100, 30+mood*100);
  triangle(251, 309, 289, 309, 270, 330);
  ellipse(255, 317+mood*100, 3, 15);
  ellipse(277, 323+mood*100, 2, 12);
  ellipse(263, 337+mood*100, 2, 12);
  ellipse(281, 340+mood*100, 2, 14);
  ellipse(269, 353+mood*100, 2, 17);
  ellipse(261, 371+mood*100, 2, 17);
  ellipse(278, 390+mood*100, 2, 20);
  
  //ear
  stroke('#AD8361');
  fill('#DAA67B');
  curve(230, 197, 275, 170, 277, 182, 210, 230);  
  curve(260, 197, 275, 170, 277, 182, 300, 250);

  //earring
  stroke(210);
  curve(250, 87, 278, 178, 280, 179, 250, 230); 
  
  //hood string
  noStroke();
  fill(255);
  ellipse(245, 254, 3, 40);
  ellipse(247, 265, 4, 20);
  
  stroke(200);
  fill(255);
  ellipse(247, 235, 4.5, 4.5);
  curve(300, 180, 245, 235, 245, 275, 240, 300);
  curve(200, 300, 245, 275, 250, 275, 250, 250);
  curve(300, 180, 249.5, 235, 249.5, 275, 240, 300);

}

For this project, I really wanted to use a song file and have different elements and shapes move or change in rhythm to the song. I wanted to focus on the visuals and concept that also sync not only in beat but also in theme to the song. By incorporating this moody love song, I tried to capture the vibe of the music by creating a character and abstract forms in the background and by creating the dripping or bleeding black heart that beats to this song. For the color scheme and moving elements in the background, it is inspired by actual albums of the artists who sang this song. Overall, it was very challenging to properly load the sound and play around with the amplitude so that the heart beats, but it was a very fun piece that showcases some of my interests.