Julie Choi – Final Project

My final project requires the use of a camera (webcam), so it does not properly run on WordPress. Here is the link to the zip file:

Julie_Choi_Final_Project.zip

Instructions: Start with a white background for this project. Preferably wear a dark shade shirt and place your portrait on the center on the screen. An easier way to interact with this short game is to wait for all the balls to drop from the top of the screen, but this does not matter hugely. Start clicking the red balls to make them disappear. Once the screen has no red balls, an orange screen will pop up and tell you “good job!”

Since the program does not run on this page, here are some screenshots of the step by step process of the game.

This is how it looks when all the bubbles have stacked on top of each other.
This is how it looks when you click on the red bubbles to get rid of them on the screen.
This screen will appear once all the red bubbles disappear from the screen.

final project

/*Julie Choi
15-104 Section E
jjchoi@andrew.cmu.edu
Final Project
*/

var px = 20;
var py =  100;
var myCaptureDevice;
var brightnessThreshold = 50;
var darknessThreshold = 45
var circleCenter = [];
var x = 5;
var radius = 20;
var randomBalls = 2;
var initGeneration = true;
var initRandom = true;
let timer = 30;


function setup() {
    createCanvas(640, 480);
    myCaptureDevice = createCapture(VIDEO);
    myCaptureDevice.size(640, 480); // attempt to size the camera. 
    myCaptureDevice.hide(); // this hides an unnecessary extra view.
    frameRate(60);
    // generateCircleCenter function is called in setup
    generateCircleCenter();   	
}

function generateCircleCenter(){
	// sets the circles on the top of the circle by pushing makeCircleFall into the circle center
    while(x <= width - radius){
        circleCenter.push(makeCircleFall(x, 20, radius, true, false));
        x += radius;
    }
    x = 5;
    generateRandom(randomBalls);
}

function generateRandom(n){
	// generates more lines of circles to fall after the previous line
    if (n == 0){
        return;
    }
    var j = int(random(0, circleCenter.length));
    if (circleCenter[j].random != true){
        circleCenter[j].random = true;
        generateRandom(n-1);
    } else {
        generateRandom(n);
    }
}

function draw() {
    background(220);
    myCaptureDevice.loadPixels(); 
    // draw the camera at 1:1 resolution
    image(myCaptureDevice, 0, 0);  
    fill(255);
    // call all the objects in draw function
    for(var c = 0; c < circleCenter.length; c++){
        if (circleCenter[c].exist){
            circleCenter[c].render();
            circleCenter[c].update();
            if(circleCenter[c].py >= height){
                circleCenter[c].reset();
            }
        }
    }
    // if the frameCount is divisible by 60, then a second has passed. it will stop at 0
    if (frameCount % 60 == 0 & timer > 0) { 
        timer --;
    }
    if (timer % 2 == 1 & initGeneration){
        initGeneration = false;
        generateCircleCenter();
    }
    if (timer % 2 == 0 & initGeneration != true){
        initGeneration = true;
    }

    // instruction text on the bottom
    fill(0);
    noStroke();
    fill(255);
    textFont('futura');
    textSize(10);
    text("use a white background", width / 2, height - 25);
    text("tip: wait for all the balls to fall and have fun playing with the balls :)", width / 2, height - 10);
    textAlign(CENTER, CENTER);
    textSize(30);
    text( "Pop the red bubbles!", width / 2, height - 50);

    // detect only the py of the yellow point exsisting on the screen
    var result = circleCenter.filter(obj => {
    return obj.py > 0 & obj.random;
    });
    // if the result of the value above is 0 then the timer stops
    if (result.length == 0){
        timer == 0;
        fill(249, 173, 129);
        rect(0, 0, width, height);
        noFill();
        stroke(255);
        strokeWeight(1);
        text("GOOD JOB!", width/2, height /2);
    }
}

function makeCircleFall(inputX, inputY, radius, exist, random) {
    return {px: inputX, py: inputY, radius: radius, exist: exist, random: random,
            update: Update,
            reset: Reset,
            render: Render
           };
}

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

function Update() {
    // fetch the color of the pixel at the (px,py) location of the circleCenter
    var theColorAtPxPy = myCaptureDevice.get(this.px, this.py);
    // compute its brightness
    if(isColor(theColorAtPxPy)){
        var theBrightnessOfTheColorAtPxPy = brightness(theColorAtPxPy);
    }
    // if the circleCenter is in a bright area, move downwards.
    // else, if it's in a dark area, move up until we're in a light area
    if(theBrightnessOfTheColorAtPxPy > brightnessThreshold){
        this.py += 1;
    } else if(theBrightnessOfTheColorAtPxPy < darknessThreshold & this.py > 0){
        this.py -=1;
        theColorAtPxPy = myCaptureDevice.get(px, py);
    }
    // take the objects in the circleCenter array and filter them into the name obj
    // obj.px to keep track of each px in the circleCenter array
    // this makes each circle stack on top of each other regarding the distance of the diameter of each circle
    var result = circleCenter.filter(obj => {
        return obj.px == this.px & obj.py > this.py
    });
    for (var i = 0; i < result.length; i++) {
        if ((result[i].py - radius) < this.py){
            this.py --;
        }
    }
}

function Reset() {
	// reset py to stop at the bottom of the screen
    this.py = height;
}

function Render() {
	// choose 3 circles randomly from each line to fill with red
    if(this.random){
        fill("red");
        stroke(255);
        strokeWeight(2);
        if (initRandom){
            initRandom = false;
        }
    }
    // draw circle at points of px and py in a line across the top of the screen
    if (this.exist) {
        ellipse(this.px, this.py, this.radius, this.radius);
        fill(random(0, 255), random(0, 255), random(0, 150), 80);
        stroke(255);
        strokeWeight(2);
        initRandom = true;
    }
}

function mousePressed() {
	// circles stay on the screen if exist is true
	// when mouse is pressed, exist and random becomes false to pop and make the circle that was clicked disappear
    for (var i = 0; i < circleCenter.length; i++) {
        var c = circleCenter[i];
        if (c.exist & dist(c.px, c.py, mouseX, mouseY) < c.radius / 2) {
            c.exist = false;
            c.random = false;
            return;
        }
    }
}

Reflection: This project utilizes the concept of raining letter assignment that we did a few weeks back. Using what we have learned plus some outside research more about p5.js, I was able to execute a satisfying result. I would say though that that using the different existing functions of p5.js and applying my mathematical calculation for the timer and the balls stacked on top of each other was the most challenging part. Overall, I learned a lot of new operators in the program and enjoyed the process of controlling both the display and the function.

Leave a Reply