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

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

Kevin Riordan and Sharon Yang – Looking Outwards 12

We are using grace days on this assignment.

Time by Inception being played on a piano app

A song being played on ‘Cool Piano’ app

The following videos are piano apps currently available on Apple app store. Though they are not computational art projects, we find these apps and how they operate highly inspiring. For our final project, we are thinking of creating a virtual piano with realistic graphics of the piano. The first video has inspired us to add more graphics such as the notes of the keys pressed, or we could also recreate the graphics of the app; when a song is uploaded the keys in the song can be obtained to be displayed on the screen with graphics. The second video has inspired us to append engaging visual effects to the notes such as the staff as well as having them float around. They could also have glowing effects. This could also be developed into a game, where the users can tap the graphics of the notes. We would like to try to make our project as engaging and useful as these apps in the videos.

Sources of the videos: https://www.youtube.com/watch?v=m5RmYEd1N9I

Kevin Riordan Sharon Yang-Project 12-Proposal

We are using one of the grace days on this assignment.

Our final project proposal is making a program that simulates a piano, like the apps available on iPhones. When keys are pressed, sound comes out, and maybe a note floats up to the top out of the key. The size of the notes or movement could be based on how long the key is pressed for or something. Creating the visual of pressing down the key and making the piano look realistic will be challenging, and making the sounds realistic and reactive will also be something we have to figure out. We have not really done much with sound in this class, so working more with sound will be challenging. Below are some sketches of what we plan to try and make visually. The basic program to make it work seems to be something realistic to try and work towards, but making it visually more appealing is something we want to work on more.

what the interface might look like

I will be working with Sharon Yang (also in Section C) on this project.

Catherine Coyle – Looking Outwards 12

For my final project, I would like to make some kind of relaxing gardening mini game. Two of my favorite games that I’ve played are the Animal Crossing series and Stardew Valley which I’ll focus on for this looking outwards assignment.

Both of these are pretty well-known video games that have less of a ‘goal’ or storyline than a lot of games (Animal Crossing moreso than Stardew Valley in that respect), and instead are just peaceful ways to relax. Both of the games feature some kind of planting or gardening activity which is what inspired the idea for my project.

I like both of these games because they make you feel peaceful when you play them. The concepts and characters are all very friendly, and the games’ art is soft and welcoming. I don’t think much was overlooked in either games’ case. They have less story and mechanics than a lot of other games, but also they don’t need that because it would ruin the peaceful feeling

A screenshot from Stardew Valley which was developed by ConcernedApe and came out in 2016
A screenshot from Animal Crossing New Leaf of Nintendo’s ‘Animal Crossing’ series

(I am using my second grace day for this post)

Robert Oh- Project 12- Proposal

For my final project, I wanted to create a “multi-tasking” game. I remember back when I was a kid, there was a multi-tasking game that my friends and I would play in order to see who could score the most number of points (and so prove who was the best at multi-tasking).

And so, I wish to create a similar game where you must keep track of many different elements at the same time in order to survive. For example, I have 4 examples in my photo below that the player must keep track of. The top-left photo contains a ball on top of a platform in which you have to balance (the ball may move to the right or left of the platform). The top-right photo contains a clicking game where you must click inside the spawned circles before the time runs out. The bottom-left photo contains a turtle running hurdles, where you have to press the space bar for the turtle to jump. Finally, the bottom-right photo shows a hollow semi-circle that you must rotate to allow the arrows on the sides to hit the middle of the semi-circle.

 

Kevin Thies – Looking Outwards 12

For my final project, I was looking at making a top-down dungeon crawler game. There’s plenty of games like this, like some of the original Legend of Zelda Titles, but the one I saw as most relevant to my project was Edward McMillen and Florian Himsl’s 2011 Flash-based game, The Binding of Isaac. Admittedly, I’d heard of the newer version, but I never really looked at the older one, which was initially made in a week. The way the player moves through rooms makes sense mechanics-wise, and means generating levels should be simple (in theory).

One of many possible Isaac rooms

From here I took a look back to see what older games did for level generation, which ended up becoming a tangent that led to my finding Nethack. I found it fascinating mostly due to the ASCII graphics, but turns out it was made in 1987 by Mike Stephenson, and has had continuous edits and updates. In fact, their GitHub repository was last updated 15 hours ago, at the time of writing. I found it interesting in that NetHack levels take up no more than the size of the screen. They’ll never scroll. It’s also all open source, albeit written in C.
An example of the NetHack ASCII graphics
If I was to follow this format, I could just store the levels in an array and be able to call them individually by number, which could be interesting. It also breaks up the really boxy rooms of Isaac, which relies more on having different environment assets placed in different places in each room.

Catherine Coyle – Project 12 – Proposal

For my final project I’d really like to make some kind of relaxing game. I think this would be a nice opportunity to make a game which I really want to do as well as make it look appealing visually. In this vein, I thought about making some kind of cute little gardening game. The player would be able to choose what seeds to plant and water them by clicking. There could be random events like weather or animals coming to visit. Different plants’ properties would be stored in objects. I think this idea is something I’d enjoy working on as well as something that I will hopefully be proud to show off when I’m done!

A concept of some initial ideas for the project

Hannah Cai—Project 12—Proposal

For my project, I’m planning to do some kind of interactive, generative audio visualization. My most concrete idea as of now is to create a field of particles in WEBGL, with each particle having a sine wave of a random frequency embedded in it. Users would be able to “look around” the viewing field with orbit control, hover over a particle to hear its pitch, and click and drag between particles to form “links.” Linked particles would emit their pitches, allowing the user to build “constellations” of harmonies. I want the particles to feel dynamic and alive, so I’ll probably implement some sort of noise into their movement, as well as create reactive interactions, such as glowing when a mouse is over a particle, or when a particle is linked. This idea will probably require the use of objects, which are one of my weak points. Hopefully, completing this project will strengthen my familiarity with objects. I’m also almost completely unfamiliar with WEBGL, but I’ve been interested in it for a while, so this will be a good opportunity to explore it.

If I have time, I’d want to take this idea further and try to gamify it in some way, as well as add more recognizable visuals into it (for example, have the particles be a night sky above a hill).