Final Project Report

A. Project Title– Your Name – Your Instructor’s Name

Who Paints More, Jim Liu, instructed by Rodolfo Cossovich. 

B. Concept and Design & C. Fabrication and Production

After doing some research and discussing our ideas, I and my partner Tawan decided to make a two-player interactive game called Who Paints More. I read the article by Ernest Edmonds and learned that Dynamic-Interactive (Varying) is the highest level of interactivity that the work is unpredictable and interactive, where the design will learn experience and adjust its output, which adds more random and interesting functions to the performance. (Ernest Edmonds, Art, Interaction and Engagement). Thus, in my final project, I included two players playing a game while using the computer as a medium to process the two players’ behaviors, together with randomness added to the project to create a “Dynamic-Interactive (Varying)” project. Thus, I came up with the game idea, in which two players will control the direction of a red brush and a blue brush respectively by physical control, and whoever paints his color more wins the game. We had intended to use a BNO055 Acceleration sensor, but we found it too difficult to use and connect two of them into one Arduino board (because the sensor must be connected to A5, which is a special pin to make it work). Because of the limited time and effort, we decided to use potentiometers instead. We decided to laser-cut two steers and stick them onto the potentiometers, which serve as car steers.  When it comes to the brushes’ display, I decided to use two strokeless circles filled with red and blue respectively. Speaking of the random buffs, I added three kinds of buffs to the game: 1. brush thickening (8/28) 2. brush accelerating (8/28) 3. paint splashing (8/28) 4. painting the whole screen black (1/28), red (1/28), blue (1/28), or pink (1/28). 

(Our design before user testing)

 

During the user testing period, some of the users complained that the potentiometer design had some limits because it could only be turned to a specific point and got stuck, but others think it can be part of the game to make it more challenging. Therefore, we eventually decided not to change it (also because we didn’t have much time). Likewise, a lot of users complain that the two steers being set vertically was not a user-friendly choice because they found it confusing to change the direction. Thus, we reformed it by setting the two steers horizontally. 

(Our finished product design)

 

After designing, we began to fabricate the product. Firstly, I grabbed two potentiometers from my Arduino kit as Arduino Inputs to make a rough code. I just used the “SendMultipleValues” example to realize it. After that, I started to code the Processing part, which is the major difficulty of the project. I first coded the movements of two paints, then the countdown system, and finally the buffs. Actually, I did not meet with too many difficulties.  The biggest may be how to delete a number every time after the second finishes in the countdown system, and after asking Prof. Rudi for help, I solved it by inserting a black square to cover the old number. During the buff coding stage, I first decided to use buffs that each have an icon and sound effect, but I found it hard to search for resources. So I decided to make it random and share all the buffs with one icon. At the same time, my partner Tawan helped me design the hardware of the project. He tried to use BNO055 sensors, but failed to figure out how to connect two of them to one Arduino board, and found it very unstable. Thus, we decided to use potentiometers instead. After that, he designed the steers and devised the cuttle.xyz file and I used the file to access laser-cutting to fabricate the hardware. Then, I ensembled all the parts together and hot-glued the parts. Finally, because I was learning music composition in my spare time this semester, I composed background music for the game and adjusted the game timing system. The primarily finished product was like this: 

(How the game works before user testing)

(The steer design)

(The music composition)

After the user testing, besides the aforementioned changes, I tried to make paints more visible when they are painting the area in the same color according to users’ complaints. It was really a hard nut to crack because I didn’t figure out how to realize it. However, I finally figured it out because of a sudden inspiration. I added a “pre-circle” in my code to realize it. Moreover, we painted our hardware half-red and half-blue. This is our final product after the user testing: 

(This is the circuit: very straightforward two potentiometers)

D. Conclusion:

In conclusion, our project aims at creating a two-player game in which two players respectively control a steer to monitor the direction of two paints, and whoever paints more wins the game. This project aims at people who seek fun and randomness when they are gathering out in their spare time. Much fun will come into being, and the users will feel like playing the game again. And I think my project has fulfilled this intended purpose. In the final presentation, my users really had fun playing the game, except for one player who complained a little about the potentiometers’ spinning limit. Based on my users’ responses, I think it is successful, except that the potentiometer can only be spun to a limit. I hope I can figure out how to use BNO055 sensors later when I have time. Likewise, I really learned a lot when doing the project, such as some logic in coding like “flag” and “pre-circles”, and how to fabricate a box using laser-cutting. Additionally, the finished project aligns with my definition of interaction, in which two or more objects input, process, and output. In my case, 3 objects are interacting with each other. Two players will see the current movement of the brushes and the situation of the game, and think about how to control the direction to maximize their painting or grab the buffs, thus outputting through moving their hands to spin the steers. The system will input the two player’s rotation of steers, then process the movements of the brushes on the screen and add random buffs in the game, and eventually display the game situation and result to the players. 

E. Annex

My diagram: 

The cuttle file:

My Arduino Code: 

#include "SerialRecord.h"

SerialRecord writer(2);

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

void loop() {
  int sensorValue = analogRead(A0);
  int sensorValue1 = analogRead(A1);

  writer[0] = sensorValue;
  writer[1] = sensorValue1;
  writer.send();

  delay(100);
} 

My Processing Code: 

import processing.serial.*;
import processing.sound.*;
import osteele.processing.SerialRecord.*;
Serial serialPort;
SoundFile sound;
SerialRecord serialRecord;
float x1_direction, y1_direction;
float x2_direction, y2_direction;
float x1, y1, x2, y2;
float prex1, prey1, prex2, prey2;
long countred=0;
long countblue=0;
int flag=1;
int buff, x, y;
boolean buff21=false;
boolean buff22=false;
boolean buff31=false;
boolean buff32=false;
boolean e=true;
long pre21=0;
long pre31=0;
long pre22=0;
long pre32=0;
int r1=200;
int r2=200;
long gamestart;
int gamestarted;

void setup() {
  fullScreen();
  background(0);
  rectMode(CENTER);
  String serialPortName = SerialUtils.findArduinoPort();
  serialPort = new Serial(this, serialPortName, 9600);
  ellipseMode(CENTER);
  serialRecord = new SerialRecord(this, serialPort, 2);
  sound = new SoundFile(this, "final.mp3");
  buff=18250;
  x=floor(random(0, width));
  y=floor(random(0, height));
}

void setbuff(int bx, int by) {
  push();
  noStroke();
  translate(bx, by);
  rotate(PI/4);
  fill(#FFCD43);
  circle(0, 0, 200);
  fill(0);
  textAlign(CENTER, CENTER);
  textSize(170);
  text("?", 0, 0);
  pop();
}

void getbuff1(int effect) {

  switch(effect) {
  case 1:
  case 2:
    {
      fill(255, 0, 0);
      noStroke();
      for (int size=200; size<=1200; size+=10) {
        circle(x, y, size);
      }
      break;
    }
  case 3:
  case 4:
    {
      pre21=millis()-gamestart;
      buff21=true;
      break;
    }
  case 5:
  case 6:
    {
      pre31=millis()-gamestart;
      buff31=true;
      break;
    }
  case 7:
    background(255*floor(random(0, 2)), 0, 255*floor(random(0, 2)));
  }
}
void getbuff2(int effect) {

  switch(effect) {
  case 1:
  case 2:
    {
      fill(0, 0, 255);
      noStroke();
      for (int size=200; size<=1200; size+=10) {
        circle(x, y, size);
      }
      break;
    }
  case 3:
  case 4:
    {
      pre22=millis()-gamestart;
      buff22=true;
      break;
    }
  case 5:
  case 6:

    {
      pre32=millis()-gamestart;
      buff32=true;
      break;
    }
  case 7:
    {
      background(255*floor(random(0, 2)), 0, 255*floor(random(0, 2)));
    }
  }
}


void draw() {
  noStroke();
  strokeWeight(3);
  if (gamestarted==0) {
    textAlign(CENTER, CENTER);
    textSize(80);
    text("CLICK TO START", width/2, height/2);
    if (mousePressed) {
      gamestarted=1;
      fill(0);
      noStroke();
      rect(width/2, height/2, 800, 800);
      gamestart=millis();
      sound.play();
    }
  }
  if (gamestarted==1) {
    if (millis()-gamestart<=4000) {
      x1=width/4;
      y1=height/2;
      x2=width*3/4;
      y2=height/2;
      fill(255, 0, 0);
      noStroke();
      ellipse(x1, y1, 200, 200);
      fill(0, 0, 255);
      ellipse(x2, y2, 200, 200);
      fill(0);

      rect(width/2, height/2, 650, 650);
      fill(255);
      textSize(80);
      textAlign(CENTER, CENTER);
      text("WHO PAINTS MORE", width/2, height/2);
    }
    if (millis()-gamestart>4000&&millis()-gamestart<=5000) {
      fill(0);
      rect(width/2, height/2, 650, 650);
      fill(#74EAC0);
      textSize(300);
      textAlign(CENTER, CENTER);
      text("5", width/2, height/2);
    }
    if (millis()-gamestart>5000&&millis()-gamestart<=6000) {
      fill(0);
      rect(width/2, height/2, 320, 320);
      fill(#74BCEA);
      textSize(300);
      textAlign(CENTER, CENTER);
      text("4", width/2, height/2);
    }
    if (millis()-gamestart>6000&&millis()-gamestart<=7000) {
      fill(0);
      rect(width/2, height/2, 320, 320);
      fill(#5D59F5);
      textSize(300);
      textAlign(CENTER, CENTER);
      text("3", width/2, height/2);
    }
    if (millis()-gamestart>7000&&millis()-gamestart<=8000) {
      fill(0);
      rect(width/2, height/2, 320, 320);
      fill(#F00F52);
      textSize(300);
      textAlign(CENTER, CENTER);
      text("2", width/2, height/2);
    }
    if (millis()-gamestart>8000&&millis()-gamestart<=9000) {
      fill(0);
      rect(width/2, height/2, 320, 320);
      fill(#F00F52);
      textSize(300);
      textAlign(CENTER, CENTER);
      text("1", width/2, height/2);
    }

    if (millis()-gamestart>9000&&millis()-gamestart<=10000) {
      fill(0);
      rect(width/2, height/2, 320, 320);
      if (millis()-gamestart<=9900) {
        fill(#F00F52);
        textSize(200);
        textAlign(CENTER, CENTER);
        text("GO", width/2, height/2);
      }
    }


    if (millis()-gamestart>10001&&millis()-gamestart<=73000) {
      if (millis()-gamestart-pre21>=5000&&millis()-gamestart-pre21<=5100) {
        buff21=false;
      }
      if (millis()-gamestart-pre22>=5000&&millis()-gamestart-pre22<=5100) {
        buff22=false;
      }
      if (millis()-gamestart-pre31>=5000&&millis()-gamestart-pre31<=5100) {
        buff31=false;
      }
      if (millis()-gamestart-pre32>=5000&&millis()-gamestart-pre32<=5100) {
        buff32=false;
      }
      serialRecord.read();
      int value1 = serialRecord.values[0];
      int value2 = serialRecord.values[1];

      float angle1 = map(value1, 0, 1024, 0, 360);
      float angle2 = map(value2, 0, 1024, 0, 360);


      x1_direction=15*cos(radians(angle1));
      y1_direction=15*sin(radians(angle1));
      x2_direction=15*cos(radians(angle2));
      y2_direction=15*sin(radians(angle2));
      prex1=x1;
      prex2=x2;
      prey1=y1;
      prey2=y2;

      stroke(0);
      if (buff31) {
        x1=x1+2*x1_direction;
        y1=y1+2*y1_direction;
      } else {
        x1=x1+x1_direction;
        y1=y1+y1_direction;
      }
      if (buff32) {
        x2=x2+2*x2_direction;
        y2=y2+2*y2_direction;
      } else {
        x2=x2+x2_direction;
        y2=y2+y2_direction;
      }
      if (x1<0) x1+=width;
      if (x1>width) x1-=width;
      if (y1<0) y1+=height;
      if (y1>height) y1-=height;
      if (x2<0) x2+=width;
      if (x2>width) x2-=width;
      if (y2<0) y2+=height;
      if (y2>height) y2-=height;
      fill(255, 0, 0);
      if (buff21) {
        ellipse(x1, y1, 2*r1, 2*r1);
        push();
        noStroke();
        ellipse(prex1, prey1, 2*r1+4, 2*r2+4);
        pop();
      } else {
        ellipse(x1, y1, r1, r1);
        push();
        noStroke();
        ellipse(prex1, prey1, r1+4, r2+4);
        pop();
      }
      fill(0, 0, 255);
      if (buff22) {
        ellipse(x2, y2, 2*r2, 2*r2);
        push();
        noStroke();
        ellipse(prex2, prey2, 2*r2+4, 2*r2+4);
        pop();
      } else {
        ellipse(x2, y2, r2, r2);
        push();
        noStroke();
        ellipse(prex2, prey2, r2+4, r2+4);
        pop();
      }

      if (abs(millis()-gamestart-buff)<20&&e) {
        setbuff(x, y);
        e=false;
      }
      if (dist(x1, y1, x, y)<=100&&millis()-gamestart>buff) {
        getbuff1(floor(random(1, 8)));

        e=true;
        noStroke();
        fill(255, 0, 0);
        circle(x, y, 210);
        buff=floor(random(millis()-gamestart+5000, millis()-gamestart+10000));
        x=floor(random(0, width));
        y=floor(random(0, height));
      }
      if (dist(x2, y2, x, y)<=100&&millis()-gamestart>buff) {
        getbuff2(floor(random(1, 8)));

        e=true;
        noStroke();
        fill(0, 0, 255);
        circle(x, y, 210);
        buff=floor(random(millis()-gamestart+5000, millis()-gamestart+10000));
        x=floor(random(0, width));
        y=floor(random(0, height));
      }
    }

    if (millis()-gamestart>73000&&flag==1) {
      loadPixels();
      flag=0;
      for (int i=0; i<height*width; i++) {
        if (pixels[i]==color(0, 0, 255)) {
          countblue++;
        }
        if (pixels[i]==color(255, 0, 0)) {
          countred++;
        }
        pixels[i]=color(0, 0, 0);
      }
      updatePixels();
      fill(#221D23);
      rect(width/2, height/2, width, height);
      textSize(300);
      textAlign(CENTER, CENTER);
      int ratioblue=floor(countblue*100/(countred+countblue)+0.5);
      int ratiored=floor(countred*100/(countred+countblue)+0.5);
      if (countblue>countred) {
        fill(0, 0, 255);
        text("BLUE WINS", width/2, height/3);
        ratioblue++;
        text(ratiored+":"+ratioblue, width/2, height*2/3);
      } else {
        fill(255, 0, 0);
        text("RED WINS", width/2, height/3);
        ratiored++;
        text(ratiored+":"+ratioblue, width/2, height*2/3);
      }
      delay(10000);
    }
  }
}

Leave a Reply

Your email address will not be published. Required fields are marked *