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.
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.
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.
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.
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.
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.
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.
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.
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:
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.
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);
}
}
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.
I chose to not paint the acrylic so all the electronics were visible. The displays were especially fun in the dark.
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!
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!