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).

 

Week 11 Group Assignment w/ Naser

Concept:

After learning about the functionalities and the means of communicating between P5 and Arduino platforms, we were given the assignments to initialize various scenarios to and from p5.

First Assignment:

In this assignment, we initialized an Arduino to p5 connection using a potentiometer to control the horizontal position of an ellipse:

Arduino code:

const int potPin = A0;  // Analog pin connected to the potentiometer
void setup() {
  Serial.begin(9600);
}
void loop() {
    int potValue = analogRead(potPin);  // Read the value from the potentiometer
      // Send the potentiometer value to p5.js
      Serial.println(potValue);
}

P5 Code:

let ellipseHorizental;
function setup() {
  createCanvas(640, 480);
  textSize(18);
  ellipseHorizental = width/2; 
}
function draw() {
  background(220);
  // Draw ellipse with width based on potentiometer value
  fill(255, 0, 255);
  ellipse(ellipseHorizental, height / 2, 100, 150);
  if (!serialActive) {
    text("Press Space Bar to select Serial Port", 20, 30);
  } else {
    text("Connected", 20, 30);
    // Print the current potentiometer value
    text('Potentiometer Value = ' + str(ellipseHorizental), 20, 50);
  }
}
function keyPressed() {
  if (key == " ") {
    setUpSerial();
  }
}
function readSerial(data) {
  if (data != null) {
    // convert the string to a number using int()
    let fromArduino = split(trim(data), ",");
    // Map the potentiometer value to the ellipse width
    ellipseHorizental = map(int(fromArduino[0]), 0, 1023, 0, 640); 
  }
}

Video:

Assignment 2:

In this assignment, we initiated a p5 to Arduino response. The slider in p5 can control the brightness of an LED in Arduino.

Arduino code:

int LED = 5; // Digital pin connected to the LED
void setup() {
  Serial.begin(9600);
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(LED, OUTPUT);
  // start the handshake
  while (Serial.available() <= 0) {
    digitalWrite(LED_BUILTIN, HIGH); // on/blink while waiting for serial data
    Serial.println("0");             // send a starting message
    delay(300);                       // wait 1/3 second
    digitalWrite(LED_BUILTIN, LOW);
    delay(50);
  }
}
void loop() {
  // wait for data from p5 before doing something
  while (Serial.available()) {
    digitalWrite(LED_BUILTIN, HIGH); // led on while receiving data
    int brightnessValue = Serial.parseInt();
    if (Serial.read() == '\n') {
      delay(5);
      Serial.println(brightnessValue);
    }
    analogWrite(LED, brightnessValue);
    digitalWrite(LED_BUILTIN, LOW);
  }
}

P5 Code:

let brightnessSlider;
function setup() {
  createCanvas(640, 480);
  textSize(18);
  // Create a slider
  brightnessSlider = createSlider(0, 255, 128); // Set the range and initial value
  brightnessSlider.position(20, 100); // Set the position of the slider
}
function draw() {
  background(255);
  // Draw a slider
  fill(255, 0, 0);
  rect(brightnessSlider.x, brightnessSlider.y, brightnessSlider.width, brightnessSlider.height);
  if (!serialActive) {
    text("Press Space Bar to select Serial Port", 20, 30);
  } else {
    text("Connected", 20, 30);
    // Print the current brightness value
    text('Brightness = ' + brightnessSlider.value(), 20, 50);
  }
}
function keyPressed() {
  if (key == " ") {
    setUpSerial();
  }
}
function readSerial(data) {
  if (data != null) {
    let sendToArduino = brightnessSlider.value() + "\n";
    writeSerial(sendToArduino);
    }
}
int LED = 5; // Digital pin connected to the LED
void setup() {
  Serial.begin(9600);
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(LED, OUTPUT);
  // start the handshake
  while (Serial.available() <= 0) {
    digitalWrite(LED_BUILTIN, HIGH); // on/blink while waiting for serial data
    Serial.println("0");             // send a starting message
    delay(300);                       // wait 1/3 second
    digitalWrite(LED_BUILTIN, LOW);
    delay(50);
  }
}
void loop() {
  // wait for data from p5 before doing something
  while (Serial.available()) {
    digitalWrite(LED_BUILTIN, HIGH); // led on while receiving data
    int brightnessValue = Serial.parseInt();
    if (Serial.read() == '\n') {
      delay(5);
      Serial.println(brightnessValue);
    }
    analogWrite(LED, brightnessValue);
    digitalWrite(LED_BUILTIN, LOW);
  }
}

Video:

Assignment 3:

This assignment we spent an unholy and frankly embarrassing amount of time on this. We modified the code from class and figured out a way to light up both LEDs when the ball on the screen bounces. The wind speed depends on readings from the LDR, so the ball goes in different directions when the board is in light or dark conditions. At a certain light level, the ball remains stationary.

Arduino Code:

int leftLedPin = 2;
int rightLedPin = 5;
void setup() {
// Start serial communication so we can send data
// over the USB connection to our p5js sketch
Serial.begin(9600);
// We&#39;ll use the builtin LED as a status output.
// We can&#39;t use the serial monitor since the serial connection is
// used to communicate to p5js and only one application on the computer
// can use a serial port at once.
pinMode(LED_BUILTIN, OUTPUT);
// Outputs on these pins
pinMode(leftLedPin, OUTPUT);
pinMode(rightLedPin, OUTPUT);
// Blink them so we can check the wiring
digitalWrite(leftLedPin, HIGH);
digitalWrite(rightLedPin, HIGH);
delay(200);
digitalWrite(leftLedPin, LOW);
digitalWrite(rightLedPin, LOW);
// start the handshake
while (Serial.available() &lt;= 0) {
digitalWrite(LED_BUILTIN, HIGH); // on/blink while waiting for serial
data
Serial.println(&quot;0,0&quot;); // send a starting message
delay(300); // wait 1/3 second
digitalWrite(LED_BUILTIN, LOW);
delay(50);
}
}
void loop() {
// wait for data from p5 before doing something
while (Serial.available()) {
digitalWrite(LED_BUILTIN, HIGH); // led on while receiving data
int left = Serial.parseInt();
int right = Serial.parseInt();
if (Serial.read() == &#39;\n&#39;) {
digitalWrite(leftLedPin, left);
digitalWrite(rightLedPin, right);
int sensor = analogRead(A0);
delay(5);
int sensor2 = analogRead(A1);
delay(5);
Serial.print(sensor);
Serial.print(&#39;,&#39;);
Serial.println(sensor2);
}
}
digitalWrite(LED_BUILTIN, LOW);
}

P5 Code:

let rVal = 0;
let velocity;
let gravity;
let position;
let acceleration;
let wind;
let drag = 0.99;
let mass = 50;
let groundFlag;
let dropFlag = false; // flag for when the spacebar is pressed and the ball should drop
let windFlag = false; // flag to start/stop wind
function setup() {
createCanvas(640, 500);
// noFill();
position = createVector(width / 2, 0);
velocity = createVector(0, 0);
acceleration = createVector(0, 0);
gravity = createVector(0, 0.5 * mass);
wind = createVector(0, 0);
groundFlag = 0;
frameRate(30);
textSize(20)
}
function draw() {
background(255);
text(&quot;rVal: &quot;+str(rVal), 20, 55);
text(&quot;wind.x: &quot;+str(wind.x), 20, 80);
if (!serialActive) {
text(&quot;Press Space Bar to select Serial Port&quot;, 20, 30);
} else {
text(&quot;Connected&quot;, 20, 30);
}
if (dropFlag == true) { // when spacebar is pressed, start the sketch
if (position.y == height - mass / 2) {
groundFlag = 1; // this value is sent to the LED in the Arduino end
} else {
groundFlag = 0;
}
applyForce(wind);
applyForce(gravity);
velocity.add(acceleration);
velocity.mult(drag);
position.add(velocity);
acceleration.mult(0);
ellipse(position.x, position.y, mass, mass);
if (position.y &gt; height - mass / 2) {
velocity.y *= -0.9;
position.y = height - mass / 2;
}
if (windFlag == true) {
wind.x = map(rVal, 0, 1023, -1, 1);
}
}
}
function applyForce(force) {
let f = p5.Vector.div(force, mass);
acceleration.add(f);
}
function keyPressed() {
if (keyCode == UP_ARROW) {
windFlag = true // wind is enabled when up arrow key is pressed
}
if (keyCode == DOWN_ARROW) {
windFlag = false // wind is paused when down arrow key is pressed
wind.x = 0
}
if (key == &quot; &quot;) {
setUpSerial();
dropFlag = true;
}
}
function readSerial(data) {
////////////////////////////////////
//READ FROM ARDUINO HERE
////////////////////////////////////
if (data != null) {
// make sure there is actually a message
let fromArduino = split(trim(data), &quot;,&quot;); // split the message
// 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. &quot;103&quot; becomes 103
rVal = int(fromArduino[0]);
alpha = int(fromArduino[1]);
}
//////////////////////////////////
//SEND TO ARDUINO HERE (handshake)
//////////////////////////////////
let sendToArduino = groundFlag + &quot;,&quot; + groundFlag + &quot;\n&quot;;
writeSerial(sendToArduino);
}
}

Video:

Reflection:

The overall exercises were a great way to comprehend further the logic behind the coding and how both platforms are intertwined.Ā  Knowing how things are operated between each other, we can now effectively work on our final projects and have so many possibilities of projects to work on.

Reading Response – Week 11

In this text, the author asks the question: How often do we excuse the design of things that cater to disabled people because of the market for which it is intended? The design of such objects, it seems, mostly tries not to portray a positive image but no image at all, as if to hide disability or to regard it as shameful. Weā€™ve come from an era where it wasnā€™t outrageous to say things like ā€œmedical products should not be styledā€ [p. 16] when talking about glasses to an era where glasses are stylized and in some contexts even considered fashionable. It is interesting how weā€™re slowly progressing with some disabilities to make the design of aiding instruments more ā€œacceptableā€ to abled people instead of trying to hide the disability through design. For example, contact lenses could make bad eyesight invisible to the observer, but most people still choose to wear glasses over lenses, in part because of the accessibility of their design. Even then, there are disabilities where advancement in aiding technology is constrained by the need for invisibility (like with hearing aids), which I think is a shame. The author wants such instruments to also follow the blueprint of glasses, so that advancement is favored over discretion. However, at the same time, the pressure of making the design of aiding instruments universal means there is a serious risk of designers going too far as to sacrifice functionality over design. The first priority, I think, should be functionality, and then design, in the case of such instruments, so the overall user experience is better.

Final Project!!!

For my final project, I’m initially thinking about expanding my midterm project’s functionality physically, so that you’ll be able to control the music player through components of the arduino kit (next, previous, volume control, slider, shuffle), and the music player will communicate back with the arduino with maybe a light show or something to go with the music. I’ll have to look into a cool physical design as well along with code design to make it realer. That’s the idea for now, anyway!

Budget Rave

Inspiration

Speakers at the Convenience Store are too expensive (the good ones that is). This presents a problem for me, because for reasons unknown to me, my room has been established as the instinctively occasional hangout spot by my friends (maybe it’s because I have the biggest fridge and a microwave and a kettle for chai AND a roommate who’s hardly ever in my room). Anyways, for this assignment, I thought I’d make up for the absence of a speaker in my room by using the free speaker given to me by the university for my IM course.

Concept

I’m using an ultrasonic sensor to gauge how far an object is from the breadboard and playing different notes based on this distance. Keep in mind I don’t know the first thing about music theory so I used random notes for each distance so the music itself might not make sense. Or maybe it does and I’ve created a new wave of post-music music. Anyways, here’s a pic and the code.

Code

const int trigPin = 9;  
const int echoPin = 10;
float duration, distance; 
#include "pitches.h"

int melody[] = {
  NOTE_A1, NOTE_B2, NOTE_C3, NOTE_D4, NOTE_E5, NOTE_F3, NOTE_C4
};

void setup() {
  pinMode(trigPin, OUTPUT);  
  pinMode(echoPin, INPUT);  
  Serial.begin(9600);  
}

void loop() {
  digitalWrite(trigPin, LOW);
  delayMicroseconds(2);
  digitalWrite(trigPin, HIGH);
  delayMicroseconds(10);
  digitalWrite(trigPin, LOW);

  duration = pulseIn(echoPin, HIGH);
  distance = (duration * 0.0343) / 2;
  delay(100);

  int melodyIndex = map((int)distance, 0, 50, 0, 7);  // Adjust the range (0-50) so the notes change over a noticeable distance
  melodyIndex = constrain(melodyIndex, 0, 7);  // Ensure the index is within the valid range
  tone(8, melody[melodyIndex]);
  Serial.print("Distance: ");
  Serial.println(melodyIndex);
}

See for yourself

Here’s links to a video showing the functionality of my instrument and a bonus video of my friend discovering my instrument and easing up to the idea of raving to the melodious notes. Easter egg alert: we used the same lamp as a strobe light as the one in my previous assignment. Maybe the 100 campus dirhams weren’t a bad investment after all…

https://drive.google.com/drive/folders/1wF5HzdHqWylkz1_lZ35EAcVphwg50Nb9?usp=sharing

 

Week 10 – Reading Reflection

A Brief Rant on the Future of Interaction Design

This guy really doesnā€™t like screens, so much so that he calls screens ā€œPictures Under Glassā€, meaning that they provide no connection to the task that one is performing. And to be honest, that is true. Even while typing this response, I can feel the keys under my fingertips, knowing where each key is without looking at it. I can tell exactly how much pressure I need to apply on each key for it to type something. I hear a clicking sound whenever I press a key, and I can anticipate which key produces which sound before I even press it with my fingers. I honestly love my keyboard because of how it feels to the touch, even though itā€™s not exactly very dynamic. I canā€™t say the same for my phone screen.

This guy suggests that screens were never meant to be the final all-encompassing technology of interaction design, but they are merely a transitional technology. After all, what else in the natural world do we use the gesture of sliding our fingers for? Even the act of making a sandwich requires more interaction than screens, the supposed Interface of the Future.

I find this vision of the future of interaction design very interesting, because Iā€™ve never really thought about how much screens on handheld devices actually ignore our very hands before. I am curious to explore some other visions and ideas that consider this incapacity of screens and provide other exciting possibilities for the purpose of interaction design.

 

Responses

This guy mentions the idea of finger-blindness here which really caught my eye. Finger blindness, as he puts it, is when you donā€™t use your hands to their full potential for doing what they were meant to do (not swiping away at screens) making it harder to attach meaning and value to the act of touching something, which is a scary thought. Other than that, I love how this guy addresses these responses with humor and also makes sense at the same time. He reiterates that screens arenā€™t necessarily bad – for now. Itā€™s just that we shouldnā€™t be limiting ourselves to just screens when we think about future advancements in technology but come up with other receptive devices that appreciate the intricacy of our hands. After all, humans deserve so much more than just screens.

Financial Management

So uhhh… I bought… A desk lamp… For a hundred dirhams. A hundred. A hundred as in 1-0-0.

Impulsive purchases got the best of me again.

Anyways, I decided I would use this lamp for something other than playing around with its colors. So I made this kind of proximity/light sensor thing. Basically when I hold the lamp close to the the circuit, the red LED turns off and as I back up the lamp, the red LED blinks slower and slower until it stops blinking. As long as the red LED blinks/is on, the green LED can be turned on using a digital switch. Initially while making this circuit, I ran into some very silly difficulties – I attached the components on a single row which meant the circuit was never complete and I couldn’t figure out the problem the entire night for the life of me. Here is the bad circuit:

But one debugging session later here is my creation, the good circuit:

It works in a very fun way and the lamp works in an even fun-er way. Check out the video I’ve attached of the LEDs in action:

https://drive.google.com/drive/folders/1uOQwTJqiPt6b5cQ-L8GTVn3CXHp50x17?usp=sharing

Here is the code:

int green = 10;
int red = 11;
// the setup routine runs once when you press reset:
void setup() {
  // initialize serial communication at 9600 bits per second:
  Serial.begin(9600);
  pinMode(green, OUTPUT);
  pinMode(red, OUTPUT);
}

// the loop routine runs over and over again forever:
void loop() {
  // read the input on analog pin 0:
  int sensorValue = analogRead(A0);
  // print out the value you read:
  Serial.println(sensorValue);
  delay(1);  // delay in between reads for stability

if (sensorValue > 40) { // when far, turn on but don't blink
  digitalWrite(green, LOW);
  digitalWrite(red, LOW);
}
if (sensorValue > 15 && sensorValue <= 40) { // when slightly close, blink slow
  digitalWrite(green, HIGH);
  digitalWrite(red, HIGH);
  delay(100);
  digitalWrite(red, LOW);
  delay(100);
}
if (sensorValue <= 15 && sensorValue > 10) { // when closer, blink fast
  digitalWrite(green, HIGH);
  digitalWrite(red, HIGH);
  delay(250);
  digitalWrite(red, LOW);
  delay(250);
} 
if (sensorValue <= 10) { // when very close, turn off
  digitalWrite(green, HIGH);
  digitalWrite(red, HIGH);
}  

}

 

Week 9 – Reading Response(s)

Making Interactive Art: Set the Stage, Then Shut Up and Listen

In this class, itā€™s easy to present our artworks as static pieces with instructions for the participants and a statement of how we want them to interpret it. The author of this piece, however, believes that this defeats the purpose of interactive art. He wants interactive artists to think of their artwork as a performance rather than a painting; you donā€™t go about narrating the plot to the audience in a performance – you let the audience perceive the performance through their own senses and interpretation. In the same way, we shouldnā€™t present our interactive artworks on a canvas with instructions and interpretations – we give them the basic context so they know what theyā€™re getting into, and then we let them explore for themselves. We have to trust our audience to discover their interpretation through a ā€˜conversationā€™ with our artwork with merely a suggestion of what their course of action should be – unaided by our own directives – because only in this way can we offer an authentic emotional experience to our audience.

 

Physical Computingā€™s Biggest Hits (and misses)

This one was just a list of some of the most common project themes in interactive artwork projects. Itā€™s funny because a lot of us will be revisiting these themes many times ourselves for our own artworks, but I donā€™t think thereā€™s something deeply wrong with that. I think finding our own twist on things that have widely been done before is still our own thing. After all, it is easy to feel like everything worth doing has already been done. Not that thatā€™s true, but I think we may just come closer to originality by stumbling upon an idea no oneā€™s heard of before while first doing stuff that has been done, countless times over.

The Last Piece

I hate this puzzle/coaster/whatever the hell this is. Ever since I stole this (got it from a fair for free) I have kept it on my desk, neatly arranged in the exact way it came. But you can never have good things it seems. Because a few days ago, my friend came over and thought himself smart enough to reassemble this. Needless to say he wasn’t and he couldn’t. I had to bear the sight of this disarranged mess of a puzzle on my desk for days until another friend who had the same puzzle showed me his and I was able to successfully rearrange it. Those few days were not it, let me just say. But I have since come to realize that the puzzle isn’t for show. It is the process of failing to put it back and then eventually rearranging it that makes it… well, it. And so I created a little game with this puzzle called “The Last Piece”. Essentially, you know where the last piece goes, but the rest you have to figure for yourself. And only when you’re done with the rest can you put back the last piece. What do you get from it? The satisfaction of seeing an LED bulb turn red. Who doesn’t love to see little red lights?

The circuit itself is simple: I used aluminum foil to create points of contact between the board and the piece which I stole from my building’s main lounge. I then thought about what more this could use: more lights! So I added more contacts so that when the last piece is placed in the wrong position, a red light turns on and when it is in the right position, the yellow light turns on, indicating you’ve won.

I also finally have a picture of the solved puzzle so I never have to deal with the mess again, no matter how many times my friends fail at this game.

Week 8 – Reading Reflection

Emotion and Design

In the previous reading from Don Norman, he talked about how design flaws in everyday objects make it difficult for normal people to operate them, and how an object that is ā€œusable but uglyā€ is more beneficial that an object that is visually pleasing but unusable; but his point was misinterpreted by many and had people thinking he advocated for usability over appealing design. In this reading, he clarifies that this was not to discredit visual appeal, but to bring usability to the same level of importance as it. He hopes that ā€œthe future of everyday things [will] be ones that do their job, that are easy to use, and that provide enjoyment and pleasureā€ [p. 41], emphasizing that even though usability gets the job done, it is still important for the design to be appealing and the functionality to be easy to figure out, because in the end, ā€œit is time to have more pleasure and enjoyment in lifeā€ [p.41]. At the same time, he says what I think is the crux of his point: ā€œTo be truly beautiful, wondrous, and pleasurable, the product has to fulfill a useful function, work well, and be usable and understandableā€ [p. 42]. And I canā€™t argue with that.Ā 

 

Her Code Got Humans on the Moon

Honestly, before reading this, I only knew that Maragaret Hamiltion was a someone who did something in the Apollo missions. But what I didnā€™t know was the depth of her involvement in the missions; without her, there might not have been a mission. It is inspiring to learn about her essentially pioneering systems programming and leading the software division of 400 people, all while mothering a 4 year old. Makes me think about how I can barely finish my assignments on time even without the taking-care-of-a-4-year-old part. It was also interesting to read about the ā€œThat would never happenā€ story, which is a very apt representation of human fallibility. The purpose of good system programming, I believe, is to minimize this exact fallibility – in other words, to be smarter than humans.