Midterm Project : Pixel Art

For my midterm project, I drew inspiration from the color-by-number games that are so popular on the internet. I used to be obsessed with them, so I decided to create my own pixelated version. In my project, users color cell by cell in a grid, where each cell corresponds to a letter, and each color on the palette is assigned to a letter as well. By following the key and filling in the grid, the design gradually comes to life, revealing the full picture bit by bit. There’s something incredibly relaxing about mindlessly clicking, watching the colors take shape, and seeing the image emerge one step at a time. It’s a simple yet satisfying process, where every small action contributes to a larger, beautiful result.

The game starts with a welcome screen where the user has two options: Start, to begin coloring immediately, or Help, which provides instructions on the various features the game offers. Once they click Start, they are taken to the selection screen, where they can choose a coloring page from the available options. After selecting a page, they arrive at the actual coloring screen, where the process is simple—choose a color from the palette above and start coloring. However, if the user accidentally fills in the wrong square, they can double-tap the cell to remove the color. If they wish to start over or choose another page, a Reset button clears the canvas. Once the user finishes coloring their chosen page, the game is considered complete, and they are prompted to restart if they wish to play/color again.


One aspect of my code that I’m particularly proud of is the implementation of the actual coloring page. The way the cells fill in over the image has a seamless and polished look. I also like how intuitive the overall user interface is—even first-time users can start playing without needing to read the instructions. I chose soothing shades of brown for the interface, paired with lofi background music, to enhance the game’s relaxing atmosphere.

That said, I encountered a few challenges along the way, particularly with the coloring process itself. One major issue was ensuring that the grid, which tracks cell positions, perfectly aligned with the image. Initially, a double grid effect was visible on top of the image, which was distracting. To fix this, I had to experiment with different parameters before finally finding a formula that worked—provided the image was a perfect square with no empty or extra areas. This was implemented in the ColoringPage class. Once the grid was properly aligned, tracking mouse clicks and filling in the correct areas became much smoother.

class ColoringPage {
  constructor(name, imagePath,thumbPath, rows, cols, palette,targetCells) {
    this.name = name;
    this.img = pageImages[name.toLowerCase()];  
    this.thumb = pageThumbnails[name.toLowerCase()];
    this.rows = rows;
    this.cols = cols;
    this.cellSize = 600 / this.cols;
    this.grid = Array.from({ length: this.rows }, () => Array(this.cols).fill(null));
    this.palette = palette;
    this.selectedColor = Object.values(palette)[0].color;
    this.targetCells = targetCells; 
    this.filledCells = 0;
  }

  display() {
    this.drawPalette();
    image(this.img, 100, 90, 600, 600);
    this.drawGrid();
    this.drawColoredGrid();
  }

  drawGrid() {
    stroke(0, 50);
    noFill();
    for (let row = 0; row < this.rows; row++) {
      for (let col = 0; col < this.cols; col++) {
        rect(100 + col * this.cellSize, 90 + row * this.cellSize, this.cellSize, this.cellSize);
      }
    }
  }

  drawColoredGrid() {
    for (let row = 0; row < this.rows; row++) {
      for (let col = 0; col < this.cols; col++) {
        if (this.grid[row][col]) {
          fill(this.grid[row][col]);
          rect(100 + col * this.cellSize, 90 + row * this.cellSize, this.cellSize, this.cellSize);
        }
      }
    }
  }

  drawPalette() {
    let keys = Object.keys(this.palette);
    let x = (width - keys.length * 60) / 2;
    let y = 20;

    noStroke();

    for (let i = 0; i < keys.length; i++) {
        let colorValue = this.palette[keys[i]].color;
        let isSelected = this.selectedColor === colorValue;
        let isHovered = mouseX > x + i * 60 && mouseX < x + i * 60 + 50 &&
                        mouseY > y && mouseY < y + 50;

        let circleSize = 50; 
        if (isHovered) circleSize = 55; 

        let centerX = x + i * 60 + 30;
        let centerY = y + 25;

        if (isSelected) {
            fill(255); 
            ellipse(centerX, centerY, circleSize + 8, circleSize + 8);
        }

        fill(colorValue);
        ellipse(centerX, centerY, circleSize, circleSize); 
      
      let c = color(colorValue);
      let brightnessValue = (red(c) * 0.299 + green(c) * 0.587 + blue(c) * 0.114); 

        fill(brightnessValue < 128 ? 255 : 0);
        textSize(14);
        textAlign(CENTER, CENTER);
        
        let labelChar = this.palette[keys[i]].label;  
        text(labelChar, centerX, centerY);
    }
}


  selectColor() {
    let keys = Object.keys(this.palette);
    let x = (width - keys.length * 60) / 2;
    for (let i = 0; i < keys.length; i++) {
      if (mouseX > x + i * 60 && mouseX < x + i * 60 + 50 && mouseY > 20 && mouseY < 70) {
        this.selectedColor = this.palette[keys[i]].color;
        break;
      }
    }
  }

  fillCell() {
    let col = floor((mouseX - 100) / this.cellSize);
    let row = floor((mouseY - 90) / this.cellSize);

    if (row >= 0 && row < this.rows && col >= 0 && col < this.cols) {
      if (!this.grid[row][col]) {
        this.grid[row][col] = this.selectedColor;
        this.filledCells++;
        console.log(this.filledCells)
        if (this.isCompleted()) {
          game.state = "final";
        }
      }
    }
  }

Another challenge I faced was determining the end condition for the game. Currently, the game ends when the total number of filled cells matches the number of cells corresponding to a color on the grid. However, this assumes that users only color the cells with assigned letters and don’t fill in any blank spaces. Additionally, since the game allows users to fill any cell with any color, the code does not track whether a specific cell is filled with the correct color. A possible solution would be to store a reference for each cell’s correct color in every coloring page, but I haven’t yet found a way to do this dynamically without hardcoding hundreds of cells, which would be inefficient and take up unnecessary space in the code. This is an area I would like to improve in the future.

Midterm Project – The Magic Studio

Concept

The game is called The Magic Studio. The original idea was to create a highly interactive room where users could generate anything they wanted—sounds, objects, and more—giving them full creative freedom. However, due to time constraints, I scaled the project down to a painting game. In the current version, users are given a reference painting, and they must recreate it by generating objects that match the original as closely as possible. The goal is to maintain an element of creativity while also challenging players to be precise in their recreations.

How it works

The game starts with an instructions screen. It then moves the scene where the user can generate objects to match the desired painting. New objects can be generated by prompting again and the previous one gets removed automatically. Once the user is satisfied with their design, they can click on the next button to move to the next painting. Once all the painting tasks are completed, the user can restart the game.

Challenges

One of the biggest challenges and something that I am the most proud of was making the language model (LLM) generate objects as accurately as possible. Initially, I used a model with limited capabilities, which struggled to create detailed objects correctly. This led to some frustrating results, as the generated objects often didn’t match the intended design. Eventually, I switched to the Gemini model, which significantly improved performance. The new model generated objects more accurately, making the gameplay experience much smoother and more enjoyable.

Another challenge was ensuring that the interaction between the user and the game felt intuitive. Since p5.js is primarily a visual tool, integrating AI-based object generation in a way that seamlessly fit into the game mechanics took a lot of trial and error.

  // Create input field and Gemini button, but hide them until the game starts
  promptInput = createInput("Provide Object Description");
  promptInput.position(350, 100);
  promptInput.hide();
  
  button = createButton("Create your painting!");
  button.position(promptInput.x+20,promptInput.y + promptInput.height + 10);
  button.mousePressed(() => {
    let userText = promptInput.value();
    let geminiPrompt = `
You are an AI code generator working within an online p5.js editor environment. In this environment, the following conditions apply:
- The p5.js library is loaded along with its DOM addon (p5.dom), so functions like createCanvas, createButton, and createInput are available.
- A canvas of 800x600 pixels is created as part of a "dream room" simulation.
- The dream room maintains an array of dream objects. Each dream object must be defined as a JavaScript object with the following properties:
    - x: a numeric value representing the horizontal coordinate (default value: 100)
    - y: a numeric value representing the vertical coordinate (default value: 100)
    - size: a numeric value representing the object’s size (default value: 50)
    - draw: a function that uses p5.js drawing commands (for example, ellipse) to render the object at (x, y) using its size. Ensure you combine multiple shapes for a rich rendering.
    - move: a function that accepts two parameters, dx and dy, and updates the x and y coordinates respectively

Your task is to generate a valid p5.js code snippet that creates a new dream object (named \`dreamObj\`) with these properties. The object must be defined using "let".

Output Requirements:
- Your output must be a valid JSON object with exactly two keys: "code" and "description".
- The "code" key’s value must be a string containing the p5.js code that defines the dream object as described.
- The "description" key’s value must be a concise explanation of what the code does.
- Do not include any additional keys or text; output only the JSON.

Now, generate the object: 
`;
    let prompt = geminiPrompt + userText;
    generateDreamObject(prompt);
  });
  button.hide();

 

Future Improvements

There are the ways I can enhance the project in the future:

  1. AI-Based Scoring System – One idea is to allow players to take a screenshot of their generated painting, and then use AI to analyze it and give a score based on accuracy and similarity to the reference image.
  2. AI-Generated Reference Objects – Instead of only providing a static reference painting, we could allow AI to generate a new image based on the original reference. The AI could create a new rendition of the image in a slightly altered style, and players could then attempt to recreate that version using p5.js.
  3. Comparing AI vs. Player Renderings – We could take screenshots of both the AI-generated image and the player-generated image, then compare them using an AI model to determine which one is a better match to the original reference. This would add another layer of challenge and gamification to the experience.
  4. More Creative Freedom – To bring the project closer to the original concept, I could add more interactive elements, such as sound generation or more diverse object creation tools, allowing users to express their creativity beyond just painting.

Midterm Project

Concept

Retro-style 2D shooters have always been a favorite among gamers. Their simplicity, fast-paced action, and pixel-art aesthetics create an engaging experience. I set out to create my own Pixel Shooter Game using p5.js, adding dynamic enemy interactions, ammo management, health pickups, and immersive sound effects.

The idea was simple:

  • The player moves around the screen and shoots at approaching enemies.
  • The enemies chase the player and deal damage upon collision.
  • The player must manage ammo and collect pickups to survive.
  • The game ends when the player’s health reaches zero.

With these mechanics in mind, I designed a game that combines action, strategy, and survival elements.

I used a lot of sound effects from popular shooter games (Half Life, Counter Strike, some soundtracks from Red Faction II) and sandbox games like Minecraft.

I also used free, open-source sprites from different forums, God bless open-source!

How It Works

The game follows a state-based system, transitioning between:

  1. Loading Screen – Displays a progress bar while assets are loading.
  2. Start Menu – Shows the title, “Start Game” and “Instructions” buttons.
  3. Instructions Page – Displays movement, shooting, and gameplay tips.
  4. Play Mode – The core gameplay where enemies chase the player.
  5. Game Over Screen – Displays the final score and an option to restart.

Core Mechanics

  • Player Movement: Uses WASD or arrow keys to move.
  • Shooting: Press Space to fire bullets.
  • Enemy AI: Enemies spawn and move toward the player.
  • Health System: The player starts with 3 HP and dies after three enemy hits.
  • Ammo System: The player has 20 bullets max and must pick up ammo to reload.
  • Pickups: Random enemies drop ammo or health packs.

Code Highlights:

1) State-Based Game Flow

To keep the game organized, I used a state-based approach to control transitions:

function draw() {
  background(20);

  if (gameState === "loading") {
    showLoadingScreen();
    if (assetsLoaded >= totalAssets && loadingCompleteTime === 0) {
      loadingCompleteTime = millis();
    }
    if (loadingCompleteTime > 0 && millis() - loadingCompleteTime > loadingDelay) {
      gameState = "start";
    }
  } 
  else if (gameState === "start") {
    showStartScreen();
    startButton.show();
    instructionsButton.show();
    backButton.hide(); // Ensure Back button is hidden
    
    if (!startMusic.isPlaying()) {
      startMusic.loop();
      startMusic.setVolume(0.5);
    }
  } 
  else if (gameState === "instructions") {
    showInstructionsScreen();
    startButton.hide();
    instructionsButton.hide();
    backButton.show();
  } 
  else if (gameState === "play") {
    runGame();
    showScore();
    startButton.hide();
    instructionsButton.hide();
    backButton.hide();
    
    if (startMusic.isPlaying()) {
      startMusic.stop();
    }
    
    if (!gameMusic.isPlaying()) {
      gameMusic.loop();
      gameMusic.setVolume(0.5);
    }
  } 
  else if (gameState === "dead") {
    showDeadScreen();
    startButton.hide();
    instructionsButton.hide();
    backButton.hide();
  }
}

Each function (showLoadingScreen(), runGame(), etc.) controls what is displayed based on the current state.


2) Player Shooting Mechanic

The player can shoot bullets, but with an ammo limit:

class Player {
  constructor() {
    this.pos = createVector(width / 2, height / 2);
    this.speed = 4;
    this.movementDist = 0; // we update this in update()
    this.ammo = 20;  
    this.health = 3;
    this.lastDamageTime = 0;  // Track last time the player took damage


  }

  update() {
   ...
  }

  show() {
    push();
    translate(this.pos.x, this.pos.y);

    // Face the mouse (common in top-down shooters).
    let angle = atan2(mouseY - this.pos.y, mouseX - this.pos.x);
    rotate(angle);
    scale(0.25);


    // Decide idle or move frames
    let moving = (this.movementDist > 0.1);

    // Pick frame index
    let frameIndex;
      imageMode(CENTER);

    if (moving) {
      frameIndex = floor(frameCount / 6) % playerMoveSprites.length;
      image(playerMoveSprites[frameIndex], 0, 0);
    } else {
      frameIndex = floor(frameCount / 12) % playerIdleSprites.length; 
      image(playerIdleSprites[frameIndex], 0, 0);
    }

    pop();
    
    this.showHealthBar();

  }
  
    showHealthBar() {
    ...
  }

  shoot() {
    // Fire only if enough time has passed since last shot AND we have ammo left
    if (this.ammo > 0 && millis() - lastShotTime > 200) {
      bullets.push(new Bullet(this.pos.x, this.pos.y));
      lastShotTime = millis();
      this.ammo--;  // reduce ammo by 1
    }
    
      if (gunSound) 
        gunSound.play();
  }
}

This ensures players can’t spam bullets, adding a strategic element to gameplay.

3) Enemy AI and Collision Handling

Enemies move toward the player, and if they collide, the player takes damage:

// Enemy-player collisions (Fixed)
for (let enemy of enemies) {
  let dPlayer = dist(player.pos.x, player.pos.y, enemy.pos.x, enemy.pos.y);
  
  // Only take damage if enough time has passed (e.g., 1 second)
  if (dPlayer < 30 && millis() - player.lastDamageTime > 1000) {
    player.health--;  
    player.lastDamageTime = millis(); // Update last hit time
    
    if (hitSound) {
      hitSound.play();
    }
    
    if (player.health <= 0) {
      if(playerDeathSound) {
        playerDeathSound.play();
      }
      gameState = 'dead';
    }
  }
}

This prevents instant death by implementing a damage cooldown.

4) Dynamic Background Music

Different background tracks play in menu and play modes:

else if (gameState === "play") {
  runGame();
  showScore();
  startButton.hide();
  instructionsButton.hide();
  backButton.hide();
  
  if (startMusic.isPlaying()) {
    startMusic.stop();
  }
  
  if (!gameMusic.isPlaying()) {
    gameMusic.loop();
    gameMusic.setVolume(0.5);
  }
}

This immerses the player by dynamically switching music.

Challenges and Improvements

1) Handling Enemy Collisions Fairly

Initially, enemies instantly killed the player if they were touching them. To fix this, I added a damage cooldown, so players have 1 second of immunity between hits.

2) Centering Buttons Properly

Buttons were misaligned when resizing the canvas. Instead of manually placing them, I used dynamic centering.

3) Preventing Accidental Game Starts

Initially, pressing any key started the game. To fix this, I made keyPressed() work only in play mode.

Final Thoughts

This Pixel Shooter Game was a fun challenge that combined:

  • Game physics (enemy movement, shooting mechanics)
  • User experience improvements (better UI, centered buttons)
  • Audio immersion (different music for each state)
  • Optimization tricks (cropping backgrounds, limiting bullet spam)

Possible Future Improvements

  • Add power-ups (e.g., speed boost, rapid fire)
  • Implement different enemy types
  • Introduce a high score system
  • Introduce multiplayer using socket.io (websocket server connection, so two different clients could play on separate machines)

This project demonstrates how p5.js can create interactive, engaging 2D games while keeping code structured and scalable.

Sketch (Click Here to Open Full Screen)

 

Midterm-gardens of butterflies

For this midterm project I chose to create a game of a garden filled with butterflies, nature-inspired environment where colorful butterflies flutter randomly across the screen. I used the spacebar to control a net that follows the cursor, to catch the butterflies making the gameplay intuitive and engaging.

The goal of the game is to catch as many butterflies as possible . Each butterfly has different colors and sizes, . I also added gentle ambient sounds, like a net sound, to create a soothing experience.

in conclusion , I aimed to create a beautiful digital garden that tests hand-eye coordination while offering a calming escape.

Future Improvements:
I believe the game could be further enhanced by adding different levels of difficulty. For example, introducing obstacles or making some butterflies faster and more evasive would add a layer of challenge. I also think that implementing a scoring system with a timer could increase replayability. Another idea is to introduce special butterflies that grant power-ups, like slowing down all butterflies or extending the catch radius of the net. Additionally, I would explore adding dynamic weather effects, such as changing skies or light rain, to create a more immersive environment. Ultimately, my goal is to refine the game by incorporating feedback and exploring new ways to engage players through both gameplay and visuals.

code im proud of:

class GameMenuDisplay {
    show() {
        butterflyMenu.move();
        butterflyMenu.show();

        noStroke();
        fill(255, 255, 255, 200);
        rect(420, 20, 360, 560);

        // How To Play Text
        textSize(32);
        textAlign(CENTER, CENTER);
        fill(0);
        text("Catch The Butterfly", 600, 60);
        
        textSize(20);
        text("Game Instruction: ", 600, 220);
        
        textSize(14);
        text("Move the net with your mouse and\npress SPACE BAR to catch the butterflies.", 600, 280);

        textAlign(CENTER, CENTER);
        text("Click anywhere to start the game.", 600, 520);
    }
}

 

Midterm Project – The Cookie Game!

For my midterm project, I decided to channel my love for baking and code a game for baking cookies!

Link to the full screen sketch: https://editor.p5js.org/lr3258/full/e-z5Ur1g3

Concept :

This is a simple game to bake chocolate chip cookies, very direct and fun! The player drags ingredients into a bowl to create the dough, which then bakes into delicious chocolate chip cookies! I wanted it to have a very comfy and cozy vibe, so I chose a colour palette of baby blues and browns. To make the entire game more aesthetically cohesive, I drew the ingredients on Autodesk Sketchbook and made the layouts on Canva. I also drew the egg –> dough sprite sheet since I couldn’t find any suitable ones online.

How it works:

The game isn’t meant to be challenging or competitive, it is more inclined to the process and joy of baking the cookies than being a “game” itself.

First, the user has to click on any key to start the game:

Next, the player has to drag the ingredients into the bowl. Here, when the egg is clicked on, it triggers the animation to display the dough. We can then press the Next button.

After this, we have to wait for 5 seconds, while the cookies “bake” in the oven. After 5 seconds, the screen automatically shifts with a Ding! sound.

Then, the final screen shows the baked cookies! We can then restart the game.

highlights of the code i’m proud of

A part of the game I’m pretty proud of is the sprite sheet animation. It took a lot of time to draw, create and then animate the sprite sheet. It was also the hardest part, and there was a LOT of debugging involved, but in the end I’m happy with how it turned out, even with the ingredients being dragged into the bowl. I’m also very happy with the aesthetic of the game, which really feels like home to me.

class Ingredient {
constructor(name, x, y, img, isEgg) {
    this.name = name;
    this.x = x;
    this.y = y;
    this.img = img;
    this.isEgg = isEgg;
    this.isDragging = false;
    this.offsetX = 0;
    this.offsetY = 0;
    this.isDropped = false;
    this.currentFrame = 0; 
    this.numFrames = 5; 
    this.frameWidth = 150;
    this.frameHeight = 150;
    this.isCracked = false;
    this.frameTimer = 0;
    this.frameSpeed = 6;  
    this.originalX = x;  
    this.originalY = y;  
  }
  
  display() {
    
    if (!this.isDropped && !this.isEgg && bowl.isIngredientInBowl(this)) {
  this.isDropped = true;
  this.x = bowl.x;
  this.y = bowl.y;
}
    
    if (this.isEgg) {
      let sx = this.currentFrame * this.frameWidth;
      image(this.img, this.x, this.y, this.frameWidth, this.frameHeight, sx, 0, this.frameWidth, this.frameHeight);
    } else if (!this.isDropped) {
      image(this.img, this.x, this.y);
    }
  }

  update() {
    if (this.isDragging) {
      this.x = mouseX + this.offsetX;
      this.y = mouseY + this.offsetY;
    }
    
    //dropping ingredient into bowl
    if (!this.isDropped && !this.isEgg && bowl.isIngredientInBowl(this)) {
      this.isDropped = true;
      this.x = bowl.x; 
      this.y = bowl.y;
    }

    // animate the egg spritesheet if it's clicked
    if (this.isEgg && this.isCracked) {
      this.frameTimer++;
      if (this.frameTimer >= this.frameSpeed) {
        this.frameTimer = 0;  // to reset timer
      if (this.currentFrame < this.numFrames - 1) {
          this.currentFrame++;  
        }
      }
    }
  }

  checkDragging() {
    if (
      mouseX > this.x &&
      mouseX < this.x + this.img.width &&
      mouseY > this.y &&
      mouseY < this.y + this.img.height &&
      !this.isDropped
    ) {
      this.isDragging = true;
      this.offsetX = this.x - mouseX;
      this.offsetY = this.y - mouseY;
    }
  }

  checkClicked() {
    if (
      mouseX > this.x &&
      mouseX < this.x + this.img.width &&
      mouseY > this.y &&
      mouseY < this.y + this.img.height &&
      !this.isCracked
    ) {
      this.isCracked = true; //start the animation of egg cracking
      this.currentFrame = 0;
    }
  }

 

challenges and improvements:

When it came to challenges, the main concern was the sprite sheet itself. It was also hard to transition from different states smoothly, but I implemented the concept of state machine we discussed in class, and it went pretty well. In the end, the final problem I had was the autoplay of the background audio, which wasn’t working in the full screen version. For this, I used the p5js built in code, userStartAudio(), which starts the audio on click. Implementing the restart button at the end was also a bit tricky, but overall, I’m pleased with the final result.

If I could make some improvements, I’d add some extra elements, like a timer, or maybe animations for the baking time. I would also like to change the design of the buttons, which are currently very basic in contrast to the rest of the game.

Embedded sketch :

Midterm Project: Interactive Fantasy Game

For this project, I decided to transform a short fantasy story I wrote in primary school into an interactive game using p5.js. This game is played by a single player who guides the protagonist, Eden, on her quest to slay the dargon and save her village. The game has multiple choices that affect the storyline, but in the end, all paths lead to the same outcome. There are a total of 5 key decision points and 14 different screens in the game.

How it works:

The game’s structure is built around a series of screens, each representing a different part of Eden’s journey. The transitions between screens are managed efficiently through the mousePressed() function and buttons. The game utilizes object-oriented programming (OOP) for button management.

Proud Achievements and Technical Decisions
One of the most significant achievements in this project is the implementation of the OOP approach for button management. Initially, built-in functions were used, but switching to OOP proved to be a more flexible and maintainable solution. This decision allows for easier customization of button behavior and appearance across different screens.

Another achievment is the overall structure of thegame, with its multiple screens and decision points. This took a long time and required  careful planning and implementation to ensure smooth transitions and logical flow of the story.

The integration of background images and music also adds to the immersive experience of the game. These elements help bring the fantasy world of EverLand to life for the player.

The buttons code:

//Creating the buttons class
class GameButton {
  //initializing the state of the button
  constructor(label) {
    this.label = label;
    this.x = 0;
    this.y = 0;
    this.width = 105;
    this.height = 30;
    this.visible = false;
  }
  //method to show button at specified coordinates
  show(x, y) {
    this.x = x;
    this.y = y;
    this.visible = true;
    fill("white");
    noStroke();
    rect(this.x, this.y, this.width, this.height);
    fill("black");
    textSize(13);
    textAlign(CENTER, CENTER);
    text(this.label, this.x + this.width / 2, this.y + this.height / 2);
    fill("white") //so trhat the text itself is white
    textSize(15); //so that the text itself has a size of 15
  }
  //method to hide button
  hide() {
    this.visible = false;
  }

  //method to identify if mouse is hovering over button
  isMouseOver() {
    return (
      this.visible &&
      mouseX > this.x &&
      mouseX < this.x + this.width &&
      mouseY > this.y &&
      mouseY < this.y + this.height
    );
  }
}

The screens code:

function draw() {
  //calling the function that displays the screen based on the screen assignment which happens when the mouse is pressed
  if (screen === 0) {
    showStartScreen();
  } else if (screen === 1) {
    showBirthdayScreen();
  } else if (screen === 11) {
    showSuppliesScreen();
  } else if (screen === 12) {
    showWeaponScreen();
  } else if (screen === 111 || screen === 121) {
    showNightScreen();
  } else if (screen === 112 || screen === 122) {
    showMorningScreen();
  } else if (screen === 1111 || screen === 1121 || screen === 1211 || screen === 1221) {
    showRiverScreen();
  } else if (screen === 1112 || screen === 1122 || screen === 1212 || screen === 1222) {
    showForestScreen();
  } else if (screen === 11000 || screen === 12000 || screen === 21000 || screen === 22000) {
    showNextScreen();
  } else if (screen === 5000) {
    showDragonCaveScreen();
  } else if (screen === 5001) {
    showInsideScreen();
  } else if (screen === 5002) {
    showOutsideScreen();
  } else if (screen === 5003) {
    showTrapScreen();
  } else if (screen === 262626) {
    showFinalScreen();
  }
}

//function to hide all buttons which i will use when switching screens so that the previous buttons don't appear on the screen
function hideAllButtons() {
  enterButton.hide();
  suppliesButton.hide();
  weaponButton.hide();
  nightButton.hide();
  morningButton.hide();
  riverButton.hide();
  forestButton.hide();
  fishButton.hide();
  riverspiritsButton.hide();
  next1Button.hide();
  insideButton.hide();
  outsideButton.hide();
  trapButton.hide();
  next2Button.hide();
  forestspiritsButton.hide();
  firefliesButton.hide();
  playButton.hide();
  quitButton.hide();
}

//assigns screen with next number based on the previous screen and the button pressed and also hides all buttons so that they aren't layered on top of one another
function mousePressed() {
  if (screen === 0 && enterButton.isMouseOver()) {
    screen = 1;
    hideAllButtons();
  } else if (screen === 1) {
    if (suppliesButton.isMouseOver()) {
      screen = 11;
      hideAllButtons();
    }else if (weaponButton.isMouseOver()) {
      screen = 12;
      hideAllButtons();
    }
  } else if (screen === 11 || screen === 12) {
    if (nightButton.isMouseOver()) {
      screen = screen * 10 + 1;
      hideAllButtons();
    }else if (morningButton.isMouseOver()) {
      screen = screen * 10 + 2;
      hideAllButtons();
    }
  }else if (screen === 111 || screen === 112 || screen === 121 || screen === 122) {
    if (riverButton.isMouseOver()) {
      screen = screen * 10 + 1;
      hideAllButtons();
    } else if (forestButton.isMouseOver()) {
      screen = screen * 10 + 2;
      hideAllButtons();
    }
  } else if (screen === 1111 || screen === 1121 || screen === 1211 || screen === 1221) {
    if (fishButton.isMouseOver()) {
      screen = 11000;
      hideAllButtons();
    } else if (riverspiritsButton.isMouseOver()) {
      screen = 12000;
      hideAllButtons();
    }
  } else if (screen === 1112 || screen === 1122 || screen === 1212 || screen === 1222) {
    if (firefliesButton.isMouseOver()) {
      screen = 21000;
      hideAllButtons();
    } else if (forestspiritsButton.isMouseOver()) {
      screen = 22000;
      hideAllButtons();
    }
  } else if (screen === 11000 || screen === 12000 || screen === 21000 || screen === 22000) {
    if (next1Button.isMouseOver()) {
      screen = 5000;
      hideAllButtons();
    }
  } else if (screen === 5000) {
    if (insideButton.isMouseOver()) {
      screen = 5001;
      hideAllButtons();
    } else if (outsideButton.isMouseOver()) {
      screen = 5002;
      hideAllButtons();
    } else if (trapButton.isMouseOver()) {
      screen = 5003;
      hideAllButtons();
    }
  } else if (screen === 5001 || screen === 5002 || screen === 5003) {
    if (next2Button.isMouseOver()) {
      screen = 262626;
      hideAllButtons();
    }
  } else if (screen === 262626) {
    if (playButton.isMouseOver()) {
      restartGame();
    } else if (quitButton.isMouseOver()) {
      quitGame();
    }
  }
}

Areas for Improvement:
While the current version of the game is functional, there are several areas for potential improvement:

Expanding the storyline: Adding more options and choices would increase replayability and make the game more interactive. For example, allowing players to pick specific weapons or have more detailed interactions with characters like the spirits could add depth to the game.

Enhanced interactivity: Implementing features like dialogue systems for conversations with spirits or other characters could make the game more engaging. This could involve creating a more complex dialogue management system.

Challenges:

One of the main challenges faced was transitioning from built-in button functions to the OOP approach. This required refactoring a significant portion of the code and it would’ve been so much easier if I just started with OOP.

Moving forward, the focus will be on enriching the game content and enhancing the player’s ability to influence the story through their choices.

The game sketch:

Midterm Project – Sweet Treats

For my midterm project, I decided to create a memory game. Given my unhelpable sweet tooth, I based my game around a bakery. I was partly inspired by the game Cooking Mama, which was a game on the Nintendo DS that I played frequently. My game involves the user picking a recipe, then having to memorise and select the right ingredients for that recipe within the given timeframe. The player is then “rewarded” with the dish if they are successful.

I wanted my game to have a cohesive theme to it, so I ended up creating all the graphics myself. This was by far one of the most time consuming aspects, as I was my own worst critic when it came to the quality of the images. Overall though, I am very satisfied with the way the graphics came out; I think they give the game a more polished and homey look.

One aspect I am particularly proud of is the Game class. Taking Professor Mang’s advice, I implemented a state machine to make the flow of the game easier to manage, as my game consisted of multiple “stages” that it would need to flow through in order to work coherently. This helped organise the code into more manageable sections, rather than a mess of code I’d need to sort through if I needed to change anything.

display() {
    background(255); // Set background to white

    if (this.state === "selection") {
      image(shelfImage, 0, 0, width, height); // Display shelf as background
    }

    textAlign(CENTER, CENTER);
    textFont(font);

    this.allIngredients.forEach((ingredient) => ingredient.display());

    stroke(0);
    strokeWeight(1);
    fill(0);

    if (this.state === "start") {
      image(titleImage, 0, 0, width, height); // Display title screen background image
      textSize(48);
      text("Sweet Treats Bakery", width / 2, height / 2 - 50); // Title Text
      textSize(32);
      text("Click to Start", width / 2, height / 2 + 50); // Subtitle Text
    } else if (this.state === "menu") {
      image(menuImage, 0, 0, width, height); // Display menu screen background image
      fill("#332211");
      textSize(64);
      text("MENU", width / 2, 100);

      buttons.forEach((button) => button.display());
    } else if (this.state === "memory") {
      image(recipeImage, 0, 0, width, height); // Display recipe image as background
      textSize(32);
      text("Memorize These Ingredients!", width / 2, 80);
      textFont(honeySaltFont);
      textSize(35);
      this.correctIngredients.forEach((ing, i) =>
        text(ing, width / 2, 300 + i * 50)
      );

      if (millis() - this.memoryTimer > 3000) {
        this.state = "selection";
        this.selectionTimer = millis();
      }
    } else if (this.state === "selection") {
      textSize(32);
      text("Select the Ingredients!", width / 2, 50);
      textSize(24);
      text(
        `Time left: ${Math.max(
          0,
          5 - floor((millis() - this.selectionTimer) / 1000)
        )}s`,
        width / 2,
        110
      );

      if (millis() - this.selectionTimer > 5000) {
        this.state = "baking";
        this.bakingTimer = millis();
      }
    } else if (this.state === "baking") {
      let elapsedTime = millis() - this.bakingTimer;
      let index = floor(elapsedTime / 1500); // Change image every 1.5 seconds

      index = min(index, 2);

      // Play ticking sound only when baking starts
      if (!tickingSound.isPlaying()) {
        tickingSound.loop();
        tickingSound.setVolume(0.8);
      }

      // Display the oven images
      image(ovenImages[index], 0, 0, width, height);

      textSize(32);
      fill("#332211");
      text("Baking...", width / 2, height / 2);

      // Stop ticking and move to result after 4.5 seconds
      if (elapsedTime > 4500) {
        this.state = "result";
        tickingSound.stop();
      }
    } else if (this.state === "result") {
      image(finalImage, 0, 0, width, height); // Display title screen background image
      textSize(40);
      if (this.checkWin()) {
        text(`Your ${this.selectedDish} is now ready!`, width / 2, 200);
        image(
          dishImages[this.selectedDish],
          width / 2 - dishImages[this.selectedDish].width / 2,
          300
        );
      } else {
        text("Oh no! Those were the wrong ingredients.", width / 2, height / 2);
      }
      textSize(20);
      text("Click to Play Again", width / 2, height / 2 + 250);
    }
  }

  handleClick() {
    if (this.state === "start") {
      this.state = "menu";
    } else if (this.state === "menu") {
      buttons.forEach((button) => {
        if (button.checkClick(mouseX, mouseY)) {
          this.selectedDish = button.label;
          this.correctIngredients = {
            // Storing the correct ingredients of the recipes
            Brownie: [
              "Flour",
              "Sugar",
              "Eggs",
              "Butter",
              "Baking Powder",
              "Chocolate",
            ],
            Cupcake: ["Flour", "Eggs", "Sugar", "Milk", "Vanilla"],
            Pie: ["Flour", "Sugar", "Eggs", "Butter", "Apples"],
          }[this.selectedDish];

          this.memoryTimer = millis();
          this.state = "memory";
        }
      });
    } else if (this.state === "selection") {
      this.allIngredients.forEach((ingredient) => {
        if (ingredient.checkClick(mouseX, mouseY)) {
          ingredient.select();
        }
      });
    } else if (this.state === "result") {
      this.resetGame();
    }
  }

The design of the game is fairly simple in concept, though I still struggled when making the intial base of the code. I had started by creating a prototype version, which I experimented on until I was satisfied with the game flow. That version was plain, with no graphics, and circles used as placeholders for the actual ingredients.

I like to think the final game looks better than this.

Not to say I didn’t struggle even after adjusting the prototype. While inserting the backgrounds and graphics, I realised that I had just simply hardcoded values for the recipes on the recipe selection screen, and that I should use buttons instead in my final code.


Making these buttons turned out to be challenging, as I had to make another class for them. I kept facing an error that would not let me adjust the y and x coordinates of the buttons, leading them to overlap. Several lines of code carefully inspected later, I found the error within the display function of the Game class, which was simultaneously calling the buttons alongside the original button class, leading to the code refreshing everytime to use the wrong coordinates for the buttons. After that hiccup, it was mostly smooth sailing.

I also implemented background music (see if you can recognise the instrumental) and a sound effect to my game. The sound effect is a ticking sound that plays for the duration of the “baking…” scene, which I thought would add a nice little touch to make the game a little more interactive.

Overall, I am very satisfied with my midterm project, and it turned out very close to what I had originally envisioned it to be, making the end product all the more rewarding to see.

Link to full screen version


 

Midterm – Interactive Party

My project is an interactive music visualizer designed to create a party-like experience. It integrates sound, visuals, and interactivity to simulate a dynamic party environment. The motivation behind this project stems from my love for music and aesthetics, aiming to bring both together in a visually engaging way. The experience features different sound modes, including a salsa mode, and reacts dynamically to audio amplitude to enhance the party atmosphere.

Link to Fullscreen:https://editor.p5js.org/Genesisreyes/full/okpJ6jCqf

It features a background DJ image, a GIF animation for the salsa mode, and dynamically changing colors and light effects. OOP is implemented in the LiquidShape class, which generates butterfly-like abstract shapes that move and react to the music’s amplitude. The project also includes button interactions, allowing users to start the party, switch to salsa mode, and switch between songs by clicking, or return to the main page. The use of p5.Amplitude() ensures that elements like background flashing and shape movements are synchronized with the audio intensity, creating a smooth experience.

Key Highlights

1. Object-Oriented Programming (OOP) for Animation

The LiquidShape class is responsible for creating and animating butterfly-like figures that move fluidly across the screen. These shapes:

    • Respond to the music amplitude by changing motion and size.
    • Have two states: floating (trapped) and moving (liberated), which the user can toggle by clicking.
    • Implement procedural animation using noise-based randomization to ensure organic movement.

2. Audio Amplitude Analysis for Visual Synchronization

The p5.Amplitude() function is used to analyze the loudness of the currently playing audio and link it to the visual elements. The butterfly image pulses with the music, making it feel more immersive.

let level = amplitude.getLevel();

let pulseSize = lerp(width * 0.2, width * 0.4, level);
image(butterflyBg, 0, 0, pulseSize, pulseSize);

How this works:

    • amplitude.getLevel() returns a value between 0 (silence) and 1 (loudest part of the track).
    • lerp() smoothly transitions the butterfly image size between 20% and 40% of the screen width based on the music volume.
    • This creates a natural pulsing effect that visually represents the beat.

3. Smooth Audio Transitions Between Tracks

Instead of abruptly switching sounds,  fade-out and fade-in effects were used when transitioning between songs to create a smoother experience.

function mousePressed() {
  if (!started) return;

  let fadeDuration = 1.5; // Fade out in 1.5 seconds
  if (sounds[currentSoundIndex].isPlaying()) {
    sounds[currentSoundIndex].setVolume(0, fadeDuration);
    setTimeout(() => {
      sounds[currentSoundIndex].stop();
      switchTrack();
    }, fadeDuration * 1000);
  } else {
    switchTrack();
  }
}
    • The current song fades out smoothly over 1.5 seconds.
    • After fading, it stops completely and switches to the next track.
    • The new track fades in, avoiding harsh audio jumps.

A major challenge was working with WEBGL, especially in handling 2D elements like buttons alongside 3D rendering. The solution involved creating a separate graphics layer (bgGraphics) for the background image.

function windowResized() {
  resizeCanvas(windowWidth, windowHeight); // Resize the WEBGL canvas
  bgGraphics.resizeCanvas(windowWidth, windowHeight); // Resize 2D graphics buffer
  updateBackground(); // Redraw the background image
}

function updateBackground() {
  bgGraphics.background(0); // Fallback if the image doesn't load

  let imgWidth, imgHeight;
  let canvasAspect = windowWidth / windowHeight;
  let imgAspect = djImage.width / djImage.height;

  if (canvasAspect > imgAspect) {
    imgWidth = windowWidth;
    imgHeight = windowWidth / imgAspect;
  } else {
    imgHeight = windowHeight;
    imgWidth = windowHeight * imgAspect;
  }

  bgGraphics.imageMode(CENTER);
  bgGraphics.image(djImage, windowWidth / 2, windowHeight / 2, imgWidth, imgHeight);
}

One of the best aspects of the project is its ability to capture the energy of a party through interactive visuals and sound. The combination of image overlays, and reactive shapes enhances the user’s engagement.

There are areas that could be improved. One issue is optimizing the responsiveness of visual elements when resizing the window, as some components may not scale perfectly. Another challenge was ensuring seamless audio transitions between different sound modes without abrupt stops. Additionally, adding clear instructions for the users would have been ideal, so they could know that when they click, they can change the music but also collect and liberate the butterflies.

Midterm project – Whack a Mole

For my midterm project, I decided to create my own version of the classic Whack-A-Mole game. The goal was to design an interactive experience where players could test their reflexes by clicking on moles as they randomly appeared on the screen. It was inspired by both traditional arcade versions and mobile games, where I wanted to reach a balance between simple navigation and entertainment.

One of the aspects I’m particularly proud of is the way I structured the game logic. I tried to keep my code modular by organizing key components into functions. This not only made the development process more efficient but also allowed me to tweak game parameters easily. Another feature I really enjoyed implementing was the randomization of mole appearances that ensures that no two games feel exactly the same. The timing and positioning algorithms took some hard work, but I’m happy with how smooth the flow of the game turned out.

How it works: Moles pop up from different holes at random intervals, and the player must click on them to score points. Each mole stays visible for a short duration before disappearing, which requires quick reactions from the player. As the game progresses, the frequency of mole appearances increases, adding a greater challenge over time. To add an element of risk, I implemented a bomb mechanic, meaning that if a player accidentally clicks on a bomb instead of a mole, the game ends immediately. This twist keeps players alert and encourages more precise clicking. The game runs in a continuous loop with constant updates on the screen that track the player’s score. Each successful mole hit adds points, while avoiding bombs becomes crucial for “survival”.

Challenges: One of the biggest issues was making sure the game felt responsive without being overwhelming. Initially, the moles appeared and disappeared too quickly which made it nearly impossible to hit them in time. I had to experiment with different timing settings to find the right balance. Another tricky aspect was collision detection, where I had to make sure that clicks were registered correctly when a mole is hit and not on empty spots or bombs. Debugging this issue took some time, but through testing and refining my hitbox logic, I was able to get it working.

Implementation: The game is built with JavaScript and p5.js, which handle the graphics and interactions. I used free images and sound effects from online stocks to make the game more engaging.  The Game class manages the core mechanics which includes the following:

  • Hole and Object Management – moles and bombs are placed randomly to keep the game unpredictable
  • Scoring System – players earn points for hitting moles, while bombs immediately end the game
  • Difficulty Scaling – as the game progresses, moles appear and disappear faster, making it more challenging
  • Sound Effects and Graphics – Background music, whacking sounds, and animations add to an immersive experienceCode snippets:>>Managing moles and bombs
    if (mouseIsPressed) {
      if (dist(mouseX, mouseY, hole.x, hole.y) < hole.d) {
        mouseIsPressed = false;
        punched = true;
        sounds.whak.play();
        setTimeout(() => {
          punched = false;
        }, 200);
        if (hole.type == "mole") {
          hole.type = "hole";
          game.score += 1;
          game.timer += 30;
        } else {
          sounds.bomb.play();
          gameOver();
        }
      }
    }
    

    >>Registering the mouse clicks

    if (frameCount % (game.difficulty - game.score) == 0) {
      let hole = random(game.holes);
      if (hole.type == "mole") {
        hole.type = "hole";
      } else {
        hole.type = "mole";
      }
      if (random(1) < 0.1) {
        hole.type = "bomb";
        setTimeout(() => {
          hole.type = "hole";
        }, 1000);
      }
    }
    

    >>Game Over logic

    function gameOver() {
      sounds.gameover.play();
      setTimeout(() => {
        sounds.back.stop();
        image(imgs.blast.img, mouseX, mouseY, 250, 250);
        background(20, 100);
        textSize(64);
        text("Game Over", width / 2, height / 2);
        textSize(16);
        text("click anywhere to restart!", width / 2, height - 50);
        textSize(46);
        text(game.score, width / 2, 35);
        state = "gameOver";
      }, 100);
      noLoop();
    }
    
    

    Improvements: Looking forward, there are definitely some areas that I’d like to improve. One feature I would like to add is a progressive difficulty system, where players are challenged at different levels of difficulty. Right now, the game is fun but could benefit from more depth. On top of that, I’d like to upgrade the user interface by adding a “start” and “home” screen, score tracker, and possibly a leaderboard.

    let imgs = {};
    let sounds = {};
    let punched = false;
    let state = "start";
    
    function preload() {
      backImg = loadImage("assets/back.png");
      font = loadFont("assets/Sigmar-Regular.ttf");
      imgs.blast = { img: loadImage("assets/blast.png"), xoff: 0, yoff: 0 };
      imgs.bomb = { img: loadImage("assets/bomb.png"), xoff: 0, yoff: 0 };
      imgs.hammer = { img: loadImage("assets/hammer.png"), xoff: 0, yoff: 0 };
      imgs.hole = { img: loadImage("assets/hole.png"), xoff: 0, yoff: 30 };
      imgs.mole = { img: loadImage("assets/mole.png"), xoff: 0, yoff: 0 };
    
      sounds.bomb = loadSound("sounds/Bomb hit.mp3");
      sounds.back = loadSound("sounds/Game main theme.mp3");
      sounds.gameover = loadSound("sounds/game-over.mp3");
      sounds.whak = loadSound("sounds/Whacking a mole.mp3");
    }
    
    function setup() {
      createCanvas(600, 600);
      imageMode(CENTER);
      textFont(font);
      game = new Game();
      textAlign(CENTER, CENTER);
    }
    
    function draw() {
      image(backImg, width / 2, height / 2, width, height);
      switch (state) {
        case "start":
          sounds.back.stop();
    
          textSize(68);
          fill(255);
          text("WhACK!\na MOLE!", width / 2, height / 2 - 120);
          textSize(16);
          text("press anywhere to start!", width / 2, height - 30);
    
          textSize(26);
          let img = [imgs.hole, imgs.mole, imgs.bomb];
          image(
            img[floor(frameCount / 60) % 3].img,
            width / 2 + img[floor(frameCount / 60) % 3].xoff,
            height / 2 + 150 + img[floor(frameCount / 60) % 3].yoff
          );
          if (mouseIsPressed) {
            mouseIsPressed = false;
            sounds.whak.play();
            state = "game";
          }
          break;
        case "game":
          game.show();
          if (!sounds.back.isPlaying()) sounds.back.play();
          break;
      }
      if (mouseX != 0 && mouseY != 0) {
        push();
        translate(mouseX, mouseY + 10);
        if (punched) {
          rotate(-PI / 2);
        }
        scale(map(game.holes.length, 4, 20, 1, 0.25));
        image(imgs.hammer.img, 0, 0, 150, 150);
        pop();
      }
    }
    
    function mousePressed() {
      if (state == "gameOver") {
        state = "start";
        game = new Game();
        mouseIsPressed = false;
        loop();
      }
    }
    function gameOver() {
      sounds.gameover.play();
      setTimeout(() => {
        sounds.back.stop();
        image(imgs.blast.img, mouseX, mouseY, 250, 250);
        background(20, 100);
        textSize(64);
        text("Game Over", width / 2, height / 2);
        textSize(16);
        text("click anywhere to restart!", width / 2, height - 50);
        textSize(46);
        text(game.score, width / 2, 35);
        state = "gameOver";
      }, 100);
    
      noLoop();
    }
    
    class Game {
      constructor() {
        this.x = 10;
        this.y = height / 2 - 80;
        this.w = width - 20;
        this.h = height / 2 + 70;
    
        this.holesNum = 4;
        this.holes = [];
        this.difficulty = 60;
        this.score = 0;
        this.timer = 4800;
      }
      show() {
        //timer
        if (this.timer > 4800) this.timer = 4800;
        this.timer -= 1.5;
        fill(20, 100);
        rect(10, 5, width - 20, 10);
        fill(255);
        rect(10, 5, map(this.timer, 0, 4800, 0, width - 20), 10);
        if (this.timer < 0) {
          mouseX = width / 2;
          mouseY = height / 2;
          gameOver();
        }
    
        //score
        fill(255);
        textSize(46);
        if (punched) textSize(54);
        text(this.score, width / 2, 35);
    
        if (this.holesNum != this.holes.length) {
          this.holes = this.findHolePositions(1);
        }
        for (let i = 0; i < this.holes.length; i++) {
          push();
          translate(this.holes[i].x, this.holes[i].y);
          scale(this.holes[i].d / 250);
          let img;
          switch (this.holes[i].type) {
            case "hole":
              img = imgs.hole;
              //nothing
              break;
            case "mole":
              img = imgs.mole;
              break;
            case "bomb":
              img = imgs.bomb;
              break;
          }
    
          if (this.holes[i].type == "mole" || this.holes[i].type == "bomb") {
            //check mouse click on mole
            if (mouseIsPressed) {
              if (
                dist(mouseX, mouseY, this.holes[i].x, this.holes[i].y) <
                this.holes[i].d
              ) {
                mouseIsPressed = false;
                punched = true;
                sounds.whak.play();
                setTimeout(() => {
                  punched = false;
                }, 200);
                if (this.holes[i].type == "mole") {
                  this.holes[i].type = "hole";
                  this.score += 1;
                  this.timer += 30;
                } else {
                  sounds.bomb.play();
                  gameOver();
                }
              }
            }
          }
          image(img.img, img.xoff, img.yoff);
          pop();
        }
        if (this.difficulty - this.score < 20) {
          this.difficulty += 30;
          this.holesNum += 1;
        }
    
        if (frameCount % (this.difficulty - this.score) == 0) {
          let hole = random(this.holes);
          if (hole.type == "mole") {
            hole.type = "hole";
          } else {
            hole.type = "mole";
          }
          if (random(1) < 0.1) {
            hole.type = "bomb";
            setTimeout(() => {
              hole.type = "hole";
            }, 1000);
          }
        }
      }
    
      findHolePositions(n, d = 200) {
        let arr = [];
    
        for (let i = 0; i < this.holesNum; i++) {
          let x = random(this.x + d / 2, this.x + this.w - d / 2);
          let y = random(this.y + d / 2, this.y + this.h - d / 2);
          arr.push({ x: x, y: y, d: d, type: "hole" });
        }
        //no hole should overlap
        for (let i = 0; i < arr.length; i++) {
          for (let j = 0; j < arr.length; j++) {
            if (i != j) {
              let d_ = dist(arr[i].x, arr[i].y, arr[j].x, arr[j].y);
              if (d_ < d) {
                n += 1;
                if (n > 50) {
                  n = 0;
                  d *= 0.9;
                  return this.findHolePositions(n, d);
                }
                return this.findHolePositions(n, d);
              }
            }
          }
        }
        return arr;
      }
    }
    

    The Visuals:

Midterm Project Plan

Scope of Work
For my midterm project, I am designing a digital version of the classic Whack-a-Mole game, inspired by the attached references. The goal is to create an engaging and interactive game where players use their mouse to “whack” moles as they pop out of holes. The game should challenge players’ reflexes and introduce risk elements like bombs that add complexity.

The game will start with a Start Screen featuring the game title and a simple “Start” button. I also plan to add access settings, like toggling sound on or off. Once the game begins, moles will randomly pop up from a grid of holes, and the player must click or tap on them to score points. Not every hole will be safe. Occasionally, a bomb will pop up, and hitting it will result in losing points or lives. Players will have a limited time (for example, 60 seconds) to score as many points as possible before the timer runs out. As the game progresses, the difficulty will increase, with moles appearing and disappearing faster, making the game more challenging.

Each successful hit of a mole adds 10 points, while mistakenly hitting a bomb will deduct 20 points or reduce a life. The game will display a score counter and a countdown timer to keep players aware of their progress and remaining time. At the end of the game, an End Screen will appear, showing the final score and offering options to “Play Again” or “Quit.”

I want the grid to contain between 9 and 16 holes, depending on the level of complexity I decide to implement. Moles and bombs will randomly pop up in these holes at varying intervals. The randomness is crucial to establish unpredictability. To add to the challenge, the moles will pop up faster as time progresses, requiring quicker reflexes from the player.

Code Structure

For the game’s development, I plan to use an object-oriented approach. The game will be structured around a few core classes:

  • Game Class: Manages the overall game loop, score tracking, and time countdown
  • Mole Class: Controls the mole’s behavior (when it pops up, how long it stays, and how it reacts to player interaction)
  • Bomb Class: Functions similarly to the mole but triggers penalties when a bomb is clicked
  • Hole Class: Represents each position on the grid, randomly spawning moles or bombs
  • UI Class: Manages elements like the start screen, score display, timer, and end screen

The core gameplay loop will rely on these functions:

  • startGame(): Initializes the game and resets scores and timers
  • spawnMole(): Randomly selects holes for moles to appear
  • spawnBomb(): Introduces bombs with a set probability
  • whackMole(): Detects player clicks and updates the score
  • hitBomb(): Triggers penalties for clicking bombs
  • updateTimer(): Counts down and ends the game when time runs out
  • increaseDifficulty(): Speeds up mole appearances as the game progresses

Challenges and Risks

I expect that one of the most complex parts of this project would be ensuring accurate collision detection. In other words, making sure the program properly registers when a player clicks on a mole or bomb.

Timing is also a big concern. Moles need to appear and disappear at unpredictable but balanced intervals to make the pace of the game flawless and not frustrating.

To tackle these challenges, I plan to build a test script focused on collision detection, using simple shapes before applying it to the actual mole and bomb sprites. This will help me adjust the hitboxes and make sure user interactions feel responsive. I might also test randomization algorithms to ensure that mole and bomb appearances are unpredictable yet adequate.