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 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.

Kevin Thies – Project Proposal

An example room with enemies

My proposal for a final project would be a top-down view dungeon crawler game. I found that I was interested in using keyboard inputs as controls, and the natural development from there is a game.

The white circle is a stand-in for the character, and the red circle is where the mouse is aiming

Players would be a wizard, armed with a staff firing magic missiles (I think turtles would be good for a meandering projectile) going room to room vanquishing skeletons. I think rooms would all be square, but could spawn with doors. I could possibly make a system to make rooms and store their data, and start with a random numbers of doors that would decrease until rooms could no longer spawn with doors linking to new rooms. In this last room would be something that would be the goal, possibly a pile of treasure. If time becomes an issue, then the scope of the project could shrink to become a single room with waves of enemies, the goal being to last as long as you can.

The main inspiration was a Scratch project a friend and I built in our early high school days.

Kevin Thies – Looking Outwards 11


A user’s generated music

While looking at various computational instruments, I ended up on a small tangent that lead to the discovery of not a person, but a tool. Specifically, that tool was WolframTones, created 2005 by Wolfram Research, based on research from the 1980s. I found it interesting in that unlike what I looked at during week 4, this was a tool that was more centered around what I suppose you could call the formality of music. It allows for control over tempo, pitch mapping, and instrumentation. As an extra blessing or curse, the site had so many different options that one could really engross themselves. There are already hundreds of premade musical scales, instruments, and instrument roles. It’s crazy.
WolframTones is powered by Wolfram Automata. Basically, there’s a square that’s either black or white and it’ll gontinue to grow based on a specific rule, generating complexity. There are 256 rules, and Stephen Wolfram’s experiment went through all those rules. Hopefully this image explains it a little better.

The 256 Rules

WolframTones takes these rules and flips them sideways, and uses them as notes.
The above video is an example of someone using the website and their generated music.

Kevin Thies – Project 11

Firefly Jar

// Kevin Thies
// Section C
// kthies@andrew.cmu.edu
// Project-11- Turtle Freestyle

var ttl; // turtle


function setup() {
    // style
    createCanvas(480, 480);
    background(4, 20, 45);

    // make the turtle with starting values
    ttl = makeTurtle(width/2, height/2);
    ttl.penDown();
    ttl.setWeight(6);
    ttl.setColor("yellow");
    ttl.face(0);
}

function draw() {
    background(4, 20, 45, 20);

// firefly
    // move
    ttl.forward(5);

    // rotate
        // random, flying-insect-like movement
    ttl.right(map(noise(ttl.x, ttl.y), 0, 1, -180, 180));

        // if mouse over jar, make it move towards mouse
    if(mouseX < 360 & mouseX > 120 && mouseY > 90 && mouseY < 390) {
        ttl.turnToward(mouseX, mouseY, 40);
    }

    // if near jar wall,
    if (ttl.x > 350 || ttl.x < 130 || ttl.y > 380 || ttl.y < 100) {
        // turn towards center
        ttl.turnToward(width/2, height/2, 180);
        ttl.forward(10);

        // teleport back into jar
        if (ttl.x > 350) {
            ttl.goto(350, ttl.y);
            print("turtle too far right");
        } else if (ttl.x < 130) {
            ttl.goto(130, ttl.y);
            print("turtle too far left");
        } else if (ttl.y > 380) {
            ttl.goto(ttl.x, 380);
            print("turtle too far down");
        } else if (ttl.y < 85) {
            ttl.goto(ttl.x, 100);
            print("turtle too far up");
        }
    }
    // firefly light
    strokeWeight(0);
    fill(255, 255, 0, 40);
    ellipse(ttl.x, ttl.y, 40, 40);


    // jar (dimensions are x 120 to 360 y 90 to 390)
        fill(0, 0, 0, 0);
        rectMode(CENTER);
        stroke(255);
        strokeWeight(2);
        rect(width/2, height/2, 240, 300, 20);
        fill(173, 151,64);
        strokeWeight(0);
        rect(width/2, 73, 240, 30, 20);
}

//////////////////////////////////////////////////////////////////////////
function turtleLeft(d) {
    this.angle -= d;
}


function turtleRight(d) {
    this.angle += d;
}


function turtleForward(p) {
    var rad = radians(this.angle);
    var newx = this.x + cos(rad) * p;
    var newy = this.y + sin(rad) * p;
    this.goto(newx, newy);
}


function turtleBack(p) {
    this.forward(-p);
}


function turtlePenDown() {
    this.penIsDown = true;
}


function turtlePenUp() {
    this.penIsDown = false;
}


function turtleGoTo(x, y) {
    if (this.penIsDown) {
      stroke(this.color);
      strokeWeight(this.weight);
      line(this.x, this.y, x, y);
    }
    this.x = x;
    this.y = y;
}


function turtleDistTo(x, y) {
    return sqrt(sq(this.x - x) + sq(this.y - y));
}


function turtleAngleTo(x, y) {
    var absAngle = degrees(atan2(y - this.y, x - this.x));
    var angle = ((absAngle - this.angle) + 360) % 360.0;
    return angle;
}


function turtleTurnToward(x, y, d) {
    var angle = this.angleTo(x, y);
    if (angle < 180) {
        this.angle += d;
    } else {
        this.angle -= d;
    }
}


function turtleSetColor(c) {
    this.color = c;
}


function turtleSetWeight(w) {
    this.weight = w;
}


function turtleFace(angle) {
    this.angle = angle;
}


function makeTurtle(tx, ty) {
    var turtle = {x: tx, y: ty,
                  angle: 0.0,
                  penIsDown: true,
                  color: color(128),
                  weight: 1,
                  left: turtleLeft, right: turtleRight,
                  forward: turtleForward, back: turtleBack,
                  penDown: turtlePenDown, penUp: turtlePenUp,
                  goto: turtleGoTo, angleto: turtleAngleTo,
                  turnToward: turtleTurnToward,
                  distanceTo: turtleDistTo, angleTo: turtleAngleTo,
                  setColor: turtleSetColor, setWeight: turtleSetWeight,
                  face: turtleFace};
    return turtle;
}

I’ve done projects making “flying bugs in a jar” before, and I decided it would be a good time to try and make one with turtles. Getting the turtle to stay inside the jar was more challenging that I expected, mostly because of the way I made the turtle move. However, it responds to the cursor while it’s inside the jar. I suppose the way to expand on this project would to have multiple fireflies in the jar that have some level of collision so that they could act like a swarm.
I would add screenshots but they’d look just like the program above.

Kevin Thies – Looking Outwards 10

LA HYBRIDScope CITY from Filipa Valente on Vimeo.

limiLAB is run by Filipa Valente, a Portugese architect and interactive artist based in Los Angeles. She did her undergrad and Masters in Architecture at the Bartlett School of Architecture in London, and her Masters in Media Art and Architecture MEDIASCAPES at SciArc. Her work explores space, and leverages light and sound.

Of her works, I found the Hybridscope City to be the most evocative. The project was sited at LAX airport, and passersby getting off the plane could cycle through the different real-time sights and sounds of the city. The form itself is also like an organic web, or specifically filleted voronori meshes. The projection mapping and skeleton tracking were powered by a kinect and a custom max/MSP/Jitter script.

While it is interactive, it doesn’t look intuitive. In the video, people really have to reach out, and it’s said that it needs a person to calibrate first. Plus, it doesn’t look obviously interactive. Since the images are projected downwards, a person’s shadow would only get in the way of the projector, which I think would make people less likely to interact with the piece.

Kevin Thies – Project 10

mountain roads

// Kevin Thies
// Section C
// kthies@andrew.cmu.edu
// Project 10 - Generative Landscape

// initiate containers for mountains, trees, and posts
var mountains = [];
var trees = [];
var posts = [];

// keeps track of time for post spawning
var timer = 0;

// preset y values to act as "layers"
var layer = [470, 435, 400, 380, 320, 290];


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

    // add starting mountains
    for(var i = 0; i < 15; i++) {
        var randomX = random(width);
        mountains[i] = makeMountain(randomX, 5);
    }

    // add starting trees
    for(var i = 0; i < 100; i++) {
        var randomX = random(width);
        trees[i] = makeTree(randomX, 5);
    }

    // add closer starting trees with different speed and color
    for(var i = 100; i < 200; i++) {
        var randomX = random(width);
        var newTree = makeTree(randomX, 4);
        newTree.color = color(10,0,29);
        newTree.speed = 0.9;
        trees[i] = newTree;
    }
}

function draw() {

    // background
    background(12,24,68);
    strokeWeight(3);
    backgroundGradient();
    strokeWeight(0);

    // sun
    strokeWeight(0);
    fill("lemonchiffon");
    ellipse(width / 2, layer[5], 100, 100);

    // ground
    fill(12,24,68);
    rectMode(CORNERS);
    rect(0, layer[5], width, height);
    fill(10,0,29);
    rect(0, layer[4], width, height);

    // mountains
    strokeWeight(0);
    updateAndDisplayMountains();
    removeOffscreenMountains();
    addNewMountains();

    // trees at base of mountain
    updateAndDisplayTrees();
    removeOffscreenTrees();
    addNewTrees();
    addNewCloserTrees();

    // highway railing
      // posts
    updateAndDisplayPosts();
    removeOffscreenPosts();
    addNewPosts();

      // solid barrier part of the rail
    fill(40,37,31);
    rect(0, layer[2], width, layer[0]);
    stroke(98,87,93);
    strokeWeight(3);
    line(0, layer[2], width, layer[2]);
    strokeWeight(0);
    rectMode(CENTER);
    fill(24,12,14);
    rect(width/2, layer[1], width, 40)
}

// POSTS //////////////////////////////////////////////////////////////////

// moves the existing posts
function updateAndDisplayPosts() {
    for(var i = 0; i < posts.length; i++) {
        posts[i].move();
        posts[i].display();
    }
}

// removes the posts that are offscreen
function removeOffscreenPosts() {
    // if posts are offscreen, don't re-add them to posts[]
    var postsToKeep = [];
    for(var i = 0; i < posts.length; i++) {
        if (posts[i].x + 40 > 0) {
            postsToKeep.push(posts[i]);
        }
    }
    posts = postsToKeep;
}

// adds the new posts
function addNewPosts() {
    // every 40 frames, shoot in a new post
    timer ++;
    if(timer == 40) {
        timer = 0;
        posts.push(makePost(width))
    }
}

// how posts are made
function makePost(px) {
    var post = {x: px,
                speed: 12,
                display: postDisplay,
                color: color(98,87,93),
                color2: color(40,37,31),
                move: postMove,
    }
    return post;
}

// is how to move the posts
function postMove() {
    this.x -= this.speed;
}

// builds the posts from the this.x coordinate
function postDisplay() {
    stroke(this.color);
    strokeWeight(3);
    //       |      part of post
    line(this.x, height, this.x, layer[2] - 20);
    //      / /     part of post
    line(this.x, layer[2] - 20, this.x - 10, layer[2] - 10);
    line(this.x - 20, layer[2] - 20, this.x - 30, layer[2] - 10);
    //       -      part of post
    line(this.x - 5, layer[2] - 15, this.x - 25, layer[2] - 15);
    strokeWeight(0);
}

// TREES //////////////////////////////////////////////////////////////////

// how trees are made
function makeTree(tx, layer) {
    var tree = {x: tx,
                layer: layer,
                speed: 0.7,
                display: treeDisplay,
                color: color(12,24,68),
                move: treeMove,
                scale: random(0.5, 1)};
    return tree;
}

// how the trees are moved
function treeMove() {
    this.x -= this.speed;
}

// builds the far trees from an x coordinate
function treeDisplay() {
    fill(this.color);
    ellipse(this.x, layer[this.layer], 20 * this.scale, 20 * this.scale);
}

// moves existing trees
function updateAndDisplayTrees() {
    for(var i = 0; i < trees.length; i++) {
        trees[i].move();
        trees[i].display();
    }
}

// removes trees that are offscreen
function removeOffscreenTrees() {
    // if tree is offscreen, don't re-add it to trees[]
    var treesToKeep = [];
    for(var i = 0; i < trees.length; i++) {
        if (trees[i].x + 20 > 0) {
            treesToKeep.push(trees[i]);
        }
    }
    trees = treesToKeep;
}

// random chance generates a new tree on layer 5
function addNewTrees() {
    var newTreeChance = 0.1;
    if(random(0,1) < newTreeChance) {
        trees.push(makeTree(width, 5))
    }
}

// random chance generates a new tree on layer 4 with fitting color and speed
function addNewCloserTrees() {
    var newTreeChance = 0.1;
    if(random(0,1) < newTreeChance) {
        var newTree = makeTree(width, 4);
        newTree.color = color(10,0,29);
        newTree.speed = 0.9;
        trees.push(newTree);
    }
}

// BACKGROUND //////////////////////////////////////////////////////////////

// makes the gradient seen in the background from y = 0 to layer 5
function backgroundGradient() {
    for(var i = 0; i < layer[5]; i++) {
        var g = map(i, 0, layer[5], 0, 140);
        stroke(255, g, 0);
        line(0, i, width, i);
    }
}

// MOUNTAINS //////////////////////////////////////////////////////////////

// moves existing mountains (so strong)
function updateAndDisplayMountains() {
    for(var i = 0; i < mountains.length; i++) {
        mountains[i].move();
        mountains[i].display();
    }
}

// removes mountains that are offscreen
function removeOffscreenMountains() {
    // if mountain is offscreen, don't re-add it to mountains[]
    var mountainsToKeep = [];
    for(var i = 0; i < mountains.length; i++) {
        if (mountains[i].x + (200 * mountains[i].scale) > 0) {
            mountainsToKeep.push(mountains[i]);
        }
    }
    mountains = mountainsToKeep;
}

// random chance generates a new mountain
function addNewMountains() {
    var newMountainChance = 0.01;
    if(random(0,1) < newMountainChance) {
        mountains.push(makeMountain(width, 5))
    }
}

// how mountains are made
function makeMountain(mx, layer) {
    var mountain = {x: mx,
                    layer: layer,
                    speed: 0.5,
                    display: mountainDisplay,
                    color: color(85,54,86),
                    color2: color(85, 54, 106),
                    move: mountainMove,
                    scale: random(0.5, 1)};
    return mountain;
}

// how mountains are moved
function mountainMove() {
    this.x -= this.speed;
}

// actually builds the moutain based off an x coordinate
function mountainDisplay() {
    fill(this.color);
    triangle(this.x, layer[this.layer],
         this.x + (200 * this.scale), layer[this.layer],
         this.x + (100 * this.scale), layer[this.layer] - (180 * this.scale));
    fill(this.color2);
    triangle(this.x + (170 * this.scale), layer[this.layer],
              this.x + (200 * this.scale), layer[this.layer],
              this.x + (100 * this.scale), layer[this.layer] - (180 * this.scale));
}

// Congradulations, you made it to the end! *confetti emoji*

At first when I was thinking about what to do, I thought about long car rides through the mountains. I sketched up an idea that involved cars going different directions, but after implementing the moving mountains, I realized I had to simplify my idea. I kept the most important part of that, the mountains, and rebuilt from there. I think having all the layers helps add some measurable scale to the mountains and trees in the background. I’m also just really with how it turned out, and although making the objects got tedious, once I had one and understood how the pieces fit together, it really sped up from there.

My plan involving mountains, roads, and layers

Kevin Thies – Looking Outwards 9

Images from the UK cities line

When going through various Looking Outwards, I found a post by Curran which talked about a 2018 Craig Taylor/Ito Design project called Coral Cities that was an interesting take on mapping cities. As a fellow architecture student, I found this project really cool but also really simple. It’s not much different than what Walkscore’s doing (here‘s an example using their API) in measuring travel times from a point, and it just takes that data, adds an aesthetics-only gradient and gradually increases its elevation and tapers off. I think what helps is the really stunning rendering that’s clean and makes the forms really pop. I think it’s worth noting that the run of data that focused on the most livable cities came from an existing list by Mercer and the displayed data is only travel distance in 30 minutes from the city center. Additionally I wouldn’t see this as a functional map, but instead as just a translation of utilitarian data into art. I’d argue that maps as they are now work well and don’t need to change. The map went from paper and static to digital and interactive, like Google Maps. However, art pieces using maps are great case studies, as road maps are what we think of in architecture. They’re site-specific. They’re made by and for humans. There’s an organic randomness that’s very deliberate. That side of maps are a cool field to look into.

Kevin Thies- Project 9

Kthies sketch
So originally, I was planning on doing just a greyscale image that used the white space to display grayscale, but my friend Raven (pictured) has been working on a project related to superheros, which made me think of comics and how those were printed early on, so I expanded the project scope to take an image and break it down into simulated CMYK values (it’s just RGB in the end because screens) with each of those being slightly offset from the analyzed pixel. I think it turned out really well and makes cool-looking images!

This is when the distance between “pixels” is 1

This is when the distance between “pixels” is 5
// Kevin Thies
// Section C
// kthies@andrew.cmu.edu
// Project 09 - Computational Portrait

var portrait;           // the image
var pixels = [];        // holds the pixels
var step = 6;           // distance between "pixels"
var radiusV;            // radius of value circles
var radiusR;            // radius of red circles
var radiusG;            // radius of green circles
var radiusB;            // radius of blue circles
var rgb = [];           // holds RGBA values of pixels


// preload image
function preload() {
    var myImageURL = "https://i.imgur.com/sveeELP.jpg";
    portrait = loadImage(myImageURL);
}

function setup() {
    createCanvas(480, 520);
    background(255);
    portrait.loadPixels();
    noLoop();
    noStroke();
}

function draw() {
    // get a grid of positions one step apart

    for(var x = 0; x < width; x += step) {
        for(var y = 0; y < height; y += step) {

            // value ===============================================

            // kept darker than the color values to just balance it out
            // RGB is structured the same way, just not using brightness but direct
            // R, G, and B values

            // sets the radius of the circle, remapping the value of color/brightness
            // to a circle between 0 and step * 1.3 in diameter
            // with step > 1, it means the colors can overlap, hiding the white background
            radiusV = map( brightness( portrait.get(x * 2, y * 2) ),
                          255, 0,
                          0, step * 1.5 );

            // sets the fill color with transparency
            fill(0, 0, 0, 80);

            // draws the ellipse at the point
            ellipse(x, y, radiusV, radiusV);

            // RGB ==================================================

            // rgb takes on the [R, G, B, A] values as an array
            rgb = portrait.get(x * 2, y * 2);

            print(rgb[0] + "  " + rgb[1] + "  " + rgb[2] + "  " + rgb[3]);
            // by filling the color with 255 of not that color, when stacked up the
            // colors match - ex. red = 0,255,255 g = 255,0,255 b = 255, 255, 0
            // basically I translated RGB to CMY

            // red / cyan
            radiusR = map(rgb[0],
                          255, 0,
                          0, step * 1.3);
            fill(0, 255, 255, 80);
            ellipse(x + step / 5, y, radiusR, radiusR);

            // green / magenta
            radiusG = map(rgb[1],
                          255, 0,
                          0, step * 1.3);
            fill(255,0,255, 80);
            ellipse(x - step / 5, y, radiusG, radiusG);

            // blue / yellow
            radiusB = map(rgb[2],
                          255, 0,
                          0, step * 1.3);
            fill(255,255,0,80);
            ellipse(x, y - step / 5, radiusB, radiusB);
        }
    }
}

Kevin Thies – Looking Outwards 08


Minimaforms is an experimental architectural design practice founded in 2002, the vision of two brothers, Theodore and Stephen Spyropoulos. Theodore is an architect, and Stephen is an artist and interactive designer. Their work is synergistic, and look at possible futures and designing projects that act as prototypes. They’re interested in looking at communication and the way information is deconstructed and connected.

Their work is quite refined. The more pure architecture projects not display parametricism, but specifically resemble communicating nodes, or as they say, an agent based system with an emphasis on how they communicate over time. One of their projects that I particularly admire is titled Archigram on their website, and is an evolution on a 1966 Archigram project “Living Pod“, which is a proposal for a trailer home-like dwelling that can move and connect to other units to create larger structures. I think the project is impressive both on the conceptual level, but especially that they brought it all into a large physical model. Additionally, they took the idea of the pod and literrally evolved it, developing spines and tails to connect, which does fit with the idea of a time-based development.

“Archigram” by Minimaforms

Their presentation strategy felt “standard architectural”. They ran through their work, showing images and videos, going through the goals and results of the projects. By looking at the results of the projects, you can frame what it is an individual or group aims to change or improve.