Let’s "Electrify" a Korean Board Game

Today we take a look into creating a version of the classic Korean board game, Yut Nori, into an electronic format.

Favorited Favorite 1

This is a guest blog post by close SparkFun friend, Jackson Hootman. Jackson is currently studying Mechanical Engineering at the University of Colorado Boulder. Last summer, Jackson had the opportunity to work with SparkFun as an intern, and ever since then has had a passion for creating his own electronics projects. You may remember his post from last year about adding electronics and lighting effects to existing Lego® kits.

How to Play

Yut Nori is a popular Korean board game. It has been played for hundreds of years and consists of players moving four horses (tokens) through a series of stations.

Yut Stations

Yut stations for a rectangular configuration.

Each horse begins and ends at the blue station. The player to first return all of their horses is the winner. If a horse lands on a large station (highlighted in red) then the player has the option of taking a shortcut by moving towards the center station on their next turn.

Yut Courses

There are four possible routes to take.

These short cuts can only be taken if a player lands on the red stations above. Otherwise, the light green path is taken. The number of stations moved per turn is determined by the throwing of four yut sticks. Yut sticks are half cylinders, meaning that when thrown they will either land on a curved side or a flat side. The combination of flat and curved sticks determines the number of stations a player will be able to move. One point is earned per stick that lands on its curved side. The following image gives the corresponding value to each possible combination.

Point Values

Point values for possible yut stick combinations.

What really makes the game fun though is the ability to have multiple tokens from the same team on the board at the same time. Instead of moving a horse already on the game board, a player can choose to add a new horse to the game. Additionally, if a token lands on another token from the same team, those horses can be stacked such that they move together for the remainder of the game. This is dangerous, however, because if an opposing player lands on a station with your horses, then all of your horses at that station are returned home. Below are the tokens and yut sticks I made for my board.

Yut Tokens

The tokens are made from multiple layers of acrylic. They are made to stack together.

Yut Sticks

The sticks are made of 4in long wood half cylinders (with diameter 3⁄4”).


Let’s Add Some Electronics

The electronics of my game board consists of three major parts. First, there is one LED per player. The LEDs are used to indicate to the players whose turn it is. In this way, only one LED will be powered at a time and that respective LED will be on for the entirety of that players turn.

Yut LED

Basic Blue LED in series with 1000 Ohm resistor.

Next, buttons are used to start the game, end the game, finish turns, and change an individual's score. For my project, I used three separate push buttons. One button is used to add points to a players score, one is used to subtract points from a players score (just in case mistakes were made in adding points). The last button is held for a few seconds to start a game and once on, is used to switch between players. Once gameplay is done, pushing down on this button for a few seconds will shut the board off. Once this is done, the scores will be reset.

Yut Button

Each player’s score (the number of horses they have returned) is displayed using a seven segment display. Because I wanted this game board to handle up to four players, I’d need a way to control at least 28 individuals pins. That's where shift registers came in handy. I utilized one shift register per seven segment display. This meant that in my final circuit, I had four daisy chained shift registers.

Yut Circuit

Four daisy chained shift registers.

One of the most interesting parts of this project was programming the seven segment displays. Each display has 10 pins, which meant it was important to be uniform in my notation and coding. While writing the Arduino code for this project I chose to define the pins in the following way:

Seven Segment Diagram

Each pin (except for the two common anode pins) controls one segment.

Each pin controls the segment highlighted with the corresponding color. These are common anode displays, meaning that when power is supplied to pin-3 or pin-8, segments will only be powered when their respective pin is set to low.

If for example, you wanted to display the number one, pin-5 and pin-7 should be set to low. To program the seven segment display with a shift register, this information should be sent as a byte. There is one bit per each of the eight pins that control an LED. If only pin-5 and pin-7 are set to low, the byte we want to send to the shift register is 11101011.

Seven Segment Example

Byte to send to shift register to display the number one.

I repeated this process for all the other numbers I wanted to display. I stored the bytes for each number in an array for easy access in Arduino. Feel free to take a look at the code attached below for more information.

//Jackson Hootman
//Yut Game Board

// shift register pins
#define dataPin 2
#define latchPin 3
#define clockPin 4

// inputs
#define ButtonBlack 13
#define ButtonBlue 12
#define ButtonRed 11

// turn indicators
int LED[4];

//player 4 - initially all off
byte sevenSegD = 0xFF;
//player 3 - initially all off
byte sevenSegC = 0xFF;
//player 2 - initially all off
byte sevenSegB = 0xFF;
//player 1 - initially all off
byte sevenSegA = 0xFF;

//array to store byte inputs for 0-4
byte sevenSegArray[5];

//other variables
boolean gameOn = false;
boolean finished[4];
int player = 1;
int score[4];
int place = 1;

void setup() {

  //initial score for each player
  score[0] = 0; //player 1
  score[1] = 0; //player 2
  score[2] = 0; //player 3
  score[3] = 0; //player 4

  //no one has finished
  finished[0] = false; //player 1
  finished[1] = false; //player 2
  finished[2] = false; //player 3
  finished[3] = false; //player 4

  //LED pins
  LED[0] = 9;
  LED[1] = 8;
  LED[2] = 7;
  LED[3] = 6;

  pinMode(LED[0], OUTPUT);
  pinMode(LED[1], OUTPUT);
  pinMode(LED[2], OUTPUT);
  pinMode(LED[3], OUTPUT);

  //byte values for 0-4 on seven segment display
  sevenSegArray[0] = 0x88; // zero in hexidecimal
  sevenSegArray[1] = 0xEB; // one in hexidecimal
  sevenSegArray[2] = 0x4C; // two in hexidecimal
  sevenSegArray[3] = 0x49; // three in hexidecimal
  sevenSegArray[4] = 0x2B; // four in hexidecimal

  // shift register pins
  pinMode(dataPin, OUTPUT);
  pinMode(latchPin, OUTPUT);
  pinMode(clockPin, OUTPUT);

  // input pins
  pinMode(ButtonBlack, INPUT_PULLUP);
  pinMode(ButtonBlue, INPUT_PULLUP);
  pinMode(ButtonRed, INPUT_PULLUP);

  digitalWrite(latchPin, LOW);
  shiftOut(dataPin, clockPin, LSBFIRST, sevenSegD);
  shiftOut(dataPin, clockPin, LSBFIRST, sevenSegC);
  //latch pin to high - data done transmitting
  digitalWrite(latchPin, HIGH);
  delay(1000);
}

void loop() {
  if (gameOn) {
    if (digitalRead(ButtonBlack) == HIGH) {
      player = switchPlayer(player); // next players turn
      delay(2000);
      if (digitalRead(ButtonBlack) == HIGH) {
        gameOn = false; // if button is held down, turn off
        shutDown();
      }
    }
    if (digitalRead(ButtonBlue) == HIGH) {
      if (score[player - 1] == 3) {
        score[player - 1] = place; //player finished
        finishSegment(player, place, score);
        finishSegment(player, place, score);
        finishSegment(player, place, score);
        updateSevenSeg(sevenSegArray[score[0]], sevenSegArray[score[1]], sevenSegArray[score[2]],                          sevenSegArray[score[3]]);
        place++;
        finished[player - 1] = true;
        delay(1000);
      }
      if (finished[player - 1] == false) {
        score[player - 1]++; //add one
        updateSevenSeg(sevenSegArray[score[0]], sevenSegArray[score[1]], sevenSegArray[score[2]],                          sevenSegArray[score[3]]);
       delay(1000);
      }
    }
    if (digitalRead(ButtonRed) == HIGH) {
      if (score[player - 1] == 0) {
      } else {
        score[player - 1]--; //subtract one
        updateSevenSeg(sevenSegArray[score[0]], sevenSegArray[score[1]], sevenSegArray[score[2]],                          sevenSegArray[score[3]]);
        delay(1000);
      }
    }
 }
  else
 {
    if (digitalRead(ButtonBlack) == HIGH) {
      delay(1000);
      if (digitalRead(ButtonBlack) == HIGH) {
        gameOn = true; //if held, start up
        startUp();
      }
    }
  }
}

void startUp() {
  //blink 3 times

  updateSevenSeg(0, 0, 0, 0); // switch on
  updateLEDs(false, false, false, false); // LEDS off
  delay(500);

  updateSevenSeg(0xFF, 0xFF, 0xFF, 0xFF); // switch off
  updateLEDs(true, true, true, true); // LEDS on
  delay(500);

  updateSevenSeg(0, 0, 0, 0); // switch on
  updateLEDs(false, false, false, false); // LEDS off
  delay(500);

  updateSevenSeg(0xFF, 0xFF, 0xFF, 0xFF); // switch off
  updateLEDs(true, true, true, true); // LEDS on
  delay(500);

  updateSevenSeg(0, 0, 0, 0); // switch on
  updateLEDs(false, false, false, false); // LEDS off
  delay(500);

  updateSevenSeg(0xFF, 0xFF, 0xFF, 0xFF); // switch off
  updateLEDs(true, true, true, true); // LEDS on
  delay(1000);

  updateLEDs(true, false, false, false); // LEDS off

  //initiallize start order
  //player 1 light up
  updateSevenSeg(sevenSegArray[1], 0xFF, 0xFF, 0xFF);
  delay(1000);

  //player 2 light up
  updateSevenSeg(sevenSegArray[1], sevenSegArray[2], 0xFF, 0xFF);
  delay(1000);

  //player 3 light up
  updateSevenSeg(sevenSegArray[1], sevenSegArray[2], sevenSegArray[3], 0xFF);
  delay(1000);

  //player 4 light up
  updateSevenSeg(sevenSegArray[1], sevenSegArray[2], sevenSegArray[3], sevenSegArray[4]);
  delay(2000);

  //all players to zero
  updateSevenSeg(sevenSegArray[0], sevenSegArray[0], sevenSegArray[0], sevenSegArray[0]);
  delay(5000);

  // start with player 1
  player=1;

  //initial score for each player
  score[0] = 0; //player 1
  score[1] = 0; //player 2
  score[2] = 0; //player 3
  score[3] = 0; //player 4

}

void shutDown() {
  //blink 3 times

  updateSevenSeg(0xFF, 0xFF, 0xFF, 0xFF); // switch off
  updateLEDs(true, true, true, true); // LEDS on
  delay(1000);

  updateSevenSeg(0, 0, 0, 0); // switch on
  updateLEDs(false, false, false, false); // LEDS off
  delay(500);

  updateSevenSeg(0xFF, 0xFF, 0xFF, 0xFF); // switch off
  updateLEDs(true, true, true, true); // LEDS on
  delay(500);

  updateSevenSeg(0, 0, 0, 0); // switch on
  updateLEDs(false, false, false, false); // LEDS off
  delay(500);

  updateSevenSeg(0xFF, 0xFF, 0xFF, 0xFF); // switch off
  updateLEDs(true, true, true, true); // LEDS on
  delay(500);

  updateSevenSeg(0, 0, 0, 0); // switch on
  updateLEDs(false, false, false, false); // LEDS off
  delay(500);

  updateSevenSeg(0xFF, 0xFF, 0xFF, 0xFF); // switch off
  updateLEDs(true, true, true, true); // LEDS on
  delay(1000);

  updateLEDs(false, false, false, false); // LEDS off
}

int switchPlayer(int player) {
  switch (player) {
    case 1:
      player = 2;
      updateLEDs(false, true, false, false);
      break;
    case 2:
      player = 3;
      updateLEDs(false, false, true, false);
      break;
    case 3:
      player = 4;
      updateLEDs(false, false, false, true);
      break;
    case 4:
      player = 1;
      updateLEDs(true, false, false, false);
      break;
  }
  return player;
}

void finishSegment(int player, int place, int score[4]) {
  switch (player) {
    case 1:
      updateSevenSeg(0xBF, sevenSegArray[score[1]], sevenSegArray[score[2]], sevenSegArray[score[3]]);
      delay(200);
      updateSevenSeg(0xDF, sevenSegArray[score[1]], sevenSegArray[score[2]], sevenSegArray[score[3]]);
      delay(200);
      updateSevenSeg(0xEF, sevenSegArray[score[1]], sevenSegArray[score[2]], sevenSegArray[score[3]]);
      delay(200);
      updateSevenSeg(0xFB, sevenSegArray[score[1]], sevenSegArray[score[2]], sevenSegArray[score[3]]);
      delay(200);
      updateSevenSeg(0xFD, sevenSegArray[score[1]], sevenSegArray[score[2]], sevenSegArray[score[3]]);
      delay(200);
      updateSevenSeg(0xFE, sevenSegArray[score[1]], sevenSegArray[score[2]], sevenSegArray[score[3]]);
      delay(200);

      break;
    case 2:
      updateSevenSeg(sevenSegArray[score[0]], 0xBF, sevenSegArray[score[2]], sevenSegArray[score[3]]);
      delay(200);
      updateSevenSeg(sevenSegArray[score[0]], 0xDF, sevenSegArray[score[2]], sevenSegArray[score[3]]);
      delay(200);
      updateSevenSeg(sevenSegArray[score[0]], 0xEF, sevenSegArray[score[2]], sevenSegArray[score[3]]);
      delay(200);
      updateSevenSeg(sevenSegArray[score[0]], 0xFB, sevenSegArray[score[2]], sevenSegArray[score[3]]);
      delay(200);
      updateSevenSeg(sevenSegArray[score[0]], 0xFD, sevenSegArray[score[2]], sevenSegArray[score[3]]);
      delay(200);
      updateSevenSeg(sevenSegArray[score[0]], 0xFE, sevenSegArray[score[2]], sevenSegArray[score[3]]);
      delay(200);
      break;
    case 3:
      updateSevenSeg(sevenSegArray[score[0]], sevenSegArray[score[1]], 0xBF, sevenSegArray[score[3]]);
      delay(200);
      updateSevenSeg(sevenSegArray[score[0]], sevenSegArray[score[1]], 0xDF, sevenSegArray[score[3]]);
      delay(200);
      updateSevenSeg(sevenSegArray[score[0]], sevenSegArray[score[1]], 0xEF, sevenSegArray[score[3]]);
      delay(200);
      updateSevenSeg(sevenSegArray[score[0]], sevenSegArray[score[1]], 0xFB, sevenSegArray[score[3]]);
      delay(200);
      updateSevenSeg(sevenSegArray[score[0]], sevenSegArray[score[1]], 0xFD, sevenSegArray[score[3]]);
      delay(200);
      updateSevenSeg(sevenSegArray[score[0]], sevenSegArray[score[1]], 0xFE, sevenSegArray[score[3]]);
      delay(200);
      break;
    case 4:
      updateSevenSeg(sevenSegArray[score[0]], sevenSegArray[score[1]], sevenSegArray[score[2]], 0xBF);
      delay(200);
      updateSevenSeg(sevenSegArray[score[0]], sevenSegArray[score[1]], sevenSegArray[score[2]], 0xDF);
      delay(200);
      updateSevenSeg(sevenSegArray[score[0]], sevenSegArray[score[1]], sevenSegArray[score[2]], 0xEF);
      delay(200);
      updateSevenSeg(sevenSegArray[score[0]], sevenSegArray[score[1]], sevenSegArray[score[2]], 0xFB);
      delay(200);
      updateSevenSeg(sevenSegArray[score[0]], sevenSegArray[score[1]], sevenSegArray[score[2]], 0xFD);
      delay(200);
      updateSevenSeg(sevenSegArray[score[0]], sevenSegArray[score[1]], sevenSegArray[score[2]], 0xFE);
      delay(200);
      break;
      }
}


void updateSevenSeg(byte A, byte B, byte C, byte D) {
  //update seven segment displays
  digitalWrite(latchPin, LOW);
  sevenSegD = D; //display 4
  sevenSegC = C; // display 3
  sevenSegB = B; //display 2
  sevenSegA = A; // display 1
  shiftOut(dataPin, clockPin, LSBFIRST, sevenSegD);
  shiftOut(dataPin, clockPin, LSBFIRST, sevenSegC);
  shiftOut(dataPin, clockPin, LSBFIRST, sevenSegB);
  shiftOut(dataPin, clockPin, LSBFIRST, sevenSegA);
  //latch pin to high - data done transmitting
  digitalWrite(latchPin, HIGH);
}

void updateLEDs(boolean A, boolean B, boolean C, boolean D) {
  //update LEDs
  if (A) {
    digitalWrite(LED[0], HIGH);
  } else {
    digitalWrite(LED[0], LOW);
  }
  if (B) {
    digitalWrite(LED[1], HIGH);
  } else {
    digitalWrite(LED[1], LOW);
  }
  if (C) {
    digitalWrite(LED[2], HIGH);
  } else {
    digitalWrite(LED[2], LOW);
  }
  if (D) {
    digitalWrite(LED[3], HIGH);
  } else {
    digitalWrite(LED[3], LOW);
  }
}

Final Product

The housing for the circuit was made with acrylic sheets. This way exact holes could be made using a laser cutter to fit the seven segment displays, LEDs, and buttons.

Final Game Board in the Dark

Game board in the dark.

I chose to not paint the acrylic so all the electronics were visible. The displays were especially fun in the dark.

Final Game Board in the Light

Tokens on the game board.

The great part about this project is that I had the freedom to create the user experience I wanted. If anyone wants to give this project a go, I recommend you switch things up and make it your own. Perhaps you want to use an LCD instead of seven segment displays. Go for it!

Yut Circuit in its Housing

Circuit in housing.

Regardless, I hope you give this game a try. I’d love to hear what you think and I hope you have as much fun as I did when I first played!


Comments 0 comments

Related Posts

Recent Posts

Why L-Band?

Tags


All Tags