Robert Oh – Final Project- “QuadTask”

version2

//Robert Oh
//Section C
//rhoh@andrew.cmu.edu
//Final Project

//assigning variables

highScore = 0;
score = 0;
scoreTimer = 0;
list1 = [0, 0, 1, 0, 0];
arrList = [0, 0, 0, 0, 0];
circleX = [-1, -1, -1];
circleY = [0, 0, 0];
circleTime = [9, 9, 9];
operations = ["times", "plus", "minus"];
currTime = -1;
currTime2 = -1;
highlightInd = 2;
wallX = 1000;
wallY = 1000;
squareY = 660;
squaredy = 0;
gameState = "Menu";
cause = "";
int1 = 0;
int2 = 0;
int3 = 0;
quizTimer = 7; 
dir = "up";
realDir = "up";
replace = 0;
real = 0;
fake = 0;


//setting up the game
function setup() {
    createCanvas(600, 600);
    frameRate(300);
    arrList[int(random(5))] = makeArrow(0);
    circleTime[0] = 10;
    circleX[0] = random(width / 2 + 25, width - 25);
    circleY[0] = random(25, height / 2 - 25); 
    wallX = width / 2 - 15;
    wallY = int(random(height / 2, height - 100));
    quizTimer = 7;
    int1 = int(random(1, 10));
    int2 = int(random(1, 10));
    if (random(0, 1) > .5){
        replace = 2;
        if (random(0, 1) > .5){
            int3 = int2 + 1;
        }
        else {
            int3 = int2 - 1;
        }
    }
    else{
        replace = 1;
        if (random(0, 1) > .5){
            int3 = int1 + 1;
        }
        else {
            int3 = int1 - 1;
        }
    }
    word = random(operations);
    if (random(0, 1) > .5){
        realDir = "up";
    }
    else {
        realDir = "down";
    }
}

function draw() {
    background(67, 239, 236);
    //playing the game
    if (gameState == "Play"){
        noStroke();
        fill(155, 216, 255);
        rect(0, 0, width / 2, height / 2);
        fill(255, 214, 112);
        rect(width / 2, 0, width, height / 2);
        fill(121, 255, 112);
        rect(0, height / 2, width / 2, height);
        fill(255, 114, 135);
        rect(width / 2, height / 2, width, height);
        strokeWeight(3);
        stroke(89, 89, 89);
        line(0, height / 2, width, height / 2);
        line(width / 2, 0, width / 2, height);
        noStroke();
        fill(0);
        textSize(20);
        textStyle(BOLD);
        text(("Score: " + score), 0, height / 2 )
        drawTopLeft();
        drawTopRight();
        drawBotLeft();
        drawBotRight();
        temp2 = second();
        if (temp2 != scoreTimer){
            scoreTimer = temp2;
            score ++;
        }
    }
    //losing the game
    else if (gameState == "Done"){
        if (score > highScore){
            highScore = score;
        }
        background(255, 91, 151);
        fill(193, 193, 193);
        rect(width / 4, height / 4, width / 2, height / 2, 2);
        textSize(25);
        fill(0);
        noStroke();
        text("Death By: ", width / 2 - 110, height / 2 - 30);
        text(cause, width / 2 + 5, height / 2 - 30);
        fill(7, 246, 255);
        text("High Score: " + highScore, width / 2 - 90, height / 2 + 30);
        fill(101, 255, 73);
        text("RESTART? Press R", width / 2 - 120, height / 2 + 100);
        textStyle(BOLD);
        textSize(40);
        fill(255, 0, 0);
        text("GAME OVER", width / 2 - 125, height / 2 - 70);   
    }
    //main menu
    else if (gameState == "Menu"){
        textStyle(BOLD);
        textSize(70);
        fill(0);
        if (mouseX >= 185 & mouseX <= 400 && mouseY >= 120 && mouseY <= 175){
            fill(0, 255, 0);
        }
        text("START", width / 2 - 120, height / 2 - 130);
        fill(0);
        if (mouseX >= 40 & mouseX <= 570 && mouseY >= 310 && mouseY <= 355){
            fill(255, 0, 0);
        }
        text("INSTRUCTIONS", width / 2 - 250, height / 2 + 60);
        textStyle(NORMAL);
        textSize(20);
        fill(0);
        text("A multi-tasking game made by Robby Oh", width - 375, height - 45);
    }
    //Looking at rules
    else if (gameState == "Info"){
        fill(0);
        textStyle(BOLD);
        textSize(30);
        text("Survive as long as you can!", 100, 40);
        textSize(23);
        textStyle(NORMAL);
        fill(0, 212, 255);
        text("Press 'A' and 'D' to avoid the arrows!", 100, 140);
        fill(255, 214, 112);
        text("Click the circles before time runs out!", 100, 240);
        fill(19, 234, 0);
        text("Press 'Space Bar' to dodge the wall!", 100, 340);
        fill(255, 114, 135);
        text("Press 'W' and 'S' to answer the quiz correctly!", 100, 440);
        fill(0);
        text("Press 'M' to return to the menu!", 100, 540);
    }
}

//drawing the top-left box
function drawTopLeft(){ 
    strokeWeight(0);
    fill(0);
    rect((width / 4) - 127.5, height / 4 + 1.5, 255, 17);
    for (i = 0; i < list1.length; i ++){
        if (list1[i] == 1){
            fill(0, 27, 206);
        }
        else {
            fill(155, 216, 255);
        }
        rect((width / 4 - 123.5) + (i * 50), (height / 4) + 5, 47, 10);
    }
    for (i = 0; i < 5; i ++){
        if (arrList[i] != 0){
            fill(0);
            obj = arrList[i];
            triangle(52 + (i * 50), obj.y, 32 + (i * 50), obj.y - 30, 67 + (i * 50), obj.y - 30);
        }
    }
    updateArr();
}

//drawing the top-right box
function drawTopRight(){
    for (i = 0; i < 3; i ++){
        if (circleX[i] != -1){
            fill(255, 181, 0);
            ellipse(circleX[i], circleY[i], 50);
            fill (0);
            textSize(15);
            textStyle(BOLD);
            text(("" + circleTime[i]), circleX[i] - 4, circleY[i] + 5);
        }
    }    
    updateCircle();
}

//drawing the bottom-left box
function drawBotLeft(){
    fill(6, 211, 50);
    rect(30, squareY, 40, 40);
    fill(0);
    rect(wallX, wallY, 15, 100);
    updateWall();
}

//drawing the bottom-right box
function drawBotRight(){
    if (quizTimer >= 0){
        fill(0);
        textStyle(BOLD);
        textSize(25);
        text("WHAT IS: ", width / 2 + 20, height / 2 + 40);
        disString = ("" + int1) + " " + word + (" " + int2) + " ?"; 
        text(disString, width / 2 + 150, height / 2 + 40);
        if (word == "plus"){
            real = int1 + int2;
            if (replace == 1){
                fake = int3 + int2;
            }
            else {
                fake = int1 + int3;
            }
        }
        if (word == "times"){
            real = int1 * int2;
            if (replace == 1){
                fake = int3 * int2;
            }
            else {
                fake = int1 * int3;
            }
        }
        if (word == "minus"){
            real = int1 - int2;
            if (replace == 1){
                fake = int3 - int2;
            }
            else {
                fake = int1 - int3;
            }
        }
        if (realDir == "up"){
            text(("" + real), width / 2 + 180, height / 2 + 90);
            text(("" + fake), width / 2 + 180, height / 2 + 280);

        }
        else {
            text(("" + fake), width / 2 + 180, height / 2 + 90);
            text(("" + real), width / 2 + 180, height / 2 + 280);
        }
        fill(0);
        textStyle(NORMAL);
        text(("TIMER: " + quizTimer), 320, 475);
        fill(224, 0, 33);
        stroke(0);
        if (dir == "up"){
            rect(width / 2 + 181, height / 2 + 125, 15, 60);
            triangle(width / 2 + 188, height / 2 + 105, width / 2 + 168, height / 2 + 125, width / 2 + 208, height / 2 + 125);
        }
        else if (dir == "down") {
            rect(width / 2 + 181, height / 2 + 185, 15, 50);
            triangle(width / 2 + 188, height / 2 + 250, width / 2 + 168, height / 2 + 230, width / 2 + 208, height / 2 + 230);
        }
        var temp = second();
        if (temp != currTime2){
            quizTimer --;
            currTime2 = temp;
        }
    }
    else{
        if (dir == realDir){
            var temp = second();
            if (temp != currTime2){
                quizTimer --;
                currTime2 = temp;
            }
            if (quizTimer > -3){
                textSize(40);
                text("CORRECT!", 3 * width / 4 - 100, 3 * height / 4 + 10);
            }
            else {
                quizTimer = 7;
                int1 = int(random(1, 10));
                int2 = int(random(1, 10));
                if (random(0, 1) > .5){
                    replace = 2;
                    if (random(0, 1) > .5){
                        int3 = int2 + 1;
                    }
                    else {
                        int3 = int2 - 1;
                    }
                }
                else{
                    replace = 1;
                    if (random(0, 1) > .5){
                        int3 = int1 + 1;
                    }
                    else {
                        int3 = int1 - 1;
                    }
                }
                word = random(operations);
                if (random(0, 1) > .5){
                    realDir = "up";
                }
                else {
                    realDir = "down";
                }
            }
        }
        else {
            cause = " Quiz!";
            gameState = "Done";
        }
    }
    
}

//updating the bottom-left box
function updateWall(){
    if ((wallX <= 70 & wallX >= 30) && ((squareY >= wallY && squareY <= wallY + 100) || (squareY + 40 >= wallY && squareY + 40 <= wallY + 100))){
        cause = " Walls!";
        gameState = "Done";
    }
        
    if (wallX + 15 < 0){
        wallX = width / 2 - 15;
        if (squareY < 3 * height / 4){
            wallY = int(random(squareY, squareY + 20));
        }
        else {
            wallY = int(random(squareY - 80, squareY - 60));
        }
    }
    wallX -= 2;
    squareY -= squaredy;
    if (keyIsDown(32)){
        squaredy = 3;
    }
    else{
        squaredy = -3;
    } 
    if (squareY + 40 >= height){
        squareY = height - 40;
    }
    if (squareY <= height / 2){
        squareY = height / 2;
    }
}

//updating the top-right box
function updateCircle(){
    var count = 0;
    for (i = 0; i < 3; i ++){
        if (circleX[i] != -1){
            count ++;
        }
        if (circleTime[i] <= 0 & circleX[i] != -1){
            cause = " Circles!";
            gameState = "Done";
        }
    }
    if (count == 0){
        circleTime[0] = 9;
        circleX[0] = random(width / 2 + 25, width - 25);
        circleY[0] = random(25, height / 2 - 25); 
        count += 1;
    }
    for (i = 0; i < 3; i ++){
        if (circleX[i] != -1){
            times = second();
            if (times != currTime){
                for (j = 0; j < 3; j ++){
                    if (circleX[i] != -1){
                        circleTime[j] -= 1;
                        currTime = second();
                    }
                }
            }
        }
        else {
            if (count < 3 & max(circleTime) < 7){
                circleTime[i] = 9;
                circleX[i] = random(width / 2 + 25, width - 25);
                circleY[i] = random(25, height / 2 - 25); 
                count += 1;
            }
        }

    }
}

//updating the top-left box
function updateArr(){
    var count = 0;
    for (i = 0; i < 5; i ++){
        if (arrList[i] != 0){
            count ++;
        }
    }
    for (i = 0; i < 5; i ++){
        if (arrList[i] != 0){
            obj = arrList[i];
            if (highlightInd == i & height / 4 > obj.y - 30 && height / 4 + 10 < obj.y ){
                gameState = "Done";
                cause = " Arrows!";
            }

            if (obj.y > height / 2){
                arrList[i] = 0;
            }
            obj.y += obj.dy;
        }
        else {
            if (random(1) > .995 & count < 3){
                arrList[i] = makeArrow(0);
                count ++
            }
        }
    }

}

//key presses
function keyPressed(){
    if (gameState == "Play"){
        if (keyIsDown(65)){
            if (highlightInd > 0){
                list1[highlightInd] = 0;
                highlightInd --;
                list1[highlightInd] = 1;
            }
        }
        if (keyIsDown(68)){
            if (highlightInd < 4){
                list1[highlightInd] = 0;
                highlightInd ++;
                list1[highlightInd] = 1;
            }
        }
        if (keyIsDown(87)){
            dir = "up";
        }
        if (keyIsDown(83)){
            dir = "down";
        }
    }
    if (gameState == "Info"){
        if (keyIsDown(77)){
            gameState = "Menu";
        }
    }
    if (gameState == "Done" & keyIsDown(82)){
        list1 = [0, 0, 1, 0, 0];
        arrList = [0, 0, 0, 0, 0];
        highlightInd = 2;
        gameState = "Menu";
        cause = "";
        arrList[int(random(5))] = makeArrow(0);
        circleX = [-1, -1, -1];
        circleY = [0, 0, 0];
        circleTime = [9, 9, 9];
        currTime = -1;
        circleX[0] = random(width / 2 + 25, width - 25);
        circleY[0] = random(25, height / 2 - 25); 
        wallX = width / 2 - 15;
        wallY = int(random(height / 2, height - 100));
        quizTimer = 7;
        int1 = int(random(1, 10));
        int2 = int(random(1, 10));
        if (random(0, 1) > .5){
            replace = 2;
            if (random(0, 1) > .5){
                int3 = int2 + 1;
            }
            else {
                int3 = int2 - 1;
            }
        }
        else{
            replace = 1;
            if (random(0, 1) > .5){
                int3 = int1 + 1;
            }
            else {
                int3 = int1 - 1;
            }
        }
        word = random(operations);
        if (random(0, 1) > .5){
            realDir = "up";
        }
        else {
            realDir = "down";
        }
        score = 0;
    }
} 

//mouse clicks
function mouseClicked(){
    if (gameState == "Play"){
        for (i = 0; i < 3; i ++){
            if (dist(mouseX, mouseY, circleX[i], circleY[i]) < 25){
                circleX[i] = -1;
                circleTime[i] = 5;
            }
        }
    }
    else if (gameState == "Menu"){
        if (mouseX >= 185 & mouseX <= 400 && mouseY >= 120 && mouseY <= 175){
            gameState = "Play"; 
        }
        
        if (mouseX >= 40 & mouseX <= 570 && mouseY >= 310 && mouseY <= 355){
            gameState = "Info";
        }
    }
}

//object for arrows (top-left box)
function makeArrow(yValue) {
    var arr = {y : yValue,
                dy : random(1, 1.5)};
    return arr;
}

I had a TON of fun creating this final project, which I have named “QuadTask”. Essentially, it is a multi-tasking game where you must look at 4 different boxes and complete each task in order to survive as long as you can. To start, you must click on “Start”, or click on “Instructions” to read up on my rules. It was fun creating a different task for each box and making them all unique but equally challenging! If I had more time to spend, I probably would have made the difficulty increase as the time increases. Personally, my high score was 81, so if you beat that score, kudos to you! All in all, I hope you have fun with my game! – Robert Oh

Jamie Dorst Final Project

sketch

/*
Jamie Dorst
jdorst@andrew.cmu.edu
Final Project
Section A
*/

// global variables
// weather
var weather;
var weatherUrl;
var temp;
var currentLocation;
var condition;
var city;
var rainConditions = ["Patchy light drizzle", "Light drizzle", "Freezing drizzle",
                      "Heavy freezing drizzle", "Patchy light rain", "Light rain",
                      "Moderate rain at times", "Moderate rain",
                      "Heavy rain at times", "Heavy rain", "Light freezing rain",
                      "Moderate or heavy freezing rain", "Light sleet",
                      "Moderate or heavy sleet", "Ice pellets", "Light rain shower",
                      "Torrential rain shower", "Light sleet showers",
                      "Moderate or heavy sleet showers", "Light showers of ice pellets",
                      "Moderate or heavy showers of ice pellets"];
var cloudyConditions = ["Cloudy", "Overcast", "Mist", "Patchy rain possible",
                        "Patchy snow possible", "Patchy sleet possible",
                        "Patchy freezing drizzle possible", "Thundery outbreaks possible",
                        "Fog", "Freezing fog"];
var snowyConditions = ["Blowing snow", "Blizzard", "Patchy light snow", "Light snow",
                       "Patchy moderate snow", "Moderate snow", "Patchy heavy snow", "Heavy snow"];
// custom variables
var button;
var cityInput;
var myFont;
// rain
var rainArray = [];
var rainAmount;
var rainSpeed;
var visibleR = false;
// snow
var snowflakeImg;
var snowArray = [];
var snowAmount;
var snowSpeed;
var visibleS = false;
// clouds
var cloudCover;
var cloudArray = [];
var cloudAmount;
var wind;
var rightWind = ["S", "SE", "E", "NE", "NNE", "ENE", "ESE", "SSE"];
var leftWind = ["N", "NW", "W", "SW", "SSW", "WSW", "WNW", "NNW"];


// preload weather API and snowflake image
function preload() {
    weatherUrl = 'https://api.apixu.com/v1/current.json?key=8f4b2cd0980d46aba2e201006182511&q=Pittsburgh';
    loadJSON(weatherUrl, getWeather);
    snowflakeImg = loadImage('https://i.imgur.com/VADyEQ9.png');
}

function setup() {
    createCanvas(480, 480);
    // case insensitive
    var lowCon = condition.toLowerCase();
    // make rain/snow relative to how heavy it is
    if (lowCon.indexOf('light') > -1) {
        rainAmount = 10;
        rainSpeed = 2;
        snowAmount = 10;
        snowSpeed = 1;
    } else if (lowCon.indexOf('moderate') > -1) {
        rainAmount = 15;
        rainSpeed = 3;
        snowAmount = 15;
        snowSpeed = 2;
    } else if (lowCon.indexOf('heavy') > -1) {
        rainAmount = 20;
        rainSpeed = 4;
        snowAmount = 20;
        snowSpeed = 3;
    } else {
        rainAmount = 30;
        rainSpeed = 5;
        snowAmount = 30;
        snowSpeed = 4;
    }
    // make amount of clouds relative to cloud cover
    cloudAmount = map(cloudCover, 0, 100, 0, 30);
    // prepare for rain/snow/clouds by filling array
    for (var i = 0; i < rainAmount; i++) {
        rainArray[i] = makeRain(random(170, 313), random(0, 315));
    }
    for (var i = 0; i < snowAmount; i++) {
        snowArray[i] = makeSnow(random(175, 305), random(0, 315));
    }
    for (var i = 0; i < cloudAmount; i++) {
        cloudArray[i] = makeCloud(random(-75, width + 75), random(320, height),
                                  random(100, 170), 175);
    }
    // let user name a city with input and button
    cityInput = createInput();
    cityInput.position(width - cityInput.width - 40, 20);
    button = createButton('GO');
    button.position(cityInput.x + cityInput.width + 5, 20);
    button.mouseClicked(changeCity);
}

// function to allow enter key to also submit input
async function keyPressed() {
    if (keyCode === ENTER) {
        changeCity();
    }
}

// function to change the weather to the user-inputted city
function changeCity() {
    // give variable a default
    city = 'Pittsburgh'
    // change city to inputted city
    city = cityInput.value();
    cityInput.value('');
    // reload weather data
    weatherUrl = 'https://api.apixu.com/v1/current.json?key=8f4b2cd0980d46aba2e201006182511&q=' + city;
    loadJSON(weatherUrl, getWeather);
}

async function cloudUpdate() {
    // make amount of clouds relative to cloud cover
        cloudAmount = map(cloudCover, 0, 100, 0, 30);
        // refill arrays so animations change
        for (var i = 0; i < rainAmount; i++) {
            rainArray[i] = makeRain(random(170, 313), random(0, 315));
        }
        for (var i = 0; i < snowAmount; i++) {
            snowArray[i] = makeSnow(random(175, 305), random(0, 315));
        }
        for (var i = 0; i < cloudAmount; i++) {
            cloudArray[i] = makeCloud(random(-75, width + 75), random(320, height),
                                      random(100, 170), 175);
        }
}

// get weather data
function getWeather(weather) {
    temp = Number(weather.current.temp_f);
    condition = weather.current.condition.text;
    currentLocation = weather.location.name;
    cloudCover = weather.current.cloud;
    wind = weather.current.wind_mph;
    windDir = weather.current.wind_dir;
    cloudUpdate();
}

async function draw() {
        // background is light during day, dark during night
        if (hour() > 8 & hour() < 18) {
            background("#0077CC");
        } else {
            background("#00487C");        
        }
        // font and size
        noStroke();
        textFont("Sans-serif");
        textAlign(CENTER);
        fill("EBF2FA");
        textSize(30);
        text(currentLocation, width / 2, 88);
        textSize(100);
        text(temp + "°", width / 2, 220);
        textSize(18);
        text(condition, width / 2, 120);
        textSize(12);
        text("Enter a city name or zip code to change location", 145, 28);
        // draw based on weather conditions
        if (condition === "Sunny") {
            sunny();
        } else if (condition === "Clear") {
            clearSky();
        } else if (condition === "Partly cloudy") {
            sunny();
            cloudy();
        } else if (rainConditions.some(weatherTest) == true) {
            rainy();
        } else if (cloudyConditions.some(weatherTest) == true) {
            cloudy();
        } else if (snowyConditions.some(weatherTest) == true) {
            snowy();
        } else if (condition === ("Patchy light rain with thunder") ||
                                 ("Moderate or heavy rain with thunder")) {
            rainy();
            thunder();
        } else if (condition === ("Patchy light snow with thunder") ||
                                 ("Moderate or heavy snow with thunder")) {
            snowy();
            thunder();
        }
}

// test weather conditions
function weatherTest(value) {
    return value == condition;
}
function directionTest(value) {
    return value == windDir;
}


//======================SUNNY=========================
function sunny() {
    // color of sun mapped to how hot it is
    // redder when hotter, yellower when cooler
    var sunColorG = map(temp, 0, 110, 230, 155);
    noStroke();
    fill(255, sunColorG, 0);
    // draw sun
    ellipse(width / 2, 350, 100, 100);
    // draw sun rays
    push();
    angleMode(DEGREES);
    translate(width / 2, 350);
    for (var i = 0; i < 10; i++) {
        fill ("orange");
        triangle(0, -80, -10, -60, 10, -60);
        rotate(36);
    }
    pop();
}

//======================CLEAR=========================
// function to draw moon
function clearSky() {
    noStroke();
    fill("#EBF2FA");
    ellipse(width / 2, 350, 150, 150);
    fill("#E3E9F2");
    ellipse(270, 330, 20, 20);
    ellipse(220, 360, 60, 60);
    ellipse(200, 300, 10, 10);
    ellipse(277, 400, 25, 25);
    ellipse(250, 291, 30, 30);
}


//=====================CLOUDY=========================
// function to make rain drop objects
function makeCloud(x, y, cloudColor) {
    var cloudObj = {
        x: x,
        y: y,
        cc: cloudColor,
        move: cloudMove,
        render: renderCloud
    };
    return cloudObj;
}

// function to draw clouds
function renderCloud() {
    noStroke();
    fill(this.cc);
    ellipse(this.x, this.y, 75, 75);
    ellipse(this.x + 60, this.y - 17, 100, 100);
    ellipse(this.x + 110, this.y + 5, 50, 50);
}

// function to make clouds move
// based on wind speed and direction
function cloudMove() {
    if (leftWind.some(directionTest) == true) {
        if (this.x > -160) {
            this.x -= map(wind, 0, 50, 0, 10);
        } else if (this.x <= -37.5) {
            this.x = width + 160;
        }
    } if (rightWind.some(directionTest) == true) {
        if (this.x < width + 37.5) {
            this.x += map(wind, 0, 50, 0, 10);
        } else if (this.x >= width + 37.5) {
            this.x = -160;
        }
    }
}

// weather condition function
function cloudy() {
    for (var i = 0; i < cloudAmount; i++) {
        cloudArray[i].render();
        cloudArray[i].move();
    }
}


//======================THUNDER=======================
// function to draw thunder
function thunder() {
    stroke("yellow");
    noFill();
    angleMode(DEGREES);
    arc(300, 320, 60, 60, 280, 0);
    arc(300, 320, 70, 70, 280, 0);
    arc(190, 315, 85, 85, 180, 285);
    arc(190, 315, 95, 95, 180, 285);
}


//======================RAIN==========================
// function to make rain drop objects
function makeRain(x, y) {
    var raindrop = {
        x: x,
        y: y,
        fall: rainFall,
        render: renderRain,
        visibleR: visibleR
    };
    return raindrop;
}

// function to draw rain
function renderRain() {
    noStroke();
    // only display raindrops when they are below the cloud
    if (this.visibleR == false) {
        ellipse(this.x, this.y, 0, 0);
    } else {
        ellipse(this.x, this.y, 7, 13);
    }
}

// function to make rain fall
function rainFall() {
    if (this.y < height) {
        this.y += rainSpeed
    } else if (this.y >= height) {
        this.y = random(300, 315);
    }
    if (this.y < 315) {
        this.visibleR = false;
    } else {
        this.visibleR = true;
    }
}

// weather condition function
function rainy() {
    // case insensitive
    var lowCon = condition.toLowerCase();
    // make rain relative to how heavy it is
    if (lowCon.indexOf('light') > -1) {
        rainAmount = 10;
        rainSpeed = 2;
    } else if (lowCon.indexOf('moderate') > -1) {
        rainAmount = 15;
        rainSpeed = 3;
    } else if (lowCon.indexOf('heavy') > -1) {
        rainAmount = 20;
        rainSpeed = 4;
    } else {
        rainAmount = 30;
        rainSpeed = 5;
    }
    // color of rain dependent upon temperature
    fill(0, map(temp, 32, 100, 255, 0), 255);
    for (var i = 0; i < rainAmount; i++) {
        rainArray[i].render();
        rainArray[i].fall();
    }
    var rainCloud = makeCloud(190, 315, 100);
    rainCloud.render();
}

//=======================SNOW=========================
// function to make snowflake objects
function makeSnow(x, y) {
    var snowflake = {
        x: x,
        y: y,
        fall: snowFall,
        render: renderSnow,
        visibleS: visibleS
    };
    return snowflake;
}

// function to draw snow
function renderSnow() {
    if (this.visibleS == false) {
        image(snowflakeImg, -5, -5, 1, 1);
    } else {
        image(snowflakeImg, this.x, this.y, 15, 15);
    }
}

// function to make snow fall
function snowFall() {
    if (this.y < height) {
        this.y += snowSpeed;
    } else if (this.y >= height) {
        this.y = 315;
    }
    if (this.y < 315) {
        this.visibleS = false;
    } else {
        this.visibleS = true;
    }
}

// weather condition function
function snowy() {
        // case insensitive
    var lowCon = condition.toLowerCase();
    // make snow relative to how heavy it is
    if (lowCon.indexOf('light') > -1) {
        snowAmount = 10;
        snowSpeed = 1;
    } else if (lowCon.indexOf('moderate') > -1) {
        snowAmount = 15;
        snowSpeed = 2;
    } else if (lowCon.indexOf('heavy') > -1) {
        snowAmount = 20;
        snowSpeed = 3;
    } else {
        snowAmount = 30;
        snowSpeed = 4;
    }
    fill(255);
    for (var i = 0; i < snowAmount; i++) {
        snowArray[i].render();
        snowArray[i].fall();
    }
    var snowCloud = makeCloud(190, 315, 100);
    snowCloud.render();
}

For my final project, I created a weather app. It defaults to Pittsburgh, but you can enter in any city or zip code you like and get the weather there. All of the animations are relative to some function of the weather such as the amount of rain, percent of cloud coverage, wind direction, and/or temperature. I learned a lot through making this project, from coming up with a plausible idea to figuring out how to get it to upload, and I’m happy with what I was able to produce.

Emily Zhou –– Final Project

Hi there, my final project is 900 x 450 px but WordPress cuts it off at 600 px width so here is the zip file to see the full thing: moon-exp.zip

Instructions: Click the stars to traverse the timeline of humanity’s exploration of the moon.

Details: Each click should reveal a key event in moon exploration in chronological order. The representation of the moon should slowly fill as time passes. After revealing all 26 events, the stars should be connected as one single constellation.

final project

var starX = [245, 260, 295, 325, 325, 365, 410, 400, 380, 390, 360, 355, 335, 
             265, 240, 205, 185, 170, 85, 95, 50, 60, 55, 65, 100, 120, 140];
var starY = [40, 32, 60, 85, 85, 100, 225, 275, 290, 305, 355, 370, 352, 385, 
             395, 382, 385, 380, 340, 325, 275, 250, 230, 140, 110, 100, 70];
var starD = [10, 6, 4, 10, 10, 6, 10, 4, 5, 8, 5, 4, 8, 6, 
             10, 4, 6, 5, 10, 5, 4, 6, 10, 5, 8, 4, 5];
var time = ["1609", "1610", "1610", "1645", "1647", "1651", "1753", "1824", "1920", "1959", 
            "1961", "1964", "1966", "1967", "1968", "1969", "1969", "1972", "1976", 
            "1990", "1994", "1998", "2007", "2007", "2008", "2009"];
var data = ["Hans Lippershey invented the telescope.",
            "Galileo Galilei made the first telescopic observation of the moon.",
            "Thomas Harriot and Galileo Galilei drew the first telescopic representation of the moon.",
            "Michael Florent van Langren made the first map of the moon.",
            "Johannes Hevelius published the first treatise devoted to the moon.",
            "Giovanni Battista Riccioli named craters after philosophers and astronomers.",
            "Roger Joseph Boscovich proved the moon has no atmosphere.",
            "Franz von Gruithuisen thought craters were formed by meteor strikes.",
            "Robert Goddard suggested sending rockets to the moon.",
            "Soviet spacecraft Luna 2 reached the moon, impacting near the crater Autolycus.",
            "President John F. Kennedy proposed a manned lunar program.",
            "NASA's Ranger 7 produced the first close-up TV pictures of the lunar surface.",
            "Soviet spacecraft Luna 9 made the first soft landing on the moon.",
            "NASA's Lunar Orbiter missions completed photographic mapping of the moon.",
            "NASA's Apollo 8 made the first manned flight to the moon, circling it 10 times before returning to Earth.",
            "Apollo 11 mission made the first landing on the moon and returned samples.",
            "Apollo 12 made first precision landing on the the moon.",
            "Apollo 17 made the last manned landing of the Apollo Program.",
            "Soviet Luna 24 returned the last sample to be returned from the moon (to date).",
            "NASA's Galileo spacecraft obtained multispectral images of the western limb and part of the far side of the moon.",
            "NASA's Clementine mission conducted multispectral mapping of the moon.",
            "NASA's Lunar Prospector mission launched.",
            "Japanese SELENE (Kaguya) spacecraft launched.",
            "Chinese Chang'e 1 lunar orbiter launched.",
            "Indian Chandrayaan 1 moon orbiter launched.",
            "NASA's Lunar Reconnaissance Orbiter launched"]
var newData = [];
var newTime = [];

var speed = 100; // typing speed
var maxCharacters = 65; // max characters per line
var spacing = 15; // spacing between lines
var clicks = 0; // click count
var next = 0; // to evaluate when next star is clicked

var starClickIndices = []; // index values of clicked stars
var clear1 = []; // stores handles for text either appearing or intercepting
var clear2 = []; // stores handles for text either appearing or intercepting
var toClearOne = true; // used to differentiate between clear1 and clear2

function setup() {
    createCanvas(900, 450);
    background(255, 255, 255);

    // to match time to new data array
    for (var i = 0; i < time.length; i++) {
    	newTime.push([]);
    	newTime[i].push(time[i]);
    }

    // code to split long text lines 
    for (var i = 0; i < data.length; i++) {
    	newData.push([]);
    	var stringStart = 0;
    	var stringEnd = 0;
    	
    	while (stringEnd < data[i].length) {

    		stringEnd += maxCharacters;

    		var trunc = stringEnd; // index of where to truncate
    		while (data[i].substring(trunc - 1, trunc) != " " & 
    				(stringEnd - trunc <= 10)) {
    			trunc--;
    		}

    		if (data[i].substring(trunc - 1, trunc) == " ") {
    			newData[i].push(data[i].substring(stringStart, trunc));
    			stringStart = trunc;
    			stringEnd = trunc;
    		}
    		else {
    			newData[i].push(data[i].substring(stringStart, stringEnd));
    			stringStart = stringEnd;
    		}
    	}
    }

    // frame
    noFill();
    stroke(220);
    rect(0, 0, width - 1, height - 1);
}

function draw() {
    // moon
    moonChord();

    // constellation lines
    for (var i = 1; i < starClickIndices.length; i++) {
        stroke(220);
        strokeWeight(0.5);
        line(starX[starClickIndices[i]], starY[starClickIndices[i]], 
        	starX[starClickIndices[i - 1]], starY[starClickIndices[i - 1]]);
    }

    if (next != clicks) {

        // erase text
        fill(255);
        noStroke();
        rect(450, 170, 425, 125);

        // code to type text
        if (toClearOne) {
        	for (var i = clear1.length - 1; i >= 0; i--) {
        		clearTimeout(clear1[i]);
        		clear1.pop(i);
        	}
        }
        else {
        	for (var i = clear2.length - 1; i >= 0; i--) {
        		clearTimeout(clear2[i]);
        		clear2.pop(i);
        	}
        }

        toClearOne = ! toClearOne;

	    fill(220);
	    noStroke();

	    if (toClearOne) {
	        typeWriter(newTime[next], 0, 0, 475, 215, clear1);
	        typeWriter(newData[next], 0, 0, 475, 235, clear1);
	    }
	    else {
	        typeWriter(newTime[next], 0, 0, 475, 215, clear2);
	        typeWriter(newData[next], 0, 0, 475, 235, clear2);

	    }
    }

    // stars
    stroke(150);
    fill(150);
    for (i = 0; i < 27; i++) {
        ellipse(starX[i], starY[i], starD[i], starD[i],)
    }

    next = clicks;
}

function mousePressed() {
    for (var i = 0; i < 27; i++) {
        var distance = dist(mouseX, mouseY, starX[i], starY[i]);
        if (distance <= starD[i] / 2 + 2) {

        	var unclicked = true;
        	for (var j = 0; j < starClickIndices.length; j++) {
        		if (starClickIndices[j] == i) unclicked = false;
        	}

        	if (unclicked) {
	            starClickIndices.push(i);
	            clicks++;
	            if (clicks >= data.length) clicks = 0;
	            curStarIndex = i;
	        }
        }
    }
}

// typewriter effect to print text
function typeWriter(info, r, c, x, y, clear) {
    if (r < (info.length)) {

    	c++;
    	if (c > info[r].length) {
    		c = 1;
    		r++;
    	}

        if (r < info.length) {
	        noStroke();
	        textSize(12);
	        text(info[r].substring(0, c), 
	            x, y + (r * spacing));

	        var handle = setTimeout(function() {
		          typeWriter(info, r, c, x, y, clear)
		        }, speed)
	        clear.push(handle);
	    }

    }
}

// continuously draw chords fill moon
function moonChord() {
    stroke(0, 0, 0, 15);

    var angle1 = random(0, 2 * PI);
    var px1 = 225 + 150 * cos(angle1);
    var py1 = 225 + 150 * sin(angle1);

    var angle2 = random(0, 2 * PI);
    var px2 = 225 + 150 * cos(angle2);
    var py2 = 225 + 150 * sin(angle2);

    line(px1, py1, px2, py2);
}

Progression:

Reflection: The hardest part was getting the typewriter text to print like I wanted and coordinating all of the different actions to the mouse click.

Sources:
The representation of the moon was inspired by this example on p5js.org:
https://p5js.org/examples/math-random-chords.html
I referenced this code as a starting point for developing the typewriter text:
https://gist.github.com/mjvo/2dce29799eb75b7ee1a571380f12ef1b

Kevin Riordan and Sharon Yang- Final Project Section C

When many keys are pressed
kzr junginny
When one key is held down
When mouse is hovered over control display

final project code

/*************************************************************************************************************************************************************************************
Kevin Riordan and Sharon Yang 15-104 Final Project Section C kzr@andrew.cmu.edu, junginny@andrew.cmu.edu
--*-IMPORTANT NOTE-*-------------------------------------
Though this final project is a collaboration between the two of us, we both wrote the code entirely together on my computer, because everything fits like a puzzle with everything
else and having it divided up among us would have made the code much less efficient (at least in our opinion). So, after meeting with Prof. Dannenberg, he agreed that as long
as this project is thoroughly documented, we can both be graded on the project in it's entirety.
--*-VISUAL DESCRIPTION-*---------------------------------
This project uses keyboard input to press the corresponding keys, which are shown when hovering over the area displayed in the top right corner of the canvas. When the key is
pressed, the corresponding note is played, a note with a color corresponding to the key and varying properties including fadeout rate, vertical speed, and note tail orientation
is created. The amount of notes created depends on how long the key is pressed down for. Lights on top of the speakers on each side of the stage and at the top display the
corresponding color, ending at the corresponding dancer location. The dancer at that location also cycles through its animation, and remembers what frame it was on and pauses when
the key is not pressed. There are four different types of dances, based on key position. Grey flickering lights, somewhat resembling a glowing disco ball (which is why its 
called discoLighting in the code) also randomly flicker both above the speakers and at the top behind the lights when a key is pressed.
--*-IMPROVEMENTS TO STRUCTURE/ FUTURE IMPLEMENTATIONS-*--
1-- A Dictionary called keyToColorHex was implemented using Hexcodes for colors near the start for the colors of each corresponding note, but later on in the project we wanted
    to vary the fadeout rate of the notes, and using fill(hexcode,alpha) is not possible, so we had to write a basic hexcode to r,g,b converter found between lines ~100 - ~120.
    These lines are unneccessary if the dictionary is changed to be in a similar format as the discoLighting array, with each key taking an r,g,b entry instead.
2-- THE SOUNDS LOOP WHEN THE KEY IS PRESSED, INSTEAD OF JUST PLAYING ONCE NO MATTER HOW LONG THE KEY IS HELD DOWN FOR ****THIS HAS BEEN FIXED AS OF 12-4-2018****
3-- The sound when the piano key is pressed should have the duration change up to a certain length while the key is held down, then fade out like a piano does in the real world
    However, when lengthening the sound using p5 sound commands, the pitch decreases (as the reference sheet warns), so a more complex solution would be required. Because the 
    main focus of this project shifted from the piano to the visual display as it was created, we feel this is not an urgent change, though it would improve the overall quality
    of the project.
--*-SOURCES FOR SOUNDS AND DANCE ANIMATIONS-*------------
This project uses free sprite sheets available for download at https://www.deviantart.com/gladkirby7/art/Vexy-Dances-FruityDance-Sprite-Sheet-675963897 for the arm dance and
windmill & https://www.pinterest.com/pin/525021269044190160/?lp=true for the spin dance && https://blueprintinteractive.com/sites/default/files/images/carlton_sprites2.png
for the man dance. The sounds for each piano key were downloaded from https://freesound.org/people/jobro/packs/2489/.
************************************************************************************************************************************************************************************/
// Variables corresponding to notes Example: cO1 = Note C in Octave 1
var cO1, dO1, eO1, fO1, gO1, aO1, bO1, cSO1, dSO1, fSO1, gSO1, aSO1, cO2, dO2, eO2, fO2, gO2, aO2, bO2, cSO2, dSO2, fSO2, gSO2, aSO2;
// Array that keeps track of total presses for each key Example: cO1 is index position 0, stores X amount of clicks
var numberOfPresses = [];
// Arrays used to store animation frames for their specific dance
var windMillDance = [];
var armDance = [];
var manDance = [];
var spinDance = [];
// Array used to store what the dancers can do Example: what dance it does, what frame of the dance it is on, if it is dancing or not
var peopleArray = [];
// Array used to store the specific property within the people Array Example: 0 means it is on the first frame, 7 means it is on the last frame
var whatFrameIsItArray = [];
// Array to store the notes with their specific properties when a key is pressed
var notesArray = [];
// Dictionary to store whether a key is already pressed down when the keys functions are called again.
var wasPressed = {'Q': false, '2': false, 'W': false, '3': false, 'E': false, 'R': false, '5': false, 'T': false, '6': false, 'Y': false, '7': false, 'U': false, 'Z': false, 'S': false, 'X': false, 'D': false, 'C': false, 'V': false, 'G': false, 'B': false, 'H': false, 'N': false, 'J': false, 'M': false};
// Variables to determine width and height of keys
var whiteBottomHeight = 15;
var wKeyW = 40;
var wKeyH = 190;
var blackBottomHeight = 10;
var bKeyW = 30;
var bKeyH = 135;
// Dictionaries used to convert keys to the corresponding information, documented in more detail when they are called in specific functions below
var keyToColorHex = {'Q': '#D8BFD8', '2': '#3C8696', 'W': '#2DD652', '3': '#B632A3', 'E': '#F9EF15', 'R': '#8A36F6', '5': '#D64939', 'T': '#2DEC74', '6': '#B08FD2', 'Y': '#25BFEC', '7': '#EC838D', 'U': '#534EEE', 'Z': '#B41E3C', 'S': '#ECC25F', 'X': '#1DB071', 'D': '#7F75F0', 'C': '#EE853E', 'V': '#CFE397', 'G': '#6EEEDE', 'B': '#EE6EC8', 'H': '#FB5441', 'N': '#7B90D8', 'J': '#EA1D32', 'M': '#91F473'}; 
var whiteKeyToX = {'Q': 20, 'W': 60, 'E': 100, 'R': 140, 'T': 180, 'Y': 220, 'U': 260, 'Z': 300, 'X': 340, 'C': 380, 'V': 420, 'B': 460, 'N': 500, 'M': 540};
var blackKeyToX = {'2': 45, '3': 85, '5': 165, '6': 205, '7': 245, 'S': 325, 'D': 365, 'G': 445, 'H': 485, 'J': 525};
var keyItoClicks = {'Q': 0, '2': 1, 'W': 2, '3': 3, 'E': 4, 'R': 5, '5': 6, 'T': 7, '6': 8, 'Y': 9, '7': 10, 'U': 11, 'Z': 12, 'S': 13, 'X': 14, 'D': 15, 'C': 16, 'V': 17, 'G': 18, 'B': 19, 'H': 20, 'N': 21, 'J': 22, 'M': 23};
// Array used for the respective shapes below and behind the lights to flicker "randomly" (in fact it is just shuffled) when the lights flicker, which make the lights prettier
var discoLighting = [(220,220,220),(211,211,211),(192,192,192),(169,169,169),(128,128,128),(105,105,105),(119,136,153),(112,128,144),(47,79,79),(47,79,79)];
// Preloading in all the sounds and animations for the dances, outputting to console when each respective group of files are ready to play
function preload() {
    cO1 = loadSound("cKeyOctave1.wav");
    dO1 = loadSound("dKeyOctave1.wav");
    eO1 = loadSound("eKeyOctave1.wav");
    fO1 = loadSound("fKeyOctave1.wav");
    gO1 = loadSound("gKeyOctave1.wav");
    aO1 = loadSound("aKeyOctave1.wav");
    bO1 = loadSound("bKeyOctave1.wav");
    console.log("Loaded White Keys Octave 1");
    cSO1 = loadSound("cSharpOctave1.wav");
    dSO1 = loadSound("dSharpOctave1.wav");
    fSO1 = loadSound("fSharpOctave1.wav");
    gSO1 = loadSound("gSharpOctave1.wav");
    aSO1 = loadSound("aSharpOctave1.wav");
    console.log("Loaded Black Keys Octave 1");
    cO2 = loadSound("cKeyOctave2.wav");
    dO2 = loadSound("dKeyOctave2.wav");
    eO2 = loadSound("eKeyOctave2.wav");
    fO2 = loadSound("fKeyOctave2.wav");
    gO2 = loadSound("gKeyOctave2.wav");
    aO2 = loadSound("aKeyOctave2.wav");
    bO2 = loadSound("bKeyOctave2.wav");
    console.log("Loaded White Keys Octave 2");
    cSO2 = loadSound("cSharpOctave2.wav");
    dSO2 = loadSound("dSharpOctave2.wav");
    fSO2 = loadSound("fSharpOctave2.wav");
    gSO2 = loadSound("gSharpOctave2.wav");
    aSO2 = loadSound("aSharpOctave2.wav");
    console.log("Loaded Black Keys Octave 2");
    for (var i = 0; i < 8; i++) {
        windMillDance[i] = loadImage("assets/windmill" + i + ".png");
        armDance[i] = loadImage("assets/Arm" + i + ".png");
        manDance[i] = loadImage("assets/man" + i +".png");
        spinDance[i] = loadImage("assets/spingirl" + i +".png")
    }
    console.log("Loaded Dance Animations");
}
/*************************************************************************************************************************************************************************************
Below are the functions used for this project. Documenting these is somewhat hard because many functions use parts of other functions, in addition to many arrays / dictionaries
*************************************************************************************************************************************************************************************/
// These functions remove the hashtag from the color hexcode, then converts the remaining hexcode into a base 16 number, from which the r,g,b values can be extracted based on
// their respective positions within the resulting number.
// This function converts the hexcode into the r value.
function hexToR(hex) {
    hex = hex.replace(/[^0-9A-F]/gi, '');
    var hexConvert = parseInt(hex,16);
    var r = (hexConvert >> 16) & 255;
    return r;
}
// This function converts the hexcode into the g value.
function hexToG(hex) {
    hex = hex.replace(/[^0-9A-F]/gi, '');
    var hexConvert = parseInt(hex,16);
    var g = (hexConvert >> 8) & 255;
    return g;
}
// This function converts the hexcode into the b value.
function hexToB(hex) {
    hex = hex.replace(/[^0-9A-F]/gi, '');
    var hexConvert = parseInt(hex,16);
    var b = hexConvert & 255;
    return b;
}
// This function's overall purpose is to take in properties it needs from various other parts, then draws a light from the top of the screen down to the corresponding character
// using the x property. It also draws the light in the color corresponding to the key pressed, coming from the colorHex, key, and whatDance properties. Another group of lights
// are also drawn above the speakers in the same color as the overhead lights.
function makeLight(x,colorHex,key,whatDance) {
    stroke(hexToR(keyToColorHex[key]),hexToG(keyToColorHex[key]),hexToB(keyToColorHex[key]),random(80,120));
    strokeWeight(1);
    fill(hexToR(keyToColorHex[key]),hexToG(keyToColorHex[key]),hexToB(keyToColorHex[key]),random(80,120));
//  This next section of code draws the overhead lights
    beginShape();
    var mappedX = map(x,20,540,220,382);
    vertex(mappedX - 2.5,50 + random(-2.5,2.5));
    vertex(mappedX + 2.5,50);
//  The if statements determine the end location of the light, based on what position the dancer is in and what dance it is doing.
    if (peopleArray[whatDance].framesArray == spinDance) {
        var endX = (peopleArray[whatDance].x - 25) * 0.85 + 70;
        var endY = peopleArray[whatDance].y + 20;
        var sizeBeam = 1;
    }
    else if (peopleArray[whatDance].framesArray == armDance) {
        var endX = peopleArray[whatDance].x * 0.7 + 120;
        var endY = peopleArray[whatDance].y - 50;
        var sizeBeam = 0.75;
    }
    else if (peopleArray[whatDance].framesArray == manDance) {
        var endX = peopleArray[whatDance].x * 0.5 + 165;
        var endY = peopleArray[whatDance].y - 90;
        var sizeBeam = 0.5;
    }
    else if (peopleArray[whatDance].framesArray == windMillDance) {
        var endX = peopleArray[whatDance].x * 1.1 + 50;
        var endY = peopleArray[whatDance].y + 100;
        var sizeBeam = 1.3;
    }
    vertex(endX + (25 * sizeBeam), endY);
    vertex(endX - (25 * sizeBeam), endY)
    endShape();
//  This for loop draws the lights in the same color as the overhead light on the tops of the side speakers
    for (var i = 0; i < 5; i++) {
        triangle(20 * i,197,(20 * i) - 13,0,(20 * i) + 15,0);
        triangle(20 * i + 15,194,(20 * i + 15) - 3,0,(20 * i + 15) + 8,0);
        triangle(20 * i + 510,197,(20 * i + 510) - 9,0,(20 * i + 510) + 15,0);
        triangle(20 * i + 495,194,(20 * i + 495) - 7,0,(20 * i + 495) + 13,0);
    }
    noStroke();
}
// This function loads people into an array of size 24, corresponding to the number of keys on the piano. It also loads in the animation frame array, setting each of the dancers
// to the first frame of their 8-frame animation.
function loadAnimationArray() {
    for (var i = 0; i < 24; i++) {
        peopleArray[i] = makePerson(22 * i + 5);
        whatFrameIsItArray[i] = 0;
    }
}
// This function is what creates and returns all the variables needed for the person to know what it needs to. This is the object that is loaded into the peopleArray.
function makePerson(x) {
    var person = {x: x, y: 245 + random(-3,3), framesArray: windMillDance, whatFrameIsIt: whatFrameIsItArray, startDancing: startDancing};
    return person;
}
// This function sets the dances for people in the array. When the array is first made, the frameArray property is set at a default windMillDance as shown above.
function setDancesForPeople() {
//  This for loop iterates through the length of the peopleArray to make sure every dancer's dance is changed accordingly.
    for (var i = 0; i < 24; i++) {
//      These if statements load in the corresponding dance based on the modulus operator Example: every fourth dancer does the spin dance.
        if (i % 4 == 0) {
            peopleArray[i].framesArray = windMillDance;
        }
        else if (i % 4 == 1) {
            peopleArray[i].framesArray = armDance;
        }
        else if (i % 4 == 2) {
            peopleArray[i].framesArray = manDance;
        }
        else if (i % 4 == 3) {
            peopleArray[i].framesArray = spinDance;
        }
    }
}
// This function is called startDancing, because it is the function that actually makes the image on the canvas, but the main purpose is to resize the images and put them into
// their corresponding place based on their dance, because each image was a different size initially.
function startDancing(whatFrame) {
    push();
//  These if statements are what resize the image as described above, and move it to their location based on the x and y properties (an additional hardcoded shift had to be 
//  implemented because of the rescaling of the image).
    if (this.framesArray == manDance) {
        translate(this.x * 0.45 + 160,this.y - 135);
        scale(0.25,0.25);
    }
    else if (this.framesArray == spinDance) {
        translate((this.x - 25) * 0.8 + 45,this.y - 60);
        scale(0.7,0.7);
    }
    else if (this.framesArray == windMillDance) {
        translate(this.x + 25,this.y);
        scale(0.3,0.2);
    }
    else if (this.framesArray == armDance) {
        translate(this.x * 0.7 + 100,this.y - 100);
        scale(0.15,0.1);
    }
    image(this.framesArray[whatFrameIsItArray[whatFrame]], 0, 0);
    pop();
}
// This function updates the dancers to the next frame of their animation, and also updates the numberOfPresses Array.
function checkAndUpdateDancing() {
//  This for loop calls the start dance function, shown above.
    for (var i = 0; i < 24; i++) {
        peopleArray[i].startDancing(i);
//      This if statement increases the number of presses every 5 "actual" presses, because I found the refresh rate to be too fast. Increasing the 5 will slow down the animation
//      Example: changing the 5 to a 10 will approximately halve the animation speed.
        if (numberOfPresses[i] % 5 == 0) {
            numberOfPresses[i]++;
            whatFrameIsItArray[i]++;
//          This if statement ensures the animations smoothly loop, and dont return undefined because each animation is only 8 frames long.
            if (whatFrameIsItArray[i] > 7) {
                whatFrameIsItArray[i] = 0;
            }
        }
    }
}
// This function is what creates and returns a note with all of the properties it needs to know.
function makeNote(x, colorHex) {
    var note = {x: random(5,550), y: 285 + random(-25,25), colorHex: colorHex, size: random(0.2,1), lineFactor: random(1.5,3.5), willItBeRotated: random(0,2), howFastItGoesUp: random(0.7,1.4), opacity: 255, fadeOutChange: random(0.4,3)};
    return note;
}
// This function is what actually displays the notes on the canvas, as well as updates existing notes in the notesArray based on their respective properties, and also updates the
// property that determines how fast it fades out.
function displayNotes() {
//  This function iterates through the notesArray.
    for (var i = 0; i < notesArray.length; i++) {
        strokeWeight(notesArray[i].size * 3.5);
        stroke(hexToR(notesArray[i].colorHex),hexToG(notesArray[i].colorHex),hexToB(notesArray[i].colorHex),notesArray[i].opacity);
        fill(hexToR(notesArray[i].colorHex),hexToG(notesArray[i].colorHex),hexToB(notesArray[i].colorHex),notesArray[i].opacity);
        ellipse(notesArray[i].x,notesArray[i].y,notesArray[i].size * 20,notesArray[i].size * 15);
//      This if statement determines if the note tail will be up or down.
        if (notesArray[i].willItBeRotated < 1) {
            line(notesArray[i].x + (notesArray[i].size * 10),notesArray[i].y,notesArray[i].x + (notesArray[i].size * 10),notesArray[i].y - (notesArray[i].size * 20 * notesArray[i].lineFactor));
            line(notesArray[i].x + (notesArray[i].size * 10),notesArray[i].y - (notesArray[i].size * 20 * notesArray[i].lineFactor),notesArray[i].x + (notesArray[i].size * 15),notesArray[i].y - (notesArray[i].size * 20 * notesArray[i].lineFactor) + (notesArray[i].lineFactor * notesArray[i].size * 10));
        } else {
            line(notesArray[i].x - (notesArray[i].size * 10),notesArray[i].y,notesArray[i].x - (notesArray[i].size * 10),notesArray[i].y + (notesArray[i].size * 20 * notesArray[i].lineFactor));
            line(notesArray[i].x - (notesArray[i].size * 10),notesArray[i].y + (notesArray[i].size * 20 * notesArray[i].lineFactor),notesArray[i].x - (notesArray[i].size * 15),notesArray[i].y + (notesArray[i].size * 20 * notesArray[i].lineFactor) - (notesArray[i].lineFactor * notesArray[i].size * 10));
        }
        notesArray[i].opacity -= notesArray[i].fadeOutChange;
    }
    strokeWeight(1);
}
// This function goes through the notesArray, which stores all the note objects and updates the y coordinate of each note object based on its howFastItGoesUp property. Thus, every
// note has a different speed at which it ascends, leading to a more pleasing visual.
function checkAndUpdateNotes() {
//  This for loop iterates through the notesArray.
    for (var i = 0; i < notesArray.length; i++) {
//      This if statement checks whether the y coordinate of each note needs to be updated.
        if (notesArray[i].y != 0) {
            notesArray[i].y -= notesArray[i].howFastItGoesUp;
        }
    }
}
// This function removes notes that have gone offscreen.
function removeOffScreenNotes() {
//  This for loop iterates through the notesArray.
    for (var i = 0; i < notesArray.length; i++) {
//      This if statement checks whether the vertical coordinate is less than or equal to 0, and if it is, it removes the note from the array. I thought about putting this together
//      with the above function, but if I did that, when the array length changes because of the resulting shift, it might cause some minor errors in the code, so it is safer to 
//      split them up.
        if (notesArray[i].y <= 0) {
            notesArray.shift(notesArray[i]);
        }
    }
}
// This function determines whether the note will come out when the key is pressed (keys are very sensitive and log presses rapidly when the key is held down).
function noteComesOut() {
    var probability = random(0,1);
//  Changing the value in this if statement will change the amount of notes on screen, if you want less clutter.
    if (probability >= 0.5) {
        return true;
    } else {
        return false;
    }
}
// This function is what displays the piano and the steps as well as the speakers at the tops and sides of the canvas. 
function pianoStructureAndLights() {
//  Creating the rectangles that make up the piano.
    rectMode(CORNER);
    noStroke();
    stroke('#8B4513');
    fill('#3f2a14');
    rect(0,397,600,205);
    noStroke();
    fill('#52361b');
    rect(0,360,600,37);
    stroke('#D4AF37');
    fill('#8b5d2e');
    rect(0,260,600,100);
    fill('#2F4F4F');
    stroke('#2F4F4F');
    line(300,0,300,15)
    line(220,15,382,15);
//  This for loop creates the changing greyscale shapes on top of the speakers and behind the overstage lights, as well as the overhead light structure.
    for (var i = 0; i < discoLighting.length; i++) {
        stroke('#2F4F4F');
        line(220 + 18 * i,15,220 + 18 * i,35);
        fill(20);
        rectMode(CENTER);
        rect(220 + 18 * i,35,15,30);
        fill(discoLighting[i]);
        ellipse(220 + 18 * i,50,15,5);
        noStroke();
        fill(discoLighting[i]);
//      This if statement handles the left speaker.
        if (i < 5) {
            rect(20 * i,197,18,7);
            rect(20 * i + 7.5,196,18,7);
            rect(20 * i + 15,194,18,7);
        }
//      This if statement handles the right speaker.
        if (i >= 5) {
            rect(20 * (i - 5) + 510,197,18,7);
            rect(20 * (i - 5) + 502.5,196,18,7);
            rect(20 * (i - 5) + 495,194,18,7);
        }
//      Creates the actual speaker structures on the side speakers.
        rectMode(CORNER);
        stroke('#2F4F4F');
        strokeWeight(2);
        fill(90);
        rect(0,200,81,100);
        quad(82,200,103,193,103,300,82,300);
        rect(509,200,91,100);
        quad(509,200,487,193,487,300,509,300);
        fill('#778899');
        ellipse(32,230,45,45);
        ellipse(559,230,45,45);
        ellipse(32,280,25,25)
        ellipse(559,280,25,25);
        noStroke();
        fill(230);
        ellipse(32,230,5,5);
        ellipse(32,280,3,3);
        ellipse(559,230,5,5);
        ellipse(559,280,5,5);
        strokeWeight(1);
    }
//  Creates the steps the dancers are on.
    rectMode(CORNER);
    stroke('#D4AF37');
    fill('#52361b');
    rect(35,270,530,50);
    rect(95,200,400,40);
    rect(145,160,300,20);
    fill('#8b5d2e');
    quad(35,270,95,240,495,240,565,270);
    quad(95,200,145,180,445,180,495,200);
    quad(145,160,445,160,405,150,185,150); 
}
// This function shuffles an array's contents randomly, and is used to shuffle the greyscale shapes behind the lights for the discoLighting array.
function shuffler(array) {
    var currentPos = array.length, tempValue, randomPos;
    while (currentPos !== 0) {
        randomPos = Math.floor(Math.random() * currentPos);
        currentPos --;
        tempValue = array[currentPos];
        array[currentPos] = array[randomPos];
        array[randomPos] = tempValue;
    }
    return array;
}
// This function displays the white keys on the canvas, and alot of other things documented within the function.
function whiteKeys() {
    rectMode(CORNER);
//  Iterates through the keys found in the whiteKey dictionary, and assigns the corresponding position to the x variable.
    for (var key in whiteKeyToX){
        var x = whiteKeyToX[key];
//      Does alot of stuff when the key is pressed.
        if (keyIsDown(key.charCodeAt(0))){
//          Increases the number of presses of the particular key within the numberOfPresses Array.
            var particularClick = keyItoClicks[key];
            numberOfPresses[particularClick] += 1;
//          Displays the key on the canvas
            fill(255);
            stroke(0);
            rect(x,410,wKeyW,wKeyH);
//          Dictionary to associate the sound variables defined at the beginning with the corresponding key.
            var whiteKeyToSound = {'Q': cO1, 'W': dO1, 'E': eO1, 'R': fO1, 'T': gO1, 'Y': aO1, 'U': bO1, 'Z': cO2, 'X': dO2, 'C': eO2, 'V': fO2, 'B': gO2, 'N': aO2, 'M': bO2};
            var keySound = whiteKeyToSound[key];
//          Plays the actual sound without looping.
            if (!wasPressed[key]) {
                keySound.play();
                wasPressed[key] = true;
            }
//          To reduce clutter on the screen, placed the visuals within the noteComesOut function.
            if (noteComesOut()) {
//              Creates a note object in the notesArray.
                notesArray.push(makeNote(x,keyToColorHex[key]));
//              Makes a light based on the parameters defined within this function.
                makeLight(x,keyToColorHex[key],key,particularClick);
//              Shuffles the discoLighting array everytime a note comes out.
                discoLighting = shuffler(discoLighting);
//              Makes the larger circle at the top of each speaker pulse each time a note comes out.
                strokeWeight(3);
                fill('#778899');
                stroke('#2F4F4F');
                var pulse = random(-5,5);
                ellipse(32,230,50 + pulse,50 + pulse);
                ellipse(559,230,50 + pulse,50 + pulse);
                strokeWeight(1);
                fill(230);
                noStroke();
                ellipse(32,230,5,5);
                ellipse(559,230,5,5);
            }
//      This draws the key in the normal, unpressed position and lets you play the sound again the next time you press the key down.
        } else {
            wasPressed[key] = false;
            fill(255);
            stroke(0);
            rect(x,410,wKeyW,wKeyH - whiteBottomHeight);
            fill(150);
            rect(x,410 + wKeyH - whiteBottomHeight, wKeyW, whiteBottomHeight);
        }
    }
}
// This function displays the black keys on the canvas, and alot of other things documented within the function.
function blackKeys() {
    rectMode(CORNER);
//  Iterates through the keys found in the blackKey dictionary, and assigns the corresponding position to the x variable.
    for (var key in blackKeyToX){
        var x = blackKeyToX[key];
//      Does alot of stuff when the key is pressed.
        if (keyIsDown(key.charCodeAt(0))){
//          Increases the number of presses of the particular key within the numberOfPresses Array.
            var particularClick = keyItoClicks[key];
            numberOfPresses[particularClick] += 1;
//          Displays the key on the canvas
            fill(60);
            stroke(60);
            rect(x,400,bKeyW,bKeyH);
//          Dictionary to associate the sound variables defined at the beginning with the corresponding key.
            var blackKeyToSound = {'2': cSO1, '3': dSO1, '5': fSO1, '6': gSO1, '7': aSO1, 'S': cSO2, 'D': dSO2, 'G': fSO2, 'H': gSO2, 'J': aSO2};
            var keySound = blackKeyToSound[key];
//          Plays the actual sound without looping.
            if (!wasPressed[key]) {
                keySound.play();
                wasPressed[key] = true;
            }
//          To reduce clutter on the screen, placed the visuals within the noteComesOut function.
            if (noteComesOut()) {
//              Creates a note object in the notesArray.
                notesArray.push(makeNote(x,keyToColorHex[key]));
//              Makes a light based on the parameters defined within this function.
                makeLight(x,keyToColorHex[key],key,particularClick);
//              Shuffles the discoLighting array everytime a note comes out.
                discoLighting = shuffler(discoLighting);
//              Makes the larger circle at the top of each speaker pulse each time a note comes out.
                strokeWeight(3);
                stroke('#2F4F4F');
                fill('#778899');
                var pulse = random(-5,5);
                ellipse(32,230,50 + pulse,50 + pulse);
                ellipse(559,230,50 + pulse,50 + pulse);
                fill(230);
                strokeWeight(1);
                noStroke();
                ellipse(32,230,5,5);
                ellipse(559,230,5,5);
            }
//      This draws the key in the normal, unpressed position and lets you play the sound again the next time you press the key down.
        } else {
            wasPressed[key] = false;
            fill(60);
            stroke(60);
            rect(x,400,bKeyW,bKeyH - blackBottomHeight);
            fill(30);
            stroke(30);
            rect(x,400 + bKeyH - blackBottomHeight, bKeyW, blackBottomHeight);
        }
    }
}
// This function shows what keyboard key corresponds to what piano key.
function showControls() {
    textAlign(CENTER,CENTER);
    textSize(20);
//  White keys are displayed with black text.
    fill(0);
    text("Q",40,570);
    text("W",80,570);
    text("E",120,570);
    text("R",160,570);
    text("T",200,570);
    text("Y",240,570);
    text("U",280,570);
    text("Z",320,570);
    text("X",360,570);
    text("C",400,570);
    text("V",440,570);
    text("B",480,570);
    text("N",520,570);
    text("M",560,570);
//  Black keys are displayed with white text.
    fill(255);
    text("2",60,520);
    text("3",100,520);
    text("5",180,520);
    text("6",220,520);
    text("7",260,520);
    text("S",340,520);
    text("D",380,520);
    text("G",460,520);
    text("H",500,520);
    text("J",540,520);
}
// This function is just for clarity and structure.
function piano() {
    whiteKeys();
    blackKeys();
}

function setup() {
    createCanvas(600,602);
//  Loading the numberOfPresses Array to track amount of presses of each key.
    for (var i = 0; i < 24; i++) {
        numberOfPresses[i] = 0;
    }
//  Sets up arrays that need to be present before draw is called.
    loadAnimationArray(); 
    setDancesForPeople();
}

function draw() {
    background(30);
//  Calling corresponding functions documented above.
    pianoStructureAndLights();
    piano();
    checkAndUpdateDancing();
    displayNotes();
    checkAndUpdateNotes();
    removeOffScreenNotes();
//  Visually representing and calling the function to display controls if mouse is hovered over it.
    stroke(220);
    fill(100);
    rect(490,0,120,20);
    textSize(12);
    textAlign(LEFT,BOTTOM);
    stroke(180);
    fill(180);
    text("Hover For Controls",495,17);
    if (mouseX >= 490 & mouseY <= 20) {
        showControls();
    }
}

To run this project, please download the zip file located below, and then create a local server so that the sounds can be loaded properly.

104 final project zip file

This project uses keyboard input to press the corresponding keys, which are shown when hovering over the area displayed in the top right corner of the canvas. When the key is pressed, the corresponding note is played, a note with a color corresponding to the key and varying properties including fadeout rate, vertical speed, and note tail orientation is created. The amount of notes created depends on how long the key is pressed down for. Lights on top of the speakers on each side of the stage and at the top display the
corresponding color, ending at the corresponding dancer location. The dancer at that location also cycles through its animation, and remembers what frame it was on and pauses when the key is not pressed. There are four different types of dances, based on key position. Grey flickering lights, somewhat resembling a glowing disco ball (which is why its called discoLighting in the code) also randomly flicker both above the speakers and at the top behind the lights when a key is pressed.

This project uses free sprite sheets available for download at: https://www.deviantart.com/gladkirby7/art/Vexy-Dances-FruityDance-Sprite-Sheet-675963897 for the arm dance and windmill dance. https://www.pinterest.com/pin/525021269044190160/?lp=true for the spin dance.

https://blueprintinteractive.com/sites/default/files/images/carlton_sprites2.png for the man dance.

The sounds for each piano key were downloaded from https://freesound.org/people/jobro/packs/2489/.

We learned a lot about code structure when making this, and also about how useful dictionaries are for converting information in an organized way. Overall we are pretty pleased with the project and how it can handle many keys being pressed without slowing down at all.

Please see the note in the comments at the top of the code for how we would like to be graded on this project.

Kevin Thies – Final Project

Dungeon Crawl

var debugMode = false;  // toggles debug mode
var char;               // stores the character
var missiles = [];      // stores the magic missiles
var enemies = [];       // stores the enemies
var rooms = [];         // stores the rooms
var roomKeys = [];      // sotres the "name" of the room
var roomEnemies = [];   // stores the leftover enemies of each room
var lastUsedFloorSeed = 1;  // previous floor seed used
var currentRoom = "C";  // room id of current room
var directions = ["N", "E", "S", "W"]; // used in generating doors
var previousDir = "C";  // initiates with the previous value of C
var mouseCounter = 0;   // how long has mouse been held for?
var spread = 30;        // spread of the angle for magic missiles
var fireInterval = 5;   // how often are missiles shot
var manaGain = 1.5;     // how much mana regained per frame
var manaLost = 20;      // mana expended to shoot a magic missile
var hitDamage = 1;      // damage player takes if hit
var moveSpeed = 1.7;    // how fast the player moves
var enemySpeed = 1.5;   // how fast enemies move
var hitRadius = 15;     // radius of enemy hitbox
var hitsToKill = 20;    // how many hits an enemy needs to be killed
var doorLimit;          // max amount of doors that can spawn
var floorsCleared = 0;  // number of floors the player has cleared
var deathCounter = 0;   // a timer after the player dies
var fadeCounter = 0;    // the counter for fade out
var shouldFade = false; // should the screen fade?
var shotsFired= 0;      // holds how many shots were fired
var enemiesSlain = 0;   // holds how many skeletons were slain
var enemiesAlive = 0;   // holds how many enemies in room are alive



function preload() {    // load the textures
    dungeonFloor = loadImage("https://i.imgur.com/VjkGIfD.jpg");
    dungeonWalls = loadImage("https://i.imgur.com/z7qxLHe.png");
    northDoor = loadImage("https://i.imgur.com/KmQ5l5A.png");
    southDoor = loadImage("https://i.imgur.com/PS56sKG.png");
    westDoor = loadImage("https://i.imgur.com/UvWhm3u.png");
    eastDoor = loadImage("https://i.imgur.com/KNxPK3v.png");
    hole = loadImage("https://i.imgur.com/2CX6TWN.png");
    holeLight = loadImage("https://i.imgur.com/GR17LVV.png");
    wizard = loadImage("https://i.imgur.com/3eZz0KO.png");
    wizardNoMagic = loadImage("https://i.imgur.com/PI6dWjQ.png");
    wizardHurt = loadImage("https://i.imgur.com/OF8kDJn.png");
    staffGlow = loadImage("https://i.imgur.com/FaP3USD.png");
    missileGlow = loadImage("https://i.imgur.com/kz9j30X.png");
    deadSkeleton = loadImage("https://i.imgur.com/XPcZiRJ.png");
    skeleton = loadImage("https://i.imgur.com/MmpBxbE.png");
    skeletonHit = loadImage("https://i.imgur.com/KxqTfbM.png");
}

function setup() {
    createCanvas(480, 480);
    angleMode(DEGREES);
    noCursor();              // hide cursor so that we can replace it with a custom one
    makeCharacter(240, 240); // starts character in the center of the canvas
    doorLimit = floor(random(6, 11)); // random door limit at the start

    // generate the initial center room
    centerRoom = {"floorSeed":1,
            "doorSeed":1,
            "directionEntered":"C",
            "north":true, "south":false, "east":false, "west":false,
            "final":false, "enemiesInRoom":[]
            };

    // push the room into the room array
    rooms.push(centerRoom);
    // push the room key into the room key array
    roomKeys.push(currentRoom);
    // push 0 enemies into the first room
    roomEnemies.push(enemies);
    // fade in
    shouldFade = true;
    fadeCounter = 120;
}

function draw() {
    background(0);      // backup in case textures don't load

    drawRoom();         // draws the room
    enemyUpdate();      // updates enemies in room
    missileUpdate();    // updates the magic missiles
    characterUpdate();  // updates the character
    cursorUpdate();     // updates the cursor
    doorCheck();        // checks if a player can go thorugh a door
    deathCheck();       // checks if the player has died
    fadeOut();          // checks if fadeout should execute
}

function fadeOut() { // fades the screen to black and back

    // applies a red overlay based on character health
    strokeWeight(0);
    fill(255, 0, 0, map(char.health, 70, 0, 0, 100));
    // if the character is dead, stop the red
    if(char.health <= 0){
        fill(0, 0, 0, 0);
    }
    rect(0, 0, width, height);

    // counts the fade counter up to 128 if fade is started
    if(fadeCounter < 128 & shouldFade === true) {
        fadeCounter ++;
    }
    // after it reaches that point, fade is set back to false
    if(fadeCounter >= 128) {
        shouldFade = false;
    }
    // if fade is false, decrement the counter
    if(shouldFade === false) {
        fadeCounter --;
    }
    // add a black box of darkening opacity to fade out
    fill(0, 0, 0, fadeCounter * 2);
    rect(0, 0, width, height);
}

function deathCheck() { // checks to see if the character has died
    // grabs the index of the current room
    // needs to be a local variable or it just doesn't work
    var roomIndex = roomKeys.indexOf(currentRoom);

    push();
    // if character is dead:
    if(char.health <= 0) {
        // increment death counter
        deathCounter++;
        // do a special death fadeout with a black box
        strokeWeight(0);
        fill(0, 0, 0, deathCounter * 2);
        rect(0, 0, width, height);
    }

    // after a certain amount of time to fade:
    if(deathCounter > 120) {
        // fade in text that the character has died
        textAlign(CENTER);
        textSize(32);
        strokeWeight(0);
        textStyle(BOLD);
        fill(255, 0, 0, (deathCounter - 120));
        text("You have Died", width / 2, 150);
        // format the bottom text
        fill(0);
        textSize(14);
        strokeWeight(1.3);
        stroke(255, 0, 0, (deathCounter - 240));
        textStyle(ITALIC);
        // fade in how many floors were cleared
        if (floorsCleared === 1) {
            text(floorsCleared + " floor freed from evil's grasp", width / 2, 200);
        } else {
            text(floorsCleared + " floors freed from evil's grasp", width / 2, 200);
        }
        // fade in how many magic missiles were cast
        stroke(255, 0, 0, (deathCounter - 360));
        text("Magic missile was cast " + shotsFired + " times", width / 2, 230);
        // fade in how many enemies were defeated
        stroke(255, 0, 0, (deathCounter - 480));
        text(enemiesSlain - enemiesAlive + " of evil's minions were put to rest", width / 2, 260);
        // after a while, offer the option to replay
        textSize(18);
        strokeWeight(0);
        textStyle(BOLD);
        fill(255, 0, 0, (deathCounter - 800));
        text("press space to play again", width / 2, 300);
    }
    pop();
}

function doorCheck() { // checks if a player can enter a door or hole
    // grabs the index of the current room
    var roomIndex = roomKeys.indexOf(currentRoom);
    // sets initial value if all enemies have been defeated
    var allEnemiesDefeated = true;
    // for all enemies in room:
    for(var i = 0; i < rooms[roomIndex].enemiesInRoom.length; i++) {
        // if one is alive, allEnemiesDefeated is false
        if(rooms[roomIndex].enemiesInRoom[i].life < 100) {
            allEnemiesDefeated = false;
        }
    }

    // if the character is near and going in the direction of a door
    // change the room in that direction
    if(dist(char.x, char.y, width / 2, 0) < 60 &
            char.up === true &&
            rooms[roomIndex].north === true &&
            allEnemiesDefeated === true) {
        char.y = 440;
        changeRoom("N");
    }
    if(dist(char.x, char.y, width / 2, height) < 60 &
            char.down === true &&
            rooms[roomIndex].south === true &&
            allEnemiesDefeated === true) {
        char.y = 40;
        changeRoom("S");
    }
    if(dist(char.x, char.y, 0, height / 2) < 60 &
            char.left === true &&
            rooms[roomIndex].west === true &&
            allEnemiesDefeated === true) {
        char.x = 440;
        changeRoom("W");
    }
    if(dist(char.x, char.y, width, height / 2) < 60 &
            char.right === true &&
            rooms[roomIndex].east === true &&
            allEnemiesDefeated === true) {
        char.x = 40;
        changeRoom("E");
    }
    if(dist(char.x, char.y, width / 2, height / 2) < 30 &
            rooms[roomIndex].final === true &&
            allEnemiesDefeated === true) {
        resetFloor();
    }
}

function drawRoom() { // draws the room each update cycle

    // grabs the index of the current room
    var roomIndex = roomKeys.indexOf(currentRoom);

    // displays floor based on floorseed
    if(rooms[roomIndex].floorSeed === 1) {
        image(dungeonFloor, 0, 0, width, height);

    } else if (rooms[roomIndex].floorSeed === 2) {
        push();
        rotate(90);
        image(dungeonFloor, 0, -1 * height, width, height);
        pop();

    } else if (rooms[roomIndex].floorSeed === 3) {
        push();
        rotate(180);
        image(dungeonFloor, -1 * width, -1 * height, width, height);
        pop();

    } else if (rooms[roomIndex].floorSeed === 4) {
        push();
        rotate(270);
        image(dungeonFloor, -1 * width, 0, width, height);
        pop();
    }

    // displays walls
    image(dungeonWalls, 0, 0, width, height);

    // display doors
    if(rooms[roomIndex].north === true) {
        image(northDoor, 0, 0, width, height);
    }

    if(rooms[roomIndex].south === true) {
        image(southDoor, 0, 0, width, height);
    }

    if(rooms[roomIndex].west === true) {
        image(westDoor, 0, 0, width, height);
    }

    if(rooms[roomIndex].east === true) {
        image(eastDoor, 0, 0, width, height);
    }

    // display hole if final room
    if(rooms[roomIndex].final === true) {
        image(hole, 0, 0, width, height);
    }

    // display light if first room
    if(currentRoom === "C") {
        image(holeLight, 0, 0, width, height);
    }

    // debug options
    if(debugMode === true) {
        strokeWeight(0);
        fill(255);
        stroke(255);
        text("room ID = " + currentRoom, 20, 20);
        text("rooms left = " + doorLimit, 20, 30);
        text("north = " + rooms[roomIndex].north, 20, 40);
        text("south = " + rooms[roomIndex].south, 20, 50);
        text("east = " + rooms[roomIndex].east, 20, 60);
        text("west = " + rooms[roomIndex].west, 20, 70);
        text("final = " + rooms[roomIndex].final, 20, 80);
        text("Number of enemies = " + rooms[roomIndex].enemiesInRoom.length, 20, 400);
        text("floors cleared = " + floorsCleared, 20, 90);
    }
}

function changeRoom(directionEntered) { // if players enter a door, change the room

    // set a random floor seed to determine the floor image rotation
    var floorSeed = ceil(random(0, 4));

    // make sure the last floor seed is different from the new one
    while(floorSeed === lastUsedFloorSeed) {
        floorSeed = ceil(random(0, 4));
    }

    // reset the last used floor seed
    lastUsedFloorSeed = floorSeed;

    // Add doorseed based on how many are left
    var doorSeed = ceil(random(0, 3));

    // This giant block manages room ids, if you enter a new room that direction
    // is added to the string for the name, and removes the last letter of the string
    // if you backtrack into a previously explored room
    if(directionEntered === "N" & previousDir === "S") {
        currentRoom = currentRoom.substr(0, currentRoom.length - 1);

    } else if(directionEntered === "S" & previousDir === "N") {
        currentRoom = currentRoom.substr(0, currentRoom.length - 1);

    } else if(directionEntered === "E" & previousDir === "W") {
        currentRoom = currentRoom.substr(0, currentRoom.length - 1);

    } else if(directionEntered === "W" & previousDir === "E") {
        currentRoom = currentRoom.substr(0, currentRoom.length - 1);

    // if the current room has not been entered, append the
    // direction entered to the end of current room
    } else {
        currentRoom = currentRoom + directionEntered;
    }

    // sets previous direction to the direction used to enter the room
    previousDir = currentRoom.substring(currentRoom.length - 1, currentRoom.length);

    // sets initial door values
    var doorNorth = false;
    var doorSouth = false;
    var doorEast = false;
    var doorWest = false;
    var doorFinal = false;

    // if the room entered doesn't exist yet:
    if(roomKeys.indexOf(currentRoom) == -1) {

        // subrtact one from the remaining rooms to generate
        doorLimit --;

        // set the starting position of where the player entered
        var initial = directions.indexOf(directionEntered);

        // generates a random door based on where the character entered
        // and the door seed
        var doorNum = initial + 2 + doorSeed;

        // make sure doorNum is between 0 and 3
        while(doorNum > 3) {
            doorNum -= 4;
        }

        // ties the door num to a direction string
        var doorToDisplay = directions[doorNum];

        // if it's the final room of the floor:
        if(doorLimit === 0) {

            // make sure the room know's it's the final one
            doorFinal = true;

            // generate the final door on the same side as would enter from
            var finalDoorNum = initial + 2;
            while(finalDoorNum > 3) {
                finalDoorNum -= 4;
            }

            // tie the final door number to a direction string
            doorToDisplay = directions[finalDoorNum];
        }

        // take the door to display as well as the door the character came from
        // and add those properties to the new room
        if(doorToDisplay === "N" || directionEntered === "S") {
            doorNorth = true;
        }
        if(doorToDisplay === "S" || directionEntered === "N") {
            doorSouth = true;
        }
        if(doorToDisplay === "E" || directionEntered === "W") {
            doorEast = true;
        }
        if(doorToDisplay === "W" || directionEntered === "E") {
            doorWest = true;
        }

    }

    // add a new room to the room array
    rooms.push(makeNewRoom(doorSeed, floorSeed, directionEntered,
                           doorNorth, doorSouth, doorEast, doorWest,
                           doorFinal));

    // add the room ID to the list of roomKeys
    roomKeys.push(currentRoom);

    // sets local room index
    var roomIndex = roomKeys.indexOf(currentRoom);

    // resets the arrays for missiles and enemies, clearing the room
    missiles = [];
    enemies = [];

    // generate new enemies
    if(doorFinal === false & roomKeys.indexOf(currentRoom) === roomKeys.length - 1) {
        for(var i = 0; i < (random(2, 4 + floorsCleared) + floorsCleared); i++) {
            rooms[roomIndex].enemiesInRoom.push(makeEnemy(random(100, 380), random(100, 380), 0, 0));
            // rotate random directions
            rooms[roomIndex].enemiesInRoom[i].right(degrees(random(360)));
            // set random speed that increases each floor
            rooms[roomIndex].enemiesInRoom[i].speed = random(1.3, 1.8) + 0.05 * floorsCleared;
            // set random move mode
            rooms[roomIndex].enemiesInRoom[i].moveMode = floor(random(2));
        }
    }
}

function makeNewRoom(doorSeed, floorSeed, directionEntered,
                     north, south, east, west,
                     final) { // makes the room

    room = {"floorSeed":floorSeed,
            "doorSeed":doorSeed,
            "directionEntered":directionEntered,
            "north":north, "south":south, "east":east, "west":west,
            "final":final,
            "enemiesInRoom":[]
            };
    return room;
}

function resetFloor() { // resets the floor if cleared

    // heal some of the character's health
    char.health += floor(((100 - char.health) * 3 / 5));

    // reset floor containers
    missiles = [];
    enemies = [];
    rooms = [];
    roomKeys = [];

    previousDir = "C";  // initiates with the previous value of C
    currentRoom = "C";  // current room is the center room

    // make a new door limit
    doorLimit = floor(random(6, 11));

    // add 1 to number of floors cleared
    floorsCleared ++;

    // define the new center room of the floor
    centerRoom = {"floorSeed":1,
            "doorSeed":1,
            "directionEntered":"C",
            "north":true, "south":false, "east":false, "west":false,
            "final":false,
            "enemiesInRoom":[]
            };

    // push the room into the rooms array
    rooms.push(centerRoom);

    // push the room ID into the room keys array
    roomKeys.push(currentRoom);

    // push 0 enemies into the array
    roomEnemies.push(enemies);

    // execute fade out
    shouldFade = true;
    fadeCounter = 0;

    // position character to the center of the room
    char.x = width / 2;
    char.y = height / 2;
}

function makeCharacter(x, y) { // creates the character object
    char = {"x":x, "y":y,
            "left":false, "right":false,
            "up":false,  "down":false,
            "facing":0, "health":100, "mana":100,
            "isHit":false
            };
    return char;
}

function characterUpdate() { // updates the character's movement

    // if the movement key is pressed, set the appropriate character move state
    if (keyIsDown(68) || keyIsDown(RIGHT_ARROW)) { // D or right arrow
        char.right = true;
    } else {
        char.right = false;
    }

    if (keyIsDown(65) || keyIsDown(LEFT_ARROW)) { // A or left arrow
        char.left = true;
    } else {
        char.left = false;
    }

    if (keyIsDown(83) || keyIsDown(DOWN_ARROW)) { // S or down arrow
        char.down = true;
    } else {
        char.down = false;
    }

    if (keyIsDown(87) || keyIsDown(UP_ARROW)) { // W or up arrow
        char.up = true;
    } else {
        char.up = false;
    }

    // Face the mouse
    push();
    translate(char.x, char.y);

    // this gets an angle between 0 and 360
    char.facing = ((atan2(mouseY - char.y, mouseX - char.x) + 360)% 360);
    pop();

    // the update moves and draws the character
    characterMove();
    characterDraw();

    // regain mana
    char.mana = min(100, char.mana += manaGain)
    return;
}

function characterMove() { // moves the character

    // if it's not fading and the character is alive:
    if(deathCounter === 0 & shouldFade === false) {

        // if the movement states are true, move by a set speed
        // move linearly slightly faster
        if(char.right === true && char.up === false && char.down === false) {
            char.x += moveSpeed * sqrt(2);
        }

        if(char.left === true & char.up === false && char.down === false) {
            char.x -= moveSpeed * sqrt(2);
        }

        if(char.up === true & char.left === false && char.right === false) {
            char.y -= moveSpeed * sqrt(2);
        }

        if(char.down === true & char.left === false && char.right === false) {
            char.y += moveSpeed * sqrt(2);
        }

        // move diagonally
        if(char.right === true & char.up === true && deathCounter === 0) {
            char.x += moveSpeed;
            char.y -= moveSpeed;
        }

        if(char.right === true & char.down === true && deathCounter === 0) {
            char.x += moveSpeed;
            char.y += moveSpeed;
        }

        if(char.left === true & char.up === true && deathCounter === 0) {
            char.x -= moveSpeed;
            char.y -= moveSpeed;
        }

        if(char.left === true & char.down === true && deathCounter === 0) {
            char.x -= moveSpeed;
            char.y += moveSpeed;
        }
    }

    // limit x and y coords to inside the walls
    char.x = constrain(char.x, 40, 440);
    char.y = constrain(char.y, 40, 440);

}

function characterDraw() { // draws the character
    // style
    fill(255);
    stroke(5);
    strokeWeight(2);

    // glow under character
    image(missileGlow, char.x - 120 + sin((char.facing + 247) * -1) * 40,
                         char.y - 120 + cos((char.facing + 247) * -1) * 40,
                         240, 240);

    // Main Character //  //  //  //  //  //  //  //  //  //
    push();

    // make the main character the origin
    translate(char.x, char.y);

    // rotate the character based on its angle
    rotate(char.facing - 80);

    // display ,agic hand if shooting magic missiles or normal if not
    if(mouseIsPressed) {
        image(wizard, -60, -45, 120, 120);
    } else {
        image(wizardNoMagic, -60, -45, 120, 120);
    }

    // if the character is hit display a red overlay
    if(char.isHit === true) {
        image(wizardHurt, -60, -45, 120, 120);
    }

    // make the staff glow based on how much mana the character has
    push();
    tint(255, map(char.mana, 0, 100, 0, 180))
    image(staffGlow, -32, 4, 20, 20);
    pop();

    // reset the main character push
    pop();

    // Debugging Options
        // if debug is true, display:
    strokeWeight(2);
    if(debugMode === true) {
        stroke(255);
        line(char.x, char.y, mouseX, mouseY);
        stroke(0);
        // what direction is the character facing?
        text("Facing:" + floor(char.facing), char.x + 20, char.y + 10);
        // what are its coordinates?
        text("X:" + floor(char.x) + " Y:" + floor(char.y), char.x + 20, char.y + 20);
        // if the mouse is pressed, what's the mouse counter?
        if(mouseIsPressed) {
            text("mouseCounter:" + mouseCounter, char.x + 20, char.y + 30);
        }
        // how much mana does it have?
        text("Mana:" + char.mana, char.x + 20, char.y + 40);
        // how much health?
        text("Health:" + char.health, char.x + 20, char.y + 50);
        // is the character hit?
        text("isHit:" + char.isHit, char.x + 20, char.y + 60);
    }
}

function makeEnemy(ex, ey, cx, cy) {  // creates the enemy object
    // make a turtle at the enemy coordinates with proxy target coords
    enemy = makeTurtle(ex, ey, cx, cy);

    enemy.angle = 1;
    enemy.penUp();
    enemy.setWeight(4);
    enemy.setColor(255);

    // add 1 to total amount of enemies slain
    enemiesSlain ++;

    return enemy;
}

function enemyUpdate() { // updates the enemies' movement

    // sets local room index
    var roomIndex = roomKeys.indexOf(currentRoom);

    // set initial player collision value
    var hasBeenHit = false;

    // set initial enemies alive value
    enemiesAlive = 0;

    // go through all the enemies
    for(var i = 0; i < rooms[roomIndex].enemiesInRoom.length; i++) {
        // if the enemy is alive:
        if(rooms[roomIndex].enemiesInRoom[i].life < 100) {

            // add 1 to enemiesAlive
            enemiesAlive ++;

            // face the character using a more aggressive, snappy formula
            if (rooms[roomIndex].enemiesInRoom[i].moveMode === 0) {
                rooms[roomIndex].enemiesInRoom[i].face(degrees((atan2(
                    char.y - rooms[roomIndex].enemiesInRoom[i].y,
                    char.x - rooms[roomIndex].enemiesInRoom[i].x) + 360)% 360));
            // or use the short interesting way where enemies wander
            } else if(rooms[roomIndex].enemiesInRoom[i].moveMode === 1) {
                rooms[roomIndex].enemiesInRoom[i].right(rooms[roomIndex].enemiesInRoom[i].angleTo(char.x, char.y));
            }

            // move forward
            rooms[roomIndex].enemiesInRoom[i].forward(rooms[roomIndex].enemiesInRoom[i].speed);

            // constrain enemy movement
            rooms[roomIndex].enemiesInRoom[i].x = constrain(rooms[roomIndex].enemiesInRoom[i].x, 40, 440);
            rooms[roomIndex].enemiesInRoom[i].y = constrain(rooms[roomIndex].enemiesInRoom[i].y, 40, 440);


            // display as alive
            push();
            // make the enemy the origin
            translate(rooms[roomIndex].enemiesInRoom[i].x, rooms[roomIndex].enemiesInRoom[i].y);
            // rotate based on its angle
            rotate(radians(rooms[roomIndex].enemiesInRoom[i].angle) - 90);
            // display the monster image
            image(skeleton, -60, -55, 120, 120);
            pop();

        } else { // if the enemy is dead
            // display as dead
            push();
            // make the enemy the origin
            translate(rooms[roomIndex].enemiesInRoom[i].x, rooms[roomIndex].enemiesInRoom[i].y);
            // rotate based on angle
            rotate(radians(rooms[roomIndex].enemiesInRoom[i].angle) - 90);
            // display the dead monster image
            image(deadSkeleton, -60, -60, 120, 120);
            pop();
        }

        // test if missile collides
        // for all missiles:
        for(var j = 0; j < missiles.length; j++) {
            // if the enemy is less than hitRadius from the missile:
            if(dist(rooms[roomIndex].enemiesInRoom[i].x, rooms[roomIndex].enemiesInRoom[i].y,
                    missiles[j].x, missiles[j].y) < hitRadius) {
                // add a portion of 100 based on the hitsToKill variable
                rooms[roomIndex].enemiesInRoom[i].life += 100 / hitsToKill;
                // if an enemy has less than 100 life (aka is alive):
                if(rooms[roomIndex].enemiesInRoom[i].life < 100){
                    // display the hurt skeleton
                    push();
                    // make the skeleton the origin
                    translate(rooms[roomIndex].enemiesInRoom[i].x, rooms[roomIndex].enemiesInRoom[i].y);
                    // rotate based on enemy angle
                    rotate(radians(rooms[roomIndex].enemiesInRoom[i].angle) - 90);
                    // display the white overlay on the skeleton
                    image(skeletonHit, -60, -55, 120, 120);
                    pop();
                }
            }
        }

        // test if it hits player
        // if the enemy x and y is withing hitRadius from the character x and y:
        if(dist(rooms[roomIndex].enemiesInRoom[i].x, rooms[roomIndex].enemiesInRoom[i].y, char.x, char.y) < hitRadius &
                rooms[roomIndex].enemiesInRoom[i].life < 100) {
            // character health goes down by hit damage
            char.health -= hitDamage;
            // character hasBeenHit becomes true to display red overlay
            hasBeenHit = true;
        }

        // if too far away, change to aggressive move mode
        if(dist(rooms[roomIndex].enemiesInRoom[i].x, rooms[roomIndex].enemiesInRoom[i].y, char.x, char.y) > width * 2 / 3) {
            rooms[roomIndex].enemiesInRoom[i].moveMode = 0;
        }

        // if too close, change to passive move mode
        if(dist(rooms[roomIndex].enemiesInRoom[i].x, rooms[roomIndex].enemiesInRoom[i].y, char.x, char.y) < hitRadius) {
            rooms[roomIndex].enemiesInRoom[i].moveMode = 1;
        }

        // Debug Options
        // if debugMode is on:
        if(debugMode === true) {
            // display
            strokeWeight(1);
            stroke(255, 0, 0);
            fill(0, 0, 0, 0);
            // what is the enemy's angle?
            text("Angle:" + floor(radians(rooms[roomIndex].enemiesInRoom[i].angle)),
                 rooms[roomIndex].enemiesInRoom[i].x + 20,
                 rooms[roomIndex].enemiesInRoom[i].y + 20);
            // what is the enemy's health?
            text("Health:" + floor(rooms[roomIndex].enemiesInRoom[i].life),
                 rooms[roomIndex].enemiesInRoom[i].x + 20,
                 rooms[roomIndex].enemiesInRoom[i].y + 30);
            // what is the enemy's angle to player?
            text("angle2Player:" + floor(rooms[roomIndex].enemiesInRoom[i].angleto(char.x, char.y)),
                 rooms[roomIndex].enemiesInRoom[i].x + 20,
                 rooms[roomIndex].enemiesInRoom[i].y + 10);
            // draw an ellipse to show the hitRadius of the enemy
            ellipse(rooms[roomIndex].enemiesInRoom[i].x, rooms[roomIndex].enemiesInRoom[i].y, hitRadius * 2, hitRadius * 2);
          }
    }

    // if any enemy in the room has hit the player:
    if(hasBeenHit === true){
        // isHit is true
        char.isHit = true;
    } else {
        // isHit is false
        char.isHit = false;
    }
}

function cursorUpdate() { // replaces the mouse cursor with a new one
    // displays a custom "cursor" over the mouse position
    // since the mouse cursor is hidden in setup
    fill(0, 0, 0, 0);
    stroke(255, 0, 0);
    strokeWeight(2);
    ellipse(mouseX, mouseY, 8, 8);
}

function missileUpdate() { // updates the magic missiles

    // if the mouse is pressed and char alive, spawn a missile every interval
    if(mouseIsPressed & deathCounter === 0) {
        // increment mouseCounter, which is the number of frames the mouse has
        // been held down for
        mouseCounter++;

        // every interval of frames after (if there's mana)
        if(mouseCounter % fireInterval == 0 & char.mana > 0) {
            // push the new missile into the array
            missiles.push(makeMissile(char.x, char.y,
                                      mouseX, mouseY,
                                      char.facing));
        }
    } else { // if mouse isn't pressed, then reset the mouse counter
        mouseCounter = 0;
    }

    // for each missile:
    for(var i = 0; i < missiles.length; i++) {
        // after a certain "age" the missile will be pushed out of the array
        // increment age
        missiles[i].life++;

        // guide missile towards its target, turning more the closer it is
        missiles[i].turnToward(missiles[i].tx, missiles[i].ty,
                                   map(abs(missiles[i].angleTo(missiles[i].tx, missiles[i].ty)),
                                        200, 0, 0, 320));

        // draw the missile tail
        fill(0, 255, 255, 99);
        strokeWeight(2);
        stroke(0, 255, 255, 0.9);
        ellipse(missiles[i].x, missiles[i].y, 10, 10);

        // move missile
        missiles[i].forward(8);

        // draw the missile glow
        image(missileGlow, missiles[i].x - 60, missiles[i].y - 60, 120, 120);

        // draw missile head
        fill("White");
        strokeWeight(0);
        stroke(0, 255, 255, 0.9);
        ellipse(missiles[i].x, missiles[i].y, 5, 5);


        // cull old missiles
        if(missiles[i].life > 70) {
            missiles.shift();
        }
    }
}

function makeMissile(cx, cy, tx, ty, d) { // creates the magic missiles
    // make a turtle at the character and with the target coords
        // there's a bunch of math to make sure they come out of the
        // magic aura around his hand
    missile = makeTurtle(cx + sin((d + 247) * -1) * 40,
                         cy + cos((d + 247) * -1) * 40,
                         tx, ty);

    // add a random offset to the launch trajectory
    var offset = random(-1 * spread, spread);

    // launch the missile
    missile.right(degrees(d + offset));
    missile.penUp();

    // add 1 to total shots fired
    shotsFired ++;

    // character looses mana when a missile is shot
    char.mana -= manaLost;
    return missile;
}

function keyPressed() { // turns on debug mode when = is pressed
    // if the "=" key is pressed, which I found was 187 or 69
    if(keyCode === 187 || keyCode === 61) {
        // if debug is on, turn it off
        if (debugMode === true) {
            debugMode = false;
        // if debug is off, turn it on
        }  else {
        debugMode = true;
        }
    }

    // if spacebar is pressed at the end of the death sequence
    if(deathCounter > 800 & keyCode === 32) {
        // reset the floor
        resetFloor();
        // reset values that are not reset usually when a floor is
        fadeCounter = 120;
        deathCounter = 0;
        floorsCleared = 0;
        char.health = 100;
        enemiesSlain = 0;
        shotsFired = 0;
    }

    // if "e" is pressed while debugMode is on, spawn an enemy at the mouse
    if(keyCode === 69 & debugMode === true) {
        var roomIndex = roomKeys.indexOf(currentRoom);
        rooms[roomIndex].enemiesInRoom.push(makeEnemy(mouseX, mouseY, 0, 0));
    }

    // if ENTER is pressed while debugMode is on, set doorLimit to 1,
    // making sure the next room is the final room
    if(keyCode === ENTER & debugMode === true) {
    doorLimit = 1;
    }

    // if SHIFT is pressed while debugMode is on, increment floorsCleared
    if(keyCode === SHIFT & debugMode === true) {
    floorsCleared ++;
    }
}



// turtle code has and added "tx" and "ty", which are coordinate points
// that are used to make the magic missiles home in on the clicked location.
// also have a "Life" that tracks how many frames it's been on screen for
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,targetx,targety){var turtle={x:tx,y:ty,
angle:0.0,penIsDown:true,color: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,
tx:targetx, ty:targety, life:0, speed:1, moveMode:0};
return turtle;}

You can move with W,A,S,D or the arrow keys. Click to cast Magic Missile!
Tips and things to note:
The glow on your staff indicates how much mana you have. If you’re shooting slowly, let up on the firing and just let it recharge
The magic missiles bank towards where your mouse is when you fire them. Aim slightly ahead of enemies for best results

I might have gone a bit overkill.
When I first saw turtle graphics I was like “Huh, you could make a cool magic missile effect”
This game is built completely around that.

To briefly go over the game itself, it’s a dungeon crawler. The player goes into randomly generated rooms with randomly generated enemies which make up floors of random length. As the player progresses through the floors, the average enemy speed and number in a room will gradually increase. Paired with the player’s health that only partially returns when a floor is completed, it’s certain that the player will die. The question then is how far can they make it?

To help me troubleshoot the project, I added in a ‘debug mode’ of sorts. By pressing ‘=’, the player can see a lot of cheaty data, spawn enemies, generate the final room of each floor, and increase how many floors the game thinks the player has cleared.
There were a lot of challenges. I wasn’t sure how to do level generation (the system can easily do non-linear levels by generating more doors but linear is easy to play through) and for a while, enemy AI was giving me some problems.

In the end, I’m more than happy with what I made. I enjoy playing it, I enjoyed making it, and I enjoy changing some of the settings to make the game truly ridiculous.
Also the visual assets are either made by me or heavily changed free assets

Final Project – Erin Fuller

My Final Project was a maze generator that you can solve!

How to Play: Wait for the maze generator to run in the background, once the loading screen is gone you are ready to play. Use your arrow keys to navigate the green dot through the maze to the red dot.

//Erin Fuller
//Section A
//efuller@andrew.cmu.edu
//Final Project

//This Maze Generator + Game uses first depth search and recursive backtracking
//to generate the maze.  

//https://en.wikipedia.org/wiki/Maze_generation_algorithm#Depth-first_search
//https://en.wikipedia.org/wiki/Maze_generation_algorithm#Recursive_backtracker

var cols, rows; // dividing canvas into grid array
var w = 30; // cell width
var grid = []; 

var sX = 0; // solver x position
var sY = 0; // solver y position

var distance = 0, longdist = 0;
var longest;
var current;
var mazeGen = false; //maze generator not finished

var stack = []; // stack of current cells maze is genrating

// wall array directions 
var up = 0; 
var right = 1;
var down = 2;
var left = 3;

function setup() {
    createCanvas(480, 480);
    cols = width / w;
    rows = height / w;
    for (var i = 0; i < rows; i++) {
        for (var j = 0; j < cols; j++) {
            var cell = new Cell(j, i); //creates cell object
            grid.push(cell); //pushes object into grid array
        }
    }
    current = grid[0]; //starts grid at top left corner
    current.visited = true;
}

function draw() {
    mazeDraw(); 
    if (mazeGen) { 
        solve(); //when maze is finsihed generating it calls a solve function
    } else {
        preText(); //shows instruction while maze is generating
    }
}

function mazeDraw() {
    background(165, 202, 218); //LIGHT BLUE
    for (var i = 0; i < grid.length; i++) {
        grid[i].show();
    } 
    // Step 1: randomly choose of the unvisited neighbors
    var next = current.checkNeighbors();
    if (next) {
        next.visited = true;
        // Step 2: push the current cell to the stack
        stack.push(current);
        // Step 3: Remove wall between current cell and chosen cell
        removeWalls(current, next);
        // Step 4: Make chosen cell the current cell and mark as visited
        current = next;
        distance++;
    } else {
        if (stack.length > 0) { 
        	current = stack.pop();
        } else { 
        	current = grid[0];
    		mazeGen = true;
        	distance;
        }
    }
    if (longdist < distance) {
            longdist = distance;
            longest = current;
    }
}

function index(i, j) {
    if (i < 0 || j < 0 || i > cols - 1 || j > rows - 1) {
        return -1; //return negative/invalid index
    } else {
    	return i + j * cols;
    }
}

function Cell(i, j) {
    this.i = i;
    this.j = j;
    this.walls = [true, true, true, true]; // N, E, S, W
    this.visited = false;

    this.checkNeighbors = function() {
        var neighbors = [];
        var n = grid[index(i, j - 1)]; // top line (N)
        var e = grid[index(i + 1, j)]; //right line (E)
        var s = grid[index(i, j + 1)]; //bottom line (S)
        var w = grid[index(i - 1, j)]; //left line (W)

        //Avoided the "conditional AND operator" because WordPress converts it to a "logical ND" operator
        if (n) {
            if (!n.visited) {
                neighbors.push(n);
            } 
        }
        if (e) {
            if (!e.visited) {
                neighbors.push(e);
            } 
        }
        if (s) {
            if (!s.visited) {
                neighbors.push(s);
            } 
        }
        if (w) {
            if (!w.visited) {
                neighbors.push(w);
            } 
        }

        if (neighbors.length > 0) {
            var r = floor(random(0, neighbors.length));
            return neighbors[r];
        } else {
            return undefined;
        }
    }

    this.show = function() {
        var x = this.i * w;
        var y = this.j * w;
        
        stroke(0);
        strokeWeight(1);

        if (this.walls[up]) {
            line(x, y, x + w, y); // top line (N)
        }
        if (this.walls[right]) {
            line(x + w, y, x + w, y + w); //right line (E)
        }
        if (this.walls[down]) {
            line(x + w, y + w, x, y + w); //bottom line (S)
        }
        if (this.walls[left]) {
            line(x, y, x, y + w); //left line (W)
        }         
    }
}

function removeWalls(a, b) {
    var x = a.i - b.i; //x = diffrence between current cell and its neighbor
    if (x === 1) {
        a.walls[left] = false; //remove left/west wall on current
        b.walls[right] = false; //removes right/east wall on neighbor
    } else if (x === -1) {
        a.walls[right] = false; //remove right/east wall on current
        b.walls[left] = false; //removes left/west wall on neighbor
    }

    var y = a.j - b.j; //x = diffrence between current cell and its neighbor
    if (y === 1) {
        a.walls[up] = false; //remove up/north wall on current
        b.walls[down] = false; //removes down/south wall on neighbor
    } else if (y === -1) {
        a.walls[down] = false; //remove down/south wall on current
        b.walls[up] = false; //removes up/north wall on neighbor
    }
}

function keyPressed() {
	var solver = grid[index(sX, sY)]; 

    //Avoided the "conditional AND operator" because WordPress converts it to a "logical ND" operator
    if (keyCode === LEFT_ARROW) { 
        if (!solver.walls[left]) {
            sX --; 
        } 
    }    

    if (keyCode === RIGHT_ARROW) { 
        if (!solver.walls[right]) {
            sX ++; 
        } 
    }	    

    if (keyCode === UP_ARROW) { 
        if (!solver.walls[up]) {
            sY --; 
        } 
    }

    if (keyCode === DOWN_ARROW) { 
        if (!solver.walls[down]) {
            sY ++;
        } 
    }
}

function solve() { 
	var a = 0.5 * w;
	noStroke();
	
	fill(214, 247, 52); //green "solver" dot 
    var gX = a + (w * sX); //controlled by arrow keys
    var gY = a + (w * sY);	
	ellipse(gX, gY, a, a);
    
    fill(250, 29, 59); //red "end" dot
    var rX = a + (w * longest.i);
    var rY = a + (w * longest.j);
    ellipse(rX, rY, a, a);

    if (dist(gX, gY, rX, rY) < a) { //if green dot reaches red dot
    	winner();
  	}
}

function preText() {
    fill(165, 202, 218); //blue background
    rect(0, 0, width, height);

    textAlign(CENTER);
    
    noStroke();
    fill(255);
    textSize(75);
    text('Get Ready.', width / 2, height / 2);

    textSize(20);
    text('The maze is loading. Use your arrow keys to navigate the green dot to the red dot.', 
        width / 2 - 150, height / 2 + 45, 300, 200);
}

function winner() {
    fill(165, 202, 218); //blue background
    rect(0, 0, width, height);

    textAlign(CENTER);
    noStroke();
    fill(255);

    textSize(87);
    text('YOU WON!', width / 2, height / 2);

    textSize(25);
    text('Refresh to play again.', width / 2, height / 2 + 45);
}

This was a big task for me. The maze was generated using Recursive Backtracking, a form of First Depth Search (more info can be found on this Wikipedia page). In short, the canvas is gridded into cells and the generator randomly chooses a neighbor cell to visit, removing the wall between it, until all the cells have been visited and a maze is created. A lot of this was new concepts for me, so big thanks to Professor Dannenberg for pointing me in the right direction and Dan Shiffman‘s Coding Train for having some material on this.

The end goal is generated to be the furthest point along the maze from the origin. The start/player begins in the top left corner and moves along the maze to the red dot. Getting the solver dot to stay inside the lines was probably the biggest challenge.

Some Errors: The red dot that signifies the target is supposed to be the furthest distance in the maze from the start. Sometimes it works, but sometimes the target is very close to the origin. I think this is because it may classify the last unvisited cell as the furthest even though the may be very close.

Eliza Pratt – Final Project

dress up doll

/*
Eliza Pratt
Section E
elpratt@andrew.cmu.edu
Final Project
*/

//This program uses images to simulate an interactive dressup game.
//It allows users to click on items which will then jump to the 
//front of the screen via reordering of the array. Users can drag 
//clothing close to the body, where it will then snap into place
//in a smooth motion. When a shirt is snapped onto the body, a button
//will appear that allows the user to recolor the shirt (if clothing is 
//layered, only the top layer will be recolored). There is additional
//functionality for resetting the canvas and randomizing outfits.

//stores figure and clothing image links
var croquisLink = "https://i.imgur.com/7aZm27c.png";

var hairLinks = ["https://i.imgur.com/SwP45yO.png", 
                 "https://i.imgur.com/TqmIRYi.png", 
                 "https://i.imgur.com/JqFGPxq.png", 
                 "https://i.imgur.com/QzqTfIY.png", 
                ];

var shirtLinks = ["https://i.imgur.com/vNLNc6V.png", 
                 "https://i.imgur.com/e3LzXk6.png", 
                 "https://i.imgur.com/tBuSMNk.png", 
                 "https://i.imgur.com/wkJ7JJC.png"
                ];

var pantsLinks = ["https://i.imgur.com/hyAxaOY.png", 
                 "https://i.imgur.com/yk3zFDt.png", 
                 "https://i.imgur.com/mF21MeN.png", 
                  "https://i.imgur.com/ki89EhN.png"
                ];

var shoesLinks = ["https://i.imgur.com/EMbMtDy.png", 
                 "https://i.imgur.com/IZaU3Ls.png", 
                 "https://i.imgur.com/xa3Rrjs.png", 
                 "https://i.imgur.com/S8GJRGa.png"
                ];

//loads images for figure and clothing
var croquis;
var hair = [];
var shirt = [];
var pants = [];
var shoes = [];

//stores clothing objects
var blouse = [];
var bottom = [];
var feet = [];

//positions for figure and clothing
var bodyCenter = 120;
var headCenterY = 87;
var shirtCenter = 224;
var pantsCenter = 389;
var shoesCenter = 544;

//stores colors for clothing
var wheel = ["white", "red", "turquoise", "green"];
//image link for color wheel
var colorLink = "https://upload.wikimedia.org/wikipedia/commons/d/dc/Eight-colour-wheel-2D.png";
//stores color wheel image
var colorWheel;
//positions for wheel
var wheelX = 40;
var wheelY = 140;

//positions for random button
var randomX = 490;
var randomY = 580;

//positions for direction button
var infoX = 560;
var infoY = 580;

//counters for changing hair style and clothing color
var hairClick = 0;
var colorClick = 0;

//reset and random button color
var buttonColor = 255;

//indicate that pants and shoes are not being dragged
var dragBottom = false;
var dragFeet = false;

//array position of shirt on body
var wearing;

//display directions
var showInfo = false;
//------------------------------------------------------------------

//loads images into variables and arrays
function preload() {
    croquis = loadImage(croquisLink);
    colorWheel = loadImage(colorLink);
    for (var i = 0; i < hairLinks.length; i++) {
        hair[i] = loadImage(hairLinks[i]);
        shirt[i] = loadImage(shirtLinks[i]);
        pants[i] = loadImage(pantsLinks[i]);
        shoes[i] = loadImage(shoesLinks[i]);
    }
}


function setup() {
    createCanvas(600, 600);
    //calls function to make clothing objects and arrange on the page
    clothesSetup();
}


//fills shirt, shoes and pants arrays with objects containing images and positions
//serves as a reset function when mix() and reset() are called
function clothesSetup() {
    hairClick = 0;
    for (var i = 0; i < 4; i++) {
        blouse[i] = makeItem(shirt[i], width * 0.48 + 81 * i, 
                             height * 0.15 + 20 * i, shirtCenter);
        bottom[i] = makeItem(pants[i], width * 0.49 + 77 * i, 
                             height * 0.55 + 10 * i, pantsCenter);
        feet[i] = makeItem(shoes[i], width * 0.49 + 77 * i, 
                             height * 0.85 + 5 * i, shoesCenter);
    }
}


function draw() {
    background(206, 255, 181);
    imageMode(CENTER);

    //displays figure
    image(croquis, bodyCenter, height / 2, 225, 600);

    //displays clothing
    for (var i = 0; i < shirt.length; i++) {
        //draws current hair style
        image(hair[hairClick % 4], bodyCenter, headCenterY);
        //draws all pants, shirts and shoes
        bottom[i].draw();
        blouse[i].draw();
        feet[i].draw();

        //if shirt is on the body, store index and make color wheel appear
        if (blouse[i].x == bodyCenter) {
            drawWheel();
            wearing = i;
        }
    }

    //calls function to fit clothing on the figure
    snap(blouse);
    snap(bottom);
    snap(feet);

    //calls reset function
    reset();
    //calls direction function
    directions();

    //If the mouse is not being pressed, reset drag state to false
    //Note: this will be utilized in the mouseDragged function to prevent
    //multiple items from being "picked up" at once
    if (!mouseIsPressed) {
        dragBottom = false;
        dragFeet = false;
    }

    //draw random button
    drawButton("random", randomX, randomY, buttonColor);
    drawButton("directions", infoX, infoY, buttonColor);
}


function mousePressed() {
    //HAIR STYLE----------------------------------------------------
    //distance from mouse to head
    var dHair = dist(mouseX, mouseY, bodyCenter, headCenterY);

    //if hair is clicked, increase counter to display different style
    if (dHair < 60) hairClick++;

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

    var click = false; //tracks if a clothing item has been selected
    var selection; //stores type of selected item

    //SHIRT---------------------------------------------
    //cycle through shirt positions
    for (var i = 0; i < blouse.length; i++) { 
        var dBlouse = dist(mouseX, mouseY, blouse[i].x, blouse[i].y);
        //if shirt is clicked, store the array and index
        if (dBlouse < 75) {
            index = i;
            click = true;
            selection = blouse;
        }
    }

    //PANTS-------------------------------------------
    //cycle through pants positions
    for (var i = 0; i < bottom.length; i++) { 
        var dBottomX = abs(mouseX - bottom[i].x);
        var dBottomY = abs(mouseY - bottom[i].y);
        //if pants are clicked, store the array and index
        if (dBottomX < 60 & dBottomY < 150) {
            index = i;
            click = true;
            selection = bottom;
        }
    }

    //SHOES-------------------------------------------
    //cycle through shoes positions
    for (var i = 0; i < feet.length; i++) { 
        var dFeetX = abs(mouseX - feet[i].x);
        var dFeetY = abs(mouseY - feet[i].y);
        //if shoes is clicked, store the array and index
        if (dFeetX < 60 & dFeetY < 40) {
            index = i;
            click = true;
            selection = feet;
        }
    }

    //if an item is selected, display it on top of other items
    if (click) bringToFront(selection, index);  
    //---------------------------------------------------------------

    //RECOLORING-----------------------------------------
    //distance from mouse to button
    var dTint = dist(mouseX, mouseY, wheelX, wheelY);
    //if button is clicked, recolor item
    if (dTint < 20) recolor(3);

    //RANDOM OUTFIT--------------------------------------
    //distance from mouse to button
    var dRandomX = abs(randomX - mouseX);
    var dRandomY = abs(randomY - mouseY);

    //if button is clicked, assemble a random outfit
    if (dRandomX < 30 & dRandomY < 12) mix();

    //DIRECTIONS-----------------------------------------
    //distance from mouse to button
    var dInfoX = abs(infoX - mouseX);
    var dInfoY = abs(infoY - mouseY);

    //if button is clicked, display info
    if (dInfoX < 30 & dInfoY < 12) showInfo = !showInfo;
}


//if an item is selected, push it to the end of the array
//and remove it from its original position.
//Reordering the array allows item to displayed "on top"
function bringToFront(item, index) {
    item.push(item[index]);
    item.splice(index, 1);   
}


function mouseDragged() {
    //distance between mouse and "most recent" shirt, pants and shoe positions
    var dBlouse = dist(mouseX, mouseY, blouse[3].x, blouse[3].y);
    var dBottomX = abs(mouseX - bottom[3].x);
    var dBottomY = abs(mouseY - bottom[3].y);
    var dFeetX = abs(mouseX - feet[3].x);
    var dFeetY = abs(mouseY - feet[3].y);

    //If mouse is on the shirt and no pants or shoes are 
    //already selected, call function to allow dragging.
    //Establishing items as "true" when they are selected prevents
    //other items from being picked up while the mouse is dragged.
    if (dBlouse < 80 & !dragBottom && !dragFeet) {
        dragItem(blouse, 30);
    }
        // if only shoes are selected, allow them to be dragged
    else if (dFeetX < 60 & dFeetY < 50 && !dragBottom) {
        dragFeet = true;
        dragItem(feet, 0);
    }
    // if only pants are selected, allow them to be dragged
    else if (dBottomX < 60 & dBottomY < 140 && !dragFeet) {
        dragBottom = true;
        dragItem(bottom, 90);
    }
}


//allows selected item to be dragged
function dragItem(item, offset) {
    item[3].x = mouseX;
    item[3].y = mouseY + offset;
}


//snaps items near the body to their fixed positions
function snap(item) {
    //measures distance from item to target position
    var dx = bodyCenter - item[3].x;
    var dy = item[3].center - item[3].y;
    var D = sqrt(dx*dx + dy*dy);

    //when an item is near its target and the mouse is released,
    //snap it into position with a smooth motion
    if (D < 50 & !mouseIsPressed) {
        dirX = dx / max(1, D);
        dirY = dy / max(1, D);
        item[3].x += dirX;
        item[3].y += dirY;
    }
}


//BUTTON FUNCTIONS-------------------------------------

//draw button with label, position, and color parameters
function drawButton(type, x, y, col) {
    //button
    stroke(0);
    strokeWeight(2);
    fill(col);
    rectMode(CENTER);
    rect(x, y, 60, 24, 10);
    //text
    noStroke();
    fill("MAGENTA");
    textAlign(CENTER);
    text(type, x, y + 5);
}


//provides button to reset clothes to original position
function reset() {
    //button position
    var buttonX = 420;
    var buttonY = 580;

    //distance from mouse to center of button
    var dResetX = abs(buttonX - mouseX);
    var dResetY = abs(buttonY - mouseY);

    //if button is clicked, recall setup and draw "clicked" button
    if (mouseIsPressed & dResetX < 30 && dResetY < 12) {
        drawButton("reset", buttonX, buttonY, buttonColor - 80);
        clothesSetup();
    }
    //else, draw button in unclicked state
    else drawButton("reset", buttonX, buttonY, buttonColor);
}


//draw color wheel with an outline
function drawWheel() {
    image(colorWheel, wheelX, wheelY, 40, 40);
    noFill();
    stroke(0);
    strokeWeight(2);
    ellipse(wheelX, wheelY, 37, 37); 
}


//recolors shirt when wheel is clicked
function recolor() {
    //increase color counter and assign tint
    colorClick++;
    blouse[wearing].tint = wheel[colorClick % 4];
}


//provides button to assemble a random outfit
function mix() {
    //selects a random shirt, pants and shoe pairing
    var randBlouse = round(random(3));
    var randBottom = round(random(3));
    var randShoes = round(random(3));

    //draw button in "clicked" mode
    //call setup to remove any clothes on the body
    clothesSetup();
    //selects random hair style
    hairClick = round(random(3));

    //positions random shirt on the body
    blouse[randBlouse].x = bodyCenter;
    blouse[randBlouse].y = shirtCenter;
    //positions random pants on the body
    bottom[randBottom].x = bodyCenter;
    bottom[randBottom].y = pantsCenter;
    //positions random shoes on the body
    feet[randShoes].x = bodyCenter;
    feet[randShoes].y = shoesCenter; 
}


//displays directions for game
function directions() {
    if (showInfo) {
        stroke(0);
        strokeWeight(10);
        fill(255);
        rect(width / 2, height / 2, 400, 150, 30);
        noStroke();
        fill("MAGENTA");
        text("Drag and drop the clothes to make one chic as heck fashion diva", width / 2, height / 2 - 30);
        text("Click her face to change hair styles", width / 2, height / 2 - 5);
        text("Click the color wheel to recolor her shirt", width / 2, height / 2 + 20);
        text("(Press any key to escape)", width / 2, height / 2 + 60);
    }
    //exits directions when a key is pressed
    if (keyIsPressed) showInfo = false;
}

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

//OBJECT CREATION AND CLOTHING DISPLAY

//Creates objects with parameters for
//image, initial position, and center relative to the body.
function makeItem(ipic, ix, iy, icenter) {
    return {pic: ipic, x: ix, y: iy, 
            center: icenter, 
            tint: 255,
            draw: drawItem,
            };
}


//draw image and recolor with specified tint
function drawItem() {
    tint(this.tint);
    image(this.pic, this.x, this.y);
    noTint();
}



Directions: Drag and drop the clothes to make one chic-as-heck fashion diva! Click on the hair to change the style. If you give her a shirt, you can tint it by clicking on the color wheel. Click the random button to generate a random outfit, and click the reset button to put everything back!

Statement: This project was a lot of fun to make and I loved that I was able to incorporate my own drawing style with my code! It ended up being a lot more complicated than I thought to get the click, drag, and drop functions right, but I’m happy with what I was able to do. I’m proud I was able to figure out a way to change the order in which items were displayed on the screen, and I think this level of responsiveness helps my project feel more like a real dress up game. While I’m happy with how the game turned out, I do wish I could have the mouse respond to clicking anywhere on a clothing item rather than estimating its distance from the center.

Rachel Lee and Jenna Kim Section E Final Project

Space Crunch

/* Rachel Lee and Jenna Kim
rwlee@andrew.cmu.edu and jeeyoonk@andrew.cmu.edu
Section E
Final Project */

// Radius of cereal loops
var radius = 20;
// Number of cereal pieces displayed (maximum number possible)
var cerealNum = 50;
// Empty array that holds cheerio elements
var cereal = [];
// Variable to store clicked cereal pieces
var clicked;
// Game time elapsed
var time_elapsed = 0;
// Game duration (60 seconds)
var gameLength = 120;
// Changes states according to instructions screen, game and game over
var gameMode = 0;

var x = [];
var y = [];

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

    // Variables for spacing of cereal loops according to triangle principles
    var tw = 90; // horizontal spacing between cheerios
    var th = 70; // vertical spacing between cheerios
    var cy = 50; // original y position
    var cx = 30; // original x position

    // Creates and pushes cereal into empty array
    for (var y = 0; y < 10; y ++) {
        // creates and displays even rows
        if ((y % 2) == 0) {
            for (var x = 0; x < 15; x++) {
                var px = cx + x * tw;
                var py = cy + y * th;
                cereal.push(new cheerios(px, py));
            }
        }
        // Creates and displays offset odd rows
        else {
            for (var x = 0; x < 10; x++) {
                th = 60 * (sqrt(3) / 2);
                var px = cx + x * tw;
                var py = cy + y * th;
                cereal.push(new cheerios (px + tw / 2, py + 150));
            } 
        } 
    } 
    
    // Initializes the player's score
    clicked = 0;

    // Updates cereal every second to determine if they pop up or not
    // Updates timer every second (1000 = 1 second in milliseconds)
    setInterval(function() {
        for (var i = 0; i < cerealNum; i ++) {
            cereal[i].updateC();
        }
        time_elapsed += 2;
    }, 1000);
}

function draw() {
	// Color gradient sky
    var color1 = color(0, 0, 153); // Blue purple
    var color2 = color(204, 51, 0); // Red

    for (var i = 0; i < height; i++) {
        var gradient = map(i, 0, height / 2, 0, 1);
        // Lerp used to ease color transitions 
        var mesh = lerpColor(color1, color2, gradient); 
        fill(mesh);
        stroke(mesh);
        line (0, i, width, i);
    }

    hillShadow();
    hill();

    noStroke();

    // Instruction page/ start game mode
    if (gameMode == 0) {
        gameStart();
    }

    // If game is still running, draws cereal, UFO, game score and time tracker
    if (gameMode == 1) {
        for (var i = 0; i < cerealNum; i ++){
            cereal[i].drawC();
        }  
        drawUFO();
        gSgT();
    }

    // Game ends when timer runs out
    if (time_elapsed >= gameLength) {
        gameMode = 2;
        gameOver();
    }

    // Press s key to start game
    if (keyIsPressed) {
    	if((key == 's') || (key == 'S')) {
    		gameMode = 1;
    	}
    	// When the game is over, press r key to reset game
        if ((key == 'r') || (key == 'R')) {
            gameMode = 1;
            time_elapsed = 0;
            clicked = 0;
        }
    }
}

// Draws UFO that follows mouse position
function drawUFO() {
    strokeCap(ROUND);
    stroke(230);
    strokeWeight(4);
    line(mouseX + 12, mouseY + 2, mouseX + 27, mouseY + 14);
    line(mouseX - 12, mouseY + 2, mouseX - 27, mouseY + 14);
    noStroke();
    fill(245, 210, 145); // orange peach
    ellipse(mouseX, mouseY + 7, 57, 16);
    fill(55, 70, 140); // blue 
    ellipse(mouseX, mouseY - 1.5, 85, 25);
    fill(255, 105, 80); // orange 
    ellipse(mouseX, mouseY - 3.5, 85, 25);
    fill(245, 210, 145); // orange peach
    arc(mouseX, mouseY - 7, 57, 37, PI, TWO_PI);
    ellipse(mouseX, mouseY - 7, 57, 16);
}

// Defines object constraints
function cheerios(x, y, duration, drawC, updateC, clickedC) {
    var pos = {x: x, y: y, duration: duration, drawC: drawCheerios, 
    updateC: updateCheerios, clickedC: clickedCheerios};
    return pos;
}

function drawCheerios() {
    if (this.duration > 0) {
        stroke(180, 147, 119);
        strokeWeight(8);
        noFill();
        ellipse(this.x, this.y, radius, radius);
        stroke(240, 225, 190);
        ellipse(this.x + 2, this.y + 2, radius, radius);
    }
}

//Determines whether or not the cheerios appear
function updateCheerios() {
    if (this.duration > 0) {
        this.duration = this.duration - 0.5;
    } 
    else {
        var ratio = random(1);
        if (ratio < 0.15) {
            this.duration = random(5);
        }
    }
}

// Checks to see if user clicked the cereal, and adds to score
function clickedCheerios (clickedX, clickedY) {
    if ((dist(clickedX, clickedY, this.x, this.y - radius) < radius) & (this.duration >= 0)) {
        this.duration = 0;
        clicked += 1;  
    }
}

// Checks to see if the mouse was pressed, and if the cereal was clicked
function mousePressed() {
    for (var i = 0; i < cerealNum; i++) {
        cereal[i].clickedC(mouseX, mouseY);
    }
}

function gameStart() {
    // Color gradient sky start
    var colorStart1 = color(0, 0, 153); // Blue purple
    var colorStart2 = color(204, 51, 0); // Red

    for (var i = 0; i < height; i++) {
        var gradientStart = map(i, 0, height / 0.9, 0, 1);
        // Lerp used to ease color transitions 
        var meshStart = lerpColor(colorStart1, colorStart2, gradientStart); 
        fill(meshStart);
        stroke(meshStart);
        line (0, i, width, i);
    }

    fill(255);
    textFont('Futura');
    textSize(13);
    text("Collect cheerios to fuel the U.F.O.'s journey through outer space!", width / 10 + 50, height / 2);
    text("Press S to Start", width/ 10 + 200, height/ 2 + 30);
}

// Display game score and remaining game time
function gSgT() {
    fill(255);
    textFont('Futura');
    textSize(12);
    text("Score : " + clicked, 500, height - 40);
    text("Time Remaining: " + (60 * 2 - time_elapsed)/2 + " secs", 50, height - 40);	
}

// Game over screen
function gameOver() {
    // Color gradient sky end
    var colorEnd1 = color(204, 51, 0); // Red
    var colorEnd2 = color(0, 0, 153); // Blue purple

    for (var i = 0; i < height; i++) {
        var gradientEnd = map(i, 0, height / 0.7, 0, 1);
        // Lerp used to ease color transitions 
        var meshEnd = lerpColor(colorEnd1, colorEnd2, gradientEnd); 
        fill(meshEnd);
        stroke(meshEnd);
        line (0, i, width, i);
    }
    noStroke();
    fill(255);
    text("Game Over!", width/ 2 - width/ 20, height/ 2 - 30);
    text("Cheerios Collected : " + clicked, width/ 3 + 35, height/ 2);
    text("Press R to restart", width/ 3 + 55, height/ 2 + 30);
}

//drawing hill
function hill() {
    var hillSpeed = 0.0002;
    var hillDetail = 0.0025;
    stroke(210, 105, 95, 80);
    for (var x = 0; x < width; x++) {
        var t = (x * hillDetail) + (millis() * hillSpeed);
        var y = map(noise(t * 2), 0, 1, 330, 200);
        line(x, y, x, height);
    }
} 

function hillShadow() {
    var hillSpeed = 0.0001;
    var hillDetail = 0.0045;
    stroke(160, 70, 70, 55);
    for (var x = 0; x < width; x++) {
        var t = (x * hillDetail) + (millis() * hillSpeed);
        var y = map(noise(t * 2.2), 0, 1, 100, 300);
        line(x, y, x, height);
    }   
}

Backstory and Instructions

Help the U.F.O. continue its intergalactic journey by collecting cheerio fuel! Click on the cheerios before they disappear, and collect as many as possible before the timer runs out. Running game time is 60 seconds.

Work Distribution 

For our project, Rachel primarily focused on the structure and mechanics of the game. Rachel created the mechanism for the flashing cheerios on screen, the game timer and the score keeping system when cheerios were pressed. Jenna focused more on the visual aspect of the game, designing the background / scenery, U.F.O., as well as the ‘Start Game’ and ‘Game Over’ pages and the mechanics of the game modes (Start, actual game, end game).

Working Together

Overall, we had a lot of fun working together. We were able to make revisions and improvements to our code in terms of style, efficiency of structure and visuals through continued collaboration.

First iteration
Second Iteration

 

Min Jun Kim and Han Yu- Final Project

Supreme Invasion!

/*
Min Jun Kim & Han Yu
minjunki && hyu1
15104
Final Project 

*/

//This Project is a spaceship fighting game.

var px = 0; //x location of ship
var py = 240; //starting y location of our ship
var psize = 50; //size of ourship
var keys = []; //for controlling sihp
var Selfprojectiles = []; //array of our projectiles 
var projectileCounter = 100; //for controlling flow of projectiles
var SOE = []; //array of enemy ships
var shipSpeed = 3; //our speed for moving ship
var distanceOne = []; //distance between projectile and enemy
var distanceTwo = []; //distance between projectile and enemy
var SOEnumb = 2; //number of enemies
var timer = 0; //for controlling flow of game
var gameStage = 1; //starting stage of the game
var timerIncrement = 1; //another variable for controlling flow of game
var enemyProjectiles = []; //array of enemy bullets
var EPdistance = []; //distance between enemy bullets and our ship
var ourHealth = 10; //how much health we have left
var gameOver = 0; //activated when user dies
var clickStart = 0; //for controlling start of game
var clickCount = 0; //for making sure no double clicks
var stars = []; //array of stars in background
var imageOne; //image
var imageTwo; //image
var imageThree; //image

//making background stars
function makeStars(x, y, size) {
    return {x: x, y: y, size: size, draw: starRender, update: starUpdate}
}

//draws stars as ellipse
function starRender() {
	fill(255);
    ellipse(this.x, this.y, this.size);
}

//scrolling speed of stars
function starUpdate() {
    this.x -= 3;
}

//making our bullets
function makeOurProjectile(x, y, fast, powerful) {
	return {x: x, y: y, speed: fast, power: powerful, 
		drawIt: OPrender, updateIt: OPupdate}
}

//making enemy bullets
function makeEnemyProjectile(x,y, speedo,status) {
	return {x: x, y: y, speed: speedo, boss: status, 
		updir: random(-5,5), drawIt: EPrender, updateIt: EPupdate}
}

//draws bullets, depending on type of enemy
function EPrender() {
	//if boss, have balls instead of lines
	if (this.boss == true) {
		fill(0);
		stroke(250,0,0);
		ellipse(this.x, this.y, 10, 10);
	}
	//standard line bullets from enemy
	else {
		stroke(250, 0, 0);
		strokeWeight(3);
		line(this.x, this.y, this.x-30, this.y);
	}
	
}

//controls the movement of enemy bullets depends on enemy type
function EPupdate() {
	//if boss, then make bullets bounce and have random speed
	if (this.boss == true) {
		this.y += this.updir;
		this.x -= this.speed*random(1,2);
		//bounces bullets
		if (this.y > height || this.y < 0) {
			this.updir = -this.updir;
		}
	}
	//all other enemies have one dimensional attacks
	else {
		this.x -= this.speed;
	}
	
}

//draws our bullets as green lines
function OPrender() {
	stroke(0,200,0);
	strokeWeight(this.power);
	line(this.x, this.y, this.x+30, this.y);
}

//moves the bullet across map
function OPupdate() {
	this.x += this.speed;
}

//the specs of enemy ships
function makeSOEnemy(x, y, fast, sizing) {
	return {x: x, y: y, big: sizing, speed: fast, 
		drawIt: SOErender, update: SOEupdate, DMG: 0}
}

//controls the movement of enemy ships depending on size and location
function SOEupdate() {
	//moves the enemy along the map
	if (gameStage == 1)	 {
		this.y += this.speed;
	}
	if (gameStage == 2)	 {
		this.y += this.speed;
	}
	//if 2nd round 1st enemy, move back as game goes on
	if (gameStage == 2 & this.x < 500) {
		this.x += 0.1;
	}

	if (this.y > height) {
		this.speed = -this.speed;
	}
	if (this.y < 0) {
		this.speed = -this.speed;
	}
	
	//first round enemy with easy projectiles
	if (gameStage == 1) {
		if (random(1) > 0.98 & this.y < 600) {
			enemyProjectiles.push(makeEnemyProjectile(this.x, this.y,5));	
		}

	}
	//2nd round enemy, with more frequent bullets
	if (gameStage == 2 & this.big < 90) {
		if (random(1) > 0.93 && this.y < 600) {
			enemyProjectiles.push(makeEnemyProjectile(this.x, this.y, 5));	
		}

	}
	//pulls our ship closer and make projectiles more frequently
	if (gameStage == 2 & this.big > 90) {
		if (random(1) > 0.95 && this.y < 600) {
			enemyProjectiles.push(makeEnemyProjectile(this.x, this.y, 
				random(4,6,1), true));	
		}
		px+=0.3;

	}
}

//draws the enemies depending on the size input.
function SOErender() {
		fill(100)
		//draw the boss and health bar
		if (gameStage == 2 & this.big > 90) {
			push();
			fill(250,0,0);
			rectMode(CORNER);
			rect(5,5,width-this.DMG*6.4,20);
			pop();
			
			push();
			translate(this.x-85, this.y-60);
			scale(0.37, 0.37);
			image(imageThree,0, 0);
			pop();

		}
		//the standard enemy 
		else {
			//health bar
			push();
			fill(220, 0, 0);
			rect(this.x, this.y - this.big/1.5, 
				this.big - this.DMG*this.big/20, 10);
			pop();
			
			//standard enemy
			push();
			translate(this.x-this.big*1.4, this.y-this.big*0.5);
			scale(0.0035*this.big, 0.0035*this.big);
			image(imageTwo, 0, 0);
			pop();
		}
}


//==============================================================

//sets up the keys and the initial stars
function setup() {
   createCanvas(640,480)
   keys = [false, false, false, false, false]
   for (i = 0; i < 200; i++) {
        stars[i]=makeStars(random(width), random(height), random(5));
    }
   imageOne = loadImage("https://i.imgur.com/GHtg2vU.png");
   imageTwo = loadImage("https://i.imgur.com/svK8OkI.png");
   imageThree = loadImage("https://i.ibb.co/n0G0yGC/eae.png");
}

function draw() {
	//basic initial specs
    background(17,21,61);
    rectMode(CENTER);
    noStroke();
    fill(100);
    
    //projectile counter increases by 1
	projectileCounter += 1;
	
	//increase timer by timer increment
	timer += timerIncrement;

	//makes first round of enemies when timer is 1
	if (timer == 1 & gameStage == 1) {
		for (i = 0; i < SOEnumb; i ++) {
    		SOE[i] = makeSOEnemy(500,random(height), random(5), random(40,60));
    	}
    	timer += 1;

	}

	//states the first round
	if (timer > 1 & timer < 40) {
		textAlign(CENTER);
		push();
		stroke(255);
		fill(255);
		textSize(20);
		text("Round One!", width/2, height/2-100);
		pop();
		
	}

	//pauses the increase in timer
	if (timer > 41) {
		timerIncrement = 0;
	}

	//resumes timer once 1st round is finished
	if (gameStage == 2) {
		timerIncrement = 1;
	}

	//once timer has started moving again, write text stating the stage
	if (timer > 50 & timer < 150) {
		push();
		noStroke();
		fill(255);
		textSize(20);
		text("Final Stage!", width/2, height/2-100);
		pop();
	}

	//makes 2 more enemies once timer reaches 60
	if (timer == 60 & gameStage == 2) {
    	SOE[0] = makeSOEnemy(400,random(height), 6.5, 30);
    	SOE[1] = makeSOEnemy(550, height/2, 3, 100);
    	timer += 1;

	}

	//move right if rightarrow is pressed
    if (keys[0] == true) {
    	px += shipSpeed;	
    }

    //move left if leftarrow is pressed
    if (keys[1] == true) {
    	px -= shipSpeed;
    }

    //move up if uparrow is pressed
    if (keys[2] == true) {
    	py -= shipSpeed;
    }

    //move down if downarrow is pressed
    if (keys[3] == true) {
    	py += shipSpeed;
    }

    //add projectiles once shift is pressed and reset counter
    if (keys[4] == true & projectileCounter > 5) { //8
    	Selfprojectiles.push(makeOurProjectile(px+50, py, 10, 3))
    	projectileCounter = 0;
    }
    
    //for teleporting up and down once limit is reached
    if (py > height) {
    	py = 0;
    }
    if (py < 0) {
    	py = height;
    }
    //prevents user from going behind map by pushing back
    if (px < -5) {
    	px += 3;
    }
    //prevents user from moving too forward by pushing back
    if (px > width/2) {
    	px -= 3.3;
    }

    //makes random star objects, and initializes the functions
    noStroke()
    for (i = 0; i < 1; i++) {
        stars.push(makeStars(width, random(height), random(5)));
    }
    for (i = 0; i < stars.length; i++) {
        stars[i].draw();
        stars[i].update();

        //if stars if too far behind map, shift the array
        if (stars[i].x < -500) {
            stars.shift();
        }

    }
    

    //initializes the enemoies
    for (i = 0; i < SOEnumb; i ++) {
    	SOE[i].drawIt();
    	SOE[i].update();
    	
    }
    
    //initializes the enemy projectiles
    for (i = 0; i < enemyProjectiles.length; i++) {
    	enemyProjectiles[i].drawIt();
    	enemyProjectiles[i].updateIt();
    }

    //initializes our projectiles
    for (i = 0; i < Selfprojectiles.length; i++) {
    	Selfprojectiles[i].drawIt();
    	Selfprojectiles[i].updateIt();
    		//calculates distance if the array is nonzero and projectiles
    		//are still on map
			if (Selfprojectiles.length > 0 & Selfprojectiles[i].x < 700) {
				//calculate the distance between enemy 1 and projectile
				distanceOne[i] = dist(Selfprojectiles[i].x, 
				Selfprojectiles[i].y, SOE[0].x, SOE[0].y);
				//calculate distance between enemy 2 and projectiles
				distanceTwo[i] = dist(Selfprojectiles[i].x, 
				Selfprojectiles[i].y, SOE[1].x, SOE[1].y);

				//if off map shift distances and projectiles
				if (Selfprojectiles[i].x > width) {
					Selfprojectiles.shift();
					distanceOne.shift();
					distanceTwo.shift();
				}
				//if distance is smaller than the size of enemy 1 shift
				if (distanceOne[i] < SOE[0].big) {
					Selfprojectiles.shift();
					distanceOne.shift();
					distanceTwo.shift();
					SOE[0].DMG ++;
				}
				//if distance is smaller than the size of enemy 2 shift
				if (distanceTwo[i] < SOE[1].big) {
					Selfprojectiles.shift();
					distanceOne.shift();
					distanceTwo.shift();
					SOE[1].DMG ++;
				}
				//if enemy 1 reach damage cap, move off map prevent shooting
				if (SOE[0].DMG > 20) {
					SOE[0].y = 700;
					SOE[0].x = 700;
					SOE[0].speed = 0;
				}
				//if enemy 2 reach damage cap, move off map prevent shooting
				if (SOE[1].DMG > 20 & gameStage == 1) {
					SOE[1].y = 700;
					SOE[1].x = 700;
					SOE[1].speed = 0;
				}
				//if boss reach damage cap move off stage
				if (SOE[1].DMG > 100 & gameStage == 2) {
					SOE[1].y = 700;
					SOE[1].x = 700;
					SOE[1].speed = 0;
				}
				//if both enemies are killed during 2nd stage, end game
				if (SOE[0].y == 700 & SOE[1].y == 700 && SOE[1].DMG > 99) {
					gameStage = 3;
				}
				//if both enemnies killed during 1st stage, move up stage
				if (SOE[0].y == 700 & SOE[1].y == 700 && gameStage == 1) {
					gameStage =2;
				}

				stroke(255);
		}    
	}

    
    for (z = 0; z < enemyProjectiles.length; z++) {
    		//if enemy projectile array is nonzero, calculate distance
			if (enemyProjectiles.length > 0) {
				EPdistance[z] = dist(enemyProjectiles[z].x, enemyProjectiles[z].y,
					px, py);
				//if bullet goes off map, shift distance and bullet
				if (enemyProjectiles[z].x < 0) {
					enemyProjectiles.shift();
					EPdistance.shift();
				}
				
				//if projectile too close to ourship, shift arrays and do damage
				if (EPdistance[z] < 20 & timer != 0) {
					enemyProjectiles.shift();
					EPdistance.shift();
					ourHealth -= 1;
				}
				//if health is zero, end game
				if (ourHealth === 0) {
					gameOver = 1;
				}

			}
		}

	//draw the health bar
	push();
    rectMode(CENTER);
    noStroke();
    fill(0,200,0);
    rect(px+psize/2, py-psize/2, ourHealth*5,5);
    pop();

    //draw our ship
    push();
    noStroke();
    fill(100);
    translate(px-37,py-25);
    scale(0.15,0.15);
    image(imageOne, 0, 0);
    pop();


    //initial stage specs. Gives instructions
	if (clickStart == 0) {
		timer = 0;
		timerIncrement = 0;
		//black background
		push();
		fill(0);
		rect(width/2, height/2, 1000, 1000);
		pop();

		//design and title
		push();
		fill(255, 253, 83, 140);
		noStroke();
		triangle(150, 100, 280, 600, 550, 500);
		triangle(110, 100, 20, 500, 130, 500);
		triangle(170, 105, 700, 50, 700, 300);
		textFont("futura");
		textStyle(ITALIC);
		fill(0);
		textSize(50);
		text("Supreme", 500-85, 130-3);
		text("Invasion", 541-85, 180-3);
		pop();

		//decoration
		push();
		translate(-150,90);
		scale(0.6, 0.6);
		rotate(-0.5);
		image(imageOne, 0, 100);
		pop();

		//instructions
		push();
		noStroke();
		fill(0,0,200);
		fill(255);
		textSize(30);
		text("click to begin!", width/2+25, 440);
		fill(0);
		textSize(20);
		text("Arrow Keys to move", width/2+5, 390);
		text("Or WASD to move", width/2+5, 410);
		text("Shift to shoot", width/2-35, 370);
		pop();
	}

	//once clicked, the opening screen is changed
	if (mouseIsPressed & clickCount == 0) {
		clickCount += 1;
		timerIncrement = 1;
		clickStart = 1;	
	}
	//if game over is active, then cover screen with message
	if (gameOver > 0) {
		fill(0);
		rect(0,0, width*2, height*2);
		fill(255);
		noStroke();
		textSize(50);
		text("Game Over!", width/2, height/2);
		textSize(30);
		text("Press 'r' to restart", width/2, height/2+50);
		push();
		translate(350, 10);
		scale(0.5, 0.5);
		image(imageTwo, 0, 0);
		pop();
	}
	//if end game is activated then cover screen and say end message
	if (gameStage == 3) {
		fill(0);
		rect(width/2, height/2, 1000,1000);
		push();
		translate(460, 200);
		rotate(0.3);
		scale(0.8, 0.8);
		image(imageThree, 0, 0);
		pop();
		push();
		noStroke();
		fill(255);
		textSize(50);
		text("You Win! :)", width/2, height/2);
		textSize(20);
		text("Press 'r' to play again", width/2, height/2+50);
		pop();
	}	
}

//Controls======================================================
//==============================================================

//if key is pressed changes the array for controls
function keyPressed() {
	if (key == "ArrowRight" || key == "d" || key == "D") {
		keys[0] = true;
	}
	if (key == "ArrowLeft" || key == "a" || key == "A") {
		keys[1] = true;
	}
	if (key == "ArrowUp" || key == "w" || key == "W") {
		keys[2] = true;
	}
	if (key == "ArrowDown" || key == "s" || key == "S") {
		keys[3] = true;
	}
	if (key == "Shift") {
		keys[4] = true;
	}
	//resets game if press r
	if (key == "r" || key == "R") {
		gameOver = 0;
		gameStage = 1;
		timer = 0;
		timerIncrement = 1;
		clickCount = 0;
		clickStart = 0;
		ourHealth = 10;
		px = 0;
		py = 240;
	}
}

//releases the keys
function keyReleased() {
	if (key == "ArrowRight" || key == "d" || key == "D") {
		keys[0] = false;
	}
	if (key == "ArrowLeft" || key == "a" || key == "A") {
		keys[1] = false;
	}
	if (key == "ArrowUp" || key == "w" || key == "W") {
		keys[2] = false;
	}
	if (key == "ArrowDown" || key == "s" || key == "S") {
		keys[3] = false;
	}
	if (key == "Shift") {
		keys[4] = false;
	}
}

Instructions:
To play the game, click the initial screen to start the game. You can use either the arrow keys or “w” to move up, “a” to move left, “s” to move down, and “d” to move right (WASD). To shoot projectiles press SHIFT. The point of the game is to defeat all enemies. If you hit the enemy with your projectiles their health will go down, and once the enemy’s health hits zero, the enemy is considered defeated. On the second stage, the boss will pull the user towards the right side of the map, so be mindful. Lastly, the user cannot go horizontally beyond certain limits, and once the user reaches the top of the map the user will be teleported to the bottom of the map.

Statements:
For this final project, I focused a lot on the structure and gameplay (the backbone of the game), while my partner focused on the design aspect and some functionality of the game. My partner personally drew all the characters, which made the game look more unique. The code spans around 600 lines, and in my perspective, it was certainly difficult because of the number of objects (4) and their interaction with each other. I made sure to limit the number in arrays to make the gameplay run more smoothly and cleanly.

The gameplay is slightly different from the initial proposal, but for good reason. We initially planned to have three stages in the game, however that either made the game extremely long or difficult. We also didn’t implement a sound source for this game, because we wanted the users to be able to play the game directly in the browser. Despite these minute changes, the main concept and paradigms are true to the original.

All in all, this project was extremely fun to program. It certainly did take hours and hours to get it right, but in the end it all paid off because of the aesthetics and fun gameplay.

Austin Treu – Final Project Proposal

For my final project, I intend to  make a game utilizing the turtle code as my base for player characters. The game will be based on Snake, with changes that could include things such as maps, power ups, and passive enemies. I intend to include a multiplayer mode in which the players can compete in rounds to last the longest. Running into anyone’s trail will cause the player who does so to lose. Including a set of playable characters with different strengths and weaknesses is also a priority.

I am still considering some other interesting possibilities for the game, like having multiple control schemes (mouse, keyboard, controller if possible), or a continuous run mode in which the game functions with the players constantly moving in one direction having to avoid different obstacles as well as each other. This mode could increase in difficulty as the game progresses, allowing players to compete to beat their own top scores.