Midterm Project: The “SpectroLoom”

Concept

For my midterm project, I thought of making something unique, which seemed like art for the casual viewers, but on closer inspection would be seen as a form of data. For this, I decided to make a circular spectrogram, i.e, visualizing sound in circular patterns. That’s when I saw an artwork on Vimeo, which visualized sound in a unique way:

Using FFT analysis, and the concept of rotating layers, I decided to recreate this artwork in my own style, and go beyond this artwork, thus, creating the SpectroLoom. I also decided that since most people sing along or hum to their favourite tunes, why not include them in the loop too?

At its core, SpectroLoom offers two distinct modes: “Eye of the Sound” and “Black Hole and the Star.” The former focuses solely on the auditory journey, presenting a circular spectrogram that spins and morphs in harmony with the music. The latter introduces a dual-layered experience, allowing users to sing along via microphone input, effectively merging their voice with the pre-loaded tracks, thus creating a sense of closeness with the song.

The Code/Science

Apart from FFT analysis, the project surprisingly used a lot of concepts related to “Relative Angular Velocity”, so that I could make the sketch behave in the way I want it to be. Using FFT analysis, I was able to get the amplitude of every frequency at any given point of time. I used these values to make a linear visualizer on a layer. The background canvas is rotating at an angular velocity of one revolution for the song’s duration in anti-clockwise direction, and the visualizing layer is rotating in the opposite direction (clockwise), making it seem that the linear visualizer is stationary because the Relative Angular Velocity is “Zero”. The other user layer, which have the user’s waveform is also doing the same, but uses the mic input as the input source for the FFT Analysis (and is only in the second mode).

Also, once the user finishes the song, they can again left click for restarting the same music. This is done by resetting the angle rotated by the layer to “Zero” after a complete revolution and clearing both song visualization layer and the User input layer.

// Visualizer screen drawing function for "Black Hole and the Star" mode
function drawBlackHoleAndStar() {
  if (song.isPlaying()) {
    background(0);

    // Get the frequency spectrum for the song
    let spectrumA = fft.analyze();
    let spectrumB = spectrumA.slice().reverse();
    spectrumB.splice(0, 40);

    blendAmount += colorBlendSpeed;
    if (blendAmount >= 1) {
      currentColor = targetColor;
      targetColor = color(random(255), random(255), random(255));
      blendAmount = 0;
    }

    let blendedColor = lerpColor(currentColor, targetColor, blendAmount);

    // Draw song visualizer
    push();
    translate(windowWidth / 2, windowHeight / 2);
    noFill();
    stroke(blendedColor);
    beginShape();
    for (let i = 0; i < spectrumB.length; i++) {
      let amp = spectrumB[i];
      let x = map(amp, 0, 256, -2, 2);
      let y = map(i, 0, spectrumB.length, 30, 215);
      vertex(x, y);
    }
    endShape();
    pop();

    layer.push();
    layer.translate(windowWidth / 2, windowHeight / 2);
    layer.rotate(radians(-currentAngle));
    layer.noFill();
    layer.colorMode(RGB);

    for (let i = 0; i < spectrumB.length; i++) {
      let amp = spectrumB[i];
      layer.strokeWeight(0.02 * amp);
      layer.stroke(amp, amp, 255 - amp, amp / 40);
      layer.line(0, i, 0, i);
    }
    layer.pop();
    
    var userSpectrum = micFFT.analyze()

    userLayer.push();
    userLayer.translate(windowWidth / 2, windowHeight / 2);
    userLayer.rotate(radians(-currentAngle));
    userLayer.noFill();
    userLayer.colorMode(RGB);

    for (let i = 0; i < userSpectrum.length; i++) {
      let amp = userSpectrum[i];
      userLayer.strokeWeight(0.02 * amp);
      userLayer.stroke(255 - amp, 100, 138, amp / 40);
      userLayer.line(0, i + 250, 0, i + 250); // Place the user imprint after the song imprint
    }

    userLayer.pop();

    push();
    translate(windowWidth / 2, windowHeight / 2);
    rotate(radians(currentAngle));
    imageMode(CENTER);
    image(layer, 0, 0);
    image(userLayer, 0, 0);
    pop();
  
    currentAngle += angularVelocity * deltaTime / 1000;

    if (currentAngle >= 360) {
      currentAngle = 0;
      
      userLayer.clear();
      layer.clear();
    }

    let level = amplitude.getLevel();
    createSparkles(level);

    drawSparkles();
  }
}

Also, there is the functionality for the user to restart too. The functionality was added via the back function. This brings the user back to the instruction screen.

function setup(){
...
  // Create back button
  backButton = createButton('Back');
  backButton.position(10, 10);
  backButton.mousePressed(goBackToInstruction);
  backButton.hide(); // Hide the button initially
...
}

// Function to handle returning to the instruction screen
function goBackToInstruction() {
  // Stop the song if it's playing
  if (song.isPlaying()) {
    song.stop();
  }
  
  // Reset the song to the beginning
  song.stop();
  
  // Clear all layers
  layer.clear();
  userLayer.clear();

  // Reset mode to instruction
  mode = "instruction";
  countdown = 4; // Reset countdown
  countdownStarted = false;

  // Show Go button again
  goButton.show();
  blackHoleButton.show();
  songSelect.show();
}

The user also has the option to save the imprint of their song via the “Save Canvas” button.

// Save canvas action
function saveCanvasAction() {
  if (mode === "visualizer") {
    saveCanvas('rotating_visualizer', 'png');    
  }
  if (mode === "blackhole") {
    saveCanvas('user_rotating_visualizer', 'png')
  }
}

Sketch

Full Screen Link: https://editor.p5js.org/adit_chopra_18/full/v5S-7c7sj

Problems Faced

Synchronizing Audio with Visualization:
    • Challenge: Ensuring that the visual elements accurately and responsively mirror the nuances of the audio was paramount. Variations in song durations and frequencies posed synchronization issues, especially when dynamically loading different tracks.
    • Solution: Implementing a flexible angular velocity calculation based on the song’s duration helped maintain synchronization. However, achieving perfect alignment across all tracks remains an area for refinement, potentially through more sophisticated time-frequency analysis techniques.
Handling Multiple Layers and Performance:
      • Challenge: Managing multiple graphics layers (layer, userLayer, tempLayer, etc.) while maintaining optimal performance was intricate. Rendering complex visualizations alongside real-time audio analysis strained computational resources, leading to potential lag or frame drops.
      • Solution: Optimizing the rendering pipeline by minimizing unnecessary redraws and leveraging efficient data structures can enhance performance. Additionally, exploring GPU acceleration or WebGL-based rendering might offer smoother visualizations.
Responsive Resizing with Layer Preservation:
    • Challenge: Preserving the state and content of various layers during window resizing was complex. Ensuring that visual elements scaled proportionally without distortion required meticulous calculations and adjustments.
    • Solution: The current approach of copying and scaling layers using temporary buffers serves as a workaround. However, implementing vector-based graphics or adaptive scaling algorithms could provide more seamless and distortion-free resizing.

Midterm Project

Concept and Inspiration:

I wanted to share the inspiration behind my game. It all started when I delved into the captivating world of folklore, specifically the stories of Sindbad the Sailor and Behula from Bangladeshi culture. Sindbad’s adventurous spirit, sailing through the vast oceans, I mean, who doesn’t love a good tale of adventure on the high seas?

Then there’s Behula, a fierce and determined woman who braves the ocean’s challenges to bring back her husband. Her journey is filled with trials and tribulations, showcasing strength, love, and resilience. These stories are so rich and deep, and I wanted to weave that essence into my game.

As I started to brainstorm, I thought, “Why not create a surfer game set against this backdrop?” The ocean is such a dynamic environment, and surfing adds an exciting twist. I wanted to capture the thrill of riding the waves while subtly nodding to these legendary tales.

Of course, I realized that not everyone might connect with the heavier themes of folklore, especially younger audiences. So, I decided to give the game a fun, cartoonish vibe. This way, it can appeal to all ages while still honoring those timeless stories. It’s all about adventure, overcoming challenges, and having a great time on the waves!

Code Explanation:

In my game, I’ve focused on creating an engaging surfing experience, and the wave mechanics play a crucial role in bringing that to life. There are some key elects that I would like to mention, especially how I generate those smooth, dynamic waves that define the gameplay.

1. Wave Creation

One of the standout features of my game is how I create the waves. I use a combination of beginShape() and endShape()to draw the wave’s outline:

beginShape();
let xOffset = waveXOffset;
for (let x = 0; x <= width; x += 10) {
    let y = map(noise(xOffset, yOffset), 0, 1, height / 2 - waveAmplitude, height / 2 + waveAmplitude);
    vertex(x, y);
    xOffset += waveFrequency;
}
endShape(CLOSE);
  • Creating Vertex Points: Inside this loop, I utilize the vertex() function to establish a series of points that define the wave’s shape. By iterating across the entire width of the canvas, I can create a flowing wave profile that enhances the surfing experience.
  • Using Perlin Noise: The magic happens with the noise() function. I chose Perlin noise because it generates smooth, natural variations, making the waves look more realistic. Unlike random values that can create jarring changes, Perlin noise ensures that the wave transitions are fluid, which adds to the game’s aesthetic appeal.
  • Mapping Values: I then use the map() function to rescale the noise output, allowing me to set the wave height within specific bounds. By centering the waves around the middle of the canvas (height / 2), I ensure they oscillate up and down, making the gameplay more visually engaging.2.

    2. Player Interaction with Waves

    The way the waves interact with the swimmer is equally important. I calculate the wave’s height and adjust the swimmer’s position accordingly:

    if (isJumping) {
        verticalVelocity += gravity;
        swimmerY += verticalVelocity;
        if (swimmerY >= waveY - swimmerHeight / 2) {
            swimmerY = waveY - swimmerHeight / 2;
            isJumping = false;
            verticalVelocity = 0;
        }
    } else {
        swimmerY = waveY - swimmerHeight / 2;
    }
    • Jump Mechanics: I implement a jumping mechanic where the swimmer’s vertical position changes based on gravity and jump forces. When the swimmer is in the air, I adjust their position based on the wave height, allowing them to ride the wave realistically.
    • Wave Height Adjustment: By ensuring the swimmer’s position is linked to the wave’s current height, I can create a seamless experience where the player feels like they are truly surfing on the waves.3. Window Resizing and Obstacles: 
      • The windowResized() function allows the canvas to resize dynamically if the window size changes, maintaining the game’s responsiveness.
        • Instantiation of Obstacles: Every time spawnObstacle() is called, a new instance of the Obstacle class is created and added to the obstacles array. This ensures that there are multiple obstacles on the screen for players to avoid, making the gameplay more challenging.
        • Continuous Challenge: By calling this function regularly within the main game loop (in the playGame function), I ensure that obstacles keep appearing as the player progresses. This continuous spawning simulates a moving ocean environment, where new challenges arise as players navigate the waves.
        • P5.js Sketch:

      Features  and Game Mechanics:

      • The game kicks off with an engaging start screen featuring the title “Surfer Game”, inviting players to click to begin their surfing.
      • Players control a lively surfer character using intuitive keyboard commands, specifically the spacebar to jump over incoming obstacles like sharks, enhancing the gameplay’s interactivity.
      • As players navigate through the waves, they encounter a dynamic wave system that adjusts in amplitude and frequency, creating a realistic surfing experience. This effect is achieved through Perlin noise, giving the waves a smooth, natural movement.
      • Collectible coins are scattered throughout the waves, adding an exciting layer of challenge. When the player collides with a coin, it disappears, contributing to the score, which is displayed on the screen.
      • The game includes a health mechanic where if the surfer collides with an obstacle, such as a shark, the game transitions to a “Game Over” state, prompting players to either restart or return to the main menu.
      • Upon reaching specific milestones, such as collecting a set number of coins, players are rewarded with a “Level Up” screen that highlights their achievements and encourages them to continue.
      • Players can easily restart the game after it ends by clicking anywhere on the screen, offering a seamless transition between attempts.

      Additional AudioVisual:

      • Sound effects enhance the immersive experience, with cheerful tunes playing when coins are collected and exciting sound bites when the surfer jumps over obstacles.
      • Background music plays continuously throughout the game, creating an engaging atmosphere. Players can enjoy a unique soundtrack that fits the theme of the game, enriching their adventure.
      • The graphics have a cartoonish style, making it appealing for all age groups while still paying homage to the folklore inspirations behind the game.

Reflection and Challenges

When I first set out to create a game, my vision was to develop something based on neuroscience, exploring complex concepts in an engaging way. However, as I delved deeper into that idea, I realized it was more challenging than I anticipated. I then pivoted to creating a 2048 game, but that also didn’t quite hit the mark for me. I felt it lacked the excitement needed to captivate players of all ages.

This experience taught me a valuable lesson: sometimes, taking a step back can provide clarity. Rather than getting bogged down in intricate designs, I opted for a simpler concept that would appeal to a wider audience. Thus, I decided to create an ocean-themed surfing game, inspired by folklore like Sindbad the Sailor and the tale of Behula from Bangladeshi culture.

I see a lot of potential for upgrading this game. Adding a storyline could further engage young players and introduce them to fascinating narratives from folklore. Additionally, I plan to enhance the wave mechanics by incorporating realistic gravity effects, making the surfing experience more immersive. These improvements could really elevate the gameplay and provide an enjoyable adventure for everyone.

Midterm Project – Serving Rush

Concept

When visiting different cafes, I often think how challenging it is for the waiters to keep in mind the orders of all people at the table – quite often they would place a dish your friend ordered in front of you instead, and then you will just exchange the plates by yourself. Not a big deal, right?  But visitors rarely think about the actual pressure in this kind of working environment – time, tastes, and preferences count for every customer.

I have decided to create a mini game that allows the player to experience how tricky it can actually be to serve the dishes during a rush hour. There is an extra challenge – the user is accompanying a table of two people on their first date, one of them is vegan and another loves meet. Will everything go as planned?

Sketch

https://editor.p5js.org/am13870/sketches/3_efwXDWQ

Highlight of the code

The most challenging part that I have managed to implement into my code was the functionality of the plates and the dishes on them. The falling dishes get attached to plate when caught by the user, and then the plate is dragged to the edge of the table. Vegetable-based dishes have to be dragged to the left, to Leonie, and meat-based dishes have to be dragged to the right, to Santi. If the player serves the dishes correctly, they get a tip – and I they don’t, they lose it.

Managing the interdependence of these objects was very tricky, but I have managed to defined specific functions within the classes so that everything appears and disappears when needed. Furthermore, setting up the winning and loosing conditions took time, so I have followed the guidelines from the class slides to add navigation through the screens.

  display() {
    image(plate2Patterned, this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2);

    // displaying the dish attached to the plate
    if (this.attachedShape) {
      this.attachedShape.x = this.x;
      this.attachedShape.y = this.y;
      this.attachedShape.display();
    }
  }

  mousePressed() {
    let d = dist(mouseX, mouseY, this.x, this.y);
    if (d < this.radius) {
      this.dragging = true;
      this.offsetX = this.x - mouseX;
    }
  }

  mouseReleased() {
    this.dragging = false;
  }

  attachShape(shape) {
    if (this.attachedShape === null) {
      this.attachedShape = shape;
    }
  }

  resolveShape() {
    if (this.attachedShape.type === 'vegetable' && this.x - this.radius <= 50) {
      score += 1;
    } else if (this.attachedShape.type === 'vegetable' && this.x + this.radius >= 750) {
      score -= 1;
    } else if (this.attachedShape.type === 'meat' && this.x + this.radius >= 750) {
      score += 1;
    } else if (this.attachedShape.type === 'meat' && this.x - this.radius <= 50) {
      score -= 1;
    }

    // removing the dish as it reaches the end
    this.attachedShape = null;
  }
}
Reflection

I have started the project by developing the “physics” part, so that all objects in a simplified style would move correctly. For example, the dishes were drawn as triangles and squares at first. After finalising this part, I have moved on to adding the detailed visuals – this was not the easiest part as well, but I enjoyed developing a certain aesthetic of the game.

I started the design stage by going back to the original reference for the game idea, the photograph by Nissa Snow, which depicts a long table with dishes on it. Using Adobe Illustrator, I have created the table for my game, adding and editing images of objects that follow an artistic, slightly random, almost incompatible aesthetic. Then I used Procreate to draw the introduction screen, the victory, and the loss slides that are shown depending on the outcome of the game. The illustrations were created in a minimalistic manner in order not to clash with clutteredness of the table.

Future improvements

To develop this mini game further, I would add more conditions for the player – for example, new sets of guests can come and go, their dietary preferences can change. This would require implementation of several more arrays and functions to set up the food preferences, and I think this is an interesting direction to dive into.

MIDTERM

CONCEPT:
For my midterm project, I decided to combine my two favorite things together, SpongeBob and my childhood game Geometry Dash (which was my first inspiration for the game).
I decided to be more creative and create my own version of geometry dash using Spongebob as my main theme. Furthermore, instead of jumping over obstacles, you have to jump over SpongeBob characters.
The main goal of the game is to score as many points as possible by avoiding colliding with an obstacle; it’s pretty simple. I also added a twist to it; there’s a feature where you can fuel up the power bar by gaining more points, which leads to a rocket mode effect where you can collect double points but instead of jumping, you’re flying. For the characters, I decided just to use png images online, which I will attach to the website at the bottom; however, to incorporate shapes and colour, I decided to use shapes and gradients to create the theme of the background, including the burgers and jellyfish. I also used a Spongebob font for the text to add more to the aesthetic. To organize my codes, because at some point it got messy, I decided to create multiple files, for functions and classes, which made it a lot easier as I knew where everything was and it was most helpful in debugging anything if there was an error.

HIGHLIGHT:
The code I’m most proud of is probably the jellyfish part of the game because it handles more than one thing like spawning, moving, and removing jellyfish, while also checking for player collisions. It also has conditional behavior since the jellyfish can only cause the game to end when the player is in rocket mode. I had to redo the code multiple times as there were a lot of errors in the beginning and I had to update multiple loops. Additionally, it depends on variables like `isRocketMode` and `gameOver` from other parts of the game, which makes it more complicated to manage since it must stay in sync with the overall game.
here is the code:

function updateJellyfishObstacles() {
  // Spawn new jellyfish obstacles at intervals
  if (frameCount % jellyfishInterval === 0 && jellyfishObstacles.length < maxJellyfish) {
    let jellyfishY = random(70, height - 80);
    let jellyfish = new Jellyfish();
    jellyfish.y = jellyfishY;
    jellyfishObstacles.push(jellyfish);
  }
   // Update jellyfish obstacles and handle rocket mode collisions
  for (let i = jellyfishObstacles.length - 1; i >= 0; i--) {
    jellyfishObstacles[i].move();
    jellyfishObstacles[i].show();
    
    // Remove off-screen jellyfish
    if (jellyfishObstacles[i].offScreen()) {
      jellyfishObstacles.splice(i, 1);
      continue; // Move to the next jellyfish
    }
    
    // Only trigger game over if the player hits a jellyfish while in rocket mode
    if (jellyfishObstacles[i].hits(player)) {
      if (isRocketMode) {
        deathSound.play();
        gameOver = true;
      }
      
    }
  }
}

 

IMPROVEMENTS:
In the future, I would probably like to add more elements to the game as it gets repetitive. Also, if I had more time I would fix this upside-down section of the game, as I feel like it looks odd in some sort of way, since the obstacles are upside down but not the player. Moreover, I would also improve the way the obstacles are shown in the game, as I fear they aren’t visually clear or hurt the eyes when you look at it too long, and it is because its moving fast, however, if its too slow, the game would be easier.

Here is the game:

 

REFRENCES:
https://www.jsdelivr.com/package/gh/bmoren/p5.collide2D. (my collide reaction HTML)
https://www.fontspace.com/category/spongebob (font)
https://www.pngegg.com/en/search?q=spongebob (all my images)

Midterm Project: Collect the Shells!

Concept.

As soon as I figured it out that I want my midterm project to be a mini-game, there was no other option but to make something I would really enjoy playing myself. The “treasure hunt” mini-game was something that I really liked playing online as a child. As a fan of Lilo & Stitch animated feature, I had little doubt about the decision to make this version of the game revolve around my favourite characters.

The backstory of the game is preparation for Lilo’s birthday celebration: Stitch has to collect shells in order to tinker a bracelet for her beloved friend, avoiding the dead fish bones on his way. The game uses timers (10 seconds to memorize the position of the bones & 4 minutes to collect the shells) as well as a counter for shells throughout the game.

I really wanted this game to be helpful to train memorisation, as I know it might be a struggle for quite a few nowadays (including me) to memorise information. I truly hope that the players will get immersed into this game and have the urge to play it over and over again, so it will turn out not only as a simplistic entertaining experience but also as a good exercise for the mind.

Highlights & Reflections.

The most challenging part of the code was to make the shells & fishes appear exactly on the sandbox grid, because I accidentally made the wrong starting positions for the grid itself at the very beginning, which I had to figure out only later on.

My favorite part was working on the sound effects and other UI-experience functions (such as the winning & losing signs). The particular part of the code I am proud of is working with timers for the first time, because there are many conditions to take into consideration when resetting both timers and making sure they do not overlap one onto another:

// CHECKING IF SECOND TIMER IS :00 // 
if (isHuntTimerActive && huntTimerValue === 0) {
stitchX = initialX;
stitchY = initialY;
showLosingSign = true;
isHuntTimerActive = false; // Stop the hunt timer
canMove = false; // Disable Stitch movement when timer hits 0
}

if (timerValue > 0) {
// CASE 1: TIMER IS STILL RUNNING (NEED TO SHOW FISH ICONS)
for (let i = 0; i < cols; i++) {
for (let j = 0; j < rows; j++) {
sandboxes[i][j].display();
}
}

for (let i = 0; i < fishes.length; i++) {
fishes[i].display();
checkFishCollision(fishes[i], i);
}

for (let i = 0; i < shells.length; i++) {
shells[i].display();
checkShellCollision(shells[i], i);
}
} else {
// CASE 2: TIMER IS :00, DRAW FISH ICONS BEHIND THE GRID
for (let i = 0; i < fishes.length; i++) {
fishes[i].display();
}

for (let i = 0; i < cols; i++) {
for (let j = 0; j < rows; j++) {
sandboxes[i][j].display();
}
}

// CHECK IF STITCH STUMBLES UPON THE FISH ICONS //

for (let i = 0; i < fishes.length; i++) {
checkFishCollision(fishes[i], i);
}

for (let i = 0; i < shells.length; i++) {
shells[i].display();
checkShellCollision(shells[i], i);
}
}

The Game.

Future improvements:

    • It would be nice to have the game in the full screen mode, but due to the presence of grid in the game, I didn’t manage to figure out how to achieve this without any distortions.
    • There is a minor bug in the game that I would like to fix in the future: it’s the fact that the randomly generated fish and shell icons can overlap and be drawn one on top of another, which makes it impossible for user to win the game. As for now, the suggestion is to simply restart the game.
    • Last but not least, I would like to have a higher resolution images in the game in the future. Honestly, I was a bit scared to upload high quality images as I had the game crashed over and over again at the beginning of my work on it. Nonetheless, I believe that in future I will find a way to make the resolution higher (by drawing specific images right in the p5 editor, e.g. the pop-up window with instructions).

MIDTERM

CONCEPT:

The idea for my project initially came from my nostalgia for Fruit Ninja.

Fruit Ninja Classic - Halfbrick

This was a game I enjoyed during my childhood, and I wanted to capture that excitement in my own way. I began by recreating the core mechanics: objects falling from above, mimicking the action of fruits being thrown up in Fruit Ninja. The player interacts by catching these items, with some scoring points and others, like the scorpions, leading to an instant loss. It took me a while to fine-tune these functions and get the timing, speed, and interactions to feel as responsive and engaging as the original game. At first, I struggled with the mechanics, as I wanted the motion and flow to feel natural and intuitive, just like in Fruit Ninja.

After a lot of trial and error, I was able to get the objects falling at varying speeds and from random positions, which gave the game a sense of unpredictability and challenge. I also added a cutting effect, just like in the game. This process helped me understand the importance of small details in game development, such as timing and object positioning. I also experimented with different speeds and sizes to make the game challenging yet enjoyable. The initial version was simple, but it felt exciting to see it come to life and mirror the familiar feeling of Fruit Ninja while incorporating my own twist. Here’s how it came out:

However, as I progressed, I realized that directly copying Fruit Ninja wouldn’t fully reflect my own creativity or bring anything new to the experience. This led me to reflect on my childhood memories. I recalled family trips to the desert in the UAE, where we would snack on dates and sweets under the open sky. I remembered the joy and the relaxed environment, but also my fear of scorpions, which were common in the desert. It struck me that this blend of joy and tension could provide a compelling twist to the game. I decided to adapt Fruit Ninja’s concept to incorporate elements of these desert trips. Instead of slicing fruits, the player would catch falling dates and sweets in a basket to score points, which was the cultural twist I wanted to add. Dates and traditional sweets, which are common in Emirati gatherings, served as the main items to catch, reflecting my childhood memories of trips to the desert with family. However, there was also a twist that added suspense to the game: scorpions. Just as scorpions are a real concern in the desert, they posed a threat in the game. If the player accidentally catches a scorpion, the game instantly ends, mirroring the risk and danger associated with encountering one in real life. To add more depth, I included power-ups like coins for extra points and clocks to extend the timer. These power-ups fall less frequently, requiring the player to remain alert and responsive. This combination of culturally inspired items, the risk of game-ending threats, and occasional rewards created a gameplay experience that balances fun and challenge. The game’s aesthetic, from the falling items to the traditional Arabic music, all contribute to the cultural theme. Overall, the game became not just a nostalgic tribute to Fruit Ninja but also a playful representation of a personal childhood experience infused with elements of Emirati culture.

HERES MY GAME:
LINK TO FULL SCREEN: https://editor.p5js.org/aaa10159/full/rZVaP6MKc

HIGHLIGHT OF MY CODE:

The gameplay mechanics involve the basket interacting dynamically with various falling items, including sweets, dates, scorpions, and power-ups. Each of these elements serves a unique purpose in the game. For example, catching sweets and dates earns points, catching a scorpion ends the game, and catching power-ups like coins or a clock offers bonuses that enhance the gameplay experience. Integrating these interactions smoothly required attention to detail and precision in coding, as I needed to ensure that each item type was recognized and handled correctly by the basket.

To handle collisions between the basket and the falling items, I implemented a collision detection method within the basket class. This method calculates the distance between the basket and each item, determining if they are close enough to be considered “caught.” If they are, the game then applies the appropriate action based on the type of item. This collision detection is crucial for the game’s functionality, as it allows the basket to interact with different items dynamically.

// Define the Basket class, which represents the player's basket in the game.
class Basket {
  // Constructor initializes the basket with an image and sets its position, size, and speed.
  constructor(img) {
    this.img = img;           // The image used to represent the basket.
    this.x = width / 2;       // Horizontal position, initially centered on the screen.
    this.y = height - 50;     // Vertical position, near the bottom of the screen.
    this.size = 150;          // Size of the basket, controls the width and height of the image.
    this.speed = 15;          // Movement speed of the basket, determining how fast it can move left or right.
  }

  // Method to control the movement of the basket using the left and right arrow keys.
  move() {
    // Move left if the left arrow;is pressed and the basket is within screen bounds.
    if (keyIsDown(LEFT_ARROW) && this.x > this.size / 2) {
      this.x -= this.speed;   // Update the x position by subtracting the speed.
    }
    // Move right if the right arrow is pressed and the basket is within screen bounds.
    if (keyIsDown(RIGHT_ARROW) && this.x < width - this.size / 2) {
      this.x += this.speed;   // Update the x position by adding the speed.
    }
  }

  // Method to display the basket on the screen.
  display() {
    // Draw the basket image at its current position with the specified size.
    image(this.img, this.x, this.y, this.size, this.size);
  }

  // Method to check if the basket catches an item (e.g., a date or power-up).
  catches(item) {
    // Calculate the distance between the basket's center and the item's center.
    let distance = dist(this.x, this.y, item.x, item.y);

    // Check if the distance is less than the sum of their radii.
    // If true, this means the basket has caught the item.
    return distance < this.size / 2 + item.size / 2;
  }
}

In this code, the ‘catches()’ function plays a central role. It calculates the distance between the basket and the center of each item. If this distance is smaller than the sum of their radii (half their sizes), it means the item is “caught” by the basket. This collision detection method is efficient and works well for circular objects, which is perfect for the various falling items in the game.

By using this approach, I was able to ensure that the basket can accurately catch power-ups, dates, and sweets, and also end the game when a scorpion is caught. It’s a simple yet effective way to handle interactions between the player and game objects, which is essential for my game. This method also ensures that players can control the basket smoothly, making it feel natural as they try to avoid scorpions and collect items to score points.

FUTURE IMPROVMENT:

One of the specific challenges I encountered was perfecting the basket’s interaction with falling items, particularly when it came to maintaining accuracy in collision detection. Initially, I found that the items would sometimes pass through the basket without registering a catch, which was frustrating. To resolve this, I adjusted the bounding boxes for each object and tuned the distance calculations in the ‘catches’ function. However, this process revealed another area for improvement: the scorpion’s animation. Currently, the scorpion is static, but animating it would add a dynamic and slightly intimidating effect, enhancing the player’s experience.

Another improvement I’d like to make involves expanding the game by introducing different game modes. Currently, the game focuses on catching dates and sweets while avoiding scorpions, but I envision adding modes with unique objectives and challenges. For instance, one mode could intensify the difficulty by increasing the speed of falling items, while another could introduce new types of falling objects that require different strategies to catch or avoid.

Additionally, I would like to explore a survival mode where players have to last as long as possible, with gradually increasing difficulty as more scorpions and fewer power ups appear over time. By adding these game modes, It would offer players more choices and variety, encouraging them to keep playing and exploring new strategies within the game. This would not only add depth to the gameplay but also align with my goal of making the game more engaging and enjoyable for a broader audience.

 

 

Midterm project | Ear Puzzle Experience

Interaction & Page design

Each page is a separate function named as displayGamePage_. Users interact with my functions by clicking the mouse & the keys.

  1. Press the canva to enter the game stage;
  2. Click M to go back to main;
  3. Click on the right arrow to enter the next game page;
  4. Click F to enter full screen.

By having small design details, such as having the icon of the cursor also as a mouse, choosing the font & the background music, and having poetry about ears at the beginning and the end, the Ear Puzzle aims to strengthen the idea of deconstruction. Putting familiar yet unfamiliar objects to a space where users view it from an unusual perspective allows them to reflect on their own relationship with the objects.

Link to full screen.

Realization & Difficulties

The most difficult part about the code is, as what I expected, checking the WIN CONDITION. 

For this initial sketch I have, the page will allow the win condition but only by chances.

I thought the issue was on the rotation(). However, I tested the same rotation logic and it worked fine for the final work. Major reason could be that I tried to cut all the images in one function in the main js sketch. Even when the user wins, the refreshing of the next page does not follow up.

function startNewGame() {
  

  let imageIndex = gameIndex - 1; // initially gameIndex = 1
  let numPiecesOptions = [4];  // number of pieces per image
  numPieces = numPiecesOptions[imageIndex]; // an array containing the number of pieces for each image
  
  let img = images[imageIndex]; // access an element in the images array at the index specified by imageIndex
  pieces = [];
  correctRotation = 0;
    
  
/////////////////////////////////////////////maybe no need
  let pieceWidth = img.width / 2
  let pieceHeight = img.height / 2

  let scaledPieceWidth = width / 2
  let scaledPieceHeight = height / 2
/////////////////////////////////////////////maybe no need

  
  // create puzzle pieces with random rotations

  for (let x = 0; x < sqrt(numPieces); x++) {
    for (let y = 0; y < sqrt(numPieces); y++) {
      let imgSection = img.get(x * pieceWidth, y * pieceHeight, pieceWidth, pieceHeight);
      let scaledX = x * scaledPieceWidth;
      let scaledY = y * scaledPieceHeight;
      let piece = new PuzzlePiece(scaledX, scaledY, scaledPieceWidth, scaledPieceHeight, imgSection);
      pieces.push(piece);
    }
  }
}

Traumatized by the chaos, I decided to break down every variable so that they don’t overrun each other. Initially, I was using square root of puzzle-pieces as an indicator of the cut. For this new (also the final) implementation, I decided to use numbers.

I also gave up on making it a win/lose situation, meaning that users won’t enter the next page automatically as they get the rotation right. The game changes to an experience, and the users have to press the right arrow to enter the next page. Honestly I don’t think it changes the concept of my game since users are still able to play with the rotation, and their eyes will tell them if it’s correct or no.

Some small issues including not being able to add the sound effect I want or not being able to avoid collision between the image and the text. Something I wanna fix in the future. Though full screen stretches the image, I still think it’s important to have it because I changed it into an experience.

 

Assignment 6: Midterm Project (More Keychains?!)

Sketch

Link to full screen!

Screenshots

Concept

The concept originated from my obsession with keychains. I love having silly little knicknacks dangling off the zippers on my bag, or making jingling noises as i move my house keys. I wanted to encapsulate this experience so that’s why I decided to base my midterm on this idea.

In this game, users are free to decorate their bag with any of the keychains provided. They can move the keychains around to their liking to place anywhere on their bag, while also being able to rotate the keychain to best fit their preferred orientation. And note how the keychain jingles when it’s moved!

There is also a bonus feature in which the user can get one randomly generated special keychain to add to the collection!

Code, Problems, and Favorites
  • I really enjoyed illustrating some of the keychain designs (including the keychain hook) by hand on PowerPoint
  • Using an array with the file names for my keychain images, and using a loop to run through the array to quickly load all of the images made it so much easier.
function preload() {
  
  // array of image names
  let imageNames = [
    "/images/strawberryChain.png",
    "/images/carrotChain.png",
    "/images/tomatoChain.png",
    "/images/specialChain.png",
    "/images/specialChain1.png",
    "/images/specialChain2.png",
    "/images/specialChain3.png",
  ];

  // load images and sound
  for (i = 0; i < imageNames.length; i++) {
    let newChain = new Keychain(
      loadImage(imageNames[i]),
      random(40 + 40, 320 - 40),
      random(150 + 50, 500 - 130),
      70
    );
    chains.push(newChain);
  }
}
  • I used a lot of Classes in my project and I’m honestly glad I did because it made my code so much neater (albeit still messy, but it’s good enough!). I made a class for my keychains, the bag, and the background scenes depending on the game state. This way, my sketch file is less populated!
  • I had a lot of troubles trying to figure out how to rotate the keychains on click, but I managed to figure it out by adding an angle attribute to my Keychain class.
function mousePressed() {
  for (let chain of chains) {
    
    // checks if mouse is over keychain
    if (chain.isMouseOver() && gameState != "end") {
      
      // tilt the keychain when clicked
      i = 10;
      if (chain.angle > -90) {
        chain.angle -= i;
      } else {
        chain.angle = 90;
      }
...
  • In terms of the design, I was sure I wanted to include pinks and purples, and the whole idea of ‘decorating/personalizing’ reminded me of GirlsGoGames (GGG), an old online gaming website that I used to play when I was younger. And this is what inspired my overall design layout, style, and color palette!

For the Future

As much as I am proud of the final outcome, I still have areas of the project that I would like to improve on.

Firstly, the png images of the keychains, after resizing, looks very pixelated, so in the future I would consider this when illustrating and implementing images.

Second, there seems to be some glitch on the special keychain side. When a special keychain has been revealed, it takes the second mouse click for it to be able to be dragged, so it doesn’t drag on the first click. I’m still unsure as to how to fix this.

Third, as of right now, all of the keychains are presented when it hits the end game state. However, it would be better if I could somehow hide unselected keychains so that users can pick which keychains they want to use and which they don’t. Perhaps I could use selection statements to see if the keychains are not position on the area of the bag, then they should not be shown when it hits the end game state.

Final Thoughts

I really love how this project turned out; it’s fun, it’s cool, and it’s pink!

Midterm Project – Rocky the Pet Rock!

Full Sketch: https://editor.p5js.org/jheel2006/full/zaxMyymUX

Inspiration

When I first started brainstorming this project, I was drawn to the idea of creating something related to pet care. I’ve always thought of pets as a source of joy and responsibility, so I thought a virtual pet could be a fun and meaningful experience. However, I wanted to add a fun twist to the concept. I thought it might be interesting and humorous to make the pet something unexpected, like an inanimate object. This was how Rocky the Pet Rock was born!

I drew and designed Rocky myself, with five distinct mood states: Very Happy, Slightly Happy, Meh, Slightly Sad, and Very Sad. These moods are influenced by the player’s actions, which determine Rocky’s overall well-being. By making Rocky a rock instead of a living creature, I could play with the idea of what it means to care for something that doesn’t typically need attention, and how even the simplest interactions can create an emotional connection. The result is an experience that feels both playful and oddly meaningful.

Concept

The concept behind Rocky the Pet Rock is to create a simple, interactive experience where you take care of a virtual pet rock for a short period of time. Rocky’s mood is initially set to 50 (out of a total of 100). The game has a timer of 100 seconds until which your goal is to keep Rocky as happy as possible.

The game begins with an introduction screen after which  the user can reach the instructions screen by clicking anywhere. Clicking on the instructions screen leads to the main game screen.

After reaching the main game screen, there are three ways to change Rocky’s mood:

Feed: The user can choose from five different types of food to feed Rocky. However, he may or may not like the food that is chosen. If he likes the food, his mood goes up, if he doesn’t like the food, his mood goes down and if he is okay with/indifferent to the food, his mood remains unchanged. These food preferences are randomized in every round of the game. Rocky can only be fed twice during the game.

Play: Here, the user can help Rocky play his favorite game – dodgeball. In this mode, balls keep getting generated from the right side of the screen at Rocky’s current position and the user must use the up and down arrow keys to move Rocky and avoid these balls. A ball hitting Rocky results in a mood decrease and playing with Rocky without hitting any obstacles leads to a consistent mood increase.

Relax: In this mode, the user helps Rocky unwind by choosing between three different music tracks (of different genres) to play. Similar to the feed option, Rocky likes one of these tracks, doesn’t like another and is okay with the third. Choosing any of these track affects his mood depending on his preferences (randomized in every round of the game). The relax option can only be chosen once during the game.

The game ends if the timer reaches 0 or if Rocky’s mood decreases to 0. The game then displays game over screen, allowing the user to begin again by clicking on the screen. The game over screen displays Rocky’s final mood with a message. If the user restarts the game, it resets to the original conditions and begins again.

Implementation:

The implementation of the game is built around the three core modes of interaction: Feed, Play, and Relax. Each one of these had its own set of challenges to implement and integrate into the game as a whole.

  • One aspect of the Feed mode that I’m particularly proud of is the way the food options are arranged around Rocky. I wanted the interaction to be visually appealing, so I placed the five food choices in a circular pattern around Rocky, making the user’s selections feel more natural and connected to the character.
    Randomizing Rocky’s food preferences in each round was another key part of the implementation. Every time the game begins, Rocky likes, dislikes, or feels neutral about different foods, keeping the player on their toes and making each round feel unique. Once the player makes a selection, feedback is immediately displayed on the main screen, showing how Rocky’s mood is affected. It was also essential to disable the feed option after Rocky has been fed twice to prevent over-feeding and keep the game balanced. Implementing this functionality added a strategic element to the game, as players must choose carefully when and what to feed Rocky, knowing their options are limited.

    Here’s some of the feed functionality code!

    function displayFoods() {
      // Display Rocky at the center
      rocky.display();
      
      
      let centerX = rocky.x - rocky.size/8; // Center of the screen (same as Rocky's x)
      let centerY = rocky.y - rocky.size/8; // Center of the screen (same as Rocky's y)
      let radius = 170; // Distance from the center to where the foods will be displayed
    
      let angleStep = TWO_PI / foods.length; // Angle between each food item
    
      for (let i = 0; i < foods.length; i++) {
        let angle = i * angleStep; // Calculate angle for each food item
        let foodX = centerX + radius * cos(angle); // X-coordinate based on angle
        let foodY = centerY + radius * sin(angle); // Y-coordinate based on angle
        
        image(foodImages[i], foodX - 40, foodY - 40, 110, 110); // Display food images
    
        fill(0);
        textAlign(CENTER); // Center the text below each food item
        text(foods[i], foodX+20, foodY + 80); // Display food names under the images
      }
    function setFoodFeedback(food) {
      let moodEffect = foodPreferences[food];
      rocky.mood += moodEffect; // Update Rocky's mood immediately
    
        // Set food feedback based on mood effect
      if (moodEffect > 0) {
        foodFeedback = `Rocky likes ${food}!`;
      } else if (moodEffect < 0) {
        foodFeedback = `Rocky doesn't like ${food}!`;
      } else {
        foodFeedback = `Rocky is okay with ${food}.`;
      }
    
    
      // Clear any previous timeout to avoid overlap
      if (foodFeedbackTimeout) {
        clearTimeout(foodFeedbackTimeout);
      }
    
      // Clear the feedback after 3 seconds
      foodFeedbackTimeout = setTimeout(() => {
        foodFeedback = "";
      }, 3000);
    }
  • The Play mode was an exciting challenge to develop, especially with the random generation of obstacles. I designed the game so that obstacles are generated from the right side of the screen based on Rocky’s current position, ensuring that the game feels dynamic and responsive to the player’s movements. This adds an element of unpredictability, requiring the player to stay alert as they guide Rocky to dodge incoming obstacles.
    I liked the idea of how Rocky’s mood was closely tied to how well the player performs during the game. As long as Rocky successfully avoids the obstacles, his mood gradually increases, rewarding careful play. However, if Rocky gets hit by an obstacle, his mood takes an immediate hit.. This balance between mood progression and obstacle avoidance makes the Play mode both engaging and meaningful, as it directly ties the player’s performance to Rocky’s emotional state.

    Here’s some of the play functionality code!

else if (playing) {
        if (backgroundMusic.isPaused()){
          backgroundMusic.play();
        }

        // Play mode
        timer.display();
        rocky.update();
        rocky.display();
        moodMeter.display(rocky.mood);


        // Generate obstacles
        if (frameCount % 40 == 0) {
          obstacles.push(new Obstacle());
        }

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

          // Check for collision
          if (obstacles[i].hits(rocky)) {
            hitSound.play();  // Play hit sound on collision
            rocky.mood -= 10; // Decrease mood on collision
            obstacles.splice(i, 1); // Remove obstacle after collision
            continue; // Skip to the next iteration to avoid calling offscreen() on a spliced element
          }

          // Remove obstacles that go off-screen
          if (obstacles[i].offscreen()) {
            obstacles.splice(i, 1);
          }
    }

        // Check if mood or timer runs out to end the game
        if (rocky.mood <= 0 || timer.isTimeUp()) {
          gameState='gameOver';
          playing = false;
          resetGame();
        }

These are the Rocky and Obstacle classes, which also have elements of the game functionality:

// Rocky class
class Rocky {
  constructor() {
    this.mood = 50;
    this.x = windowWidth / 2;
    this.y = windowHeight / 2 + 100;
    this.size = 100; // The size of Rocky (matches the previous ellipse size)
    this.speed = 5; // Speed for moving up and down
  }

  display() {
    // fill(150);
    // ellipse(this.x, this.y, 100, 100); // Rocky as an ellipse
    if (this.mood >=80){
      rockyImage = rockyImageHappy;
    }
    else if (this.mood >=60){
      rockyImage = rockyImageHappyish;
    }
    else if (this.mood >=40){
      rockyImage = rockyImageMeh;
    }
    else if (this.mood >=20){
      rockyImage = rockyImageSadish;
    }
    else {
      rockyImage = rockyImageSad;
    }
    
    image(rockyImage, this.x - this.size / 2 - 25, this.y - this.size / 2 , this.size+40, this.size); // Display the image with Rocky's coordinates
  }


// Obstacle class for the play mode
class Obstacle {
  constructor() {
    this.x = windowWidth;
    this.size = random(60,75); // Size of obstacle
    // this.y = random(0, windowHeight - this.size); // Random y-position
    this.y = rocky.y; // Spawn the obstacle at Rocky's current y position
  }

  display() {
    // fill(255, 0, 0);
    // rect(this.x, this.y, this.size, this.size);
    image(obstacleImage, this.x, this.y, this.size, this.size);
  }

  update() {
    this.x -= obstacleSpeed; // Move the obstacle to the left
  }

  offscreen() {
    return this.x < -this.size; // Check if the obstacle has moved off-screen
  }

  hits(rocky) {
    let d = dist(this.x, this.y, rocky.x, rocky.y);
    return d < this.size / 2 + 50; // Check for collision with Rocky
  }
}
  • The Relax mode shares similarities with the Feed mode in terms of randomizing Rocky’s music preferences, but the added challenge here was managing the background music. The player can choose from three different music tracks (a lullaby, a funk track and some lo-fi beats), each representing a different genre. Rocky’s mood responds to the track selection—he likes one, dislikes another, and is indifferent to the third, all of which are randomized each round. This randomness keeps the experience fresh and unpredictable, just like the food preferences.
    A technical challenge was ensuring that the background music from the Relax mode would stop properly once the player entered the Relax mode and restarted when the player exited the mode or when the track finished playing. It was also important to return to the main screen after the track ended (after 10 seconds) while displaying the feedback and effect on Rocky’s mood.

    Here’s some of the relax functionality code!

    function randomizeMusicPreferences() {
      let shuffledTracks = shuffle([10, 0, -10]); // One increases, one decreases, one is neutral
      for (let i = 0; i < relaxTracks.length; i++) {
        musicPreferences[i] = shuffledTracks[i]; // Map tracks to mood effects
      }
    }
    function startRelaxMode() {
        relaxing = true;
        playing = false;
        displayTrackButtons(); // Display buttons for selecting tracks
        backButton.display(); // Show back button to return to main screen
      
    }
    
    
    // Function to display track buttons
    function displayTrackButtons() {
      // Display each track button
      for (let trackButton of trackButtons) {
        trackButton.display();
      }
    }
    
    // Function to play the selected track and show feedback
    function playRelaxTrack(trackIndex) {
      if (isRelaxing) {
            return; // If a track is already playing, prevent any other interaction
        }
    
        isRelaxing = true; // Set to true once a track starts playing
        selectedTrack = trackIndex;
        relaxTracks[trackIndex].play(); // Play the selected track
        
        setTimeout(function() {
            relaxTracks[trackIndex].stop(); // Stop the track after 10 seconds
          showRelaxFeedback(trackIndex);
          relaxing = false;      // Return to the main game state
          relaxDisabled = true;       // Disable the Relax button after it's used
          isRelaxing = false;
          rocky.x = windowWidth / 2;
          rocky.y = windowHeight / 2 + 100;
        }, 10000); // Play for 10 seconds
    }
    
    // Function to show feedback based on Rocky's preferences
    function showRelaxFeedback(trackIndex) {
        let moodEffect = musicPreferences[trackIndex]; // Get mood effect for the selected track
        rocky.mood += moodEffect; // Adjust Rocky's mood accordingly
    
        // Set relax feedback based on the mood effect of the track
        if (moodEffect > 0) {
            relaxFeedback = `Rocky likes this track!`;
        } else if (moodEffect === 0) {
            relaxFeedback = `Rocky is okay with this track.`;
        } else {
            relaxFeedback = `Rocky doesn't like this track!`;
        }
    
        // Clear any previous timeout to avoid overlap
      if (relaxFeedbackTimeout) {
        clearTimeout(relaxFeedbackTimeout);
      }
    
      // Clear the feedback after 3 seconds
      relaxFeedbackTimeout = setTimeout(() => {
        relaxFeedback = "";
      }, 3000);
      // Clear any previous timeout to avoid overlap
     
    }

    Reflections and Further Improvements

    Overall I’m quite satisfied with how the game turned out, especially considering the many stages it went through – from my vague, initial idea to its final implementation. Some things that I can consider doing to add to it could be making the Feed mode slightly more interactive by having a drag-and-drop feature (to make the user feel like they’re actually feeding Rocky). I could also add some sound feedback when Rocky’s mood changes (for instance, when he ate a food he liked or listened to a track he didn’t like). Similarly, I could also add sound feedback when the game ends, reflecting Rocky’s current mood state.

All in all, I really enjoyed the experience of bringing my idea to life. There were quite a few bugs as well as some unexpected behavior that came up along the way, but I’m pretty happy with the final result :))

Midterm Project

Concept

My project is a simple car game. I decided on a  game as I wanted to create something that will be fully user controlled to ensure that I engage people into my work.

The game simple to learn and play. The player is tasked to navigate a car along a three lane road with incoming traffic. The player uses the arrow keys to move the car left and right from one lane to another and the space key to “zap” over the middle lane. The zapping basically means that if the player is not on the middle lane i.e in the furthest right or left lane, they can move the car to the furthest end lane jumping over the middle lane. This feature is important when playing the hard level.

Traffic cars move in opposite direction of the player car in random intervals and lanes. The player can choose the level of difficulty between the easy, medium and hard. The levels of difficulty have traffic of different speeds with easy having traffic of less speed and hard with high speeds. Traffic is more frequent in hard level and less frequent in the easy level. To ensure that the game is endless I limited the distance of play to 1500 so that the game ends when a player has driven for 1500. I am proud how I was able to make my game more complex by including different difficulty levels .These conditions ensures that while the player enjoys the game they also feel the challenge and push themselves.

Below are some of the images of the game showing the start screen, game mode and how game ends.

Sketch

Code highlight
I was proud of how I executed my collisions in the game as in the code below

//collision handling
  checkCollision(player) {
    let shrinkFactor = 20; 
    let playerLeft = player.x + shrinkFactor;
    let playerRight = player.x + player.w - shrinkFactor;
    let playerTop = player.y + shrinkFactor;
    let playerBottom = player.y + player.h - shrinkFactor;

    let trafficLeft = this.x + shrinkFactor;
    let trafficRight = this.x + this.w - shrinkFactor;
    let trafficTop = this.y + shrinkFactor;
    let trafficBottom = this.y + this.h - shrinkFactor;

    // Check if the bounding boxes of the player and traffic car overlap
    if (
      playerRight > trafficLeft && 
      playerLeft < trafficRight && 
      playerBottom > trafficTop && 
      playerTop < trafficBottom
    ) {
      return true; // Collision detected
    }
    return false; // No collision
  }

To handle collision between the cars I used a logic of the images being a rectangle or a box with their bounds. The algorithm checks if these boxes overlap and shows that a collision has occurred. I use a shrink factor to make the boxes smaller that the actual object size to avoid wrong collision detection when objects are close but not touching.

Challenges and future improvements

I still have a lot to do to improve my work and make it better. For example, I would like to have different types cars for the traffic and have animation for coins collected. I also wanted to add power up such as indestructibility for some period of time after picking a token. My game start and game end page are also not that visually appealing, I would like to make them more interesting and fun. Lastly I would like to have a crash effect after the player hits the traffic.
I had a couple of challenges while implementing. One challenge was balancing the background movement to the movement of the traffic cars. Since both images were moving at different speeds in opposite directions it created an effect which made the traffic look like it’s not moving. I was able to partially resolve this by making the traffic speed bigger than the background speed but it still doesn’t work well for the hard level.
Another challenge was switching game difficulty. While the player switches difficulty after losing I have scenarios of cars from the previous round still appearing . I resolved this by clearing my traffic array once a player loses and game ends.
Another issue was handling collision which I luckily resolved after a lot of code errors.
Overall the game can be made much better by incorporating some of the ideas that I mentioned above. However, I am still proud of what I was able to come up with.