Ian Kaneko – Final Project

ikaneko 15-104 final project

Unfortunately I ran into trouble while trying to embed my project. Linked above is a zip file that contains my project and the sound files necessary to run it. Clicking the link should begin a download of the zip file. After extracting the file the project should be available just by running the index found in the template-all folder.

For my final project I created a set of wind chimes that the user can interact with. The user can swipe their mouse through the chimes, moving them and causing them to sound when they collide with each other. When the mouse hits a chime it will move in the direction that the mouse was moving with force proportional to how fast the mouse was moving as well. The chimes will also transfer momentum when they collide with each other. The pitches from left to right are C, D, E, G, A, C, and D. The sounds are sampled from a glockenspiel which sounds very similar to wind chimes. (If you swipe really hard and some chimes go off-screen don’t worry, they come back!)

The starting position of the chimes
Chimes after being swiped around

Crystal-Xue-Final-Project

sketch-134js


var imgPhone;
var imgLogo;
var imgAlb;
var imgBg;
var imgLike;
var song;
var amp;
var fft;
var volhistory = [];
var scrWid = 270; // width of the iphone screen

function preload(){
    //preloading images and sound source
    imgPhone = loadImage("https://i.imgur.com/Mb4yoMB.jpg?2");
    imgLogo = loadImage("https://i.imgur.com/sFAzrlV.jpg?3");
    imgAlb = loadImage("https://i.imgur.com/EAsdXzG.png?1");
    imgBg = loadImage("https://i.imgur.com/Ztd0x2o.jpg")
    imgLike = loadImage("https://i.imgur.com/vX1cA55.jpg?1");
    song = loadSound("https://courses.ideate.cmu.edu/15-104/f2019/wp-content/uploads/2019/12/instagram.wav");
    song.setVolume(0.5);
}

function setup() {
    createCanvas(323, 635);
    song.play();
    amp = new p5.Amplitude();
    fft = new p5.FFT(0.9, 256);
    angleMode(DEGREES);

}

function draw() {
    background(255);

    var vol = amp.getLevel();
    volhistory.push(vol);

    //setup constrains for bountries
    var topWall = scrWid / 2 + 130;
    var botWall = height - scrWid / 2 - 100;
    var yc = constrain(mouseY, topWall, botWall);

    //draw iphne and instagram logo
    image(imgPhone, 0, 0);
    image(imgLogo, width / 2 - 50, 47);

    drawPost();

    push();
    translate((width - scrWid) / 2, yc-height + scrWid - 40);
    drawfft();
    pop();

    push();
    translate((width - scrWid) / 2, -height + scrWid / 2 + yc);
    drawAmp();
    pop();

    push();
    translate(width / 2, yc);
    drawCir();
    pop();


}

function drawfft(){
    //setup colors
    var colorvar = frameCount;
    if (colorvar >= 255) {
        colorvar = 0;
    } else {colorvar = frameCount;}
    var rC = map(colorvar, 0, width, random(255), 255);
    var g = map(colorvar, 0, width, random(255), 100);
    var b = map(colorvar, 0, height, random(255), 255);
    strokeWeight(2);
    stroke(rC, g, b, 90);

    var spectrum = fft.analyze();
    for (var i = 0; i < spectrum.length; i++) {
        var y = map(spectrum[i], 0, 250, 0, scrWid);
        //spacings
        var w = scrWid/64;
        line((i)*w, scrWid, (i)*w , y+scrWid);
    }
}

function drawAmp(){
    //setup colors
    var colorvar = frameCount;
    if (colorvar >= 255) {
        colorvar = 0;
    } else {colorvar = frameCount;}
    var rC = map(colorvar, 0, width, random(255), 255);
    var g = map(colorvar, 0, width, random(255), 100);
    var b = map(colorvar, 0, height, random(255), 255);

    noStroke();
    fill(rC, g, b, 90);
    //draw amplitutes
    beginShape();
    vertex(0, height);
    for (var i = 0; i < volhistory.length; i++) {
        var y = map(volhistory[i], 0, 1, height, 0);
        vertex(i, y);
    }
    vertex(scrWid, height);
    endShape();

    //make sure the graphs stays inside the width of the screen
    if (volhistory.length > scrWid) {
        volhistory.splice(0, 1);
    }
}

function drawPost(){
    rectMode(CENTER);
    stroke(255);
    //setup constrains
    var topWall = scrWid / 2 + 130;
    var botWall = height - scrWid / 2 - 100;
    var yc = constrain(mouseY, topWall, botWall);

    //draw post images
    fill(100);
    rect(width/2, yc, scrWid, scrWid);
    //draw album profile image
    image(imgAlb, 35, yc - scrWid / 2 - 60, 45, 45);
    //draw background image
    image(imgBg, width / 2 - scrWid / 2, yc - scrWid / 2, scrWid, scrWid);
    //draw like image
    image(imgLike, 250, yc, 40 + random(0, 5) ,32 + random(0, 5));

    //draw post texts
    textSize(14);
    text("DEAN", 100,  yc-scrWid / 2 - 30);
    textSize(12);
    text(frameCount, 30, yc + scrWid / 2 + 30);
    text("likes", 70, yc + scrWid / 2 + 30);
    text("#dean #instagram #soundvisualization", 30, yc + scrWid / 2 + 45);
    text("All night just wasting time like this", 30, yc + scrWid / 2 + 60);
    text("Inside your Instagram", 30, yc + scrWid / 2 + 75);
}

function drawCir(){
    noFill();
    stroke(255);
    strokeWeight(1.5);
    beginShape();
    for (var i = 0; i < 360; i++) {
        var r = map(volhistory[i], 0, 1, 10, 300);
        var x = r*cos(i);
        var y = r*sin(i);
        vertex(x,y);
    }
    endShape();
    //make sure that the the lines don't overlap
    if (volhistory.length > 360) {
        volhistory.splice(0, 1);
    }
}

This Project is about visualization of the song “Instagram”. The main theme is to deliver the message of social media anxiety among teenagers that everything is about the obsession of “likes” and how to gain popularity through posts. I mimicked the Instagram app page to set the base of this project. And as the progression of the song is represented by the intensity of the graphics. The anxiety also levels up when the “like” number goes up over time.

STAGE 1
STAGE 2
STAGE 3

Final Project – Taisei Manheim, Carly Sacco, Jai Sawkar


For our project, we decided to create an iPhone and allow for interactions with some of the apps typical users like to spend time on. Carly created the Snapchat app and added in a few filters that can be changed by hovering over the different circles.Tai recreated a music app where the user can choose different songs to listen to. Jai created the Instagram and Clock apps and also some of the initial images for the home screen. For instagram, the page mimics a person’s profile picture (his own), and for the clock app, each of the locations in different time zones represent their current live time. Putting together the code was difficult, but Carly went over the commenting and formatting, while Tai helped with debugging and combining all of the code.

Although this project was very fun to complete, we had a lot of glitches we weren’t expecting. Having multiple apps that all ran but did not interfere with each other was a bit of a struggle. Coding between three people made the code lengthy and since we had separately used the same functions, when we consolidated our codes, the functions were overriding each other. Also, for the music app, we originally had an entire album to play from, but by the end of the project our code was so long and took a while to load, that we had to cut out a lot of songs and shorten the length of the songs, so there are only 2 short sound clips that play if you click the “1” or “2” key.

sketch

// Jai Sawkar, Tai Manheim, Carly Sacco
// jsawkar@andrew.cmu.edu, tmanheim@andrew.cmu.edu, csacco@andrew.cmu.edu
// Section C
// Final Project

//states that will determine is each app is running
//home screen starts on
menuOn = true;
//all other apps are off at the beginning of the code
musicOn = false;
snapchatOn = false;
instagramOn = false;
clockOn = false;

//var playerIMG;
var song1;
var song2;

function preload() {
    
    //images for the app icons on home screenh
    var snapURL = "https://i.imgur.com/UfJfDg1.png?"
    snapPic = loadImage(snapURL); 
    var musicURL = "https://i.imgur.com/m6NxUGy.png?"
    musicPic = loadImage(musicURL);
    var instaURL = "https://i.imgur.com/qTYtnyQ.png?"
    instaPic = loadImage(instaURL);
    var clockURL = "https://i.imgur.com/eX2G9P3.png?"
    clockBG = loadImage(clockURL);
    
    //Instagram Pictures
    var instaPic1URL = "https://i.imgur.com/FVXjmZU.jpg?2"
        instaPic1 = loadImage(instaPic1URL);
    var instaPic2URL = "https://i.imgur.com/RIkwcRD.jpg?2"
        instaPic2 = loadImage(instaPic2URL);
    var instaPic3URL = "https://i.imgur.com/eBPLHVa.jpg?2"
        instaPic3 = loadImage(instaPic3URL);
    var instaPic4URL = "https://i.imgur.com/aKhmsST.jpg?2"
        instaPic4 = loadImage(instaPic4URL);
        
    var instaPic5URL = "https://i.imgur.com/Pf6NRRh.png"
        instaPic5 = loadImage(instaPic5URL); 
        

    //music cover album photo
    var albumImage = "https://i.imgur.com/ajcDQxJ.jpg"
    albumCover = loadImage(albumImage);

    //load tracks
    song1 = loadSound("https://courses.ideate.cmu.edu/15-104/f2019/wp-content/uploads/2019/12/382698__iwawiwi__small-fragment-of-one-man-rumba-music-recording-from-wikimedia-common.wav");
    song2 = loadSound("https://courses.ideate.cmu.edu/15-104/f2019/wp-content/uploads/2019/12/213096__soundsexciting__gong-with-music.wav"); 
}


function soundSetup(){
    //volume for tracks
    song1.setVolume(2);
    song2.setVolume(2);
}


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

    //background of phone
    push();
    rectMode(CENTER);
    fill(100)
    rect(width/2, height/2, 159, 323, 15);
    pop();

    //loads in the cameras for snapchat
    //starting camera: no filter
    normalCamera = createCapture(VIDEO);
    normalCamera.size(158, 318); //camera size to fit in phone
    normalCamera.hide();

    //filter1: threshold
    filter1 = createCapture(VIDEO);
    filter1.size(158, 318); //camera size to fit in phone
    filter1.hide();

    //filter2: invert
    filter2 = createCapture(VIDEO);
    filter2.size(158, 318); //camera size to fit in phone
    filter2.hide();

    //filter3: posterize
    filter3 = createCapture(VIDEO);
    filter3.size(158, 318); //camera size to fit in phone
    filter3.hide();

    //filter4: grayscale
    filter4 = createCapture(VIDEO);
    filter4.size(158, 318); //camera size to fit in phone
    filter4.hide();
}

function draw() {
    
    //when the m key is pressed it sets the music screen to true
    if (keyIsDown(77)) {
        musicOn = true;
        menuOn = false;
    }
    //when the s key is pressed it sets the snapchat screen to true
    if (keyIsDown(83)) {
        snapchatOn = true;
        menuOn = false;
    }
    //when the i key is pressed it sets the instagram screen to true
    if (keyIsDown(73)) {
        instagramOn = true;
        menuOn = false;
    }
    //when the c key is pressed it sets the clock screen to true
    if (keyIsDown(67)) {
        clockOn = true;
        menuOn = false;
    }
    //when the h key is pressed it sets the menu to true
    if (keyIsDown(72)) {
        menuOn = true;
        snapchatOn = false;
        musicOn = false;
        instagramOn = false;
        clockOn = false;
    }
    //when menuOn is true it calls the function to make the phone
    if (menuOn == true) {
        makePhone();
    }
    //when musicOn is true it calls the function for the music app
    if (musicOn == true){
       makeMusicApp();
    }
    //when snapchatOn is true it calls the function for the snapchat app
    if (snapchatOn == true){
       makeSnapchatApp();
    }
    //when instagramOn is true, it calls the funtion for the instagram app
    if (instagramOn == true){
       makeInstagramApp();
    }
    //when clockOn is true it calls the function for the clock app
    if (clockOn == true){
       makeClockApp();
    }

    //a function for the the outline of the phone 
    //so that it stays consistent through the apps
    makeBorder();

    //directions on the canvas
    push();
    noStroke();
    textFont('Helvetica');
    fill(189, 160, 0)
    text("type s for snapchat", 20, height / 2 - 40);
    text("hover over buttons to change filter", 30, height / 2 - 25);
    fill(169, 68, 194)
    text("type m for music", 20, height / 2 - 5);
    text("press the song number you want", 30, height / 2 + 10 ) 
    text("press the space bar to play & pause", 30, height  / 2 + 20);
    fill(219, 37, 113);
    text("type i for instagram", 20, height / 2 + 40)
    fill(103, 168, 156);
    text("type c for clock", 20, height / 2 + 60);

    fill(89, 92, 91);
    textSize(20)
    text("type h to return home", 20, height / 2 - 70);
    pop(); 
} 

function makeMusicApp() {
    //phone screen
    push()
    rectMode(CENTER);
    fill(50);
    rect(width / 2, height / 2, 148, 308, 15); 

    //album cover image
    image(albumCover, width / 2 - 30, height / 2 - 140);

    //pause button
    fill(50, 235, 50);
    noStroke();
    rect(width / 2, height / 2 - 42.5, 50, 20, 10);

    //lines seperating songs
    push();
    strokeWeight(2);
    stroke(255);
    line(width / 2 - 74, height / 2 - 25, width / 2 + 74, height / 2 - 25);
    line(width / 2 - 74, height / 2 - 5, width / 2 + 74, height / 2 - 5);
    line(width / 2 - 74, height / 2 + 15, width / 2 + 74, height / 2 + 15);
    line(width / 2 - 74, height / 2 + 35, width / 2 + 74, height / 2 + 35);
    line(width / 2 - 74, height / 2 + 55, width / 2 + 74, height / 2 + 55);
    line(width / 2 - 74, height / 2 + 75, width / 2 + 74, height / 2 + 75);
    line(width / 2 - 74, height / 2 + 95, width / 2 + 74, height / 2 + 95);
    line(width / 2 - 74, height / 2 + 115, width / 2 + 74, height / 2 + 115);
    line(width / 2 - 74, height / 2 + 135, width / 2 + 74, height / 2 + 135);
    pop();

    //album title
    noStroke();
    fill(255);
    textAlign(CENTER);
    textSize(12);
    text("Nostalgia Ultra", width / 2, height / 2 - 67.5);

    //pause button writing
    textSize(10);
    text("Pause", width / 2, height / 2 - 40);

    //song titles
    text("1. strawberry swing", width / 2, height / 2 - 12.5);
    text("2. novacane", width / 2, height / 2 + 7.5);
    text("3. we all try", width / 2, height / 2 + 27.5);
    text("4. songs for women", width / 2, height / 2 + 47.5);
    text("5. there will be tears", width / 2, height / 2 + 67.5);
    text("6. swim good", width / 2, height / 2 + 87.5);
    text("7. dust", width / 2, height / 2 + 107.5);
    text("8. american wedding", width / 2, height / 2 + 127.5);
    text("9. nature feels", width / 2, height / 2 + 147.5);
    pop(); 
}

function keyPressed() {
    //corresponds the number key pressed to the song
    //key 1 plays song 1
    if (keyCode === 49) {
        //play song 1
        song1.play();
        //pause others
        song2.pause();
    }
    
    //key 2 plays song 2
    if (keyCode === 50) {
        //play song 2
        song2.play();
        //pause others
        song1.pause();
    }

    //spacebar pauses any song
    if (keyCode === 32) {
        //pause all songs
        song1.pause();
        song2.pause();
    }
} 

function normalCamera() {
   push();
   //loads the pixels from the camera
   normalCamera.loadPixels(); 
   //displays the camera
   image(normalCamera, 242, 41);  
   pop();
}

function filterThreshold() {
    push();
    //loads the pixels from the camera
    filter1.loadPixels(); 
    //displays the camera
    image(filter1, 242, 41);  
    //applies the filter
    filter(THRESHOLD);
    pop();
    //buttons options on the screen
    buttons();

}

function filterInvert() {
    push();
     //loads the pixels from the camera
    filter2.loadPixels(); 
    //displays the camera
    image(filter2, 242, 41);  
    //applies the filter
    filter(INVERT);
    pop();
    //buttons options on the screen
    buttons();

}

function filterPosterize() {
    push();
    //loads the pixels from the camera
    filter3.loadPixels(); 
    //displays the camera
    image(filter3, 242, 41); 
    //applies the camera 
    filter(POSTERIZE, 3);
    pop();
    //buttons options on the screen
    buttons();

}

function filterGray() {
    push();
        //loads the pixels from the camera 
    filter4.loadPixels();
    //displays the camera
    image(filter4, 242, 41);  
    //applies the filter
    filter(GRAY);
    pop();
    //buttons options on the screen
    buttons();

}

function buttons() {
    //function that creates the buttons that appear in the snapchat app
    //biggest normal middle circle
    noFill();
    stroke(255);
    strokeWeight(2);
    ellipse(width/2, height / 2 + 135, 25, 25);

    //first button: threshold
    noFill();
    stroke(255);
    strokeWeight(2);
    ellipse(width/2 - 60, height / 2 + 135, 20, 20);

    //second button: invert
    noFill();
    stroke(255);
    strokeWeight(2);
    ellipse(width/2 - 30, height / 2 + 135, 20, 20);

    //third button: posterize
    noFill();
    stroke(255);
    strokeWeight(2);
    ellipse(width/2 + 30, height / 2 + 135, 20, 20);

    //fourth button: blur
    noFill();
    stroke(255);
    strokeWeight(2);
    ellipse(width/2 + 60, height / 2 + 135, 20, 20);

}

function makeSnapchatApp(){
   //begins with the normal camera &
   //uses the normal camera as default when no 
   //filter is running
   push();
   normalCamera.loadPixels(); 
   image(normalCamera, 242, 41);
   pop();

   buttons();

   //if the mouse is hovering over a button, then the associated 
   //filter will appear

   //threshold filter
    if  (mouseX > 250 & mouseX < 275  && mouseY > 320) {
        filterThreshold();
    }
    //invert filter
    if  (mouseX > 270 & mouseX < 295  && mouseY > 320) {
        filterInvert();
    }
    //posterize filter
    if  (mouseX > 335 & mouseX < 360  && mouseY > 320) {
        filterPosterize();
    }
    //gray filter
    if  (mouseX > 365 & mouseX < 400  && mouseY > 320) {
        filterGray();
    }

}

function makeInstagramApp(){
        //Instagram Background
            push();
            rectMode(CENTER);
            fill(245);
            noStroke();
            rect(width/2, height/2, 159, 323, 15);

        //Instagram Heading
            textSize(32);
            fill(42, 57, 107);
            textAlign(CENTER);
            textFont('Satisfy');
            text('Instagram', width/2 + 1, 80);

        //Line Below Instagram
            stroke(42, 57, 107); 
            strokeWeight(.5);
            line(width/2 - 73.5, 95, width/2 + 73.5, 95);

        //Profile Pic
            fill(240);
            strokeWeight(1.5);
            push();
            scale(1.2)
            image(instaPic5, width/2 - 113.5, 84);
            pop();
            noFill();

        //Profile Info
            noStroke();
            fill(42, 57, 107);
            textAlign(LEFT);
            textFont('Helvetica');
            textSize(10);
            text("Jai Sawkar", width/2 - 68, 165);
            fill(60)
            textSize(9);
            text("@jsawkar", width/2 - 68, 175);

        //Followers & Following
            rectMode(CORNER)
            fill(60, 60, 60, 20)
            rect(width/2 - 7, 102.5, 75, 30);
            stroke(60);
            strokeWeight(.2);
            line((width/2-7 + width/2 + 68)/2, 102.5, (width/2-7 + width/2 + 68)/2, 132.5)
         
            fill(60)
            textSize(7);
            noStroke();
            text("Followers", width/2 - 3, 110);
            text("Following", width/2 + 35, 110);

            textSize(8);
            fill(0);
            textAlign(LEFT);
            text("1,027", width/2 + 2, 122)
            text("971", width/2 + 43, 122);

        //Profile Bio
            textSize(9);
            textAlign(RIGHT);
            text("sammamish, wa", width/2 + 65, 165);
            text("carnegie mellon '22", width/2 + 65, 175);
        
        //Line Below Instagram Bio
            stroke(42, 57, 107); 
            strokeWeight(.5);
            line(width/2 - 73.5, 190, width/2 + 73.5, 190);
            pop();

        //Loading Instagram Pictures
            push();
            scale(0.6)
            image(instaPic1, width/2 + 105, 330); 
            image(instaPic2, width/2 + 220, 330);
            image(instaPic3, width/2 + 105, 445);
            image(instaPic4, width/2 + 220, 445);
            pop();
}

function makeClockApp(){
    push();
    //Black Background
        rectMode(CENTER);
        fill(20);
        noStroke();
        rect(width/2, height/2, 159, 323, 15);

    //CLOCK Heading
     
        textSize(32);
        fill('WHITE');
        textFont('Helvetica');
        text('Clock', width/2 - 70, 80);


    //Line below Clock
        stroke(255); 
        strokeWeight(.5);
        line(width/2 - 73.5, 90, width/2 + 73.5, 90);

        var h = hour();
        var m = minute();
        var s = second();

        var hPos = width/2 + 10; //variable for hour time position
        var mPos = width/2 + 40; //variable for minute time position

        var singleHPos = width/2 + 20; //variable for hour time position if it is a single digit
        var singleMPos = width/2 + 51;//variable for minute time position if it is a single digit

        //Pittsburgh Variables
            var pittsburghH = h; //Current hour
            var pittsburghM = m; //Current minute

            var pittsburghHPos = hPos; //Hour position for Pittsburgh
            var pittsburghMPos = mPos; //Minute position for Pittsburgh

            //If Hour is a Single Digit, Move Hour Time To Be Closer to ":"
                if (pittsburghH < 10){ 
                    pittsburghHPos = singleHPos;
                }

            //If Minute is a Single Digit, Move Minute Time To Be Closer to ":" & add a "0"
                if (pittsburghM < 10){
                    pittsburghMPos = singleMPos;
                    textSize(20);
                    fill(255);
                    text("0", mPos, 115);
                }

        //Cupertino Variables
        var cupertinoH = h - 3; //Current local hour
        var cupertinoM = m; //Current local minute

        var cupertinoHPos = hPos; //Hour position for Cupertino
        var cupertinoMPos = mPos; //Minute position for Cupertino

        //If Hour is a Single Digit, Move Hour Time To Be Closer to ":"
                if (cupertinoH < 10){ 
                    cupertinoHPos = singleHPos;
                }
        //If Minute is a Single Digit, Move Minute Time To Be Closer to ":" & add a "0"
                if (cupertinoM < 10){
                    cupertinoMPos = singleMPos;
                    textSize(20);
                    fill(255);
                    text("0", mPos, 147);
                }

        //Beijing Variables
        var beijingH = (h + 13) % 12; //Current local hour
        var beijingM = m; //Current local minute

        var beijingHPos = hPos; //Hour position for Beijing
        var beijingMPos = mPos; //Minute position for Beijing

        //If Hour is a Single Digit, Move Hour Time To Be Closer to ":"
                if (beijingH < 10){ 
                    beijingHPos = singleHPos;
                }
        //If Minute is a Single Digit, Move Minute Time To Be Closer to ":" & add a "0"
                if (beijingM < 10){
                    beijingMPos = singleMPos;
                    textSize(20);
                    fill(255);
                    text("0", mPos, 181);
                }

        //Chennai Variables
        var chennaiH = (h + 10) % 12; //Current local hour
        var chennaiM = (m + 30) % 60; //Current local minute

        var chennaiHPos = hPos; //Hour position for Chennai
        var chennaiMPos = mPos; //Minute position for Chennai

        //If Hour is a Single Digit, Move Hour Time To Be Closer to ":"
                if (chennaiH < 10){ 
                    chennaiHPos = singleHPos;
                }
        //If Minute is a Single Digit, Move Minute Time To Be Closer to ":" & add a "0"
                if (chennaiM < 10){
                    chennaiMPos = singleMPos;
                    textSize(20);
                    fill(255);
                    text("0", mPos, 215);
                }

        //Chicago Variables
        var chicagoH = h-1; //Current local hour
        var chicagoM = (m) % 60; //Current local minute

        var chicagoHPos = hPos; //Hour position for Chicago
        var chicagoMPos = mPos; //Minute position for Chicago

        //If Hour is a Single Digit, Move Hour Time To Be Closer to ":"
                if (chicagoH < 10){ 
                    chicagoHPos = singleHPos;
                }
        //If Minute is a Single Digit, Move Minute Time To Be Closer to ":" & add a "0"
                if (chicagoM < 10){
                    chicagoMPos = singleMPos;
                    textSize(20);
                    fill(255);
                    text("0", mPos, 249);
                }

        //Abu Dhabi Variables
        var adH = (h + 9) % 12; //Current local hour
        var adM = (m) % 60; //Current local minute

        var adHPos = hPos; //Hour position for Abu Dhabi
        var adMPos = mPos; //Minute position for Abu Dhabi

        //If Hour is a Single Digit, Move Hour Time To Be Closer to ":"
                if (adH < 10){ 
                    adHPos = singleHPos;
                }
        //If Minute is a Single Digit, Move Minute Time To Be Closer to ":" & add a "0"
                if (adM < 10){
                    adMPos = singleMPos;
                    textSize(20);
                    fill(255);
                    text("0", mPos, 283);
                }

        //Adelaide Variables
        var adelaideH = (h + 6) % 12; //Current local hour
        var adelaideM = (m + 30) % 60; //Current local minute

        var adelaideHPos = hPos; //Hour position for Abu Dhabi
        var adelaideMPos = mPos; //Minute position for Abu Dhabi

        //If Hour is a Single Digit, Move Hour Time To Be Closer to ":"
                if (adelaideH < 10){ 
                    adelaideHPos = singleHPos;
                }
        //If Minute is a Single Digit, Move Minute Time To Be Closer to ":" & add a "0"
                if (adelaideM < 10){
                    adelaideMPos = singleMPos;
                    textSize(20);
                    fill(255);
                    text("0", mPos, 318);
                }


        //Displaying Pittsburgh Time
            //Local Time Label
                textSize(8);
                fill(150);
                noStroke();
                text('LOCAL TIME', width/2 - 70, 100);
            //Pittsburgh Label
                textSize(15);
                fill(255);
                text('Pittsburgh', width/2 - 70, 115);
            //Pittsburgh Time
                textSize(20);
                text(pittsburghH, pittsburghHPos, 115);
                text(pittsburghM, pittsburghMPos, 115);
                text(":", width/2 + 32, 114);
            //Line below Pittsburgh
                stroke(255); 
                strokeWeight(.5);
                line(width/2 - 74, 122, width / 2 + 73, 122);

        //Displaying Cupertino Time
            //- 3 Hrs Time Label
                textSize(8);
                fill(150);
                noStroke();
                text('-3HRS', width / 2 - 70, 132);
            //Cupertino Label
                textSize(15);
                fill(255);
                text('Cupertino', width / 2 - 70, 147);
            //Cupertino Time
                textSize(20);
                text(cupertinoH, cupertinoHPos, 147);
                text(cupertinoM, cupertinoMPos, 147);
                text(":", width/2 + 32, 146);
            //Line below Cupertino
                stroke(255); 
                strokeWeight(.5)
                line(width/2 - 74, 156, width / 2 + 73, 156);

        //Displaying Beijing Time
            // + 13 Hrs Time Label
                textSize(8);
                fill(150);
                noStroke();
                text('+13HRS', width / 2 - 70, 166);
            //Beijing Label
                textSize(15);
                fill(255);
                text('Beijing', width / 2 - 70, 181);
            //Beijing Time
                textSize(20);
                text(beijingH, beijingHPos, 181);
                text(beijingM, beijingMPos, 181);
                text(":", width / 2 + 32, 180);
            //Line below Beijing
                stroke(255); 
                strokeWeight(.5);
                line(width / 2 - 74, 190, width / 2 + 73, 190);

        //Displaying Chennai Time
            // + 10:30 Hrs Time Label
                textSize(8);
                fill(150);
                noStroke();
                text('+10:30HRS', width/2 - 70, 200);
            //Chennai Label
                textSize(15);
                fill(255);
                text('Chennai', width/2 - 70, 215);
            //Chennai Time
                textSize(20);
                text(chennaiH, chennaiHPos, 215);
                text(chennaiM, chennaiMPos, 215);
                text(":", width / 2 + 32, 214);
            //Line below Chennai
                stroke(255); 
                strokeWeight(.5);
                line(width / 2 - 74, 224, width/2 + 73, 224);


        //Displaying Chicago Time
            // - 1Hrs Time Label
                textSize(8);
                fill(150);
                noStroke();
                text('-1HRS', width/2 - 70, 234);
            //Chennai Label
                textSize(15);
                fill(255);
                text('Chicago', width/2 - 70, 249);
            //Chennai Time
                textSize(20);
                text(chicagoH, chicagoHPos, 249);
                text(chicagoM, chicagoMPos, 249);
                text(":", width / 2 + 32, 248);
            //Line below Chicago
                stroke(255); 
                strokeWeight(.5);
                line(width / 2 - 74, 258, width/2 + 73, 258);

        //Displaying Abu Dhabi Time
            // +9Hrs Time Label
                textSize(8);
                fill(150);
                noStroke();
                text('+9Hrs', width / 2 - 70, 268);
            //Abu Dhabi Label
                textSize(15);
                fill(255);
                text('Abu Dhabi', width / 2 - 70, 283);
            //Abu Dhabi Time
                textSize(20);
                text(adH, adHPos, 283);
                text(adM, adMPos, 283);
                text(":", width / 2 + 32, 282);
            //Line below Abu Dhabi
                stroke(255); 
                strokeWeight(.5);
                line(width / 2 - 74, 293, width / 2 + 73, 293);

        //Displaying Adelaide Time
            // +9Hrs Time Label
                textSize(8);
                fill(150);
                noStroke();
                text('+15:30Hrs', width / 2 - 70, 303);
            //Abu Dhabi Label
                textSize(15);
                fill(255);
                text('Adelaide', width / 2 - 70, 318);
            //Abu Dhabi Time
                textSize(20);
                text(adelaideH, adelaideHPos, 318);
                text(adelaideM, adelaideMPos, 318);
                text(":", width / 2 + 32, 317);
                pop();
}


function makeBorder(){
    //phone outline
    noFill();
    stroke(60, 59, 59);
    strokeWeight(6);
    rect(width/2, height/2, 159, 323, 15)

    //notch
    fill(60, 59, 59);
    rect(width/2, 48, 81, 11, 10);

    //fill(61, 60, 60);
    //ringer
    rect(239.5, 94, 1.6, 11.9);
    //Volume Up
    rect(239.5, 123, 1.6, 23.75);
    //Volume Down
    rect(239.5, 149, 1.6, 23.75);

    //Lock
    rect(400.5, 137, 1.6, 38)
   
    //Notch Hardware (Left to Right)
    fill(88, 89, 91);
    ellipseMode(CENTER);
    ellipse(289, 48, 5);
    ellipse(301, 48, 5);
    ellipse(340, 48, 5);
}

function makePhone(){
    //phone background
    push();
    rectMode(CENTER);
    fill(100)
    rect(width/2, height/2, 159, 323, 15);
    pop();
    //calls the app images on the home screen
    push()
    rectMode(CENTER);
    makeSnapchat();
    makeMusic();
    makeInsta();
    makeClock();
    pop();
}


function makeSnapchat(){
    //loads the snapchat app for the homescreen
    push();
    translate(width/2 - 60, 85)
    scale(0.25);
    image(snapPic, 0, 0)
    pop();
}

function makeMusic(){
    //loads the music app for the homescreen
    push();
    
    translate(width/2 + 6, 85)
    scale(0.25);
    image(musicPic, 0, 0)

    pop();
}

function makeInsta(){
    //loads the insta app for the homescreen
    push();
    
    translate(width/2 - 60, 160)
    scale(0.25);
    image(instaPic, 0, 0)

    pop();
}

function makeClock(){
    //loads the clock app for the homescreen
    push();
    
    translate(width/2 + 6, 160)
    scale(0.25);
    image(clockBG, 0, 0)
    pop();

    push()
    translate(width/2 + 1.5, 155);
    scale(0.15);
    //function that makes the clock work
    clockMove();
}


function clockMove(){
    //controls the clock on the homescreen to work
    angleMode(DEGREES);

    var h = hour();
    var m = minute();
    var s = second();

    push()
    noFill();

    translate(200, 200);
    rotate(-90);

    strokeWeight(5);
    var hStart = map(h % 12, 0, 12, -90, 360);
    arc(0, 0, 220, 220, 0, hStart)

    strokeWeight(4);
    var mStart = map(m, 0, 60, 0, 360);
    arc(0, 0, 240, 240, 0, mStart)

    stroke(0);
    strokeWeight(2);
    var sStart = map(s, 0, 60, 0, 360);
    arc(0, 0, 260, 260, 0, sStart)
    pop()
}

Kimberlyn Cho and Sarah Kang – Final Project

finalproj

/*Kimberlyn Cho
ycho2@andrew.cmu.edu
Sarah Kang
sarahk1@andrew.cmu.edu
Section C
Final Project
*/

/*INSTRUCTIONS:
(SARAH KANG) OPENING PAGE

(KIMBERLYN CHO) SCENE 1: 
    -use MOUSE to choose ingredient and click in bowl to place ingredient
    -press ENTER to mix ingredients
    -use MOUSE to add salt and pepper as desired and ENTER to mix again
(KIMBERLYN CHO) SCENE 2:
    -use LEFT and RIGHT arrow keys to move frying pan
    -dumpling is fully fried once "FINISH" button pops up
(SARAH KANG) SCENE 3: 
    -use DOWN arrow key to plate/start
    -use keys 'y, u, m' to finish and eat
*/


//KIMBERLYN CHO GLOBAL VARIABLES\\
//ingredient image links
var ingredients = [
    "https://i.imgur.com/kbiRJNO.png?3",
    "https://i.imgur.com/BqKFhGa.png?3",
    "https://i.imgur.com/X37RSp0.png?2",
    "https://i.imgur.com/1hN8NJV.png?5",
    "https://i.imgur.com/zlwmpov.png?3"];
//current ingredient selection
var currIngredient;
//content of bowl
var texts = [];
//shifting frying pan positions
var xpos = 200;
var ypos = 150;
//dumpling location on frying pan
var dxpos = 200;
//measure frying time (number of frying pan shifts)
var frytime = 1;
//toggle to control when scene3 is drawn
var updatedraw = true;
//toggles to control scenes
var toggle = true;
var toggle2 = true;
//drag effect of dumpling on frying pan
var shift = false;
var startTime = -1000;
var startXPos;
var transitionLength = 1.0;
var direction = 200;

function preload() {
    //KIMBERLYN CHO IMAGES
    //loading images into variables
    mushroom = loadImage(ingredients[0]);
    greenonion = loadImage(ingredients[1]);
    steak = loadImage(ingredients[2]);
    salt = loadImage(ingredients[3]);
    pepper = loadImage(ingredients[4]);

    //SARAH KANG IMAGES
    var plate = "https://i.imgur.com/kvmDFlr.jpg";
    var chopsticks = "https://i.imgur.com/cWrGYFq.jpg";
    var dinnertime = "https://i.imgur.com/QCuDyQk.jpg";
    var dumps = "https://i.imgur.com/wIrlEFb.jpg";
    var hand1 = "https://i.imgur.com/MmOZHtp.jpg"
    var hand2 = "https://i.imgur.com/FBDI4yN.jpg"
    var hand3 = "https://i.imgur.com/bYtuDzt.jpg"
    var kid = "https://i.imgur.com/BiavReL.jpg"
    var finaldump = "https://i.imgur.com/0ZQYp9Y.jpg"
        Img = loadImage(plate);
        Img2 = loadImage(chopsticks);
        Img3 = loadImage(dinnertime);
        Img4 = loadImage(dumps);
        Img5 = loadImage(hand1);
        Img6 = loadImage(hand2);
        Img7 = loadImage(hand3);
        Img8 = loadImage(kid);
        Img9 = loadImage(finaldump);
}

function setup() {
    createCanvas(400, 300);
    background(0);
    scene0setup();
}

function draw() {
    scene1();
    //scene3
    if (updatedraw == false) {
        background(0);
        scene2();
        toggle2 == false;
    };
}

//START PAGE (SARAH KANG)
function scene0setup() {
    //plate
    image(Img, 0, 50);
    //title
    textSize(60);
    textFont('Arial');
    textAlign(CENTER);
    stroke(255);
    strokeWeight(3);
    fill(0);
    text("\"DUMPLING\"", 200, 150);
    //chopsticks
    image(Img2, 200, 0);
    //start button
    noFill();
    rect(320, 260, 60, 24);
    textSize(10);
    strokeWeight(0.4);
    text("START", 350, 276);
}

//SCENE 1: ADDING AND MIXING FILLING (KIMBERLYN CHO)

//SCENE 1 BACKGROUND
function scene1setup() {
    //bowl
    background(0);
    noStroke();
    fill(240);
    ellipse(width / 2, height / 2, 200);
    fill(255);
    ellipse(width / 2, height / 2, 100);
    stroke(0);
    noFill();
    ellipse(width / 2, height / 2, 180);
    stroke(255);
    //next button
    rect(320, 260, 60, 24);
    textSize(10);
    strokeWeight(0.4);
    textAlign(CENTER);
    text("NEXT", 350, 276);
    //ingredient selection
    image(mushroom, 10, 10);
    image(greenonion, 10, 60);
    image(steak, 12, 130);
    image(salt, 340, 20);
    image(pepper, 337, 90);

    fill(255);
    textAlign(LEFT);
    textSize(10);
    text("1. click ingredient of choice, then place it in bowl with another click", 20, 280);
    text("2. to mix, press the ENTER key", 20, 290);
}   

//SCENE 1 INTERACTION
function scene1() {
    //updating ingredient selection based on where user clicks
    if (mouseIsPressed) {
        fill(0);
        if (mouseX > 10 & mouseX < 60 &&
            mouseY > 15 && mouseY < 55) {
            currIngredient = "\"mushroom\""
        };
        if (mouseX > 10 & mouseX < 60 &&
            mouseY > 65 && mouseY < 120) {
            currIngredient = "\"green onion\""
        };
        if (mouseX > 10 & mouseX < 60 &&
            mouseY > 130 && mouseY < 160) {
            currIngredient = "\"beef\""        
        };
        if (mouseX > 340 & mouseX < 370 &&
            mouseY > 20 && mouseY < 70) {
            currIngredient = "\"salt\""
        };
        if (mouseX > 340 & mouseX < 360 &&
            mouseY > 95 && mouseY < 140) {
            currIngredient = "\"pepper\""
        };
    };
}
//*see mousepressed fuction for code that updates and draws content of bowl
//*see keypressed function for code that mixes content of bowl

//SCENE 2: FRYING DUMPLING

//SCENE 2 BACKGROUND
function scene2() {
    //frying pan
    noStroke();
    fill(240);
    ellipse(xpos, ypos, 200);
    rectMode(CENTER);
    rect(xpos, 250, 25, 100);
    fill(255);
    ellipse(xpos, ypos, 150);
    fill(0);
    text("\"DUMPLING\"", dxpos, ypos);
    fill(255);
    textAlign(LEFT);
    textSize(12);
    text("press left and right arrow keys to fry until ready to plate!", 20, 20);

    //indication of when dumpling is done frying
    if  (frytime > 5) {
        stroke(255);
        noFill();
        strokeWeight(1);
        rectMode(CORNER);
        rect(320, 260, 60, 24);
        textSize(10);
        strokeWeight(0.4);
        textAlign(CENTER);
        text("PLATE", 350, 276);
        noLoop();
    } else {
        stroke(255);
        noFill();
        strokeWeight(1);
        rectMode(CORNER);
        rect(320, 260, 60, 24);
        textSize(10);
        strokeWeight(0.4);
        textAlign(CENTER);
        text("NEXT", 350, 276);
    };
    //updating position of dumpling on frying pan with a drag effect
    if (shift & startTime > 0) {
        var currentTime = millis()/1000 - startTime;
        var slide = map(currentTime, 0, transitionLength, 0, 1);
        dxpos = lerp(dxpos, direction, slide);
        if (currentTime > transitionLength) {
            shift = false;
        };
    };
}

//SCENE 2 INTERACTION
//**see keypressed function for code that shifts frying pan

function scene3setup() {
    background(0);
    //plate with silverware
    image(Img3, 0, 10);
    //instruction
    fill(255);
    textAlign(LEFT);
    textSize(12);
    text("press down arrow to plate, then type the letter that pops up", 15, 285);
}

function mousePressed() {
//INTERACTION PART OF SCENE 1 CONT.
    //constraining ingredients to bowl
    if (mouseX > 150 & mouseX < 250 &&
        mouseY > 100 && mouseY < 200) {
        //updating array to store content of bowl even if ingredients are added after mix
        texts.push(currIngredient);
        //adding selected ingredient into bowl
        text(currIngredient, mouseX, mouseY);
    };

//SCENE TRANSITIONS
    //start to scene 1
    if ((mouseX > 320 & mouseX < 380 && mouseY > 260 && mouseY < 280) && (toggle == true)) {
        scene1setup();
    };
    //scene 1 to scene 2
    if ((mouseX > 320 & mouseX < 380 && mouseY > 260 && mouseY < 280) && (toggle == false)) {
        updatedraw = false;
    };

    //scene 2 to scene 3
    if ((mouseX > 320 & mouseX < 380 && mouseY > 260 && mouseY < 280) && (frytime > 5)) {
        updatedraw = true;
        scene3setup()
    };

}

function keyPressed() {
//INTERACTION PART OF SCENE 1 CONT.
    //mixing content of bowl
    if (keyCode === ENTER) {
        //clearing bowl
        scene1setup();
        //redrawing contents of bowl with randomization to mimic mixing
        for (var i = 0; i < texts.length; i++) {
            fill(0);
            text(texts[i], floor(random(150, 250)), floor(random(100, 200)));
        };
        toggle = false;
    };

//INTERACTION PART OF SCENE 2
    //shifting pan and dumpling position
    if (keyCode == LEFT_ARROW) {
        //updating frying pan location
        xpos = 150;
        //initiating drag effect on dumpling
        shift = true;
        direction = 150;
        startTime = millis()/1000;
        startXPos = xpos;
        frytime +=1;
        toggle2 = false;
    };
    if (keyCode == RIGHT_ARROW) {
        //updating frying pan location
        xpos = 250;
        //initiating drag effect on dumpling
        direction = 250;
        shift = true;
        startTime = millis()/1000;
        startXPos = xpos;
    };

//INTERACTION PART OF SCENE 3
    //keypress to start
    if (keyCode == DOWN_ARROW) {
            image(Img4, 145, 115); //dumpling
            stroke(255);
            textSize(20);
            text("\"Y\"", 300, 40);
    }
    //sequence of typing
    if (keyCode == 'y') {
    }
    if (keyCode == 'u') {
    }
    if (keyCode == 'm') {        
    }
}
function keyTyped() {
    if (key === 'y') {
        stroke(255);
        textSize(20);
        text("\"U\"", 300, 250);
    }
    if (key === 'u') {
        stroke(255);
        textSize(20);
        text("\"M\"", 75, 250);
    }    
    if (key === 'm') {
        //kid thumbs up
        image(Img8, 10, 10);
        //plate of dumplings
        image(Img9, 125, 100);
        noStroke();
        fill(0);
        rect(0, 270, 400, 30);
        fill(255, 194, 194);
        textAlign(CENTER);
        textSize(20);
        text("time to eat!", 200, 285);
    }  
}

For our final project, we were inspired by the classic Nintendo game, Cooking Mama and wanted to recreate an unconventional, more conceptual version of the cooking experience. We were also inspired by the abstract approach in representing visuals often seen in high fashion, such as Off-White; the dumpling being cooked is represented by the literal word “dumpling”. We struggled a lot with putting our scenes together and working out the transitions, but after some compromise and editing, we were able to put together a fun game.

Claire Lee – Final Project

Instructions: move the net with the mouse and click on the litter to remove it from the ocean! Make sure not to harm any of the sea creatures in the process. Collect as many points as possible in one minute.

finalproject

/* Claire Lee
15-104 Section B
seoyounl@andrew.cmu.edu
Final Project */

var bubbles = [];
var fish = [];
var bottle = [];
var seaTurtle = [];
var candyWrapper = [];
var candyColors = ["pink", "red", "gold", "plum", "dodgerblue"];
var score = 0;
var time = 60;
// arrays to store objects

function setup() {
    createCanvas(600, 240);
    frameRate(10);

    for (i = 0; i < 50; i++) {
        bubbleX = random(width);
        bubbleY = random(height);
        bubbles[i] = blowBubbles(bubbleX, bubbleY);
    }

    for (k = 0; k < 10; k++) {
        fishX = random(width);
        fishY = random(0, 220);
        fish[k] = makeFish(fishX, fishY);
    }

    for (j = 0; j < 6; j++) {
        bottleX = random(width);
        bottleY = random(20, 140);
        bottle[j] = makeBottle(bottleX, bottleY);
    }

    for (l = 0; l < 4; l++) {
        seaTurtleX = random(width);
        seaTurtleY = random(60, 140);
        seaTurtle[l] = makeSeaTurtle(seaTurtleX, seaTurtleY);
    }

    for (m = 0; m < 4; m++) {
        candyWrapperX = random(width); 
        candyWrapperY = random(20, 140);
        candyWrapper[m] = makeCandyWrapper(candyWrapperX, candyWrapperY);
    }
}

function keyPressed() {
    if (time == 0 & keyCode === 32) {
        bubbles = [];
        fish = [];
        bottle = [];
        seaTurtle = [];
        candyWrapper = [];
        score = 0;
        time = 60;
        setup();
        loop();
    }
    // pressing space bar will restart the game
}

function draw() {
    background(140, 225, 255);
    showBubbles();
    showFish();
    showBottle();
    showSeaTurtle(); 
    showCandyWrapper();
    showSand();

    netW = 40;
    netH = 20;
    netX = mouseX;
    netY = mouseY;
    if (netX > width / 2) {
        netX = width / 2;
    }

    if (netY > height - 20) {
        netY = height - 20;
    }

    if (frameCount % 10 == 0 & time > 0) {
        time--;
    } // using frameCount to create a 60s timer

    if (time == 0) {
        noStroke();
        fill(120, 205, 235, 120);
        rect(0, 0, width, height);
        noStroke();
        fill(0);
        textSize(15);
        text('The End!', (width / 2) - 50, height / 2 - 30);
        text('Final Score: ' + score, width / 2 - 70, height / 2 - 10);
        text('Press SPACE to restart', width / 2 - 90, 3 * height / 4);
    } // end of game

    if(time != 0) {
        stroke(0);
        strokeWeight(2);
        line(netX, 0, netX, netY - (netH / 2));
        stroke(255);
        strokeWeight(1);
        fill(255, 255, 255, 100);
        arc(netX, netY, netW, netH, HALF_PI, -HALF_PI);
        ellipse(netX, netY, netW / 4, netH);

        noStroke();
        fill(0);
        textSize(15);
        text('Score: ' + score, 500, 230);
        text('Time Remaining: ' + time + ' s', 325, 230);
    } // during game: code for user-controlled net and bottom text
}

var sandSpeed = 0.0004;

function showSand() {
    var sand = 0.01;
    stroke(235, 220, 180);
    strokeWeight(2);
    beginShape();
    for (var i = 0; i < width; i++) {
        var x = (i * sand) + (millis() * sandSpeed);
        var s = map(noise(x), 0, 1, 190, 210);
        line(i, s, i, height);
        // using map() and noise() function to generate randomized sand terrain
    }
    endShape();
}

function blowBubbles (bubbleX, bubbleY) {
    // making bubble object
    var bubble = {
        x: bubbleX,
        y: bubbleY,
        radius: random(8, 12),
        speed: -0.5,
        float: floatBubbles, 
        draw: drawBubbles 
    }
    return bubble;
}

function floatBubbles() {
    // making the bubbles move horizontally and float
    this.x += (this.speed * 2);
    this.y += this.speed;
    if (this.x <= 0) {
        this.x = width;
    }
    if (this.y <= 0) {
        this.y = height;
    } // bubbles "pop" when they reach top of screen
}

function drawBubbles() {
    push();
    translate(this.x, this.y);
    strokeWeight(1);
    stroke(255, 255, 255, 90);
    fill(160, 245, 255, 90);
    ellipse(1, 10, this.radius, this.radius);
    noStroke();
    fill(255, 255, 255, 90);
    ellipse(-1, 8, 2, 2);
    pop();
}

function showBubbles() {
    for (i = 0; i < bubbles.length; i++) {
        bubbles[i].float();
        bubbles[i].draw();
    }
}

function makeFish(fishX, fishY) {
    // making the fish object
    var fish = {
        fx: fishX,
        fy: fishY,
        fishspeed: random(-3, -8),
        fishmove: moveFish,
        fishcolor: random(100, 150),
        fishdraw: drawFish
    }
    return fish;
}

function moveFish() {
    this.fx += this.fishspeed;
    if (this.fx <= -10) {
        this.fx += width;
        this.fy = random(height);
        this.fspeed = random(-3, -8);
    }
}

function drawFish() {
    var tailLength = 8;
    noStroke();
    fill(this.fishcolor);
    ellipse(this.fx, this.fy, 12, 6);
    triangle(this.fx + (tailLength / 2), this.fy, this.fx + tailLength, this.fy - 3, this.fx + tailLength, this.fy + 3);
}

function showFish() {
    for (i = 0; i < fish.length; i++) {
        fish[i].fishmove();
        fish[i].fishdraw();
    }
}

function makeBottle(bottleX, bottleY) {
    //making the bottle object
    var bottle = {
        bx: bottleX,
        by: bottleY,
        bspeed: random(-3, -8), 
        bmove: moveBottle,
        bdraw: drawBottle,
    }
    return bottle;
}

function moveBottle() {
    this.bx += this.bspeed;
    if (this.bx <= -15) {
        this.bx += width;
        this.by = random(20, 160);
        this.bspeed = random(-3, -8);
        if (time != 0) score -= 20;
        // if bottle isn't clicked, points are deducted
    }
}

function drawBottle() {
    bottleW = 7;
    bottleH = 14;
    bottleCapW = 3;
    bottleCapH = 2;
    stroke(255);
    strokeWeight(0.5);
    fill(255, 255, 255, 50);
    rect(this.bx, this.by, bottleW, bottleH);
    triangle(this.bx, this.by, this.bx + (bottleW / 2), this.by - 4, this.bx + bottleW, this.by);
    noStroke();
    fill(0, 160, 0);
    rect(this.bx + (bottleW / 2) - (bottleCapW / 2), this.by - 5, bottleCapW, bottleCapH);
    noStroke();
    fill("turquoise");
    rect(this.bx, this.by, bottleW, bottleH / 3);
}

function showBottle() {
    for (i = 0; i < bottle.length; i++) {
        bottle[i].bmove();
        bottle[i].bdraw();
    }
}

function makeSeaTurtle (seaTurtleX, seaTurtleY) {
    //making the sea turtle object
    var seaTurtle = {
        stx: seaTurtleX,
        sty: seaTurtleY,
        stspeed: random(-3, -6),
        stmove: swimSeaTurtle,
        stdraw: hatchSeaTurtle
    }
    return seaTurtle;
}

function swimSeaTurtle() {
    this.stx += this.stspeed;
    if (this.stx <= -15) {
        this.stx += width;
        this.sty = random(60, 140);
        this.stspeed = random(-3, -6);
    }
}

function hatchSeaTurtle() {
    shellW = 30;
    shellH = 20;
    stroke(110, 150, 75);
    strokeWeight(0.5);
    fill(175, 200, 90);
    ellipse(this.stx - 17, this.sty - 3, 12, 5);
    arc(this.stx - 5, this.sty, 7, 15, HALF_PI - QUARTER_PI, PI + HALF_PI - QUARTER_PI, CHORD);
    arc(this.stx + 10, this.sty, 5, 15, HALF_PI - QUARTER_PI, PI + HALF_PI - QUARTER_PI, CHORD);
    triangle(this.stx + (shellW / 4) + 3, this.sty - 4, this.stx + (shellW / 2) + 3, this.sty - 5, this.stx + (shellW / 4) + 3, this.sty);
    stroke(70, 125, 30);
    strokeWeight(0.5);
    fill(110, 150, 75);
    arc(this.stx, this.sty, shellW, shellH, -PI, 0, CHORD);
    noStroke();
    fill(50, 105, 10);
    ellipse(this.stx - 18, this.sty - 2, 1, 1);
    stroke(175, 200, 90);
    strokeWeight(0.5);
    noFill();
    arc(this.stx, this.sty - 8, 15, 5, 0, PI, OPEN);
    line(this.stx - 4, this.sty - 5.5, this.stx - 6, this.sty - 0.5);
    line(this.stx + 4, this.sty - 5.5, this.stx + 6, this.sty - 0.5);
}

function showSeaTurtle() {
    for (i = 0; i < seaTurtle.length; i++) {
        seaTurtle[i].stmove();
        seaTurtle[i].stdraw();
    }
}

function makeCandyWrapper() {
    // making the caondy wrapper object
    var colorRand = Math.floor(random(0,5));
    var candyWrapper = {
        cwx: candyWrapperX,
        cwy: candyWrapperY,
        cwspeed: random(-3, -6),
        cwmove: floatCandyWrapper,
        cwdraw: tossCandyWrapper,
        cwcolor: candyColors[colorRand]
    }
    return candyWrapper;  
}

function floatCandyWrapper() {
    this.cwx += this.cwspeed;
    if (this.cwx <= -15) {
        this.cwx += width;
        this.cwy = random(60, 140);
        this.cwspeed = random(-3, -6);
        if (time != 0) score -= 5;
        // if candy wrapper isn't clicked, points are deducted
    }
}

function tossCandyWrapper() {
    candyW = 7;
    candyH = 5;
    noStroke();
    fill(this.cwcolor);
    ellipse(this.cwx, this.cwy, candyW, candyH);
    triangle(this.cwx - (candyW / 2), this.cwy, this.cwx - (candyW / 2) - 3, this.cwy - 2, this.cwx - (candyW / 2) - 3, this.cwy + 2);
    triangle(this.cwx + (candyW / 2), this.cwy, this.cwx + (candyW / 2) + 3, this.cwy - 2, this.cwx + (candyW / 2) + 3, this.cwy + 2);
}

function showCandyWrapper() {
    for (i = 0; i < candyWrapper.length; i++) {
        candyWrapper[i].cwmove();
        candyWrapper[i].cwdraw();
    }
}

function mousePressed() {
    //making the objects "reset" when pressed on by mouse
    for(var i = 0; i < fish.length; i++) {
        if (dist(netX - 7, netY, fish[i].fx, fish[i].fy) < 10 & mouseIsPressed && time != 0) {
            fish[i].fx = width;
            fish[i].fy = random(height);
            fish[i].fspeed = random(-3, -8);
            score -= 5;
            // if fish are clicked, points are deducted
        }
    }

    for(var i = 0; i < seaTurtle.length; i++) {
        if (dist(netX - 7, netY, seaTurtle[i].stx, seaTurtle[i].sty) < 15 & mouseIsPressed && time != 0) {
            seaTurtle[i].stx = width;
            seaTurtle[i].sty = random(height);
            seaTurtle[i].stspeed = random(-3, -6);
            score -= 25;
            // if turtle is clicked, points are deducted
        }
    }

    for(var i = 0; i < bottle.length; i++) {
        if (dist(netX - 7, netY, bottle[i].bx, bottle[i].by) < 10 & mouseIsPressed && time != 0) {
            bottle[i].bx = width;
            bottle[i].by = random(height);
            bottle[i].bspeed = random(-3, -6);
            score += 20;
        }
    }

    for(var i = 0; i < candyWrapper.length; i++) {
        if (dist(netX - 7, netY, candyWrapper[i].cwx, candyWrapper[i].cwy) < 10 & mouseIsPressed && time != 0) {
            candyWrapper[i].cwx = width;
            candyWrapper[i].cwy = random(height);
            candyWrapper[i].cwspeed = random(-3, -8);
            score += 5;
        }
    }
}

For my final project, I made an interactive game that’s supposed to serve as a commentary on the ocean pollution crisis. The game itself is a metaphor: we have a limited amount of time to solve the problem of ocean pollution, and finding a solution requires awareness and active work.

Working more with using objects in code and figuring out how to make objects interactive was a really fun process. I also liked testing out my game and thinking about the ways to improve the user-oriented aspects of my game design.

Ghalya Alsanea – Final Project

Project Interface

I created a smart watch interface for someone with anxiety and panic disorder. It reads someone’s pulse and breath rate and can detect when someone is having a panic attack. I used real-time biometric data from a pulse sensor and a wind sensor and hooked it up to an ardiuno, then I used serial communication to send the data and trigger & display different calming graphics using p5.js. I will go through the project interface and design first, then I will show you how I went about doing it.

Regular Interface

The regular display when someone is not having a panic attack consists of a clock, the date, and a BPM graphic that reacts to your pulse.

Interface during a panic attack

Depending on the kind of panic attack you are having, there are 3 modes/techniques. The good thing is that any of these techniques would work during a panic attack. I weighted certain techniques to be triggered based on someone’s sensor data. But for the purpose of the scope of this project, and because this requires more knowledge, time and research, I used keyboard keys to to demonstrate the different interfaces.

  1. Deep Breathing Technique (for when you’re hyperventilating)
  2. Counting Technique (for when your heart rate cannot be controlled)
  3. Distraction Technique (for when you need to be distracted from your overwhelming thoughts)

These techniques and methods were based on my research on medical articles here, here, and here.

Deep Breathing
The blobs are made to soothe you and help you concentrate on your breathing. As you breath in, the blobs expand, and as you exhale the deflate.
Counting

“Recently I found that trying to focus on something else such as counting numbers… works, because it requires concentration.”

Melanie Luxenberg
The idea here is you count to 5 over and over again, until you naturally realize that your slowly calming down. Before you know it, as you’re trying to fill the canvas with tick marks, you’ll start to feel much better!
Distraction
This is a game interface to act as a distraction technique. Note the large “IT’S OK!” text as an emphasis on how it’s okay that you lost, and you can try again, since it’s not about winning. This way, you can play for as long as you want and/or until you feel calmer.

Proof of Concept

Testing the pulse sensor and how it communicates using the p5.js serial port.
Testing the wind sensor and how it communicates with the p5.js graphics
Going through all the different types of interfaces and how they react to the user’s biometric sensor data and interactions.

How to Implement it

I used the step by step guide provided by NYU’s physical computing department here to learn how to use ardiuno and p5.js together. If you follow that guide, you can know how to download and run the project. You will need to download the P5.js complete library, the P5.serialserver, and the ardiuno software.

Circuit Sketch of how to hook up the sensors to an ardiuno

You can download all the files you need to run the project here:

galsanea_FinalProject_allFiles

WordPress Version

If you just want to see the p5.js interaction, I modified the project to run on WordPress using mouse X location to simulate breathing pattern, and mouse Y location to simulate pulse patterns.

Use the following to replace the sensor data interaction:

keys:
b:
breathing blobs interface
c: counting interface
g: game interface
any other key: regular clock interface

mouse:
x:
move mouse in x directions to simulate breathing
y: move mouse in y directions to simulate heart beat

sketch

/* 
Ghalya Alsanea
Section B
galsanea@andrew.cmu.edu
Final Project

normal mode:clock
            date
            sensor data: heart rate with beating heart

panic mode: deep breathing --- moving blob objects reacting to your breathing
            distraction technique --- use pingpong-like game to distract you
            counting technique --- count till a number to calm down
*/
// ----------------------------------------------------------------------------
// overall global variables
var proportion = 500;   //canvas proportion
var MX, MY;             //mouse x and y locations
// ----------------------------------------------------------------------------
//SENSOR global variables
var pulseSensor;        //global variable for pulse sensor data
var breathSensor;       //global variable for breath sensor data
var toggle = false;     //toggle to help sort the incoming sensor data from the serial
var pulseData = [];     //array container for incoming pulse data
// ----------------------------------------------------------------------------
// SERIAL global variables
var serial;                     // variable to hold an instance of the serialport library
var portName = 'COM11';         // fill in your serial port name here
var inData;                     // for incoming serial data. NOTE: it comes a byte at a time
var options = {baudrate: 9600}; // change the data rate to whatever you wish
// ----------------------------------------------------------------------------
// CLOCK global variables
var diameter = proportion * 0.75;   //clock diameter
//configure clock hands based on clock diameter
var secHand = diameter * 0.45;
var minHand = diameter * 0.35;
var hourHand = diameter * 0.2;
// ----------------------------------------------------------------------------
// BLOBS global variables
var blobs = [];     //used to store instances of blob objects
var change = 0;     //stores the rate of rotation and the y coordinate for noise later
var colorsPalette;  //color palette for blob objects
var numBlobs = 120; //number of blobs to make
// ----------------------------------------------------------------------------
// GAME global variables
var ballX;          //ball x position
var ballY;          //ball y position
var ballSize = 40;  //diameter of ball
var xvel;           //horizontal velocity
var yvel;           //vertical velocity
var d;              //distance between mouse and circle center
var dangerX;        //danger zone
var ballColor;      //what to color the ball
var score;          //keep score of hits
var scores = [];    //keep a list of all final scores to get the highest score
// ----------------------------------------------------------------------------
// CLICKER global variables
var gClickCount = 0;    //mouse click counter
var inc = 20;           //x increments
var yInc = 100;         //y increments         
var yPos1 = 25          //top y position
var yPos2 = yPos1 + 50; //bottom y position
// ----------------------------------------------------------------------------

function setup() {
    createCanvas(proportion, proportion);
    // setupSerial();   ..comment out to disable serial
    setupBlobs();
    setupGame();
}

// ----------------------------------------------------------------------------

function draw() {
    //constrain mouse x and y to within the canvas
    MX = constrain(mouseX, 0, width);
    MY = constrain(mouseY, 0, height);

    if (key === "b") {
        //draw the blobs
        displayBlobs();
    } else if (key === "g") {
        //draw the game
        drawGame();
    } else if (key === "c") {
        //draw clicker
        displayClicker();
    } else {
        clockDisplay();
    }
}

////////////////////////////////////////////////////////////////////////
//**************************** SERIAL ********************************//
////////////////////////////////////////////////////////////////////////

function setupSerial() {
    serial = new p5.SerialPort();       // make a new instance of the serialport library
    serial.on('data', serialEvent);     // callback for when new data arrives
    serial.open(portName);              // open the serial port
}

function serialEvent() {
    // read a byte from the serial port, convert it to a number
    // NOTE: the inData comes in a byte at a time (so range is 0-255)
    inData = serial.read();
    // sort the data incoming by distributing every other byte to their respective variable
    // serial data comes in 2 at a time, so using a toggle you can store the data correctly
    if (toggle) {
        pulseSensor = inData;
    } else {
        breathSensor = inData;
    }
    toggle = !toggle;
}


////////////////////////////////////////////////////////////////////////
//*********************** base/CLOCK Display *************************//
////////////////////////////////////////////////////////////////////////

function clockDisplay() {
    background(255, 228, 225);    //mistyRose
    noStroke();

    //show the date
    dateDisplay();

    //for testing: using mouse data instead of sensor data
    var size = map(MY, 0, height, 20, 40);
    var BPM = 88;

    // // for serial: map heart size to pulse sensor data
    // var size = map(pulseSensor, 0, 255, 20, 40);
    // 
    // //calculate beats per min (BPM) based on average pulse data
    // pulseData.push(pulseSensor);
    // //total pulse values read
    // var total = 0;
    // //find the total of all the readings
    // for(var i = 0; i < pulseData.length; i++) {
    //     total += pulseData[i];
    // }
    // //divide by the length of the data to get the average
    // var avg = total / pulseData.length;
    // //map the average number onto a range of BPM hear rates
    // var BPM = map(avg, 0, 255, 70, 130);
    //
    // //only read the last 100 readings
    // if (pulseData.length > 100) {
    //     pulseData.shift();
    // }

    //show the heart rate data
    heartDisplay(size, BPM);

    //draw from the center of the canvas
    push();
    translate(width / 2, height / 2);

    //draw clock base
    fill(255);
    circle(0, 0, diameter);

    //convert time to angles
    //subtract half a circle so the clock starts at the top, rather than 6 o'clock 
    var s = map(second(), 0, 60, 0, TWO_PI) - PI; 
    var m = map(minute(), 0, 60, 0, TWO_PI) - PI;
    //the % takes the remainder of the hours/12, which makes the dif between AM/PM 
    var h = map((hour() % 12), 0, 12, 0, TWO_PI) - PI;
 
    //draw the hands
    drawClockHands(s, secHand, 1);  //seconds hand
    drawClockHands(m, minHand, 3);  //minutes hand
    drawClockHands(h, hourHand, 5); //hour hand

    //draw the ticks every 6 degrees (6 * 60 = 360)
    for (i = 0; i < 360; i += 6) {
        //make every 5th tick thicker
        if(i % 30 == 0) {
            strokeWeight(2);
        } else {
            strokeWeight(1);
        }
        push();
        rotate(radians(i));
        line(0, diameter / 2, 0, secHand + 5);
        pop();
    }
    pop()
}

//draw the clock hands 
function drawClockHands(angle, hand, weight) {
    stroke("PaleVioletRed");
    push();
    rotate(angle);
    strokeWeight(weight);
    line(0, 0, 0, hand);
    pop();
}

//display today's date
function dateDisplay() {
    var d = day();
    var m = month();
    var y = year();
    fill("PaleVioletRed");
    textAlign(LEFT);
    textSize(18);
    text(m + "." + d + "." + y, 10, height - 15);
}

//display heart rate with heart graphic
function heartDisplay(size, BPM) {
    textSize(12);
    textAlign(CENTER);
    text(floor(BPM), width - 50, height - 60);
    drawHeart(width - 50, height - 50, size);
}

function drawHeart(x, y, size) {
    beginShape();
    vertex(x, y);
    bezierVertex(x - size / 2, y - size / 2, x - size, y + size / 3, x, y + size);
    bezierVertex(x + size, y + size / 3, x + size / 2, y - size / 2, x, y);
    endShape(CLOSE);
}

////////////////////////////////////////////////////////////////////////
//******************** breathing BLOB Display ************************//
////////////////////////////////////////////////////////////////////////

function setupBlobs() {
    //create color paletter for the blob objects
    colorsPalette = [color(146, 167, 202,30),
                    color(186, 196, 219,30),
                    color(118, 135, 172,30),
                    color(76, 41, 81,30),
                    color(144, 62, 92,30),
                    color(178, 93, 119,30),
                    color(215, 118, 136,30),
                    color(246, 156, 164,30)];

    // create new blob object for every num of blobs
    for (var i = 0; i < numBlobs; i++){
        var temp = makeBlob(i + 0.1, i, i * random(90),
            colorsPalette[floor(random(colorsPalette.length))]);
        blobs.push(temp);
    }
}

function displayBlobs() {
    background(240, 200, 240, 30);
    //for testing: using mouse data instead of sensor data
    var addToRadius = map(MX, 0, width, 0, 100);

    // //for serial: map the breathing sensor data to blob radius
    // //it's inversely mapped because when you breath out it decreases and vice versa
    // var addToRadius = map(breathSensor, 255, 0, 0, 100);

    for(var i = 0; i < blobs.length; i++){
        blobs[i].r = i + addToRadius;
        blobs[i].draw(change);
    }
    //create rotation change and noise change
    change+=0.01;
}

function makeBlob(radius, roughness, angle, color){
    var blob = {r: radius,      // radius of blob
                x: width / 2,   // x position of blob
                y: height / 2,  // y position of blob
                rough: roughness, // magnitude of how much the circle is distorted
                ang: angle,     // how much to rotate the circle by
                c: color,       // color of the blob
                draw: drawBlob, //draw the blobs
    }
    return blob;
}

function drawBlob(change) {
    noStroke(); 
    fill(this.c);   //color to fill the blob

    push();     
    translate(this.x, this.y);  //move to xpos, ypos
    rotate(this.ang + change);  //rotate by this.angle+change
    
    beginShape();   //begin a shape based on the vertex points below
    
    //create vertex points
    var off = 0;
    for (var i = 0; i < TWO_PI; i += 0.1) {
        var offset = map(noise(off, change), 0, 1, -this.rough, this.rough);
        var R = this.r + offset;
        var x = R * cos(i);
        var y = R * sin(i);
        vertex(x, y);
        off += 0.1;
    }
    endShape(); 
    pop();
}

////////////////////////////////////////////////////////////////////////
//************************ GAME display ******************************//
////////////////////////////////////////////////////////////////////////

function setupGame() {
    noStroke();
    //ball starts at the center of the canvas
    ballX = width / 2;
    ballY = height / 2;
    //define danger zone
    dangerX = width / 4;
    //set x and y velocity
    xvel = 5;
    yvel = 0;
    //set score
    score = 0;
}

function drawGame() {
    background (100);

    //draw the hitting paddle
    strokeWeight(5);
    stroke(255);
    line(mouseX, mouseY + 10, mouseX, mouseY - 10);

    //instructions
    noStroke();
    fill(255);
    textAlign(LEFT);
    writeText(20, "Let's play a game!", 20, 25);
    writeText(12, "Don't let the ball go left of the screen", 20, 50);

    //define the playing area
    fill(0);
    rect(width / 2, 0, width / 2, height);

    //warn if ball is too close to edge
    if(ballX - ballSize / 2 <= dangerX & xvel < 0){
        ballColor = "yellow"; 
    } else {
        ballColor = "white";
    }

    //draw the circle
    fill (ballColor);
    circle(ballX, ballY, ballSize);
    
    //makes the ball move in x and y directions
    ballX = ballX + xvel;
    ballY = ballY + yvel;

    //When it hits the right edge of the canvas,
    //it reverses horizontal velocity
    if (ballX >= width - ballSize / 2) {
        xvel = -1 * xvel;
    }

    //when it hits the bottom or top edge of the canvas,
    //it reverses vertical velocity
    if (ballY <= ballSize / 2) {
        yvel = -1 * yvel;
    } else if (ballY >= height - ballSize / 2) {
        yvel = -1 * yvel;
    }

    //find the distance between ball center and mouse
    d = dist(mouseX, mouseY, ballX, ballY);

    //if  (1) the mouse hits the edge of the ball 
    //and (2) the mouse is on the left half of the canvas
    //and (3) the x velocity is negative
    //then switch directions and get a random yvel. 
    if (d <= ballSize / 2 & mouseX <= width / 2 && xvel < 0){
        yvel = random(-3, 3);
        xvel = -1 * xvel;
        yvel = -1 * yvel;
        score = score + 1;            
    }

    //keep current score
    fill("white");
    writeText(30, score, width - 50, height - 50);

    //reset game if th eball went to far out the canvas
    if (ballX < -150) {
        resetGame();
    }
}

//reset game
function resetGame() {
    scores.push(score)          //push final score to scores
    background (0, 51, 102);
    fill(255);
    textAlign(LEFT);
    writeText(80, "IT'S OK!", 10, height / 4);
    writeText(30, "click to try again", 20, height / 2);
    //cureent score
    writeText(20, "score: " + score, 20, height / 2 + 50);
    //highest score
    writeText(20, "best score: " + max(scores), 20, height / 2 + 100);
    
    if (mouseIsPressed){
        setupGame();
    }
}

function writeText(size, words, x, y) {
    textSize(size);
    text(words, x, y);
}

////////////////////////////////////////////////////////////////////////
//*********************** COUNT display ******************************//
////////////////////////////////////////////////////////////////////////

function displayClicker() {
    background("pink");

    if (gClickCount === 0) {
        noStroke();
        fill(0);
        textAlign(LEFT);
        textSize(20);
        text("tap me and count to fives...", 20, 50);
    } 

    stroke (0);
    strokeWeight(3);

    //everytime you click, draw a line
    for(var i = 0; i < gClickCount; i++){
        //variable to show which line the tickmarks are on
        var a = floor((i * inc + inc - 1) / width);

        //x and y locations depending on which line
        var x = i * inc + inc - width * a;
        var y1 = yPos1 + a * yInc;
        var y2 = yPos2 + a * yInc;

        if ( (i + 1) % 5 === 0) {
            //draw diagonal line every fifth tick
            line(x, y1, x - 5 * inc, y2);
        } else {
            //otherwise draw a straight line
            line(x, y1, x, y2);
        }    
    }
}


function mousePressed() {
    //everytime you click, you increase the amount of times the loop runs
    gClickCount++;
}

function keyPressed() {
    //reset gClickCount to zero whenever a keyboard key is pressed
    gClickCount = 0;
}

Charmaine Qiu – Final Project


sketch

For my final project, I created an ice-cream decoration game inspired by cooking games that I play as a child. The game allows users to drag around and place the decorations on top of the ice-cream, which changes flavors when being pressed. This project encourage me to create my own illustrations and incorporated them to my code to create a game. I had fun figuring out the interactions the players can explore on the canvas. 

//Charmaine Qiu
//Section E
//charmaiq@andrew.cmu.edu
//Final project

//Array of links for icecream images
var iceLinks = [
    "https://i.imgur.com/RQVGPb3.png",
    "https://i.imgur.com/3NURceZ.png",
    "https://i.imgur.com/gk7Jbfc.png",
    "https://i.imgur.com/KzfjvaF.png",
    "https://i.imgur.com/RyVDAlq.png",
    "https://i.imgur.com/OoltZSF.png",
    "https://i.imgur.com/DLpHcUh.png",
    "https://i.imgur.com/Jd0Hx3a.png",
    "https://i.imgur.com/G4zwiYV.png"]
//initialize index and empty array
var iceindex = 0;
var ice = [];
//set dragging for each decorationa as false
var bdragging = false;
var kdragging = false;
var sdragging = false;
var chdragging = false;
var fdragging = false;
var codragging = false;

var finishgame = false;
//set initial coordinate values for the decorations
var bx = 460;
var by = 60;
var kx = 540;
var ky = 100;
var sx = 490;
var sy = 140;
var chx = 160;
var chy = 50;
var fx = 160;
var fy = 125;
var cox = 80;
var coy = 100;
//initiallize offset values
var offsetX;
var offsetY;
var offsetX2;
var offsetY2;
var offsetX3;
var offsetY3;
var offsetX4;
var offsetY4;
var offsetX5;
var offsetY5;
var offsetX6;
var offsetY6;

function preload(){
    //preload the icecream images and store them into the arrays
      for (var i = 0; i < iceLinks.length; i++){
        //store the index from the links to the new arrays
        ice[i] = loadImage(iceLinks[i]);
      }
      //preload the images for decorations
      blueberry = loadImage("https://i.imgur.com/BbD4FQd.png");
      kiwi = loadImage("https://i.imgur.com/pwxFml9.png");
      strawberry = loadImage("https://i.imgur.com/qwPbN2l.png");
      cookie = loadImage("https://i.imgur.com/Eo3TG0F.png");
      cherry = loadImage("https://i.imgur.com/FI8MUOH.png");
      flower = loadImage("https://i.imgur.com/PDusCa3.png");
      star = loadImage("https://i.imgur.com/09DBgK0.png");
}

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

function draw() {
    background(255, 217, 214);
    ellipse(width/2, height, 280, 250);
    //draw table
    drawCurtain();
    //draw the text for instructions
    fill(255);
    text("Drag to decorate", 240, 90);
    text("Click to change a flavor", 210, 120);
    //draw the icream base
    imageMode(CENTER);
    image(ice[iceindex], width / 2, 375);
    //draw image of decorations
    image(blueberry, bx, by);
    image(kiwi, kx, ky);
    image(strawberry, sx, sy);
    image(cookie, cox, coy);
    image(cherry, chx, chy);
    image(flower, fx, fy);
    //when the decoration is being dragged, apply offset values
    if (bdragging){
        bx = mouseX + offsetX;
        by = mouseY + offsetY;
    }
    if(kdragging){
        kx = mouseX + offsetX2;
        ky = mouseY + offsetY2;
    }
    if(sdragging){
        sx = mouseX + offsetX3;
        sy = mouseY + offsetY3;
    }
    if(chdragging){
        chx = mouseX + offsetX4;
        chy = mouseY + offsetY4;
    }
    if(fdragging){
        fx = mouseX + offsetX5;
        fy = mouseY + offsetY5;
    }
    if(codragging){
        cox = mouseX + offsetX6;
        coy = mouseY + offsetY6;
    }
    //draw the buttons for finish and reset
    fill(255);
    noStroke();
    ellipse(80, 500, 100, 40);
    ellipse(80, 550, 100, 40);
    fill(217, 118, 115);
    textSize(20);
    text("Finish!", 52, 506);
    text("Reset", 52, 556);
    //when the game is finished, draw shape that displays the icecream
    if(finishgame){
        finishGame();
    }
}

function mousePressed(){
    //when the mouse is pressed on the icecream
    if(mouseY > 250 & mouseX > 180 && mouseX < 420) {
      //set the current ice equal to current index
      var currentIce = iceindex;
      while(currentIce === iceindex){
        //randomnize the next image
        iceindex = floor(random(0, 8));
      }
    }
    //when the mouse is pressed on each decoration, dragging becomes true and
    //offset values are applied
    if(mouseX > bx - 21 & mouseX < bx + 21 && mouseY > by - 17 && mouseY < by + 17){
        bdragging = true;
        offsetX = bx - mouseX;
        offsetY = by - mouseY;
    }
    if(mouseX > kx - 28 & mouseX < kx + 28 && mouseY > ky - 27 && mouseY < ky + 27){
        kdragging = true;
        offsetX2 = kx - mouseX;
        offsetY2 = ky - mouseY;
    }
    if(mouseX > sx - 32 & mouseX < sx + 32 && mouseY > sy - 40 && mouseY < sy + 40){
        sdragging = true;
        offsetX3 = sx - mouseX;
        offsetY3 = sy - mouseY;
    }
    if(mouseX > chx - 26 & mouseX < chx + 26 && mouseY > chy - 34 && mouseY < chy + 34){
        chdragging = true;
        offsetX4 = chx - mouseX;
        offsetY4 = chy - mouseY;
    }
    if(mouseX > fx - 22 & mouseX < fx + 22 && mouseY > fy - 29 && mouseY < fy + 29){
        fdragging = true;
        offsetX5 = fx - mouseX;
        offsetY5 = fy - mouseY;
    }
    if(mouseX > cox - 49 & mouseX < cox + 49 && mouseY > coy - 86 && mouseY < coy + 86){
        codragging = true;
        offsetX6 = cox - mouseX;
        offsetY6 = coy - mouseY;
    }
    //when the finish button is pressed, the icecream is displayed
    if(mouseX > 30 & mouseX < 130 && mouseY > 480 && mouseY < 520){
        finishgame = true;
        finishGame();
    }
    //reset the game when button is pressed
    reset();
}

function mouseReleased(){
    //when the mouse is released, dragging is nolonger happening
    bdragging = false;
    kdragging = false;
    sdragging = false;
    chdragging = false;
    codragging = false;
    fdragging = false;
}

function reset(){
    //set cordinates to initial values when the reset button is pressed
    if(mouseX > 30 & mouseX < 130 && mouseY > 530 && mouseY < 570){
        bx = 460;
        by = 60;
        kx = 540;
        ky = 100;
        sx = 490;
        sy = 140;
        chx = 160;
        chy = 50;
        fx = 160;
        fy = 125;
        cox = 80;
        coy = 100;
        finishgame = false;
    }
}

function drawCurtain(){
    //when game is not finished, display the curtains
    if (!finishgame){
        ellipseMode(CENTER);
        fill(217, 118, 115);
        rect(0 ,0, width, 190);
        for(var i = 25; i < width; i+= 50){
            fill(217, 118, 115);
            ellipse(i, 190, 50, 50);
        }
    }
}

function finishGame(){
        //creates shape that covers the background to display ice
        fill(255, 217, 214);
        beginShape();
        vertex(0, 0);
        vertex(0, 525);
        vertex(158, 525);
        vertex(158, 150);
        vertex(440, 150);
        vertex(440, 600);
        vertex(600, 600);
        vertex(600, 0);
        vertex(0, 0);
        endShape();
        //draws the text Enjoy
        fill(217, 118, 115);
        text("Enjoy~", 280, 140);
        //displays stars
        frameRate(8);
        starX = random(width);
        starY = random(height);
        starX2 = random(width);
        starY2 = random(height);
        image(star, starX, starY);
        image(star, starX2, starY2);
}

Min Ji Kim Kim & Kristine Kim – Final Project


sketch

Kristine and I created 2 interactive stories from the Bible since Christianity is something that we really value. We decided to create this as a method to teach children about the Bible while also letting them have fun.

Kristine worked on the “Feeding the 5000” story while I worked on the “Noah’s Ark” story. We met together throughout the week to make sure our two programs connected and worked smoothly which took the most time. I also took charge of securing of online images and creating the image arrays since I was working on the more image heavy portion and Kristine in turn worked on the menu screen and the character movement as well. We divided the work very evenly and made sure to consult with each other if we had problems or didn’t know how to do something.

The only thing you might need to know to play our game is to make sure to  completely go to the end of the path to the end of canvas width, since it goes to the next screen automatically once the character gets to the end.

We had so much fun creating this project!

// Kristine Kim & Min Ji Kim Kim
// Section D & A
// younsook & mkimkim
// Final Project

// -------------------------------- VARIABLES --------------------------------

// GENERAL
var texts = [];
var people = [];
var peopleImages = [];
var character;

// MENU
var screen = "menu"; //screen always set to menu when first opened
var Bible;
var leftFrame;
var rightFrame;

//FEEDING THE 5000
var food = [];
var foodImages = [];
var disciple;
var crowd1;
var crowd2;
var crowd3;

// NOAH'S ARK
var ark;
var rain = [];
var animalImages = [];
var animals = [];
var offsetX; // offsets for mouse drag later
var offsetY;

// --------------------------- PRELOAD IMAGES ----------------------------------

function preload() {
    var foodLinks = [];
    foodLinks[0] = 'https://i.imgur.com/I2GtIHI.png'; // fish
    foodLinks[1] = 'https://i.imgur.com/37fSZcK.png'; // bread
    foodLinks[2] = 'https://i.imgur.com/U5WevzT.png'; // basket
    for (var i = 0; i < foodLinks.length; i++) { // push urls into array
        foodImages[i] = loadImage(foodLinks[i]);
    }

    var animalLinks = [];
    animalLinks[0] = 'https://i.imgur.com/CBoFToW.png'; // giraffe
    animalLinks[1] = 'https://i.imgur.com/mOTcpZg.png'; // koala
    animalLinks[2] = 'https://i.imgur.com/cIjfUHk.png'; // monkey
    animalLinks[3] = 'https://i.imgur.com/ucc7kcb.png'; // elephant
    animalLinks[4] = 'https://i.imgur.com/n3MKNo5.png'; // lion
    animalLinks[5] = 'https://i.imgur.com/fcfIkPJ.png'; // bird
    animalLinks[6] = 'https://i.imgur.com/XhSjml7.png'; // turtle
    animalLinks[7] = 'https://i.imgur.com/bq0OgoB.png'; // zebra
    for (var i = 0; i < animalLinks.length; i++) { // push urls into array
        animalImages[i] = loadImage(animalLinks[i]);
    }

    var textLinks = [];
    textLinks[0] = 'https://i.imgur.com/JB4gpam.png'; // Andrew Intro
    textLinks[1] = 'https://i.imgur.com/CTLxAP2.png'; // Andrew End
    textLinks[2] = 'https://i.imgur.com/XIGb2J9.png'; // Noah Intro
    textLinks[3] = 'https://i.imgur.com/uEHleCT.png'; // Noah End
    textLinks[4] = 'https://i.imgur.com/ouDdHh0.png'; // Feed Instructions
    textLinks[5] = 'https://i.imgur.com/84dBAhN.png'; // Ark Instructions
    for (var i = 0; i < textLinks.length; i++) { // push urls into array
        texts[i] = loadImage(textLinks[i]);
    }

    var peopleLinks = [];
    peopleLinks[0] = 'https://i.imgur.com/jZVql8t.png'; // Noah
    peopleLinks[1] = 'https://i.imgur.com/EDZROu3.png'; // Andrew
    peopleLinks[2] = 'https://i.imgur.com/moPXqEi.png'; // Jesus
    for (var i = 0; i < peopleLinks.length; i++) { // push urls into array
        peopleImages[i] = loadImage(peopleLinks[i]);
    }

    Bible = loadImage('https://i.imgur.com/VKVcQg5.png');
    leftFrame = loadImage('https://i.imgur.com/XyGYYj0.jpg');
    rightFrame = loadImage('https://i.imgur.com/jpBO0LB.jpg');
    character = loadImage('https://i.imgur.com/BiTQr7u.png');
    ark = loadImage('https://i.imgur.com/kqvTedI.png');
    crowd1 = loadImage('https://i.imgur.com/Gz2HLDi.png');
    crowd2 = loadImage('https://i.imgur.com/l4SCJCo.png');
    crowd3 = loadImage('https://i.imgur.com/JON4rd1.png');
}

// -----------------------------------------------------------------------------

function setup() {
    createCanvas(600, 600);
    strokeJoin(MITER); // turtle code stroke join & cap settings
    strokeCap(PROJECT);
    for(var i = 0; i < 300; i++) {
        rain.push(drop()); // load raindrops into array (for Noah's ark story)
    }
}

// DRAWING SCREENS
function draw() {
    if(screen == "menu") {
        menuScreen();
    }
    else if (screen == "feedIntro") {
        feedIntroScreen();
    }
    else if (screen == "feed") {
        feedScreen();
    }
    else if (screen == "feedEnd") {
        feedEndScreen();
    }
    else if (screen == "arkIntro") {
        arkIntroScreen();
    }
    else if (screen == "ark") {
        arkScreen();
    }
    else if (screen == "arkEnd") {
        arkEndScreen();
    }
}

// ---------------------------------- MENU -------------------------------------

function menuScreen() {
    background("#0F7B96");
    
    //table
    noStroke();
    fill("#553C2A");
    rect(0, height - 120, width, 120);
    
    //book
    image(Bible, 80, 320);
    
    //draw & change frame color if selected
    //left frame
    fill(0);
    if(mouseX < 270 & mouseX > 40 && mouseY > 140 && mouseY < 320) {
        fill("#AEA4FA");
    }
    rect(40, 150, 230, 180);
    image(leftFrame, 50, 160);

    //right frame
    fill(0);
    if(mouseX < 560 & mouseX > 330 && mouseY > 140 && mouseY < 320) {
        fill("#5DED79");
    }
    rect(330, 150, 230, 180);
    image(rightFrame, 340, 160);
    
    //text
    fill(255);
    textAlign(CENTER);
    textFont("Trebuchet MS", 25);
    text("Select a Bible story", width / 2, 60);
    textSize(20);
    text("Jesus feeds the 5000", 155, 130);
    text("Noah's Ark", 450, 130);
}

// SELECT A STORY
function mouseClicked() {
    if (screen == "menu") {
        if(mouseX < 270 & mouseX > 40 && mouseY > 140 && mouseY < 320) {
            screen = "feedIntro"; // click on left frame
        }
        if(mouseX < 560 & mouseX > 330 && mouseY > 140 && mouseY < 320) {
            screen = "arkIntro"; // click on right frame
        }
    }

    if (screen == "feedEnd" || screen == "arkEnd") {
        if(mouseX > 500 & mouseY > 530 && mouseX < 600 && mouseY < 600) {
            screen = "menu"; // click on "menu" to go back to menu
        }
    }

    if (screen == "feedIntro") {
        if (mouseX > 500 & mouseY > 530 && mouseX < 600 && mouseY < 600) {
            screen = "feed"; // click "go help ->" to go to main story
        }
    }

    if (screen == "arkIntro") {
        if (mouseX > 500 & mouseY > 530 && mouseX < 600 && mouseY < 600) {
            screen = "ark"; // click "go help ->" to go to main story
        }
    }
}

// --------------------------- JESUS FEEDS THE 5000 ----------------------------

//STORY  INTRODUCTION
function feedIntroScreen() {
    background(255);
    //display Andrew, basket, text, and button to go to main story
    image(peopleImages[1], 50, height / 4);
    image(foodImages[2], 230, 350);
    image(texts[0], 250, 120);
    goHelpButton();
}

// MAIN STORY SCREEN
function feedScreen() {
    background("#a9c75f");
    maze1();
    createMainChar(); //display character 
    image(peopleImages[2], 330, 280); //display Jesus
    for (var i = 0; i < food.length; i++) { //draw food inside the array
        food[i].draw();
    }

    //display basket at mouse location 
    var scaleWidth = foodImages[2].width * 0.4; //scale basket image 
    var scaleHeight = foodImages[2].height * 0.4;
    var bkx = scaleWidth / 2; // offset from left corner
    var bky = scaleHeight / 2;
    image(foodImages[2], mouseX - bkx, mouseY - bky, scaleWidth, scaleHeight);

    //display instructions and crowds
    image(texts[4], 0, 530);
    image(crowd1, 230, 380);
    image(crowd2, 310, 5);
    image(crowd3, 350, 40);

    //when character reaches the end of the maze, reset character position
    //clear food array and go to end screen
    if (people[0].x > width) {
        people[0].x = 10;
        people[0].y = 410;
        food = [];
        screen = "feedEnd";
    }
}

//CODE FOR FOOD IMAGES
// create an object for food images 
function makeFood(x, y, type) {
    var foods = {x: x,
                y: y,
                type: type,
                draw: foodDraw}
    return foods;
}

function foodDraw() {
    if (this.type === 'fish') { //draw bread at mouse location
        image(foodImages[0], this.x, this.y);
    }
    else if (this.type === 'bread') { //draw fish at mouse location
        image(foodImages[1], this.x, this.y);
    }
}

// interaction keys to make fish and bread on canvas at mouse location
function keyPressed() {
    if(screen == "feed") { // create fish = press f
        if (keyCode == 70) {
            var fx = foodImages[0].width / 2; // offset from left corner
            var fy = foodImages[0].height / 2;
            newFood = makeFood(mouseX - fx, mouseY - fy, 'fish');
            food.push(newFood);
        }
        if (keyCode == 66) { // create bread = press b
            var bx = foodImages[1].width / 2; // offset from left corner
            var by = foodImages[1].height / 2;
            newFood = makeFood(mouseX - bx, mouseY - by, 'bread');
            food.push(newFood);
        }
    }
}

// create maze path using turtle graphics
function maze1() {
    var ttl1 = makeTurtle(20, 480);
        ttl1.setColor(color("#a6927b"));
        ttl1.setWeight(40);
        ttl1.forward(150);
        ttl1.left(90);
        ttl1.forward(100);
        ttl1.left(90);
        ttl1.forward(100);
        ttl1.right(90);
        ttl1.forward(280);
        ttl1.right(90);
        ttl1.forward(240);
        ttl1.right(90);
        ttl1.forward(80);
        ttl1.right(90);
        ttl1.forward(120);
        ttl1.left(90);
        ttl1.forward(100);
        ttl1.left(90);
        ttl1.forward(250);
        ttl1.right(90);
        ttl1.forward(200);
        ttl1.left(90);
        ttl1.forward(120);
        ttl1.left(90);
        ttl1.forward(70);
        ttl1.right(90);
        ttl1.forward(20);
    //stage for Jesus
    fill("#a6927b");
    rect(280, 250, 100, 100);
}

// STORY ENDING SCREEN 
function feedEndScreen() {
    background(169, 146, 119);
    //display Andrew, text and menu button
    image(peopleImages[1], 50, height / 4);
    image(texts[1], 250, 100);
    menuButton(); // go back to "menu" button
}

// ------------------------------ NOAH'S ARK -----------------------------------

// STORY INTRODUCTION
function arkIntroScreen() {
    background(255);
    //display Noah, text, ark and button to go to main story
    image(peopleImages[0], 50, height / 4);
    image(texts[2], 250, 130);
    image(ark, 250, 250);
    goHelpButton(); // button to go to main screen
}

// MAIN STORY SCREEN
function arkScreen() {
    // display background gradient
    var turquoise = color(16, 157, 172); 
    var navy = color(1,84,134);
    gradient(turquoise, navy);

    //display maze, ark, character and instructions
    maze2();
    image(ark, 370, 80);
    createMainChar();
    image(texts[5], 0, 550);

    // display animals
    for (var i = 0; i < animalImages.length; i++) {
        animals.push(makeAnimal(animalImages[i])); //push images into array
        animals[i].draw(); //draw animals
        // if the animal is moved, adjust that image location
        if (animals[i].moving) {
            animals[i].x = mouseX + offsetX;
            animals[i].y = mouseY + offsetY;
        }
    }

    // make it rain when pressing "r"
    if (keyIsDown(82)) {
        for (var r = 0; r < rain.length; r++) {
            rain[r].display();
            rain[r].falling();
        }
    }
    //when character reaches the end of the maze, reset character position,
    //clear animals array and go to end screen
    if (people[0].x > width) {
        people[0].x = 10;
        people[0].y = 410;
        animals = [];
        screen = "arkEnd";
    }
}

// to render background gradient for main story
function gradient(c1, c2) {
    noFill();
    noStroke();
    for (var x = 0; x < width; x++) {
        var mapColor = map(x, 0, width, 0, 2);
        var c = lerpColor(c1, c2, mapColor);
        stroke(c);
        line(x, 0, x, height);
    }
}

// create maze path using turtle graphics
function maze2() {
    var ttl2 = makeTurtle(20, 480);
        ttl2.setColor(color("#AF9879"));
        ttl2.setWeight(40);
        ttl2.forward(80);
        ttl2.left(90);
        ttl2.forward(400);
        ttl2.right(90);
        ttl2.forward(180);
        ttl2.right(90);
        ttl2.forward(150);
        ttl2.right(90);
        ttl2.forward(100);
        ttl2.left(90);
        ttl2.forward(120);
        ttl2.left(90);
        ttl2.forward(180);
        ttl2.right(90);
        ttl2.forward(170);
        ttl2.left(90);
        ttl2.forward(120);
        ttl2.left(90);
        ttl2.forward(300);
        ttl2.right(90);
        ttl2.forward(100);
}

// CODE FOR ANIMALS
// to click and drag animal image
function mousePressed() {
    for (var i = 0; i < animalImages.length; i++) {
        if (animals[i] == undefined) { // terminate if animals[i] is undefined
            return;
        }
        // check if mouse is on the animal image
        if (mouseX > animals[i].x & mouseX < animals[i].x + animalImages[i].width && 
            mouseY > animals[i].y && mouseY < animals[i].y + animalImages[i].height) {
            animals[i].moving = true;
            // if yes, track location of the mouse click to the corner of animal image
            offsetX = animals[i].x - mouseX;
            offsetY = animals[i].y - mouseY;
        }
    }
}

function mouseReleased() {
    //stop dragging animal
    for (var i = 0; i < animalImages.length; i++) {
        if (animals[i] != undefined) { //check if animals[i] is defined
            animals[i].moving = false;
        }
    }
}

// create an object for animal images
function makeAnimal(image) {
    var animal = {img: image,
                    x: random(10, 550),
                    y: random(10, 490),
                    moving: false,
                    draw: drawAnimal}
    return animal;
}

function drawAnimal() {
    image(this.img, this.x, this.y);
}

// CODE FOR RAIN
// create rain drop object
function drop() {
    var drop = {x: random(0, width),
                y: random(-5, height),
                len: random(5, 20),
                thick: random(1, 3),
                vel: random(5, 15),
                display: dropDisplay,
                falling: dropFalling}
    return drop;
}

// display rain drop
function dropDisplay() {
    noStroke();
    fill(255, 90);
    ellipse(this.x, this.y, this.thick, this.len);
}

// make raindrop fall
function dropFalling() {
    this.y += this.vel;
    if (this.y > height) {
        this.y = -this.len;
    }
}

//STORY END SCREEN
function arkEndScreen() {
    background(178, 153, 116);
    //display Noah, text and menu button
    image(peopleImages[0], 50, height / 4);
    image(texts[3], 250, 130);
    menuButton(); // go back to "menu" button

}

// -------------------------- CHARACTER & MOVEMENT -----------------------------

function createMainChar () {
    //create the character object
    people.push(makeCharacter());
    people[0].draw();

    //MOVE CHARACTER BY PRESSING ARROW KEYS
    if (keyIsPressed) {
        if (keyCode === UP_ARROW) {
            people[0].y -= 2;
        }
        else if (keyCode === DOWN_ARROW) {
            people[0].y += 2;
        }
        else if (keyCode === LEFT_ARROW) {
            people[0].x -= 2;
        }
        else if (keyCode === RIGHT_ARROW) {
            people[0].x += 2;
        }
    }
}

function makeCharacter() {
    var char = { x: 10, y: 410, draw: drawCharacter }
    return char;
}

function drawCharacter() {
    image(character, this.x, this.y);
}

// ----------------------------- EXTRA BUTTONS ---------------------------------

// create button to go back to menu after story ends
function menuButton() {
    textSize(20);
    fill(0)
    if(mouseX > 500 & mouseY > 530 && mouseX < 600 && mouseY < 600) {
        fill('#27AEE3'); // change color if mouse is hovering over
    }
    text('menu', 550, 550);
}

// create button to go to main story
function goHelpButton() {
    textSize(20);
    fill(0);
    if(mouseX > 500 & mouseY > 530 && mouseX < 600 && mouseY < 600) {
        fill('#27AEE3'); // change color if mouse is hovering over
    }
    text('go help -> ', 550, 550);
}

// ------------------------- TURTLE CODE FOR MAZES -----------------------------

function turtleLeft(d){this.angle-=d;}function turtleRight(d){this.angle+=d;}
function turtleForward(p){var rad=radians(this.angle);var newx=this.x+cos(rad)*p;
var newy=this.y+sin(rad)*p;this.goto(newx,newy);}function turtleBack(p){
this.forward(-p);}function turtlePenDown(){this.penIsDown=true;}
function turtlePenUp(){this.penIsDown = false;}function turtleGoTo(x,y){
if(this.penIsDown){stroke(this.color);strokeWeight(this.weight);
line(this.x,this.y,x,y);}this.x = x;this.y = y;}function turtleDistTo(x,y){
return sqrt(sq(this.x-x)+sq(this.y-y));}function turtleAngleTo(x,y){
var absAngle=degrees(atan2(y-this.y,x-this.x));
var angle=((absAngle-this.angle)+360)%360.0;return angle;}
function turtleTurnToward(x,y,d){var angle = this.angleTo(x,y);if(angle< 180){
this.angle+=d;}else{this.angle-=d;}}function turtleSetColor(c){this.color=c;}
function turtleSetWeight(w){this.weight=w;}function turtleFace(angle){
this.angle = angle;}function makeTurtle(tx,ty){var turtle={x:tx,y:ty,
angle:0.0,penIsDown:true,color:128,weight:1,left:turtleLeft,
right:turtleRight,forward:turtleForward, back:turtleBack,penDown:turtlePenDown,
penUp:turtlePenUp,goto:turtleGoTo, angleto:turtleAngleTo,
turnToward:turtleTurnToward,distanceTo:turtleDistTo, angleTo:turtleAngleTo,
setColor:turtleSetColor, setWeight:turtleSetWeight,face:turtleFace};
return turtle;}

Timothy Liu — Final Project


tcliu-FINALPROJECT

Welcome to Crossy Chicken, my final project for 15-104! A tribute to the original Frogger, Crossy Chicken involves a chicken trying to reach her beloved egg. But to get there, the chicken must first cross two busy streets as well as a perilous bridge; can you get her back to her egg?

To play, use the WASD keys (W = Up, S = Down, A = Left, D = Right) to direct the chicken and follow the instructions on screen! Make sure you dodge all the cars and don’t let the chicken’s feet touch the water!

I had a fantastic time coding this project, and I’m proud of how I distributed the work. I broke my project down into phases, and I worked through them in the following order:

  1. Character movement
  2. Introducing cars
  3. Adding roads
  4. Creating the river and bridge
  5. Adding the next 3 rows of cars
  6. Losing screen
  7. Winning screen
  8. Reset button (ENTER)
  9. Start button (SPACE)
  10. Adding sounds

In the end, I wound up with what I felt was a very polished and complete game. I’m really happy that I was able to build a visually pleasing game with fun and retro graphics while using a multitude of elements from p5js. From objects, to loops, to sound, my game really has a bit of everything in it. The soundtrack (which is actually the original Frogger music!) adds an element of excitement and nostalgia, and the losing and winning sound effects complete the experience for the gamer. I hope you have a great time playing!

(*Note 2: as is typically the case with p5js, the sound is sometimes a bit glitchy on Chrome and certain other browsers. However, the implementation of sound is all correct, as can be seen in my code.)

// Timothy Liu
// 15-104, Section C
// tcliu@andrew.cmu.edu
// FINAL PROJECT: Crossy Chicken, the game!

var showStart = true; // start off the game showing the start screen

var myChar; // the player (the chicken)
var charSpeed = 2; // speed player moves at

var borderW = 40; // border used in all popup screens

var firstCars = []; // arrays of cars (from bottom of screen to top)
var secondCars = [];
var thirdCars = [];
var fourthCars = [];

// road characteristics
var roadWidth = 100;
var roadLine = 2;
var carSpacing;

// river and bridge characteristics
var riverStart = 243; // identifies the top of the river
var riverWidth = 100; // identifies the width of the river
var riverEnd = riverStart + riverWidth; // identifies the bottom of the river
var bridgeWidth = 30; // width of the bridge
var bridgeMiddle = riverStart + riverWidth / 2; // the top part of the middle bridge segment
var randomBridgeStart; // where the first segment of the bridge starts 
var randomBridgeLength; // how long the middle segment of the bridge is

// variables that help draw/locate the egg and nest
var eggLocY = 50;
var eggW = 12;
var eggH = 16;
var nestLocY = 60;
var nestShadowY = 57;
var nestW = 30;
var nestH = 18;

// sound
var soundtrack;
var winSound;
var loseSound;

// preload all sounds
function preload() {

    // background track
    soundtrack = loadSound("https://courses.ideate.cmu.edu/15-104/f2019/wp-content/uploads/2019/12/froggertrack.wav");
    soundtrack.setVolume(0.5);

    // losing jingle
    loseSound = loadSound("https://courses.ideate.cmu.edu/15-104/f2019/wp-content/uploads/2019/12/lose.wav");
    loseSound.setVolume(0.6);

    // winning jingle
    winSound = loadSound("https://courses.ideate.cmu.edu/15-104/f2019/wp-content/uploads/2019/12/win.wav");
    winSound.setVolume(0.9);

}

function setup() {

    createCanvas(600, 600); // create the canvas

    soundtrack.play(); // play the frogger soundtrack that's been preloaded

    myChar = makeCharacter(); // makes the character

    carSpacing = random(30, 45); // determines spacing between cars; ensures they don't hit each other (but varies the spacing randomly)
    
    randomBridgeStart = random(50, 500); // determines the random location of the bridge
    randomBridgeLength = random(60, 250); // determines the random length of the bridge

    firstCarSetup(); // defines properties for all the different rows of cars
    secondCarSetup(); // second row of cars (2nd from bottom)
    thirdCarSetup(); // third row of cars (3rd from bottom)
    fourthCarSetup(); // top row of cars

}

function draw() {

    background(87, 168, 84); // grass floor

    noStroke();

    drawFirstRoad(); // draw in the bottom road
    drawRiver(); // draw in the river
    drawSecondRoad(); // draw in the top road

    drawNest(); // draw in the nest

    // draw in and move the character!
    myChar.draw();
    myChar.move();

    // draws in the cars on the road using drawCars()
    showFirstCars();
    showSecondCars();
    showThirdCars();
    showFourthCars();

    characterWins(); // function that runs when the character reaches the egg and wins
    characterLoses(); // function that identifies when the character loses

    startScreen(); // starting screen shows right at the beginning

}

// the starting screen that shows rules and game info
function startScreen() {

    // right at start of game, this statement is true UNTIL SPACE IS PRESSED
    if (showStart === true) {

        noLoop();

        // the frame/popup screen
        fill(235, 131, 131);
        rect(width / 6, height / 6, width * 2 / 3, height * 2 / 3);

        fill(255);
        rect(width / 6 + borderW / 2, height / 6 + borderW / 2, width * 2 / 3 - borderW, height * 2 / 3 - borderW);

        // Start screen
        fill(0);
        textSize(20);

        textAlign(CENTER); // center the text
        textFont('Avenir'); // text font

        // starting screen text: the intro!
        textStyle(NORMAL);
        text("Welcome to Crossy Chicken!", width / 2, 225);
        text("Watch out for cars, and", width / 2, 255);
        text("don't let your feet get wet!", width / 2, 285);

        text("Use the WASD keys to move, and", width / 2, 345); 
        textStyle(BOLD);
        text("press SPACE to get started!", width / 2, 375);

    }

}

// triggers to start and reset the game
function keyPressed() {

    if (keyCode === 32) { // if SPACEBAR is pressed, let the game begin!
        showStart = false;
        loop();
    }
    if (keyCode === ENTER) { // if ENTER is pressed, the level restarts!
        restart(); // restart function resets the stage
    }

    return false; // prevents the webpage from moving when arrows are pressed

}

// this function initializes all variables and then reruns the setup function, effectively restarting the game
function restart() {

    firstCars = []; // initialize all of the car arrays so they can be redrawn/setup
    secondCars = [];
    thirdCars = [];
    fourthCars = [];

    setup(); // re-run setup, which resets the stage
    loop(); // start running the code again!

}

// define all character properties (the chicken)
function makeCharacter() {

    return {
        x: 300,
        y: 550,
        c: 255,
        w: 25,
        h: 40,
        r: 25,
        hX: 300,
        hY: 490,
        hW: 15,
        hH: 20,
        eyeC: 0,
        eyeW: 2,
        beakC: color(252, 202, 3),
        crownW: 5,
        crownH: 10,
        crownC: color(207, 58, 58),
        footW: 7,
        footH: 4,
        draw: drawCharacter,
        move: moveCharacter
    }

}

// uses the obj characteristics and var myChar to draw the chicken
function drawCharacter() {

    var headOffset = 18;
    var eyeOffsetX = 3;
    var eyeOffsetY = 21;
    var beakOffsetX = 4;
    var beakOffsetY = 19;
    var beakOffsetY2 = 15;
    var crownOffsetY = 27;
    var footOffset = 5;

    // red crown on head
    fill(myChar.crownC);
    arc(myChar.x, myChar.y - crownOffsetY, myChar.crownW, myChar.crownH, PI, TWO_PI);

    // feet
    fill(myChar.beakC);
    arc(myChar.x - footOffset, myChar.y, myChar.footW, myChar.footH, 0, PI);   
    arc(myChar.x + footOffset, myChar.y, myChar.footW, myChar.footH, 0, PI);  

    // head and body   
    fill(myChar.c);
    arc(myChar.x, myChar.y, myChar.w, myChar.h, PI, TWO_PI);
    arc(myChar.x, myChar.y - headOffset, myChar.hW, myChar.hH, PI, TWO_PI);

    // eyes
    fill(myChar.eyeC);
    ellipse(myChar.x - eyeOffsetX, myChar.y - eyeOffsetY, myChar.eyeW, myChar.eyeW);
    ellipse(myChar.x + eyeOffsetX, myChar.y - eyeOffsetY, myChar.eyeW, myChar.eyeW);

    // beak
    fill(myChar.beakC);
    triangle(myChar.x - beakOffsetX, myChar.y - beakOffsetY, myChar.x + beakOffsetX, myChar.y - beakOffsetY,
            myChar.x, myChar.y - beakOffsetY2);

}

// this function allows the character to move
function moveCharacter() {

    if (keyIsDown(65)) { // if pressing the A key
        myChar.x -= charSpeed; // move the character's x position left
        if (myChar.x + myChar.r < 0) { // if the character moves off the screen, have it wrap to the other side
            myChar.x = width + myChar.r;
        }
    }

    if (keyIsDown(68)) { // if pressing the D key 
        myChar.x += charSpeed; // move the character's x position right
        if (myChar.x - myChar.r > width) { // if the character moves off the screen, have it wrap to the other side
            myChar.x = -myChar.r;
        }
    }

    if (keyIsDown(87)) { // if pressing the W key
        myChar.y -= charSpeed; // move the character's y position up
    }

    if (keyIsDown(83)) { // if pressing the S key
        myChar.y += charSpeed; // move the character's y position down
        if (myChar.y + myChar.r > height) { // if it hits the bottom, don't let it go off the page
            myChar.y = height - myChar.r;
        }
    }

}

// what happens if the character reaches the egg
function characterWins() {

    // if the character reaches the egg successfully
    if (myChar.y > eggLocY - eggH / 2 & myChar.y < nestLocY + nestH / 2) {
        if (myChar.x > width / 2 - nestW / 2 && myChar.x < width / 2 + nestW / 2) {
            noLoop(); // game stops
            winningScreen(); // display winning screen
            winSound.play(); // play winning jingle!
        }
    }

}

// this function determines if the character loses using two other functions: hit by car, or fall in river
function characterLoses() {

    characterHitByCar(); // function if myChar is hit by a car
    characterFallsInRiver(); // function if myChar falls into the river

}

// if the character is hit by a car
function characterHitByCar() {

    // if char is hit by first row of cars
    for (var i = 0; i < firstCars.length; i++) {

        var carMiddleX = firstCars[i].x + (firstCars[i].w / 2);
        var carMiddleY = firstCars[i].y + (firstCars[i].h / 2);

        var charMiddleX = myChar.x;
        var charMiddleY = myChar.y - (myChar.h / 2);

        // if the edges of the character fall within the edges of the car, you lose!
        if (charMiddleX - myChar.w / 4 < carMiddleX + firstCars[i].w / 2 & charMiddleX + myChar.w / 4 > carMiddleX - firstCars[i].w / 2
            && myChar.y > carMiddleY - firstCars[i].h / 2 && charMiddleY - myChar.h / 5 < carMiddleY + firstCars[i].h / 2) {
            noLoop(); // game stops
            losingScreenCar(); // show losing screen
            loseSound.play(); // losing sound plays
        }

    }

    // if char is hit by second row of cars
    for (var j = 0; j < secondCars.length; j++) {

        var carMiddleX = secondCars[j].x + (secondCars[j].w / 2);
        var carMiddleY = secondCars[j].y + (secondCars[j].h / 2);

        var charMiddleX = myChar.x;
        var charMiddleY = myChar.y - (myChar.h / 2);

        // if the edges of the character fall within the edges of the car, you lose!
        if (charMiddleX - myChar.w / 4 < carMiddleX + secondCars[j].w / 2 & charMiddleX + myChar.w / 4 > carMiddleX - secondCars[j].w / 2
            && myChar.y > carMiddleY - secondCars[j].h / 2 && charMiddleY - myChar.h / 5 < carMiddleY + secondCars[j].h / 2) {
            noLoop(); // game stops
            losingScreenCar(); // show losing screen
            loseSound.play(); // losing sound plays
        }

    }

    // if char is hit by third row of cars
    for (var k = 0; k < thirdCars.length; k++) {

        var carMiddleX = thirdCars[k].x + (thirdCars[k].w / 2);
        var carMiddleY = thirdCars[k].y + (thirdCars[k].h / 2);

        var charMiddleX = myChar.x;
        var charMiddleY = myChar.y - (myChar.h / 2);

        // if the edges of the character fall within the edges of the car, you lose!
        if (charMiddleX - myChar.w / 4 < carMiddleX + thirdCars[k].w / 2 & charMiddleX + myChar.w / 4 > carMiddleX - thirdCars[k].w / 2
            && myChar.y > carMiddleY - thirdCars[k].h / 2 && charMiddleY - myChar.h / 5 < carMiddleY + thirdCars[k].h / 2) {
            noLoop(); // game stops
            losingScreenCar(); // show losing screen
            loseSound.play(); // losing sound plays
        }

    }

    // if char is hit by fourth row of cars
    for (var l = 0; l < fourthCars.length; l++) {

        var carMiddleX = fourthCars[l].x + (fourthCars[l].w / 2);
        var carMiddleY = fourthCars[l].y + (fourthCars[l].h / 2);

        var charMiddleX = myChar.x;
        var charMiddleY = myChar.y - (myChar.h / 2);

        // if the edges of the character fall within the edges of the car, you lose!
        if (charMiddleX - myChar.w / 4 < carMiddleX + fourthCars[l].w / 2 & charMiddleX + myChar.w / 4 > carMiddleX - fourthCars[l].w / 2
            && myChar.y > carMiddleY - fourthCars[l].h / 2 && charMiddleY - myChar.h / 5 < carMiddleY + fourthCars[l].h / 2) {
            noLoop(); // game stops
            losingScreenCar(); // show losing screen (car version)
            loseSound.play(); // losing sound plays
        }
    }

}

// if the strays from the bridge and falls into the river
function characterFallsInRiver() {

    // if the character is crossing the river...
    if (myChar.y > riverStart & myChar.y < riverEnd) {

        // first segment of the bridge (vertical)
        if (myChar.y > bridgeMiddle + bridgeWidth && myChar.y < riverEnd) {

            // if char is outside the bridge boundaries
            if (myChar.x < randomBridgeStart || myChar.x > randomBridgeStart + bridgeWidth) {
                noLoop(); // game stops
                losingScreenRiver(); // show losing screen (splash version)
                loseSound.play(); // play losing sound
            }
        }
        
        // the bridge randomly goes left or right based on starting position...
        // if the bridge goes rightward:
        if (randomBridgeStart < width / 2) {

            // second segment of the bridge (horizontal)
            if (myChar.y > bridgeMiddle & myChar.y < bridgeMiddle + bridgeWidth) {
                // if char is outside the boundaries of the horizontal bridge segment
                if (myChar.x < randomBridgeStart || myChar.x > randomBridgeStart + randomBridgeLength + bridgeWidth) {
                    noLoop(); // game stops
                    losingScreenRiver(); // show losing screen (splash version)
                    loseSound.play(); // play losing sound
                }
            }
        
            // last segment of the bridge (vertical)
            if (myChar.y > riverStart & myChar.y < bridgeMiddle) {
                if (myChar.x < randomBridgeStart + randomBridgeLength || myChar.x > randomBridgeStart + randomBridgeLength + bridgeWidth) {
                    noLoop(); // game stops
                    losingScreenRiver(); // show losing screen (splash version)
                    loseSound.play(); // play losing sound
                }
            }

        // if the bridge goes leftward:
        } else {

            // second segment of the bridge (horizontal)
            if (myChar.y > bridgeMiddle & myChar.y < bridgeMiddle + bridgeWidth) {
                if (myChar.x < randomBridgeStart - randomBridgeLength || myChar.x > randomBridgeStart + bridgeWidth) {
                    noLoop(); // game stops
                    losingScreenRiver(); // show losing screen (splash version)
                    loseSound.play(); // play losing sound
                }
            }

            // last segment of the bridge (vertical)
            if (myChar.y > riverStart & myChar.y < bridgeMiddle) {
                if (myChar.x < randomBridgeStart - randomBridgeLength || myChar.x > randomBridgeStart - randomBridgeLength + bridgeWidth) {
                    noLoop(); // game stops
                    losingScreenRiver(); // show losing screen (splash version)
                    loseSound.play(); // play losing sound
                }
            }
        }
    }
}

// the popup screen when you win
function winningScreen() {

	soundtrack.stop(); // stop background sound

    // border + screen
    fill(255, 213, 0);
    rect(width / 4, height / 4, width / 2, height / 2);

    fill(255);
    rect(width / 4 + borderW / 2, height / 4 + borderW / 2, width / 2 - borderW, height / 2 - borderW);

    // winning screen text for when you make it to the egg
    fill(0);
    textSize(20);
    textAlign(CENTER); // center the text
    textFont('Avenir'); // text font

    // text:
    textStyle(NORMAL);
    text("You protected your egg;", width / 2, 270);
    textStyle(BOLD);
    text("Congratulations!", width / 2, 300);
    textStyle(NORMAL);
    text("Hit ENTER to play again!", width / 2, 330);

}

// the popup screen when you're hit by a car
function losingScreenCar() {

    soundtrack.stop();

    fill(235, 131, 131);
    rect(width / 4, height / 4, width / 2, height / 2);

    fill(255);
    rect(width / 4 + borderW / 2, height / 4 + borderW / 2, width / 2 - borderW, height / 2 - borderW);

    // losing screen text for when a car runs into you
    fill(0);
    textSize(20);
    textAlign(CENTER); // center the text
    textFont('Avenir'); // text font

    // text:
    textStyle(NORMAL);
    text("Oh no, a car got you!", width / 2, 285);
    text("Hit ENTER to try again!", width / 2, 315);

}

// the popup screen when you fall into the river
function losingScreenRiver() {

    soundtrack.stop();

    fill(53, 99, 240);
    rect(width / 4, height / 4, width / 2, height / 2);

    fill(255);
    rect(width / 4 + borderW / 2, height / 4 + borderW / 2, width / 2 - borderW, height / 2 - borderW);

    // losing screen text for when you fall into the water
    fill(0);
    textSize(20);

    textAlign(CENTER); // center the text
    textFont('Avenir'); // text font

    // text:
    textStyle(BOLD); // bold the sound effect (splash)
    text("Splash...", width / 2, 270);
    textStyle(NORMAL);
    text("You fell into the water!", width / 2, 300);
    text("Hit ENTER to try again!", width / 2, 330);

}

// draw in the bottom (first) road
function drawFirstRoad() {

    var roadLocX = 0;
    var roadLocY = 385;
    var lineLocY = 434;

    fill(180);
    rect(roadLocX, roadLocY, width, roadWidth); // draws the road

    fill(245, 224, 66);
    rect(roadLocX, lineLocY, width, roadLine); // the dividing yellow line

}

// draw in the top (second) road
function drawSecondRoad() {

    var roadLocX = 0;
    var roadLocY = 100;
    var lineLocY = 149;

    fill(180);
    rect(roadLocX, roadLocY, width, roadWidth); // draws the road

    fill(245, 224, 66);
    rect(roadLocX, lineLocY, width, roadLine); // the dividing yellow line

}

// draw in the river
function drawRiver() {

    var riverEdgeL = 0;

	fill(100, 100, 255); // blue color
    rect(riverEdgeL, riverStart, width, riverWidth);

    drawBridge(); // draw in bridge using bridge function

}

// draws the bridge the crosses the river. the bridge is randomly generated each time the game is played!
// it will go rightward if the starting point is on the left of the screen, and leftward if the starting point is to the right
function drawBridge() {

    fill(186, 145, 104); // brown bridge color

    rect(randomBridgeStart, bridgeMiddle, bridgeWidth, riverWidth / 2); // first bridge segment
    
    // this if-else statement determines which direction the bridge goes in
    if (randomBridgeStart < width / 2) {
        rect(randomBridgeStart, bridgeMiddle, randomBridgeLength, bridgeWidth); // second bridge segment
        rect(randomBridgeStart + randomBridgeLength, riverStart, bridgeWidth, riverWidth / 2 + bridgeWidth); // final bridge segment
    } else {
        rect(randomBridgeStart - randomBridgeLength, bridgeMiddle, randomBridgeLength, bridgeWidth); // second bridge segment
        rect(randomBridgeStart - randomBridgeLength, riverStart, bridgeWidth, riverWidth / 2 + bridgeWidth); // final bridge segment
    }

}

// characteristics of the car objects
function makeCars(px, py) {
    return {
        x: px,
        y: py,
        c: color(random(255), random(255), random(255)),
        w: 50,
        h: 30,
        lightsW: 2,
        lightsH: 5,
        lightsC: color(242, 235, 19),
        tireW: 9,
        tireH: 3,
        tireC: color(0, 0, 0),
        windshieldW: 4,
        windshieldH: 25,
        windshieldC: color(100, 100, 100),
        draw: drawCars
    }
}

// uses the obj characteristics to draw cars
function drawCars() {

	var tireOffsetX = 6;
	var tireOffsetY = 2;
    var windshieldOffsetX = 10;
    var windshieldOffsetY = 2.5;

    // car body
    fill(this.c);
    rect(this.x, this.y, this.w, this.h);

    // car headlights (x4)
    fill(this.lightsC);
    rect(this.x + this.w - this.lightsW, this.y, this.lightsW, this.lightsH);
    rect(this.x + this.w - this.lightsW, this.y + this.h - this.lightsH, this.lightsW, this.lightsH);
    rect(this.x, this.y, this.lightsW, this.lightsH);
    rect(this.x, this.y + this.h - this.lightsH, this.lightsW, this.lightsH);

    // car tires
    fill(this.tireC);
    rect(this.x + this.w - (2.5 * tireOffsetX), this.y - tireOffsetY, this.tireW, this.tireH);
    rect(this.x + tireOffsetX, this.y - tireOffsetY, this.tireW, this.tireH);
    rect(this.x + this.w - (2.5 * tireOffsetX), this.y + this.h - (tireOffsetY / 2), this.tireW, this.tireH);
    rect(this.x + tireOffsetX, this.y + this.h - (tireOffsetY / 2), this.tireW, this.tireH);

    // car windshields
    fill(this.windshieldC);
    rect(this.x + (3.5 * windshieldOffsetX), this.y + windshieldOffsetY, this.windshieldW, this.windshieldH);
    rect(this.x + (1.2 * windshieldOffsetX), this.y + windshieldOffsetY, this.windshieldW, this.windshieldH);

    if (this.x > width) {
        this.x = -this.w;
    }

    if (this.x + this.w < 0) {
        this.x = width;
    }
}

// draws the nest using nest variables
function drawNest() {

    // the nest
    fill(219, 181, 75);
    ellipse(width / 2, nestLocY, nestW * 1.3, nestH);

    fill(163, 129, 34);
    ellipse(width / 2, nestShadowY, nestW, nestH / 2);
 
    // the egg
    fill(250, 242, 220);
    ellipse(width / 2, eggLocY, eggW, eggH);

}

// setting up the first row of cars and placing them (very bottom row of cars)
function firstCarSetup() { 

    var carPlacement; // car placement variable
    var numCars = 5; // number of cars in the first row of cars
    var carLocY = 445;

    var newCar = makeCars();
    firstCars.push(newCar); // add each new car to the empty array firstCars

    // place each car in the first row of cars and add to the array firstCars
    for (var i = 0; i < numCars; i++) {
        carPlacement = random((i * width / numCars) + carSpacing, ((i + 1) * width / numCars) - carSpacing);
        firstCars[i] = makeCars(carPlacement, carLocY); // placement of the first row of cars
    }

}

// setting up the second row of cars and placing them
function secondCarSetup() {

    var carPlacement; // car placement variable
    var numCars = 5; // number of cars in the first row of cars
    var carLocY = 395;

    var newCar = makeCars();
    secondCars.push(newCar); // add each new car to the empty array secondCars

    // place each car in the second row of cars and add to the array secondCars
    for (var i = 0; i < numCars; i++) {
        carPlacement = random((i * width / numCars) + carSpacing, ((i + 1) * width / numCars) - carSpacing);
        secondCars[i] = makeCars(carPlacement, carLocY);
    }

}

// setting up the third row of cars and placing them
function thirdCarSetup() {

    var carPlacement; // car placement variable
    var numCars = 5; // number of cars in the first row of cars
    var carLocY = 160;

    var newCar = makeCars();
    thirdCars.push(newCar); // add each new car to the empty array secondCars

    // place each car in the third row of cars and add to the array secondCars
    for (var i = 0; i < numCars; i++) {
        carPlacement = random((i * width / numCars) + carSpacing, ((i + 1) * width / numCars) - carSpacing);
        thirdCars[i] = makeCars(carPlacement, carLocY);
    }

}

// setting up the fourth row of cars and placing them
function fourthCarSetup() {

    var carPlacement; // car placement variable
    var numCars = 5; // number of cars in the first row of cars
    var carLocY = 110;

    var newCar = makeCars();
    fourthCars.push(newCar); // add each new car to the empty array secondCars

    // place each car in the fourth row of cars and add to the array secondCars
    for (var i = 0; i < numCars; i++) {
        carPlacement = random((i * width / numCars) + carSpacing, ((i + 1) * width / numCars) - carSpacing);
        fourthCars[i] = makeCars(carPlacement, carLocY);
    }

}

// draws the first row of cars (closest to the bottom of the screen)
function showFirstCars() {

    for (var i = 0; i < firstCars.length; i++) {
        firstCars[i].draw(); // draws in cars
        firstCars[i].x += 1; // makes the cars drive rightward on the first road (bottom)
    }

}

// draws the second row of cars (next row up from bottom of the screen)
function showSecondCars() {

    for (var i = 0; i < secondCars.length; i++) {
        secondCars[i].draw();
        secondCars[i].x -= 1; // makes the cars move in the opposite direction (leftward) on the first road
    }

}

// draws the third row of cars (next row up from bottom of the screen)
function showThirdCars() {

    for (var i = 0; i < thirdCars.length; i++) {
        thirdCars[i].draw();
        thirdCars[i].x += 1.8; // faster cars to increase the difficulty (rightward, top road)!
    }

}

// draws the fourth row of cars (row of cars near top of screen)
function showFourthCars() {

    for (var i = 0; i < fourthCars.length; i++) {
        fourthCars[i].draw();
        fourthCars[i].x -= 1.8; // faster cars in opposite direction to increase the difficulty (leftward, top road)!
    }

}

Cathy Dong – Final Project

sketch

/* Cathy Dong
   Section D
   yinhuid@andrew.cmu.edu
   Project 12 - Final Project
*/

// load image as color base
var baseImg;
var proportion = 0.24;
//cover text space setting
var space1 = 50;
var space2 = 32;
//block dimension (cover page)
var bw = 100;
var bh = 25;
//page number: cover + levels
var page = 0;

// draw rect fill color variable
var fillColor = 255;


var cSpace1 = 10;
var colorSize = 30;

// variables for drawRect()
var c = [];
var rectX = [];
var rectY = [];

// variables for drawLines()
var w = [];
var lineL = [];
var lineR = [];
var lineU = [];
var lineD = [];


// preload cover de stijl base image
function preload() {
    var img = "https://i.imgur.com/pJRSO5b.jpg";
    baseImg = loadImage(img);
}

function setup() {
    createCanvas(480, 480);
    background(0);
}

function draw() {
    // decide page
    pageSetting();

    //cover when page = 0;
    if (page == 0) {
        // make cover image, set to scale
        scale(proportion);
        image(baseImg,0,0);
        filter(THRESHOLD);
        // set scale back to 1:1 for text draw
        scale(1 / proportion);
        coverBlocks();
        coverText();
    }
    //level 1 when page == 1
    else if (page == 1) {
        clear();
        background(210);
        baseCanvas();
        commandKey();
        baseGrids(5);
        changeColor();
        drawRect(5);
        if (key !== "r" & key !== "R") {
            drawLine(5);
        }4
    }

    // level 2 when page == 2
    else if (page == 2) {
        clear();
        background(210);
        baseCanvas();
        commandKey();
        baseGrids(7);
        changeColor();
        drawRect(7);
        drawRect(7);
        if (key !== "r" || "R") {
            drawLine(7);
        }
    }

    // level 3 when page == 3
    else if (page == 3) {
        clear();
        background(210);
        baseCanvas();
        commandKey();
        baseGrids(15);
        changeColor();
        drawRect(15);
        drawRect(15);
        if (key !== "r" || "R") {
            drawLine(15);
        }
    }
}

// jump to page through cover levels
function pageSetting() {
    if (page == 0) {
        // select page 1
        if ((mouseIsPressed) 
            & (mouseY > height / 2 + space2) && (mouseY < height / 2 + space2 + bh)
            && (mouseX > width / 2 - 50) && (mouseX < width / 2 - 50 + bw)) {
            page  = 1;
        }
        // select page 2
        else if ((mouseIsPressed) 
            & (mouseY > height / 2 + space2 * 2) && (mouseY < height / 2 + space2 * 2 + bh)
            && (mouseX > width / 2 - 50) && (mouseX < width / 2 - 50 + bw)) {
                page  = 2;
        }
        // select page 3
        else if ((mouseIsPressed) 
            & (mouseY > height / 2 + space2 * 3) && (mouseY < height / 2 + space2 * 3 + bh)
            && (mouseX > width / 2 - 50) && (mouseX < width / 2 - 50 + bw)) {
                page  = 3;
        }
    }
}


// white blocks below option texts for texts to read better
function coverBlocks () {
    fill(255);
    noStroke();
    for (var i = 1; i < 4; i++) {
        rect(width / 2 - 50, height / 2 + space2 * i , bw, bh);
    }
}

// cover texts: title & levels
function coverText() {
    textStyle(BOLD);
    //title base setting
    noStroke();
    textAlign(CENTER);
    textSize(80);
    // title shadow
    fill(0);
    text("De Stijl", width / 2 + 5, height / 2 +-3);
    // title text
    fill(255);
    text("De Stijl", width / 2, height / 2);


    //options
    fill(0);
    noStroke();
    textSize(15);
    text("LEVEL 1", width / 2, height / 2 + space1);
    text("LEVEL 2", width / 2, height / 2 + space1 + space2);
    text("LEVEL 3", width / 2, height / 2 + space1 + space2 * 2);
}

// pick rectangle colors
function changeColor() {
    var keyX = width - 45;
    var cy = width / 2;
    if (mouseIsPressed) {
        if ((mouseX > keyX) & (mouseX < keyX + colorSize)) {
            // blue block
            if ((mouseY > cy + cSpace1) && (mouseY < cy + cSpace1 + colorSize)){
                fillColor = "blue";
            }
            // red block
            if ((mouseY > cy + cSpace1 * 2 + colorSize) 
                & (mouseY < cy + cSpace1 * 2 + colorSize * 2)) {
                fillColor = "red";
            }
            // yellow block
            if ((mouseY > cy + cSpace1 * 3 + colorSize * 2)
                & (mouseY < cy + cSpace1 * 3 + colorSize * 3)) {
                fillColor = "yellow";
            }
            // white block
            else if ((mouseY > cy + cSpace1 * 4 + colorSize * 3)
                & (mouseY < cy + cSpace1 * 4  +colorSize * 4)) {
                fillColor = "white";
            }
        }
    }
}


// white base draw canvas
function baseCanvas() {
    strokeWeight(5);
    stroke(0);
    fill(255);
    rect(10, 10, width - 70, height - 70);
}

//option grid lines
function baseGrids(level) {
    // grid lines
    noFill();
    strokeWeight(0.2);
    stroke("gray");

    // use loop to draw grids
    for (var i = 0; i < level; i++) {
        for (var j = 0; j < level; j++) {
            var gw = width - 70;
            var gh = height - 70;
            rect(10 + gw / level * i, 10 + gh / level * j, gw / level, gh / level);            
        }
    }
}

// color fill command key draw on the side
function commandKey() {
    var cx = width - 50;
    var cy = width / 2;
    var keyX = width - 45;
    // base setting
    textAlign(LEFT);
    textSize(10);
    textStyle(BOLD);
    fill(0);
    noStroke();
    text("COLORS", cx, cy);

    // keys blocks
    noStroke();
    fill("blue");
    rect(keyX, cy + cSpace1, colorSize, colorSize);
    fill("red");
    rect(keyX, cy + cSpace1 * 2 + colorSize, colorSize, colorSize);
    fill("yellow");
    rect(keyX, cy + cSpace1 * 3 + colorSize * 2, colorSize, colorSize);
    fill("white");
    rect(keyX, cy + cSpace1 * 4 + colorSize * 3, colorSize, colorSize);
}

// draw colored rectangles based on grid
function drawRect(level){
    var gw = width - 70;
    var gh = height - 70;
    // add x, y, colorFill to lists when mouse clicked and hold key "r" or "R"
    if (keyIsPressed) {
        if (key == "r" || key == "R") {
            // check if mouse clicked in which grid
            for (var m = 0; m < level; m++) {
                for (var n = 0; n < level; n++) {
                    // grid axis location
                    var left = 10 + gw / level * m;
                    var up = 10 + gh / level * n;
                    var right = left + gw / level;
                    var down = up + gh / level;
                    // mouse click location to find the grid
                    if (mouseIsPressed) {
                        if ((mouseX > left) & (mouseX < right) && (mouseY > up) && (mouseY < down)) {
                            // push fillColor, x, y into lists for later draw use
                            c.push(fillColor);
                            rectX.push(left);
                            rectY.push(up);
                        }
                    }
                }
            } 
        }
    }
    // loop through lists to draw rect
    for (var x = 0; x < c.length; x++) {
        fill(c[x]);
        noStroke();
        rect(rectX[x], rectY[x], gw / level, gh / level);
    }
}

function drawLine(level) {
    var gw = width - 70;
    var gh = height - 70;
    // precision torlerence
    var tolerence = 3;
    // loop through every grid
    for (var m = 0; m < level; m++) {
        for (var n = 0 ; n < level; n++) {
            var left = 10 + gw / level * m;
            var up = 10 + gh / level * n;
            var right = left + gw / level;
            var down = up + gh / level;

            // hold key to get the line weight
            if (keyIsPressed) {
                var currentKey = key;
            }
            // check mouseX and mouseY to determine if to draw a line
            if (mouseIsPressed) {
                // vertical lines into lists
                if (mouseY > up + tolerence & mouseY < down - tolerence) {
                    if (mouseX > left - tolerence && mouseX < left + tolerence) {
                        lineL.push(left);
                        lineR.push(left);
                        lineU.push(up);
                        lineD.push(up + gh / level);
                        w.push(currentKey);
                    }
                }
                // horizontal lines into lists
                if (mouseX > left + tolerence & mouseX < right - tolerence) {
                    if (mouseY > up - tolerence && mouseY < up + tolerence) {
                        lineL.push(left);
                        lineR.push(left + gw / level);
                        lineU.push(up);
                        lineD.push(up);
                        w.push(currentKey);
                    }
                }
            }
        }
    }
    // draw lines based on lists
    for (var x = 0; x < lineL.length; x ++) {
        strokeWeight(w[x]);
        noFill();
        stroke(0);
        line(lineL[x], lineU[x], lineR[x], lineD[x]);
    }
}

This project is to create a de stijl drawing of your own. By clicking on level 1-3, you will get 5×5, 7×7 or 15×15 grids.

Hold key “1” to “9” and click on the the grid edge to draw lines with the exact line weight. (selection precision tolerance is 3 pixels)

Click on the color keys on the lower left corner to select color. Holding “r” or “R” and clicking on the grid will draw rectangles that are the size of the grid. When drawing rectangles, lines will be hidden. Show lines by pressing on any other keys.