Interaction Lab Midterm Project Blog

1. Tic-Tac-Mole! – Junhao Zhu & Jiaxi Zhang – Gottfried Haider

Here is a picture of our project.

2. Context & Significance

In our project, we are trying to reimagine how the classical game in our childhood can be more enjoyable to modern players. Among many candidates that we spent a lot of time on when we were children, we chose Whac-A-Mole, an arcade game in which multiple holes in the play area top are filled with small, plastic, cartoonish moles, which pop up at random. Points are scored by whacking each mole with the hammer as it appears. The faster the reaction, the higher the score. The game demands quick reflexes, hand-eye coordination, and timing from the player to successfully hit the moles as they appear.

By sa_ku_ra / sakura – *Source: Flickr image., CC BY 2.0, https://commons.wikimedia.org/w/index.php?curid=36174230

To add interactivity to the original design, we applied the rules of Tic-Tac-Toe, a paper-and-pencil game for two players who take turns marking the spaces in a three-by-three grid with X or O. The player who succeeds in placing three of their marks in a horizontal, vertical, or diagonal row is the winner.

By Jkca Newton – *Source: Board Game Wiki, https://board-games.fandom.com/wiki/Tic-tac-toe

Since Tic-Tac-Toe is a solved game, which means the first player will always win if he or she follows an optimal strategy, we think the randomness from the Whac-A-Mole can make the simple Tic-Tac-Toe challenging. Similarly, the rules from Tic-Tac-Toe can make the project more strategic as it involves predicting the necessity of each move within a limited time. Altogether, the project involves a blend of unpredictability, skill, and certain thinking, making the project interactive for players.

3. Conception & Design

In our original design, we were trying to build a multiplayer game with a combination of Tic-Tac-Toe and Whac-A-Mole, the concept is shown in the image below.

The box behind would act as an “arcade”. We designed that the sticks attached to servos would indicate moles, and they would randomly flip up in every round of the game. Two players would take turns to hit the corresponding square on the cardboard in front of them. Capacitive sensors were hidden behind to sense whether the current player hit the mole with their hands correctly, and if so, lit up the player’s LED on the “arcade”.  We also designed a rule book in our initial design.

In each round, the current player can choose either to “stay”, which is not to hit the mole, or to “move”, which is to hit. If he or she “stays”, the position changes to the other player. If he or she “moves”, there will be 2 circumstances: he or she hits the “right” mole and hits the “wrong” mole. If he or she hits the “right” mole, the LED on the corresponding square will be lit and the game will move to the next round. If he or she hits the “wrong” mole, there will be a penalty round. The other player will be offered an extra round for hitting the mole without any further penalty if he or she also hits the wrong mole.

However, several drawbacks existed in this design made us need to compromise and enhance the project. 

  1. The complicated rule book. The users could not easily understand how to interact with the projects without our detailed explanations and will require a long period to be familiar with the gameplay process. That is to say, our program is not intuitive and is of poor interactivity.
  2. The multiplayer design. Within our current design, it is hard for the Arduino to track which player is currently making the move. Different issues will occur if the users do not follow the instructions correctly, and they might confuse the users in eventually enjoying playing this game.
  3.  The efficiency in mechanism. To specify, this issue refers to the excessive emphasis on displaying the status on the “arcade”. If we want to control nine servos individually, the power supply would be an issue, and it would also take up too many digital pins on the Arduino. 

We solved these three questions in our final design. The game is now in single-player mode which the user plays against the Arduino. In the code, the Arduino will randomly pick a square that lights up the LED and will earn that point if the user does not hit the right square or not hit at all. For the rules, we just maintain those essential components of Tic-Tac-Toe: the player who succeeds in placing three of their marks in a horizontal, vertical, or diagonal row is the winner. This alternative plan ensures the project is more easy to play with without sacrificing much delight.

To enhance efficiency, we choose neopixels in both displaying the moles’ positions and the chessboard position by changing the individual LED’s blinking status. This option will significantly reduce the over-consumption of digital pins and reach our purpose.

4. Fabrication & Production

In the first part, I will introduce the main structure of this project: a display board and a control panel.

Display Board:


The image shown above is a double-layered cardboard with embedded neopixels in our prototype. This cardboard will show the current status of the “mole” and the chessboard.  The video shows the process when we tested whether it worked properly in the prototype of a 2×2 version of Tic-Tac-Mole!  

Here is a video of us testing the feasibility of neopixels.

In our initial design, we decided to drill holes in one cardboard and thread the neopixels through them as the basic structure of the display board. However, we found that the pads on neopixel LEDs were extremely vulnerable because they could easily be dragged down when we were threading.

To solve this issue, we came up with an alternative plan to protect the pads. We soldered nine LEDs into a long strip, first securing them on one piece of cardboard, then drilling holes at corresponding positions on another piece of cardboard to expose the LEDs, and finally, sticking the two pieces of cardboard together. The double-layered board will act as a cushion from the outside force. 

This new design had proven its accessibility and durability in the user test, and we kept this design in our final result.

Control Panel:

This image is the circuit of the capacitive sensors. The 9 sensors will take the digital pins 2 to 10 on the Arduino. The capacitive sensors are soldered to the copper tapes that are stuck to the panel in front of the users. 

We also used a hammer to enhance immersiveness for users. The hammer is wrapped up with copper tapes so that when the user holds it with their hands and smashes it on the panel, the electric field changes, and the capacitive sensor can sense their movements with the DigitalRead functions, then light up the corresponding LED with the pre-assigned addresses in the code. 

Code:

#include FastLED.h
#include time.h
int cur_pos; //The current position of the mole
#define NUM_LEDS 9// How many LEDs are on your strip?
#define DATA_PIN 11

CRGB leds[NUM_LEDS];
int chessboard[9] = {0,0,0,0,0,0,0,0,0}; //0 for no one, 1 for player, 2 for mole
int sensor_array[9] = {0,0,0,0,0,0,0,0,0};
int state = 1;//control state
long startTime;
int WoL_state = 0;

void setup() {
  Serial.begin(9600);
  pinMode(2, INPUT);
  pinMode(3, INPUT);
  pinMode(4, INPUT);
  pinMode(5, INPUT);
  pinMode(6, INPUT);
  pinMode(7, INPUT);
  pinMode(8, INPUT);
  pinMode(9, INPUT);
  pinMode(10, INPUT);
  randomSeed(analogRead(A0));
  FastLED.addLeds<NEOPIXEL, DATA_PIN>(leds, NUM_LEDS);
  FastLED.setBrightness(50); 
}

void loop() {
  // Serial.println(state);
  if (state == 1) {//game time
    state1();
  } else if (state == 2) {// win/lose/draw time
    state2();
  }
}


// main game
void state1() {
  for (int i = 0; i <= 8; i++){
    sensor_array[i] = digitalRead(i + 2);
  }
  cur_pos = random(0,9);
  while (chessboard[cur_pos] != 0) {
  cur_pos = random(0,9);
  }
  chessboard[cur_pos] = 2;
  for (int i = 0; i <= 3; i++){
    leds[cur_pos] = CRGB(255, 165, 0);
    FastLED.show();
    if (sensor_array[cur_pos] == 1) {
      chessboard[cur_pos] = 1;
      leds[cur_pos] = CRGB(0, 255, 0);
      FastLED.show();
    }
    delay(100);
    if (sensor_array[cur_pos] == 1) {
      chessboard[cur_pos] = 1;
      leds[cur_pos] = CRGB(0, 255, 0);
      FastLED.show();
    }
    leds[cur_pos] = CRGB(0, 0, 0);
    FastLED.show();
    if (sensor_array[cur_pos] == 1) {
      chessboard[cur_pos] = 1;
      leds[cur_pos] = CRGB(0, 255, 0);
      FastLED.show();
    }
    delay(100);
    if (sensor_array[cur_pos] == 1) {
      chessboard[cur_pos] = 1;
      leds[cur_pos] = CRGB(0, 255, 0);
      FastLED.show();
    }
    for (int i = 0; i <= 8; i++){
    sensor_array[i] = digitalRead(i + 2); 
  }

    if (sensor_array[cur_pos] == 1) {
      chessboard[cur_pos] = 1;
      leds[cur_pos] = CRGB(0, 255, 0);
      FastLED.show();
      break;
    }
  }
  if (chessboard[cur_pos] == 2){
      leds[cur_pos] = CRGB(255, 0, 0);
      FastLED.show();
  }
  delay(1000);
  if (chessboard[2] == 1 && chessboard[1] == 1 && chessboard[0] == 1 || 
  chessboard[3] == 1 && chessboard[4] == 1 && chessboard[5] == 1 || chessboard[8] == 1 && chessboard[7] == 1 && chessboard[6] == 1 || chessboard[2] == 1 && chessboard[3] == 1 && chessboard[8] == 1 || chessboard[1] == 1 && chessboard[4] == 1 && chessboard[7] == 1|| chessboard[0] == 1 && chessboard[5] == 1 && chessboard[6] == 1||chessboard[2] == 1 && chessboard[4] == 1 && chessboard[6] == 1||chessboard[0] == 1 && chessboard[4] == 1 && chessboard[8] == 1){
    state = 2;
    WoL_state = 1;
  }
  else if (chessboard[2] == 2 && chessboard[1] == 2 && chessboard[0] == 2 ||
   chessboard[3] == 2 && chessboard[4] == 2 && chessboard[5] == 2 || chessboard[8] == 2 && chessboard[7] == 2 && chessboard[6] == 2 || chessboard[2] == 2 && chessboard[3] == 2 && chessboard[8] == 2 || chessboard[1] == 2 && chessboard[4] == 2 && chessboard[7] == 2|| chessboard[0] == 2 && chessboard[5] == 2 && chessboard[6] == 2||chessboard[2] == 2 && chessboard[4] == 2 && chessboard[6] == 2||chessboard[0] == 2 && chessboard[4] == 2 && chessboard[8] == 2){
    state = 2;
    WoL_state = 2;
  }
  else if (chessboard[0] != 0 && chessboard[1] != 0 && chessboard[2] != 0 && chessboard[3] != 0 && chessboard[4] != 0 && chessboard[5] != 0 && chessboard[6] != 0 && chessboard[7] != 0 && chessboard[8] != 0){
    state = 2;
  }
}
// outro
void state2() {
  
  leds[0] = CRGB(0,0,0);
  leds[1] = CRGB(0,0,0);
  leds[2] = CRGB(0,0,0);
  leds[3] = CRGB(0,0,0);
  leds[4] = CRGB(0,0,0);
  leds[5] = CRGB(0,0,0);
  leds[6] = CRGB(0,0,0);
  leds[7] = CRGB(0,0,0);
  leds[8] = CRGB(0,0,0);

  if (WoL_state == 1){
    leds[0] = CRGB(255,0,0);
    FastLED.show();
    delay(50);
    leds[1] = CRGB(255,165,0);
    FastLED.show();
    delay(50);
    leds[2] = CRGB(255,255,0);
    FastLED.show();
    delay(50);
    leds[3] = CRGB(0,255,0);
    FastLED.show();
    delay(50);
    leds[4] = CRGB(0,255,255);
    FastLED.show();
    delay(50);
    leds[5] = CRGB(0,0,255);
    FastLED.show();
    delay(50);
    leds[6] = CRGB(128,0,255);
    FastLED.show();
    delay(50);
    leds[7] = CRGB(255,0,255);
    FastLED.show();
    delay(50);
    leds[8] = CRGB(255,0,128);
    FastLED.show();
    delay(50);
    leds[0] = CRGB(0,0,0);
    FastLED.show();
    delay(50);
    leds[1] = CRGB(0,0,0);
    FastLED.show();
    delay(50);
    leds[2] = CRGB(0,0,0);
    FastLED.show();
    delay(50);
    leds[3] = CRGB(0,0,0);
    FastLED.show();
    delay(50);
    leds[4] = CRGB(0,0,0);
    FastLED.show();
    delay(50);
    leds[5] = CRGB(0,0,0);
    FastLED.show();
    delay(50);
    leds[6] = CRGB(0,0,0);
    FastLED.show();
    delay(50);
    leds[7] = CRGB(0,0,0);
    FastLED.show();
    delay(50);
    leds[8] = CRGB(0,0,0);
    FastLED.show();
    delay(50);
  }
if (WoL_state == 2){
    leds[0] = CRGB(255,0,0);
    leds[2] = CRGB(255,0,0);
    leds[4] = CRGB(255,0,0);
    leds[6] = CRGB(255,0,0);
    leds[8] = CRGB(255,0,0);
    FastLED.show();
    delay(200);
    leds[0] = CRGB(0,0,0);
    leds[2] = CRGB(0,0,0);
    leds[4] = CRGB(0,0,0);
    leds[6] = CRGB(0,0,0);
    leds[8] = CRGB(0,0,0);
    FastLED.show();
    delay(200);
}
if (WoL_state == 0){
  leds[0] = CRGB(255,255,255);
  leds[1] = CRGB(255,255,255);
  leds[2] = CRGB(255,255,255);
  leds[3] = CRGB(255,255,255);
  leds[4] = CRGB(255,255,255);
  leds[5] = CRGB(255,255,255);
  leds[6] = CRGB(255,255,255);
  leds[7] = CRGB(255,255,255);
  leds[8] = CRGB(255,255,255);
  FastLED.show();
}
}

Since this project is strongly coding-based, I will explain a bit about how we conduct the coding. At the very beginning, we divided the program into two states: state 1 was for recording the movements of the users, indicating the places of moles, and examining whether the winner won, lost, or drew the game by continuously measuring all possible lines horizontally, vertically, and diagonally. We adopted a random seed function to make sure that the mole would appear at a completely random place at the beginning of each game since if removed, the LEDs would be lit on in the same pattern. This was a precious experience that we learned from our user test.

After several rounds of the game, it would finally end in one of the three statuses: win, lose, and draw. In this situation, we would move to status 2, a status that indicates the result to the users. Here are three clips for different results:

Win:

Lose:

Draw:

Experience from User Test:

In the user test, we exhibited a prototype of a 2×2 version of Tic-Tac-Mole!  as the previous video showed. We received valuable feedback from our testers. Compared to the prototype, we have added guides on the display board to help users better understand the game rules. We also installed a servo motor on the upper side of the display board to control a hammer for hitting the mole, increasing the fun factor. Additionally, we are using a box to cover the breadboard and Arduino on the control panel, making the entire project look like an arcade machine.

In the process of building this project, I was responsible for the coding and the electronics while my teammate, Jiaxi Zhang, put her effort into the construction and improvements of our design. Thank her for the great job!

5. Conclusion

This project is a reimagination of a more enjoyable classical game. We generated our inspiration from two games that people of our generation are familiar with in our childhood: Tic-Tac-Toe and Whac-A-Mole to create a new game that everyone can enjoy. The project involves unpredictability, skill, and certain thinking from the users, and these are the features that generate interactivity. We believe that after our advancements, the game itself is easy to start with but will take some time to fully master it, and that’s why it is attractive.

However, this project is absolutely not that perfect, and more improvements can be made. The first is the choice of sensors. Due to the working principle of touching capacitive sensors, such sensors will monitor inputs even if the user is not placing the hammers on the panel since the electric field can change even when the copper tapes are at a distant place above the panel. The touching capacitive sensors are oversensitive for this project. 

Another issue is the continuity of the projects. In the current design, if the user wants to start again, he or she can only press the reset on the Arduino. We can add a button to the “arcade” so the user can press it to restart, and we should add certain codes to record the winning/losing status for the user and display it if the user is curious.

The process of this project taught me that the initial idea can vary a lot from the final result. When we conceived the idea for the project, we put a lot of emphasis on the gameplay. We tried to make it special, to make it challenging, to make the player laugh out loud when interacting. However, when we started to build it and listened to our testers, we figured out that many compromises needed to be made due to the limitations of our abilities or the materials that we chose to build the project. For example, the sensitivity of the capacitive sensors posed unexpected challenges, requiring adjustments to ensure accurate gameplay; the complicated game rules that we thought were interesting enough seemed to be non-intuitive. By actively hearing from others, can we make our project better.

6. Disassembly

7. Appendix

Code for the prototype:

#include FastLED.h
#include time.h
int cur_pos, sAudioPin = 7; //The current position of the mole
#define NUM_LEDS 5// How many leds on your strip?
#define DATA_PIN 6
#define NOTE_A5  880
#define NOTE_B5  988
#define NOTE_C5  1047
#define NOTE_D5  1175
#define NOTE_E5  1319
#define NOTE_G4  392
#define NOTE_C4  262

CRGB leds[NUM_LEDS];
int chessboard[9] = {0,0,0,0,0}; 
int sensor_array[5] = {0,0,0,0,0};
int state = 2;
int button;
long startTime;
char WoL_state = "Draw";

void setup() {
  Serial.begin(9600);
  pinMode(13,INPUT);
  pinMode(2, INPUT);
  pinMode(3, INPUT);
  pinMode(4, INPUT);
  pinMode(5, INPUT);
  pinMode(sAudioPin, OUTPUT);
  randomSeed(analogRead(A0));
  FastLED.addLeds<NEOPIXEL, DATA_PIN>(leds, NUM_LEDS);
  FastLED.setBrightness(50); 
  for (int i = 0; i <= 8; i ++ ){
    leds[i] = CRGB(0, 0, 0);
    FastLED.show();
  }
}

void loop() {
  // Serial.println(state);
  if (state == 1) {
    state1();
  } else if (state == 2) {//game time
    state2();
  }
}

void state1() {
  button = digitalRead(13);
  if (button == HIGH){
    state = 2;
  }
}

void state2() {
  for (int i = 0; i <= 4; i++){
    sensor_array[i] = digitalRead(i + 1); 
  }
  cur_pos = random(1,5);
  Serial.println(cur_pos);
  while (chessboard[cur_pos] != 0) {
  cur_pos = random(1,5);
  }
  Serial.print(cur_pos);
  chessboard[cur_pos] = 2;
  for (int i = 0; i <= 3; i++){
    leds[cur_pos] = CRGB(255, 165, 0);
    FastLED.show();
    delay(100);
    leds[cur_pos] = CRGB(0, 0, 0);
    FastLED.show();
    delay(100);
    for (int i = 0; i <= 4; i++){
    sensor_array[i] = digitalRead(i + 1);
  }
    if (sensor_array[cur_pos] == 1) {
      chessboard[cur_pos] = 1;
      leds[cur_pos] = CRGB(0, 255, 0);
      FastLED.show();
      break;
    }
  }
  if (chessboard[cur_pos] == 2){
      leds[cur_pos] = CRGB(255, 0, 0);
      FastLED.show();
  }
  delay(1000);
  if (chessboard[1] == 1 && chessboard[2] == 1 || chessboard[3] == 1 && chessboard[4] == 1 || chessboard[1] == 1 && chessboard[3] == 1 || chessboard[2] == 1 && chessboard[4] == 1 || chessboard[1] == 1 && chessboard[4] == 1 || chessboard[2] == 1 && chessboard[3] == 1){
    state = 3;
    WoL_state = "Win";
  }
  else if (chessboard[1] == 2 && chessboard[2] == 2 || chessboard[3] == 2 && chessboard[4] == 2 || chessboard[1] == 2 && chessboard[3] == 2 || chessboard[2] == 2 && chessboard[4] == 2 || chessboard[1] == 2 && chessboard[4] == 2 || chessboard[2] == 2 && chessboard[3] == 2){
    state = 3;
    WoL_state = "Lose";
  }
} 

The illustration for the multiplayer gameplay in initial design:

Leave a Reply

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