Interaction Lab: FINAL

 

Whack the Color-Amelia Shao-Margaret Minsky

Concept, design, and solving problems

Apparently, our project is a modification of “Whack a Mole”. I didn’t think much when I originally designed this game. As I said in the proposal essay, this idea came to me because I believe playing with color is super cool, and neopixel is a fascinating element that I want to include. Also, a classic game that can bring the memory of childhood and pure happiness to suffering students and teachers during the final is also meaningful, so I get started.

The original idea was to design two levels: First, the player only needs to find the location and hit as in the classic game; Second, the player needs to see the color on the neopixel, find it on the screen, and finally know the right location. In level 1, a successful hit will turn the corresponding light into green, otherwise, it will be red. In level 2, the right color will show up on the neopixel one by one when the round comes, and turn red or green as it was in level 1.

This time I still worked in a group. My teammate is Nicole, who invited me to work with her on the final project a month ago. Our cooperation was relatively fluent. She worked on the basic processing codes, do the laser cutting part and combine them together. I proposed the idea and made several revisions, designed and tested all the theories, and wrote the Arduino codes as well as their interaction with the Processing. I would say that I did most of the work and spend time largely by myself in 826, but I still appreciate Nicole’s mental support when I was desperate.

Arduino part is not very difficult. I carefully read the slides and build-in examples to realize the send and receive multiple values function. But there was a problem that using delay() in processing will cause some problems so I cannot make the two platforms in step. I asked fellow Iris to help me with this, and she taught me how to use millis() together with boolean to make the colors show up and disappear successfully. (Thank you so much Iris!) 

The main technique in the real world I decided to use is a circuit — The hammer is the positive side and will touch the cables which are plugged into pin pots. Pin ports are in INPUT PULL_UP mode, so when there is electricity running through, the pin pot will receive a value. By the way, I firstly used INPUT, but found that the sended value is not stable. After searched a lot online, I found that if there’s nothing goes into the pot under the INPUT mode, the value will be randomly 0 or 1. So I tried to use the pull_up, and professor Minsky explained how this mode works in detail, helping me write the right code.

The conductive tape plays a very important role to connect circuits. At first, I didn’t really understand the working theory of conductive tape, so I put a part of the cables on the wire’s sticky side. However, this is obviously wrong and I thank Rachel very much as she pointed out the big mistake. Meanwhile, soldering is not lovely and may be hard to modify, so I fold the tape to make both “up and down” sides actually one side. Nicole redesigned the tape into letters to make it fancier. Our other choices at the very beginning were either the light sensor or a pressure sensor. But, 1. we have nine pieces of data to detect which will make the circuit super messy; 2. sensors are not as stable as simple electricity going through; 3. There will be a thing above the board, so players can never “hit”. In that case, we gave up and use the seemly most simple one. To be honest, the circuit still has some disadvantages, such as it’s harder to make the game goes into the next round as soon as the right block is touched because there’s no clear value change to write if(). But I still think our choice is the right one for us.

As for other techniques, we mainly used laser cutting to make certain shapes of wood and build them up to make the hitting board and hammer. Laser cutting is so amazing and interesting. It helps us to build the prototype much stabler and easier.

Solving problems after the user test

 

During the user test, I found most people could well understand level one but would feel confused when level two started even with an instruction. That is to say, the transition between the two levels is not clear enough, and people usually need more time to match the colors.

Professor Minsky gave a key suggestion: let the neopixel only show right or wrong. The color that needs to be hit should be placed somewhere else to make it clear. In that case, I decided to put the first light as the instruction light, and tell people to focus on that during the game.

The other main problem during the user test is that there exists a color difference between the neopixel and the screen. I didn’t have time to solve this before the user test, but later I worked with professor Minsky and professor Steele for more than 4 hours, and we finally make it by using HSB color mode. It was a really suffering process. I was almost desperate after writing 5 demos to check problems part by part but found nothing, and finally, professors decide to use a completely new thing for me — HSB and explain how it works and how to write the code carefully. Later, as there still exist problems, professor Minsky explained the map() function to make the possible fraction part into an integer. I made this! I really appreciate professor Minsky’s patience and insistence on helping me solve the problem. After this, I began to realize that there are always ways to make our ideas come true. Even if I cannot make it right now, I still can find experts to help me and learn a lot through this process.

One other small problem is that the player can hardly tell if they succeed or fail, so they want to have a sound. I add a buzzer to the circuit and it will beep when there’s wrong hit.

There still existed problems during the final presentation. I don’t know what makes two of the thirty blocks disappear, but I guess it’s delay’s issue (delay always has an issue). The other question bothered me a lot is that random() is not really random. Every time I could see same 30 numbers go through. To solve this, I searched the random.Seed(), but neither of them worked.

Conclusion

As I mentioned before, I learnt a lot through the process of making this project. I realized that knowledge I got through the whole semester matters, while learning through practicing is the most helpful way to examine what I learnt as well as learn new knowledge. It’s really important to search and try to understand, to ask experts, to learn from my peers. These are things that I will also apply in my future classes and life.

To conclude, the process of making such a project is suffering, and our final work is not flawless. I would not say that our work has significance to someone in society, but it’s significant to me. It records what I learned throughout the whole semester, witnessing my joys and tears. I will never forget these codes which appeared in my dream and drove me mad, as all of these experiences prove that I worked so hard for this course from the beginning to the end. The other important thing is to express my thankfulness to everyone who went together with me. I am grateful for the 3-week long period of working together with my ix lab “comrades” who kept staying in 826 from day to night; I thank professor Minsky, professor Rudi, professor Steele as well as our dear fellow Iris and Sylvia for their support. Interaction lab is my first self-selected course in NYUSH, and I will not regret my choice.

Thank you all!

Annex

Arduino⬇️

#include <SerialRecord.h>
#include <FastLED.h>

#define NUM_LEDS 30  // How many leds on your strip?
#define DATA_PIN 11
byte pinBuzzer = 13;

CRGB leds[NUM_LEDS];
//int NUMBER;

SerialRecord writer(2);

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

  pinMode (pinBuzzer, OUTPUT);

  FastLED.addLeds<NEOPIXEL, DATA_PIN>(leds, NUM_LEDS);
  FastLED.setBrightness(50);

  pinMode(2, INPUT_PULLUP);
  pinMode(3, INPUT_PULLUP);
  pinMode(4, INPUT_PULLUP);
  pinMode(5, INPUT_PULLUP);
  pinMode(6, INPUT_PULLUP);
  pinMode(7, INPUT_PULLUP);
  pinMode(8, INPUT_PULLUP);
  pinMode(9, INPUT_PULLUP);
  pinMode(10, INPUT_PULLUP);

  for (int i = 0; i <= 30; i++) {
    int NUMBER = random(1, 10) + 1;
    writer[0] = NUMBER;
    writer[1] = i;
    writer.send();
    if (i < 10) {
      delay(5000-i*400);
      if (digitalRead(NUMBER) == LOW) {
        leds[i+1] = CRGB(0, 255, 0);
        FastLED.show();
      } else {
        leds[i+1] = CRGB(255, 0, 0);
        FastLED.show();
        tone(13, 440, 200);
      }
    } else if (i >= 10 && i <= 19) {
     
      leds[0] = CHSV(map(i, 10, 19, 0, 255), 255, 255);
      FastLED.show();
      delay(5000 - (i - 9) * 300);
      //delay(20);
      if (digitalRead(NUMBER) == LOW) {
        leds[i+1] = CRGB(0, 255, 0);
        FastLED.show();
      } else {
        leds[i+1] = CRGB(255, 0, 0);
        FastLED.show();
        tone(13, 440, 200);
      }
    } else if (i >= 19 && i <= 29) {
      //leds[0] = CRGB(NUMBER * 2 * i, NUMBER * i, NUMBER*5);
      //leds[0] = CHSV(map(NUMBER, 1, 10, 0, 255), 255, 255);
      leds[0] = CHSV(map(i, 19, 29, 0, 255), 255, 255);
      FastLED.show();
      delay(5000 - (i - 19) * 300);

      if (digitalRead(NUMBER) == LOW) {
        leds[i+1] = CRGB(0, 255, 0);
        FastLED.show();
      } else {
        leds[i+1] = CRGB(255, 0, 0);
        FastLED.show();
        tone(13, 440, 200);
      }
    }
  }
}

void loop() {
}

Processing⬇️

import processing.serial.*;
import osteele.processing.SerialRecord.*;

Serial serialPort;
SerialRecord serialRecord;
int NUMBER;
int i;

int startTime1;
boolean light1 = false;

int startTime2;
boolean light2 = false;

int startTime3;
boolean light3 = false;

int startTime4;
boolean light4 = false;

int startTime5;
boolean light5 = false;

int startTime6;
boolean light6 = false;

int startTime7;
boolean light7 = false;

int startTime8;
boolean light8 = false;

int startTime9;
boolean light9 = false;

int startTime11;
int startTime22;
int startTime33;
int startTime44;
int startTime55;
int startTime66;
int startTime77;
int startTime88;
int startTime99;

int startTime111;
int startTime222;
int startTime333;
int startTime444;
int startTime555;
int startTime666;
int startTime777;
int startTime888;
int startTime999;


void setup() {
  background(255);
  //size(500,500);
  fullScreen();
  colorMode(HSB, 255, 255, 255);
  String serialPortName = SerialUtils.findArduinoPort();
  serialPort = new Serial(this, serialPortName, 9600);
  serialRecord = new SerialRecord(this, serialPort, 2);

  stroke(0);
  strokeWeight(10);
  fill(255);
  rect(0, 0, width/3, height/3 );

  strokeWeight(10);
  fill(255);
  rect(2*width/3, 0, width/3, height/3);

  strokeWeight(10);
  fill(255);
  rect(width/3, 0, width/3, height/3);

  strokeWeight(10);
  fill(255);
  rect(0, 300, width/3, height/3);

  strokeWeight(10);
  fill(255);
  rect(480, 300, width/3, height/3);

  fill(0);
  textSize(50);

  text("WHACK THE COLOR", 515, 480);

  strokeWeight(10);
  fill(255);
  rect(960, 300, width/3, height/3);

  strokeWeight(10);
  fill(255);
  rect(0, 600, width/3, height/3);

  strokeWeight(10);
  fill(255);
  rect(480, 600, width/3, height/3);

  strokeWeight(10);
  fill(255);
  rect(960, 600, width/3, height/3);
}


void draw() {
  serialRecord.read();
  NUMBER = serialRecord.values[0];
  i = serialRecord.values[1];

  if (millis() - startTime1 > 5000-i*400 && light1 == true) {
    fill(0,0,255);
    rect(0, 0, width/3, height/3 );
    light1 = false;
  }
  if (millis() - startTime2 > 5000-i*400 && light2 == true) {
    fill(0,0,255);
    rect(2*width/3, 0, width/3, height/3);
    light2 = false;
  }

  if (millis() - startTime3 > 5000-i*400 && light3 == true) {
    fill(0,0,255);
    rect(2*width/3, 0, width/3, height/3);
    light3 = false;
  }

  if (millis() - startTime4 > 5000-i*400 && light4 == true) {
    fill(0,0,255);
    rect(0, 300, width/3, height/3);
    light4 = false;
  }
  if (millis() - startTime5 > 5000-i*400 && light5 == true) {
    fill(0,0,255);
    rect(480, 300, width/3, height/3);
    light5 = false;
  }
  if (millis() - startTime6 > 5000-i*400 && light6 == true) {
    fill(0,0,255);
    rect(960, 300, width/3, height/3);
    light6 = false;
  }
  if (millis() - startTime7 > 5000-i*400 && light7 == true) {
    fill(0,0,255);
    rect(0, 600, width/3, height/3);
    light7 = false;
  }
  if (millis() - startTime8 > 5000-i*400 && light8 == true) {
    fill(0,0,255);
    rect(480, 600, width/3, height/3);
    light8 = false;
  }
  if (millis() - startTime9 > 5000-i*400 && light9 == true) {
    fill(0,0,255);
    rect(960, 600, width/3, height/3);
    light9 = false;
  }

  if (millis() - startTime11 > 5000 - (i - 9) * 300 && light1 == true) {
    fill(0,0,255);
    rect(0, 0, width/3, height/3 );
    rect(960, 300, width/3, height/3);
    light1 = false;
  }
  if (millis() - startTime22 > 5000 - (i - 9) * 300 && light2 == true) {
    fill(0,0,255);
    rect(2*width/3, 0, width/3, height/3);
    rect(0, 0, width/3, height/3 );
    light2 = false;
  }

  if (millis() - startTime33 > 5000 - (i - 9) * 300 && light3 == true) {
    fill(0,0,255 );
    rect(width/3, 0, width/3, height/3);
    rect(960, 600, width/3, height/3);
    light3 = false;
  }

  if (millis() - startTime44 > 5000 - (i - 9) * 300 && light4 == true) {
    fill(0,0,255);
    rect(0, 300, width/3, height/3);
    rect(0, 600, width/3, height/3);
    light4 = false;
  }
  if (millis() - startTime55 > 5000 - (i - 9) * 300 == true) {
    fill(0,0,255);
    rect(480, 300, width/3, height/3);
    rect(0, 0, width/3, height/3 );
    light5 = false;
  }
  if (millis() - startTime66 > 5000 - (i - 9) * 300 && light6 == true) {
    fill(0,0,255);
    rect(960, 300, width/3, height/3);
    rect(2*width/3, 0, width/3, height/3);
    light6 = false;
  }
  if (millis() - startTime77 > 5000 - (i - 9) * 300 && light7 == true) {
    fill(0,0,255);
    rect(0, 600, width/3, height/3);
    fill(255);
    rect(2*width/3, 0, width/3, height/3);
    light7 = false;
  }
  if (millis() - startTime88 >5000 - (i - 9) *300 && light8 == true) {
    fill(0,0,255);
    rect(480, 600, width/3, height/3);
    rect(0, 600, width/3, height/3);
    light8 = false;
  }
  if (millis() - startTime99 > 5000 - (i - 9) * 300 && light9 == true) {
    fill(0,0,255);
    rect(960, 600, width/3, height/3);
    rect(960, 300, width/3, height/3);
    light9 = false;
  }

  if (millis() - startTime111 > 5000 - (i - 19) * 300 && light1 == true) {
    fill(255);
    rect(0, 0, width/3, height/3 );
    light1 = false;
  }
  if (millis() - startTime222 > 5000 - (i - 19) * 300 && light2 == true) {
    fill(255);
    rect(2*width/3, 0, width/3, height/3);
    light2 = false;
  }

  if (millis() - startTime333 > 5000 - (i - 19) * 300 && light3 == true) {
    fill(255);
    rect(2*width/3, 0, width/3, height/3);
    light3 = false;
  }

  if (millis() - startTime444 > 5000 - (i - 19) * 300 && light4 == true) {
    fill(255);
    rect(0, 300, width/3, height/3);
    light4 = false;
  }
  if (millis() - startTime555 > 5000 - (i - 19) * 300 == true) {
    fill(255);
    rect(480, 300, width/3, height/3);
    light5 = false;
  }
  if (millis() - startTime666 > 5000 - (i - 19) * 300 && light6 == true) {
    fill(255);
    rect(960, 300, width/3, height/3);
    light6 = false;
  }
  if (millis() - startTime777 > 5000 - (i - 19) * 300 && light7 == true) {
    fill(255);
    rect(0, 600, width/3, height/3);
    light7 = false;
  }
  if (millis() - startTime888 >5000 - (i - 19) * 300 && light8 == true) {
    fill(255);
    rect(480, 600, width/3, height/3);
    light8 = false;
  }
  if (millis() - startTime999 > 5000 - (i - 19) * 300 && light9 == true) {
    fill(255);
    rect(960, 600, width/3, height/3);
    light9 = false;
  }

  if (i<=9) {
    if (NUMBER == 2) {
      rect1();
    } else if (NUMBER == 3) {
      rect2();
    } else if ( NUMBER == 4) {
      rect3();
    } else if (NUMBER == 5) {
      rect4();
    } else if (NUMBER == 6) {
      rect5();
    } else if (NUMBER == 7) {
      rect6();
    } else if (NUMBER == 8) {
      rect7();
    } else if (NUMBER == 9) {
      rect8();
    } else if (NUMBER == 10) {
      rect9();
    }
  } else if (i>=10 && i<20) {
    if (NUMBER == 2) {
      rect11();
    } else if (NUMBER == 3) {
      rect22();
    } else if ( NUMBER == 4) {
      rect33();
    } else if (NUMBER == 5) {
      rect44();
    } else if (NUMBER == 6) {
      rect55();
    } else if (NUMBER == 7) {
      rect66();
    } else if (NUMBER == 8) {
      rect77();
    } else if (NUMBER == 9) {
      rect88();
    } else if (NUMBER == 10) {
      rect99();
    }
  } else if (i>=20 && i<=29) {
    if (NUMBER == 2) {
      rect111();
    } else if (NUMBER == 3) {
      rect222();
    } else if ( NUMBER == 4) {
      rect333();
    } else if (NUMBER == 5) {
      rect444();
    } else if (NUMBER == 6) {
      rect555();
    } else if (NUMBER == 7) {
      rect666();
    } else if (NUMBER == 8) {
      rect777();
    } else if (NUMBER == 9) {
      rect888();
    } else if (NUMBER == 10) {
      rect999();
    }
  } else if(i>29) {
    sky();
  }
}

void rect1() {
  fill(255, 255, 255);
  rect(0, 0, width/3, height/3 );
  startTime1 = millis();
  light1 = true;
}

void rect2() {
  fill(100, 255, 255);
  rect(2*width/3, 0, width/3, height/3);
  startTime2 = millis();
  light2 = true;
}

void rect3() {
  fill(10, 255, 200);
  rect(width/3, 0, width/3, height/3);
  startTime3 = millis();
  light3 = true;
}

void rect4() {
  fill(30, 255, 200);
  rect(0, 300, width/3, height/3);
  startTime4 = millis();
  light4= true;
}

void rect5() {
  fill(100, 255, 250);
  rect(480, 300, width/3, height/3);
  startTime5 = millis();
  light5= true;
}

void rect6() {
  fill(230, 255, 255);
  rect(960, 300, width/3, height/3);
  startTime6 = millis();
  light6= true;
}

void rect7() {
  fill(200, 255, 200);
  rect(0, 600, width/3, height/3);
  startTime7 = millis();
  light7= true;
}

void rect8() {
  fill(150, 255, 230);
  rect(480, 600, width/3, height/3);
  startTime8 = millis();
  light8 = true;
}

void rect9() {
  fill(80, 255, 255);
  rect(960, 600, width/3, height/3);
  startTime9 = millis();
  light9= true;
}

void rect11() {
  fill(int(map(i, 10, 19, 0, 255)), 255, 255);
  rect(0, 0, width/3, height/3 );
  fill(80, 255, 150);
  rect(960, 300, width/3, height/3);
  startTime11 = millis();
  light1 = true;
}

void rect22() {
  fill(int(map(i, 10, 19, 0, 255)), 255, 255);
  rect(2*width/3, 0, width/3, height/3);
  fill(180, 255, 180);
  rect(0, 0, width/3, height/3 );
  startTime22 = millis();
  light2 = true;
}

void rect33() {
  fill(int(map(i, 10, 19, 0, 255)), 255, 255);
  rect(width/3, 0, width/3, height/3);
  fill(5, 200, 255);
  rect(960, 600, width/3, height/3);
  startTime33 = millis();
  light3= true;
}

void rect44() {
  fill(int(map(i, 10, 19, 0, 255)), 255, 255);
  rect(0, 300, width/3, height/3);
  fill(200, 255, 255);
  rect(0, 600, width/3, height/3);
  startTime44 = millis();
  light4 = true;
}

void rect55() {
  fill(int(map(i, 10, 19, 0, 255)), 255, 255);
  rect(480, 300, width/3, height/3);
  fill(170, 255, 255);
  rect(0, 0, width/3, height/3 );
  startTime55 = millis();
  light5 = true;
}

void rect66() {
  fill(int(map(i, 10, 19, 0, 255)), 255, 255);
  rect(960, 300, width/3, height/3);
  fill(30, 255, 255);
  rect(2*width/3, 0, width/3, height/3);
  startTime66 = millis();
  light6 = true;
}

void rect77() {
  fill(int(map(i, 10, 19, 0, 255)), 255, 255);
  rect(0, 600, width/3, height/3);
  fill(210,255,255);
  rect(2*width/3, 0, width/3, height/3);
  startTime77 = millis();
  light7 = true;
}

void rect88() {
  fill(int(map(i, 10, 19, 0, 255)), 255, 255);
  rect(480, 600, width/3, height/3);
  fill(50,200,200);
  rect(0, 600, width/3, height/3);
  startTime88 = millis();
  light8 = true;
}

void rect99() {
  fill(int(map(i, 10, 19, 0, 255)), 255, 255);
  rect(960, 600, width/3, height/3);
  fill(100, 250, 200);
  rect(960, 300, width/3, height/3);
  startTime99 = millis();
  light9 = true;
}

void rect111() {
  fill(int(map(i, 19, 29, 0, 255)), 255, 255);
  rect(0, 0, width/3, height/3 );
  fill(25, 200, 255);
  rect(480, 600, width/3, height/3);
  startTime111 = millis();
  light1 = true;
}

void rect222() {
  fill(int(map(i, 19, 29, 0, 255)), 255, 255);
  rect(2*width/3, 0, width/3, height/3);
  fill(100, 200, 200);
  rect(960, 300, width/3, height/3);
  startTime222 = millis();
  light2 = true;
}

void rect333() {
  fill(int(map(i, 19, 29, 0, 255)), 255, 255);
  rect(width/3, 0, width/3, height/3);
  fill(30, 200, 220);
  rect(480, 600, width/3, height/3);
  startTime333 = millis();
  light3 = true;
}

void rect444() {
  fill(int(map(i, 19, 29, 0, 255)), 255, 255);
  rect(0, 300, width/3, height/3);
  fill(200, 200, 200);
  rect(2*width/3, 0, width/3, height/3);
  startTime444 = millis();
  light4 = true;
}

void rect555() {
  fill(int(map(i, 19, 29, 0, 255)), 255, 255);
  rect(480, 300, width/3, height/3);
  startTime555 = millis();
  light5 = true;
}

void rect666() {
  fill(int(map(i, 19, 29, 0, 255)), 255, 255);
  rect(960, 300, width/3, height/3);
  startTime666 = millis();
  light6 = true;
}

void rect777() {
  fill(int(map(i, 19, 29, 0, 255)), 255, 255);
  rect(0, 600, width/3, height/3);
  fill(255, 200, 255);
  startTime777 = millis();
  light7 = true;
}

void rect888() {
  fill(int(map(i, 19, 29, 0, 255)), 255, 255);
  rect(480, 600, width/3, height/3);
  startTime888 = millis();
  light8 = true;
}

void rect999() {
  fill(int(map(i, 19, 29, 0, 255)), 255, 255);
  rect(960, 600, width/3, height/3);
  startTime999 = millis();
  light9 = true;
}

void sky() {
  for (int m=0; m<height; m++) {
    float b = map(m, 0, height, 0, 255);
    stroke(0, 0, b);
    line(0, m, width, m);
  }
  textSize(120);
  fill(255);
  text("Finished!", 480, 400);
  text("Plz count green lights!", 200, 550);
}

Cuttle (Neopixel holder and the hit board)⬇️

One comment

Leave a Reply

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