Match Maker
-Chaoyue Yuan & Steve Lu
-Instructed by Rodolfo Cossovich
B. Context & Significance
Speaking of how the group research project inspired my creative process. there indeed are many resemblances between the two. The idea of creating a connection between people is much inherited. Like the attempt we made in the group research project to resolve the power dynamic by putting people into others’ shoes, I still dedicate to dealing with the imbalanced power dynamic within another kind of relationship, which is a romantic relationship (at the least that was something that I would like to do in the first place).
Notwithstanding the previous project, I contemplated how to diversify the interactivity involved, based on the precious feedback from fellow students and dear professors. Frankly, in the group research project, there’s little physical computing involved and rather monotonical interaction seems too straight and deadpan. Consequently, I decided to engage more visual and auditory feedback in my midterm project.
I found how games, especially fighting games, visualize the status of their characters with different colors. Like a continuous change in hue or saturation that gives a spectrum of different shades of color, which gamers intuitively see how far they are from winning or whatever. So, I decided to appropriate the design of games. The interactivity involved in my project reasonably then resembles that in arcade game machines. Gamers interact with the game machine in the first place in terms of buttons and visuals and thus interact with the game partner via the machine.
The targeted audience, at the initial stage, is those who want to express their affection but not willing to risk losing the relationship if the attempt fails. I believe the idea is unique, as it focuses on a fairly specific spot in our life and the creative process is quite spontaneous, since, frankly the idea just came to me in class while I was reflecting on my life and daydreaming about the future. It serves a special value to the targetted audience because it solves the life-or-death problem many faces when they consider whether to express their love. The project sweeps out the most inhibiting obstacle in the way, so that, ideally, people should be more willing to share their thought, without backpedaling all day long and doing nothing. However, as Rudi pointed out, the initial idea gradually got worn out in the process.
C. Conception & Design
The design of the user interface is aligned with the initial intention that the project should help people with the “anonymous expression of love”. We found that it is critical to keep the two from seeing each other’ choice so that the less movement involved, the better. As a result, we went for buttons, with consistent colors with yes & no, identifying the affordance. The original design is to have the two sit across the table so that they are face to face, and have the buttons in front of them. Later, due to practical limitations, we failed to make it happen but went for an alternative to what the project appeared to be now. Though we thought about the design thoroughly, there was always something unexpected. In more tangible terms, it was not until later we had the whole project presented to users, that we realized it was counter-intuitive to have the “no” buttons on the top, from the feedback of our friends.
Another option that we had been considering is to use a heart rate sensor to make decisions. Or, to use a heart rate sensor to control the visuals. Unfortunately, the idea turned out to be a bit impractical, given that people’s heart rate varies and it’s not promising to anticipate the same increment in heart rate for different people if they fall in love or not.
D. Fabrication & Production
SUPER hard!!! Forgive me for starting the documentation with an exclamation. At the conceiving stage, I thought the logic would be clear and the coding should be trivial. Unfortunately, as soon as I got my hands on the project for real, the whole thing turned out to be extremely tricky (I mean, outrageously hard).
-
Buttons!
The first problem that we encountered, is that the pushes of buttons are highly asynchronous. Unlike the scenario that one button is involved, where the conditional is straightforward, or the speed-pushing sample that we had gone through during the recitation, the coordination of two buttons came to me as unpredictably difficult. To articulate the problem, with a pull-down resistor bound, the untriggered state of buttons is low. Consequently, only at the split seconds when they press the button, will there be a high state input. Sadly, people usually don’t know each other that well so they don’t press the buttons simultaneously, which means the conditionals require us to store the input state for a while. Luckily, I brainstormed for a while and realized, namely what we had to do was to come up with a way to lock the HIGH value as long as it appears.
To test if the idea worked, I proposed to build a prototype first, whose significance we had studied for a long in recitations as well as group research projects. Even the simple prototype didn’t function as I pictured. After an energy-exhausting painstaking exclusion, I ascertained that the problem was with the breadboard… The breadboard was replaced, the problem was solved.
-
Visuals!
The second problem had to do with visualization. We decided to use an LCD screen to hint to users. We first used an express edition of the LCD screen which was borrowed from Andy (ER doesn’t have what it claims to have!!!). I figured out how to run the LCD as well as the MP3 module in an hour. Though it didn’t function in the first place, I managed to make it work after doing a bit of research about i2c and the address stuff. Something more tricky is the fact that the MP3 module took up RX and TX slots makes it annoying. Fortunately, Rudi told me that they could be remapped to other slots with programming. Everything went smoothly so far.
However, working with the advanced LCD was a pure nightmare. The wiring of the LCD screen was disastrous, as it essentially took up every slot on the Arduino Uno board to run at full function. Well, we cannot do that, since it would be more challenging to have two Arduino Uno boards work coordinately using the same program. Eventually, I figured out the solution during the class of neuroscience (Eureka!). Thanks to the four pins designed for SD card storage, it’s possible to have them not connected. So, I, simply, jumped wires across the LCD to a breadboard first, then connected to the UNO. Adding on to that, I clustered the pins together and taped around them in case anyone of them dropped off accidentally, as well as tidying things up.
-
Lack of slots!
The next problem occurred immediately. The LCD took up too many digital output slots that should have been intentionally left vacant for the button controls. Thankfully, I noticed that it was possible to designate the analog slots to fulfill the digital function, after doing some research on the Internet. So, I assigned A5 to be a digital slot.
-
Game design nightmare!
Though it took me a lot of time to tackle the LCD, its visual is nothing worthy of the fanciness of a neoPixel light strip. I was confused by the 4-digit 8-bit hexadecimal address coding for color. Luckily I was hinted by the library that using a method of .Color could easily complete the transformation.
Well, apparently I was already exhausted in making sense of the hardwares, way before the logic of the game, which later turned out to be much more difficult. If you want to know my struggle, take a glimpse at the annotations around the code down there, then you know what I have been through…
-
User test!
During the user test, we received a lot of valuable suggestions from my fellow students, learning assistants, and professors. They pointed out that the affordance of the project could be further improved by hinting audience visually or physically, like putting hand pads on the two sides. Rudi gave suggestions on having a startup, which provides an alternative for the users to get familiar with the project. Taking these great ideas into consideration, and we decided to add textual hints around the buttons and bring forth a game training session before the game.
Despite all the efforts I have put into the project, there’s no denying that I did deviate from my origin intention. We started up with a simple idea, which made us doubted if it would be too pale or too trivial. So, we decided to involve multiple questions and the leveling up systems, and by this time, the questions had already been designed not for the so-called “love express facilitator”, but rather a kinda blind dating app.
E. Conclusions
The goal of my project is to help people overcome the intimidating consequence of love expression. The project per se aligns perfectly with my definition of interaction. The project has many similarities with games, in which gamers have a certain extent of authority to write their own story. For different choices they make, there are gonna be different feedbacks, and subsequently, forward the game to the next stage. In terms of our audience’s response, I think they all knew what to do without informing them of anything in advance. So, I took that as a success. However, the project does not align with our initial goal, as we mentioned earlier. In the process of making it, I thought too much about adding something fancier, which distorts the focus of the work into a blind dating machine but the original idea was lost on the way. What a pity!
The lesson I took from that is that I should always write the ideas down, especially the founding ideas. Whenever I come up with other new features or ideas, it’s important for me to interrogate them, to see if they serve the original expression, or if they are simply ornamental and distractive. There’s no such thing as encompassing, but what really matters is that every component in the artifact should serve a central purpose.
There’s also something positive drawn from the achievement. From a technical aspect, I have dealt with various technologies in this project, which undoubtedly strengthens my ability in coding as well as hands-on work. Adding on to that, the cheerful vibe that we had throughout those hardworking nights was especially unforgettable.
If we had more time to do the project, the priority would be to do more user tests. It occurred to me that the feedback from the users was critical indeed in polishing the artifact as well as making sense of the project from a different perspective, which helps us to correct the path along the way. The other thing that I am gonna do is to develop a pre-game training session (underdeveloped but u can see it there in the program) for users to know how to play with it. The third thing, which is more technical, is to figure out the reason for the gigantic delay. There’s no denying the deadly problem, but indeed we had eliminated it before. It recurred as I added the pre-start display. I believe if given sufficient time, I could make it happen. The final, and most important thing I would do, reflects on my creative process, and adjust the project to fit the original intention.
As the significance of the project has been articulated previously, here I would like to talk more about the process that we made it happen, and why it matters. Despite so many fancy technologies involved in the project, they did not align with the original idea. If you want a good analogy for our failure here, think of an essay with flowery language but without a focused theme. It’s cautionary advice for my future exploration in art discipline as well as product design, that simply piling up technologies or fancy components does no good, until they are arranged in such a way that they closely serve a focused theme or concept.
Making something out of a sketch is indeed an experience. Although it seems to be that the grand party that lasted days and nights suddenly came to an end, the memories that gathered during these days would be treasured. I suppose the day is going to be one of the images that slide through when I am about to die one day. Would you recall my face and my name years after, Rudi? (though I found you already struggling with recalling my name LOL)
F. Annex
Well, the full code is gonna attached below, but it’s outrageously long though I intensively used function to simplify it. So, don’t bother reading them.
// for tft LCD display
// some of the following code is based on the example code included in the library
#define BLACK 0x0000
#define RED 0xF800 //for color
#define WHITE 0xFFFF // 这行代码有什么用,用来维护世界和平 (fill color必须用hex!) 或者其实刚刚才发现 pixels.Color(r, g, b, w)可以直接转换
#include <Adafruit_GFX.h>
#include <MCUFRIEND_kbv.h> //for lcd display
MCUFRIEND_kbv tft;
// for NeoPixel
#include <Adafruit_NeoPixel.h>
#ifdef __AVR__
#include <avr/power.h> // Required for 16 MHz Adafruit Trinket
#endif
#define NUMPIXELS 140 // Popular NeoPixel ring size
#define PIN 19 // On Trinket or Gemma, suggest changing this to 1
// When setting up the NeoPixel library, we tell it how many pixels,
// and which pin to use to send signals. Note that for older NeoPixel
// strips you might need to change the third parameter -- see the
// strandtest example for more information on possible values.
Adafruit_NeoPixel pixels(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);
// claim button pins
#define yes1 13
#define yes2 10
#define no1 11
#define no2 12
// claim variables to store buttons' state
int yes_1=0;
int yes_2=0;
int no_1=0;
int no_2=0;
int increment = 0; // to pass results to the telepathy
int telepathy = 0; // to count the number of winning
int questionStage = 1; // to enter different stages on the game
unsigned long time = 0; // is not used in the code, so far
bool runChecker = false; // so that the line will only run once in the loop
bool runCheckerFinal = false; // for the final stage neopixel to only display once
bool gameInitiator = false; // to start the game
// for lcd Display
char* display[] = {"0", "Do you likeeating out?", //claim string set
"Are you an adventurous person?",
"Do you likegoing to the gym?",
"Would you prefer an outgoing partner?",
"Can your partnerhave a best friend of the opposite sex?",
"Is it important that you see youpartner every day?",
"Do you like the personin front of you?"
};
void setup(){
Serial.begin(9600);
pinMode(2, INPUT);
pinMode(3, INPUT);
pinMode(4, INPUT);
pinMode(5, INPUT); //claim pinmode for buttons
// These lines are specifically to support the Adafruit Trinket 5V 16 MHz.
// Any other board, you can remove this part (but no harm leaving it):
#if defined(__AVR_ATtiny85__) && (F_CPU == 16000000)
clock_prescale_set(clock_div_1);
#endif
// END of Trinket-specific code.
pixels.begin(); // INITIALIZE NeoPixel strip object (REQUIRED) 你猜因为漏了这行代码我查了多久? 一个多小时你妈的 10.22
uint16_t ID = tft.readID();
tft.begin(ID);
tft.fillScreen(BLACK);
tft.setRotation(1); //initialize the LCD screen
}
voidtelepathyValue(intinc, intrunStateReporter=0){
// to check and record how many questions they have answered correctly, call led to visualize
// use default runStateReporter to different normal neopixel light effect, from the stateupdate at the end of each round
telepathy += inc;
// wonder if this switch case statement could work, much neater, instead of if statment
switch (telepathy) {
case 0:
ledStripe(0, 1);
increment = 0;
break;
case 1:
ledStripe(0, 3);
increment = 0;
break;
case 2:
ledStripe(0, 5);
increment = 0;
break;
case 3:
ledStripe(0, 7);
increment = 0;
break;
case 4:
ledStripe(0, 12);
increment = 0;
break;
case 5:
ledStripe(0, 14);
increment = 0;
break;
case 6:
ledStripe(0, 17);
increment = 0;
break;
case 7:
ledStripeHuge();
increment = 0;
break;
}
if (runStateReporter==1) {
stateUpdate();
}
}
void loop(){
initiator();
if (gameInitiator == true) { // to start the game
buttonReadSaver(); // to read the buttons and lock them up until the end of one round
question(questionStage); // to forward the stage of the game while also avoid the annoying function of loop that runs the former one over and over again
// buttonChecker(); // check if the buttons work or not
telepathyValue(increment); // with the second input as default, making the circling LED effect, without LCD display on
}
else {
beforeStart();
}
}
void lcdDisplay(char b[], int position=0, int fontSize=8){ // passes string for LCD display
tft.fillScreen(BLACK); // to clear the screen after each question is answered?
tft.setCursor(0,position);
tft.setTextColor(WHITE);
tft.print(b);
}
void fuckyou(){ // restore the variables to original state to get ready for the next round
yes_1=0;
yes_2=0;
no_1=0; //directly assign value to the variables without using "int", so that changes the variable as global instead of definning a new local variable
no_2=0;
runChecker=false;
questionStage ++;
tft.fillScreen(BLACK); // to clear the screen after each question is answered?
// delay(1000); // may not be necessary because the auto delay going on
}
void buttonReadSaver (){ // read out the buttons and lock them once they reach HIGH
if (yes_1==1) {
yes_1==1;
}
else {
yes_1 = digitalRead(yes1);
delay(1);
}
if (yes_2==1) {
yes_2==1;
}
else {
yes_2 = digitalRead(yes2);
delay(1);
}
if (no_1==1) {
no_1==1;
}
else {
no_1 = digitalRead(no1);
delay(1);
}
if (no_2==1) {
no_2==1;
}
else {
no_2 = digitalRead(no2);
delay(1);
}
}
void question(int question){
if (runChecker == false) {
if (questionStage==5 || questionStage==6) {
tft.setTextSize(5); // set font size in case any outside change
}
else {
tft.setTextSize(7);
}
lcdDisplay(display[questionStage]);
// Serial.println(display[question]); // for preliminary check
runChecker = true;
}
if (questionStage==7) { // the final question is peculiar that doesn't follow the matchup convention used in previous quesitons
if (yes_1==1 && yes_2 == 1) {
// lcdDisplay("You Match!!!");
// Serial.println("great job"); // for preliminary check
fuckyou();
increment = 1;
telepathyValue(increment, 1);
lcdDisplaySucc();
gameInitiator = false; // reset game to resting stage
}
if (yes_1==1 && no_2 ==1 || no_1==1 && yes_2 ==1 || no_1==1 && no_2 ==1) {
// lcdDisplay("Uh-Oh No Match");
// Serial.println("try harder");
fuckyou();
increment = 0;
telepathyValue(increment, 1);
lcdDisplayFail();
gameInitiator = false; // reset game to resting stage
}
}
else {
if (yes_1==1 && yes_2 == 1 || no_1==1 && no_2 ==1) {
// lcdDisplay("You Match!!!");
// Serial.println("great job"); // for preliminary check
fuckyou();
increment = 1;
telepathyValue(increment);
}
if (yes_1==1 && no_2 ==1 || no_1==1 && yes_2 ==1) {
// lcdDisplay("Uh-Oh No Match");
// Serial.println("try harder");
fuckyou();
increment = 0;
telepathyValue(increment); // to check and record how many questions they have answered correctly, call led and lcd to visualize
}
}
}
void buttonChecker(){ // check if the buttons work or not
// Serial.print(yes_1);
// Serial.print(yes_2);
// Serial.print(no_1);
// Serial.print(no_2);
Serial.println(increment);
delay(1);
}
void ledStripe (int initialPixel, int pixelNum) { // light up the pixel with both filling colors and also visualize different stages
pixels.fill(WHITE, 45, 90); // decide what the filling color would be
pixels.show();
// Serial.println("led works"); // to check if the function has been called correctly, so that we know that if the LED doesn't work, it must have to do with something else
time = millis();
while (millis()-time<=50) { // Pause before next pass through loop
pixels.fill(0, 6, 39); // don't fucking 你妈的用 pixel.clear()!! 你妈的全局清空,fill还有什么用?
pixels.show();
}
for (int i = initialPixel+6; i <= initialPixel+pixelNum+6; i++) { // For each pixel...
time = millis();
while (millis()-time<=50) { // Pause before next pass through loop
pixels.setPixelColor(i, pixels.Color(255, 0, 0));
pixels.setBrightness(128);
pixels.setPixelColor(45 - i, pixels.Color(0, 0, 255));
pixels.setBrightness(128);
pixels.show(); // Send the updated pixel colors to the hardware.
// pixels.setPixelColor(i, pixels.Color(0, 0, 0));
// pixels.setPixelColor(45 - i, pixels.Color(0, 0, 0));
// pixels.show(); // add a piece of code here can make do running effect
}
}
}
void ledStripeHuge () { // for the final stage of the game
while (runCheckerFinal == false) {
runCheckerFinal = true;
while (millis()-time<=50) { // Pause before next pass through loop
pixels.fill(0, 6, 39); // don't fucking 你妈的用 pixel.clear()!! 你妈的全局清空,fill还有什么用?
pixels.show();
}
for (int i = 6; i <= 23; i++) { // For each pixel...
time = millis();
while(millis()-time<=50){
pixels.setPixelColor(i, pixels.Color(255, 0, 0, 128));
pixels.setPixelColor(45 - i, pixels.Color(0, 0, 255, 128));
pixels.show(); // Send the updated pixel colors to the hardware.
pixels.setPixelColor(i, pixels.Color(0, 0, 0));
pixels.setPixelColor(45 - i, pixels.Color(0, 0, 0));
pixels.show(); // add a piece of code here can make do running effect
}
}
while (millis()-time<=500) { // fill out the pixels on the heart with nothing, namely doing a partial clear
pixels.fill(0, 6, 39);
pixels.show();
}
for (int i = 23; i >= 6; i--) {
time = millis();
while (millis()-time<=15) { // Pause before next pass through loop
pixels.setPixelColor(i, pixels.Color(255, 0, 0, 150));
pixels.setPixelColor(45 - i, pixels.Color(255, 0, 0, 150));
pixels.show(); // Send the updated pixel colors to the hardware.
}
}
}
// the following code is to do the breathing effect for the leds
for (int i = 0; i <= 255; i++) {
time = millis();
while (millis()-time<=50) { // Pause before next pass through loop
pixels.fill(pixels.Color(255, 0, 0), 6, 39);
pixels.setBrightness(i);
pixels.show();
}
}
for (int i = 255; i <= 0; i--) {
time = millis();
while (millis()-time<=50) { // Pause before next pass through loop
pixels.fill(pixels.Color(255, 0, 0), 6, 39);
pixels.setBrightness(i);
pixels.show();
}
}
}
// 带着个指针的void还有函数老是报错, invalid use of void expression.
// void strGenartor (int telepathy, int question) {
// // for lcd string output
// char str1[80];
// char str2[80];
// sprintf(str1, "You Have Matched %i", telepathy+1);
// sprintf(str2, "Remaining Questions %i", 7-question);
// char c[] = {str1, str2};
// return(c);
// }
// 但感觉好像直接这样写不就行了
void stateUpdate () {
tft.fillScreen(BLACK); // to clear the screen
tft.setCursor(0, 0);
tft.setTextSize(8);
// if (questionCorrectness==0) {
// tft.print("UNMATCH");
// questionCorrectness = 2;
// }
// if (questionCorrectness==1) {
// tft.print( "MATCH");
// questionCorrectness = 2;
// }
// delay(2000);
tft.setTextColor(WHITE);
tft.print("YOU HAVE MATCHED ");
char b[10]; // create a local variable to convert an int into char for display
sprintf(b, "%d", telepathy);
tft.print(b);
delay(2000); // so that the text could stay on the screen for a while
}
void lcdDisplayFail () {
tft.fillScreen(BLACK); // to clear the screen
tft.setCursor(0, 0);
tft.setTextSize(10);
tft.print("YOU DID NOT MATCH >_<");
time = millis();
while (millis()-time<=50) { // Pause before next pass through loop
pixels.fill(0, 6, 39);
pixels.show();
}
for (int i = 0+6; i <= 23; i++) { // For each pixel...
time = millis();
while (millis()-time<=50) { // Pause before next pass through loop
pixels.setPixelColor(i, pixels.Color(0, 76, 153));
pixels.setBrightness(128);
pixels.setPixelColor(45 - i, pixels.Color(0, 76, 153));
pixels.setBrightness(128);
pixels.show(); // Send the updated pixel colors to the hardware.
}
}
delay(10000);
}
void lcdDisplaySucc () {
tft.fillScreen(BLACK); // to clear the screen
tft.setCursor(0, 0);
tft.setTextSize(10);
tft.print("YOU ARE PERFECT MATCH (p>w<q)");
delay(10000);
}
void beforeStart () {
increment = 0;
telepathy = 0;
pixels.fill(BLACK);
pixels.rainbow(0, 1, 255, 128, true);
pixels.show();
char array1[] = "TEST YOUR TELEPATHY ";
char array2[] = "TWO PLAYERS ";
char array3[] = "PLEASE SIT DOWN";
char array4[] = "PRESS ANY BUTTON TO BEGIN";
tft.setCursor(0,0);
tft.setTextSize(8);
for (int positionCounter1=0; positionCounter1<32; positionCounter1++) { // to get running word effect for LCD
tft.print(array1[positionCounter1]);
delay(50);
}
delay(1000);
tft.fillScreen(BLACK);
tft.setCursor(0,0);
for (int positionCounter1=0; positionCounter1<18; positionCounter1++) { // to get running word effect for LCD
tft.print(array2[positionCounter1]);
delay(50);
}
delay(1000);
tft.fillScreen(BLACK);
tft.setCursor(0,0);
for (int positionCounter1=0; positionCounter1<15; positionCounter1++) { // to get running word effect for LCD
tft.print(array3[positionCounter1]);
delay(50);
}
delay(1000);
tft.fillScreen(BLACK);
tft.fillScreen(BLACK);
tft.setCursor(0,0);
for (int positionCounter1=0; positionCounter1<25; positionCounter1++) { // to get running word effect for LCD
tft.print(array4[positionCounter1]);
delay(50);
}
delay(1000);
tft.fillScreen(BLACK);
}
void initiator () {
buttonReadSaver();
if (yes_1==1 || yes_2==1 || no_1==1 || no_2==1) {
gameInitiator = true;
delay(1000);
yes_1=0;
yes_2=0;
no_1=0; //directly assign value to the variables without using "int", so that changes the variable as global instead of definning a new local variable
no_2=0;
}
}
void trainSession () {
tft.setCursor(0, 0);
tft.setTextColor(WHITE);
tft.setTextSize(8);
tft.print("Here's To Show You How To Play!");
tft.fillScreen(BLACK);
tft.setTextSize(10);
unsigned long time = millis();
while (millis-time<=1000) {
tft.print("<--- NO --->");
tft.println("<--- YES --->");
}
tft.fillScreen(BLACK);
tft.print("LET US TRY");
}