Midterm Project – The Pearl Dive

THE Concept

The Pearl Dive was inspired by my love for the ocean and my interest in Emirati culture and history. I wanted to create a playful, game-like experience that would also teach players about the UAE’s economy before the discovery of oil, when pearl diving was an important source of income for many families. The game begins with a welcome screen featuring an Arabic greeting, followed by an instructions screen that explains how to play. The main game shows a traditional boat above the ocean, with a nahham (the traditional ship singer) represented on the boat. Ocean sounds and traditional chants play during the game to create a more immersive experience and reflect the cultural role of music in motivating divers and sailors. The player controls an underwater diver using the arrow keys to move around the ocean and collect clams. The diver and clam images, animated waves, sun, clouds and seabed make my underwater environment. Three clams appear in random positions and regenerate every two to three seconds. Each time the diver touches a clam, the score increases and after collecting six clams, the game transitions to the pearl screen. Here, the player clicks on each of the six clams to reveal a randomly generated pearl with a specific colour, size and value. I created five possible pearl colours including blue, purple, pink, green and white each with a different base value. The size of the pearl also affects its value with larger pearls receiving more points. The values are then added together to calculate the total value of all six pearls in millions of AED. This makes the pearl auction interactive because the player does not know what each clam contains until they open it. My goal was to combine game mechanics to make a traditional pearl-diving industry while highlighting its importance in Emirati history and culture.

How the Game Works

The game consists of three main screens: the start screen, the instructions screen and the main game, followed by the pearl screen.

Start Screen: The game begins with a welcome screen displaying “السلام عليكم” and “Welcome to The Pearl Dive.” The player clicks the yellow “Press to start” button to move to the instructions screen.

Instructions Screen: This screen explains how to play the game. The player reads the instructions and clicks the button to begin diving. Once the game starts, the ocean sounds and traditional chants begin playing.

Main Game Screen: The player controls the diver using the arrow keys to move left, right, up and down underwater. The objective is to collect six clams by moving the diver into them. Three clams appear at a time in random positions and regenerate every two to three seconds. The score at the top left increases whenever the diver collects a clam. The background features a traditional dhow boat, animated ocean waves, a sun, clouds and the seabed. A nahham, the traditional ship singer, is also shown on the boat, accompanied by music notes and traditional chants. Once the player collects six clams, the game automatically moves to the pearl screen.

Pearl Screen: The six collected clams appear in two rows of three. The player clicks each clam to reveal a randomly generated pearl. Each pearl has a colour, size and value. There are five possible colours: blue, purple, pink, green and white, each with a different base value. The pearl’s size also affects its value, with larger pearls receiving more points. The colour points and size points are added together to calculate the pearl’s value in millions of AED. Each clam can only be opened once, and the player can see the value of each pearl underneath it. After all six clams have been opened, the total value of all the pearls is displayed at the bottom of the screen. The player can then click the “Return” button to return to the start screen and play again.

PLAY GAME!!

Code I Am Proud Of

There were quite a few aspects of the game that I found difficult to implement, but with the help of references and experimentation, I managed to bring my ideas to life and create a game that I am proud of. The three parts I am most proud of are the moving waves, the collision detection and the random pearl generation.

1.Moving Waves and the Animated Ocean

function drawWave(offset, speed, phase, amp, c) {
  // sin() makes the smooth up and down, t makes it move
  const waveY = (x) =>
    surfaceY + offset + sin(x * 0.02 + t * speed + phase) * amp;

  stroke(30, 80, 130);
  strokeWeight(3);
  fill(c);

  // go along the top of the wave, then down and back to close the shape
  beginShape();
  vertex(0, floorY);

  for (let x = 0; x <= width; x += 6) {
    vertex(x, waveY(x));
  }

  vertex(width, floorY);
  endShape(CLOSE);
}

I did not want the ocean to stay still so I wanted to mimic how waves move in the ocean. I used mathematical functions to create waves that move up and down instead of using a video or a moving image. The sin() function creates the wave shape while the variable t changes over time to animate it. I also used different speeds, offsets and phases to create layers of waves. This was challenging because I had to understand how the sine function works and how to connect points to create a filled shape rather than just a line.

2. Detecting Collisions Between the Diver and the Clams

function checkClamCollisions() {
  // if the diver is closer than this to a clam, it counts as a touch
  const hitDist = diverH / 2.5 + clamH / 2;

  // we go backwards so removing a clam does not mess up the loop
  for (let i = clams.length - 1; i >= 0; i--) {
    if (dist(diverX, diverY, clams[i].x, clams[i].y) < hitDist) {
      clams.splice(i, 1);
      score++;

      // 6 clams means it is time to open them
      if (score >= 6) {
        startPearlScreen();
        return;
      }
    }
  }
}

I am proud of how I made the diver collect clams by moving into them, which made my project an actual game rather than just an animation. The dist() function calculates the distance between the diver and each clam. When the diver gets close enough, the clam disappears and the score increases. Once the player collects six clams the game moves onto the pearl screen. This was challenging because I had to remove clams from a list while looping through it. I solved this by counting backwards in the loop so that removing an item would not cause the code to skip another clam.

3. Random Pearl Generation and Calculating Pearl Values

function openPearl(i) {
  const c = pearlClams[i];

  // a clam can only be opened once
  if (c.opened) return;
  c.opened = true;

  // pick a random colour and a random size
  const shade = random(pearlColors);
  const size = Math.floor(random(15, 201));

  // bigger pearls give more points
  const sizePoints = size >= 150 ? 5 : size >= 100 ? 4 : size >= 50 ? 3 : 2;
  const value = shade.points + sizePoints;

  collectedPearls[i] = {
    size: size,
    color: shade.name,
    rgb: shade.color,
    value: value
  };

I am proud of this feature because it adds an element of surprise to the game and connects it to the historical importance of pearl diving in the UAE. When the player clicks a clam, the game randomly generates a pearl with a colour and size. Each colour has a different point value, and the pearl’s size adds more points depending on its range. The values are then added together to calculate the total value of the collected pearls. This was the most challenging part because I had to combine random values, arrays, objects, conditional statements and calculations. I also had to ensure that each clam could only be opened once to prevent the player from repeatedly increasing the total by clicking the same clam.

Development Process and Challenges

One of my biggest challenges was deciding how to end the game and make the user feel accomplished after collecting all six clams. At first, I wanted to create a pearl auction with all the pearls the user collected but as I developed the game I realised that my code was already too long with all the aesthetic features I wanted to include. I then came up with the idea of randomly generating pearls with different colours, sizes and values, allowing the user to open each clam and discover how much their pearls were worth. I decided that showing the total value would be a simpler and more satisfying ending. This helped me keep the game interactive while giving the user a clear sense of achievement without adding too many extra features. 

I also had a hard time finding a audio recording of a traditional UAE chant that had enough volume and clear sound to work well in the game. I wanted the audio to fit the UAE pearl diving theme and make the experience feel more authentic. Finding the right sound took some time but it was important to me because I wanted the game to feel connected to the culture and history I was representing.

Reflection and Future Improvements

This project took a really long time to complete because I wanted to include many details and aesthetic features to make the game more interesting. Looking back I could have shown Emirati traditions in many other ways if I had more time. I could have explored more creative ideas and added features that gave the user a deeper understanding of the history and culture of pearl diving in the UAE. For future improvements, I would like to make the game more interactive and include more creative ways to represent traditional life, such as adding more activities, stories or cultural details. 

References and AI Usage

Sine Function and Moving Waves

I used the p5.js reference for the sin() function to help create the moving waves in the ocean.

Code! Programming with p5.js for Beginners by The Coding Train

I frequently referred back to this series, especially the videos about while loops, for loops, conditionals and classes. These videos were extremely helpful when understanding the concepts and retracing errors in my code.

Random Pearl Generation: I used the p5.js reference for the random() function to help generate random pearl colours and sizes. https://p5js.org/reference/p5/random/

Audio: For the audio features, I used the class notes available on GitHub to help implement the ocean sounds and traditional chant.

Mouse Interaction: I referred to The Coding Train’s tutorial on mouse interaction to help implement clicking on buttons and opening the clams. https://thecodingtrain.com/tracks/code-programming-with-p5-js/code/7-arrays/4-mouse-interaction

Waves and Other Visual Features:I used this tutorial as a reference for creating the waves and other visual elements in the game. https://youtu.be/Fbzqfsy5GnM?si=Tc5C07y_HyG6XCOR

AI Usage: I used ChatGPT a little to help me understand and fix errors in my code. It was also useful for creating the moving waves using the sin() function and helping me generate a list of pearl colours with different point values then apply them randomly when the player opens each clam. It also suggested adding a helper function at the end of my code to check whether the mouse is inside a button or rectangle which made the mouse interaction easier to manage.

I also used Google Gemini to help generate the diver and instructions-page graphics based on my sketches. Since the graphics were AI-generated, some details may not be completely accurate or finished.

Midterm Project | LARP Devil

Concept

I was very interested in using non-conventional inputs for RPG based games, and had initially planned to make an audio-based movement game. However, I came to the conclusion that the lag between the player voicing out the command and it executing in game would cause a loss of interest. This is when I decided to go into using a hybrid strategy where the user still controls the player using keys, but unlocks special abilities when humming.

I incorporated the idea of short but tricky levels from Level Devil, a simple 2D level based RPG game, so a player would typically try multiple times per level,  making the player feel smarter at the end of each level. Here are a few key decisions I took to give my project direction:

    1. Level 3 should be unusually harder and the newfound ability doesn’t help,  the player will get stuck, and they must learn to master the key controls to maneuver the player between the ladders. This accomplishment will make the next levels much easier, and make players more likely to finish the game.
    2. The game starts after enter is pressed from the start scene, the player will use either WASD or up,down,left,right arrows to move. The movement must be smooth and the player walks should feel natural.
    3. Sprites increase computing and reduce FPS, making the game slow, so I chose to create the character using code.
    4. I kept the death count and level count as indicators of progress, and decided to name each level.
    5. Sound effects, and background music must be handpicked.
    6. Player must get the stop-time and reverse-time abilities after 2 levels and abilities must grow as levels progress.
    7. Giving player and theme customisability (a suggestion from a classmate)

Sketch

Implementation details – Game States

The global state variable (managed in js/state.js) determines which update cycle and rendering pipeline draw() executes each frame:

title → tutorialControls → play ↔ dead / won (level 1,2)→ tutorialAudio → play ↔ dead / won (level 3-12) → … → ending

Every frame, draw() in js/sketch.js performs the following steps:

    1. Calculates a clamped delta/marginal time (dt).
    2. Executes the update logic corresponding to the active state.
    3. Applies uniform aspect ratio scaling and letterboxing transforms (translate(0, view.oy); scale(view.s);).
    4. Calls the appropriate drawing functions for world entities, traps, and UI overlays.

Script Load Order

Load order is critical for initialization-time dependencies (for instance, state.js relies on constants defined in levels.js). sketch.js must always be loaded last as the main entry point.

# Script File Responsibility & Core Systems
1 levels.js Constants (W, H, physics parameters, stamina limits, skin presets, color palette) and the 12 level layout definitions via buildLevel(i).
2 audio.js Sound effects, background music streams, microphone/FFT frequency analysis, hum calibration, and time-ability duration timers.
3 state.js Shared mutable game state (level, player, active state, particle systems, skin/theme preferences, camera view dimensions, win animation variables).
4 stamina.js Player stamina consumption/regeneration, sprint inertia carry-over, skin styling, and camera viewport follow updates.
5 storage.js Safe localStorage wrapper (GameStorage) with graceful fallbacks for private browsing / restricted contexts.
6 input.js Native capture-phase key tracking via activeKeys, keyboard focus grabbing on canvas click, and multi-key helper functions.
7 voice-powers.js Hum-activated time manipulation: Freeze (stops world simulation) and Rewind (rewinds environment while leaving player freely movable).
8 physics.js Moving platforms, ladder climbing mechanics, platform collisions, footstep triggers, and crumbling tile timers.
9 game-update.js Main gameplay per-frame loop (updateGame), gate relocations, exit switches, and collision query utilities (isSolid).
10 traps.js Trap trigger detection and movement mechanics (chase wall, pop spikes, falling spikes, spinning saws, lasers, springboards, fake platforms).
11 game-flow.js Geometry intersection checks (AABB, circle-rect), death handling, level completion sequences, win animations, and particle emissions.
12 time-overlays.js Post-processing visual overlays and HUD indicators for active Time Freeze and Time Rewind.
13 scene.js Top-level scene composition orchestrator (drawScene).
14 title.js Title screen rendering, theme & skin selection selectors, and game start transitions.
15 tutorial.js Controls onboarding screen, ability selection menu, and voice frequency calibration module.
16 ending.js Ending cutscene sequence, stats overview, and victory presentation.
17 backdrop.js Background rendering, artwork asset loading, and procedural fallbacks for theme backgrounds.
18 world-render.js Procedural geometry drawing for platforms, doors, ladders, character speech banners, and programmatic player animation.
19 traps-render.js Individual trap renderers and dispatchers (spikes, saws, laser beams, crumbling stones).
20 ui.js In-game HUD (death tally, level titles, controls banner), settings slide-out panel, and UI button click areas.
21 events.js p5 lifecycle event dispatchers (mousePressed, touchStarted, keyPressed, keyReleased).
22 sketch.js Entry point (setup, draw, windowResized), p5 accessibility bug monkey-patches, letterbox scaling, and level resetting.

Assets Used

(All audio used was found from Pixabay and trimmed to fit the game sound needed)

    • Background Music (background-music.mp3): Ambient soundtrack looping during gameplay.
    • Good / Success (good.mp3): Level clear chime played upon reaching the exit gate.
    • Glass Breaking (glass-breaking.mp3): Death and impact sound effect when hitting traps.
    • Ladder (ladder.mp3): Climbing audio triggered when ascending/descending ladders.
    • New Message (new-msg.mp3): Dialogue and narrative notification pop sound.
    • Hell Theme (hell.png): Background artwork for the hell/underworld theme.
    • Nature Theme (nature.png): Unlockable background artwork for the nature theme.
    • Settings Gear (gear.svg): Vector icon for the in-game settings panel button.

Challenges Faced 

    1. Key inputs were a major challenge on the web editor. Often the preview canvas doesn’t initially receive keyboard focus. In the canvas, clicking the preview didn’t focus it, so keyboard events went to the editor instead.
    2. Refactoring sketch.js wasn’t easy either, maintaining SOLID principles got harder as more features were added.
    3. Humming conflicting with background noise, to combat this, I chose to simply lower the volume while recording the hum and keeping it lower in the next levels.

Code that I am Proud Of

Detecting pitch in real-time in the browser can be noisy. Rather than relying on heavy third-party speech libraries, I wrote a lightweight FFT peak-detection algorithm that isolates the dominant fundamental frequency around the player’s calibrated humming range while filtering out background noise:

// Detects the dominant pitch frequency from FFT spectrum data
function detectPitch(spectrum) {
    let maxEnergy = 0;
    let peakBin = -1;
    let nyquist = sampleRate() / 2;
    let binWidth = nyquist / spectrum.length;

    // Scan only musical vocal range (80 Hz to 600 Hz)
    let minBin = Math.floor(80 / binWidth);
    let maxBin = Math.min(Math.floor(600 / binWidth), spectrum.length - 1);

    for (let i = minBin; i <= maxBin; i++) { if (spectrum[i] > maxEnergy) {
            maxEnergy = spectrum[i];
            peakBin = i;
        }
    }

    // Return the detected frequency if it passes our confidence threshold
    return (maxEnergy > 140 && peakBin !== -1) ? peakBin * binWidth : 0;
}

Why I’m proud of this: It runs at 60 FPS directly within the p5 audio loop with zero noticeable latency, allowing the voice abilities to feel instant and responsive.

When rewinding time, standard mechanics often rewind the player along with everything else, trapping them in the same fatal mistake. I designed an asymmetric rewind system where the game world rolls back, but the player retains full agency to escape incoming hazards:

// Rewinds traps and level obstacles while leaving the player freely movable
function applyTimeRewind(targetFrame) {
    let snapshot = timeHistory[targetFrame];
    if (!snapshot) return;

    // Restore environmental state (traps, moving platforms, crumbling floors)
    for (let i = 0; i < snapshot.traps.length; i++) {
        Object.assign(level.traps[i], snapshot.traps[i]);
    }
    gameTime = snapshot.gameTime;

    // Noticeably absent: player.x and player.y are NOT restored.
    // The player stays completely active and can dodge out of the trap's path!
}

Why I’m proud of this: It turns a frustrating trial-and-error moment into a dynamic, clutch escape mechanic that feels empowering to the player.

Reflection

I am pretty happy with the final game, I learnt a lot of new things in p5 and realized how the organization of the code into classes representing various types of objects made it much easier to understand and develop. There is some scope for improvement that could be done in the future:

    1. Making it multiplayer. (Conflicting hums would be interesting to tackle)
    2. Adding more sound effects and tricky situations in levels.
    3. Adding a level map, so players can revisit old levels

Week 7 – Midterm

https://editor.p5js.org/tms9947/sketches/9XCrV1Jab

The Concept:

I started collecting records and CDs last year, and I really enjoy the experience of going into a store and just spending time looking through the records and CDs they have available. However, since I began, I noticed that Abu Dhabi doesn’t have many stores that sell records. Thus, I decided to take matters into my own hands and create a record store experience as my midterm project, where I hope to replicate that same relaxing feeling that I experience when at an actual record store.

At first, I was unsure how to go about the project, in terms of how to make it something fun and interactive. Once I gave it some thought, though, I came up with the idea of having three sections in the store, each relating to music. When creating the three sections, I wanted to have it be realistic and have activities that you could experience in an actual record store. This led me to the three I chose: the actual store, where you view and purchase records, the music corner where a record player is controlling the music in the store, letting users choose the music they hear while looking at the project and lastly a music based trivia game inspired by restaurant trivia nights and board game cafes, asking variety of questions around genres, artists and genreal music knowledge.

The design of the starting area(the store entrance) and the main area of the store(the record shevles) were inspired by actual record store layouts and architecture which I tried to recreate using shapes within p5. Below is an example for each.

Record store entrance example
Record shelves example

How The Project Works:

The experience allows users to navigate through a record store, using a combination of keys and the mouse pad to interact with and move through different sections. I first made each section of the store in a separate p5 project, allowing me to make sure it was fully functional before adding it to the main project, also making the process of debugging much easier given the quicker loading times. Using a lot of ‘if’ and ‘else if’ statements throughout the code to manage the different states of the game, which reference the section of the store the user is in. This allowed me to get the changing scenes to work seamlessly with the button interactions that allow the users to switch between them.

A part of the code I’m really proud of, which is also one of my favorite parts of the project, is the section that creates the animation for the record on the player. When I first started working on this part of the code, I was using a tutorial from The Coding Train titled “How to Rotate Shapes in p5.js (translate, rotate, push, pop)”, but I wasn’t able to get it to work at first. This led me to ask the professor for help, and he then explained the concepts of rotation, translation, and the push and pop functions, which really helped me understand how to get the rotation to work.

//spinnign record
    imageMode(CENTER);
    translate(275,height/2);
    rotate(angle);
    image(record,0,0,recordWidth,recordHeight);
    pop();
    angle = angle + 1;

Improvements:

One improvement I would like to add is maybe having more realistic assets, rather than just the simple ones I created in p5, possibly even combining some realistic and drawn elements. Additionally, I wanted to add randomization for the albums that would display on the shelf, as well as possibly having them move along the shelves like a conveyor belt, but I was not able to get to this due to time constraints. Adding to the randomization, having a larger database of albums to pull from would also be another improvement to add.

When currentl viewing the albums in the larger mode, the mouse clicks still register when clicking over a different album. This is an issue I wasn’t able to correct in time, but in the future I’d like to make it so that when the users is viewing the larger version of the albums they can’t immediatly switch to the others and instead need to click again to exit and then select a different ablum to view.

References:

I used AI to figure out how to go about creating the album class I ended up implementing, as well as formatting the text and positions of a few things in the later stages of development, because the large amount of code made it a bit harder to manage. 

Video/Turials I used for major aspects

https://youtu.be/o9sgjuh-CBM?si=VLxmfbLdzPb72X8l

https://youtu.be/Pn1g1wjxl_0?si=HJhL-7whJ1jlxXU0

https://youtu.be/4w7PR70XJkE?si=IBRi3oZBpgoYj7Iz

https://youtu.be/3pI3SQjfVyA?si=geUIo5htIBbONHli

Lofi covers I used for the background

https://youtu.be/p2Jp-HLva28?si=yj7_UfyhsEwXetpH

https://youtu.be/9-J8G9KQjWg?si=LTP2qWdXRggH89Z_

https://youtu.be/Xoj02q0beSw?si=v5q1zEMCrVQX7rNL

I used the p5 reference website to figure out some smaller things

https://p5js.org/

Midterm Project

Week 7 – Midterm Documentation:

My Concept:

For my midterm project, I wanted to create something that was connected to my own experience at NYU Abu Dhabi, rather than making a game that could be set anywhere. One thing I noticed since starting university is how many people use scooters to get around campuses like NYUAD, AUS, UAEU and many other campuses around the world. I usually hear a lot about scooter incidents, and how sometimes people end up almost crashing into each other, bushes, or other things along the way. I thought it would be funny to turn this into a game, especially because it is something that other students would probably recognize, and maybe learn to be more aware of.

My initial idea was pretty simple. The player would control a scooter and try to avoid obstacles while moving through campus. However, as I started developing it, I wanted to make the game a bit more interesting than just surviving for as long as possible. I remembered that when I first discussed my idea with Professor Mang, he suggested that crashing into a professor could actually give the player extra points. I really liked this idea because it made the game more unexpected, and it also meant that the player would have to decide which characters to avoid and which ones to aim for. Eventually, I also decided to add Professor himself as a rare character who gives the player an even bigger bonus.

My Game

 

How My Game Works

The game has four main screens: the start screen, instructions screen, gameplay screen, and game over screen. I used a variable called gameState to keep track of which screen the player is currently on. The draw() function checks the value of this variable and displays the corresponding screen. I also added both keyboard and mouse controls so that the player can move through the different screens, start the game, and restart after crashing without having to reload the entire sketch.

During the game, the player controls a purple scooter using the left and right arrow keys. The goal is to survive for as long as possible while avoiding students, backpacks, and traffic cones. The score gradually increases the longer the player survives. However, unlike the other obstacles, professors give bonus points when the player collides with them. A regular professor gives 100 points, while Professor Mang gives 500 points. I also added a high-score system so that players can try to beat their previous score.

One of the main things I wanted to achieve was making the game feel like the player was actually moving through campus, even though the scooter itself stays towards the bottom of the screen. To do this, I used a campus background image and displayed it twice, with one copy placed above the other. Both images move down continuously, and once they reach a certain point, their position resets. This creates the illusion of an endless moving background.

// moving campus background
function drawMovingBackground() {
  backgroundY += backgroundSpeed;

  if (backgroundY >= height) {
    backgroundY -= height;
  }

  image(campusImg, 0, backgroundY, width, height);
  image(campusImg, 0, backgroundY - height, width, height);
}

Another important part of the game was using object-oriented programming. I created an Obstacle class to manage the different characters and objects that appear throughout the game. Each obstacle has its own type, position, and speed, along with functions that control how it moves, appears, and resets. I stored these obstacles in an array so that I could use a loop to update and display them instead of having to write separate movement code for every character.

I also used the dist() function for collision detection. It calculates the distance between the scooter and an obstacle, and if that distance becomes small enough, the game checks what type of obstacle the player collided with. Depending on the obstacle, the player either receives bonus points or the game ends

// collision detection
  checkCollision() {
    let x = this.getX();

    let obstacleDistance = dist(
      scooterX,
      height - 120,
      x,
      this.y
    );

    return obstacleDistance < 42;
  }
}


What I’m Proud Of:

One of the parts I’m happiest with is how the game becomes more challenging as the playerm progresses. At first, the game starts with fewer obstacles and a slower background. However, once the player reaches certain scores, the difficulty level increases. This changes the speed of the background and obstacles, while also increasing the number of obstacles appearing on the screen. I wanted the game to become more difficult gradually, rather than having the same level of difficulty throughout, because otherwise I felt that it would become repetitive.

// difficulty increases with score
function updateDifficulty() {
  if (score >= 5000) {
    difficulty = 4;
  } else if (score >= 2500) {
    difficulty = 3;
  } else if (score >= 1000) {
    difficulty = 2;
  } else {
    difficulty = 1;
  }

I’m also proud of the different characters I created using p5.js shapes rather than relying on images for everything. I used rectangles, ellipses, lines, and other shapes to draw the students, professors, and Professor Mang. I also experimented with using sin() and frameCount to animate the characters’ legs and arms so that they look like they are walking or running. I think these small details helped make the game feel more alive.

Another part of the game that I’m really proud of is the professors. Instead of making every character an obstacle that the player has to avoid, I decided to make professors give bonus points when the player crashes into them. Regular professors give 100 points, while Professor Mang is a rarer character who gives 500 points. I also added a purple aura around the professors to make them stand out from the other characters. I liked how this made the game a bit more interesting because players have to pay attention to who they’re approaching rather than just avoiding everyone. I also added different sound effects for crashing into students, hitting other obstacles, collecting bonuses, and clicking through the screens, as well as background music. I think these small details helped make the game feel more complete and fun to play.

Problems I Ran Into and Areas for Improvement

I ran into quite a few problems while making this project, especially because this was my first time creating a game with this many different elements. One of the biggest challenges was making sure that everything worked together properly. For example, I had to figure out how to make the obstacles move, reset their positions, and detect collisions without interfering with the rest of the game. I also spent time adjusting the spawn rate and difficulty because I wanted the game to be challenging but still playable. Another issue was getting the background music to work consistently. At one point, the sound effects were working but the music wasn’t playing, so I had to go back and review how I was loading and playing the audio files.

I also had some problems with the visual design, particularly when trying to make the score and level easy to read during gameplay. I experimented with different fonts and layouts, but some of them ended up being difficult to read or distracting from the game itself. Eventually, I decided to keep the score and level together in a simple box in the corner. If I were to continue developing this project, I would like to improve the way obstacles spawn so that they don’t appear too close together, and maybe add more recognizable NYUAD locations or characters. I also think it would be interesting to introduce different power-ups or special events as the player reaches higher levels.

Overall, I’m happy with how the game turned out, especially because I originally started with a much simpler idea. This project helped me understand how the different concepts we learned in class, such as arrays, classes, conditionals, and functions, can work together to create something interactive. It also made me realize how much time goes into testing and making small adjustments, even when the final game might seem relatively simple to play.

References and Use of AI

I used the lecture notes and p5.js examples from class to revisit concepts such as arrays, object-oriented programming, functions, and collision detection.

I also used ChatGPT throughout the development process to help me troubleshoot errors, understand unfamiliar parts of the code, and explore ways to implement features such as increasing difficulty, sound effects, and different game states. I used it to help develop and revise the code for the game, as well as simplify certain sections so I could better understand how they worked. I also used AI assistance when developing the visual assets for the different game screens.

Other resources:

Midterm Project

Concept

For my midterm project, I decided to create an interactive slime-making game called Slime Lab. I wanted to make something cute, colorful, and fun that people could interact using their own choices. The idea is that the user gets to create their own slime by choosing its color, face, and charm, and then play with it using different actions.

I also wanted the game to feel like an experience rather than just having a slime in the middle of the screen and poking it (like other slime games). So I included an instructions page, a customization section, and a play area where the user can poke, stretch, and shake their slime, and finally a page where you could either save the slime to your device or go back to create another. I used different PNG images and a sound effect to make the interactions feel more satisfying and realistic. I wanted everything to have a consistent relaxing and cute style including the buttons and the slime itself.

How My Project Works and What I’m Proud Of

The game starts with an instructions page that explains how to play. Once the user clicks the start button, they can customize their slime by selecting a color, face, and charm. I used arrays and “for” loops to organize the options rather than writing separate code for every single button. When the user clicks an option, the program saves their choice using variables like slimeColor, slimeFace, and slimeItem. These choices are then used to display the customized slime. I am proud of this part because there are multiple combinations the user can create, and I wanted the customization process to be simple and easy to understand.

let rows = [252, 357, 462];

for (let row = 0; row < 3; row++) {
  for (let i = 0; i < OPTIONS_X.length; i++) {
    if (
      dist(
        gameMouseX(),
        gameMouseY(),
        OPTIONS_X[i],
        rows[row]
      ) < 22
    ) {
      if (row === 0) slimeColor = i;
      if (row === 1) slimeFace = i;
      if (row === 2) slimeItem = i;

      return;
    }
  }
}

This part of my code uses nested for loops and the dist() function to check which color, face, or charm the user clicks on. I used this instead of writing separate code for every button, which made everything a lot more organized.

One of my favorite parts is the actual slime interactions. After creating their slime, the user can poke, stretch, or shake it. Instead of just changing the size of the slime like I originally planned in my sketch (process pic below), I decided to use different PNG images to show what the slime looks like during each action. I think this made the interactions look a lot better because I had more control over the appearance of the slime. I also added a sound effect for the poking to make the actions more satisfying and the shake button moving the slime in a quick motion.

(^When slime is stretched originally)

(Process pictures)

Another thing I’m proud of is making the game work in fullscreen mode. Originally, I designed everything around a fixed canvas size, so when I tried making the canvas fullscreen, the buttons and images were not positioned correctly. I eventually used scaling and offset calculations to keep the game’s proportions consistent across different screen sizes. I also had to adjust the mouse coordinates so that the buttons would still work after scaling, and then I used Professor Mang’s example sketch for clicking “F” to enter and exit full screen. This was probably one of the more confusing parts of the project, but I learned a lot from fixing it. I also included a way for users to restart the experience from within the game by including a button instead of having to press the p5.js Play button again.

function gameMouseX() {
  return (mouseX - gameOffsetX) / gameScale;
}

function gameMouseY() {
  return (mouseY - gameOffsetY) / gameScale;
}

Problems and Areas for Improvement

One of the biggest problems I ran into was organizing all the different PNG images for the slime. Since the user can choose different colors, faces, and charms, I had to make sure the correct images appeared depending on what they selected. It got a little confusing because I had so many images to keep track of, and sometimes the wrong image would show up or something wouldn’t work the way I wanted it to. I also struggled with making the interactions look realistic. At first, I thought simply changing the slime’s size would be enough, but after testing it, I realized it didn’t really look like slime being stretched or poked. This is why I decided to use different PNGs for the actions instead, and then I also ran into issues with the new version of p5.j and the load images would not work.

Another thing I struggled with was drawing the different slime images and making sure everything matched properly. Sometimes the faces wouldn’t line up with the slime, or the sizes would be completely off when I tried displaying them in the game. This was especially frustrating because the drawings looked fine on their own, but once I put them into p5.js, they didn’t always fit together the way I wanted. I had to keep going back and adjusting the sizes and positions of the images until everything looked mostly right. I didn’t expect this part to be so difficult because I thought drawing the slime would be one of the easier parts of the project!

If I had more time, I would definitely want to make the slime more interactive. Right now, the user has to click buttons to poke, stretch, or shake it, but I think it would be so much cooler if they could actually interact with the slime directly. For example, dragging the slime with the mouse to stretch it or clicking anywhere on it to poke it. I would also love to add more slime textures, colors, and charms, and maybe even different sound effects for each action. Originally, I had planned to draw the charms but there were too many issues with that, so i ended up just using emojis for the charms, but that’s definitely something I wish I could have changed.

References

p5.js Reference: https://p5js.org/reference/

I used p5.js functions throughout my project, especially mousePressed() for the buttons, dist() for detecting which customization option the user clicked, and loadImage() to display the different slime PNGs. I also worked with windowResized() and resizeCanvas() when trying to make the game fullscreen.

Slime Games and Videos –  A lot of my inspiration came from my childhood because I used to be obsessed with slime. I loved watching slime videos and experimenting with different colors and textures. I wanted to recreate that same experience digitally, especially the customization and satisfying interactions.

Professor Mangs Lecture Notes –

I referred to Professor Mang’s lecture notes to help me understand some of the coding concepts we covered in class and how I could apply them to my Slime Lab project. The notes were useful when working with functions, loops, and organizing different parts of my code as well as how to upload sound into my sketch.

https://github.com/mangtronix/IntroductionToInteractiveMedia/blob/master/lectureNotes.md#week-5

https://editor.p5js.org/mangtronix/sketches/t4G0erH1B

Coding Train videos –

For creating Mouse Interactions- https://youtu.be/TaN5At5RWH8?si=_xHhlhbPZx7t5YrJ

Arrays of Objects –https://youtu.be/fBqaA7zRO58?si=NP9jlVjqXL85jS7C

Fonts Used:

https://fonts.google.com/specimen/Fredoka

https://fonts.google.com/specimen/Nunito

Background Music: https://freesound.org/people/Seth_Makes_Sounds/sounds/701610/

Poke sound: https://freesound.org/people/kuchenanderung1/sounds/340794/

AI Statement

I used ChatGPT for this project when I got stuck on something and couldn’t figure out what was going wrong. Most of the time, I knew exactly what I wanted my game to do, but I didn’t always know how to actually code it. I would try things myself first, and if they weren’t working, I would ask ChatGPT to help me figure out what I was doing wrong. I also asked it to explain certain functions because sometimes I would get the code working but still not fully understand why it worked.

I also used it when I was struggling with the customization options, especially organizing everything using arrays and nested for loops. Another issue I kept running into was with my PNG images. For some reason, whenever I tried uploading multiple images at once, they would sometimes get corrupted or wouldn’t load properly, and I couldn’t figure out why. I used ChatGPT to help troubleshoot this and figure out how to organize and display the correct images. (I did not use Ai for any image generating)

Midterm Project

Interactive Media Midterm Documentation 

My Concept: When assigned the midterm project I immediately knew that I wanted to create a game that resonated with Formula 1. My initial focus was on creating a game that was simple and accessible to play. I got some inspiration from familiar computer games such as Tetris and Flappy Bird and I aimed  to incorporate similar functions and elements through straightforward controls, such as the use of arrow keys. This idea initially inspired me to create a game where you avoid hitting any competitor cars and try to achieve a new time highscore each time. However, I later thought that this idea was too generic and decided to correlate to myself more strongly. 

Therefore, I came up with the idea of basing my game off of the Abu Dhabi grand prix in Yas Marina Circuit. The Abu Dhabi GP is famous for starting in the daylight and finishing at night which is a feeling that I wanted to recreate in the game. Therefore the whole race happens as the track slowly transforms from daylight to nightline in the span of 60 seconds. 

Therefore, this inspired me to move away from the idea of only focusing on survival, and instead revolve it around the time span between day and night. Therefore before each race, the player gives a random number of gold tokens to collect between 10 and 20 and the aim is to collect them all before the sun sets. However, if you crash into another car before collecting the needed amount of tokens you immediately lose. This differentiates my game to others as not only is it based in Abu Dhabi, but also challenges the player as they have to decide between going for a token or remaining safe from competitor cars. 

How the game works: 

I decided to split the game into several screens such as the start screen, goal screen, race screen, game over screen, and win screen. In order to keep track of which screen the game is supposed to be on, I created a variable called gameState. Whereas the draw function makes sure to check it in every frame to show the right screen. Using the mousePressed function I was able to create the “start” and “play again” buttons which work by checking if the mouse is clicked within the boundaries of the button’s rectangle (ensuring to only start the game when the player intends to do so). 

One of the most complicated parts of the code was creating the motion of the road whilst ensuring that it was switching from day to night. To do this I used a top-down track image that I drew onto the canvas twice, one on top of the other. The two images therefore slide down every frame allowing one to go off the screen whilst the other enters. This creates an endless effect that allows the road to look like it is continuously moving and changing. To create the transformation from day to night I used a progress value from 0 to 1 based on the timer. This progress value ensures to split the time equally between day to night. I then remembered using the map function in one of my previous weekly assignments and therefore thought that It would be suitable to implement into the game. Therefore the map() turns the progress into a transparency level, so the sunset image fades in over the day image in the first half of the game and the night image fades in over the sunset image in the second half of the game. The tint function is also used to make each of the next images lined up see through, working with the progress function to know when an image should disappear. ُthis all takes place while the images scroll in order to keep the track moving.

// changing sky from daylight to sunset to nightime
let progress = constrain(survivalTime / raceLength, 0, 1);

if (progress < 0.5) {
  drawScene(dayImg);
  tint(255, map(progress, 0, 0.5, 0, 255));
  drawScene(sunsetImg);
} else {
  // sunset to night
  drawScene(sunsetImg);
  tint(255, map(progress, 0.5, 1, 0, 255));
  drawScene(nightImg);
}

The competitor cars and tokens were made using classes called “Obstacle” and “Token”. Each class keeps its own position and speed and has its own move() and show() implemented into the code. They are stored into arrays, allowing the game to add a new obstacle/token over time and as the game proceeds.

 

// Setting up the race and game page 
function startRace() {
  enemies = [];
  tokens = [];
  tokenCount = 0;
  nextCar = 60;

  // Doubling the tokens needed to ensure that its always possible to win 
  tokenGap = (raceLength - 5) / (tokenGoal * 2);
  nextTokenAt = 1;

Code that I am proud of:

The part of the game that I am honestly proudest of is how the game is kept fair. I ensured to keep the game fair through the use of certain elements such as releasing double the amount of tokens needed that are spread evenly across the race rather than being saturated over one area of the track. This therefore allows every goal introduced in the goal page to be achievable. I am also proud of the function that I created called spotIsFree which checks to see that nothing is on the track before adding a new token or competitor car. I also ensured that the competitor cars and tokens drive at the same speed so that they can never drive into each other, therefore ensuring fairness across all ends.

 

// release a new token when it's time
if (survivalTime >= nextTokenAt && survivalTime < raceLength - 5) {
  let x = random(roadX + carW / 2, roadX + roadW - carW / 2);
  if (spotIsFree(x, -40)) {
    tokens.push(new Token(x));
    nextTokenAt = nextTokenAt + tokenGap;
  }
}

 

Problems and areas for improvements:

Overall, I am really satisfied with the final outcome of the game. However, I did run into several issues throughout the process of creating it. Most of the issues included small bugs such as typos like capital letters in variable names, using = instead of ===, or a : instead of a ;. This therefore taught me to go back and read my code carefully, as well as use the console errors to guide my mistakes as I tend to avoid reading these especially when they’re too long. Some of the other issues that I ran into included images failing to load when uploading files, cars overlapping with one another when I tried to implement random speeds, and the game slowing down when I added the tint function and custom font. 

Therefore, if I was given the opportunity to work on this assignment again, I’d make the game run more smoothly on every computer by looking back at the issue that was slowing the game down and creating unsmooth transitions between frames. I would also find a way to randomize the speeds of the cars without facing the troubles of them overlapping on top of one another. It would also be really cool to add more F1 features such as DRS boosts, pit stops, and real team competitors. 

References 

Use of AI:

Throughout the project, whenever I encountered difficulties or was unsure  of what functions are available for use to code a particular element, I used AI to explore potential functions and techniques that could help me achieve my intentions. For example, this helped me learn about the progress reference, which divided the game into equal sections, the tint function, which made images transparent, the floor math function, which rounded the number of tokens down to a whole number, and the constrain function, which helped keep the cars within the track boundaries. This guidance allowed me to investigate these functions further using other resources such as the coding train videos, understand how they worked, and apply them to my code to achieve my intended effects. I also used AI to generate background images for the game and create the sound effect of a car collision.

Other references: 

https://www.dreamstime.com/illustration/formula-top-view.html 

( this is the competitor car image reference used throughout the code)

https://www.youtube.com/watch?v=1Osb_iGDdjk

(This is the coding train video I watched on conditional statements to remind me of them when creating my game)

https://www.youtube.com/watch?v=cnRD9o6odjk

(I also watched this video on while and for loops for a refresher when creating my game)

https://www.youtube.com/watch?v=tA_ZgruFF9k

(This video on removing objects from arrays helped me when trying to get rid of the coins that were touched and/or passed and not collected)

https://www.youtube.com/watch?v=nicMAoW6u1g

(This video taught me more about masking which helped me when trying to transform one image into another using transparency features)

https://fonts.google.com/specimen/Permanent+Marker?categoryFilters=Feeling:%2FExpressive%2FActive&preview.script=Latn

(This is the font I used throughout my game)

Works Cited

Ostroukhova, Alena. “Black Silhouette of a Formula One Car With White Outlines. Racing Car Side View. Wheels, Spoilers and Halo. Form High-Speed Transport. Isolated on White Background. Illustration..” Vecteezy, www.vecteezy.com/free-vector/formula-1-car-isolated. Accessed 14 Oct. 2026.

“Pixabay.” Pixabay.Com, 2024, pixabay.com/sound-effects/search/coin/. Accessed 14 Oct. 2026.

The Coding Train. “2.5: The Map() Function – P5.Js Tutorial.” Www.Youtube.Com, 4 Sept. 2014, www.youtube.com/watch?v=nicMAoW6u1g. Accessed 14 Oct. 2026.

—. 3.1: Introduction to Conditional Statements – P5.Js Tutorial. YouTube, 10 Sept. 2015.

—. 4.1: While and for Loops – P5.Js Tutorial. YouTube, 11 Sept. 2015.

—. 7.5: Removing Objects From Arrays – P5.Js Tutorial. YouTube, 17 Oct. 2017.

Daniel Green Midterm work

In the realm of entertaining carnival games, surely nobody is unfamiliar with the infamous game known as Whack-A-Mole. It is already known as a widespread game that we all surely must have enjoyed at least once during our lives; after all, who doesn’t enjoy bonking moles that pop out of dirt holes in the head with a big hammer? I am happy to make it clear that I myself am among the folks who have always enjoyed the game; hence why I chose to create my own unique take of the Whack-a-Mole game, but with a few twists to make it be a little bit different from the original. The twist to mention, the one that is the elephant in the room, the moles in my game are instead replaced by colored 2D versions of Mickey Mouse’s head! This is because of how not only was he my favorite during my childhood, but also because my major is based in getting involved in digital business with certain theme parks, which does include Disney! Therefore, I would consider this project as my own practice for when the day comes of me hopefully getting the chance to work alongside a major theme park like Disney. Speaking of colored heads, allow me to successfully reveal to you how the game works.

The way my game works is that it takes the classic Whack-a-Mole formula and formulates two rows of three holes, which altogether make up six holes. On top of that, the game would also supposedly take place in a forest, which is apart of my attempt to have the game feel as if you found these six holes somewhere in a special kind-of forest where these six holes have these Mickeys popping up in an act to simply challenge to see how fast your reflexes are; something that just sounds as if it popped right of a literal Disney short film! The only exception, however, would be the scoreboard on the top left corner of the game that will have points (aka your points) increase each time you hit a specific Mickey Head. Now, as for the Mickey heads that will pop out, there are about four different variations of them. The first variation is the Mickey Head whose head is colored cyan and his ears are colored white; simply, he is the variation that you are supposed to hit, since each time you hit one, you earn five points added to your score! Of course, that isn’t the only Mickey Head you are able to hit, for you must also be on the lookout for a yellow Mickey Head that will pop up anywhere between the gaps of the rows or in any of the six holes; it only shows you for one second, so you are going to be tested to see how fast you can click on it being it disappears into a three-minute cooldown. The reward for clicking it on time will add thirty points to your score! However, there are two other Mickey Heads that are best recommended for you to avoid. The first Mickey head to avoid is an orange-colored Mickey head that will be anywhere in either the row gaps or holes by random; this is because if you click on that one, it will reset your scoreback to zero as a penalty! Lastly, the other Mickey head to avoid is a red-colored Mickey head that will be unpredictable and pop up in almost the same spots as the yellow and orange ones, because clicking on it will immediately send you to a game over screen that lasts six seconds before taking you back to the main menu! However, a funny thing that I made sure was possible in my game, was that if a player were to wish to quit the game via being retreated back to the main menu and closing the game from there, they would have to click on a red Mickey head on purpose to do; I simply that since the game over screen is kind-of hilarious on its own without its awkward transition and silly trombone sound effect, it would be funny for the person and thus leave them with a smile on their face.

let mickeys = [];
let lastSpawnTime = 0;
let spawnDuration = 2500;

let score = 0;
let hitSound;

let redMickey = null;
let nextRedSpawn = 0;
let redSpawnTime = 0;
let redDuration = 1500;

let gameOverSound;
let gameOverStartTime = 0;

let yellowMickey = null;
let nextYellowSpawn = 0;
let yellowSpawnTime = 0;
let yellowDuration = 1000;
let yellowCooldown = 180000;

let orangeMickey = null;
let nextOrangeSpawn = 0;
let orangeSpawnTime = 0;
let orangeDuration = 1500;


// Being sure to check whether it's time for a new group to come up
    if (millis() - lastSpawnTime >= spawnDuration){
      spawnMickeys();
    }

    // Making sure to draw every Mickey currently in "mickeys=[]" array
    for (let mickey of mickeys){
      mickey.display();
    }

    // Spawn in red Mickey Head at the time as scheduled for it
    if(
      redMickey === null && 
      orangeMickey === null &&
      millis() >= nextRedSpawn
    ){

      spawnRedMickey();
      
    }
      
    // Take away red Mickey head after 1.5 second timer is up
    if (
      redMickey != null &&
      millis() - redSpawnTime >= redDuration
    ){
      redMickey = null;
      scheduleRedMickey();
    }

    // have the Red Mickey head be drawn
    if (redMickey !== null){
      redMickey.display();
    }

    // Spawn in yellow Mickey Head at the time as scheduled for it
    if (yellowMickey === null && millis() >= nextYellowSpawn){
      spawnYellowMickey();
    }

    // Put yellow head away after 1 second
    if (
      yellowMickey !== null &&
      millis() - yellowSpawnTime >= yellowDuration
    ){

      yellowMickey = null;

      // Begin cooldown (maximum 3 minutes)
      scheduleYellowMickey();
      
    }
    
    // Letting the yellow Mickey head be drawn
    if (yellowMickey !== null){
      yellowMickey.display();
    }

    // Spawn in orange Mickey Head at the time as scheduled for it
    if (
      orangeMickey === null &&
      redMickey === null &&
      millis () >= nextOrangeSpawn
    ){

      spawnOrangeMickey();
      
    }

    // Take out orange Mickey Head after 1.5 seconds
    if (
      orangeMickey !== null &&
      millis() - orangeSpawnTime >= orangeDuration
    ){

      orangeMickey = null;
      scheduleOrangeMickey();
    }

    // Let orange Mickey head be drawn and displayed
    if (orangeMickey !== null){

      orangeMickey.display();
      
    }

 function spawnMickeys(){

    // Remove any previous Mickey Heads
    mickeys = [];

    // Making the decision of whether 1, 2, or 3 Mickey Heads appear
    let amount = floor(random(1, 4));

    // Making sure it remembers which holes have been selected
    let usedHoles = [];

    for (let i = 0; i < amount; i++){

      // Decide on a random hole from 0 to 5
      let hole = floor(random(6));

      while(usedHoles.includes(hole) ||
            (redMickey !== null &&
             redMickey.holeIndex === hole
              
            )
           ){
        hole = floor(random(6));
           }

      // Making sure it remembers that this hole is in-use
      usedHoles.push(hole);

      // Being sure to calculate the hole's role and column
      let row = floor(hole / 3);
      let col = hole % 3;

      // Being sure to calculate the position of the Mickey Head
      let x = width * (0.25 + col * 0.25);
      let y = height * (0.45 + row * 0.3);

      // Bring the Mickey Head to life above the hole by a tiny bit
      let mickey = new MickeyHead(x, y - 30);

      // Adding Mickey Head to "let mickeys = []" array
      mickeys.push(mickey);
    }

    // Remembering when this circle group appeared
    lastSpawnTime = millis();

    // Choosing random duration between 2-3 seconds
    spawnDuration = random(2000, 3000);
  }

  function spawnRedMickey(){

    // Choose which of the 9 positions for the red head to go by random
    let position = floor(random(9));

    let x;
    let y;

    if (position < 6){

      // one of the 6 holes, should the Mickey head appear there
      let row = floor(position / 3);
      let col = position % 3;

      x = width * (0.25 + col * 0.25);
      y = height * (0.45 + row * 0.3) - 30;
      
    } else {

      // One of the 3 gaps between columns
      let col = position - 6;

      x = width * (0.25 + col * 0.25);
      y = height * 0.60 - 30;
      
    }

    redMickey = new RedMickeyHead(x, y);
    redMickey.holeIndex = position < 6 ? position : -1;
    redSpawnTime = millis();
    
  }

  function spawnOrangeMickey(){
    let availablePositions = [];

    // Be on lookout for all 9 possible positions for orange head to use
    for (let position = 0; position < 9; position++){

      let x;
      let y;

      if (position < 6){

        let row = floor(position / 3);
        let col = position % 3;

        x = width * (0.25 + col * 0.25);
        y = height *(0.45 + row * 0.3) - 30;
      } else {
        let col = position - 6;

        x = width * (0.25 + col * 0.25);
        y = height * 0.60 -30;
        
      }

      let occupied = false;

      // Tracking of cyan heads (if need be)
      for(let mickey of mickeys){

        if (dist(x, y, mickey.x, mickey.y) < 65){
          occupied = true;
        }
        
      }

      // Check for yellow head (if need be)
      if (yellowMickey !== null){

        if (dist(x, y, yellowMickey.x, yellowMickey.y) < 65){
          occupied = true;
        }
        
      }

      if (!occupied){
        availablePositions.push({ x: x, y: y, position: position});
      }
      
    }

    // Orange head should only spawn if there's a free open-position
    if (availablePositions.length === 0){
      scheduleOrangeMickey();
      return;
    }

    let chosen = random(availablePositions);

    orangeMickey = new OrangeMickeyHead(chosen.x, chosen.y);
    orangeMickey.holeIndex = chosen.position < 6 ? chosen.position : -1;

    orangeSpawnTime = millis();
    
  }

  function spawnYellowMickey(){

    let availablePositions = [];

    // Keep track of yellow Mickey Head's 9 possible spots
    for (let position = 0; position < 9; position++){

      let occupied = false;

      // Check on the cyan Mickey heads
      for (let mickey of mickeys){

        let row = floor(position / 3);
        let col = position % 3;

        if (position < 6){

          let x = width * (0.25 + col * 0.25);
          let y = height *(0.45 + row * 0.3) - 30;

          if (mickey.x === x && mickey.y === y){
            occupied = true;
          }
          
        }
        
      }

      // Check on the red heads if necessary on behalf of yellow heads
      if (redMickey !== null){

        let redPositionX = redMickey.x;
        let redPositionY = redMickey.y;

        let x;
        let y;

        if (position < 6){

          let row = floor(position / 3);
          let col = position % 3;

          x = width * (0.25 + col * 0.25);
          y = height * (0.45 + row * 0.3) - 30;
        } else {
          
          let col = position - 6;

          x = width * (0.25 + col * 0.25);
          y = height * 0.60 - 30;
        }

        if ( x === redPositionX && y === redPositionY){
          occupied = true;
        }

        
      }

      if (!occupied){
        availablePositions.push(position);
      }
      
    }
    // decision-making on picking any available position
    let position = random(availablePositions);

    let x;
    let y;

    if (position < 6){

      let row = floor(position/3);
      let col = position % 3;

      x = width * (0.25 + col * 0.25);
      y = height * (0.45 + row * 0.3) - 30;
    } else {
      
      let col = position - 6;

      x = width * (0.25 + col * 0.25);
      y = height * 0.60 - 30;
    }

    yellowMickey = new YellowMickeyHead(x, y);
    yellowMickey.HoleIndex = position < 6 ? position : -1;

    yellowSpawnTime = millis();
     
  }

  function scheduleRedMickey(){

    // Decide random delay from 4 to 10 seconds
    nextRedSpawn = millis() + random(4000, 10000);
  }

  function scheduleOrangeMickey(){

    nextOrangeSpawn = millis() + random(4000, 10000);
  }

  function scheduleYellowMickey(){

    //Reminder to wait a few minutes (3 maximum at least) before next head appears
    nextYellowSpawn = millis() + yellowCooldown;
  }

  function drawGameOver(){

    // Set-up of the background color for Game Over screen
    background(0);

    //Set-up of game over message appearance
    textAlign(CENTER, CENTER);
    textFont("Press Start 2P");
    textSize(30);

    fill(255, 80, 180);
    text("FAIL!\nSORRY SON!", width / 2, height / 2);

    // Keep track of whether 6 seconds have finished being passed
    if(millis() - gameOverStartTime >= 6000){

      // Bring the player back to the main menu automatically
      gameState = "menu";

      playMenuMusic();
    }
    
  }

  function mousePressed(){
   if (gameState === "menu") {

     console.log("Mouse clicked! Game state:", gameState);

     // Making sure that PLAY button is activated
     if (
       mouseX > width / 2 - 110 &&
       mouseX < width / 2 + 110 &&
       mouseY > height - 170 &&
       mouseY < height - 90
     ){

       // Begin soundtrack after user interacts with menu
       userStartAudio();
       menuSoundtrack.play();
       

       

       gameState = "instructions";
     }
     
   }
     
   else if (gameState === "instructions"){

     // Making sure that GO TIME (start) button gets activated
     if (
       mouseX > width / 2 - 130 &&
       mouseX < width / 2 + 130 &&
       mouseY > height * 0.83 - 32.5 &&
       mouseY < height - 0.83 + 32.5
     ){

       // Have score be resetted within a new game
       score = 0;

       // Gameplay start
       gameState = "game";

       // cease main menu music
       menuSoundtrack.stop();

       // Begin gameplay music
       gameplayMusic.play();
       console.log("Attempted to start gameplay music!");
       
       spawnMickeys();

       // Reset scheduling of red Mickey head
       redMickey = null;
       scheduleRedMickey();

       // Reset yellow Mickey head for new game
       yellowMickey = null;

       // First yellow Mickey Head appearing after 10-20 seconds by random
       nextYellowSpawn = millis() + random(10000, 20000);

       // Reset orange Mickey head for new game
       orangeMickey = null;
       scheduleOrangeMickey();
       
       // cease menu music upon gameplay beginning
       if (menuSoundtrack.isPlaying()){
         menuSoundtrack.stop();
       }
       
     }
     
   }

   else if (gameState === "game"){

     // Be on lookout for player clicking on red Mickey head
     if (
       redMickey !== null &&
       redMickey.isClicked(mouseX, mouseY)
     ){

       // have gameplay music stop immediately
       gameplayMusic.stop();
       
       // Switch over to game over
       gameState = "gameover";

       //Setting put of the memory of when the player lost
       gameOverStartTime = millis();

       // Play sad trombone at precise moment
       gameOverSound.play();

       // Take away Red Mickey Head for game over to operate with ease
       redMickey = null;

       return;
     }

     // Check to see if player clicked on the orange Mickey head
     if (
       orangeMickey !== null &&
       orangeMickey.isClicked(mouseX, mouseY)
     ){

       // bring score back to zero
       score = 0;

       // Play sound effect indicating loss of score
       orangeSound.play();

       // Take out Mickey head right away
       orangeMickey = null;

       // get to scheduling the orange Mickey head's next pop-up moment
       scheduleOrangeMickey();

       return;
     }

     // Check to see if player clicked on the yellow Mickey head
     if (
       yellowMickey !== null &&
       yellowMickey.isClicked(mouseX, mouseY)
     ){

       // Award player with +30 points
       score += 30;

       // Play existing hit sound
       bonusSound.play();

       // Take away yellow Mickey head instantly
       yellowMickey = null;

       // Let cooldown (whose maximum is technically 3 minutes) begin
       scheduleYellowMickey();

       return;
     }
     
     //set-up of loop rountine regarding when a head gets hit/clicked
     for(let i = mickeys.length - 1; i >= 0; i--){

       if (mickeys[i].isClicked(mouseX, mouseY)){

         // Letting 5 points be added to scoreboard
         score += 5;

         // Letting "hit" sound effect be played
         hitSound.play();

         // Take away Mickey Head upon it being hit
         mickeys.splice(i, 1);

         // Quit checking the hit was landed and successful
         break;
         
       }
       
     }
     
   }
    
    
  }

class MickeyHead{

  // Making sure that the Mickey Heads are positioned
  constructor(x, y){
    this.x = x;
    this.y = y;
  }

  display(){

    // The 2 white Mickey ears set-up
    fill(255);
    circle(this.x - 25, this.y - 30, 35);
    circle(this.x + 25, this.y - 30, 35);

    // The cyan head of the Mickey head
    fill(0, 255, 255);
    circle(this.x, this.y, 65);
  }

  isClicked(mx, my){

    // Measurement of how far is the player from hitting the head
    let clickDistance = dist(mx, my, this.x, this.y);

    // Check to make sure the player indeed clicked inside of the head
    return clickDistance < 32.5;
    
  }
    
}

// Set-up of the red Mickey Head that borrows elements from the "MickeyHead" class function
class RedMickeyHead extends MickeyHead {

  display(){

    // Set-up of red Mickey head ears
    fill(255, 0, 0);
    circle(this.x - 25, this.y - 30, 35);
    circle(this.x + 25, this.y - 30, 35);

    // The circular head of the Mickey head
    fill(255, 0, 0);
    circle(this.x, this.y, 65);
  }
    
}

// Set-up of the yellow Mickey Head that borrows elements from the "MickeyHead" class function
class YellowMickeyHead extends MickeyHead{

  display(){
    // Set-up of yellow Mickey head ears
    fill(255, 225, 0);
    circle(this.x - 25, this.y - 30, 35);
    circle(this.x + 25, this.y - 30, 35);

    // Circular head of the Mickey head
    fill(255, 225, 0);
    circle(this.x, this.y, 65);
  }
  
}

// Set-up of the orange Mickey Head that borrows elements from the "MickeyHead" class function
class OrangeMickeyHead extends MickeyHead{

  display(){

    // Set-up of orange Mickey head ears
    fill(255, 140, 0);
    circle(this.x - 25, this.y - 30, 35);
    circle(this.x + 25, this.y - 30, 35);

    // Orange Mickey's circular head
    fill(255, 140, 0);
    circle(this.x, this.y, 65);
  }
  
}

If there was a part that I am proud of, it is definitely implementing the Mickey heads themselves, as well as the sound effects that accompany them! It is because of how I understood that most people like it when a game is as goofy as it possibly could be. It’s something about that hilarious energy that would cause people to continue playing for however long they need, especially if there’s sound effects resembling that of memes that almost anybody would recognize. Not to mention that I found myself laughing at the sound effects that each Mickey Head is accompanied with, which already proved to me that what I’m hearing is something that anybody would be delighted to hear as well. After all, a game is all about engagement and it can maybe be convincing for a user to keep on indulging in the humorous nature from within the game.

let menuSoundtrack;
let gameplayMusic;

menuSoundtrack = await loadSound("menuSoundtrack.mp3");
  gameplayMusic = await loadSound("gameplay_music.mp3");

 // Keep track of whether 6 seconds have finished being passed
    if(millis() - gameOverStartTime >= 6000){

      // Bring the player back to the main menu automatically
      gameState = "menu";

      playMenuMusic();

// Begin soundtrack after user interacts with menu
       userStartAudio();
       menuSoundtrack.play();
       

       

       gameState = "instructions";

 // Be on lookout for player clicking on red Mickey head
     if (
       redMickey !== null &&
       redMickey.isClicked(mouseX, mouseY)
     ){

       // have gameplay music stop immediately
       gameplayMusic.stop();
       
       // Switch over to game over
       gameState = "gameover";

       //Setting put of the memory of when the player lost
       gameOverStartTime = millis();

       // Play sad trombone at precise moment
       gameOverSound.play();

function playMenuMusic(){

    // Cease gameplay music if it's playing right now
    if (gameplayMusic.isPlaying()){
      gameplayMusic.stop();
    }

    // Start up menu music ONLY if it ain't playing at the moment
    if (!menuSoundtrack.isPlaying()){
      menuSoundtrack.loop();
    }
    
  }

  function playGameplayMusic(){

    // Cease menu music
    if (menuSoundtrack.isPlaying()){
      menuSoundtrack.stop();
    }

    // Start up gameplay music
    if (!gameplayMusic.isPlaying()){
      gameplayMusic.loop();
    }
    
  }

The biggest challenge for me, however, would definitely be the implementation of music in my game; this is often due to how even though I would have my downloaded mp3 songs uploaded within my project fair and square, the music would never seem to be activated. It was almost as if they weren’t recognized enough to even be played where I wanted them to be played in, despite me doing what I thought was correctly defining the variables/functions that would cause the music to be played (such as defining the function that was meant to play music during the gameplay as “gameplayMusic”).  This is when I knew that p5.js sure knew a thing or two about being strict with functions/variables and how to activate them properly. Which is why I have had to refer to some of the older example projects that were linked, as well as proofread my mistakes with the assistance of Ai so that I would see if I didn’t write something correctly or whatnot. Eventually, I was able to figure it out via switching certain commands with the ones that my program was able to read, such as how for my “gameplayMusic” variable, i was able to switch out “loop()” with “play()” so that it can understand better on where and how to play said-music. Before you know it, I was able to sort out an evolution of music within my game that would go as follows: main menu (no music) => instructions page (club music that is supposed to get people’s energy up and running for the game) = gameplay itself(another form of techno club music that will boost the already-risen energy of the player as they get to enjoying themselves in the game! It is these simple steps of build-up that I was able to debug and bring to life within my game.

All in all, I believe that despite the complicated strict lines I had to follow in order for p5.js to listen to me, this was quite the eventful project! Now, I still consider myself a giant personified rookie when it comes to game creation, but what I can say is that this may have given me the idea of what the whole process may technically be like. The only thing I wish I could have done is see if there could have been more Mickey heads that could have been added to the game to add even more value to the game. Sadly, I didn’t exactly have enough ideas, but at least four ideas are better than nothing, you know? Overall, these are nothing short but my thoughts of how the project was like for me.

Week 7: midterm project

Concept

Who Pays? is about the fight that happens at the end of every meal with my friends. Nobody wants to split the bill. Everyone wants to be the one who pays, and it turns into an actual race to get your card to the card machine first. So in this game, paying is winning.

The project starts as a short comic. Four pixel art frames I made tell the story of one lunch at Campus East. The food is gone, you casually reach for your uni card, everyone notices, and suddenly three real cards are out and pointed at the machine. The game happens inside that fourth frame. You hold the black card and race Eshaal and Eman by mashing Space, and whoever’s card touches the card machine first pays.

How it works

The sketch has three scenes, and a variable called scene decides whether draw() shows the title screen, the story or the race. Each story frame is a Panel object that knows its picture, its caption and the frame it came on screen. That’s all it needs to type the caption out one letter every two frames. The title screen is frame 1 blurred with grain on top, and it waits for Enter or a click before anything starts.

Each hand in the race is a Racer object. It holds the hand picture cut out of frame 4, where it starts, how far it has to travel to reach the card machine, and a progress number from 0 to 1. Pressing Space adds 0.045 to your progress, and Eshaal and Eman add a small random amount every frame. Where a card sits on screen is just its start position plus the distance times its progress.

Problems and what I’d improve

The hardest design problem was three cards racing toward one small card machine. If they all moved toward the same point, they’d pile on top of each other halfway through and you couldn’t tell whose card was ahead. So each hand got its own straight path, measured from frame 4: mine comes in from the right, Eshaal’s from the left and Eman’s from below, and each path ends at a slightly different spot on the machine. The cards only really meet at the very end. When they do overlap, the draw order decides who’s on top, and your card is drawn last so it’s always the one you can see.

Then I had to decide what “winning” actually means in a mashing game. My first thought was to check when a card entered the card reader’s area on screen. But the cards are tilted and come from three directions, so that area would be a different shape for each of them. It was much simpler to make each path end exactly where the card touches the machine. That way reaching the machine just means progress hits 1, and after every frame checkforWInner() looks for the first card that got there.

Most of the other problems came from the p5 web editor. An older version of the grain effect looped over every pixel on the screen, and the editor stopped it for running longer than 100 milliseconds and called it a “potential infinite loop”. Drawing a few thousand random dots each frame fixed that.

There’s still a lot I’d change. Eshaal and Eman add a random amount every frame, but over a whole race that averages out, so they move at an almost steady speed. Random bursts of speed would make them feel more like people. A tie goes to whoever is first in the racers list, which isn’t really fair. The speeds are also per frame, so on.

Code I am proud of

The part I’m proudest of is the arm stretching in the Racer class. It was the trickiest thing to get right, and you can see it every time the cards move.

// in the constructor, the forearm slice gets cut out of the hand picture once
this.armSlice = this.img.get(this.armX - this.x, this.armY - this.y, info.arm[2], info.arm[3]);

show() {
  // The forearm slice is drawn again and again, from where the hand started to where
  // it is now. Stacked up like that it looks like the arm is stretching toward the machine.
  for (let step = 0; step < this.progress; step += 0.01) {
    image(this.armSlice, this.armX + this.reachX * step, this.armY + this.reachY * step);
  }

  // hand and card go on top, moved along by however far they've got
  image(this.img, this.x + this.reachX * this.progress, this.y + this.reachY * this.progress);
}

When the hands first moved, they floated away from their arms and left a gap in the drawing. The fix was a thin slice of each forearm, taken from exactly where the hand was cut out, drawn over and over along the path behind the hand. Because the slice comes from the real drawing, the arm keeps its shading and outline, and it looks like everyone is actually leaning across the table.

Reflection

I made this game because of how every meal with my friends ends. In Pakistan, paying is hospitality. Covering the bill is how you show someone you care about them, and letting them pay can feel like you’ve failed as a host, even when nobody was hosting anyone. So nobody splits. Everyone reaches for their card at the same time, someone says put that away, someone tries to sneak their card to the cashier before the bill even comes, and it turns into a full fight that everyone is secretly enjoying.

I wanted the game to work the same way, so winning means you’re the one who pays. In most games you win something. Here you win by being the first to give your money away, and that’s honestly what it feels like at the table. Even the second frame, where you casually go for your uni card, is a move every one of us has tried at some point.

References

These Coding Train videos cover the parts of p5.js this project leans on.

Classes in ES6, for the Panel and Racer classes. https://thecodingtrain.com/tracks/code-programming-with-p5-js/code/6-objects/2-classes

Constructor Arguments with Classes, for giving each Racer its name, hand image and path. https://thecodingtrain.com/tracks/code-programming-with-p5-js/code/6-objects/3-constructor

Arrays of Objects, for the racers and panels arrays and looping over them. https://thecodingtrain.com/tracks/code-programming-with-p5-js/code/7-arrays/3-arrays-objects

Objects & Images, for each Racer drawing its own hand picture. https://thecodingtrain.com/tracks/code-programming-with-p5-js/code/7-arrays/8-objects-images

p5.js Web Editor: Uploading Media Files, for getting the frames, hands and sounds into the editor. https://thecodingtrain.com/tracks/code-programming-with-p5-js/code/7-arrays/upload-editor

random(), for Eshaal and Eman’s speed and the grain on the title screen. https://thecodingtrain.com/tracks/code-programming-with-p5-js/code/2-variables/4-random

while & for Loops, for the grain loop and the arm loop. https://thecodingtrain.com/tracks/code-programming-with-p5-js/code/4-loops/1-while-for

Loading and Playing, for loadSound(), play(), loop() and setVolume(). https://thecodingtrain.com/tracks/sound/sound/1-loading-and-playing

Adding Sound Effects, for the beep, approved and declined sounds. https://thecodingtrain.com/tracks/sound/sound/5-adding-sound-effects

Press Start 2P by CodeMan38, the pixel font. https://fonts.google.com/specimen/Press+Start+2P

Improve loop protection warning for slow loops, a forum thread that helped me understand the “infinite loop” warning. https://discourse.processing.org/t/improve-loop-protection-warning-for-slow-loops-that-are-not-infinite/49147

AI Usage

It was most useful when something broke or confused me. It found that the preload error came from the editor defaulting to p5.js 2.0, that my uploads weren’t loading because file names have to be typed out in full in quotes, and that the “infinite loop” was the editor’s 100 millisecond limit. When I switched to portrait frames, it worked out that my frame 4 was still landscape inside the file and rotated the cut outs and every hand position to match. When I didn’t understand the grain, it pulled the title screen apart into layers so I could see it’s just thousands of faint random dots drawn on top of the blurred frame. It also explained how frameCount controls the flicker, and gave me simple pseudocode for the moving hands (start position plus distance times progress), which is the line in show().

I also used gemini to help me generate the graphics, so like that is why the hands or fingers may not be complete haha.

Week 7 – Midterm Project: Save The Fish!

Concept

For my midterm project, I created a game where the player controls a fish swimming through a river. The main goal of the game is to survive for as long as possible. While swimming through the river, the fish encounters different food objects and garbage objects. The player needs to eat the food to keep the fish healthy while avoiding the garbage. The fish’s health drains constantly, so the player cannot just sit still, and eating three pieces of garbage kills the fish.

The game is also intended to communicate a message about pollution in freshwater environments. Pollution in rivers can seriously affect the aquatic creatures living in that ecosystem. By making this the main idea of the game, I want the player to experience this problem through the gameplay rather than simply being told about it. Every piece of garbage the fish touches costs it a life, so the player quickly feels how dangerous a polluted river can be.

Implementation

I organized the code using several classes, there’s a Fish class for the player, a RiverObject class for every piece of food and garbage, a Bubble class for the background decoration, and a Game class that basically controls everything. The Game class has three states: a home screen, the playing screen, and a game over screen. While playing, the player steers the fish with the arrow keys and has three lives (shown as red circles) and a health bar that slowly drains. Eating shrimp, small fish or algae restores health and gives points, while colliding with plastic, a bottle or a can costs one life. Instead of writing a separate class for each kind of food or garbage, every type is stored as data (its size, the points it gives and the health it restores). Sound effects play when the fish eats food, eats garbage and dies. Another feature implemented was a high-score system using the browser’s localStorage, so the high score is still there the next time the game opens. A New High Score message appears when the player beats it. Images were generated using META AI and I used Claude AI to help me understand certain problems and the materials shared in class better. It helped me learn which tools or approaches would be useful for the what I wanted to do. The materials shared in class were also very helpful. Claude was also used to generate sound effects garbage collision.

Code I am Proud of

One part I am proud of is how the game gets harder. At first the game felt boring and easy to play, so I decided to make it harder as the player’s score increases. The game calculates a difficulty level from the score (one level for every 25 points, up to a maximum of 7).

// Difficulty level: goes up by 1 every 25 points (max 7)
getLevel() {
  return min(floor(this.score / 25), 7);
}

That level is then added to the speed of every new object that is created:

let newObject = new RiverObject(
  width + 20,
  random(100, height - 50),
  objectType,
  random(2, 4) + this.getLevel() * 2,  // faster at higher levels
  category,
  this.images[objectType.type]
);

this.objects.push(newObject);

This allows the game to become progressively more difficult without requiring me to manually define a different speed for every level. Instead, the speed is calculated automatically whenever a new object is created. It also makes the difficulty system easier to adjust. The same level also shortens the delay between spawns, so the river stays busy even though objects cross the screen faster.

I am also proud of the health bar that I created which changes colour.

// How full the bar is (between 0 and 1)
    let ratio = constrain(this.health / MAX_HEALTH, 0, 1);

    // Green when healthy, yellow when low, red (flashing) when almost empty
    let barColor;

    if (ratio > 0.5) {
      barColor = color(70, 200, 90);
    } else if (ratio > 0.25) {
      barColor = color(240, 200, 50);
    } else {
      barColor = color(230, 60, 50);
      // Flash lighter and darker 4 times a second
      if (floor(millis() / 250) % 2 === 0) {
        barColor = color(255, 130, 120);
      }
    }

The colour of the bar changes from green to yellow and then red as the fish loses health. When its health becomes critically low, the bar starts flashing to create a sense of urgency. I implemented this using the constrain() function to keep the health ratio between 0 and 1, conditional statements to determine the colour, and millis() to create the flashing effect every 250 milliseconds. I am proud of this feature because it provides clear visual feedback, allowing players to quickly recognize the fish’s condition and react before it runs out of health.

Reflection

The hardest part was balancing the difficulty. My first version only made the objects faster as the score went up, but then the player often saw just one object on the screen at a time. New objects were still appearing every 1.5 seconds, while each one crossed the screen much faster than before. I fixed this by also reducing the spawn delay as the level increases (with a minimum of 0.3 seconds) and by capping the level at 7 so the game does not become impossible. It was also challenging to decide how the game should end. I first thought of running the game for a fixed time then the users could compare their scores, but then I decided to have a high score system so I added two ways in which the game ends. One when the health bar goes down and another is when the fish has collided with 3 trash items

If I was to improve this game in the future, I would add more variety to the gameplay. For example, I could introduce different river environments, additional types of pollution, or special objects that temporarily help the fish. The game would also be more interesting if the users could enter their username before playing and at the home screen the top 3 high scores with its username would be shown. This would make the game feel more competitive, as everyone would want to be on the leader board. Another improvement would be to include a short educational message or fact about freshwater pollution at the end of each game, helping players to understand real-world environmental issues. Overall, one of the most valuable things I learned from this project was the importance of organizing code using classes and methods. Separating the fish, river objects, bubbles, and overall game logic made the program much easier to manage.

Midterm Project

Concept

Back in school, they wouldn’t give us the WiFi password, so whenever I opened a browser I got stuck on the “No internet” page with the little dinosaur. With nothing else to do, my classmates and I would end up playing the offline dino game, and it got really competitive as we tried to beat each other’s high scores. That memory is where Desert Runner started. I wanted to keep the same simple, addictive feeling of the dino game but make it my own by bringing my culture into it. Instead of a dinosaur jumping over cacti, the main character is an Emirati man in a kandura, ghutra and agal, and instead of cacti he has to get past animals from the UAE desert.

I also wanted the game to have a goal instead of running forever, so I gave it a story. The character is an NYUAD student who leaves campus on Saadiyat Island and gets lost in the desert. Every road takes him further away, first to Sweihan, then Al Ain, then all the way to Liwa, before he finally finds the long road back to Saadiyat. If you reach 2000 points, an NYU Abu Dhabi building appears, he runs up to the door and you win. If you crash, you’re “lost in the desert” and start again from Saadiyat.

How to Hack the Hidden Google Chrome Dinosaur Game

The NYU Abu Dhabi Campus - NYU Abu Dhabi

 

How it works and what I’m proud of

The game is plyed in the following. The player never actually moves forward. He stays in the same spot while the world scrolls toward him, the same trick the dino game uses. Every frame the game updates the speed, score, jumping and obstacles, checks for collisions, and then redraws everything. The jump uses simple gravity. When you press jump he gets an upward speed, and every frame gravity pulls a bit of it away, so the jump curves up and comes back down naturally. Instead of counting frames, the movement uses the time between frames, so the game runs at the same speed on fast and slow computers. The game also slowly speeds up, and the gap between animals grows with the speed, so there is always enough room to land and jump again.

The part I’m most proud of is that everything is drawn with code. There are no images at all. The Emirati man, the snake, oryx, camel and falcon are all built from ellipses, lines, triangles and custom shapes. Animation comes from a sine wave, which makes his arms and legs swing in opposite directions, his ghutra flap in the wind, the snake slither and the falcon flap its wings. Each location also has its own look. Saadiyat has the beach, the sea, dhows and the Louvre dome. Sweihan has camel farms and ghaf trees. Al Ain has Jebel Hafeet, an old fort with the UAE flag and date palms, and Liwa has a sunset over huge red dunes. The scenery is drawn in layers that move at different speeds, so far things move slowly and close things move fast, which gives the world depth. The places also follow the real route you would drive from Saadiyat, and a bar at the top shows how far along the journey you are.

I’m also proud of some game design decisions. The falcon flies at head height, so you have to duck instead of jump. If you try to jump, it swoops up after you, which makes sense for a hunting bird and makes ducking the clear choice. Collisions use invisible boxes that are slightly smaller than the drawings, so you only lose when you clearly hit something. The best score is saved in the browser so it stays when you retry, which brings back the competitive feeling from school, and it resets when you finally make it home so every journey starts fresh. The menu, tutorial and controls screens make it easy for someone new to understand the story and the animals before they play.

. Same speed on every computer. So the movement here is based on the time between frames, and not on counting frames.

// timeStep keeps the game the same speed on fast and slow computers
let timeStep = min(deltaTime / 16.667, 3);

 

. Jumping with gravity. Gravity takes away a little upward speed every frame, so the jump curves up and comes back down.

function updatePlayer(timeStep) {
  if (!player.onGround) {
    player.verticalSpeed -= GRAVITY * timeStep;
    player.height += player.verticalSpeed * timeStep;
    // Landed
    if (player.height <= 0) {
      player.height = 0;
      player.verticalSpeed = 0;
      player.onGround = true;
    }
  }
}

Areas for improvement and problems I ran into

I ran into several problems along the way. The first version of the falcon felt unfair, because it looked low enough to jump over but jumping always made you lose. I fixed this by raising it to head level and making it follow you up when you jump, so losing actually looks right. The music took two to three seconds to start and the crash sound was delayed, because the audio was being streamed like a video. Loading the sound files fully into memory at the start fixed both. My high score also showed 2316 out of 2000 because it had been saved back when the goal was 2500, so I changed how the score is saved and capped it at the goal.

There are still things I would like to improve. I’d like to add lives, so the player gets a few chances before the game ends instead of losing everything on the first hit. I’d also like to make the death scene more interactive, for example by showing an animation of the man falling or the animal reacting, instead of the game just stopping. I’d like to give each location its own animals too, like more camels in Sweihan or snakes in Liwa. Right now the high score only saves on your own computer, so a shared online leaderboard would bring back the real competition with friends. I’d also like to add a pause button and make the controls work better on phones.

References

.Inspiration, the Chrome offline dinosaur game.

.Reference images: Google Images searches for “UAE desert” and cartoon Emirati characters, used for inspiration on clothing, colors and scenery. The final character is my own original design.

.MDN Web Docs, Web Audio API; loading and playing sound files from memory.

.Music “Desert Caravan” by Walen, Free To Use (freetouse.com).

.Distances used to order the route: Bayut Sweihan guide, Wikipedia: Sweihan, Rome2rio Al Ain to Liwa and Rome2rio Liwa to Abu Dhabi.

.The Coding Train, Coding Challenge #147: Chrome Dinosaur Game; how to build the Chrome dino game in p5.js, with a jumping player and obstacles.

.Jumping and gravity: Programming a Retro-Style video game in P5.js, Part 3: simulating gravity and Lesson 2: Simulating Gravity with JavaScript and P5JS: how speed and gravity change the character’s position every frame so he falls back down after jumping.

.Changing scenes: Changing scenes P5 JS: switching between the menu, tutorial, game over and win screens using a game state variable.

.Collision detection: The Coding Train, 8.4 Class and Objects: Collision Detection: checking when the player’s hitbox touches an animal.

.Animation with sine waves: The Coding Train, 3.5 Simple Harmonic Motion (Nature of Code): using sin() to make the arms and legs swing, the falcon’s wings flap and the snake slither.

.Sand dunes and mountains: The Coding Train, I.2: Perlin Noise and p5.js: using noise() for the smooth shapes of the dunes and Jebel Hafeet.

.Saving the high score: Local Storage in JavaScript with p5.js: saving the best score in the browser so it stays after you close the page.

AI  helped me make the Tutorial screen feel more alive. At first I thought of the Tutorial as a page of instructions, but I wanted players to actually see the animals they’d face, not just read about them. Claude explained that each animal’s drawing function takes an animation number, and that number decides where the legs are in their step, how the snake’s body is curved and where the falcon’s wings are. During the game this number goes up as the animal moves. On the Tutorial screen it uses p5.js’s built-in framecount instead, which goes up by 1 every frame. Because the number keeps changing, the snake slithers, the oryx and camel walk in place, and the falcon flaps its wings, all without moving across the screen. This showed me how one variable can bring a still image to life, and that I can reuse the same drawing functions on different screens just by changing the number I give them.