Final Project: Arcade Snake Game

Blind User Testing

First things first:

Concept

I initially planned on extending the functionality of my midterm project through the final project by giving it physical controls using the arduino. However, in the end, I decided to make a game inspired by all the amazing games that I saw in the midterms and final proposals (call it FOMO, I guess 😉 ). I settled with a game we all know and love, the Snake game that I’m sure most of us remember playing on our parents’ Nokias and BlackBerries. You eat fruits, you avoid hitting the boundaries and you avoid eating yourself – pretty simple, right? I settled for a simple aesthetic, making it pixel-y for the whole nostalgia package. Instead of using buttons on our parents’ phones, I’ve made it an arcade game where the snake is controlled by a joystick!

Basically, the game begins by pressing the joystick. For every fruit eaten, the player’s score increments by 1. After every 5 fruits eaten, if the player is still alive, the player levels up and the speed increases until you get to 30 scores after which it remains at the highest speed. After eating 20 fruits, your habitat (background) changes. There’s fun sounds and Minecraft music playing in the background, so the player will never get bored! The game is competitive too; it keeps track of the highest score for all turns!

Visuals

Implementation

I basically coded the game in p5.js initially, with an intention to control the snake with push buttons. After struggling with the implementation and orientation on the small breadboard, I thought about using the big LED buttons in a box, but when I saw Khaleeqa’s project using a joystick, I thought to myself: We can do that??? (thank you for the idea, Khaleeqa. I pray you’re able to do the Tolerance readings and response on time!). So now the snake is controlled by a joystick, adding to the nostalgic arcade feel. The design is extremely user friendly; you couldn’t get stuck or confused if you tried.

Arduino Code and Description of Communication

Basically, the joystick uses two potentiometers on the x and y axes, and I sent these two values to my p5.js sketch to control the direction of the snake’s movement. Pressing the joystick gives a specific value on the x potentiometer (1023) which I use to restart the game (thank you again Professor Aya for lending me a joystick, real lifesaver here).

Arduino Schematic

Arduino Code

int xValue = 0 ;
int yValue = 0 ; 

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

}	

void loop()	
{	
    xValue = analogRead(A2);	
    yValue = analogRead(A1);		
    Serial.print(xValue,DEC);
    Serial.print(",");
    Serial.println(yValue,DEC);

    delay(100);	
}

P5 Code and Sketch

The P5 code is extensive in functionality, but I’ve tried to make the code as short as possible to make up for the 800 line monster I created for my midterm. I’ve basically used 4 classes in the implementation for each element of the snake, the snake as a whole, fruits, and the gameplay. The snake class is a list of snake elements, so it inherits the list class. The various classes, methods and logic are explained in the sketch comments, so instead of describing it again, here is the code:

let RESOLUTION = 500; // Setting canvas resolution
let ROWS = 20; // Number of rows in the grid
let COLS = 20; // Number of columns in the grid
let WIDTH = RESOLUTION / ROWS; // Width of each grid cell
let HEIGHT = RESOLUTION / COLS; // Height of each grid cell
let end_flag = false; // Flag to indicate the end of the game
let start_flag = false;

let bg_flag = 0;
let xVal = 0;
let yVal = 0;
let high_score = 0;

let head_up,
  head_left,
  apple,
  banana,
  game_background1,
  gamebackground2,
  game_font,
  bite_sound,
  level_up,
  game_over,
  background_music; // Loading images for background, snake head, apple, and banana; fonts; sounds

class SnakeElement {
  constructor(x, y, element_num) {
    // Snake element constructor
    this.x = x; // X-coordinate of the element
    this.y = y; // Y-coordinate of the element
    this.element_num = element_num; // Identifier for the type of element
  }

  display() {
    if (this.element_num === 1) {
      // Displaying the head facing up
      noFill();
      stroke(250);
      strokeWeight(2);
      ellipse(this.x + WIDTH / 2, this.y + HEIGHT / 2, WIDTH + 2);
      image(head_up, this.x, this.y, WIDTH, HEIGHT);
    } else if (this.element_num === 2) {
      // Displaying the head facing down (flipped vertically)
      noFill();
      stroke(250);
      strokeWeight(2);
      ellipse(this.x + WIDTH / 2, this.y + HEIGHT / 2, WIDTH + 2);
      push(); // Save the current drawing state
      scale(1, -1); // Flip vertically
      image(head_up, this.x, -this.y - HEIGHT, WIDTH, HEIGHT);
      pop(); // Restore the original drawing state
    } else if (this.element_num === 3) {
      // Displaying the head facing left
      noFill();
      stroke(250);
      strokeWeight(2);
      ellipse(this.x + WIDTH / 2, this.y + HEIGHT / 2, WIDTH + 2);
      image(head_left, this.x, this.y, WIDTH, HEIGHT);
    } else if (this.element_num === 4) {
      // Displaying the head facing right (flipped horizontally)
      noFill();
      stroke(250);
      strokeWeight(2);
      ellipse(this.x + WIDTH / 2, this.y + HEIGHT / 2, WIDTH + 2);
      push(); // Save the current drawing state
      scale(-1, 1); // Flip horizontally
      image(head_left, -this.x - WIDTH, this.y, WIDTH, HEIGHT);
      pop(); // Restore the original drawing state
    } else {
      // Displaying a circle for the body elements
      stroke(250);
      strokeWeight(2);
      if (this.element_num === 5) {
        fill(120, 220, 20); // Green circle
      } else if (this.element_num === 6) {
        fill(200, 48, 32); // Red circle
      } else if (this.element_num === 7) {
        fill(251, 240, 76); // Yellow circle
      }
      ellipse(this.x + WIDTH / 2, this.y + HEIGHT / 2, WIDTH);
    }
  }
}

class Snake extends Array {
  constructor() {
    super();
    // Initializing the snake with head and initial body elements
    this.push(new SnakeElement(RESOLUTION / 2, RESOLUTION / 2, 1));
    this.push(new SnakeElement(RESOLUTION / 2 - WIDTH, RESOLUTION / 2, 5));
    this.push(new SnakeElement(RESOLUTION / 2 - WIDTH * 2, RESOLUTION / 2, 5));
    this.full_flag = false; // Flag to check if the snake has filled the grid
  }

  display() {
    // Displaying all snake elements
    for (let element of this) {
      element.display();
    }
  }

  move(current_dir) {
    // Controlling the movement of the snake
    let head_x = this[0].x;
    let head_y = this[0].y;

    // Updating head position based on the current direction
    if (current_dir === "UP") {
      this[0].element_num = 1; // Updating element_num for head facing up
      this[0].y -= WIDTH; // Moving up
    } else if (current_dir === "DOWN") {
      this[0].element_num = 2; // Updating element_num for head facing down
      this[0].y += WIDTH; // Moving down
    } else if (current_dir === "LEFT") {
      this[0].element_num = 3; // Updating element_num for head facing left
      this[0].x -= WIDTH; // Moving left
    } else if (current_dir === "RIGHT") {
      this[0].element_num = 4; // Updating element_num for head facing right
      this[0].x += WIDTH; // Moving right
    }

    // Moving the body elements
    for (let i = 1; i < this.length; i++) {
      let temp_x = this[i].x;
      let temp_y = this[i].y;
      this[i].x = head_x;
      this[i].y = head_y;
      head_x = temp_x;
      head_y = temp_y;
    }
  }

  collide_self() {
    // Checking if the snake collides with itself
    for (let i = 1; i < this.length; i++) {
      if (this[0].x === this[i].x && this[0].y === this[i].y) {
        end_flag = true; // Collision occurred, end the game
        game_over.play();
      }
    }
  }

  collide_walls() {
    // Checking if the snake collides with the canvas borders
    if (
      this[0].x >= RESOLUTION ||
      this[0].x < 0 ||
      this[0].y < 0 ||
      this[0].y >= RESOLUTION
    ) {
      end_flag = true; // Snake has left the canvas, end the game
      game_over.play();
    }
  }

  board_full() {
    // Checking if the snake has filled the entire grid
    if (this.length === ROWS * COLS) {
      end_flag = true; // Board is full, end the game
      this.full_flag = true; // Player wins
    }
  }
}

class Fruit {
  constructor() {
    // Generating a random position for the fruit
    this.x = Math.floor(Math.random() * ROWS) * WIDTH;
    this.y = Math.floor(Math.random() * COLS) * HEIGHT;
    this.fruit_num = Math.floor(Math.random() * 2); // Randomly choosing apple or banana
  }

  display() {
    // Displaying the fruit based on its type
    if (this.fruit_num === 0) {
      image(apple, this.x, this.y, WIDTH, HEIGHT);
    } else {
      image(banana, this.x, this.y, WIDTH, HEIGHT);
    }
  }
}

class Game {
  constructor() {
    // Initializing the game with snake, fruit, and default direction
    this.snake = new Snake();
    this.fruit = new Fruit();
    this.current_dir = "RIGHT";
    this.score = 0; // Player's score
    this.frames = 12;
    this.eat_count = 0;
  }

  display() {
    // Displaying the snake, checking for fruit collision, and displaying the score
    this.snake.display();
    let n = 0;
    while (n < this.snake.length) {
      // Checking if the fruit is at the same position as any snake element
      if (
        this.fruit.x === this.snake[n].x &&
        this.fruit.y === this.snake[n].y
      ) {
        this.fruit = new Fruit(); // Create a new fruit
      }
      n++;
    }
    this.fruit.display();
    textSize(10);
    fill(0);
    text("Score: " + this.score, RESOLUTION - 100, 30);
  }

  move() {
    // Moving the snake, checking for collisions
    this.snake.move(this.current_dir);
    this.snake.collide_self();
    this.snake.collide_walls();
  }

  eat() {
    // Checking if the snake eats the fruit
    if (this.snake[0].x === this.fruit.x && this.snake[0].y === this.fruit.y) {
      // Adding a new element to the snake based on the current direction
      if (this.current_dir === "DOWN") {
        this.snake.push(
          new SnakeElement(
            this.snake[this.snake.length - 1].x,
            this.snake[this.snake.length - 1].y - HEIGHT,
            6 + this.fruit.fruit_num
          )
        );
      }
      if (this.current_dir === "UP") {
        this.snake.push(
          new SnakeElement(
            this.snake[this.snake.length - 1].x,
            this.snake[this.snake.length - 1].y + HEIGHT,
            6 + this.fruit.fruit_num
          )
        );
      }
      if (this.current_dir === "LEFT") {
        this.snake.push(
          new SnakeElement(
            this.snake[this.snake.length - 1].x + WIDTH,
            this.snake[this.snake.length - 1].y,
            6 + this.fruit.fruit_num
          )
        );
      }
      if (this.current_dir === "RIGHT") {
        this.snake.push(
          new SnakeElement(
            this.snake[this.snake.length - 1].x - WIDTH,
            this.snake[this.snake.length - 1].y,
            6 + this.fruit.fruit_num
          )
        );
      }
      this.fruit = new Fruit(); // Create a new fruit
      this.score += 1; // Increase the score
      if (this.score > 20) {
        bg_flag = 1;
      }
      frames -= 1;
      this.eat_count += 1;

      if (this.eat_count % 5 === 0 && this.eat_count <= 30) {
        this.frames -= 1;
        level_up.play();
      }
      bite_sound.play();
    }
  }

  rungame() {
    // Main method to be called in draw()
    if (frameCount % this.frames === 0) {
      if (!end_flag) {
        // If the game hasn't ended
        if (bg_flag === 0) {
          image(game_background1, 0, 0, RESOLUTION, RESOLUTION);
        } else if (bg_flag === 1) {
          image(game_background2, 0, 0, RESOLUTION, RESOLUTION);
        }
        this.display();
        this.move();
        this.eat();
      } else if (!this.snake.full_flag) {
        // If the game has ended and the board is not full
        if (bg_flag === 0) {
          image(game_background1, 0, 0, RESOLUTION, RESOLUTION);
        } else if (bg_flag === 1) {
          image(game_background2, 0, 0, RESOLUTION, RESOLUTION);
        }
        textSize(30);
        text("Game Over :(", RESOLUTION / 2 - 175, RESOLUTION / 2 - 15);
        textSize(18);
        text(
          "Final Score: " + this.score,
          RESOLUTION / 2 - 120,
          RESOLUTION / 2 + 15
        );
        textSize(10);
        text(
          "Click anywhere to restart :D",
          RESOLUTION / 2 - 130,
          RESOLUTION - 40
        );
        if (this.score > high_score) {
          high_score = this.score;
        }
        push();
        textSize(18);
        fill(255);
        stroke(0);
        text(
          "High Score: " + high_score,
          RESOLUTION / 2 - 110,
          RESOLUTION / 2 + 40
        );
        pop();
      } else {
        // If the game has ended and the board is full
        if (bg_flag === 0) {
          image(game_background1, 0, 0, RESOLUTION, RESOLUTION);
        } else if (bg_flag === 1) {
          image(game_background2, 0, 0, RESOLUTION, RESOLUTION);
        }
        textSize(25);
        text("You Win :D", RESOLUTION / 2 - 140, RESOLUTION / 2);
        textSize(7.5);
        text(
          "Click anywhere to restart :D",
          RESOLUTION / 2 - 100,
          RESOLUTION - 50
        );
      }
    }
  }
}

let game;

function preload() {
  // Loading images before setup()
  head_up = loadImage("images/head_up.png");
  head_left = loadImage("images/head_left.png");
  apple = loadImage("images/apple.png");
  banana = loadImage("images/banana.png");
  game_background1 = loadImage("images/snake_game_background1.png");
  game_background2 = loadImage("images/snake_game_background2.png");
  game_font = loadFont("fonts/snake_game_font.ttf");
  bite_sound = loadSound("sounds/bite.m4a");
  level_up = loadSound("sounds/levelup.mp3");
  game_over = loadSound("sounds/gameover.mp3");
  background_music = loadSound("sounds/backgroundmusic.m4a");
}

function setup() {
  // Setup function for creating the canvas and initializing the game
  createCanvas(RESOLUTION, RESOLUTION);
  game = new Game();
  textFont(game_font);
}

function draw() {
  // Draw function for running the game
  if (xVal >= 1000) {
    start_flag = true;
  }
  if (!background_music.isPlaying()) {
    background_music.play();
  }
  if (start_flag === true) {
    game.rungame();
  } else {
    image(game_background1, 0, 0, RESOLUTION, RESOLUTION);
    push();
    textSize(50);
    text("SNAKE", RESOLUTION / 2 - 120, RESOLUTION / 2 + 20);
    textSize(15);
    strokeWeight(1.5);
    text(
      "the most original game ever",
      RESOLUTION / 2 - 200,
      RESOLUTION / 2 + 40
    );
    pop();
  }

  if (!serialActive) {
    stroke(255);
    strokeWeight(2);
    text("Press Space Bar to select Serial Port", 20, 30);
  } else if (start_flag === true) {
    text("Connected", 20, 30);
  } else {
    text("Connected. Press the joystick to \nstart playing!", 20, 30);
  }

  // changing the direction of the snake using values from arduino
  if (xVal < 300) {
    // joystick moved left
    if (game.current_dir !== "RIGHT") {
      game.current_dir = "LEFT";
    }
  } else if (xVal > 700 && xVal < 1000) {
    // joystick moved right
    if (game.current_dir !== "LEFT") {
      game.current_dir = "RIGHT";
    }
  } else if (yVal < 300) {
    // joystick moved down
    if (game.current_dir !== "UP") {
      game.current_dir = "DOWN";
    }
  } else if (yVal > 700) {
    if (game.current_dir !== "DOWN") {
      // joystick moved up
      game.current_dir = "UP";
    }
  } else if (xVal >= 1000) {
    // restart game when joystick pressed
    if (end_flag === true) {
      end_flag = false;
      game = new Game();
      bg_flag = 0;
    }
  }
}

function mousePressed() {
  // Restart the game on mouse click
  if (end_flag === true) {
    start_flag = true;
    end_flag = false;
    game = new Game();
    bg_flag = 0;
  }
}

function keyPressed() {
  // Change the direction of movement on arrow key press
  if (keyCode === LEFT_ARROW) {
    if (game.current_dir !== "RIGHT") {
      game.current_dir = "LEFT";
    }
  } else if (keyCode === RIGHT_ARROW) {
    if (game.current_dir !== "LEFT") {
      game.current_dir = "RIGHT";
    }
  } else if (keyCode === UP_ARROW) {
    if (game.current_dir !== "DOWN") {
      game.current_dir = "UP";
    }
  } else if (keyCode === DOWN_ARROW) {
    if (game.current_dir !== "UP") {
      game.current_dir = "DOWN";
    }
  }

  if (key == " ") {
    // important to have in order to start the serial connection!!
    setUpSerial();
  }
}

// This function will be called by the web-serial library
// with each new *line* of data. The serial library reads
// the data until the newline and then gives it to us through
// this callback function
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), ",");
    // if the right length, then proceed
    if (fromArduino.length == 2) {
      // only store values here
      // do everything with those values in the main draw loop
      xVal = int(fromArduino[0]);
      yVal = int(fromArduino[1]);
    }
  }
}

 

And a link to the sketch:

https://editor.p5js.org/haroonshafi/full/M1HJeVoiy

Aspects That I’m (IM) Proud Of

Actually getting the Snake game to work was a big part of this project. There’s so many conditions to cater to, so much stuff to consider. You don’t think that while you’re on the playing end but boy when you’re on the making end – whew. I’m proud that after messing up a bajillion times, particularly with the serial communication part, that I’ve finally got this to work with the help of the professor.  (I’m also proud of getting a high score of 34, so I wanna see someone beat that lol).

Areas for Future Improvement

There is plenty of room for improvement. Had I had more time, I would have incorporated some lights for indicating when a fruit is eaten or when the snake collides so there would be communication from p5 to arduino as well. I also wanted to use a potentiometer to control the speed of the snake through the framerate, because that would be fun (read: chaotic).

 

Pet the Plant: D.I.Y Christmas Special Edition

https://editor.p5js.org/Minjae0216/sketches/Qyv31CZ2_

Concept:

Through “Pet the Plant”, I wanted to make an interactive project that allows you to experience the satisfaction of growing your plant friend without the real responsibilities or risks. In this game, there are three different plants that you can choose to grow and make your own Christmas tree.

I was inspired by my friend who failed to grow her plants. By using an Arduino and different sensors, users can go through the physical motions of caring for a plant, allowing the digital plant to grow without any accidents or loss. The virtual experience is coded in p5.js and designed with Canva.

The reason for <Special Edition: D.I.Y Christmas Tree> is for the environment and the Christmas season. People buy Christmas trees in UAE to enjoy the winter. However, I thought buying Christmas trees was not environmentally friendly. Buying a Christmas tree is often considered environmentally unfriendly due to concerns like deforestation, transportation emissions, and the use of chemicals on tree farms. So, I think of other solutions that can help people to not purchase Christmas trees every winter. Making your own Christmas tree virtually will help the environment and save you money!

Hardware Image:

P5.js Screen Image:

User Testing:

IM Showcase:

IMG_9979

Many people came to see the IM showcase! My project was very popular for kids because it was pretty clear to understand through my screen and the instructions were clear. They liked my interactive project with plants. However, there was some problem in giving water. I had to put the max value to the soil moisture sensor, but I thought it would be fine. People gave too much water to plants, so sometimes virtual plants went so big. It was still an amazing day to experience!

Implementation:

<Arduino Code>

// Arduino code

// Input:
// A0 - photocell sensor
// A1 - force resistor sensor
// A2 - soil moisture sensor

int photocellPin = A0; //photocell sensor
int forceSensorPin = A1; //force resistor sensor
int soil = A2; //soil moisture sensor
int forceSensorValue = 0;

void setup() {
  Serial.begin(9600);
  pinMode(photocellPin, INPUT);
  pinMode(forceSensorPin,INPUT);
  pinMode(soil, INPUT);
}

void loop() {
  // Read the value from three sensors
  int lightValue = analogRead(photocellPin);
  int forceSensorValue = analogRead(forceSensorPin);
  int soilValue = analogRead(soil);

  // Send the value to p5.js
    Serial.print(soilValue);
    Serial.print(",");
    Serial.print(lightValue);
    Serial.print(",");
    Serial.print(forceSensorValue);
    Serial.println(); // Print a newline to indicate the end of the data

My Arduino code establishes input connections for a photocell sensor, force resistor sensor, and soil moisture sensor on analog pins A0, A1, and A2. In the setup function, the serial communication is initiated at a baud rate of 9600, and the pinMode is configured for the three pins. Within the main loop, analogRead captures values from the photocell, force sensor, and soil sensor, and these readings are sent to the serial port.

<P5.js Code>

// Define global variables for images, page tracking, and sensor data
let backgroundImage;
let choicebackgroundImage;
let resultbackgroundImage;
let merrychristmasImage;
let cactusImage;
let greenplantImage;
let palmtreeImage;
let currentPage = 1;
let serial;
let latestData = "waiting for data";
let plantSize = 80;
let plantX, plantY;
let xOffset = 0;
let yOffset = 0;
let userChoice = ''

//Sensor Values
let soilMoistureValue = 0;
let photocellValue = 0;
let forceSensorValue = 0;

//Ornament Variables
let ornaments = ['https://i.imgur.com/FPuVyGI.png', 'https://i.imgur.com/fJxVZe3.png', 'https://i.imgur.com/rGZS7Yx.png']; 
let currentOrnament = '';
let ornamentsList = [] ;
let hasOrnament = false;
let isAddingOrnament = false;


//Preload images before setup
function preload() {
  //loadimages
  backgroundImage = loadImage('https://i.imgur.com/lGiEbaQ.png');
  choicebackgroundImage = loadImage('https://i.imgur.com/tmRSqeN.png')
  cactusImage = loadImage('https://i.imgur.com/kIxRg6H.png')
  palmtreeImage = loadImage('https://i.imgur.com/A8tjpQM.png')
  greenplantImage = loadImage('https://i.imgur.com/yMYwr8D.png'); 
  resultbackgroundImage = loadImage('https://i.imgur.com/1IHM8SH.png')
  merrychristmasImage = loadImage ('https://i.imgur.com/MoUEmwD.png')
  
  //Load Ornament Images
   for (let i = 0; i < ornaments.length; i++) {
    ornaments[i] = loadImage(ornaments[i]);
  }
}

// Setup function, called once at the beginning
function setup() {
  // Create canvas fit to the background image
  createCanvas(700,500);
    // Connect to the p5.serialport server
  serial = new p5.SerialPort();
 // Set up serial communication
 serial.list();
 serial.open('/dev/tty.usbmodem101');

 serial.on('connected', serverConnected);

 serial.on('list', gotList);

 serial.on('data', gotData);

 serial.on('error', gotError);

 serial.on('open', gotOpen);

 serial.on('close', gotClose);
 
}

// Callback function when connected to the serial server
function serverConnected() {
 print("Connected to Server");
}

// Callback function when a list of serial ports is received
function gotList(thelist) {
 print("List of Serial Ports:");

 for (let i = 0; i < thelist.length; i++) {
  print(i + " " + thelist[i]);
 }
}

// Callback function when the serial port is opened
function gotOpen() {
 print("Serial Port is Open");
}

// Callback function when the serial port is closed
function gotClose(){
 print("Serial Port is Closed");
 latestData = "Serial Port is Closed";
}

// Callback function when an error occurs in serial communication
function gotError(theerror) {
 print(theerror);
}

// Callback function when new data is received from the serial port
function gotData() {
 
  let currentString = serial.readLine();
  console.log('serial received: ' + currentString);
  trim(currentString);
  if (!currentString) return;
  console.log(currentString);
  latestData = currentString;

  // Split the received data into values
  let dataValues = split(latestData, ',');

  // Assign the values to the corresponding variables
  console.log(dataValues);
  if (dataValues.length === 3) {
    console.log('got 3 values');
    soilMoistureValue = int(dataValues[0]);
    console.log('soil: ' + soilMoistureValue);
    photocellValue = int(dataValues[1]);
    console.log('photocell: ' + photocellValue);
    forceSensorValue = int(dataValues[2]);
    console.log('force: ' + forceSensorValue);
  
}

}


//fullscreen
function keyPressed() {
  if (key === 'f') {
    toggleFullscreen();
  }
}

// Draw function, called continuously
function draw() {
  if (currentPage === 1) {
    drawStartPage();
  } else if (currentPage === 2) {
    drawChoicePage();
  } else if (currentPage === 3) {
    drawResultPage();
  }

}

// Draw function for the start page
function drawStartPage() {
  background(backgroundImage);

  if (mouseIsPressed) {
    currentPage = 2; // Move to the next page when clicked
  }
}

// Draw function for the choice page (next page)
function drawChoicePage() {
  background(choicebackgroundImage);

  textSize(24);
  fill(0);
  

  if (keyIsPressed) {
    // Check the pressed key and move to the corresponding page
    if (key === '1') {
      userChoice = 'cactus';
      currentPage = 3; // Go to the third page with cactus
    } else if (key === '2') {
      userChoice = 'palmTree';
      currentPage = 3; // Go to the third page with green plant
    } else if (key === '3') {
      userChoice = 'greenPlant';
      currentPage = 3; // Go to the third page with palm tree
    }
  }
}


// Draw function for the result page (last page)
function drawResultPage() {
  background(resultbackgroundImage);

   // Display selected plant based on user choice
  if (userChoice === 'cactus') {
    plantX = 485 + xOffset;
    plantY = 337 + yOffset;
    image(cactusImage, plantX, plantY, plantSize, plantSize);
  } else if (userChoice === 'palmTree') {
    plantX = 489 + xOffset;
    plantY = 341 + yOffset;
    image(palmtreeImage, plantX, plantY, plantSize, plantSize);
  } else if (userChoice === 'greenPlant') {
    plantX = 500 + xOffset;
    plantY = 339 + yOffset;
    image(greenplantImage, plantX, plantY, plantSize, plantSize);
  }

  // Check sensor values and simulate plant growth for all plant types
  if (userChoice === 'cactus' || userChoice === 'palmTree' || userChoice === 'greenPlant') {
    // Check soil moisture sensor value
    if (soilMoistureValue > 860) {
      // Increase size when soil moisture is high
      plantSize += 1.5;
      xOffset -= 0.8;
      yOffset -= 1.4;
    }

    // Check photocell sensor value
    if (photocellValue > 70) {
      // Increase size when light is high
      plantSize += 1.5;
      xOffset -= 0.8;
      yOffset -= 1.4;
    }

    // Check force sensor value
    if (forceSensorValue > 1000) {
      // Add ornament when force sensor value is high
      isAddingOrnament = true;
    }
  }

  // Display ornaments
  for (let i = 0; i < ornamentsList.length; i++) {
    let ornament = ornamentsList[i];
    image(ornament.image, ornament.x, ornament.y, ornament.size, ornament.size);
  }

  // If the photocell value is less than 10, switch to a Christmas background
  if (photocellValue < 10) {
    background(merrychristmasImage);
    if (userChoice === 'cactus') {
      plantX = 485 + xOffset;
      plantY = 337 + yOffset;
      image(cactusImage, plantX, plantY, plantSize, plantSize);
      
      // Display the selected plant based on user choice along with ornaments
      if (ornamentsList[0]) {
        for (let i = 0; i < ornamentsList.length; i++) {
          let ornament = ornamentsList[i];
          image(ornament.image, ornament.x, ornament.y, ornament.size, ornament.size);
        }
      }
  } else if (userChoice === 'palmTree') {
      plantX = 489 + xOffset;
      plantY = 341 + yOffset;
      image(palmtreeImage, plantX, plantY, plantSize, plantSize);
    
      if (ornamentsList[0]) {
        for (let i = 0; i < ornamentsList.length; i++) {
          let ornament = ornamentsList[i];
          image(ornament.image, ornament.x, ornament.y, ornament.size, ornament.size);
        }
      }
  } else if (userChoice === 'greenPlant') {
      plantX = 500 + xOffset;
      plantY = 339 + yOffset;
      image(greenplantImage, plantX, plantY, plantSize, plantSize);
    
      if (ornamentsList[0]) {
        for (let i = 0; i < ornamentsList.length; i++) {
          let ornament = ornamentsList[i];
          image(ornament.image, ornament.x, ornament.y, ornament.size, ornament.size);
        }
      }
  }
    
  }
  
}
// MousePressed function checks if the user is ready to add an ornament and if the click is within the plant area
function mousePressed() {
   // Add ornament at the clicked position
  if (isAddingOrnament && mouseX > plantX && mouseX < plantX + plantSize && mouseY > plantY && mouseY < plantY + plantSize) {
    // Add ornament at the clicked position
    let ornament = {
      image: random(ornaments),
      x: mouseX,
      y: mouseY,
      size: 40, // Adjust the size of the ornament as needed
    };

    ornamentsList.push(ornament);

    // Reset the flag
    isAddingOrnament = false;
  }
}


I’ve created a p5.js sketch that brings a virtual Christmas tree to life, responding to Arduino sensors and user interactions. The code begins by initializing global variables, including images for backgrounds, plants, and ornaments, as well as parameters for page tracking and sensor data.

In the setup function, the canvas is created, and a connection to the p5.serialport server is established for communication with an Arduino. Callback functions are set up to handle various serial events, ensuring a smooth connection between the digital representation and physical sensor inputs.

The draw function renders different pages based on user choices and sensor inputs. Users progress through pages by clicking the mouse or pressing keys to select the type of plant displayed on the last page.

The drawResultPage function, specific to the final page, displays the chosen plant with dynamic growth simulation based on soil moisture, light intensity, and force sensor values. The plant size adjusts, and ornaments are added under specific conditions, creating an interactive and festive atmosphere. The code also intelligently switches to a Christmas-themed background in low-light conditions, enhancing the holiday spirit.

My code handles user interactions, allowing users to enter fullscreen mode with the ‘f’ key and add ornaments to the virtual tree by clicking on it. The integration of visual elements, sensor data, and user interactions results in an engaging experience, turning virtual festivities into a tangible and interactive celebration of the Christmas season.

<Communication between Arduino and p5.js>

function drawResultPage() {
  background(resultbackgroundImage);

   // Display selected plant based on user choice
  if (userChoice === 'cactus') {
    plantX = 485 + xOffset;
    plantY = 337 + yOffset;
    image(cactusImage, plantX, plantY, plantSize, plantSize);
  } else if (userChoice === 'palmTree') {
    plantX = 489 + xOffset;
    plantY = 341 + yOffset;
    image(palmtreeImage, plantX, plantY, plantSize, plantSize);
  } else if (userChoice === 'greenPlant') {
    plantX = 500 + xOffset;
    plantY = 339 + yOffset;
    image(greenplantImage, plantX, plantY, plantSize, plantSize);
  }

  // Check sensor values and simulate plant growth for all plant types
  if (userChoice === 'cactus' || userChoice === 'palmTree' || userChoice === 'greenPlant') {
    // Check soil moisture sensor value
    if (soilMoistureValue > 860) {
      // Increase size when soil moisture is high
      plantSize += 1.5;
      xOffset -= 0.8;
      yOffset -= 1.4;
    }

    // Check photocell sensor value
    if (photocellValue > 70) {
      // Increase size when light is high
      plantSize += 1.5;
      xOffset -= 0.8;
      yOffset -= 1.4;
    }

    // Check force sensor value
    if (forceSensorValue > 1000) {
      // Add ornament when force sensor value is high
      isAddingOrnament = true;
    }
  }

  // Display ornaments
  for (let i = 0; i < ornamentsList.length; i++) {
    let ornament = ornamentsList[i];
    image(ornament.image, ornament.x, ornament.y, ornament.size, ornament.size);
  }

  // If the photocell value is less than 10, switch to a Christmas background
  if (photocellValue < 10) {
    background(merrychristmasImage);
    if (userChoice === 'cactus') {
      plantX = 485 + xOffset;
      plantY = 337 + yOffset;
      image(cactusImage, plantX, plantY, plantSize, plantSize);
      
      // Display the selected plant based on user choice along with ornaments
      if (ornamentsList[0]) {
        for (let i = 0; i < ornamentsList.length; i++) {
          let ornament = ornamentsList[i];
          image(ornament.image, ornament.x, ornament.y, ornament.size, ornament.size);
        }
      }
  } else if (userChoice === 'palmTree') {
      plantX = 489 + xOffset;
      plantY = 341 + yOffset;
      image(palmtreeImage, plantX, plantY, plantSize, plantSize);
    
      if (ornamentsList[0]) {
        for (let i = 0; i < ornamentsList.length; i++) {
          let ornament = ornamentsList[i];
          image(ornament.image, ornament.x, ornament.y, ornament.size, ornament.size);
        }
      }
  } else if (userChoice === 'greenPlant') {
      plantX = 500 + xOffset;
      plantY = 339 + yOffset;
      image(greenplantImage, plantX, plantY, plantSize, plantSize);
    
      if (ornamentsList[0]) {
        for (let i = 0; i < ornamentsList.length; i++) {
          let ornament = ornamentsList[i];
          image(ornament.image, ornament.x, ornament.y, ornament.size, ornament.size);
        }
      }
  }
    
  }
  
}
// MousePressed function checks if the user is ready to add an ornament and if the click is within the plant area
function mousePressed() {
   // Add ornament at the clicked position
  if (isAddingOrnament && mouseX > plantX && mouseX < plantX + plantSize && mouseY > plantY && mouseY < plantY + plantSize) {
    // Add ornament at the clicked position
    let ornament = {
      image: random(ornaments),
      x: mouseX,
      y: mouseY,
      size: 40, // Adjust the size of the ornament as needed
    };

    ornamentsList.push(ornament);

    // Reset the flag
    isAddingOrnament = false;
  }
}

In the drawResultPage function, the code checks the sensor values (soil moisture, photocell, and force sensor) and simulates plant growth based on these values. If conditions related to sensor values are met, the plant size is increased, and offsets are adjusted to simulate growth. If the force sensor value is high, the variable isAddingOrnament is set to true, indicating that an ornament should be added. The code also includes a section for displaying ornaments on the plant based on user interactions. If isAddingOrnament is true, the mousePressed function adds an ornament at the clicked position within the plant area.

In summary, the p5.serialport library is used to establish a connection between the p5.js sketch and the Arduino, enabling real-time communication and interaction based on sensor data. The code continuously reads data from the Arduino, processes it, and updates the visual representation of the Christmas tree accordingly.

Digital Challenges & Future Improvement:

Before working on “Pet the Plant”, my projects mostly involved using a single Arduino sensor. However, for this project, I needed to incorporate three different sensors. Initially, I tackled each sensor’s interaction separately by coding them in different p5.js sketches. The challenge arose when I had to merge all these interactions into one comprehensive project and figure out a way to smoothly transition between them.

For future improvement, I would like to use the real plant to make the design of a virtual plant. For this project, I did not know how to animate a real plant to make a virtual plant, and I used pngs to show the virtual plants to users. However, since it is “Pet the Plant”, it would be much more friendly to users if I used their real plants to be virtual plant like avatars of a human and grow them in the game. This could be achieved through computer vision and image processing techniques to capture and interpret the characteristics of users’ real plants. By using a webcam or other image-capturing devices, the system could analyze the live feed of the real plant, extracting features such as color, size, and overall health. I can enable users to see their real plants reflected in the virtual environment. Also, I might implement interactive features where users can virtually nurture and care for their specific plant avatars.

 

 

 

 

 

Final Project – PixelPals

Concept:

This project follows my initial idea of making a digital petting zoo. The name of this zoo is PixelPals. In this petting zoo, you are able to approach different animals and make different interactions with them based on their needs and personalities. The theme and design is quite adorable, aiming to create an atmosphere of comfort and fun. The soundtrack of the background music is from the Nintendo Game, Animal Crossing, something very similar to this project. This project also includes a dedicated control box.

https://editor.p5js.org/Yupuuu/full/2L2O-pC8e

Challenges and Highlights:

The most difficult challenge I had was the serial communication. It took me sometime understand how the serial communication truly works so that I could send more complicated codes. Designing levels of the experience is also hard: it’s important to decide which levels one animal uses, and how to differentiate among all these animals, etc. This is especially difficult because I had to deal with two ends of the communication at the same time. Therefore, it took me a lot of time to figure these things out. And also, sometimes the circuit itself did not work and I had to try some methods such as resetting the Arduino or rewiring things to make it work.

Another problem I ran into the wiring the wires and making the control panel. The control panel is smaller than I thought when I was using the laser cutter to make it. This gave me a hard time putting everything into this enclosed box. In fact, I am still not able to put all wires into it. But one advantage of keeping things outside is that I can easily spot the problem if anything happens. However, this is still a lesson learnt: always make the box bigger than I think.

I am especially proud of using many different features in this single project, including state machine, spritesheet, colliders, etc. All these parts work smoothly with each other to make this project possible. And I am also glad how I used fours buttons to complete several different tasks. Allocating these buttons is also important for the implementation of this project.

Arduino code: https://drive.google.com/drive/folders/1tKOEiyYHFteIk_WYt9-tBES8K4-c9bhL?usp=sharing

VIDEO: IMG_1755

Control Panel: Arduino  Mega,  LCD  Display,  Buttons,  Light  Sensor

User Testing

The current control panel is a result of user testing. Originally, there were no instructions at all. Especially when it came to the light sensor, people who did not know what it was had no idea what it could do and how it could relate to the owl. Therefore, I decided to add a shape of the sun to indicate that this can represent a sun and you need to cover this sun for the owl to sleep. Following this, I added instructions to the control panel. However, as a relaxing experience, I do not want the player to spend a lot of time reading the instructions. Therefore, I ended up using some simple illustrations and words to guide the player.

(After the showcase) It was interesting that almost nobody followed the instructions before I told them to. They liked pushing the button even when it was not lit up. And they never read what was on the LCD screen. The only persons who read them were kids. Probably this game was meant for kids. This really shows me the gap between the designed experience and the actual experience people have. It actually reflects on one of our readings that the interactive media artists should let the audience explore on themselves. Obviously, my project is not such a good example in which audience should be allowed to freely explore it as it might break my project… However, overall, people could understand what my project was about and made a laugh when they actually read the texts on the LCD screen!

Reflection & Improvements

This project is very interesting. It prompted me to utilize all the skills I learnt in this class with fabrication skills make this project. It is very satisfactory to see the final product. Reflecting on this project, I realized the power of physical computing and how the hardware and softwire can work together to create unique experiences. However, one of my initial ideas was to create an animal like controller that can act like an animal. However, that would require much more physical constructions, which I did not have enough time for this time. In this future improvement, this could be added, and more interactions could be added to more animals in this zoo. Maybe some customization functions could also be added to make this experience more interesting and personal.

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

 

EOS Final Project : Frenzy Jump

 

Concept : “Frenzy Jump”

For my project, I decided to create a simple game on p5js.

Idea of the game : For the Arduino part, there are two buttons one functions as a “restart button” to restart the game after losing, and the other functions as an action  button to make the player “triangle” to jump.
The game starts with a background that says “Press red button to start and white button to jump”, upon pressing the red red button the screen will start the game with the player being a “triangle”, as per instruction at the start the user will press the white button for the player to jump over rectangular obstacles, throughout the game there are golden “coins” which are ellipses created to gain extra points on the scoreboard that can be seen on the top right corner of the game. When the user fails with jumping over a certain obstacle, the screen will give you a message saying “GAMEOVER” . To restart the game, the user should press on the red button, and the game will automatically start again.

Inspiration:

I got this idea from a game i think most people have played once in their life, view the image below :

Components:

  • 2 LED buttons , red and white
  • Resistors are built in the led button i did not use the LED side of the button therefore i did not add extra resistors on the breadboard
  • 6 jumper wires

Controller:

Prototype 1 :

Final prototype :

User Experience and Gameplay :

IMG_4787

IMG_4783

IMG_4784

Embedded sketch :

Schematics:

 

its probably incorrect but i tried

 

Testing in Arduino Serial Port :

References :

P5js code : https://editor.p5js.org/mka413/sketches/kwS7JN7Wh

The code that made me proud: I cant choose only one part, because if i must say i am very proud of myself, and what i have achieved this semester, to knowing nothing about coding to creating this simple yet successful simple game. What about difficulties? i will talk about that later on in my blog post.

How does Arduino work with p5js? :

Starting with the code in Arduino after I’ve set up the connections in the controller i wrote the lines of code that we were taught in class about Serial Data communication and put in the corresponding pin numbers for the “restart” and “action”(jump) buttons also i used the Function (pinMode) as  an input for my pins , and the function digitalRead for my pins to be read in values of :false or true lastly, i used the Serial.println to send/print serial data to the port as seen in the lines of code below :

int RedButton= 7;
int whiteButton= 8;
bool isActionButtonPressed = false; //start with false as the buttons are not pressed 
bool isRestartButtonPressed = false;


void setup() {
  Serial.begin(9600); // setting the baud rate for serial data communication 

  pinMode(RedButton, INPUT_PULLUP); 
  pinMode(whiteButton, INPUT_PULLUP);


}

void loop() {
//sending signals whether the buttons are pressed and giving true or false statements to confirm 
  if(digitalRead(RedButton)) {
    if(!isActionButtonPressed) {

      eventActionButtonPressed(RedButton);

      isActionButtonPressed = true;
    }
  }
  else {
    if(isActionButtonPressed) {

      eventActionButtonReleased(RedButton);

      isActionButtonPressed = false;
    }  
  }

  if(digitalRead(whiteButton)) {
    if(!isRestartButtonPressed) {

      eventRestartButtonPressed(whiteButton);

      isRestartButtonPressed = true;
    }
  }
  else {
    if(isRestartButtonPressed) {

      eventRestartButtonReleased(whiteButton);

      isRestartButtonPressed = false; //// printing data to the serial port void eventActionButtonPressed(int pin) 
    }  
  }

}

// printing data to the serial port void eventActionButtonPressed(int pin) 
void eventActionButtonPressed(int pin) {
  Serial.println(String(pin) + ":pressed");
}

void eventActionButtonReleased(int pin) {
  Serial.println(String(pin) + ":released");
}

void eventRestartButtonPressed(int pin) {
  Serial.println(String(pin) + ":pressed");
}

void eventRestartButtonReleased(int pin) {
  Serial.println(String(pin) + ":released");
}

 

After finishing up the Arduino code, now its time to begin serial data communication in p5JS, with firstly adding the ready web serial file that was made available for us, then in the actual p5js sketch code i added these lines of code to make serial data communication work with Arduino IDE :

// Function to read serial data
function readSerial(data) {
  const pin = data.split(':')[0]; 
  const action = data.split(':')[1];
  
  console.log(data);
  
  if(pin == "7" && action == "pressed") {
    if(!player.isJumping) {
      player.isJumping = true;
      player.timeJump = millis();
      player.forceY = -10;
    }
  }
  
   if(pin == "8" && action == "pressed") {
      if(!gameStarted) {
        gameStarted = true;
      }
     
     if(gameover) {
        gameover = false;
        score = 0;
        player.posX = width / 3;
       
        for(const o of obstacles) o.posX += width;

      }
    }

}

// Function to handle key presses
function keyPressed() {
  // If space is pressed, set up serial communication
  if(key == " ") {
    setUpSerial();
  }
  
  // press f to play the game in fullscreen
  if(key == 'f') {
    const fs = fullscreen();
    fullscreen(!fs);
  }
}
// this function is called every time the browser window is resized
function windowResized() {
  resizeCanvas(windowWidth, windowHeight);
}

Difficulties :

I had a lot of difficulties in making my initial acrylic controller to work, which i firstly thought there must be a problem with my code, after numerous attempts in trying to make the button and flip switch to work, i gathered that the connections were not correct or they have been short circuited. Thinking fast, i created the usual breadboard  connections with push button switches that worked instantly when uploading the code to my arduino. Also i had trouble with making the serial data communication to work on p5js where the browser would not pop up so i can make the connection to the serial port, which was nerve wrecking but i eventually after trial and error i got it to work perfectly.

My initial Conntroller :

Changes for the future:

I would really liked to have my first controller to work, it looked really cool, and would have fit in more with my game and its aesthetic. Still i would like to advance more in my coding skills to create something much more complex. Adding more difficult obstacles, and increasing the speed the longer you play, with a high score page also.

 

 

 

 

 

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);
}

Final Project – Interactive Glove

Concept:

My concept for the final presentation is the Interactive Glove: a completely different way of interacting with the computer. The idea behind this project comes from “A Brief Rant on the Future of Interaction Design”, where interaction with digital media is discussed and criticized. After thinking about it thoroughly, I imagined that the best way to innovate in the interaction field would be to use a medium that we commonly use, our hands, but in a way that will utilize the limbs differently.

In this case, we can imagine the interactive glove as a binary system: we have 4 sensors in the left hand and 4 pins on the right hand that will turn on a sensor on contact. This allows for the user to have versatility in terms of how they want to use the glove. Some users will prefer to use one finger for all 4 sensors, while others might require 4 fingers for 4 sensors. Because it is a binary system, the glove can do anything: It can become a mouse, a keyboard, a switch, a controller, etc…

To show the versatility of this input method, we will use p5.js alongside some small games and experiences in order to show how the glove inputs values.

Implementation:

P5.js Sketch: https://editor.p5js.org/ff2185/sketches/A4mnwlg4h

Arduino code:

/*
  DigitalReadSerial

  Reads a digital input on pin 2, prints the result to the Serial Monitor

  This example code is in the public domain.

  https://www.arduino.cc/en/Tutorial/BuiltInExamples/DigitalReadSerial
*/

// digital pin 2 has a pushbutton attached to it. Give it a name:
int inputA = 2;
int inputB = 3;
int inputC = 4;
int inputD = 5;

// the setup routine runs once when you press reset:
void setup() {
  // initialize serial communication at 9600 bits per second:
  Serial.begin(9600);
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(inputA, INPUT);
  pinMode(inputB, INPUT);
  pinMode(inputC, INPUT);
  pinMode(inputD, INPUT);
  while (Serial.available() <= 0) {
    digitalWrite(LED_BUILTIN, HIGH);  // on/blink while waiting for serial data
    Serial.println("0,0");            // send a starting message
    delay(300);                       // wait 1/3 second
    digitalWrite(LED_BUILTIN, LOW);
    delay(50);
  }
}

// the loop routine runs over and over again forever:
void loop() {
  // print out the state of the button:// wait for data from p5 before doing something
  while (Serial.available()) {
    digitalWrite(LED_BUILTIN, HIGH);  // led on while receiving data
    if (Serial.read() == '\n') {
      // read the input pin:
      int stateA = digitalRead(inputA);
      int stateB = digitalRead(inputB);
      int stateC = digitalRead(inputC);
      int stateD = digitalRead(inputD);
      delay(5);
      int sensor2 = analogRead(A1);
      delay(5);
      Serial.print(stateA);
      Serial.print(',');
      Serial.print(stateB);
      Serial.print(',');
      Serial.print(stateC);
      Serial.print(',');
      Serial.println(stateD);
    }
  }
  digitalWrite(LED_BUILTIN, LOW);
  delay(1);  // delay in between reads for stability
}

 

Arduino:

The Arduino Design is a simple switch system with 4 resistors connected to the ground, each connected to its proper pin and a cable which will be the connection to the 5v source. On the other side, we connected 4 cables to the 5v strip. This allows us to create a circuit in which any time 5v touches a cable that is on the other side, it will set the digitalInput of the pin to 1.

To cover the circuit, I handcrafted a “sandwich” shaped box that will allow the cables to move freely in the vertical axis. The biggest problem was to create an enclosure that would not disturb the cable movement. My idea, to not utilize the classic open top box, was to do this tringular enclosure that allows cables to move up and down freely. Due to the pure nature of the circuit itself, horizontal movement has to be limited in order to not disturb the functioning, so that is why movement is only constrained in the vertical axis

P5.js:

The P5.js sketch is a combination of past projects, examples provided by p5.js and other sketches I have gathered during my research. Each one of the experiences/games is a representation of different inputs styles: For example, the piano will behave differently based on the combination of fingers we press, while the rocket moves from left to right with the index and ring finger.

The available experiences are the following:

  • Piano: The piano experience is a recreation of a previous project. Back then, I utilized the keyboard in order to press the piano keys and generate the sound. This time, the gloves will do that function. Every key is mapped to a certain combination. For example. the “DO” note corresponds to touching just the index finger sensor. The following one is mapped to the middle finger, and so on until you will need to use two fingers. The input method shown here is converting the glove into a set of 2^4 inputs. In other words, a binary input.
  • Rocket: The Rocket Game involves long pressing a single sensor in order to fly the rocket up, go to the left or go to the right. There is no score, no timer, just you and a single fuel bar. Careful, if you go too low you will crash. Try your best to stay on air!
    This game simulates what it would be to use it as a mouse (the mouse clicks) to control the rocket.
  • Disco: The Disco experience is simple. Out of the 4 buttons you press, each of them will show a different ever-changing background. Try all of them and focus on the colors!
    This shows the glove working as 4 different buttons independent of each other.
  • Snake: The Snake Game is all about trying to eat the fruits that will randomly spawn in the map. The 4 sensors in the glove represent the 4 directions that the snake can go to: left, right, top, down. This makes the glove resemble the keyboard arrows system.

Code:

One piece of code I am proud of is the state management. A seemingly trivial problem becomes a major challenge when you realize every single experience needs to have a different setup. More over, the different frame rate, strokes, fill, background and other setting that vary in each screen make it especially specific to manage.

if (state == "intro") {
      scoreElem.html("");
      drawIntro();
    } else if (state == "piano") {
      scoreElem.html("");
      drawPiano();
      if (frameCount % 60 == 0) {
        for (var i = 0; i < 10; i++) {
          rSide[i].white();
          black[i].white();
          mid[i].white();
          lSide[i].white();
        }
      }
    } else if (state == "rocket") {
      rectMode(CENTER);
      scoreElem.html("");
      drawRocket();
    } else if (state == "disco") {
      scoreElem.html("");
      drawDisco();
    } else if (state == "snake") {
      scoreElem.html("Score = 0");
      frameRate(15);
      stroke(255);
      strokeWeight(10);
      drawSnake();
    }else if(state == "tutorial"){
      stroke(255);
      scoreElem.html("");
      drawTutorial();
    }

Another code I especially like is the code that allows the snake to move:

function updateSnakeCoordinates() {
  for (let i = 0; i < numSegments - 1; i++) {
    xCor[i] = xCor[i + 1];
    yCor[i] = yCor[i + 1];
  }
  switch (direction) {
    case "right":
      xCor[numSegments - 1] = xCor[numSegments - 2] + diff;
      yCor[numSegments - 1] = yCor[numSegments - 2];
      break;
    case "up":
      xCor[numSegments - 1] = xCor[numSegments - 2];
      yCor[numSegments - 1] = yCor[numSegments - 2] - diff;
      break;
    case "left":
      xCor[numSegments - 1] = xCor[numSegments - 2] - diff;
      yCor[numSegments - 1] = yCor[numSegments - 2];
      break;
    case "down":
      xCor[numSegments - 1] = xCor[numSegments - 2];
      yCor[numSegments - 1] = yCor[numSegments - 2] + diff;
      break;
  }
}

Learnings and Improvements

For this final project, I feel that I have learned a lot from the Arduino development field. Small things that we usually omit during the process of creating these types of projects become bigger problems when it comes to user usability and versatility. For example, the contacts for the glove are really hard to match due to the lesser amount of copper I had available. Moreover, the cables were extremely hard to position properly in order to fix them but also allow movement of the whole arm.

In general, the construction process for the cables and circuit was the hardest part of this project. I would like, in future iterations to improve the cable management and the outside box, maybe ideate some other way to construct a box that will allow the movement of the glove but also easy access if needed (open box).

To conclude, I have really enjoyed every step I took in this class. I have learned ways of expressing my ideas and thoughts in manners I would have never imagined. I have grown to love the idea of exploring new horizons and implementing new designs, approaches, methods for my projects.

Thank you and see you soon.