Week 8 – Midterm Project

Sketch link!

Describe the overall concept of your project (1-2 paragraphs)

My project is a puzzle solving game with an “Ancient Egypt” theme. I wanted to have theme that related to me and since I’m Egyptian, this theme only made sense. Initially, I planned to use cartoon-like images of landmarks in Egypt, but I couldn’t find any images online of what I wanted or there wasn’t enough variety. I also can’t draw so I ended up just using realistic images instead (which I think I found through a website called freepik, but it was so long ago that I don’t even remember anymore). I had a rough plan of how I wanted the different screens within the game to look like in Canva (included in my midterm progress documentation), most things stayed the same in my final project. I found a font online called “khamenet,” which I decided to use throughout. I feel that this really added to the vibe of my project. 

As for the game aspect, I settled on having each piece just be a square, rather than actual puzzle shaped since that would’ve been too difficult to code. The user can move the pieces around using their mouse. I didn’t add any instructions for my game as I felt it was self-explanatory, I tested it with my siblings and they both were able to play the game without any instructions. The only thing that I needed to point out was the ‘peek’ button. The peek button allows the user to see a preview of what the image they’re putting together looks like on the board itself. Initially, I was planning to just have a small reference image at the bottom of the screen, however, the peek overlay option ended up being more helpful and looks more seamless than having something in the corner. While the user plays, there’s background music and a ‘ding’ whenever a piece is placed correctly, if the user turns of the sound, both of these audio elements are muted/stopped. In addition, while the user plays, the game keeps track of the time taken, number of pieces placed, and number of moves taken. Finally, at any point, if the user gives up, they can press the menu button at top right to go back to the main menu. If they do not give up and complete the puzzle successfully, there’s a victory cheer (called a zaghrouta) that plays as well as an ending screen with their time taken. They can press anywhere to restart.

Describe how your project works and what parts you’re proud of (e.g. good technical decisions, good game design) 2-3 paragraphs

For the start screen, the main interactions are choosing the image and the difficulty level. The user can select each of these elements by clicking on them using the mouse. If the user presses within the coordinates of the element, their choice is recorded and the game proceeds accordingly. By default, if the user does not click on anything, the first image is selected and the difficulty is easy. The user can see which option is selected as the selected image has a border around it and selected difficulty is darker in color. Finally, the user starts the game by pressing within the boundaries of the lines around the work “START”. As long as the user is on the start screen, there is some music in the background.

I’m really proud of the aesthetic and the look of my start screen, since I wasn’t sure how closely I would be able to match the idea I had put together on Canva. However, luckily everything came together really nicely. I was really happy with the visual feedback to the user selecting the image and difficulty.

On the game screen, there’s a puzzle board on the left where the user puts the pieces together, a piece tray on the right where all the pieces start out scattered, and a few buttons at the top to control different things. First, top left, there’s the peek button. This button (as described earlier) allows the users to see a light preview of the full image overlaid on the puzzle board. I’m really proud of this element since I didn’t really have a plan of how I wanted to display the preview, so the fact that in the end, it ended up being something so simple is really nice.

// shows a preview of the image being solved, as a background 
function drawPeekOverlay() {
  let img = getSelectedImage();
  if (img) {
    push();
    tint(255, 70);
    imageMode(CORNER);
    image(img, 10, 120, 600, 600);
    pop();
  }
}

Next to the peek button is the volume button which simply either toggles all the sound on or off, which includes the correct ding sound and the background music. A possible improvement is keeping the ding sound audible even when the user silences the music since I feel like that audible feedback can be more helpful.

In the middle at the top there’s an info bar that tracks the time elapsed, how many pieces have been placed out of the total, and the number of moves taken. The time elapsed is tracked by calculating the difference between millis() and the startTime that was recorded when the puzzle began, and counts placed pieces by looping through the pieces array each frame. Finally, on the top right, there’s a menu button which simply takes the user back to the start screen at any point.

As for the actual puzzle, when buildPuzzle() is called it creates a PuzzlePiece  object for every cell in the grid and scatters each one at a random position inside the tray using random(). Each piece stores which row and column it belongs to, so it knows its exact target position on the board. The drag and drop system uses three separate p5.js mouse functions working together: mousePressed() picks up the topmost unplaced piece the user clicked on by looping backwards through the array, mouseDragged() updates the piece’s position to follow the mouse every frame, and mouseReleased() drops it and calls trySnap() which checks if the piece landed within 30 pixels of its correct target — if it did, it locks into place exactly and the border around it turns green. The dragged piece is always moved to the end of the pieces array so it draws on top of everything else. When all pieces are placed, the background music stops, the zaghrouta audio plays, and the state switches to WIN which triggers the win screen on the next frame.

class PuzzlePiece {

  constructor(id, col, row, cols, img) {
    this.id = id;
    this.col = col;
    this.row = row;
    this.cols = cols;
    this.img = img;
    
    // calculate piece size based on the no. of cols and board size
    this.w = 600 / cols;
    this.h = 600 / cols;

    // current position, updated in buildPuzzle
    this.x = 0;
    this.y = 0;
    
    // target position (where the piece actually belongs on the board)
    this.targetX = 10 + col*this.w;
    this.targetY = 120 + row*this.h
    this.isPlaced = false;
}
draw() {
    push();
    // draw the actual image slice
    if (this.img) {
      let sliceW = this.img.width / this.cols;
      let sliceH = this.img.height / this.cols;
      let sliceX = this.col * sliceW;
      let sliceY = this.row * sliceH;

      imageMode(CORNER);
      // draw slice at current this.x and this.y
      image(this.img, this.x, this.y, this.w, this.h, sliceX, sliceY, sliceW, sliceH);
    }
    // draw border based on 'state'
      noFill();
      if (this.isPlaced) {
      stroke("#2a7a2a"); // green, if correct
      strokeWeight(3);
    } else if (this === dragging) {
      stroke("#e59828"); // orange, if being moved
      strokeWeight(3);
    } else {
      stroke("#6b2705");
      strokeWeight(1);
    }
      rect(this.x, this.y, this.w, this.h);
      pop();
    }

    // check if the mouse is touching this specific piece
   contains(mx, my) {
    return mx > this.x && mx < this.x + this.w && my > this.y && my < this.y + this.h;
  }
// snap logic
  trySnap() {
    let d = dist(this.x, this.y, this.targetX, this.targetY);
    if (d < 30) {
      this.x = this.targetX;
      this.y = this.targetY;
      this.isPlaced = true;
      return true;
    }
    return false;
  }
}
Describe some areas for improvement and problems that you ran into (resolved or otherwise) (1-2 paragraphs)

I think one main area for improvement is having actual puzzle shaped pieces rather than just squares. I think that would’ve definitely my idea across more, however, considering my skill set and based on tutorials and p5.js reference page, square seemed more feasible. I think adding instructions to clarify what the peek button does would’ve also been helpful, but my hope was that the user would just play around with the buttons and eventually discover it themselves (or ask me how to view a preview and I would guide them to use the button). I also would’ve loved to add some visual animations associated with the background music and also have different music/audio for each picture, but I completely forgot about the sound requirement till the last minute so there was a bit of a time constraint there.

I ran into a few problems throughout writing the code for this project, however, at this point, most of the problems have slipped my mind. One thing that I do remember since it was added last minute is that when I added audio files, my code was stuck in an endless loading loop. I was stuck on that for around 10 minutes, thinking maybe the files are just taking longer than usual to load, before I checked back through our class notes and realized I needed to add the sound library to my index.html file. Luckily, that wasn’t too big of an issue. Whenever I did run into any problems with my code (debugging) or was stuck with how to begin or how to proceed with specific features, I did get some help from Gemini as it would guide me on what topics to cover, give me links to videos to refer to, and what pages would helpful from the p5.js reference so I felt that I learnt a lot more that way.

Midterm – Zere Kystaubayeva

View only link: https://editor.p5js.org/lunaland/full/lguzYiIJr

1. Concept of the game: I decided to create an interactive piece called “Day in the Life of a Cat”, where the user gets to experience normal cat stuff that cats get to do throughout their days.

  • I have a hairless cat, Luna. I miss her a lot since she lives with my grandparents, and I decided to remind myself of her by creating this interactive experience. Everything that the game includes is pretty much all she does every day – eat, play with random things, terrorize insects outside, and poop.
  • What and how: The user gets to experience morning, day, and night as a cat, clicking on different items and discovering what they can do with them. I decided to keep the interface very simple, since overcomplicating things would make it hard for me personally to produce a good interactive experience. Each part of the day has its own mini-game to complete in order to transition into the next part of the day. The user collects points for completing the mini games. You get 30 points in total. You can play the game again after finishing it.
  • Additional stuff: Initially, I wanted to draw all of the backdrops myself or create them using code, using simple shapes and colors. Then I remembered a game I always wanted to play called Animal Crossing, and wanted to create a similar atmosphere – cute and simple background decor. I wanted to use images from the internet, but none were “flat” enough for a backdrop, and I couldn’t insert my own images in there due to a lack of space. In the end, I decided to generate Animal Crossing and other cute mini-games-inspired backdrops, as well as all of the items that are needed to complete the game. I generated everything through ChatGPT image generation.

2. Code that I’m proud of and why

The part of my code that I am most proud of is the scene system and the way the game moves from one part of the day to another. I used different game states like the instruction screen, morning scene, day scene, night scene, and ending scene. This made the project feel real and interactive, as if it’s truly a cat that goes about its day. I am proud of this because it helped me organize the game clearly and made it easier to control what the user sees at each moment.

3. What I found challenging

The most challenging part for me was making the different scenes connect smoothly. At first, I struggled with where the player should go after each interaction. I had to think carefully about how to move from the instruction screen to the morning scene, then to mini-game scenes, then back again, and finally to the ending, without making it confusing for the user and me. This taught me that you need to have both the idea and the execution planned to detail before starting the process, because I had to redo a lot of stuff to create mini-game scenes in between the bigger scenes.

Midterm “Nebula Chase” Game – Kamila Dautkhan

Sketch

The Concept

I wanted to make something that felt exciting and had that arcade game tension where you’re always on edge. The idea came from thinking about those old falling-object games, but I wanted to add my own twist, for example, what if the game itself reacted to how much time you have left? What if it got harder as you played? And what if the music changed when things got intense? So Nebula Chase became this space game where you’re flying through a nebula collecting stars while dodging bombs. Simple concept, but I put a lot of work into making it feel engaging.

The Game in Action

Start Screen: 

When you first load the game, you see the title with this “CLICK TO START” button. I made the title bounce a little using a sine wave. And in order to make it easier for the user to understand the instructions of the game, they can press ‘I’ to see them. 

Gameplay:

Once you’re playing, the screen gets busy very fast. Yellow stars fall down, those are the good ones, and red bombs come at you too. Your ship follows your mouse, and you will have only 60 seconds to grab as many stars as possible without hitting bombs. The UI at the top shows your score, timer, and lives. I made the timer turn red and the whole screen flash when you’re under 10 seconds. 

Object-Oriented Design

I used three classes to organize everything:

  1. Star Class 
  2. class Star {
      constructor() {
        this.x = random(width);
        this.y = random(-600, -50);
        this.speed = random(2, 4) * difficulty;
        this.wobble = random(TWO_PI);
      }
      
      move() {
        this.y += this.speed;
        this.wobble += 0.05;
        if (this.y > height + 50) {
          this.y = -50;
          this.x = random(width);
        }
      }
      
      display() {
        push();
        translate(this.x + sin(this.wobble) * 10, this.y);
        tint(255, tintAmount, tintAmount);
        image(starImg, -20, -20);
        pop();
      }
    }
    

    Each star wobbles side to side as it falls which makes them really hard to catch. The speed multiplies by the difficulty variable, so as the game goes on, everything gets faster and it makes everything harder for the user. 

    1. Bomb Class – The obstacles
    class Bomb {
      constructor() {
        this.x = random(width);
        this.y = random(-600, -50);
        this.speed = random(3, 5) * difficulty;
        this.rotation = 0;
        this.pulsePhase = random(TWO_PI);
      }
      
      move() {
        this.y += this.speed;
        this.rotation += 0.05;
        this.pulsePhase += 0.1;
        if (this.y > height + 50) {
          this.y = -50;
          this.x = random(width);
        }
      }
      
      display() {
        push();
        translate(this.x, this.y);
        rotate(this.rotation);
        let pulseSize = 1 + sin(this.pulsePhase) * 0.15;
        scale(pulseSize);
        image(bombImg, -22, -22);
        pop();
      }
    }

    I made the bombs rotate and pulse to make them feel dangerous. They also move slightly faster than stars on average, which created the pressure for the player. 

  3. Particle Class
class Particle {
  constructor(x, y, col) {
    this.x = x;
    this.y = y;
    this.vx = random(-3, 3);
    this.vy = random(-3, 3);
    this.life = 255;
    this.col = col;
  }
  
  update() {
    this.x += this.vx;
    this.y += this.vy;
    this.vy += 0.1;  // Gravity
    this.life -= 5;
  }
  
  display() {
    fill(red(this.col), green(this.col), blue(this.col), this.life);
    ellipse(this.x, this.y, this.size);
  }
}

Whenever you collect a star or hit a bomb, it creates approx 15 particles that spray out in random directions. They fade out over time and fall slightly from gravity. I made this just to make the interactions feel way more satisfying. 

The Spaceship:

I didn’t want to use basic shapes for everything, so I made custom graphics using p5’s createGraphics():

playerImg = createGraphics(60, 60);
// Outer glow
playerImg.fill(0, 255, 200, 100);
playerImg.triangle(30, 5, 10, 50, 50, 50);
// Main body
playerImg.fill(0, 255, 150);
playerImg.triangle(30, 10, 15, 45, 45, 45);
// Cockpit detail
playerImg.fill(100, 200, 255);
playerImg.ellipse(30, 25, 8, 12);

Since to come up with this code was very challenging for me, I used these resources to help me navigate:

https://www.deconbatch.com/2022/01/blendmode-add.html

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

As you can see the stars and bombs have this glowing effects  because I drew multiple overlapping circles with decreasing opacity to create that glow. The stars are yellow or white and the bombs are red with a darker center that kind of looks like a skull.

The code I’m proud of:

It is definitely the sound system since I didn’t want to just upload the mp3 because I didn’t find the suitable one. So, I decided to generate it myself , that’s the reason why I spent a lot of time on it.

function updateBackgroundMusic() {
  if (timer > 10) {
    // Calm ambient music
    bgMusic.amp(0.15, 0.5);
    bgMusic.freq(220 + sin(frameCount * 0.02) * 20);
    bassLine.amp(0.1, 0.5);
    urgentMusic.amp(0, 0.5);
  } else {
    // DRAMATIC URGENT MUSIC FOR FINAL 10 SECONDS
    bgMusic.amp(0.05, 0.3);
    urgentMusic.amp(0.25, 0.3);
    urgentMusic.freq(110 + sin(frameCount * 0.1) * 30);
    bassLine.amp(0.2, 0.3);
  }
}

For most of the game you hear this calm wave that wavers slightly (that’s the sin(frameCount * 0.02) * 20 part, it creates a slow sound in pitch). There’s also a quiet bass line. But when you hit 10 seconds left the music completely changes.  The bass gets louder and the calm music fades. I just wanted to make feel the pressure for the user.

Reflection

I’m really happy with how this game turned out. The music transition at 10 seconds is probably my favorite part because it genuinely makes the game feel more intense and interesting. The particle effects were also surprisingly easy to implement but it added so much to the feel of the game. The biggest thing I learned was about game balance. It’s one thing to make mechanics work, but making them feel good for the user is way harder. I probably spent as much time tweaking numbers like how fast things fall and etc. as I did writing the actual code.

 



Midterm Project – SignSprint

Sign Sprint

Concept

SignSprint is a game based on computer vision that recognizes 7 different hand gestures which are Thumbs up, Thumbs down, Victory, Pointing Up, Victory, Closed Fist, I love you and Open Palm. The game works on a gesture recognition machine learning model by Google AI for Developers through MediaPipe Studio. The model can be tried out here.

The whole concept of the game is to make as many signs within a specified time period. An array of the possible hand gestures is created and one is randomly displayed at a time and the user is meant to make the hand gesture corresponding to the gesture being displayed. The score of the user is recorded and displayed at the end of the designated time period. The ML model uses measurements to accurately estimate a hand gesture and detect it. A validation condition is used to check if the detected gesture is exactly as the the target gesture and only then will the target gesture change. The model has also been set to detect one hand at a time so using multiple hands will cause the gesture not to be detected.

The main function sets up the machine learning model, detects gesture and randomly selects a target gesture

 

 

 

Code I am proud of

function drawGame() {
  background(0);
  image(video, 0, 0, width, height);
  
  // Timing game
  let elapsed = (millis() - startTime)/1000;
  if (elapsed >= gameTime) {
    gamestate = "end";
    }
  
  // Gesture detected & scoring
  let detectedName;
  let detectedEmoji;
  
  if (results && results.gestures && results.gestures.length > 0) {
    detectedName = results.gestures[0][0].categoryName;
    detectedEmoji = gestureMap[detectedName];
    
    if (targetGesture.name == detectedName && !matchCooldown) {
      score++;
      // sound for feedback
      matchCooldown = true;
      correct.play();
      pickNewTarget();
      }
    }
  
  // Target Emoji
  if (targetGesture) {
    textFont("OpenSans");
    textAlign(CENTER, TOP);
    textSize(70);
    text(targetGesture.emoji, width/2, 30);
  }
  
  // Score
  textFont(font);
  fill(255);
  textAlign(RIGHT, TOP);
  textSize(30);
  textFont("OpenSans")
  text("⭐", width-55, height-45);
  textFont(font);
  text(score, width-20, height-45);
  
  // Time remaining
  textSize(40);
  text(ceil(gameTime-elapsed), width-20, 20)  
}

The code I am proud of is the drawGame function. This is the function that contains the bulk of the game mechanism. It first shows the camera output of the the program, shows the target gesture as an emoji, detects the gesture of the player through the camera and checks if it is the same as the required gesture. If the detected and target emojis are the same, it increases the score and creates a new target. The function also displays the time left and the current score on the screen. Finally the function has a condition that automatically switches the game to the end screen when the specified time is elapsed.

How it was made

The game mainly runs on the the hand gesture machine learning model which was stated above. The biggest challenge in making this game was importing the gesture recognition model in p5js. I used cluade AI to help in this process. With the help of AI, I was able to modify the html file and create functions in order to import the right model into the p5js file which enables us to run the game. Claude AI was also used in the making of the setupMediaPipe and detectGesture() function to enable the game run the scoring system.

The game code was mostly composed of if conditions and booleans for the game logic. The start and end screen background was fully generated by Gemini AI and the sounds for the game, which are the theme sound and the correct matching sound were obtained from Freesound.

Reflection

This was a really fun game to create. I got to explore all the concepts deal with class and I got a greater understanding of decisions structures and also learn how import models into p5js. A possible improvement in the increasing the number of hands that can be in the game and the hand gesture can be further developed to remotely control other computer devices to generate art and just express creativity. I see this project as stepping stone to explore my interest in computer vision and its possible application in interactive media and I am excited to see how I can blend this knowledge and skill with physical computing.

Midterm Project: A Handful of Color

Project:

Concept:

The main concept behind this project was my 3rd production assignment “Colorful Concoction“. Ever since that asssignment I’ve wanted to build up on it and create something generative to the user and interactive in the same sense. So when I found out that we had access to tools such as hand tracking and face tracking, I knew I wanted to go deeper into creating something that would truly give a fun feeling to the user.

I decided to create a simple project where the user could generate balls of different color and with the user’s hand, they are able to decide the size of the given ball. So if the user chooses to make a screen filled with tiny balls, they are able to. Or if they want larger ones they can as well. It gives that sense of variety for the user. And then they are able to interact with the balls using their own hands by flicking them around and pushing them. This was the main highlight of what I wanted to do to make it truly fun for the user.

So how does it work and what am I proud of:

The program starts with an instructions screen I whipped up in Canva, waiting for the user to click to start the actual program. Going from my own game that I’ve made for a different course, it was done using different states for when the program should be starting, and when it should be played (which is why they’re named game state).

function draw() {
  //Different state for Start, to show the instructions screen
  if (gameState === "START") {
    //Displays the instruction screen
    image(instructionsImg, 0, 0, width, height);
    
  } 
//Starts the actual program
  else if (gameState === "PLAY") {

    background(0);

Now in order for the user to actually create the ball, they must firstly allow camera access and then present their hand to the camera so it can track their position of their thumb and index finger. The tracking was done simply with ml5.js’ hand tracking library which was of course the main part of the whole project. Now in terms of the size of the ball, that was simply done with seeing the distance between the points of the thumb and index finger, and from there we can generate a simple circle. Also to note all of the balls were added to an array so that it can be dynamically updated when needed.

let finger = hand.index_finger_tip;
        let thumb = hand.thumb_tip;

        //Finds the midpoint between the thumb and index finger
        currentX = (finger.x + thumb.x) / 2;
        currentY = (finger.y + thumb.y) / 2;
        
        //Based off the distance, it determines the size of the ball
        currentPinch = dist(finger.x, finger.y, thumb.x, thumb.y);

        //This makes a new random color for the cursor on screen, or in this case the ball
        cursorColor = color(random(255), random(255), random(255), 200);
        
        //Creating the random colored ball
        fill(cursorColor); 
        stroke(255);
        strokeWeight(2);
        circle(currentX, currentY, currentPinch);

Problems and how I solved them:

The main part of the program was also the main problem that I faced. Getting the hands to be displayed on screen while also giving the balls a notion that they should think of the hands as a wall was a bit tricky to write. Not to mention that doing hand tracking with a camera isn’t as smooth as it could be so it took a good amount of time before I got it working.

Firstly, using the ml5js reference, all of the joints of the hand were marked with numbers. Using this, I made an array where all of the joints can be loaded and I could then dynamically add lines to them and dots to signify the joints of the hand. handPose keypoints diagram

This was done using a for loop that constantly loads the lines and joints depending on where the hand is, so that it is actively updating where the hand is on the screen instead of just, having a static image. I used Google Gemini to help me out with this part as I struggled with how I would conenct the hands using lines and create the skeleton.

//This draws the lines for the skeleton of the hand
for (let i = 0; i < connections.length; i++) {
  let pointBase = hand.keypoints[connections[i][0]];
  let pointEnd = hand.keypoints[connections[i][1]];
  line(pointBase.x, pointBase.y, pointEnd.x, pointEnd.y);
}

//This draws the joins as white dots
fill(255);
noStroke();
for (let i = 0; i < hand.keypoints.length; i++) {
  circle(hand.keypoints[i].x, hand.keypoints[i].y, 10);
}

And after that it was simple as to just adding vectors to the balls and having them repeal off the canvas’ wall and also off the hand as well. Finally after a good while, I managed to get it to work. Now your mileage may vary, as some camera’s capture it better than other (I myself was stuck with an older, bit blurry camera).

Areas of Improvement:

Most of all I think the main area of improvement for me is just probably to add more customization should I want to. I was debating on having some way for the user to change color, but time constraints sort of hampered that notion. I was thinking even this by itself can be enough as adding too much can drown out the simple nature of the project.

I think another area is making it seemless for the user to interact with the program fully with their hands. I tried but I wasn’t getting the results I desired and ultimately I decided just to stick with keyboard inputs for most things which is unfortunate. Possibly it could be a nice idea to have it so the other hand can be clamped and the ball could spawn or somehow find a gesture with a hand that could change modes. But overall I’m happy with how it turned out in the end.

Midterm: Morocco’s Door Studio

Overall concept:

THE DESIGNING GAME! The main reasons why I created this game is because I love designing, and I have been learning on my own throughout the years (I have helped friends and family members with their businesses since design is an important factor in this field). With this background I wanted to create something for people who have the same interest, and for those who want to explore. The second reason is that since I am half Moroccan, I wanted to include a personal touch to my work and connect it to my culture. I decided to create a game where users can design one of my favorite elements of Moroccan architecture, which is the doors. Instead of simply creating a game that includes templates and colors, after speaking to Professor Mang, I decided to make it more interactive and generative. This way players can add their own shapes, change colors, and control the speed of the patterns. The shapes are also randomly generated, which is one of my favorite parts because my game includes randomness but at the same time it makes every design unique and unpredictable.

In Moroccan architecture, the Riyad is one of my favorite styles because of the level of detail and craftsmanship. It’s known for its intricate patterns, zellige tiles, and carefully designed spaces. What I find most interesting is how much time and effort goes into creating these details by hand, sometimes taking weeks or even months and up to years depending on the piece. I also grew up seeing these designs in my own home in Morocco, especially in fountains, tiled walls, and stairs which inspired me to recreate this experience digitally. I realized that there aren’t many games that focus on cultural design, especially Moroccan culture, so I wanted to create something that represents it in an interactive and modern way.

I started this project with an idea, although I had multiple ideas and sketches I decided to stay with this one. I mainly feared that it would be hard to create but in fact although it was challenging (starting before spring break and then with everything happening it was confusing to go back to unfinished work so a part of me wanted to start everything all over again and even change the concept but I didnt!) I loved every part of it because the concept truly speaks to me.

Inspo from Moroccan Architecture: 

Embedded sketch: (click to start!) [for full screen click here]


How it was made/how it works:

The project is built using a state-based system (a system where it changes, operates, or makes decisions based on the current “state” of the process), where the player moves through five different screens or pages as I call them: the home screen, help screen, info page, door selection, and finally the interactive “studio.” Each page is controlled by a variable that updates depending on where the user clicks, making the navigation simple but effective. I’ve always liked using Canva which is why I decided to create shapes and patterns to make something inspired by Moroccan architecture while still keeping it visually aesthetic and aligned with the overall concept. I also decided to look at Aya’s class since they finished their midterms, I got inspired to generate an image using Gemini which pushed me to experiment more with tools like Gemini. Instead of just using basic images, I uploaded my own references and used Gemini to help generate and refine visuals for the first three pages (home, intro, and instructions). So while the first 3 pages (home page, intro, and instructions) are generated by ai using an original existing image that I uploaded I still did lots of the designing myself in the other pages. When it comes to the doors I used a base vector from Vecteezy, but since the quality was low, I enhanced and clarified the design using Gemini so it would look clean and detailed in the final game. For the sound, I wanted something that matched the cultural vibe of my project and game so I searched for Moroccan-style audio on TikTok. I wanted to find a sound I can play in the background and a nice click sound effect, the click sound I found actually reminded me of games I used to play as a kid (friv, roblox, blocks). When I found the sounds I liked I had to convert the files into MP3 format since p5.js does not support formats like MOV or MP4. After that, I uploaded all the files into the sketch, I initially thought this would be the hardest part, but it turned out to be one of the easiest since it mainly involved organizing and dropping files correctly. I also added some the px size next to the size of the shapes because of my background in coding, I also added the HSB slider numbers 0-360° but I think in the future having buttons for it to be easy for people would be better. This game is not meant for everyone, but specifically for people interested in design and creative exploration.

When it comes to coding, (the fun part!) I mainly relied on class lecture notes from Professor Mang and Professor Aya because the material and examples available are always extremely helpful and always makes my process a lot smoother. For the main interaction, I used Object-Oriented Programming by creating a Tile class so every time the player clicks inside the door area (the white part), a new tile (which are the shapes) is created and added to an array. Each tile is independent so it stores its own size and color based on the slider values at the exact moment it is placed but for the speed slider, all the tiles change accordingly. One part I’m especially proud of is the geometric pattern system, specifically the 8-point star. I created this using a loop that rotates shapes by PI / 4 (45 degrees) around a central point. By repeating this rotation, I was able to recreate patterns inspired by traditional Moroccan zellige designs. Even though the code itself is relatively simple, the final result looks detailed and visually complex. I also focused on the user interface. I used createDiv() along with .parent() to group the canvas and sliders into one container, which keeps everything centered and organized. This idea came from researching how to structure UI elements because I thought I needed to research it a bit more. By doing this, the sliders stay aligned with the canvas and the whole project feels more like a complete application rather than just a basic sketch. The sliders allow users to control size, color, and speed, making the experience interactive and personalized instead of static.

Resources:

From p5.js:

  • parent() + createDiv(): I used this to attach my sliders and canvas to a single createDiv(). I had to use this to keep my UI organized and in position on the page.
  • this. learning how to use the [this.] function I used this.s inside a constructor function for a specific class.
  • colorMode(HSB): I used HSB (Hue, Saturation, Brightness) so that my color slider could be used as a 360 degree rainbow wheel since the game is a design studio.

    Other:
  • The Coding Train: How to Rotate Shapes in p5.js (translate, rotate, push, pop): This helped me understand how to use push() and pop() in my Tile class so that the rotation of one star wouldn’t affect the rest.
  • MDN Web Docs (Early Return): I used return in my mousePressed function to stop the code once a button was clicked. This helped me fixed an issue (the bug) where a tile would accidentally be placed behind the “Home” or “Screenshot” buttons.
  • Vecteezy Moroccan Door Vector: For the architectural doors which I then refined and cleared up using Gemini AI.
  • Aya Riad lecture notes: Anytime I forgot how to use a concept like PI or OOP and most specifically the mouse functions because I was able to go back and look at the slides which is always helpful!
  • Mang lecture notes: I always referred to the lecture notes for writing the code and functions as well understanding the bug in my code by checking the examples.
  • Gemini’s images: creating and clearing up images
  • Converter: to convert images and sound files
  • Canva: used to help create all the pages
  • Click sound from TikTok
  • Moroccan oud sound from TikTok

    Code:

 for (let i = 0; i < 8; i++) { //option 3: star pattern using rotated rectangles. i got help from ai for this part
rotate(PI / 4); 
rect(0, 0, this.s, this.s / 4,2); } //rotates 45 degrees for each of the 8 petals
}

This part of the code creates one of the more complex patterns in my project, which is the 8-point Moroccan star inspired by Zellige designs. Instead of drawing it manually, I used a loop that runs 8 times and rotates the shape by 45 degrees each time. With the help of AI, I was able to better understand how this pattern works and use it to create a repeating rotational design. Even though the code itself is simple, the result looks detailed and reflects real Moroccan geometric design.

class Tile {
constructor(x, y, type) { //constructor to allow the tiles to appear
this.x = x; //remembers where the user clicked horizontally
this.y = y; //remembers where the user clicked vertically
this.type = type; //remembers the type/shape
//captures the current slider values at the exact moment of placement
this.hue = colorSlider.value(); //color
this.s = sizeSlider.value(); //size
}

This constructor is what makes each tile unique! When a tile is created, it stores the exact position, type, size, and color based on the slider values at the moment the user clicks. This means that even if the user changes the sliders later, the old tiles stay the same instead of updating. Basically, each tile “remembers” how it was created, which makes the design feel more layered and personalized instead of everything changing at once.

 //Home button: house icon which will be used to go back to the home screen
let homeBtn = dist(mouseX, mouseY, 745, 356); //the center of the house icon location is x:745, y:356
if (homeBtn < 55) { //if the click is within 55 pixels the home button will be triggered 
clickSound.play(); //sound effect
currentPage = 2; //return to the main home screen
tiles = []; //clear canvas
hideSliders(); //sliders removed to avoid overlap
return; //pauses/stops the function
}

The ‘Early Return’ problem I faced. This part of the code solved one of my biggest issues because before adding this, whenever I clicked the Home button, it would also place a tile behind it, which messed up the design. By using return, the function stops immediately after detecting the button click, so no extra code runs after that. This makes sure the button works properly without triggering other actions at the same time.

Personal Sketches:

Code I am proud of + Areas for improvements and problems I ran into:

One of the biggest problems I ran into was when I clicked the “Home” or “Screenshot” buttons in the arch design pages, the program would accidentally place a tile on the door behind the button. This kept happening, and I didn’t know how to fix it until I used Early Return logic. Now, when the code detects a click on a button, it immediately stops the rest of the function, so no extra tiles are placed and the interaction works properly. Another issue I faced was when pressing the Home button on the arch design page, it would take me back to the home screen, but the sliders would still be visible, which made the interface confusing. I fixed this by explicitly hiding the sliders when leaving the studio page, so they only appear where they’re actually needed.

Overall, the idea of creating a game sounded scary at first. Before entering Intro to Interactive Media, I didn’t think I would be able to build something like this, let alone make it personal and meaningful. I’m really proud that I was able to create a full interactive experience and connect it to my culture. Once again this game is a designing game, its for people who love design and want to create something close to those historic designs but online. However, there are still areas I would improve. For example, I would expand the variety of shapes instead of only having a few options. I could also add an information button where users can learn about Moroccan architecture, the purpose of the game, and cultural facts. Another improvement would be giving users direct control over shape selection (like buttons for each shape) instead of relying only on randomness and sliders, which would make the experience feel more intentional and user-controlled.

Midterm Project: Where is the ghost?

Concept 
This project is a small “ghost hunting” camera game inspired by Identity V. In Identity V, a detective enters a mysterious place and slowly discovers clues. I borrowed that detective-in-a-secret-castle feeling and turned it into an interactive p5.js experience. The player is a brave “ghost catcher” who explores an ancient castle at night, hears strange whispers, and tries to help the people living there by collecting evidence.

I also wanted to change the usual mood of ghosts. Ghosts don’t always have to be terrifying. In my game, the ghosts are faint and mysterious in the live scene, but when you successfully capture one in a photo, it becomes cute and playful (with a tongue-out expression). I like this because it matches how cameras work in real life: people often want to look “better” in front of the camera, and the photo becomes a different version of reality.

How the project works + what I’m proud of 
The game starts on an instruction screen with a short story setup, then waits for the player to press a key or click a start button. During gameplay, the mouse controls a flashlight that reveals the scene. Ghosts appear only sometimes, and they are only visible when they are inside the flashlight area. To “capture” a ghost, the player takes a photo (click or space) while a ghost is inside the light. The photo preview shows the captured frame like a polaroid, and if a ghost was caught, it displays a special “cute ghost” version. The game ends when the player captures enough ghosts, runs out of time, or runs out of film, and then it offers a restart without refreshing.

I separated “live view” from “photo view.” In the live scene, ghosts only count if they are currently visible AND inside the flashlight radius (so the player must aim and time it). Then, after a successful capture, I draw a special “tongue ghost” onto the captured image buffer (photoImage). This makes the camera feel meaningful: it doesn’t just add score, it changes the ghost’s personality in the “photo reality,” matching my concept that people want to look better on camera.

let capturedGhost = null;
for (let g of ghosts) {
  if (g.isVisibleNow() && g.isInsideFlashlight(mouseX, mouseY)) {
    capturedGhost = g;
    break;
  }
}

Then I did the important trick: I made a separate “photo layer” instead of drawing everything directly on the main screen. I create a new graphics canvas for the photo, and I copy the current screen into it. That’s what makes the photo feel like it’s frozen in time:

photoImage = createGraphics(width, height);
photoImage.image(get(), 0, 0);

After that, if I really did capture a ghost, I draw the cute tongue ghost onto the photo layer (not the live game). And I add to my capture count:

capturedGhost.drawTongueStrongOn(photoImage);
ghostsCaptured++;

Once I got this working, the whole game started to make sense. The live view stays spooky and subtle, but the photo becomes the “evidence,” and the ghost looks cuter in the picture—kind of like how people also want to look better when a camera points at them.

Areas for improvement + problems I ran into 
One area to improve is balancing and clarity. Sometimes players may miss ghosts too easily, depending on timing and where the flashlight is. I want to tune the ghost visibility timing and the capture conditions so it feels fair but still challenging. I also want to add clearer feedback when a ghost is nearby so the player can learn the game faster.

Adding sound was harder than I expected because browsers don’t just let a game play audio whenever it wants. In p5.js, if you try to play sound automatically when the page loads, most browsers will block it. They only allow audio after a real user action, like a click or pressing a key. At first this felt confusing, because my code was “correct,” but nothing played. So the challenge wasn’t only choosing sounds—it was designing the game flow so sound is unlocked in a clean way.

To fix that, I made sure audio starts only after the player begins the game on purpose. When the player presses SPACE (or clicks START), I call my audio setup function ensureAudioStarted(). Inside that function I use userStartAudio() (from p5.sound) to unlock audio, then I start my sound sources (an oscillator and a noise generator) at zero volume so they’re ready but not making noise.

Midterm “Ladushki”

Sketch

* In order to play the game, go to p5 sketch and give access to the camera!

Concept

I created a game that is controlled by user’s video input. In Russia, we play a clapping game called “Ладушки” (ladushki; in English it’s called Patty Cake), where you need to match the rythm of the other person clapping, as well as their hands (right to right, left to left, two hands to two hands). A cute kind girl in the room welcomes the player to play this game with her, starting the game after a short tutorial.

However, if the player fails to match the girl’s rythm and handpose, she will get more and more upset. With more mistakes, the girl will clap faster, and her anger will distort the environment and sound around her. What happens if you manage to fail so many times that she reaches the boiling point? Play and find out.

Proccess of Development & Parts I’m Proud of
Sprites & Design

To create the sprites, I first created a character on Picrew, so I can later edit the image of a complete, well-designed in one style character. I chose the design of the girl to be cute-classy to fit the mood of the game.

After that, I inserted the photo to Nano Banana to pixelate to 16-bit and extend the image. After that, I edited the image in Canva, so all the faces, hands positions are properly aligned, and the image has all 4 positions with 4 different faces.

Sound

The sounds from the game were generated and/or taken from open-source copyright free resources. The background music was created using Suno AI using the following prompt:

Dreamy chiptune instrumental, midtempo, modular sections built for easy tempo shifts, Playful square leads carry a singable main motif, doubled an octave up on repeats, Soft, bouncy drum kit with rounded kicks and brushed snares; bubbly sub-sine/square bass locks to a simple walking pattern, Light 8-bit arps and gentle pitch bends sparkle at phrase ends while warm, detuned pad layers smear the edges for a cozy, nostalgic arcade glow, Occasional breakdowns thin to arps and pad swells before the full groove pops back in with extra countermelodies for an intensifying, joyful loop, playful, nostalgic, light, warm, soft, gentle, bright

Other sounds, such as clapping sounds, screaming sound were taken from Pixabay.

I had a lot of manipulations with sound for its speeding up/distortion for creepy effect.

update() {    
  //for sounds
  let current_rate = map(this.level, 50, 100, 1.0, 1.3, true);
  soundtrack.rate(current_rate);
  if (this.level >= 70) {
    let intensity = map(this.level, 70, 100, 0, 0.3); 
    distortion.set(intensity); // set the distortion amount
    distortion.drywet(map(this.level, 70, 100, 0, 0.2));
  } else {
  distortion.drywet(0); // keep it clean under level 70
  }

Here, I use few methods from p5.js sound reference page. Background soundtrack is connected to the distortion variable that can be seen in the code. By mapping the rate (speed of the soundtrack) and intensity (the distortion amount), as well as drywet value (for reverbing) and connecting all these values to the background soundtrack, the sound effect and background music slow but noticeable change is created.

ml5

The fundamental part of my project is hands tracking, which was implemented using ml5.js HandPose ML model.

The implementation process was carefully explained in my previous post since it was the first step in the development. I didn’t change this part since then, but I built up on closed palm pose detection: I added the following condition:

//DISTANCE BETWEEN THUMB AND PINKY is also counted for state of the hand
//define what means when hand is open and set status of the user's hand positions
if (hand.keypoints && hand.keypoints.length >= 21) {
  let isHandOpen = (
    hand.keypoints[4].y < hand.keypoints[2].y &&   
    hand.keypoints[8].y < hand.keypoints[5].y &&   
    hand.keypoints[12].y < hand.keypoints[9].y &&  
    hand.keypoints[16].y < hand.keypoints[13].y && 
    hand.keypoints[20].y < hand.keypoints[17].y &&
    abs(hand.keypoints[4].x - hand.keypoints[20].x) > abs(hand.keypoints[5].x - hand.keypoints[17].x));

  if (isHandOpen) {
    if (hand.handedness === "Right" && hand.keypoints[20].x - hand.keypoints[4].x > 0) {
      leftOpen = true;  
    } else if (hand.handedness === "Left" && hand.keypoints[20].x - hand.keypoints[4].x < 0) {
      rightOpen = true; 
    }
  }
}

The condition  abs(hand.keypoints[4].x - hand.keypoints[20].x) > abs(hand.keypoints[5].x - hand.keypoints[17].x));  measures the distance between pinky tip and thumb tip and compares it with the distance between knuckle of index finger and pinky, ensuring that the palm is fully open and not tilted. The condition  hand.keypoints[20].x - hand.keypoints[4].x < 0  checks if the distance between pinky and thumb tip is positive, ensuring that the user shows the inner side of the palm to the camera, not its back side.

Other parts

One part that I’m proud of in this code is the typewriter text effect in pixel dialogue window.

//draw text like a typewriter
function draw_text(t, anger_level) {
  //add shaking for higher anger levels
  let shakeAmount = 0;
  if (anger_level > 40 && anger_level < 100) {
    shakeAmount = map(anger_level, 40, 99, 0, 5, true); 
  }
  // random offset
  let offsetX = random(-shakeAmount, shakeAmount);
  let offsetY = random(-shakeAmount, shakeAmount);

  let currentIndex = floor(text_counter / text_speed);
  if (currentIndex < t.length) {
    text_counter++;
  }
  let displayedText = t.substring(0, currentIndex);

  push();
  translate(offsetX, offsetY);
  
  textFont(myFont);
  textSize(19);
  noStroke();
  
  fill(0);
  textAlign(CENTER, CENTER);
  rect(width/2, height*0.9, width*0.6+15, 40); //lines from side
  rect(width/2, height*0.9, width*0.6, 55); //lines from up/down
  //dialogue window
  fill(237, 240, 240);
  rect(width/2, height*0.9, width*0.6, 40);
  fill(0);
  text(displayedText, width/2, height*0.9);
  pop();
}

Here, if-condition checks on which index in the text we are currently on (default is set to 0 since text_counter = 0), if it’s less that the length of the desirable output string. If it is, it increments a counter. The counter is being divided by text speed (set to 2 frames), and the current index displayed is a rounded to lower number result of this division with the help of floor() function. Substring function converts the initial string to an array of characters using starting index (0) and ending index which is exactly the current index we’re reevaluating every time, and then it outputs the string captured between these indices. This way, a small pause (of 2 frames) between drawing each letter is created, creating an effect of typewriting.

In the final part of the function black rectangles are created under the main gray dialogue window, creating a pixel-style border to it.

Another valuable part of the code here is the shaking. In other parts of the code the shaking technique is almost the same: the offsets by x and y that depends on the anger level are passed to translate() function, changing the coordinates origin. Thanks to that, the whole dialogue window displayed has this new coordinate origin each time the function runs if the condition is satisfied, creating an effect of shaking.


Apart from that, the core of my code is the class “Girl” which controls almost everything connected to the girl charachter, from her speech to comparing handpose states. Also, I have some independent functions, like detect() that recognizes and returns the handpose state of the player and tutorial running that explains the player the rules of the game (by controlling and modifying some class public variables as well).

To control the game state, when it should run the tutorial, when the main part is being played, and when it’s over and needs a restart I use game states. For resseting, player is prompted to press “ENTER” on the final screen to fully restart the game by triggering the resetting function that sets all global variables back to default state and creates a new Girl object with new default attributes:

//reset the whole game upon calling this function
function resetGame() {
  // reset global variables
  game_state = "START";
  state = "CLOSED";
  text_counter = 0;
  screenFlash = 0;
  girlImages = [];
  
  girl = new Girl();
  
  // reset girl's variables
  girl.current_state = 0; 
  girl.level = 0;
  girl.change_state();
  endStage = 0;
  endTimer = 60;

  
  // reset the audio
  soundtrack.stop();
  soundtrack.rate(1.0);
  soundtrack.setVolume(1.0);
  distortion.set(0);
  distortion.drywet(0);
  soundtrack.loop();
}

...

function keyPressed() {
  ...
  if (keyCode === ENTER) {
    if (game_state === "GAME_OVER") {
      resetGame();
    }
  }
...
}

My code is pretty big but I feel like explained parts are the most interesting ones. I believe I have some inefficient parts in my code (such as hardcoded ending speech and its progression) but they all work now without lagging or taking long time to load, so I believe that at least for this projects it is fine to leave them like that.

While writing the code, I used the following resources:

    1. p5.js reference
    2. ml5.js reference
    3. The Coding Train Handpose video
    4. Gemini (Guided Learning Mode) for debugging and searching for functions of p5.js (such as substring function in typewriter, for example)

+just googling some methods and clarifications

Problems

Throughout the development of the project I ran into a lot of problems and small bugs but I will describe one that actually taught me a very useful trick.

I had a lot of visual parts that required precise positioning of the object, as well as I had different effects applied to them. Offsets of the object that were limiting its shaking, the mode of displaying the object (rectMode, imageMode), aligning, the translating conditions etc. were different for many parts. However, when you assign imageMode in one place globally, and then somewhere else you set another imageMode, and then in the third place you just use it without assigning expecting the default mode — the whole sketch turns to complete chaos. As you can see on the photos, I had video being aligned to another part of the screen, the textMode being set to some weird value, font style dissapearing, and textbox moving out of the screen. I learned how to isolate the styles (with the help of Gemini), as in this example:

function draw_video() {
  push();
  imageMode(CORNER);
  image(bg_img, 0, 0, width, height);
  
  //layer that gets the room darker as the anger level rises
  rectMode(CORNER);
  let mask_level = map(girl.level, 20, 100, 0, 180);
  noStroke();
  fill(0, mask_level);
  rect(0, 0, 640, 480);
  pop();

By surrounding the code block with push() and pop(), the style and code inside the block becomes isolated and doesn’t impact other parts of the code. It was really helpful, so I used it almost everywhere in my project!

Areas for Improvement

There’re some parts of my project that can be significantly improved and parts I don’t really like.

First of all, the final screamer, I feel like it is not scary enough to really make a great impact on the user. The concept was to have that cuteness vs. creepiness contrast. So, in contrast for a small childrens’ game and cutesy design, I wanted to make a really impactful and creepy screamer in the end, additional to other glitch/creepy effects. Turned out that making a scary screamer is actually a very hard job. I tested a few of the screamers versions, asking my friends to test the game so they can tell which one is scarier. I stopped on the current version because it was more unexpected, since it appears mid-sentence and has some stop-frame picture and not zoomed video or something else. Still, I feel like there’re ways to make this part much more surprising and scary that I wasn’t able to come up with.

Another part I could work on more is the design. I can’t draw, so in order to create visual assets I used picrew, editing AI (described earlier). However, I think that sprites created could be more fitting, and maybe I could have added additional sprites for more smooth pose-change, and sprites of a “still” pose. It is a bit hard to do in time-constraits and lack of skill, but I’m sure it’s something I can think about in the future.

Also, I believe I could introduce more unified control system. While playing, the user doesn’t touch the keyboard and only show their hands to the screen, but to progress through the tutorial and ending scene they need to press some buttons. I believe it is not really good to have these two controls systems mixed so maybe one of the improvement can be introducing some additional poses (like peace sign, maybe?) instead of keyboard pressing.

Week 5: Midterm Progress Report

Concept & Design

My concept for the Midterm is an interactive piece of a Filipino-style bakery or a panaderya. I want to make a nostalgic and cozy piece where you can learn about different Filipino pastries and baked goods, interact with the radio to change to music (the songs being classic Filipino songs), and the electric fan in the background.

I started with a rough sketch  of the design and I’m planning to design the whole piece using pixel art and I will be using the PressStart2P font which is this pixelated looking font to really give it that nostalgic feeling. For the landing screen, I wanted it to be simple and straightforward with detailed instructions for the user  and to transition to the actual bakery, I’ll be using keyPressed() function. For the bakery, there’s four main interactivity functions for now and all of them are going to be clicked to use. The radio is going to have play, pause, next, and previous buttons that will control the music. For the electric fan, I’m planning to implement animation using sprite from the previous lesson and I want the blades of the fan to change speed when clicked (I’m still debating whether to put numbers that correspond to the speed like an actual fan). Most importantly, the shelf is gonna have different objects and when clicked, there will be a pop up that’s going to tell the user about that specific pastry. Lastly, the door is going to restart the experience starting with the landing screen.

Code Testing & Uncertain Part

I wanted to test out using the font to ensure that I actually knew how to use them for the sketch and it looked the way I wanted it to. It was quite easy to figure that out as we already had a discussion on using downloaded fonts. I also wanted to test out having images as my object and the pop when clicked for my pastries section. I spent some time and asked AI for assistance because I only remember how to do interactions with shapes but not images. I eventually figured out that p5.js doesn’t automatically detect clicks on images, so we have to manually create an invisible box around an image using four variables (X, Y, width, and height)  to track where it is on the canvas. Then in mousePressed() we check if the mouse coordinates fall inside that box, and if they do we know the image was clicked and trigger the popup.

Midterm 3 Progress: ROOFTOP RUSH

The Concept

ROOFTOP RUSH is a side-scrolling parkour runner built in p5.js. The player controls a free-runner crossing a city skyline at dusk. The city scrolls to the right at increasing speed. The player must jump between rooftops, avoid obstacles, and collect coins. Each run generates a different sequence of buildings, gaps, and obstacles, so no two runs are the same.

The central idea is one core mechanic: the more points you earn, the farther your jumps carry you. Score is not just a number in the corner. It directly changes how far the player can jump. Early in a run, jumps are short and the player must plan each crossing carefully. As the score grows, the jumps grow with it. The player gains the ability to clear gaps that were not possible at the start. At the same time, the world speeds up. The game becomes harder and more powerful at once. The tension between those two forces is what makes each run feel urgent.

The planned interactive features are:

  • Grapple Hook (G key): A crane will spawn automatically over any gap that is too wide to jump. Pressing G will lock onto the crane and swing the player across.
  • Wall-Run (Up key on a wall): Touching a wall will trigger a wall-slide. Holding Up will convert it into a wall-run, carrying the player upward before launching off.
  • Trick System (F for flip, R for spin): Performing tricks in mid-air will award bonus points. Chaining multiple tricks in one jump will multiply the reward.
  • Slide Kick: Sliding into certain obstacles will destroy them and award points instead of dealing damage. This turns a defensive move into an offensive one.
  • Upgrade Shop: Coins will carry over between runs. The player will spend them on permanent upgrades such as stronger jumps, longer dashes, or a larger coin magnet range.
  • Day and Night Cycle: The sky will shift from sunset to night over time. Stars will appear and a helicopter with a spotlight will patrol the skyline after dark.

The Riskiest Part: The Jump Curve

The most uncertain part of this project is the score-to-jump-force progression curve. This mechanic is the entire point of the game. If the curve is wrong, nothing else works. If it is too flat, the player will not notice the progression. If it is too steep, the player will overshoot buildings and the game will break.

The challenge is not technical. It is perceptual. Jump force is measured in pixels per frame. That number has no intuitive meaning to a player. The curve needs to satisfy three conditions:

  1. The change must be noticeable early. A player who earns 500 points should feel a real difference in jump distance.
  2. It must plateau at high scores. The growth must slow down so the game stays controllable.
  3. The maximum jump height must stay within the bounds of the level. Buildings differ in height by at most 90px. The widest gap will be 180px.

I plan to use a logarithmic curve. Logarithms grow fast near zero and flatten at large values. This matches both requirements. The formula will be:

jumpForce = max( BASE_JUMP - K * ln(1 + score) , MAX_JUMP )

Planned constants: BASE_JUMP = -11.0, K = 0.0004, MAX_JUMP = -18.5. The negative sign follows the p5.js convention where upward velocity is negative.

To test this before building the game, I wrote a standalone sketch. It plots jump height in pixels against score so I can read the curve visually and check the numbers at key milestones.

// Risk-reduction test sketch
// Paste into p5.js editor to visualize the jump progression curve
// before writing any game logic

const BASE_JUMP = -11.0;
const MAX_JUMP  = -18.5;
const K         = 0.0004;
const GRAVITY   = 0.62;

function getJumpForce(score) {
  return max(BASE_JUMP - K * log(1 + score), MAX_JUMP);
}

// Physics: h = v^2 / (2 * gravity)
function jumpHeight(score) {
  let v = abs(getJumpForce(score));
  return (v * v) / (2 * GRAVITY);
}

function setup() {
  createCanvas(700, 400);
}

function draw() {
  background(20, 20, 30);

  // axis labels
  fill(180); noStroke(); textSize(12);
  text("Score ->", 600, 390);
  text("^ Jump Height (px)", 10, 20);

  // reference lines
  stroke(60, 60, 80);
  for (let h = 50; h <= 300; h += 50) {
    let y = map(h, 0, 300, height - 40, 20);
    line(40, y, width - 20, y);
    fill(100); noStroke(); text(h + "px", 2, y + 4);
    stroke(60, 60, 80);
  }

  // curve
  stroke(255, 160, 40);
  strokeWeight(2.5);
  noFill();
  beginShape();
  for (let score = 0; score <= 10000; score += 50) {
    let x = map(score, 0, 10000, 40, width - 20);
    let y = map(jumpHeight(score), 0, 300, height - 40, 20);
    vertex(x, y);
  }
  endShape();

  // milestone markers
  let milestones = [0, 500, 1000, 2500, 5000, 10000];
  for (let s of milestones) {
    let x = map(s, 0, 10000, 40, width - 20);
    let h = jumpHeight(s);
    let y = map(h, 0, 300, height - 40, 20);
    stroke(255, 80, 80); fill(255, 80, 80); ellipse(x, y, 7);
    noStroke(); fill(220);
    text("s=" + s + "\n" + nf(h, 0, 1) + "px", x - 10, y - 16);
  }

  noLoop();
}

The sketch produces the following numbers:

Score Jump height What the player will feel
0 97 px Short. The player must judge each gap carefully.
500 116 px Noticeably higher. The reward is felt immediately.
1,000 128 px Confident. Medium gaps are now comfortable.
2,500 147 px Strong. Most gaps are within reach.
5,000 163 px Powerful. Wide gaps feel manageable.
10,000 177 px Near the ceiling. The curve has flattened.

The hard cap at MAX_JUMP = -18.5 gives a maximum jump height of 277px. That is just under half the canvas height and within the maximum building height of 360px. A player at any score will never jump off screen. The widest gap in the level will always be crossable. These numbers confirm the curve is safe to use before writing a single line of game logic.

The second risk is procedural level generation. A bad sequence could produce an impossible gap or a long boring flat stretch. To address this, I will clamp the height difference between adjacent buildings to 90px. I will also write a query function that automatically places a crane anchor over any gap wider than 110px. The grapple hook will always be reachable from that gap, so no run will ever be blocked by the level generator.

Next Steps

The concept and design are clear. The riskiest algorithm has been tested and validated. The next step is to build the full game system: the player state machine, the building generator, the collision detection, the scoring logic, and the upgrade shop.


p5.js v1.9.0 · February 2026 · Intro to IM, NYUAD