Final Project – Production, Progress & User Testing

My project is finally coming along! After a lot of debugging, failed laser cutting attempts and design fixes, I finally have something to test with my friends!

My project, a single player, finger version twister game has a wheel that spins based on the User saying start-stop and then displays the colors and the hand they need to use and the users just press the switches. Having all the fingers on the board correctly is a win and any wrong attempt is an immediate end to the game. 

Are they able to figure it out? Yes, it was evident from the instructions page on what they had to do.

 Where do they get confused and why? There wasn’t anything as confusing that they found as it was easy to navigate. The only part of it that was confusing the first time I tested this were the switches. Since the switches are maroon in color they get mistaken as the red buttons. However my intent is to have colored stickers around each button to show which color it belongs too.

Do they understand the mapping between the controls and what happens in the experience? Yes they were able to understand this as the wheel showed which color and even the hand depicted the color on screen making it easier to follow. Sometimes it got a little tricky but that is part of the game where you need a few seconds to process the color and hand when you see so many together.

What parts of the experience are working well? What areas could be improved?

The colors, switches, scores are working well. My only problem is the speech recognition. It is very conditional as sometimes it worked flawlessly and sometimes it just wouldn’t recognize the words, As you can see in the videos below. Since there isn’t anything wrong with the code, I’m not sure how to fix it so I’m looking for alternate ways to control this without the usage of hands since that is not possible. It can get a little frustrating if the person has to keep saying start and stop to get it right and finally get the wheel moving, it might also be too loud. So I’m thinking of shifting it to just one word thats easily capturable and then the wheel would automatically stop after 2 seconds to slightly make this better. But apart from this I will try to see if i can use any other sensors to fix this issue as I want the experience to be as interactive as it is with the sound.

Works: https://youtu.be/o5YWcVglcbk

Stop working after 60-90 seconds: https://youtu.be/eyu1sFvJHdc

What parts of your project did you feel the need to explain? How could you make these areas more clear to someone that is experiencing your project for the first time?

The maroon colored switches! I’m not sure if i should paint them as that would ruin the feel of it. Maybe i can try cutting the stickers and pasting them on the switches to make this clearer.

Week 13 – Final Project – User Testing

Video

Are they able to figure it out? Where do they get confused and why? Do they understand the mapping between the controls and what happens in the experience?

Some users did not know when to start playing the Skeeball machine after interacting with the p5.js sketch.

Some users also did not know how to play the skeeball machine, and it was a little frustrating for them as they kept missing the targets.

What parts of the experience are working well? What areas could be improved?

Users loved the instant feedback from hitting a target in the skeeball machine, and in general most users loved the p5.js sketch as they liked the art style and animations. They also liked the high score list.

The ball would sometimes get stuck in the machine, so users would be confused when the ball would not return.

What parts of your project did you feel the need to explain? How could you make these areas more clear to someone that is experiencing your project for the first time?

I often had to manually remove the ball from the machine when it got stuck. I could improve this experience by making the ball return mechanism work better, but I was not able to figure out a way of fixing it 100% of the time in the 2 hours I’ve spent trying to fix this thing.

I also had to show many users how to roll the ball, as some users would try to throw the ball which is not the intended way of playing. I could improve this by adding a screen to the front of the targets, so the only way to hit the targets is by rolling the balls up the ramp.

Week 13- Final Project User Testing

I had two of my friends try the project during user testing. While conducting the test, I connected the p5.js sketch to the Arduino, displayed the sketch in full screen, and left the users to determine how to interact with both the box and the screen. Even though they were mentioned in the instruction that was displayed on the screen, users were uncertain about interacting with the box and screen simultaneously. So I needed to explain that they would receive hints on the screen and input the code using the button and knob on the box.

About the guessing the code, they quickly understood how to use the hints to guess the three digits required to unlock the code. However, they found it confusing regarding how to backtrack and edit an entered code if they wanted to change one of the digits, which is not possible in the game. And that was missing in the instruction.

In this vide the user is trying to crack the code for the second time: https://youtube.com/shorts/YVequMRq7eI?feature=shared

After user testing, I decided to add instructions on how to enter the code and clarify that editing the already entered code was not an option. Also, I chose to include printed instructions on the box, indicating users should gather hints from the screen to input the digits accordingly.

Week 13 – Final Project – Skeeball Dungeon

Describe your concept

I wanted to make an arcade machine for my final project, and my initial idea was a pachinko machine. However, I pivoted away from that as I couldn’t think of a good way to integrate P5.js into that project, and decided to make Skeeball instead.

My project is basically skeeball, with a touch of an RPG battle concept, hence the very creative name ‘Skeeball Dungeon’. The player will have to fight 4 increasingly tougher opponents in P5.JS, and they the damage they do is equal to the amount of points they get in skeeball. For each opponent, they are given 5 balls, and they have to defeat the opponent in the number of given balls.

Include some pictures / video of your project interaction

p5.js battle! skeeball !

Description of interaction design

The p5.js sketch is used to display instructions, showing high scores, visualizing the battles and hold the graphics. The Arduino is used to obtain information from the user playing Skeeball, detecting when a target has been hit and how many points was obtained.

Description of Arduino code and include or link to full Arduino sketch

The Arduino was used to detect when targets has been hit in the Skeeball physical component. To detect when a target has been hit, a flex sensor is used for each hole. When the ball passes through the hole, the Arduino pin input that is connected through the flex sensor-voltage divider circuit will have a lower/higher reading. This change is reading is then used to detect when a target has been hit.

Description of p5.js code and embed p5.js sketch in post

https://editor.p5js.org/ojmjunming/sketches/V0ARmZA70
The p5.js code holds all of the battle logic and the screens. A state system is used to transition between each different ‘screen’ to make the code more readable. e.g the first screen, instructions and more.

if (currentScene === "highScore") {
    highScoreScreen();
  } else if (currentScene === "instructions") {
    instructionScreen();
  } else if (currentScene === "battle") {
    renderBattleScreen();
  }

The code also keeps track of the health of the monster, the current monster, the score and the number of balls. The Arduino does not know how many balls are left, it only communicates the ball hits to p5.js. The p5.js sketch also maintains a top 5 list of high scores.

Description of communication between Arduino and p5.js

The Arduino will send a message of length 2 a single time when a target is hit. Otherwise, it sends a garbage message of length 1. The message contains the amount of points that was scored with that hit. The p5.js will continuously handshake with Arduino, most of the time the data is useless (when it’s length 1), and when the length is 2, it means that a target has been hit and the message will be processed, dealing damage to the monster equivalent to the points scored.

What are some aspects of the project that you’re particularly proud of?

There were some really interesting problems that I was proud of solving:
1) Constructing the Skeeball machine.
I originally planned to use the laser cutter to build the physical component, but the laser cutter only managed to cut out one piece before it started acting up. I waited 3 hours and the laser cutter was still not working. So, I pivoted to making it with cardboard and wood pieces, using tons of hot glue but it turned out to be surprisingly stable so I’m pretty proud of the machine itself as it took me a lot of time.

2) 7 flex sensors, but only 6 analog inputs on the Arduino Uno.

I was using flex sensors to detect target hits, but I had 7 targets/holes but only 6 analog inputs. Remembering that a digitalPin reads HIGH or LOW voltages (2.5-5V, 0-2.5V), I constructed a voltage divider with 30K resistors, so the analog reading teetered on the edge of 2.4-2.6V (480-530 analogInput) depending on the flex of the flex sensor. Knowing this, I was able to use a digitalPin to read the flex of the sensor ( the other sensors were read with a analogPin, which was much easier ). When the sensor is flexed, the reading would change from HIGH to LOW.

What are some areas for future improvement?

The skeeball machine could have been constructed better, as I did not have time to paint or decorate it well. The ball also sometimes gets stuck while it’s attempting to return to the start tray and you have to manually push it out. I also had to scrap an idea I had of detecting missed ball hits, as I wasn’t able to figure out how to attach a sensor for that, so in the future it would be nice to be able to include that in my game.

Week 13: Final Project

 

Project concept:

For my final project, I stuck closely to the original concept and developed a straightforward yet engaging game. The premise involves safeguarding a car navigating a road from descending obstacles. Users are tasked to protect the car by moving it left and right using the buttons on the Arduino board. Failure to shield the car results is a game over, while successfully safeguarding it from at least 10 obstacles leads to victory. I introduced an element of challenge by ensuring that the car moves at a fast pace, adding a layer of difficulty for users as they skillfully escape the approaching obstacles.

User testing:

I tested the game with my friend Javeria, and given its straightforward design with limited options, she quickly understood the mechanics. During the experience, she said she was able to grasp the functionality effortlessly without any guidance. Her understanding of the game highlighted its user-friendly nature and accessibility.

Challenging part:

The core functionality of the game coding operates smoothly, demonstrating performance without encountering any errors related to its main features. Despite facing multiple challenges, the essential gameplay elements functioned flawlessly. However, the area requiring further improvement lies in the transition between pages. The aspect of navigating between different sections of the game could benefit from some enhancements.

I intentionally excluded the idea of an instruction page, as I, reflecting on my own experiences as a child, preferred the thrill of figuring things out independently. However, recognizing that not everyone shares this perspective, users may need guidance on understanding the fundamental operations of the game. 

For the arduino I implemented simple 2 switches coding. Initially my p5 coding was the movement of the car to right and left using the keys of the keyboard. But by utilizing the readSerial() function as the communication link between p5.js and Arduino, the Arduino code is structured to retrieve the statuses of two switches and transmit these values to p5.js.

Arduino code:

const int switch1Pin = 4;  // Replace with the actual pin for switch 1
const int switch2Pin = 8;  // Replace with the actual pin for switch 2

void setup() {
  Serial.begin(9600);
  pinMode(switch1Pin, INPUT_PULLUP);
  pinMode(switch2Pin, INPUT_PULLUP);
  

  while (Serial.available() <= 0 ){
    Serial.println("0,0");
    delay(300);
  }
}

void loop() {


  while(Serial.available()) {
    if (Serial.read() == '\n') {
      int switch1State = digitalRead(switch1Pin);
      delay(5);
      int switch2State = digitalRead(switch2Pin);
      
      Serial.print(switch1State);
      Serial.print(',');
      Serial.println(switch2State);
    }
  }


  // if (switch1State == LOW) {
  //   // Switch 1 is clicked, set output to 1
  //   Serial.println("1");
  //   while (digitalRead(switch1Pin) == LOW) {
  //     // Wait until switch 1 is released
  //   }
  // } else if (switch2State == LOW) {
  //   // Switch 2 is clicked, set output to 0
  //   Serial.println("0");
  //   while (digitalRead(switch2Pin) == LOW) {
  //     // Wait until switch 2 is released
  //   }
  // }
}

p5.js code:

let img;
let rock;
let bg;
let car;
let obstacles = [];
let score = 0;
let bgSpeed = 2; // Background scrolling speed
let y1 = 0;
let y2;
let switch1State, switch2State;
let start;
let restart;
let gameStarted = false;
let gameOver = false;
let gameWon = false;
let winThreshold = 5;
let win;
let music;

function preload() {
  img = loadImage('pinkcarsss.png');
  rock = loadImage('rockss.png');
  bg = loadImage('backgroundroad.png');
  start = loadImage('startpage123.png');
  restart = loadImage('restartpage.png');
  win = loadImage('winpage123.png');
  music = loadSound('gamemusic.mp3');
}


function setup() {
  createCanvas(500, 600);
  car = new Car();
  y2 = height;
  music.play();
}

function draw() {
  background(250);

  // displaying of pages according to win/lose
  if (gameWon) {
    // Player wins
    drawWinPage();
  } else if (gameOver) {
    // Player loses
    drawLosePage();
  } else {
    // Display the start page
    image(start, 0, 0, width, height);

    if (gameStarted) {
      drawGame();
    }
  }
}

function drawWinPage() {
  image(win, 0, 0, width, height);
}

function drawLosePage() {
  image(restart, 0, 0, width, height);
}

function restartGame() {
  gameOver = false;
  gameStarted = false;
  score = 0;
  obstacles = [];
  setupGame();
}

function winGame() {
  gameWon = true;
  gameOver = false;
  gameStarted = false;
}

function mousePressed() {
  if (gameOver || gameWon) {
    if (mouseX > 200 && mouseX < 328 && mouseY > 235 && mouseY < 300) {
      restartGame();
    }
  } else if (!gameStarted) {
    if (mouseX > 200 && mouseX < 328 && mouseY > 235 && mouseY < 300) {
      gameStarted = true;
      setupGame();
    }
  }
}

function drawGame() {
  y1 += bgSpeed;
  y2 += bgSpeed;

  if (y1 > height) {
    y1 = -height;
  }
  if (y2 > height) {
    y2 = -height;
  }

  // Draw background images
  image(bg, 0, y1, width, height);
  image(bg, 0, y2, width, height);

  car.show();
  car.move();

  if (frameCount % 80 === 0) {
    obstacles.push(new Obstacle());
  }

  for (let obstacle of obstacles) {
    obstacle.show();
    obstacle.move();

    if (car.hits(obstacle)) {
      gameOver = true;
    }

    if (obstacle.offscreen()) {
      score++;
      obstacles.shift();
    }
  }

  if (score >= winThreshold) {
    winGame();
  }

  // score
  showScore();
}

function setupGame() {
  obstacles = [];
  score = 0;
  y1 = 0;
  y2 = height;
  car = new Car();
  gameStarted = true;
  gameOver = false;
  gameWon = false;
}

function showScore() {
  fill(0);
  textSize(17);
  text(`Score: ${score}`, 20, 20);
}

class Car {
  constructor() {
    this.w = 80;
    this.h = 90;
    this.x = width / 2 - this.w / 2;
    this.y = height / 2 - this.h / 2;
  }

  show() {
    fill(0, 255, 0);
    image(img, this.x, this.y, this.w, this.h);
  }

  move() {
    // Car moves automatically in the vertical direction
    this.y -= 3;

    // Reset car's position when it goes off the top
    if (this.y < -this.h) {
      this.y = height - this.h - 20;
    }
  }

  moveLeft() {
    this.x -= 10;
  }

  moveRight() {
    this.x += 10;
  }

  hits(obstacle) {
    return (
      this.x < obstacle.x + obstacle.w &&
      this.x + this.w > obstacle.x &&
      this.y < obstacle.y + obstacle.h &&
      this.y + this.h > obstacle.y
    );
  }
}

class Obstacle {
  constructor() {
    this.w = 40;
    this.h = 50;
    this.x = random(width - this.w);
    this.y = -this.h;
  }

  show() {
    fill(255, 0, 0);
    image(rock, this.x, this.y, this.w, this.h);
  }

  move() {
    this.y += 5;
  }

  offscreen() {
    return this.y > height;
  }

As for the p5, it contains the main logic of the game, with four different pages. There are multiple images added and also the elements like moving car and the rock are all png images. A happy music was also implemented in p5.

Looking ahead, I aspire to elevate the game beyond its current straightforward nature, by infusing it with more excitement and thrill. In terms of future enhancements, my goal is to inject more excitement into the game, moving beyond its current straightforward design. I’m also eager to explore and incorporate additional physical elements, further enhancing the interactive and immersive aspects of the gaming experience.

Video:

Week 13: Final Project Progress and User Testing (Prototype)

Still in its prototype phase, I did user testing on my project. The video is seen below:

 

As of now, the project is not complete. The p5.js code needs to be improved (it is a little buggy) and sounds need to be added. The hardware setup is also not final right now – the strip will be attached to the box.

Thing that worked well was that The interface was pretty straightforward for people to figure out. I didn’t really feel the need to explain to people to what to do with the project. I also got positive feedback about the aesthetics of the project!

The main thing that could be improved was that the length of the light sequence was not congruent with the sequence on the screen. This led to people pressing on the button multiple times, but nothing on the lights changing, giving the impression that nothing was happening.

 

 

Week 13: Final Project Documentation & User Testing

Concept

Fruitfall Frenzy is an entertaining and straightforward catching game where players aim to accumulate points by catching fruits while avoiding junk food to prevent point deductions. To achieve victory, players must collect fruits and reach a score of 15 or more. However, failing to do so and catching 10 junk foods with a score of 0 results in a loss for the player.

link to the game: https://editor.p5js.org/Javeria/sketches/e-4vC95i_

Implementation

In terms of interaction, I’ve incorporated two buttons on the breadboard to facilitate the control of the basket’s left and right movements. The Arduino integration is seamlessly implemented, with the readSerial() function acting as the communication bridge between p5.js and Arduino. The Arduino code is designed to read the status of two switches and relay these values to p5.js. On the other hand, p5.js takes charge of displaying the game graphics, playing sounds, and managing the overall game logic.

p5js Code:

let basket;
let fruits = [];
let junkFoods = [];
let score = 0;
let gameOver = false;
let startPage;
let front;
let instructions;
let gifImage;
let win;
let lose;
let junkFoodsCaught = 0;
let gameStarted = false;
let instructionMode = false;
let customFont;
let gameWon = false;
let switch1State, switch2State;
let startmusic;
let winmusic;
let lossmusic;
let gamemusic;
let fruitmusic;
let junkfoodmusic;
let currentMusic;

let basketImg, backgroundImage;
let fruitImages = [];
let junkFoodImages = [];

function preload() {
  customFont = loadFont('Sunday Happy.ttf');
  startPage = loadImage('1.png');
  instructions = loadImage('2.png');
  basketImg = loadImage('basket.png');
  backgroundImage = loadImage('background.png');
  win = loadImage('win.png'); // Add this line
  lose = loadImage('lose.gif'); // Add this line
  startmusic = loadSound ('start.mp3');
  winmusic  = loadSound ('win.mp3');
  lossmusic  = loadSound ('loss.mp3');
  gamemusic  = loadSound ('game.mp3');
  fruitmusic  = loadSound ('fruit.mp3');
  junkfoodmusic  = loadSound ('junkfood.mp3');

  // Load different fruit images
  for (let i = 1; i <= 5; i++) {
    fruitImages.push(loadImage('fruit' + i + '.png'));
  }

  // Load different junk food images
  for (let i = 1; i <= 2; i++) {
    junkFoodImages.push(loadImage('junkfood' + i + '.png'));
  }
}

function setup() {
  createCanvas(620, 650);
  setupGame();
  currentMusic = startmusic;
  currentMusic.play();
}

function setupGame() {
  basket = new Basket(basketImg);

  // Limit the initial number of falling elements
  const initialFruitsCount = 2; // Adjust this value as needed
  const initialJunkFoodsCount = 2; // Adjust this value as needed

  for (let i = 0; i < initialFruitsCount; i++) {
    fruits.push(createRandomFruit());
  }

  for (let i = 0; i < initialJunkFoodsCount; i++) {
    junkFoods.push(createRandomJunkFood());
  }
}

function drawGame() {
  // Draw background image
  image(backgroundImage, 0, 0, width, height);

  basket.display();
  basket.move();

  // Add new fruits and junk foods based on certain conditions
  if (frameCount % 60 === 0 && fruits.length + junkFoods.length < 5) {
    // Check if there's enough space for a new fruit
    if (fruits.length < 2) {
      fruits.push(createRandomFruit());
    }

    // Check if there's enough space for a new junk food
    if (junkFoods.length < 2) {
      junkFoods.push(createRandomJunkFood());
    }
  } }
 //<-- Add this closing bracket

function draw() {
  background(250);

  if (gameWon) {
    // Player wins
    currentMusic.stop(); // Stop the current music
    winmusic.play();    // Play the win music
    currentMusic = winmusic;
    drawWinPage();
     currentMusic.stop(); // Stop the current music
    winmusic.play();    // Play the win music
    currentMusic = winmusic; // Update currentMusic
  } else if (gameOver) {
    // Player loses
    drawLosePage();
    currentMusic.stop(); // Stop the current music
    lossmusic.play();   // Play the lose music
    currentMusic = lossmusic; // Update currentMusic
  } else {
    if (instructionMode) {
      // Display instructions
      image(instructions, 0, 0, width, height);
      fill(255, 204, 0, 180);
      noStroke();
      rect(535, 524, 70, 35);
      fill(0);
      textSize(30);
      textFont(customFont);
      text("Back", 542, 549);
       if (currentMusic !== startmusic) {
        currentMusic.stop(); // Stop the current music if it's not start music
        startmusic.play();  // Play the start music
        currentMusic = startmusic; // Update currentMusic
      }

      
    } else {
      // Display the start page
      image(startPage, 0, 0, width, height);
      fill(255, 204, 0, 180);
      noStroke();
      rect(228, 335, 150, 35);
      fill(0);
      textSize(30);
      textFont(customFont);
      text("Start Game", 237, 359);
      fill(255, 204, 10, 180);
      noStroke();
      rect(227, 391, 150, 35);
      fill(0);
      textSize(30);
      textFont(customFont);
      text("Instructions", 235, 417);
      

      // Draw game elements if the game has started
      if (gameStarted) {
        drawGame();
         if (currentMusic !== gamemusic) {
          currentMusic.stop(); // Stop the current music if it's not game music
          gamemusic.play();   // Play the game music
          currentMusic = gamemusic; // Update currentMusic
      }
    }
  }
}
}



function drawWinPage() {
  // Draw the win background image
  image(win, 0, 0, width, height);
  fill(255, 204, 0, 180);
      noStroke();
      rect(247, 266, 140, 35);
      fill(0);
      textSize(30);
      textFont(customFont);
      text("Restart", 272, 290);
  
  
  if (mouseIsPressed && mouseX > 247 && mouseX < 387 && mouseY > 266 && mouseY < 301){
    
  
    restartGame();
   }
  
}

function drawLosePage() {
  // Draw the lose background image
  image(lose, 0, 0, width, height);
  fill(255, 204, 0, 180);
      noStroke();
      rect(247, 592, 150, 35);
      fill(0);
      textSize(30);
      textFont(customFont);
      text("Restart", 280, 616);
  
  
   if (mouseIsPressed && mouseX > 247 && mouseX < 397 && mouseY > 592 && mouseY < 627) {
    restartGame();
   }
  

  
  
}
function restartGame() {
  gameOver = false;
  gameStarted = false;
  score = 0;
  junkFoodsCaught = 0;
  gameWon = false;
  setupGame();
}

function mousePressed() {
  if (!gameStarted) {
    if (instructionMode) {
      // Handle mouse click to return to the main menu
      if (mouseX > 535 && mouseX < 605 && mouseY > 524 && mouseY < 559) {
        instructionMode = false;
      }
    } else {
      if (mouseX > 228 && mouseX < 378) {
        if (mouseY > 335 && mouseY < 370) {
          // Start the game
          gameStarted = true;
          setupGame(); // Call setupGame when the game starts
        } else if (mouseY > 391 && mouseY < 426) {
          // Show instructions
          instructionMode = true;
        }
      }
    }
  }
}


class Basket {
  constructor(img) {
    this.width = 200;
    this.height = 150;
    this.x = width / 2 - this.width / 2;
    this.y = height - this.height - 10;
    this.img = img;
  }

  display() {
    image(this.img, this.x, this.y, this.width, this.height);
  }

  move() {
    if (keyIsDown(LEFT_ARROW) && this.x > 0) {
      this.x -= 5;
    }
    if (keyIsDown(RIGHT_ARROW) && this.x < width - this.width) {
      this.x += 5;
    }
  }

  moveLeft() {
    if (this.x > 0) {
      this.x -= 5;
    }
  }

  moveRight() {
    if (this.x < width - this.width) {
      this.x += 5;
    }
  }
}


class Fruit {
  constructor(img) {
    this.x = random(width);
    this.y = 0;
    this.diameter = 120;
    this.img = img;
  }

  display() {
    image(this.img, this.x - this.diameter / 2, this.y - this.diameter / 2, this.diameter, this.diameter);
  }

  fall() {
    this.y += 5;
  }

  intersects(basket) {
    let halfBasket = basket.width / 2;
    return (
      this.x > basket.x - halfBasket &&
      this.x < basket.x + basket.width + halfBasket &&
      this.y > basket.y &&
      this.y < basket.y + basket.height
    );
  }

  intersectsBasket(basket) {
    let halfBasket = basket.width / 2;
    return (
      this.x > basket.x - halfBasket &&
      this.x < basket.x + basket.width + halfBasket &&
      this.y + this.diameter / 2 > basket.y &&
      this.y - this.diameter / 2 < basket.y + basket.height
    );
  }
}


class JunkFood extends Fruit {
  constructor(img) {
    super(img);
    this.diameter = 60;
   
  }

  // Override the intersectsBasket method
  intersectsBasket(basket) {
    let halfBasket = basket.width / 2;
    return (
      this.x > basket.x - halfBasket &&
      this.x < basket.x + basket.width + halfBasket &&
      this.y + this.diameter / 2 > basket.y &&
      this.y - this.diameter / 2 < basket.y + basket.height
    );
  }
}

function drawGame() {
  // Draw background image
  image(backgroundImage, 0, 0, width, height);

  basket.display();
  basket.move();

  if (frameCount % 60 === 0) {
    fruits.push(createRandomFruit());
    junkFoods.push(createRandomJunkFood());
  }

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

    if (fruits[i].intersects(basket)) {
      score += 1;
      fruits.splice(i, 1);
    } else if (fruits[i].y > height) {
      fruits.splice(i, 1);
    }
  }

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

    if (junkFoods[i].intersects(basket)) {
      score -= 1; // Deduct score if junk food touches the basket
      junkFoods.splice(i, 1);
    } else if (junkFoods[i].y > height) {
      junkFoods.splice(i, 1);
    }
  }

  textSize(20);
  fill(0);
  text("Score: " + score, 20, 30);

  // Check win condition
  if (score >= 10) {
    gameWon = true;
    drawWinPage();
  }

  // Check lose condition
  if (score < 0 || junkFoodsCaught >= 10) {
    gameOver = true;
    drawLosePage();
  }
}

function createRandomFruit() {
  let randomFruitImg = random(fruitImages);
  let fruit = new Fruit(randomFruitImg);

  for (let existingFruit of fruits) {
    while (fruit.intersects(existingFruit) || fruit.intersectsBasket(basket)) {
      fruit = new Fruit(randomFruitImg);
    }
  }

  return fruit;
}

function createRandomJunkFood() {
  let randomJunkFoodImg = random(junkFoodImages);
  let junkFood = new JunkFood(randomJunkFoodImg);

  for (let existingFruit of fruits) {
    while (junkFood.intersects(existingFruit) || junkFood.intersectsBasket(basket)) {
      junkFood = new JunkFood(randomJunkFoodImg);
    }
  }
  for (let existingJunkFood of junkFoods) {
    while (junkFood.intersects(existingJunkFood) || junkFood.intersectsBasket(basket)) {
      junkFood = new JunkFood(randomJunkFoodImg);
    }
  }

  return junkFood;
}
function keyPressed() {
  // Handle key presses (this function only detects if a key is pressed at the moment)
}
function readSerial(data) {
  ////////////////////////////////////
  //READ FROM ARDUINO HERE
  ////////////////////////////////////

  if (data != null) {
    // make sure there is actually a message
    // split the message
    let fromArduino = split(trim(data), ",");
    console.log(fromArduino);
    // if the right length, then proceed
    if (fromArduino.length == 2) {
      // only store values here
      // do everything with those values in the main draw loop
      
      // We take the string we get from Arduino and explicitly
      // convert it to a number by using int()
      // e.g. "103" becomes 103
      switch1State = int(fromArduino[0]);
      switch2State = int(fromArduino[1]);
      
      if (switch1State) {
        basket.moveLeft();
      } 
      
      if (switch2State) {
        basket.moveRight();
      }
    }

    //////////////////////////////////
    //SEND TO ARDUINO HERE (handshake)
    //////////////////////////////////
    let sendToArduino = "\n";
    writeSerial(sendToArduino);
  }
}


function keyPressed(){
  setUpSerial();
}

Arduino Code:

const int switch1Pin = 4;  
const int switch2Pin = 8;  

void setup() {
  Serial.begin(9600);
  pinMode(switch1Pin, INPUT_PULLUP);
  pinMode(switch2Pin, INPUT_PULLUP);
  

  while (Serial.available() <= 0 ){
    Serial.println("0,0");
    delay(300);
  }
}

void loop() {


  while(Serial.available()) {
    if (Serial.read() == '\n') {
      int switch1State = digitalRead(switch1Pin);
      delay(5);
      int switch2State = digitalRead(switch2Pin);
      
      Serial.print(switch1State);
      Serial.print(',');
      Serial.println(switch2State);
    }
  }


  
}

 

Future improvements

To enhance both the engagement and challenge levels of the game, I’m considering  incorporating additional features. These may include the gradual escalation of difficulty as the game progresses and the introduction of power-ups to provide players with unique advantages.

User Testing 

In the testing phase, I asked Nafiha to give my game a try. Impressively, she seamlessly utilized the buttons to navigate the basket without any hesitation. Her feedback highlighted that the controls were straightforward, requiring no additional instructions. Taking her suggestion into account, I modified the game dynamics by initially decreasing the number of falling objects and contemplating a gradual increase over time, resulting in a more balanced and enjoyable gameplay experience.

https://drive.google.com/file/d/1_36pcfyrhlTWxp_WqHmETfE6l8cfDicl/view?usp=sharing

 

Week 13 – Final Project Documentation

Concept

Reaction Game Based, or RGB, is a rhythm tool/game used to test your reaction time. Three randomized dots appear on the screen with two characteristics: number (1, 2, 3) and color (red, green, blue). Your objective is to hit each dot according to its assigned resistor, clearing the screen of all dots and obtaining the biggest score possible under the time limit of 40 seconds. After that, the user is presented with their score and their estimated age according to their reaction time.

Implementation

For the interaction, three force-sensitive resistors were used in order to hit the dots, along with a buzzer, which is responsible for playing a sound every time the user hits a resistor. As for the Arduino code, each input was declared, and the basic serial communication was established with P5.js, sending the data from each resistor. With each tap on the resistor, a sound is also played by the buzzer.

int fsrPin1 = A0;
int fsrPin2 = A1;
int fsrPin3 = A2;
int buzzerPin = 2;

void setup() {
  Serial.begin(9600);
}

void loop() {
  int fsrValue1 = analogRead(fsrPin1);
  int fsrValue2 = analogRead(fsrPin2);
  int fsrValue3 = analogRead(fsrPin3);

  // send FSR values to p5.js
  Serial.print(fsrValue1);
  Serial.print(",");
  Serial.print(fsrValue2);
  Serial.print(",");
  Serial.println(fsrValue3);

  // force sensitive resistor hits
  checkHit(fsrValue1, 1);
  checkHit(fsrValue2, 2);
  checkHit(fsrValue3, 3);
}

void checkHit(int fsrValue, int noteNumber) {
  if (fsrValue > 130) { // FSR threshold to activate sound
    Serial.println("Hit detected!");

    Serial.print("Note,");
    Serial.println(noteNumber);

    // buzzer sound on tap
    tone(buzzerPin, 1000, 100); // frequency and duration
  }
}

As for P5.js, the most important variables are the ones that keep track of the player’s score and the notes. The function “checkHit” is also important to determine whether a note has been successfully hit based on the input from force-sensitive resistors (FSRs). It iterates through the FSR values and compares them to predefined thresholds. If the FSR value exceeds its threshold and the associated note is not marked as already hit, it signifies a successful hit. The function then updates the score, initiates visual effects on the ellipses like glowing, sets a cooldown for the corresponding FSR, and checks if all notes are hit. If all notes are hit, it triggers the generation of new notes. Regarding serial communication, a p5.web-serial.js file provided in class by Michael Ang and Aaron Sherwood is responsible for the communication between Arduino and p5.js.

function checkHit() {
  for (let i = 0; i < fsrValues.length; i++) {
    if (resistorCooldowns[i] <= 0) {
      for (let j = notes.length - 1; j >= 0; j--) {
        let note = notes[j];
        if (fsrValues[i] > fsrThresholds[i] && !note.hit) {
          let expectedNote = i + 1; // expected note based on resistor index
          if (note.number === expectedNote) {
            console.log("Hit detected for note " + expectedNote + "!");
            score += 10;
            glowingFrames = glowingDuration;
            note.hit = true;
            resistorCooldowns[i] = cooldownDuration; // set cooldown for the resistor
            if (allNotesHit()) {
              setTimeout(generateNotes, 1000); // wait for 1 second before generating new notes
            }
            break; // exit the loop once a hit is detected
          }
        }
      }
    }
  }

Future improvements

Future changes to the project would depend on different purposes that it could offer, be it simply a reaction time test, a game focused on fun, or maybe a combination of both. Nonetheless, I believe that more colors could be added, and possibly music. Also, I would have liked to see different modes and dynamics for the notes, testing the user in different ways. Other than that, I am proud of offering a fun yet quite useful experience for the user.

User testing

User testing was conducted with three different people, and it went generally well. Most of them managed to figure out the controls and objectives of the game pretty much instantly, and they had no difficulty in obtaining quite high scores despite no prior knowledge of the game.

However, in one instance the controls did not seem as obvious, and the participant struggled a little bit to figure out the resistor and the timer.

Overall, the experience is working well, and not a lot of explanation is needed. Something that could be improved would be maybe utilizing different buttons other than the force-sensitive resistors. The reason is that the force-sensitive resistors do not work perfectly. Maybe they look less obvious than three huge buttons would, and it is a bit difficult to work with the force thresholds. Have too much necessary force for them, and you have to smash the table in order to register a hit, but if you have too little, they will register even if you barely touch them. Making it more obvious that each resistor is assigned to a number would also help.

Week 11 – Arduino and P5.js

1 – Make something that uses only one sensor on Arduino and makes the ellipse in p5 move on the horizontal axis, in the middle of the screen, and nothing on arduino is controlled by p5

Code:

const int potPin = A0;

void setup() {
  Serial.begin(9600);
}

void loop() {
  int potValue = analogRead(potPin);

  // send the potentiometer value to the serial port
  Serial.println(potValue);

  delay(50);
}

2 – Make something that controls the LED brightness from p5

Code:

int ledPin = 6;

void setup() {
  Serial.begin(9600);
  pinMode(ledPin, OUTPUT);
}

void loop() {
  while (Serial.available() > 0) {
    // read the incoming brightness value from p5.js
    int brightness = Serial.parseInt();

    // adjust the LED brightness based on the received value
    analogWrite(ledPin, brightness);
  }
}

3 – Take the gravity wind example and make it so every time the ball bounces one led lights up and then turns off, and you can control the wind from one analog sensor

int potPin = A0;
int ledPin = 6;

void setup() {
  Serial.begin(9600);
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(potPin, INPUT);
  pinMode(ledPin, OUTPUT);
}

void loop() {
  digitalWrite(LED_BUILTIN, LOW);
  while (Serial.available()) {
    digitalWrite(LED_BUILTIN, HIGH);
    int ambientBrightness = Serial.parseInt();
    if (Serial.read() == '\n') {
      int sensorReading = analogRead(potPin);
      Serial.println(sensorReading);
    }
    if (ambientBrightness >= 350 && ambientBrightness <= 360) {
      digitalWrite(ledPin, HIGH);
    } else {
      digitalWrite(ledPin, LOW);
    }
  }
  int sensorReading = analogRead(potPin);
  Serial.println(sensorReading);
}

Week 12 – Finalized Project Concept

Concept

For my final project, I have decided to change my idea. Now, I have settled on a rhythm music game, akin to Guitar Hero. Using force sensitive resistors (FSR), the user will have to quickly press the sensors to match the combinations that are being shown on the screen, almost like drums but using your hands. The design of the game on P5.js will be minimalistic, but it will also contain many colors and bright lights when the sensors are pressed. The more the user progresses, the more points will be accumulated, until the game eventually ends and restarts. To add more replayability, the rhythm of the sensors will be random.

Arduino

The Arduino will contain force sensitive resistors for inputs to capture the user’s input for the game. Each sensor will have an analog input assigned to it, which I will be able to manipulate with code. A cardboard box will also most likely be used around the breadboard to deliver a more aesthetically pleasing experience. Below is a schematic with the sensors for the board:

P5.js

The P5 program will be responsible for displaying the game, including the score and the notes that are necessary to be hit. The music could also be played by P5 instead of a buzzer on the breadboard, but that still needs to be decided. In terms of code, P5 will receive the information from the sensors and know when to activate a hit or miss in the notes. Below is a low-fidelity drawing of the game: