Midterm Project – “Ice&Glasses” by Marcos Hernández

Introduction

I think I might have liked challenges since I was a kid, and the idea of trying to do something different and challenging was very compelling.

During the weeks that passed in the I.M. lab, even though some of my works seemed a bit lacking in art, I was mostly focused on the technicality of things. For example, in one assignment I was experimenting with using the mouse as a medium of interactivity, and in the other I was experimenting with the concept of “gravity and physics”. When I noticed that what I was trying to do was possible, then I focused myself on delivering the best I could in terms of what I had learned so far. Thus, presenting “Ice&Glasses”, an interactable piece of art, and also a game that focuses on delivering the player with a good auditory and visual experience with the use of nostalgic-like art and a heavy focus on the use of the mouse.

Full-screen link:  Ice&Glasses in fullscreen

Concept and Inspiration

As mentioned, thanks to the experimentation I did in previous assignments, I came to the realization that, most likely, everything that I imagined was possible to apply to it, but obviously, it would require a lot of time to apply.

The whole concept of the game is not inspired in reality by other mediums, but rather by something that came into my mind while I was doing Assignment #4, in which I wanted to simulate some diamonds falling as to represent some data; in reality, it looked more like ice cubes… Sadly, that simulation was an illusion since it only took into account some distance and then stopped when it reached it.

When I thought about ice cubes, I had a weird realization. When I thought about ice cubes, the first things that came to mind were ice cubes and glasses. I searched for it on Google and came across this A.I. picture:

oil painting of two empty transparent cups on a table

After seeing this, I knew what I wanted to do.

With this idea in my mind, I wanted to actually create the illusion of physics and proper gravity in the game with the use of ice cubes and cup glasses. I had the code; it was only a matter of experimenting enough to find a way to implement this without causing massive slowdowns in the computer due to the amount of “if” conditions it would require.

The code

a. Overview
In summary, it is a lot of code.

It is easily the largest code I have ever created at the moment, and I am really proud of the outcome. Some of the highlights are the following:

1. It has a system to use the mouse without it going too spammy.

function mouseReleased() {
  if (mouse.called == 1) {
    mouse.called = 0;
    if (cubes[mouse.grabbing] == null) {
    } else {
      cubes[mouse.grabbing].isgrabbed = 0;
    }
  }

  //Allows to exit the game and avoid array issues.
  mouse.grabbing = "nothing";
}

2. A collision system.

function checkCollision(i, c) {
  //Check collition in X axis.
  if (
    cubes[i].y + cubes[i].h > cubes[c].y &&
    cubes[i].y < cubes[c].y + cubes[c].h
  ) {
    if (cubes[i].type == "ice") {
      if (cubes[i].insidecup == 1) {
        if (
          cubes[i].x < cubes[c].x &&
          cubes[i].x > cubes[c].x - 25 &&
          cubes[c].type == "glass"
        ) {
          cubes[i].hit_x_r();
        }
        if (
          cubes[i].x + cubes[i].w > cubes[c].x + cubes[c].w &&
          cubes[i].x + cubes[i].w < cubes[c].x + cubes[c].w + 25 &&
          cubes[c].type == "glass"
        ) {
          cubes[i].hit_x_l();
        }
        if (
          cubes[i].x + cubes[i].w > cubes[c].x &&
          cubes[i].x + cubes[i].w < cubes[c].x + 8 &&
          cubes[c].type == "ice"
        ) {
          cubes[i].hit_x_l();
        }
        if (
          cubes[i].x < cubes[c].x + cubes[c].w &&
          cubes[i].x > cubes[c].x + cubes[c].w - 8 &&
          cubes[c].type == "ice"
        ) {
          cubes[i].hit_x_r();
        }
      } else if (cubes[i].type == "ice" && cubes[i].insidecup == 0) {
        if (cubes[c].type == "ice") {
          if (
            cubes[i].x + cubes[i].w >= cubes[c].x &&
            cubes[i].x + cubes[i].w <= cubes[c].x + 25
          ) {
            cubes[c].hit_x_r();
          } else if (
            cubes[i].x <= cubes[c].x + cubes[c].w &&
            cubes[i].x >= cubes[c].x + cubes[c].w - 25
          ) {
            cubes[c].hit_x_l();
          }
        } else if (cubes[c].type == "glass") {
          if (
            cubes[i].x + cubes[i].w >= cubes[c].x &&
            cubes[i].x + cubes[i].w <= cubes[c].x + 15
          ) {
            cubes[c].hit_x_r();
          } else if (
            cubes[i].x <= cubes[c].x + cubes[c].w &&
            cubes[i].x >= cubes[c].x + cubes[c].w - 15
          ) {
            cubes[c].hit_x_l();
          }
        }
      }
    }

    if (cubes[i].type == "glass") {
      if (cubes[c].type == "ice") {
        if (
          cubes[i].x + cubes[i].w >= cubes[c].x - 3 &&
          cubes[i].x + cubes[i].w <= cubes[c].x + 10 &&
          cubes[c].insidecup == 0
        ) {
          cubes[c].hit_x_r();
        } else if (
          cubes[i].x <= cubes[c].x + cubes[c].w + 3 &&
          cubes[i].x >= cubes[c].x + cubes[c].w - 10 &&
          cubes[c].insidecup == 0
        ) {
          cubes[c].hit_x_l();
        }
      } else if (cubes[c].type == "glass") {
        if (
          cubes[i].x + cubes[i].w >= cubes[c].x - 3 &&
          cubes[i].x + cubes[i].w <= cubes[c].x + 40 &&
          cubes[c].isgrabbed == 0 &&
          cubes[c].insidecup == 0
        ) {
          cubes[c].hit_x_r();
        } else if (
          cubes[i].x <= cubes[c].x + cubes[c].w + 3 &&
          cubes[i].x >= cubes[c].x + cubes[c].w - 40 &&
          cubes[c].isgrabbed == 0 &&
          cubes[c].insidecup == 0
        ) {
          cubes[c].hit_x_l();
        }
      }
    }
  }

  //Check collition in Y axis.
  if (
    cubes[i].x <= cubes[c].x + cubes[c].w &&
    cubes[i].x + cubes[i].w >= cubes[c].x
  ) {
    if (cubes[i].type == "ice") {
      if (
        cubes[i].y + cubes[i].h > cubes[c].y &&
        cubes[i].y + cubes[i].h < cubes[c].y + 15
      ) {
        cubes[c].hit_y_d();
      }
      if (cubes[c].type == "ice") {
        if (
          cubes[i].y > cubes[c].y &&
          cubes[i].y < cubes[c].y + cubes[c].h + 1
        ) {
          //To avoid any issues with clipping, this makes sure that every block that is inside the cup that is above it, it will share the "inside of" property.
          index2 = cubes.indexOf(cubes[c]);
          index3 = cubes.indexOf(cubes[i]);

          if (index2 > -1) {
            if (cubes[index2].insidecup == 1) {
              cubes[index3].insidecup = 1;
            }
          }

          cubes[c].hit_y_u();
        }
      }
      if (cubes[c].type == "glass") {
        if (
          cubes[i].y + 45 > cubes[c].y + cubes[c].h &&
          cubes[i].y < cubes[c].y + cubes[c].h &&
          cubes[c].x + cubes[c].w > cubes[i].x + 10 &&
          cubes[c].x < cubes[i].x + cubes[i].w - 10 &&
          cubes[c].insidecup == 0
        ) {
          cubes[c].hit_y_u();
        }
      }
    }
    if (cubes[i].type == "glass") {
      if (
        cubes[i].y + cubes[i].h > cubes[c].y &&
        cubes[i].y + cubes[i].h < cubes[c].y + 15 &&
        cubes[c].insidecup == 0 &&
        cubes[c].type == "ice"
      ) {
        cubes[c].hit_y_d();
      }
      if (cubes[c].type == "ice") {
        if (
          cubes[c].y > cubes[i].y &&
          cubes[c].y + cubes[c].h < cubes[i].y + cubes[i].h - 90 &&
          cubes[c].x + cubes[c].w > cubes[i].x + 25 &&
          cubes[c].x < cubes[i].x + cubes[i].w - 25 &&
          cubes[c].insidecup == 0
        ) {
          //Find who cube index is inside. Also, this is really necessary to fix a bug with the cubes going out of the glass cup.
          index2 = cubes.indexOf(cubes[c]);

          if (index2 > -1) {
            cubes[index2].insidecup = 1;
          }
        } else if (
          ((cubes[c].y > cubes[i].y - 20 && cubes[c].y < cubes[i].y - 2) ||
            (cubes[c].y > cubes[i].y + cubes[i].h + 1 &&
              cubes[c].y < cubes[i].y + cubes[i].h + 10)) &&
          cubes[c].insidecup == 1
        ) {
          //Find who cube index is outside. Also, this is really necessary to fix a bug with the cubes going out of the glass cup.
          index2 = cubes.indexOf(cubes[c]);

          if (index2 > -1) {
            cubes[index2].insidecup = 0;
          }
        }

        if (
          cubes[i].y + cubes[i].h > cubes[c].y &&
          cubes[i].y + cubes[i].h - 30 < cubes[c].y + cubes[c].h &&
          cubes[c].insidecup == 1
        ) {
          cubes[c].hit_y_u();
        }
      }

      if (cubes[c].type == "glass") {
        if (
          cubes[c].y + cubes[c].h > cubes[i].y &&
          cubes[c].y + cubes[c].h < cubes[i].y + 150
        ) {
          cubes[c].hit_y_u();
        }
      }
    }
  }

  //Check collition with table.
  if (
    cubes[c].y + cubes[c].h > scenario_table.y &&
    cubes[c].y + cubes[c].h < scenario_table.y + 60 &&
    cubes[c].x + cubes[c].w > scenario_table.x &&
    cubes[c].x < scenario_table.x + scenario_table.w
  ) {
    cubes[c].hit_y_u();
  } else {
    cubes[c].tempy = 1000; //Send to deletion.
  }
}

3. A gravity and acceleration system.

function Gravity(i) {
  if (cubes[i].y + cubes[i].h < cubes[i].tempy) {
    cubes[i].gravity();
  }
}

The function is the following in Cubes.js:

gravity() {
  //14 seems more than enough to simulate gravity and acceleration.
  if (this.acceleration < 14) {
    this.acceleration++;
  }
  this.y += this.acceleration;
}

4. A code that helps keeps variety in the ice cubes (that means, from a single .png have different outcomes like different sizes and angles)

display() {
    //Push() and pop() isolates the figure properties.
    push();
    if (this.type == "ice") {
      //Different rotations for the same cube.
      if (this.rotated == 0) {
        translate(this.x, this.y);
        rotate(0);
      } else if (this.rotated == 1) {
        translate(this.x + 30, this.y + 2);
        rotate(90);
      } else if (this.rotated == 2) {
        translate(this.x + 30, this.y + 30);
        rotate(180);
      } else if (this.rotated == 3) {
        translate(this.x - 2, this.y + 30);
        rotate(270);
      }
      image(cubeimg, 0, 0, this.w, this.h);
    }

b. The separation of classes

The code is divided into the following way:

  • Cubes.js (Has the logic for the ice cubes and glasses).
  • Levels.js (Originally started as a set of levels, it is now a set of rules established in order to ensure that there are gameplay elements).
  • Menu.js (It contains the start menu, tutorial, credits, selections, information and winning or losing screens).
  • UI.js (It is the information the player will not only see but be able to interact with. For example, it displays how many ice cubes are left and an icon of a door to return to the start menu).
  • Scenario.js (it has the code that allows the display of the scenario and the logic of the spawners).
  • Mouse.js (Created with the sole purpose of allowing a better use of the mouse and its events, like also allowing to store data in its class).

I also divided the graphics and sounds into their respective folders, since there were a lot of them.

c. Difficulties

The collision system took a lot of time, easily around 10 hours, and I discovered a good method by just accident since I was still trying to make it in the way of finding the distance between two objects, when in reality, the best possible solution in this case is just to continuously apply force in specific directions according to whether an object is nearby or trying to clip through it. Not only that, but using the mouse as a medium to drag other objects and still be able to display physics was very complicated.

The graphics

This is one of the parts that were actually very fast to do, maybe either to already having the idea in my mind or due to having experience in using tools such as GIMP for photo editing.

Since I wanted to capture the essence of nostalgia (like in the crayon.ai image), I had to actually go in my way to “downgrade” some images I found on Google. Here are some screenshots of the process I went through in creating these images:

Here I was just starting

A bit more of progress.

The “ice cubes”

Some of the layouts

After I finished editing all these images, since I would look very bad in full screen due to the realism of these images, I applied a filter to make them more “nostalgic.”

The process was very similar to the rest of the images.

As an important note, the ice cubes are made yellow in order to stand out even more with the glass cups, as in to allow the best possible user engagement possible by using intuition.

The sound & music

Most of the sounds I obtained accomplished their purpose, but some of them were a bit delayed; thus, I had to use a tool called “Audacity” to cut some of the delay to allow the best possible feedback for the user.

Also, the music choice was still the same, although I decided to go with the second proposed option from my report, since it reflected a more nostalgic and calming ambient:

A problem… Where is the gameplay?

Indeed, there was only one problem: Where is the gameplay? I had only created an interactable piece of art, but it really conveyed gameplay elements. For the user, it was like there was gameplay, but it could not be seen anywhere; thus, I had to come up with a solution: Divide the game between “normal” gameplay and “sandbox,” which is more of what I envisioned.

In the normal mode, the player has the established challenges of aligning the cubes and the glass cups in a visible red highlight, under a time limit, and without losing any object. The sandbox mode is just complete freedom, and there are no penalties. Also, the player has the ability to use the keyboard in order to spawn more objects quicker or to delete all of them in an instant.

Trying to figure out the gameplay.

Improvements and Conclusion

I wanted to improve the collisions a bit more and add more effects for images, but due to time constraints, it was not possible. Also, the challenges could vary more; for example, move the ice cubes to the glass cup while avoiding the red-highlighted zones that will make you lose the game immediately.

Still, I am really proud of my work, and this allowed me to have more confidence in my skills as a programmer, and also as an artist, since I had to come up with proper solutions to create the best outcome possible, while also maintaining a good (possibly thoughtful) experience for the user.

Extra content

If you want to see some progress, here are some old versions of the midterm:

Have a good day! And thanks for reading my midterm report.

Midterm Project – Tank Game

Inspiration

I remember one of the game I played when I was a child having a small arcade handle. There was a specific tank game that I remember to be really difficult to even reach the fifth level because of the small screen and the aggressive attack from the enemies. Below is an image of the game I mentioned:Therefore, I want to replicate this game as my midterm project using p5.js.

Character design:

There are only 2 types of characters in the game: the Player and the Enemies. As similar to the inspiration game, I made the player to have the exact design. It has 2 rows and a body and a head to shoot the bullets. The layout and the head position will change based on the key arrows. Below is the illustration:Next is the enemy design. Instead of having the same enemy design, I made the enemies to have a plus sign. This is because the enemies will not move from the their position. However, I want to make it more difficult to win, so I made the enemies to have the ability to change the shooting direction based on the player’s position.

Below is the code snippet for the changing of enemies’ shooting direction:

//change the bullet direction based on the player's position
for (let i = 0; i < enemiesList.length; i++) {
  if (player.y == int(enemiesList[i].y)) {
    if (player.x < enemiesList[i].x) {
      enemiesList[i].direction = 3;
    } else {
      enemiesList[i].direction = 1;
    }
  }
  if (player.x == int(enemiesList[i].x)) {
    if (player.y < enemiesList[i].y) {
      enemiesList[i].direction = 0;
    } else {
      enemiesList[i].direction = 2;
    }
  }
}

Game structure:

Initially, the first thing the user will see is the Main game page. The page includes a “Start” button and a button for instruction. It looks like below:

The player can use the arrow keys to move around and use the space bar to shoot at the enemies. The game goal is not to obtain to highest score but to survive as long as possible. Below is the main screen game, it includes player, enemies and level information:

Finally, if the player’s character is hit by the enemy’s bullet, it will display the “Game Over” screen and display the highest level achieved:

Below is the code snippet for the Player, Enemy and Bullet objects:

class Player {
  constructor(x, y, w) {
    this.x = x;
    this.y = y;
    this.w = w;
    this.direction = 0;
    this.layout = 1;
  }
}

class Enemy {
  constructor(x, y, w) {
    this.x = x;
    this.y = y;
    this.w = w;
    this.direction = int(random(3));
  }
}

class Bullet{
  constructor(x, y, w, direction, rectWidth, colorCode){
    this.x = x;
    this.y = y;
    this.w = w;
    this.direction = direction;
    this.rectWidth = rectWidth;
    this.colorCode = colorCode;
  }
}

Design and Sounds:

Since this is similar to an arcade game, I chose and the characters’ design to be built up from blocks of squares. Because of this, I chose to the fonts to have the similar effect, made up from different squares. I uploaded a font file from the Google Fonts (Silk).

For sound, there are 4 different sound effects. The first one is the background music that will play in throughout the game play experience. The second one is the shooting sound when the user hits the space bar. The third one is when the enemy is hit by the bullet. Lastly, there is a losing sound effect if the player is hit.

Below is the code snippet for all the sounds used:

sound = loadSound("sound/shoot.wav");
hit = loadSound("sound/hit.mp3");
bgSound = loadSound("sound/bg.mp3");
loseSound = loadSound("sound/lose.mp3");

Obstacles and Improvements:

It is really difficult to know the exact position of the characters to control when they are hit. Currently it only works partially. This means that even though the system recognizes that the player is hit, it takes a bit of time for the screen to change. My thought is that my math for coordinate checking is wrong. However, I tried to changes the coordinates multiple times but it does not seem to work.

Another thing that is really difficult to do is the enemies are spawned overlap with the player’s position. Sometimes, this make an immediate lose game because the user does not have enough time to move. I tried to but conditions but it does not seem to work.

For improvements, I want to make the enemies to be more interactive. I want them to move around according to the player’s position and shoot towards them. The current version only target the player partially.

Game link:

Fullscreen link

Midterm | Pyro Dancer Release

Game Description

Pyro Dancer is an endless-runner 2D game with heavy inspirations from notable titles, such as Castlevania and Metroid inspire the game. Paired with 8-bit sound and theme by Haynes, Pyro Dancer tries to mimic the retro feel of older games.

TOP |Castlevania -Grimoire of Souls- Official Website | KONAMI

Castlevania.

The player assumes the role of a knight, invading the evil castle. Upon entering the castle, he is greeted by dozens of fireballs. To win the game, the player must dodge the fireball and achieve 500 score points. If the player runs out of health points before reaching the score, they will die.

Development Process.

Knight Sprite

Sketches, Ideas, Drawings!

The game uses sprites, images, and the p5 Play library. All assets used are linked in this document, please refer to it for further information.  The library is extensively used to allow better control over the sprites and performance.

In the beginning, I sketched, drew, and created a map of my code. Generally, it is split into three parts: a) Hero class, which functions as a class that generates every component of the player’s character.

class Hero {
  constructor(x, y) {
    this.x = x;
    this.y = y;
    //HEALTH
    this.heroHealth = 100;
    this.healthbar;
    //HERO
    this.hero;
    //BARRIER
    this.barrier;
    this.barrier2;
    this.barrierX = this.x;
  }

b) Fireball class, which serves as the group to generate, spawn, and display the fireball obstacles.

class Fireballs {
  constructor(x, y) {
    this.x = x;
    this.y = y;
    this.amount;
    this.fireballs = new Group();
    this.fireball;
    this.fireballAni;
  }

c) Main sketch, which encompasses both previous classes and displays the game properly. In this file, many functions initialize, check, and update the game. The game itself is split into three parts: The main menu, the Game Screen, and the Victory / Death Screen.

Main Screen

Game Screen

Death Screen

Challenges & Difficulties

Figuring out how the sprite system works in the p5 Play library was quite a challenge, however, it helped me a ton when configuring the fireball and character. But because of the way they draw sprites, this also made a huge problem when it comes to display updates.

I tried to code the game to allow fullscreen mode, but it never really worked properly. However, the game is coded so that it fits any kind of display resolution, but a manual refresh is needed to properly display the elements inside. I suspect this is mostly because of how spaghettified my code has become to the point that I could not re-arrange ways to update the display.

Conclusion

I truly enjoyed the process of making games with p5 js. It is not the best tool to make one, but it does have some unique capabilities that are interesting to explore. However, at some point, I became frustrated and named a function psychopathically:

function butcherSprites() {
  //remove all sprites
  player.hero.remove();
  obstacle.fireballs.removeAll();
  player.healthbar.opacity = 0;
  homeButton.opacity = 1;
}
Try out the game here!

p5.js Web Editor | Pyro Dancer – Release (p5js.org) [Display with Code]

Pyro Dancer – Release (p5js.org) [Windowed Fullscreen]

Midterm Project: Space Navigator by Sihyun Kim

Final outcome:

Link to fullscreen:

https://editor.p5js.org/sihyunkim/full/o-b8ZVYnT

Concept of the Game

If someone asks me what my childhood favorite game was, I will say that my favorite game was Flappy Bird without any doubts. As a child, I found avoiding obstacles by controlling the bird up and down along with the increase in speed as the game progressed very intriguing and fun. Hence, for this midterm project, I wanted to create something working with the same logic but in a different style. So, inspired by my childhood favorite game- Flappy Bird, I created a game called “Space Navigator”. As its name suggests, the theme of the game is space exploration. The player is part of an astronaut team sent from Earth to explore Saturn. The mission of the player is simple. All the player has to do is avoid collision with the moving meteoroids on the way to Saturn and follow the path of the stars to arrive on Saturn.  

The images below are the concept image I drew while conceptualizing my project and the transparent images I utilized for my project. I wanted my midterm project to look cute and round. So, I searched for lots of images in the Canva. And ended up using the round-ish images below. Also, I wanted to give people the feeling of childish wonder. So, I intentionally chose a piece of music (video below) from a Nintendo game called Captain Toad: Treasure Tracker. I found this music to be a great fit to be  background music for my game as this music is not too loud but still not boring. Most importantly, this song gave me that feeling of “childish wonder”. So,  I decided to use this. 

Concept Image I drew for my project before working on it:

Images obtained from Canva that I have utilized for the game


The background music for the game:

How the game works: 

The game is quite straightforward. As mentioned above, all what player has to do is to avoid the meteoroids and follow the stars as much as he/ she can. Also, the player has to be careful to not touch the bottom and top boundaries to not lose track of the orbit.  And this is done by controlling the rocket. There are two modes in which the player could choose: Voice mode and key mode. As the modes’ names suggest, the voice mode enables the player to control his/her rocket using his/her voice volume. The voice volume is mapped to the adjustment of the velocity of the meteoroids. The higher the voice volume of the player is, the higher the rocket arises. The key mode is the same as the Voice Mode except that this mode involves controlling the rocket using the keyboard’s space bar instead of the player’s voice volume. The better the player plays the game, the faster the game goes. After “collecting” 15 stars, the game will end with the text that the player arrived at Saturn will appear. 

Explanation of codes for the game:

I have three essential components in my game: rocket, meteoroid, and stars. And I made  Meteoroids(), Rocket(), and Stars() classes for these components of the game. First of all, as the name suggests, the Meteoroids() class is responsible for making the moving meteoroids (obstacles) of the game and checking if they collide with the rocket. Inside this class, there are three functions: update(), show(), and checkCollision(). The update function is responsible to animate the meteoroid while the show function is responsible for depicting the meteoroid itself in the screen. Checkcollision is a function that does check Collision with the rocket. The logic behind the Checkcollision function follows the equation of the circle. Whenever collision with a rocket is detected, the flag named winningState changes to “lost”, indicating that the player lost. 

Next, the Rocket() class is responsible for properties related to the rocket that the player controls. In this class, there are four functions: gravity(), move(), update(), show(), and boundary check(). The gravity function stimulates gravity affecting the rocket. Meaning, that if the player does nothing, the gravity function will let the rocket move down. And move function is responsible for adjusting the velocity of the rocket based on the user’s voice volume or the space bar input. Using an if-else statement and a flag named “keycode”, I made sure that the rocket would only be controlled using the voice volume in voice mode and controlled by pressing the space bar in key mode. The update function is responsible for adding the adjustment from the move function to the y position of the rocket, resulting in the animation of a rocket being moved up or down. And show function is responsible for depicting the rocket in the function. The boundaryCheck() function checks if the rocket touched the top or bottom boundary by checking the position of the rocket and the coordinates of the boundaries. If the rocket touches the boundary, two flags, winningState and touchedBoundary, are changed. 

Thirdly, the Stars() class is the class responsible for generating the stats that will guide the rocket. Like the other two classes, the star function has show() and update() function which allows the stars to be animated and shown. One unique thing about the Stars class is that it has two collision check functions- one with the meteoroids and the other with the star. This is because I had to make sure that the stars did not overlap with the meteoroids and use collision detection with stars to know when to score the player. 

In the main sketch, codes related to ensuring the correct procedure of the game, global variables, and functions such as windowResized(), preload(), setup(), keyPressed(), and mousePressed() are located. Basically, windowResized() is responsible for resizing the game screen for the fullscreen mode, preload() is responsible for loading the images, fonts, and files for the game. In setup() function, I have put some properties of the game that should not be redrawn every time (e.g. background music).  The keyPressed() function is responsible for triggering the boost for the rocket in key mode when the space bar is pressed and letting fullscreen be activated/ deactivated when the “f” or “F” key is pressed. 

The mousePressed() is responsible for all conditions related to the mouse press. All the buttons in the work game work due to flags such as gameState being triggered in this mousePressed() function.  the draw() function handles the procedural aspects of the game, and the game’s state changes based on the value of the gameState variable. The use of if-else statements ensures that different screens are displayed depending on the current state of the game. I think this approach allows seamless transitions between various screens, guiding players through different gameplay stages.

Proud Things from the Project

The part which I am most proud of is also part of the star class. Particularly, I am proud of the code snippets attached below, which are responsible for generating stars. The first code snippet is the code of the checkCollision() method in the Stars class. As the first code snippet shows, the logic of a star’s detecting collision with the meteoroids involves the equation of the circle. It checks if the distance between the meteoroid and the star is greater than the sum of the size of the star and the radius of the meteoroid. The output of checkCollision() inside the star class is a boolean variable. It returns true if there was a collision detected and false if there was no collision. The second code snippet is the code snippet located in the main sketch, which simply ensures that the stars are making sure that stars are not being generated at the same position as the meteoroids. The logic of this code snippet using a while-loop is that it keeps generating new Star objects until the checkCollision() method of the Star object returns false. Then, we add the Star object into the array of the Star object. There will be only one object being pushed into an array at a time as we do not technically “save” the generated Star object in the while loop if it overlaps with the meteoroid object.

Code snippet of checkCollision() inside the Star class:

// method to check for collisions with meteoroids
checkCollision(meteoroids) {
  for (let i = 0; i < meteoroids.length; i++) {
    let distX = this.position.x - meteoroids[i].position.x;
    let distY = this.position.y - meteoroids[i].position.y;
    let distance = sqrt(distX * distX + distY * distY);
    if (distance < this.size + meteoroids[i].radius) {
      return true; // collision detected
    }
  }
  return false; // no collision detected
}
//method to check for collisions with the rocket
checkCollisionRocket(other,music) {
  let distX = this.position.x - other.position.x;
  let distY = this.position.y - other.position.y;
  let distance = sqrt(distX * distX + distY * distY);
  if (distance < this.radius + other.radius) {
    metRocket = true;
    music.play()
  }
}

The while-loop that ensures generated star doesn’t collide with meteoroids:

//checking for collision with stars
while (collision) {
  starX = windowWidth + 100;
  starY = random(windowWidth * 0.125, windowWidth * 0.875);
  newStar = new Stars(starX, starY); // Regenerate star
  collision = newStar.checkCollision(meteoroids);
}
//adding the created new Star object to the array if it does not collide with any meteoroids
stars.push(newStar);

I was just so proud of this part of my project because I felt like this was the hardest part of my project. I think that the collision check that the stars have is way more complicated than the collision check that the meteoroids have, because the stars have to consider where would the meteoroids be placed and make sure that stars are not being generated there. I thought using a while-loop that only exists when a star object does not collide with the meteoroid for this part of coding was a very simple but wise decision.  Visual-wise, I just find my output so cute and gives that sense of childish wonder. So, I am very proud of my intuitions(?) to choose the images and fonts  I have utilized. Also, I am proud of the little details I added to this project such as the sound effects when the buttons are clicked or when the mission failed/succeded. While they are very subtle, I think they altogether enhanced the quality of my work. So, I am very satisfied with my output. 

Areas of improvement and problems that you ran into: 

In the future, I think I can improve the game by adding more features. For instance, I could make the player have multiple “lives” per session of the game to enable the player to easily succeed in the mission. I could maybe also consider enabling a two-player key mode, where two players “race” to reach Saturn first. I think it will be also interesting if I allow the player to choose the level of difficulty in the first place or choose the types of obstacles that the player will encounter (e.g. aliens, meteoroids, or shooting stars).

Other than this, there weren’t lots of problems that I ran into, and I was able to resolve these as well.  One of the biggest problems I encountered was to ensure that stars were generated in a random place that didn’t overlap with the meteoroids. 

However, I was able to solve this problem by using a while loop (further explanation is explained above) and the solution to this problem became my proudest part of the project. Another problem I encountered was a very mysterious problem that I still don’t get why it was solved. I tried to load images but it didn’t work out well. I first thought that I perhaps made some spelling mistakes or syntax errors when writing the code. But, it turns out that I didn’t make one. After a few minutes of debugging, I just duplicated the project itself. The duplicated project just loaded the images properly when I did not do anything after duplicating it. I honestly still don’t get why the problem was caused and how it was resolved only through “duplicating” the project. 

Overall

As usual, I loved working on this project. This project was very fun to create and it improved my coding skills. I am very satisfied with the outcome, but I think it could have been improved by adding more modes or features as I have mentioned above. 

Thank you so much for reading my blog post 🙂 

 

Midterm Project – Rotten Rescue

Concept:

The initial concept of this game was to be something different and unusual of the games that we already see where the “good” item is collected and the bad item is supposed to be avoided. But since I am using apples, I decided to relate this a bit to physics and the Isaac Newton apple incident that led to him discovering gravity… This makes the game more interesting and leaves a chance to improve it and make it a bit educational as well.

Full screen display of the game: https://editor.p5js.org/jana_mohsen/full/jEKauH5OR

Game:


Design:

In this game, players maneuver a trash can to catch rotten apples while avoiding the clean ones. The game is structured into several screens: an introduction, instructions, the main game, and a game-over sequence. There are multiple classes used like the Apple Class that regulates both the types of apples, the Trashcan Class which ensures the moevments of the trashcan on the horizontal axis and lastly, the Bubbles Class which is just used to regulate the number, size and speed of apples that are being displayed in the game over page.

The most important part of the code are the functions for each page of the game. The (displayIntro()) function displays the introduction page, which greets with the player with the game title and a start button. Then the second function: (displayInstructions()) teaches the player the “how-to-play” guidelines, respectively, with options to start or proceed to the game. It also allows the player to input the game before starting to play.

During the active game phase, apples of two types—“clean” and “rotten”—descend from the screen’s top. Players control the trash can, moving it left or right to catch rotten apples while avoiding the clean ones. The game keeps score and includes a countdown timer, enhancing the challenge. It manages the player’s interaction with the game through the trash can’s movements when they press keys, allowing the player to move the trash can left or right to catch falling apples, facilitated by the trashCan.display() and trashCan.move() methods.

Within its cycle, runGame() also takes charge of spawning new apples at fixed intervals, alternating between “clean” and “rotten” types, which are then added to an array for tracking. As time progresses, it continuously updates the player’s score by incrementing by 10 based on the type of apples caught and counts down the game timer, implementing a sense of urgency and challenge. The function diligently checks for end-game scenarios, such as the depletion of the timer or accumulation of too many clean apples, transitioning to a game-over state if any conditions are met. Moreover, within this loop, each apple’s position is updated, and its status is checked to determine if it has been successfully caught by the trash can or missed. Apples that are caught or fall off the screen are removed from the gameplay loop, ensuring the game’s state remains current.

When the game ends, either from catching more than 2 clean apples or because the timer ended, the game trasitions to a game over page where the players final score appears. Finally, the game employs a “bubble” effect on the game-over screen for visual appeal, where bubbles generated by the createBubbles() function move around the screen.

Challenging part: 

The most interesting and challenging part of this game is creating the logic behind it. The rest is just assembling buttons, images and creating a nice aesthetic to the game.

function runGame() {
    inputName.hide();
  // Set Instruction.jpeg as the background
    image(imgInstructions, 0, 0, width, height); 

    trashCan.display();
    trashCan.move();
  // Displays score and timer at the top left
    displayScore(); 

    // Logic for apples falling and being collected or missed
    if (frameCount % 60 === 0) {
        let type = random(['clean', 'dirty']);
        apples.push(new Apple(type));
    }

    for (let i = apples.length - 1; i >= 0; i--) {
        apples[i].move();
        apples[i].display();

        if (apples[i].y + apples[i].size > trashCan.y && apples[i].x > trashCan.x && apples[i].x < trashCan.x + trashCan.width) {
            if (apples[i].type === 'dirty') {
                score += 10;
            } else {
                redApplesCollected++;
                if (redApplesCollected >= 3) {
                    currentPage = 'gameOver';
                  // Create bubbles for game over animation
                    createBubbles(20); 
                    break;
                }
            }
          // Remove collected or missed apple from array
            apples.splice(i, 1); 
        } else if (apples[i].y > height) {
          // Remove apple that falls off screen
            apples.splice(i, 1); 
        }
    }

    // Timer countdown and check for game over condition
    if (frameCount % 60 === 0 && gameTimer > 0) {
        gameTimer--;
    } else if (gameTimer === 0) {
        currentPage = 'gameOver'; // Time's up
        createBubbles(20); // Prepare game over screen
    }

    // Drawing and positioning Start Over and Back buttons
    drawGameButtons();
}

The main issue of the project was developing the logic for this game, which proved to be the most major challenge during development. The complex interactions between the various components of the game—like the falling apples, the moving garbage can, and the score system—needed to be carefully thought out and executed. To guarantee seamless gaming and compliance with the game’s regulations, the logic controlling the game’s behavior has to be strong and effective. In order to guarantee precise and reliable performance, handling edge circumstances such as identifying collisions between apples and the trash can or controlling the game’s timing required careful coding and testing.

The code consists of many parts. The trashcan is displayed and moved by the function in response to the players key press. It also shows the amount of game time left and updates the player’s score. The reasoning behind the apples falling is the most important aspect of the function. At regular intervals, it produces red apples, randomly classifying them as normal or rotten. The apple array is then iterated over, with each apple being moved and displayed as the program looks for collisions with the trash can. If an apple is detected, the apple is taken out of the array and the score is changed appropriately. On the other hand, if an apple is missed, it is removed, and if the player has missed too many and it is rotten, the game ends. Lastly, the function controls the game timer, which starts a countdown every second and enters the game over state when the allotted time is up. It also draws and arranges buttons so that you can go back to the main menu or resume the game. In order to create a compelling gaming experience, this function coordinates the interplay between game objects and player input, encapsulating the fundamental gameplay logic.

Limitations and improvements:

The game can have better ways to make it a bit competitive like adding a high score board to show the scores of all the players. Moreover, the amount of apples is random so it might be unfair between players since one can have 10 rotten apples and another only 5. All of these issues are seen as parts to be improved to make the game more appealing and competitive.

Game visuals: 

Introduction page:

Instructions page:

Game page:

Game over page:

Assignment 4: UAE Population Map Visualization

For this week’s assignment, we were tasked with either making some sort of data visualization, or creating a generative text output. I decided to make a visualization of the population of some of the major cities of the UAE on a map of the country.

I started by retrieving a map of UAE from Google Images, uploading it to my sketch, then loading it in the setup and resizing it to fit my canvas. To obtain the canvas coordinates of each city I used the following function to print mouse location to the console (which I commented out in the final sketch):

print(mouseX, mouseY);

Afterwards, I used ChatGPT to obtain an estimate of each city’s population (to save time) along with the coordinates I obtained earlier to create an array with each city’s position and population:

let cities = [
  { name: "Abu Dhabi", x: 440, y: 240, population: "1,480,000" },
  { name: "Al Ain", x: 625, y: 285, population: "656,000" },
  { name: "Madinat Zayed", x: 333, y: 335, population: "10,000" },
  { name: "Dubai", x: 575, y: 150, population: "3,380,000" },
  { name: "Sharjah", x: 600, y: 135, population: "1,400,000" },
  { name: "Khor Fakkan", x: 730, y: 140, population: "33,575" },
  { name: "Ajman", x: 610, y: 130, population: "500,000" },
  { name: "Fujairah", x: 720, y: 170, population: "230,000" },
  { name: "Dibba Al Hisn", x: 720, y: 105, population: "26,395" },
  { name: "Umm Al Quwain", x: 620, y: 115, population: "80,000" },
  { name: "Ras Al Khaimah", x: 670, y: 85, population: "350,000" },
  { name: "Ar Rams", x: 690, y: 70, population: "16,000" }
];

Instead of a traditional for loop, I used a forEach loop that I had seen online to draw a point for each location in the array and display the population when the user’s mouse hovers on each point, and this was my final result:

Looking to the future, I guess I could use a better map and add more cities with more accurate population estimates.

Assignment 2: Cloudy Day

For this assignment, we were tasked with using either a for loop or a while loop to make a simple work of art. I decided to make a UAE flag with some clouds moving in the background using loops.

I started by randomizing 5 starting positions for the clouds to make them different every time the program is run.

let cloudPositions = []; // Array to hold the positions of clouds

function setup() {
  createCanvas(800, 600);
  
  // Create 5 clouds at random positions and add them to the array
  for (let i = 0; i < 5; i++) { 
    cloudPositions.push(random(-50, width - 50));
  }
}

After that I used another for loop to move the clouds in the using the drawCloud() function I wrote:

fill('white');
for (let i = 0; i < cloudPositions.length; i++) {
  drawCloud(cloudPositions[i], 100 + i * 80); // Drawing each cloud at a different vertical position
  cloudPositions[i] += 1; // Moving the cloud to the right
  if (cloudPositions[i] > width) { // Reset cloud position after moving off screen
    cloudPositions[i] = -100;
  }
}

For reference, this is my drawCloud function, it uses multiple ellipses to make a cloud shape:

function drawCloud(x, y) {
  ellipse(x, y, 60, 60);
  ellipse(x + 20, y - 20, 70, 70);
  ellipse(x + 40, y, 50, 50);
  ellipse(x + 60, y - 10, 60, 60);
}

Overall, this is my final art piece:

Looking to the future, this artwork seems very plain, I could add some more elements such as a sun or some buildings.

Lord of The Maze – Midterm – Dachi Tarughishvili

Sketch (Fullscreen) https://editor.p5js.org/dt2307/full/vrBxuAsfN

(I have embedded here just in case but please open it in separate tab for everything to properly work (most importantly audio))

Idea: Lord of the Rings inspired game, where a player takes initiative to escape the maze, avoid the orcs and flying monster, find the ring and reach the mount of doom

Project summary: This game takes inspiration from the epic fantasy novel The Lord of the Rings, which my previous project Eye of Sauron was also based on. In this maze-style game, the main character Frodo must navigate through a maze and reach Mount Doom, the volcanic location in Mordor where the powerful ring was forged and the only place it can be destroyed. Roaming orcs patrol the maze pathways, which Frodo must avoid. Coming into direct contact with an orc reduces Frodo’s health. If Frodo loses all three health points, the game is over. If Frodo successfully reaches Mount Doom in time, the player wins the game and an image is displayed. The goal is to guide Frodo through the maze while evading orcs in order to make it to Mount Doom and destroy the ring. However, there is a catch, if you reach mount of doom without obtaining the ring, flying monster will start chasing you and at that point you should get the ring as soon ass possible to evade it. Once you capture the ring, Sauron animation will be displayed (based on my previous project with the perlin noise after Coding Train intro). After that you can see game become more gloomy as colors start to change, background included. Fortunately, due to magical powers of the ring you are granted an invisibility buff which lasts for certain amount of time. The visual cue is there for player by reducing Frodo’s transparency as well as audio cue which gets more frequent with more pulses indicating when you are gonna run out. Finally, you are able to reach mount of doom and destroy the ring if you get through the remaining orcs!

Inspiration: this game is inspired by lord of the rings movies (books):  The Fellowship of the Ring (2001), The Two Towers (2002), and The Return of the King (2003). I want to recreate an experience where player gets to have their own journey, traversing long distance, making strategic choices, avoiding the danger and reaching destination similar to what happens in the movies.

Visuals: the maze itself is black on green canvas. Characters have their own images (orc, frodo, mount of doom etc.). They are in pixel art style to give players a nostalgic feeling which also makes whole game work on this platform much smoother. The main menu screen as well as instructions and game won game over screen are AI generated, but text on top is using custom font.

Process and Challenges: I made sure to utilize an object-oriented approach. There were several development hurdles. Firstly, after designing the maze layout and slowly incorporating it into code to test functionality, I struggled greatly with collision detection (characters could access the maze improperly from certain sides) which took substantial time to correct. Additionally, programming the repetitive orc movements to patrol the maze appropriately relied heavily on trial-and-error to determine optimal pathways. (And lots of Googling!). Last few days, I also added sounds which were not too difficult but took some time to pick right soundtracks and make it working. Volume slider was a bit tricky as I head to read documentation online because I did not like the way its default behavior worked. I also added countdown which lets player see their current time as well as total time they took to beat the challenge. Additionally, I fixed issue with ring, and volume slider being displayed over game over screen and such. I added even more soundtracks, for getting the ring and spawning the ring. Moreover, I implemented features such as flying monster which spawns and moves towards frodo if he goes to mount of doom without picking up the ring. Upon picking up the ring, I added a feature based on my last project where eye of sauron animation gets displayed (which was done using perlin noise). This comes with change in background as well as another feature – Invisibility. In simple terms, frodo becomes more transparent visually, a sound for invisibility starts playing and in specific timeframe he is immune to enemies. I added another orc near ring to make getting it more challenging. Last but not least, ring gets spawned only if Frodo reaches certain area in the map, to ensure that player can’t just camp at base and wait for ring to spawn if there was a timer instead, making game much simpler.

Here are some development progress pictures (I have not included every one of them) :

Code:

I have separate JS classes for different functions of the game.

Lets go over most of them (briefly, more details are in code comments):

Drawing UI Class: takes care of top bar with health, volume and timer texts.

function drawUI() {
  // Draw Health text
  fill(255);
  textSize(14);
  noStroke();
  text("Lives: " + playerHealth, 55, 11);

  // Draw Volume text
  fill(255);
  textSize(14);
  noStroke();
  text("Volume:", 150, 11);
  // Make sure volume slider is visible
  volumeSlider.style('display', 'block');

  // Draw Timer
  fill(255);
  textSize(14);
  text("Time: " + playTime.toFixed(1) + "s", width - 60, 11);

  // Set volume based on slider value
  initialVolume = volumeSlider.value();
  backgroundMusic.setVolume(initialVolume);
}

Orc class: takes care of spawning as well as moving orcs (also makes sure they don’t go in the maze)

class Orc {
  constructor(pointA, pointB, spawn) {
    this.pointA = pointA; //start
    this.pointB = pointB; //end
    this.size = 20;
    this.speed = 1.2;

    //initial spawn
    this.x = spawn.x;
    this.y = spawn.y;

    // target
    this.currentTarget = this.pointA;
  }

  display() {
    image(orcImg, this.x, this.y, this.size, this.size);
  }

  move() {
    let dx = this.currentTarget.x - this.x;
    let dy = this.currentTarget.y - this.y;
    let length = sqrt(dx * dx + dy * dy); //direction vector

    if (length > 0) {
      dx /= length; //normalize vector for consistent speed
      dy /= length;
      
      //calculate new position
      let newPosX = this.x + dx * this.speed;
      let newPosY = this.y + dy * this.speed;

      if ( //if new position is in bound and does not collide with walls
        newPosX > 0 &&
        newPosX < width - this.size &&
        newPosY > 0 &&
        newPosY < height - this.size &&
        maze[getRow(newPosY)][getCol(newPosX)] !== '#'
      ) {
        this.x = newPosX;
        this.y = newPosY;

        // check if orc reached target
        if (dist(this.x, this.y, this.currentTarget.x, this.currentTarget.y) < this.speed) {
          // switch points
          this.currentTarget = this.currentTarget === this.pointA ? this.pointB : this.pointA;
        }
      }
    }
  }
}

function generateLevel() {
  orcs = [];
  orcs.push(new Orc({ x: 28, y: 350 }, { x: 28, y: 180 }, { x: 28, y: 180 }));
  orcs.push(new Orc({ x: 605, y: 100 }, { x: 605, y: 400 }, { x: 605, y: 180 }));
  orcs.push(new Orc({ x: 452, y: 420 }, { x: 452, y: 250 }, { x: 452, y: 250 }));
  orcs.push(new Orc({ x: 260, y: 605 }, { x: 455, y: 605 }, { x: 455, y: 605 }));
  orcs.push(new Orc({ x: 300, y: 100 }, { x: 200, y: 100 }, { x: 200, y: 100 }));
  // orcs and their pathways
}

Player class: initializes player, as well as deals with maze collision and invisibility buff.

class Player {
  constructor() {
    this.size = 20; 
    this.speed = 3;
    this.spawn();
  }

  display() {
    if (millis() < invincibleUntil) {
      tint(255, 63); //25% transparency
    } else {
      tint(255, 255); 
    }
    image(playerImg, this.x, this.y, this.size, this.size);
  }
  move() {
    
    if (eyeOfSauronActive) { //cant move if active
      return; 
    }
    let newX = this.x;
    let newY = this.y;
    //movement
    if (keyIsDown(LEFT_ARROW) && this.x > 0) {
      newX -= this.speed;
    } else if (keyIsDown(RIGHT_ARROW) && this.x < width - this.size) {
      newX += this.speed;
    }
    if (keyIsDown(UP_ARROW) && this.y > 0) {
      newY -= this.speed;
    } else if (keyIsDown(DOWN_ARROW) && this.y < height - this.size) {
      newY += this.speed;
    }

    if (!this.collidesWithWall(newX, this.y) && !this.collidesWithWall(newX, newY) && !this.collidesWithWall(this.x, newY) && !this.collidesWithWall(newX, newY)) {
      this.x = newX;
      this.y = newY; //updates if there are no collisions with walls
    }
  }
  //collision
  collidesWithWall(x, y) {
    
    //calculates grid indices with helpers
    let left = getCol(x);
    let right = getCol(x + this.size - 1);
    let top = getRow(y);
    let bottom = getRow(y + this.size - 1);
    
    //checks if any grids around player position has # (meaning wall)
    return ( //returns true if collision happens if not false ( or conditions)
      maze[top][left] === '#' ||
      maze[top][right] === '#' ||
      maze[bottom][left] === '#' ||
      maze[bottom][right] === '#'
    );
  }
    //initial spawn
  spawn() {
    this.x = 30;
    this.y = 30;
  }
}

Ring Class: takes care of spawning golden ring as well as checking its collision for player and determining invisibility buff time

const ringSpawnLocation = { row: 10, col: 20 }; //properties
let goldenRingRadius = 10;

function checkRingCollision() {
  if (goldenRing && dist(player.x, player.y, goldenRing.x, goldenRing.y) < goldenRing.size) { //if ring exists and distance is less than rings radius
    //activate sauron and invis buff sequence
    collidedWithRing = true;
    eyeOfSauronActive = true;
    invincibleUntil = millis() + 40000; // make Frodo invincible for 30 seconds
    monsterSpawned = false;
     setTimeout(() => {
      invisibilitySound.play();
    }, 20000);
    if (!sauronSound.isPlaying() && !sauronSoundStarted) {
      sauronSound.play();
      sauronSoundStarted = true;
      goldenRing = null;
    }
  }
}

//creating golden ring
function createGoldenRing(x, y, size) {
  return {
    x,
    y,
    size,
  };
}



//drawing golden ring
function drawGoldenRing() {
  
  image(ringImage, goldenRing.x - goldenRingRadius, goldenRing.y - goldenRingRadius, goldenRingRadius * 2, goldenRingRadius * 2);
}

Game Management Class:  takes care of different game states, main menu state, game win, gameplay, gameover etc. It also displays main menu, instructions and helps with clearing as well as reloading objects and variables upon restart

function startGame() {
  gameStarted = true;
  this.remove(); // remove the Start Game button
  volumeSlider.style('display', 'block'); //displays volume slider

}

function returnToMainMenu() {
  currentScreen = 'mainMenu';
  backButton.hide(); // hide the back button when returning to main menu
}

function showInstructions() {
  currentScreen = 'instructions';
  backButton.show(); // show the back button when instructions are visible
}


function createRestartButton() {
  if (restartButton) {
    restartButton.remove(); // ensure any existing button is removed
  }
  let buttonWidth = 140; 
  let buttonHeight = 35; 
  let buttonX = (width - buttonWidth) / 2; // 
  let buttonY = 200; 
  restartButton = createButton('');
  restartButton.position(buttonX, buttonY);
  restartButton.size(buttonWidth, buttonHeight);
  restartButton.style('background-color', 'transparent');
  restartButton.style('border', 'none'); 
  restartButton.style('cursor', 'pointer');
  restartButton.mousePressed(restartGame);

  // change cursor on hover
  restartButton.mouseOver(() => restartButton.style('cursor', 'pointer'));
}


function gameWin() {
  gameState = 'win';
  backgroundMusic.stop();
  
  //  the game win image
  background(gameWinImg);

  //  text on top of the image
  textSize(31);
  stroke(0);
  strokeWeight(4);
  fill(255); 
  textAlign(CENTER, CENTER);
  text("You've reached the Mount of Doom!", width / 2, height / 2 -50);
  text("Journey Length: " + playTime.toFixed(1) + " seconds", width / 2, height / 2);

  winSound.play();
  if (monsterSound.isPlaying()) {
    monsterSound.stop();
  }
  volumeSlider.style('display', 'none');
  
  createRestartButton();
  noLoop(); //game pause
}


function gameOver() {
  gameState = 'gameOver';
  backgroundMusic.stop();
  
  // the game over image
  background(gameOverImg);
  
  //  text on top of the image
  textSize(45);
  stroke(0);
  strokeWeight(4);
  fill(255); 
  textAlign(CENTER, CENTER);
  text("Game Over!", width / 2, 100);
  text("Survival Time: " + playTime.toFixed(1) + " seconds", width / 2, 150);

  gameoverSound.play();
  if (monsterSound.isPlaying()) {
    monsterSound.stop();
  }
  
  volumeSlider.style('display', 'none');

  createRestartButton();
  noLoop(); // pause
}


function restartGame() {
  // stop sounds
  if (gameoverSound.isPlaying()) {
    gameoverSound.stop();
  }
  if (backgroundMusic.isPlaying()) {
    backgroundMusic.stop();
  }
  if (monsterSound.isPlaying()) {
    monsterSound.stop();
  }
  if (dyingSound.isPlaying()) {  
    dyingSound.stop();
  }
  if (sauronSound.isPlaying()) {
    sauronSound.stop();
  }
  if (invisibilitySound.isPlaying()) {
    invisibilitySound.stop();
  }
  if (winSound.isPlaying()) {
    winSound.stop();
  }

  // remove the restart button if it exists
  if (restartButton) {
    restartButton.remove();
    restartButton = null;
  }

  // reset the game state and variables for a new game
  resetGameState();

  // reset startTime to the current time to restart the timer
  startTime = millis();

  // ensure the game loop is running if it was stopped
  loop();
}



function resetGameState() {
  // reset game flags and variables
  gameStarted = true;
  gameState = 'playing';
  playerHealth = 3;
  playTime = 0;
  monsterSpawned = false;
  collidedWithRing = false;
  goldenRingSpawned = false;
  eyeOfSauronActive = false;
  eyeOfSauronDeactivated = false;
  eyeSize = 15;
  currentLevel = 1;
  
  // reset positions and states of game entities
  player.spawn();
  orcs = []; // clear existing orcs
  generateLevel(); // repopulate the orcs array

  if (volumeSlider) {
    volumeSlider.remove(); // ensure existing slider is removed before creating a new one
  }
  
  //new slider
  volumeSlider = createSlider(0, 1, 1, 0.01); 
  volumeSlider.position(180, 1.5);
  volumeSlider.style('width', '100px');
  volumeSlider.style('color', 'black');
  volumeSlider.style('outline', 'none');
  volumeSlider.style('background', '#white');
  volumeSlider.style('opacity', '0.7');
  volumeSlider.input(() => backgroundMusic.setVolume(volumeSlider.value()));

  // reset the background music volume and play it if not already playing
  backgroundMusic.setVolume(1); // set initial volume
  if (!backgroundMusic.isPlaying()) {
    backgroundMusic.loop();
  }

  // ensure the game loop is running if it was stopped
  loop();
}

Maze class: takes care of maze layout as well as drawing maze. There are two layouts, first one is official game one and second one is for quick testing. It uses helper functions to divide canvas into grids and then draws a maze if it finds # in a grid. It is using a graphic which uses a wall texture and for other places in grids we have grass texture.

let maze = [
  "##########################",
  "#        #   # #   #   # #",
  "# #### # # #   # # # # # #",
  "#   #  ##### ### # # # # #",
  "# #### #     #   # # # # #",
  "#      # # # # ### # # # #",
  "#####    # # # # # # # # #",
  "#   #  ### ### # # # # # #",
  "# # #  # #     # # # #   #",
  "# # # ## ####### ### ### #",
  "# #      #         # # # #",
  "# ################ # # # #",
  "#        #   #     # # # #",
  "# ######## ### ### # # # #",
  "#        # # # # # # # # #",
  "######## # #   # # # # # #",
  "# #    # # # ### # # # # #",
  "#    #   # # #     # # # #",
  "# ## ##### # # ##### # # #",
  "# #  #       #   #   #   #",
  "# #  # ######### # ### ###",
  "# ####   # #   # # # #   #",
  "# #  # # # #   # # # # ###",
  "# ## ### # # ### # # #    ",
  "#        #         #      ",
  "##########################",
];


// let maze = [
//   "                          ",
//   "#        #   # #   #   # #",
//   "# #### # # #   # # # # # #",
//   "#   #  ##### ### # # # # #",
//   "# #### #     #   # # # # #",
//   "#      # # # # ### # # # #",
//   "#####    # # # # # # # # #",
//   "#   #  ### ### # # # # # #",
//   "# # #  # #     # # # #   #",
//   "# # # ## ####### ### ### #",
//   "# #      #         # # # #",
//   "# ################ # # # #",
//   "#        #   #     # # # #",
//   "# ######## ### ### # # # #",
//   "#        # # # # # # # # #",
//   "######## # #   # # # # # #",
//   "# #    # # # ### # # # # #",
//   "#    #   # # #     # # # #",
//   "# ## ##### # # ##### # # #",
//   "# #  #       #   #   #   #",
//   "# #  # ######### # ### ###",
//   "# ####   # #   # # # #   #",
//   "# #  # # # #   # # # # ###",
//   "# ## ### # # ### # # #    ",
//   "#        #         #      ",
//   "##########################",
// ];

// helper functions for row and col 
function getRow(y) {
  return floor(y / 30);
}

function getCol(x) {
  return floor(x / 30);
}

function drawMaze() {
  for (let i = 0; i < maze.length; i++) {
    for (let j = 0; j < maze[i].length; j++) {
      let x = j * 25;
      let y = i * 25;
      if (maze[i][j] === '#' && !eyeOfSauronActive) {
        // Draw wall texture only if Eye of Sauron is not active
        image(wallBuffer, x, y, 25, 25, x, y, 25, 25);
      } else if (drawGrass && maze[i][j] !== '#') {
        // Draw grass texture over the green areas (paths) if drawGrass is true
        // and the current cell is not a wall.
        image(grassBuffer, x, y, 25, 25, x, y, 25, 25);
      }
    }
  }
}

MountDoom Class: creates mount doom, uses a function for tracking and moving monster towards Frodo as well as a function which determines if Frodo is inside mount of doom range.

function moveMonsterTowardsFrodo() {
  let dx = player.x - monsterX;
  let dy = player.y - monsterY;
  let angle = atan2(dy, dx); //angle between monster and player
  monsterX += monsterSpeed * cos(angle);
  monsterY += monsterSpeed * sin(angle);
  //update monster position based on calculated angle
}


class MountOfDoom {
  constructor() {
    this.x = width - 75;
    this.y = height - 95;
    this.size = 75;
  }
}


function createMountOfDoom() {
  return new MountOfDoom();
}


function playerReachedMountOfDoom() {
  return (
    !monsterSpawned && //monster has not spawned and its in bounds
    player.x + player.size > mountOfDoom.x &&
    player.x < mountOfDoom.x + mountOfDoom.size &&
    player.y + player.size > mountOfDoom.y &&
    player.y < mountOfDoom.y + mountOfDoom.size
  );
}

This is Eye of Sauron class:

it takes care of Eye of Sauron animation (used from one of the previous projects). This is drawn using various perlin noise loops. It also has activation and eye increase rate after predetermined time period. (It times well with audio e.g. death = engulfed in darkness).

let orange = 165; // clicking color variable
let size_t = 100; // clicking size variable

function drawEyeOfSauron() {
  background(0, 0, 0, 3);
  push();
  translate(width / 2, height / 2);
  let noiseMax = 5; // fixed value for spikiness
  let alphaValue = 400;

  
  eyeSize += 0.05;

  // outer shape
  stroke(255, 10, 0, alphaValue);
  noFill();
  beginShape();
  for (let a = 0; a < TWO_PI; a += 0.1) {
    let xoff = map(10 * cos(a + phase), -1, 1, 0, noiseMax);
    let yoff = map(sin(a + phase), -1, 1, 0, noiseMax);
    let r = map(noise(xoff, yoff, zoff), 0, 1, 100, 220) * (eyeSize / 20); // scale based on eyesize
    let x = r * cos(a);
    let y = r * sin(a);
    vertex(x, y);
  }
  endShape(CLOSE);

  // orange glow for the first outer shape
  fill(255, orange, 0, alphaValue * 0.5); // lower transp
  beginShape();
  for (let a = 0; a < TWO_PI; a += 0.1) {
    let xoff = map(8 * cos(a + phase), -1, 1, 0, noiseMax);
    let yoff = map(8 * sin(a + phase), -1, 1, 0, noiseMax);
    let r = map(noise(xoff, yoff, zoff), 0, 1, 0, size_t) * (eyeSize / 20); // scale based on eyesize
    let x = r * cos(a);
    let y = r * sin(a);
    vertex(x, y);
  }
  endShape(CLOSE);

  // second glow
  fill(255, 165, 0, alphaValue * 0.5);
  beginShape();
  for (let a = 0; a < TWO_PI; a += 0.1) {
    let xoff = map(10 * cos(a + phase + 1), -1, 1, 0, noiseMax); // different phase
    let yoff = map(10 * sin(a + phase + 1), -1, 1, 0, noiseMax);
    let r = map(noise(xoff, yoff, zoff), 0, 1, 50, 220) * (eyeSize / 20); // scale based on eyesize
    let x = r * cos(a);
    let y = r * sin(a);
    vertex(x, y);
  }
  endShape(CLOSE);

  // inner pupil black which is a vertical ellipse
  fill(0); // black
  beginShape();
  for (let a = 0; a < TWO_PI; a += 0.1) {
    let xoff = map(5 * cos(a + phase), -1, 1, 0, noiseMax);
    let yoff = map(5 * sin(a + phase), -1, 1, 0, noiseMax);
    let rx = map(noise(xoff, yoff, zoff), 0, 1, 5, 20) * (eyeSize / 20); // scale based on eyesize
    let ry = map(noise(yoff, xoff, zoff), 0, 1, 50, 120) * (eyeSize / 20); // scale based on eyesize
    let x = rx * cos(a);
    let y = ry * sin(a);
    vertex(x, y);
  }
  endShape(CLOSE);

  zoff += 0.008;
  phase += 0.008;
 
  if (eyeOfSauronActive && sauronSound.isPlaying()) {
    let timeRemaining = sauronSound.duration() - sauronSound.currentTime();
    if (timeRemaining < 0.7) { 
      eyeSize += 50;
    }
  }

  pop();
}

And lastly, the code I am most proud, where everything comes together is my sketch code:

This is where, variables, preload and setup is made. You can see detailed list in the code but in summary it takes care of initializing objects, creating buttons, slider as well as separate graphic for textures.

The next section is draw function which has different if conditions for different states. For example, if game has not started are we in instructions or main menu. We also have additional drawings, game state function, and references to previous classes to make everything initialize and work well together. Getting everything work well together was through multiple hours of trial and error but eventually the experience created was pretty fluid with no significant performance or visual bugs.

//Variables

//Time
let startTime; 
let playTime = 0;
let mountOfDoomTime = 0;

//Objects
let playerImg, orcImg, mountOfDoomImg;
let player, orcs, playerHealth, mountOfDoom;

//Audio

let volumeSlider;
let winSound;
let backgroundMusicStarted = false;

//ring
let goldenRing;
let goldenRingSpawned = false;
let collidedWithRing = false;

//sauron
let eyeSize = 15;
let eyeOfSauronActive = false;
let isSauronSoundLowered = false;
let eyeOfSauronDeactivated = false;
let zoff = 0;
let phase = 0;
let noiseMax = 0;
let sauronSoundStarted = false;

//monster
let monsterImg;
let monsterSpawned = false;
let monsterSpeed = 0.3;
let monsterX, monsterY;
let monsterCheck = false;
let monsterSizeMultiplier = 0.2;

//buff
let invincibleUntil = 0;

//state 
let gameWinImg, gameOverImg;
let gameState = 'playing'; 
let gameStarted = false;

//font
let pixelFont;

//for managing 
let restartButton;
let mainmenu;
let currentScreen = 'mainMenu';
let newBackgroundImg;

//Maze Management Misc
let drawGrass = true;

//Preload

function preload() {
  playerImg = loadImage('frodo.png');
  orcImg = loadImage('orc.png');
  mountOfDoomImg = loadImage('volcano.png');
  backgroundMusic = loadSound('lotr.mp3');
  dyingSound = loadSound('dying.mp3');
  gameoverSound = loadSound('gameoversound.mp3');
  winSound = loadSound('win.mp3');//all the sounds
  ringImage = loadImage('ring.png');
  sauronSound = loadSound("sauron.mp3")
  monsterImg = loadImage('monster.gif');
  invisibilitySound = loadSound('invisible.mp3');
  ringSpawnSound = loadSound('ringspawn.mp3');
  monsterSound = loadSound('monster.mp3');
  gameWinImg = loadImage('game_won.png');
  gameOverImg = loadImage('game_over.png');
  pixelFont = loadFont('alagard.ttf');
  mainmenu = loadImage('mainmenu.png')
  newBackgroundImg = loadImage('instructions.png');
  grassTexture = loadImage('grass.jpeg');
  wallTexture = loadImage('wall.jpg');
  
}

//Safari Bug (audio does not autoplay, not a problem on chromium)
function keyPressed() {
  // start background music when a key is pressed
  if (!backgroundMusicStarted) {
    backgroundMusicStarted = true;
    backgroundMusic.play();
  }
}

//Setup

function setup() {
  textFont(pixelFont); //using external font
  frameRate(60);
  startTime = millis(); //for calculating journey time in the end
  let initialVolume = 1;
  createCanvas(650, 650);
  generateLevel(); // ?
  player = new Player(); //initialize player
  playerHealth = 3; //initialize player health
  window.addEventListener('keydown', function (e) {
    if (e.key === 'ArrowUp' || e.key === 'ArrowDown' || e.key === 'ArrowLeft' || e.key === 'ArrowRight') {
      e.preventDefault(); // prevent default arrow key behavior for safari bug (moving screen)
    }
  });
  
  //monster
  monsterX = width / 2;
  monsterY = height / 2;
 
  mountOfDoom = createMountOfDoom();
  
  //vol slider
  volumeSlider = createSlider(0, 1, initialVolume, 0.01);
  volumeSlider.position(180, 1.5);
  volumeSlider.style('width', '100px');
  volumeSlider.style('color', 'black');
  volumeSlider.style('outline', 'none');
  volumeSlider.style('background', '#white');
  volumeSlider.style('outline', 'none');
  volumeSlider.style('opacity', '0.7');
  volumeSlider.style('transition', 'opacity .2s');


  // mouse over effect
  volumeSlider.mouseOver(() => {
    volumeSlider.style('opacity', '1');
  });

  volumeSlider.mouseOut(() => {
    volumeSlider.style('opacity', '0.7');
  });

  // webkit for browsers
  volumeSlider.style('::-webkit-slider-thumb', 'width: 25px; height: 25px; background: #04AA6D; cursor: pointer;');
  
  volumeSlider.style('display', 'none');

  backgroundMusic.loop();
  
  //start button
  let startButton = createButton(''); //empty because i couldnt get button itself have external font, so it realies on empty button and actual clickable text superimposed on it
  let buttonWidth = 140; 
  let buttonHeight = 35; 
  let buttonX = (width - buttonWidth) / 2; // center the button horizontally
  let buttonY = 175; 
  startButton.position(buttonX, buttonY);
  startButton.size(buttonWidth, buttonHeight);
  startButton.style('background-color', 'transparent');
  startButton.style('border', 'none'); // no border
  startButton.style('cursor', 'pointer');

  // start game on click
  startButton.mousePressed(startGame);
  
  // question, instruciton button
  
  questionMarkButton = createButton('?'); 
  questionMarkButton.position(width/2-45, 15); 
  questionMarkButton.style('background-color', 'transparent');
  questionMarkButton.style('border', 'none');
  questionMarkButton.style('color', '#FFFFFF'); // text color
  questionMarkButton.style('font-size', '50px'); // size 
  questionMarkButton.style('background-color', 'black'); //  background color 
  questionMarkButton.style('color', '#FFFFFF');  //question mark color
  questionMarkButton.style('padding', '7px 35px'); // pading
  questionMarkButton.style('border-radius', '5px'); // rounded corners

  // managing mouse over effect
  questionMarkButton.mousePressed(showInstructions);

  questionMarkButton.mouseOver(() => questionMarkButton.style('color', '#FFC109')); // change color on hover
  questionMarkButton.mouseOut(() => questionMarkButton.style('color', '#FFFFFF')); // revert color on mouse not hovering

  // arrow button 
  backButton = createButton('←'); 
  backButton.position(10, 10); 
  backButton.mousePressed(returnToMainMenu);
  backButton.hide(); // hide it initially
  
  //buffer for creating another canvas instance
  grassBuffer = createGraphics(width, height);
  
  // grass texture with reduced transparency
  grassBuffer.tint(255, 100); // a bit transparent for visuals
  grassBuffer.image(grassTexture, 0, 0, width, height);
  
  // buffer for the wall texture
  wallBuffer = createGraphics(width, height);

  // scale the buffer before drawing the image
  wallBuffer.push(); // save
  wallBuffer.scale(2); // scale up
  wallBuffer.image(wallTexture, 0, 0, width * 2, height * 2); 
  wallBuffer.pop(); // restore
  
}

//Draw

function draw() {
  if (!gameStarted) { //for menu and instructions
    if (currentScreen === 'mainMenu') { //main menu
      
      background(mainmenu); //background image
      //text properties
      textSize(38);
      stroke(0);
      strokeWeight(5);
      textAlign(CENTER, CENTER);
      text('Lord of the Maze', width / 2, 150);

      textSize(24); //start
      let startGameText = "Begin Journey!";
      let startGameWidth = textWidth(startGameText);
      let startGameX = width / 2 - startGameWidth / 2;
      let startGameY = 180 - 2; // y position of text
      let startGameHeight = 24; // height of the text

      // mouse hover detection based on text position and size
      if (mouseX >= startGameX && mouseX <= startGameX + startGameWidth && mouseY >= startGameY && mouseY <= startGameY + startGameHeight) {
        fill("#FFC109"); // change color to indicate hover
      } else {
        fill("#FFFFFF"); // defauult color
      }

      textAlign(CENTER, CENTER);
      text(startGameText, width / 2, 180 + 12); // draw begin jorney text
      questionMarkButton.show();

    } else if (currentScreen === 'instructions') {
      background(newBackgroundImg); // show the instructions background
      fill(255); //  text color
      textSize(20); //  text size
      textAlign(CENTER, CENTER);
      text("Oh valiant traveler, embroiled in a quest most dire: \n to traverse the winding labyrinths of Middle-earth and \n consign the accursed One Ring to the molten depths  of Mount Doom. \n Be forewarned, the path is fraught with peril, \n and the all-seeing Eye of Sauron ever seeks to ensnare thee. \n \nEmploy the sacred Arrow Keys \n to navigate the maze's enigmatic corridors. \n Each stride shall bring thee closer to thy destiny or doom. \n Avoid orcs! Find one ring and reach Mount of Doom", width / 2, 130);
      questionMarkButton.hide();
      
    }
  } else {
      if (gameState === 'playing') {
        questionMarkButton.hide();
      // change background color based on whether the Eye of Sauron has deactivated
      if (!eyeOfSauronActive && !eyeOfSauronDeactivated) {
        background(178, 223, 138); // original color before Eye of Sauron appears
      } else if (eyeOfSauronDeactivated) {
        background(210, 180, 140); // new color after Eye of Sauron disappears
      }
        
        if (!eyeOfSauronActive) {
          image(mountOfDoomImg, mountOfDoom.x, mountOfDoom.y, mountOfDoom.size, mountOfDoom.size);
        }
      
      //more drawing
      drawMaze();
      playTime = (millis() - startTime) / 1000;
      drawUI();
      player.move();
      player.display();

      //stoo invisibility buff sound
      if (millis() >= invincibleUntil && invisibilitySound.isPlaying()) {
        invisibilitySound.stop();
      }

      noTint();
      orcs.forEach(orc => {
        orc.move();
        orc.display();
      });

      checkCollisions();

      if (playerReachedMountOfDoom()) {
        if (!collidedWithRing) {
          // spawn monster in the center only if they still havent collected ring
          monsterSpawned = true;
          monsterCheck = true;

        } else {

          gameWin();
          volumeSlider.remove();
        }
      }

    let newMonsterWidth = monsterImg.width * monsterSizeMultiplier;
    let newMonsterHeight = monsterImg.height * monsterSizeMultiplier;

    // draw the monster 
    if (monsterSpawned) {
      if (!monsterSound.isPlaying()) {
        monsterSound.loop(); 
      }

      moveMonsterTowardsFrodo();

      let newMonsterWidth = monsterImg.width * monsterSizeMultiplier;
      let newMonsterHeight = monsterImg.height * monsterSizeMultiplier;
      image(monsterImg, monsterX, monsterY, newMonsterWidth, newMonsterHeight);
      
      //monster touches frodo looses

      if (dist(player.x, player.y, monsterX, monsterY) < player.size) {
        gameOver();
      }
    } else {
      if (monsterSound.isPlaying()) {
        monsterSound.stop();
      }
    }
    
    //golden ring

    if (goldenRingSpawned && gameState === 'playing' && goldenRing != null) {
      drawGoldenRing();
      checkRingCollision();
    }

      if (orcs.length == 0) {
        currentLevel++;
        generateLevel();
      }
      
       

    //golden ring and specific location
        
      if (!goldenRingSpawned && getRow(player.y) === ringSpawnLocation.row && getCol(player.x) === ringSpawnLocation.col) {
        goldenRing = createGoldenRing(width / 2 + 138, height / 2 - 110, 15);
        goldenRingSpawned = true;
        ringSpawnSound.play(); 
      }


    //eye of sauron and managing music
      if (eyeOfSauronActive ) {
          drawGrass = false; // to not draw grass during eye of sauron animation
          drawEyeOfSauron();
          if (!sauronSound.isPlaying() && sauronSoundStarted) {
            eyeOfSauronActive = false;
            sauronSoundStarted = false;
            eyeOfSauronDeactivated = true; 
            backgroundMusic.setVolume(initialVolume);
          }
        } else {
          drawGrass = true; // resume drawing grass when eye of sauron is not active
        }
    //another game over condition 
      if (playerHealth <= 0) {
        gameOver();
      }
        
        //restart button
        
         if (gameState === 'gameOver' || gameState === 'win') {
    fill(255); 
    textAlign(CENTER, CENTER);
    textSize(24); 
    textFont(pixelFont); 
    text("Restart Game", width / 2, 200); // y position to match the button's
     }


    } 
  }
}


//Collisions


function checkCollisions() {
  if (millis() < invincibleUntil) {
    return; // skip collision check if Frodo is invincible
  }

  orcs.forEach(orc => {
    if ( //orc dimensions
      player.x < orc.x + orc.size &&
      player.x + player.size > orc.x &&
      player.y < orc.y + orc.size &&
      player.y + player.size > orc.y
    ) {
      playerHealth--; //substract player health
      player.spawn(); //respawn
      dyingSound.play(); //play death sound
    }
  });
}

//Helper Functions


function getRow(y) { //convert y into row index (25 units for maze)
  return floor(y / 25);
}

function getCol(x) { //convert x into column index (25 units for maze)
  return floor(x / 25);
}

Conclusion and Future Considerations

In the end, I am very happy with how things turned out. Despite numerous problems and many more solutions and trials and errors, I developed a project that stands strong in almost every department we studied – there is a bit of everything. I hope it did at least some level of justice to inspiration source and achieved a design aesthetic that is consistent throughout. The scope of the project, will initially seemed simple, actually turned out to be much more complex when I started working on it, as there are lots of moving elements (literally and figuratively). This does not mean that it’s perfect of course. There are some improvements and suggestions to be made. For example, I could potentially add more monster types and more interactions and hidden surprises. The scale of the maze could be larger as well. Additionally, this is only one part of Hero’s journey. Story could be extended to what happens after reaching mount of doom. This calls for additional level. Moreover, the maze is fixed and static. It would be interesting to try procedural maze generation technique, so it is a unique maze each time game is loaded. On a final note, I hope you enjoy my game and I will definitely expand it in the future.

 

Week 5 Reading Response: Machines can see us now?

This week’s reading is eerily interesting. Learning about how we can now interact with our computers with our entire body seems uncanny, if not revolutionary. Reading about Myron Krueger’s work, Videoplace, which he developed between 1969 and 1975, stands out as an early example where participants’ silhouettes were digitized and used for interactive graphics, showing the potential of whole-body interactions with computers. This overcomes the common use of mouse and keyboards. It reminds me of the Apple Vision Pro and how we can interact with the digital space by just moving our hands around, and how physically turning can show a 360 view of our space.

The article also introduces Messa di Voce, a collaborative project incorporating whole-body vision-based interactions, speech analysis, and augmented reality to create a unique audiovisual performance. This shows the evolution of computer vision in the arts, combining different sensory inputs for creative expression.

David Rokeby’s Sorting Daemon and the Suicide Box by the Bureau of Inverse Technology delve into a rather darker side of computer vision, shedding light on issues of surveillance and profiling. Rokeby’s installation creates diagnostic portraits of social environments, reflecting concerns about automatic systems in the context of the “war on terrorism.” On the other hand, Suicide Box, which is near the Golden Gate Bridge, captures live data on suicides, giving birth to controversy of using technology in social and public settings.

Week 5 Reading Response: Computer Vision

I am not a programmer or a videographer, so I’ll mostly speak about the first topic, Computer Vision in Interactive Art.

I was really impressed by Krueger’s Videoplace. It seemed to be a highly interactive Computer Vision piece. I found it especially interesting that this project is actually quite old, older even than the computer mouse. This surprised me as I thought that computer vision, at least the kind that could track poses accurately, was relatively new. It’s pretty amazing that the piece was working even in 2006.

Also, the part about computer vision and interactive art based on it being a response to increasing surveillance really stood out to me. Art has often been a response to various kinds of troubles or challenges faced by society, and is often a means of coping with societal trauma. Therefore, it is no surprise that the increased surveillance following 9/11 and the War on Terror, especially machine-based surveillance, triggered an outpouring of interactive artpieces based on computer vision. Lozano-Hemmer’s Standards and Double Standards is probably the best example of this, as to me, it represents that surveillance makes people more distant from each other (represented by the “absent crowd”) while enforcing a sense of oppression and authority (represented by the belts).

Rokeby’s Sorting Daemon was another particularly interesting project, especially after I visited his website to understand how the composites were made. On the left side of the composite are the faces, sorted from dark skin to fair, left to right. The right side meanwhile captures details of their clothes, sorted into mosaics by similar color. I found it to be a rather fitting representation of the profiling of people, which is often racial and unfairly targets racial minorities and immigrants who appear more similar to the stereotypical “terrorist” or “criminal”. It is also a visual representation of the dehumanization of people into datapoints that are monitored by the state.

Overall, this was a very interesting reading about the history of Computer Vision-based art. While I regret not being able to understand the technical aspects better, I would say this was quite a well-written article, which simplified many complex concepts of this old, yet cutting-edge, field.