overview
This project is a Bingo-style game for Jeffrey to play with his 6-year-old granddaughter, Stella. This game, partially disguised as trivia, would be used as a tool to spark conversations about interests and personal topics as a way to get both young kids and adult family members to know each other better. See here for documentation of our initial meeting with Jeffrey and see here for documentation of our prototype.
What we built
We built an electronic board-game, with very simple rules, which could be used for both a 73 year old and a 6 year old. The game-play is a mix between Trivial Pursuit, Bingo and Truth or Dare.
Before the game starts, each Player writes down a set of questions for the other player to answer. The use of this analog method is to create a constantly evolving game, which grows and complexifies as Stella does. Player 1 asks a question from their stack of cards, and player 2 picks a spot on the board and attempts to answer. If answered correctly, player one presses their green button which locks the location on the button pad (as seen in the image below). If the answer is wrong, however, the illuminated button disappears, and player 2 must try again in the next round. This format continue until one player obtains 5 lit-up buttons in a row.
For a long game play, penalty cards such as “Tell me a story in song” or “Invent a short dance routine”, may be given to the person who has answered wrong at the end of each turn. For a short game play, the penalty card may be given at the end of the game.
Player 1 reading a question card, while player 2 chooses a position on the button pad.
Detail shot of the LCD screen, giving the players indications: “Green asks, Pink answers.” You also get a sense of the box’s transparency, which was a customer request made by Jeffrey.
Detail shot of the button pad, correct and incorrect buttons, as well as the LCD screen.
Detail shot of the speakers and box design on the back face.
Narrative Sketch
Stella gets dropped off by her single father at Jeffrey’s house, for their weekly Wednesday hangout. Stella would like to play a game, but Jeffrey isn’t sure how to successfully entertain a 6-year girl. Instead of having his wife help him out, he pulls out their customized board-game.
Stella likes to start, because she enjoys the pretty colors the button pad generates. As the game starts, a rainbow pattern illuminates the board, making Stella smile.
The first step of the game is for each player to write down a list of personalized questions on their assigned cards, which can be re-used or re-written as pleased. The aim of these questions is to generate conversation, and learn something from one another. Then, Stella starts by pressing a button of her chosen location on the button pad. Jeffrey asks her a question from his green deck of cards .
Player 1 chooses a location on the board.
Stella attempts to answer the question. If answered correctly, Jeffrey will select the green button, locking in the position on the buttonpad. If the answer is incorrect, Jeffrey presses the red button, and the button’s light goes out. Stella can then try again during the next round. In a long-play game, Stella would then have to perform a penalty found on the white stack of cards.
Player 2 reads the card from their stack, and prompts Player 1 to answer. If correct, Player 2 presses the “correct” button, and the button pad stays lit.
After Stella’s turn, Jeffrey can pick a position and attempt to answer a question. The game goes on until a player is able to line up 5 of their colored buttons on the pad. During a short play game, the loser must perform a penalty found on the white stack of cards, only at the end of the game.
When the game is over, the orange “reset” button can be pressed, a rainbow pattern appears and the board is cleared.
How we got here
Initial Meeting and Brainstorm
On November 1st, 2018, our group – Chloé Desaulles, Jiatian Sun, Jianxiao Ge – firstly met up with Jeffrey, who is an older man participating in the CMU Osher program, with the intention of building a useful device for him.
We had a meeting and interviewed Jeffrey in the university cafe. After the first interview, we started by brainstorming ideas to make Jeffrey’s life easier or more pleasurable. He did not seem to need anything fixed, so we tried to look into ways of enhancing good things in his life. It became clear very quickly that he kept gravitating towards his granddaughter Stella, and the time he and his wife get to spend with her. We additionally noticed that Jeffrey might not perfectly know how to interact with a six-year-old, often letting his wife entertain her.
Our main challenge was creating an interesting and fun game for such a wide age gap. Making a game intuitive and worth playing was a hard thinking exercise considering none of us had any game design experience. In addition, we needed to add physical computing elements into the game, which made it even harder.
Finally, we were inspired by the traditional game Gomoku & Bingo and decided to create a similar game with trivia elements, which match Jeffrey’s interests, and a simple and colorful button interface, with a changeable gameplay so the game can be adaptable as Stella grows up.
Prototype and the Second Meeting
After settling on the concept and gameplay, we started working on the layout and board design, option for big buttons with fun interactions, which would make the game more interesting to a 6-year-old. However, because large buttons took up too much space, we ended up with a medium sized button.
We then started programming the colored buttons, LCD screen, and button pads separately and finally got together to merge our individual codes.
One of the biggest challenges we met during prototype was the core part of our game, the button pad. The RGB keypad we needed was out of stock at that time. We contacted the manufacturer Adafruit and received a reply that it would be two weeks before we could have the keypad available. Therefore, we had to order the LED version of the pad as an alternative in order to finish the prototype on time.
Button Pad is able to light up according to the color of LED beneath it.
We met Jeffrey again after the prototype presentation. Jeffrey seemed very excited by our prototype and believed he and Stella would have fun playing the game. He was also happy about our choices in tactility and color and even suggested we add sound (which is something we had been shying away from because we thought it might be irritating Jeffrey and his wife). What’s more, he gave us a lot of input when it came to the aesthetics of the board, suggesting we should make it out of clear acrylic so that Stella could see the colorful wiring on the inside. Some other fabrication advice he and Zach gave us included building a box to hold question and penalty cards. The crit was useful for our team and allowed us to move confidently forward with our design.
Jeffrey giving us feedback on our prototype.
Final Fabrication
After the second meeting, we adjusted the game based on the feedback. For example, combine the card box with the game board, add the game sound effect, and remove people’s name from the instruction on the LCD screen to involve other family members as well.
Chloé and Jiatian adjusting the game after the second meeting.
Another good news was that RGB Keypad, which was out of stock before, had been replenished. With the help of Zach, we got the keypad in time and immediately started to adjust the code as well as the soldering and testing.
Soldering four 4*4 RGB Keypads together.
At the same time, we started the design of the game box and material preparation. According to Jeffrey’s suggestion, we chose the white fog-faced acrylic as the main material to highlight the colorful buttons and create a light and transparent visual effect.
We sketched the game board by hand and determined the general layout. After actually measuring the size of each part, we drew it exactly one-to-one and then redrew it in our laptop using Autocad and Adobe Illustrator.
Rough hand sketch of game board design and dimensions.
Drawing it one-to-one after measuring the size of each part.
While Jiatian was in charge of the code adjustment and part testing, Chloé and Jianxiao took the acrylic to the laser cut studio. The laser cutting process was not as smooth as expected. Because of the measuring error, the buttons and the LCD screen couldn’t be embedded smoothly. So we measured again, adjusted the size and then carried out a second laser cut, finally got the panel in line with the requirements and started the assembly.
On December 4th, a month after the initial meeting, we had the final presentation and met Jeffrey again. We were delighted to see that he was very pleased with our final product. He played the game with us and was ready to give it to his granddaughter Stella as a Christmas present.
Conclusions and lessons learned
To our surprise, almost all of the oral feedbacks we received during the final crit were positive, whether it was about the experience of the game or the design of the box itself. (which, of course, could be because people were too shy to make suggestions in person). We were happy to see that older people seemed to be very interested in the game. Some of them even thought that it had commercial value. Since the question cards can be written by the players themselves, it gives the game more possibilities. It can be a bridge for family members to communicate and learn from each other, even if there is a huge age gap.
After the crit, we also see a lot of very good advice from the written feedback. For example, “It would be useful to add the name/instructions onto the game.” Because of the novel mechanics of the game, people may be confused when they start to play. Although we try to give each step a hint on the LCD screen, some instructions are not clear and accurate due to the size of the screen. At the same time, an extra game description may help people understand the game faster.
Here is another one: “It could be smaller/thinner – effects portability. Would be nice if users could choose which colors they are in the button pad – maybe they have a favorite color.” The final volume of the game box was indeed larger than we expected. In order to prevent the electronic components from being unable to fit, we reserved much space during the box design. But when we actually assembled it, we found that most of the space in the box was empty. If we can make full use of space, the game can be greatly improved in portability (although the portability may be not that important for a family game box). For the color part, we did take Jeffrey and Stella’s preferences into account and set up keyboard lighting based on their favorite colors. It might be a better idea to let players themselves choose the own lighting colors.
We didn’t have much experience working with older people before, but the process of working with Jeffrey was very pleasant. He was very talkative and provided us with many practical suggestions, which helped us adjust in different stages and finally completed a work that satisfied us all. Due to the time limit, our initial interview could only be held in the university cafe. Although we talked a lot, it seemed difficult for us to get Jeffrey’s demand quickly from his conversation, which made us struggle about where to start in the brainstorm. Next time we have the opportunity to cooperate with the elderly, maybe we will choose their home as the interview place. On the one hand, it can make them feel more comfortable and be willing to talk more, on the other hand, it is more convenient for us to observe the details of their life so that we can understand their needs more quickly.
All in all, this project is an interesting interdisciplinary cooperation experience. We had a lot of brainstorming in the early stage and repeatedly discussed and improved the design scheme according to Jeffrey and Zach’s feedback. In the post-fabrication stage, we divided the work according to everyone’s background and expertise, and finally achieved satisfactory results. If you have any more questions or suggestions, please feel free to contact us by email: jianxiag@andrew.cmu.edu, thanks!
Technical details
Schematic
Code
/* * This code makes reference to sample code of Adafruit seesaw liborary * whose source code can be viewed at https://github.com/adafruit/Adafruit_Seesaw * * Game Logic part Author: Caroline Sun & Jianxiao Ge * This is a game is similar to Bingo. To create a enjoyable gaming experience, we add extra * features like playing sound sound to the game. */ #include "Adafruit_NeoTrellis.h" #include <LiquidCrystal_I2C.h> LiquidCrystal_I2C lcd(0x27, 16, 2); #define DEBUG 1 // TONES ========================================== // Start by defining the relationship between // note, period, & frequency. #define c 3830 // 261 Hz #define d 3400 // 294 Hz #define e 3038 // 329 Hz #define f 2864 // 349 Hz #define g 2550 // 392 Hz #define a 2272 // 440 Hz #define b 2028 // 493 Hz #define C 1912 // 523 Hz // Define a special note, 'R', to represent a rest #define R 0 #define Y_DIM 8 //number of rows of key #define X_DIM 8 //number of columns of keys //Define game states #define QUESTION 0 #define ANSWER 1 #define NEXT 2 //Define Answer to Questions #define YESJ 2 #define YESS 4 #define NOJ 3 #define NOS 5 //Define button pins #define RESET 6 #define NEXTJ 5 #define NEXTS 6 //Define player states #define STELLA 0 #define JEFFREY 1 //Define Board Value #define MIDDLE 3 #define INVALID (-1) #define BINGOSIZE 5 //Define speaker pins #define SPEAKER 9 //Color value int val = 125; //Declare game States int isStart; int state; int player; int currSquare; int scoreS; int scoreJ; int SIZE = 8 ; int totalSize = SIZE * SIZE; int board[64]; // Set overall Speaker configuration long tempo = 10000; int pause = 1000; int rest_count = 100; // MELODY and TIMING ======================================= // melody[] is an array of notes, accompanied by beats[], // which sets each note's relative length (higher #, longer note) //This is the tune of correct answer int successMelody[] = { c, e, g, C, C, C}; int successBeats[] = { 8, 8, 8, 8, 8,8}; int SUCCESS_MAX_COUNT = sizeof(successMelody) / 2; //This is the tune of wrong answer int failureMelody[] = { g, e, d, c, c}; int failureBeats[] = { 8, 8, 8, 8, 8}; int FAILURE_MAX_COUNT = sizeof(failureMelody) / 2; //This is the tune of winning int winMelody[] = { e, e, e, e, f, f, f, f, g,g, g, g, g, g, g, g, f, f, f, f, e,e, e, e, d, d, d, d}; int winBeats[] = { 16, 16, 16, 16, 16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16}; int WIN_MAX_COUNT = sizeof(winMelody) / 2; //This is the tune of reset int resetMelody[] = { c, c, c, c, g, g, g, g, c, c, c, c, g, g, g, g}; int resetBeats[] = { 16, 16, 16, 16, 16,16,16,16, 16, 16, 16, 16,16,16,16,16}; int RESET_MAX_COUNT = sizeof(resetMelody) / 2; // PLAY TONE FUNCTION ============================================== // Pulse the speaker to play a tone for a particular duration void playTone(int tone_, int beat, int duration) { long elapsed_time = 0; if (tone_ > 0) { // if this isn't a Rest beat, while the tone has // played less long than 'duration', pulse speaker HIGH and LOW while (elapsed_time < duration) { digitalWrite(SPEAKER,HIGH); delayMicroseconds(tone_ / 2); // DOWN digitalWrite(SPEAKER, LOW); delayMicroseconds(tone_ / 2); // Keep track of how long we pulsed elapsed_time += (tone_); } } else { // Rest beat; loop times delay for (int j = 0; j < rest_count; j++) { // See NOTE on rest_count delayMicroseconds(duration); } } } //Play Melody function void playMelody(int* pitches, int* beats, int count){ // Set up a counter to pull from melody[] and beats[] int tone_,beat,duration; for (int i=0; i<count; i++) { tone_ = pitches[i]; beat = beats[i]; duration = beat * tempo; // Set up timing playTone(tone_,beat,duration); // A pause between notes... delayMicroseconds(pause); } } //create a matrix of trellis panels Adafruit_NeoTrellis t_array[Y_DIM/4][X_DIM/4] = { { Adafruit_NeoTrellis(0x2E), Adafruit_NeoTrellis(0x2F) }, { Adafruit_NeoTrellis(0x30), Adafruit_NeoTrellis(0x31) } }; //pass this matrix to the multitrellis object Adafruit_MultiTrellis trellis((Adafruit_NeoTrellis *)t_array, Y_DIM/4, X_DIM/4); //Print Board Function for testing void printBoard(){ Serial.println("board"); for(int i = 0;i < totalSize;i++){ Serial.print(board[i]); Serial.print(" "); } Serial.println(""); } //Helper function that cleans up a array void cleanArr(int *arr, int n){ for(int i = 0; i < n;i++){ arr[i] = 0; } } //Write a two-line message to the LCD screen void writemessage(String message1, String message2){ lcd.backlight(); lcd.setCursor(0, 0); lcd.print(message1); lcd.setCursor(0, 1); lcd.print(message2); } //Turn on the button first being pressed int turnOnFirstPressed(int num){ int val = 125; if(player == JEFFREY) val = 255; trellis.setPixelColor(num, Wheel(val)); trellis.show(); return num; } // Input a value 0 to 255 to get a color value. // The colors are a transition r - g - b - back to r. uint32_t Wheel(byte WheelPos) { if(WheelPos < 85) { return seesaw_NeoPixel::Color(WheelPos * 3, 255 - WheelPos * 3, 0); } else if(WheelPos < 170) { WheelPos -= 85; return seesaw_NeoPixel::Color(255 - WheelPos * 3, 0, WheelPos * 3); } else { WheelPos -= 170; return seesaw_NeoPixel::Color(0, WheelPos * 3, 255 - WheelPos * 3); } return 0; } //Check if one index is in bound of a board bool inBound(int x, int y, int width, int height){ return x>=0 && y >=0 && x < width && y <height; } //define a callback for key presses TrellisCallback blink(keyEvent evt){ if(evt.bit.EDGE == SEESAW_KEYPAD_EDGE_RISING){ //Wheel(map(evt.bit.NUM, 0, X_DIM*Y_DIM, 0, 255)) Serial.print("blink! "); Serial.print(evt.bit.NUM); Serial.print(" "); Serial.print(val); Serial.println(" prit"); trellis.setPixelColor(evt.bit.NUM, Wheel(val)); val = val>125? 125: 225; }//on rising else if(evt.bit.EDGE == SEESAW_KEYPAD_EDGE_FALLING) trellis.setPixelColor(evt.bit.NUM, 0); //off falling trellis.show(); return 0; } //The function that checks if there exists winner on the board bool checkWinner(){ int rowCorrect[2]; int colCorrect[2]; int diagDownCorrect[2]; int diagUpCorrect[2]; int currCont[2] = {0,0}; cleanArr(rowCorrect,2); cleanArr(colCorrect,2); cleanArr(diagUpCorrect,2); cleanArr(diagDownCorrect,2); cleanArr(currCont, 2); //Check if there is bingo in a row for(int i = 0; i < SIZE; i++){ for(int j = 1; j < SIZE; j++){ int currP = board[i * SIZE + j]; if(board[i * SIZE + j] == board[i * SIZE + j -1] && board[i * SIZE + j]!=INVALID){ currCont[currP] += 1; if(currCont[currP] > rowCorrect[currP]) rowCorrect[currP] = currCont[currP]; } else{ cleanArr(currCont, 2); } } } cleanArr(currCont, 2); //Check if there is Bingo in a column for(int j = 0; j < SIZE; j++){ for(int i = 1; i < SIZE; i++){ int currP = board[i * SIZE + j]; if(board[i * SIZE + j] == board[(i-1) * SIZE + j] && board[i * SIZE + j]!=INVALID ){ currCont[currP] += 1; if(currCont[currP] > colCorrect[currP]) colCorrect[currP] = currCont[currP]; } else{ cleanArr(currCont, 2); } } } //Check if there is bingo in a diagonal for(int xShift = -SIZE +1; xShift<SIZE;xShift++){ cleanArr(currCont, 2); for(int diagX = 1; diagX<SIZE;diagX++){ int xPos = diagX + xShift; int yPos = diagX; int prevX = xPos -1; int prevY = yPos -1; if(!inBound(xPos,yPos,SIZE,SIZE) || !inBound(prevX,prevY,SIZE,SIZE)){ cleanArr(currCont,2); continue; } int currP = board[yPos * SIZE + xPos]; if(currP == board[prevY * SIZE + prevX] && currP!=INVALID){ currCont[currP] += 1; if(currCont[currP] > diagDownCorrect[currP]) diagDownCorrect[currP] = currCont[currP]; } else{ cleanArr(currCont, 2); } } } for(int xShift = 0; xShift<2 * SIZE -1;xShift++){ cleanArr(currCont, 2); for(int diagX = 1; diagX<SIZE;diagX++){ int xPos = -diagX + xShift; int yPos = diagX; int prevX = xPos +1; int prevY = yPos -1; if(!inBound(xPos,yPos,SIZE,SIZE) || !inBound(prevX,prevY,SIZE,SIZE)){ cleanArr(currCont,2); continue; } int currP = board[yPos * SIZE + xPos]; if(currP == board[prevY* SIZE + prevX] && currP!=INVALID){ currCont[currP] += 1; if(currCont[currP] > diagUpCorrect[currP]) diagUpCorrect[currP] = currCont[currP]; } else{ cleanArr(currCont, 2); } } } int sScore = max(colCorrect[STELLA],max(rowCorrect[STELLA],max(diagDownCorrect[STELLA],diagUpCorrect[STELLA]))); int jScore = max(colCorrect[JEFFREY],max(rowCorrect[JEFFREY],max(diagDownCorrect[JEFFREY],diagUpCorrect[JEFFREY]))); //Display the winner of the game if(sScore >= BINGOSIZE-1 && jScore >=BINGOSIZE-1){ Serial.println("Both of you wins!"); writemessage(" Both of you "," Wins! "); playMelody(winMelody,winBeats,WIN_MAX_COUNT); return true; } if(sScore >= BINGOSIZE-1){ Serial.println("Pink wins!"); writemessage(" Pink "," Wins! "); playMelody(winMelody,winBeats,WIN_MAX_COUNT); return true; } if(jScore >= BINGOSIZE-1){ Serial.println("Green wins!"); writemessage(" Green "," Wins! "); playMelody(winMelody,winBeats,WIN_MAX_COUNT); return true; } return false; } //The callBack function on the Trellis Buttons //This function tells the Button to update the game state, //if it is pressed when someone is answering question TrellisCallback gameControl(keyEvent evt){ if(isStart){ // go through every button if(state == QUESTION){ if(evt.bit.EDGE == SEESAW_KEYPAD_EDGE_RISING){ currSquare = turnOnFirstPressed(evt.bit.NUM); if(currSquare>=0){ state = ANSWER; } } } } } //Setup the game void setup() { Serial.begin(9600); //while(!Serial); if(!trellis.begin()){ Serial.println("failed to begin trellis"); while(1); } //Register all buttons pinMode(YESJ,INPUT_PULLUP); pinMode(YESS,INPUT_PULLUP); pinMode(NOJ,INPUT_PULLUP); pinMode(NOS,INPUT_PULLUP); pinMode(NEXTJ,INPUT_PULLUP); pinMode(NEXTS,INPUT_PULLUP); pinMode(RESET,INPUT_PULLUP); //Register speaker pinMode(SPEAKER,OUTPUT); //Initialize LCD lcd.init(); // initialize the lcd // Print a message to the LCD. lcd.backlight(); lcd.setCursor(0, 0); lcd.print("Pink & Green"); lcd.setCursor(0, 1); lcd.print("Welcome to BINGO"); //Clear board for(int i = 0; i< SIZE * SIZE;i++){ board[i] = -1; } //Go through each button on the board and display different color for(int i=0; i<Y_DIM*X_DIM; i++){ trellis.setPixelColor(i, Wheel(map(i, 0, X_DIM*Y_DIM, 0, 255))); trellis.show(); delay(20); } //Setup the trellis button pad for(int y=0; y<Y_DIM; y++){ for(int x=0; x<X_DIM; x++){ //activate rising and falling edges on all keys trellis.activateKey(x, y, SEESAW_KEYPAD_EDGE_RISING, true); trellis.activateKey(x, y, SEESAW_KEYPAD_EDGE_FALLING, true); trellis.registerCallback(x, y,gameControl); trellis.setPixelColor(x, y, 0x000000); //addressed with x,y trellis.show(); //show all LEDs delay(20); } } //Randomize a player to start the game float r = random()%2; if( r > 0){ player = STELLA; } else{ player = JEFFREY; } //Reset Game State isStart = 1; state = QUESTION; scoreS = 0; scoreJ = 0; currSquare = -1; //Display welcome message writemessage("Pink & Green","Welcome to BINGO"); } void loop() { trellis.read(); delay(20); //If Rest Button is pressed, reset the game if(digitalRead(RESET)<1){ writemessage(" Reset! "," "); delay(500); playMelody(resetMelody,resetBeats,RESET_MAX_COUNT); setup(); return; } //let the game continue only if the game has not ended yet if(isStart){ //If it is question and question time if(state == QUESTION){ //Display messages showing states if(player ==STELLA) writemessage(" Green asks ", " Pink answers "); else writemessage(" Pink asks ", " Green answers "); } //If it is revealing answer state, else if(state == ANSWER){ if(player==STELLA){ writemessage(" Is Pink's ", " answer correct? "); //If Pink's answer is correct if(digitalRead(YESJ)<1){ board[currSquare] = STELLA; Serial.print("Pink places at position: "); Serial.println(currSquare); printBoard(); //play music playMelody(successMelody,successBeats,SUCCESS_MAX_COUNT); //Change game state to next round currSquare = -1; state = QUESTION; isStart = !checkWinner(); player = !player; } //If Pink's answer is wrong else if(digitalRead(NOJ)<1){ trellis.setPixelColor(currSquare, 0); trellis.show(); //play music playMelody(failureMelody,failureBeats,FAILURE_MAX_COUNT); //change game state to next round currSquare = -1; state = QUESTION; isStart = !checkWinner(); player = !player; } } else{ writemessage(" Is Green's ", "answer correct? "); //If Green's answer is correct if(digitalRead(YESS)<1){ board[currSquare] = JEFFREY; Serial.print("Green places at position: "); Serial.println(currSquare); printBoard(); playMelody(successMelody,successBeats,SUCCESS_MAX_COUNT); currSquare = -1; state = QUESTION; isStart = !checkWinner(); player = !player; } //If Green's answer is wrong else if(digitalRead(NOS)<1){ trellis.setPixelColor(currSquare, 0); trellis.show(); playMelody(failureMelody,failureBeats,FAILURE_MAX_COUNT); currSquare = -1; state = QUESTION; isStart = !checkWinner(); player = !player; } } } } }
Leave a Reply
You must be logged in to post a comment.