Squid Games Doll build using Arduino UNO

Build a fully functional (non-violent) Squid Games Doll that plays red-light-green-light with you.

projectImage

Things used in this project

Hardware components

HARDWARE LIST
1 DFRobot DFPlayer - A Mini MP3 Player Voice
2 RGB Diffused Common Cathode Eyes
1 SparkFun 7-Segment Serial Display - Red Timer
1 Texas Instruments Shift Register- Parallel to Serial 4 digit 7 segment display
1 Alphanumeric LCD, 16 x 2 Menu display
1 IR receiver (generic) For remote control
1 Arduino UNO The brains
1 SG90 Micro-servo motor Rotate the head
1 PIR Motion Sensor (generic) Motion detection
1 Ultrasonic Sensor - HC-SR04 (Generic) Distance detection for win or lose
1 Pushbutton Switch, Momentary menu buttons and the start button

Software apps and online services

 

Arduino IDE

Hand tools and fabrication machines

 

3D Printer (generic)

Story

 

Built a fully functional squid games doll. She plays the red-light-green-light game with you. Complete with rotating head, colored eyes, and she talks! She uses ultra-sonic and motion detection to determine if you win or lose. But don't worry, if you lose she just asks if you want to play again.

Watch the video and let me know what you think.

I used every pin on the Arduino UNO! Which I've never done before so this was an achievement for myself. This project took me 3 weeks to build with 1 week dedicated entirely to printing! It took me 6 days to print this doll. 1 week for the build and another week to edit the video.

 

ELEGOO sent me a free UNO kit if I make them a video so this is why I built the doll. It was either this or build an escape room. I'm happy they chose this project. I hope people enjoy it because it was fun build that came out looking really nice and creeps out a bunch of people. But more importantly, it works.

 

Here are all the parts I will use for this build.

projectImage

1. Start Printing

 

Printing is going to take a long time. It took me 6 days to print the entire doll out. I also used different color filament so that I can reduce the amount of painting.

 

I remixed a model I found on thingiverse.com, hollowed out the center, and added access holes for the electronics. I also modified the chest plate for the Servo and Ultra Sonic to be mounted.

projectImage
projectImage
projectImage

2. Nobody like painting

 

Time to paint. I used generic spray paint for this. I painted the inside of the dolls head (masked off the eyes) so that the LEDs for the eyes will not make the entire face glow. Although this might be the effect you are looking for. I wanted just the eyes to glow.

projectImage
projectImage
projectImage

3. Magnets attract but glue sticks

 

One way to attach all of the doll's limbs is to melt magnets into the plastic. This is if you want to be able to take her apart. If I were to do this project again I would probably just glue all the limbs on her. As I see it now, there is little advantage to use magnets aside from she can fit into a smaller box for storage if you want. Only thing you should not attach is the head at this point.

projectImage

4. Easy on the eyes

 

Start with the easiest step, the eyes. I used tri-color LEDs for the eyes. As you know, you can mix and match RGB colors to get basic any color you'd like. I stuck with primary and secondary colors so I didn't have to PWM the signals. But you can if you are looking for that.

 

The longest pin is the ground, that will be pin 2.

 

Connect the LED as pictured using 220ohm resistors for each lead aside from the ground.

For mounting, I simply hot glued the LEDs as close to the center of the eyes as I could get but on the reverse side. Be sure to long enough wire to pass down the neck and into the lower part of her body.

projectImage
projectImage
projectImage
projectImage

5. LCD Menu

 

The next easiest component is the 16x2 LCD screen. You should use the LCD screen with an I2C adapter. It will make your life much easier and reduces the IO count from 6 to 2. Once this is connected, the LCD should startup with "Welcome to the squid games!" on the display.

 

For mounting, I printed out a 1mm thick circle. I make this thin so that I can mold it to the dolls back with a heat gun. This is much easier than figuring out the contours of her back ( at least for me ). I installed threaded inserts for the display with nuts on the revers side to secure the display and the display mount to the body.

projectImage
projectImage

6. Only owl heads rotate 180 degrees

 

The servo was difficult for one main reason, I don't use the servo library. I know that sounds weird but I had to use the timer1 for the 4 digit display update and the servo library also uses this. Luckily, the servo is either 0 degrees or 180 degrees and there is no in between making this a lot easier.

 

Timer1 is setup for.5ms intervals, 2000hz. The servo period is 20ms. At 0 degrees the pin only need to be high for 2 counts and low the rest of the period. For 180 degrees the pin needs to be high for 4 counts and low the rest of the time.

 

There is a nice mount on the chest plate for the servo. You can screw it into place or glue it into place. I used epoxy to secure the servo to the chest plate because it will also add strength to the chest plate and hopefully prevent it from damage.

projectImage
projectImage
projectImage

7. Sounds like a bat

 

Next we will install the ultra sonic distance module. I have this updating every 250ms. It also has a nice mounting location on the chest plate. There are only 2 wires for this module.

 

I used epoxy to mount the ultra sonic to the chest plate.

projectImage
projectImage
projectImage

8. No strings attached

 

The IR sensor for the remote is only needed if you want to control the game play. I thought this would be fun but don't really use this mode, automatic game play is fun enough.

 

I chose to mount the IR sensor inside a clip on the doll's hair. You can obviously choose to place it somewhere else. I was trying to hid it but maybe there is a better place because the IR doesn't always see the remote when she turns her head and the sensor is on the other side.

projectImage
projectImage

9. Time to Time

 

Next we will setup the timer display. This is a lot of work for a 4 digit display. I will include the connection diagram from ELEGOO. The game play is only up to 5 minutes so I also removed the use of the most significant digit. But you an decide to keep it if you have the IO pin available. To update the display you have to cycle the LED very quickly because you can only have one digit active at a time. This is why they seem to flicker when watched through a camera. I used a 2ms refresh rate which is fast enough that you can not see the flicker. At 5ms I can start to see it flicker when looking at the display in your peripheral vision. In addition, you will need the shift register 74HC595.

 

Mounting the display what not fun. I decided it was best to integrate the display into her belt. The original doll in Squid Games does not have a belt of course, but sacrifices had to be made to get this display on her. If you choose this route too, mask off a square the same size of the display then cut out with a Dremel. I then used epoxy putty to add a gradual transition to the display. But this was not needed, I just thought it looked better this way.

 

I mounted the 74HC595 to the prototype shield, otherwise you will have wires going all over the place. An alternative solution is to use a different timer display that has a more convenient communication with less pins.

projectImage
projectImage
projectImage

10. I saw you move

 

The motion detector is a weird little guy. This thing uses infrared to detect movement. One thing I learned is that this sensor needs time to warm up. On startup it needs 1 minute to warm up. That is why there is a 1 minute startup time for the doll. Another annoyance with this module is that the fastest it can update a movement detection is about 5 seconds. The last annoyance is how sensitive this sensor is. Even with the sensitivity turned all the way down, it still can see the smallest of movements and sometimes movement that I don't even know what it is talking about. To help prevent these "false positives" I mounted the sensor inside a horse blinder box. The box has a small hole (7mm) for the motion detector to look out. As a bonus, this prevents you from having to mount this giant sensor on the outside of the doll. The motion sensor only has one binary wire for feedback, motion or not.

 

To mount the sensor, I printed out the horse blinder and glued it to the inside of the doll. I then drilled a hole through the body. I used threaded insert on the blinder box to secure the motion sensor.

projectImage
projectImage
projectImage

11. Don't push my buttons

 

Finally, we are at the buttons. If you have the extra I/O pins, it is easier to connect each of these to a digital input. But I did not have this luxury for the UNO. Instead I had to use an analog input to read the resistor values to determine which button was being pressed. The values I used were, 1K, 2K, and 5K. Then I had a 220 Ohm resistor to pull the analog input low. Otherwise it will float and get random button presses.

 

I mounted the buttons on the same mounting plate as the LCD. This was not easy but I didn't have a better way. Soldering the wires onto these buttons then getting them to pass through little holes drilled in the plastic will test your patients.

projectImage
projectImage

12. Can you hear me now?

 

Last step and probably the most important is the sound module. This will use the serial port on the UNO so you must add 1K Ohm resistors to the Tx and Rx pins otherwise, you will get blocked from programming the UNO after this connection is made. In addition, you will need to use the "busy" pin so that the UNO knows that a sounds is already playing. This is very important if you have MP3s play back-to-back.

 

I mounted the MP3 player module on the prototype shield. This shield makes mounting components like this very convenient because it then just plugs into the UNO. This module will need an 8ohm speaker and has an output of 3W. The speaker was just glued down to the base of the doll. I drilled small holes under the speaker for the sound to come out better.

projectImage
projectImage

13. Mount the UNO

 

Install the UNO onto the platform and plug the prototype shield onto the UNO. Be sure that you have labeled all of the wires, if not you probably don't know where any of them go by now. With a little bit of negotiation, you can get the mounted UNO inside the doll with all the wires connected.

 

I used threaded inserts to mount the platform to the bottom of the doll.

projectImage
projectImage

14. Test Fix Test

 

This is when you get to put your debugging hat on. I can tell you the software is working on GitHub so at least that is one less thing to debug. But go ahead anyway if you have doubts and send me any updates you find.

projectImage

15. Let's play

 

Time to test her out and play a game. Here is how the game is programmed.

 

On startup she turns her head forward.

 

The motion sensor take a full minute to startup. So there is a timer when it starts. Half way through she giggles and turns her head around. Then announces when she is ready.

 

Depending on if you have the game set to remote she says different things. In Auto mode she asks you to press the play button. In my case, this is the far right button. In remote mode she will ask you to press the power button when you are ready. Then press the play button to toggle to red light or green light.

 

So when you are ready, press the go button and she will give you 10 seconds to get in place. Usually someone else nearby will press this button.

 

Then the game begins. She will start with Green light. For green light you have to get within 50cm to trigger a win. If you are within 100cm she will say indicate that you are getting closer. Green light is only using the sonar.

 

For red light the motion sensor and the distance sensor is being used. If you move enough for the motion sensor to trip or if you move more than 10cm forward, you will loose the game. You will also loose the game if time runs out. She will remind you that time is almost out at 5 seconds left.

 

The last cool feature is that she will also speak in the Korean voice for the red light. This is a menu feature. Press the far left button to toggle the menu item, and the center button to toggle the item options.

 

16. Watch Video

 

This video took me a long time to edit. I have probably 30 hours in just editing. But it was fun making it. I think it came out good and is funny but want you to see for yourself. Please let me know what you think and if you have any questions.

Schematics

 

Wire diagram

This is how I connected all of the components to the UNO.

projectImage

The project repo

All of the files for this build are stored here.

 

https://github.com/CodeMakesItGo/SquidGamesDoll

Code

 

Squid Game Doll Sketch

C/C++

This will control all of the sensor and the game logic

CODE
/// CodeMakesItGo Dec 2021

#include <DFPlayerMini_Fast.h>
#include <FireTimer.h>
#include <IRremote.h>
#include <LiquidCrystal_I2C.h>
#include <SoftwareSerial.h>
#include <SR04.h>
#include <Wire.h>

/*-----( Analog Pins )-----*/
#define BUTTONS_IN A0
#define SONAR_TRIG_PIN A1
#define SONAR_ECHO_PIN A2
#define MOTION_IN A3

/*-----( Digital Pins )-----*/
#define LED_BLUE 13
#define LED_GREEN 12
#define LED_RED 11
#define SEGMENT_DATA 10 // DS
#define SEGMENT_CLOCK 9 // SHCP
#define SEGMENT_LATCH 8 // STCP
#define SEGMENT_1_OUT 7
#define SEGMENT_2_OUT 6
#define SEGMENT_3_OUT 5
#define IR_DIGITAL_IN 4 // IR Remote
#define SERVO_OUT 3
#define DFPLAYER_BUSY_IN 2

/*-----( Configuration )-----*/
#define TIMER_FREQUENCY 2000
#define TIMER_MATCH (int)(((16E+6) / (TIMER_FREQUENCY * 64.0)) - 1)
#define TIMER_2MS ((TIMER_FREQUENCY / 1000) * 2)
#define VOLUME 30                    // 0-30
#define BETTER_HURRY_S 5             // play clip at 5 seconds left
#define WIN_PROXIMITY_CM 50          // cm distance for winner
#define CLOSE_PROXIMITY_CM 100       // cm distance for close to winning
#define GREEN_LIGHT_MS 3000          // 3 seconds on for green light
#define RED_LIGHT_MS 5000            // 5 seconds on for green light
#define WAIT_FOR_STOP_MOTION_MS 5000 // 5 seconds to wait for motion detection to stop

/*-----( Global Variables )-----*/
static unsigned int timer_1000ms = 0;
static unsigned int timer_2ms = 0;
static unsigned char digit = 0;      // digit for 4 segment display
static int countDown = 60;           // Start 1 minute countdown on startup
static const int sonarVariance = 10; // detect movement if greater than this
static bool gameInPlay = false;
static bool faceTree = false;
static bool remotePlay = false;

//                                 0 ,   1,   2,   3,   4,   5,   6,   7,   8,   9,   A,   B,   C,   D,   E,   F, NULL
const unsigned char numbers[] = {0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07, 0x7f, 0x6f, 0x77, 0x7c, 0x39, 0x5e, 0x79, 0x71, 0x00};

const char *MenuItems[] = {"Language", "Play Time", "Play Type"};
typedef enum
{
  LANGUAGE,
  PLAYTIME,
  PLAYTYPE,
  MENUITEM_COUNT
} MenuItemTypes;

const char *Languages[] = {"English", "Korean"};
typedef enum
{
  ENGLISH,
  KOREAN,
  LANUAGE_COUNT
} LanguageTypes;
static int language = 0;

const char *PlayTime[] = {"300", "240", "180", "120", "60", "30", "15"};
typedef enum
{
  PT300,
  PT240,
  PT180,
  PT120,
  PT60,
  PT30,
  PT15,
  PLAYTIME_COUNT
} PlayTimeTypes;
const int playTimes[] = {300, 240, 180, 120, 60, 30, 15};
static int playTime = 0;

const char *PlayType[] = {"Auto", "Remote"};
typedef enum
{
  AUTO,
  REMOTE,
  PLAYTYPE_COUNT
} PlayTypeTypes;
static int playType = 0;

typedef enum
{
  BLACK,
  RED,
  GREEN,
  BLUE,
  WHITE,
  YELLOW,
  PURPLE
} EyeColors;
EyeColors eyeColor = BLACK;

typedef enum
{
  WARMUP,
  WAIT,
  READY,
  GREENLIGHT,
  REDLIGHT,
  WIN,
  LOSE
} GameStates;
static GameStates gameState = WARMUP;

/*-----( Class Objects )-----*/
FireTimer task_50ms;
FireTimer task_250ms;
DFPlayerMini_Fast dfPlayer;
SR04 sonar = SR04(SONAR_ECHO_PIN, SONAR_TRIG_PIN);
IRrecv irRecv(IR_DIGITAL_IN);
decode_results irResults;
LiquidCrystal_I2C lcdDisplay(0x27, 16, 2); // 16x2 LCD display

/*-----( Functions )-----*/
void translateIR() // takes action based on IR code received
{
  switch (irResults.value)
  {
  case 0xFFA25D:
    Serial.println("POWER");
    if (gameState == WAIT)
    {
      gameInPlay = true;
    }
    break;
  case 0xFFE21D:
    Serial.println("FUNC/STOP");
    break;
  case 0xFF629D:
    Serial.println("VOL+");
    break;
  case 0xFF22DD:
    Serial.println("FAST BACK");
    break;
  case 0xFF02FD:
    Serial.println("PAUSE");
    remotePlay = !remotePlay;
    break;
  case 0xFFC23D:
    Serial.println("FAST FORWARD");
    break;
  case 0xFFE01F:
    Serial.println("DOWN");
    break;
  case 0xFFA857:
    Serial.println("VOL-");
    break;
  case 0xFF906F:
    Serial.println("UP");
    break;
  case 0xFF9867:
    Serial.println("EQ");
    break;
  case 0xFFB04F:
    Serial.println("ST/REPT");
    break;
  case 0xFF6897:
    Serial.println("0");
    break;
  case 0xFF30CF:
    Serial.println("1");
    break;
  case 0xFF18E7:
    Serial.println("2");
    break;
  case 0xFF7A85:
    Serial.println("3");
    break;
  case 0xFF10EF:
    Serial.println("4");
    break;
  case 0xFF38C7:
    Serial.println("5");
    break;
  case 0xFF5AA5:
    Serial.println("6");
    break;
  case 0xFF42BD:
    Serial.println("7");
    break;
  case 0xFF4AB5:
    Serial.println("8");
    break;
  case 0xFF52AD:
    Serial.println("9");
    break;
  case 0xFFFFFFFF:
    Serial.println(" REPEAT");
    break;

  default:
    Serial.println(" other button   ");
  }
}

bool isPlayingSound()
{
  return (digitalRead(DFPLAYER_BUSY_IN) == LOW);
}

void updateTimeDisplay(unsigned char digit, unsigned char num)
{
  digitalWrite(SEGMENT_LATCH, LOW);
  shiftOut(SEGMENT_DATA, SEGMENT_CLOCK, MSBFIRST, numbers[num]);

  // Active LOW
  digitalWrite(SEGMENT_1_OUT, digit == 1 ? LOW : HIGH);
  digitalWrite(SEGMENT_2_OUT, digit == 2 ? LOW : HIGH);
  digitalWrite(SEGMENT_3_OUT, digit == 3 ? LOW : HIGH);

  digitalWrite(SEGMENT_LATCH, HIGH);
}

void updateServoPosition()
{
  static int servoPulseCount = 0;
  static bool lastPosition = false;

  // Only get new value at start of period
  if (servoPulseCount == 0)
    lastPosition = faceTree;

  if (!lastPosition) // 180 degrees
  {
    digitalWrite(SERVO_OUT, servoPulseCount < 5 ? HIGH : LOW);
  }
  else // 0 degrees
  {
    digitalWrite(SERVO_OUT, servoPulseCount < 1 ? HIGH : LOW);
  }

  servoPulseCount = (servoPulseCount + 1) % 40; // 20ms period
}

void updateMenuDisplay(const int button)
{
  static int menuItem = 0;
  static int menuOption = 0;
  switch (button)
  {
  case 1:
    menuItem = (menuItem + 1) % MENUITEM_COUNT;
    if (menuItem == LANGUAGE)
    {
      menuOption = language;
    }
    else if (menuItem == PLAYTIME)
    {
      menuOption = playTime;
    }
    else if (menuItem == PLAYTYPE)
    {
      menuOption = playType;
    }
    else
    {
      menuOption = 0;
    }
    break;
  case 2:
    if (menuItem == LANGUAGE)
    {
      menuOption = (menuOption + 1) % LANUAGE_COUNT;
      language = menuOption;
    }
    else if (menuItem == PLAYTIME)
    {
      menuOption = (menuOption + 1) % PLAYTIME_COUNT;
      playTime = menuOption;
    }
    else if (menuItem == PLAYTYPE)
    {
      menuOption = (menuOption + 1) % PLAYTYPE_COUNT;
      playType = menuOption;
    }
    else
    {
      menuOption = 0;
    }
    break;
  case 3:
    if (gameState == WAIT)
    {
      gameInPlay = true;
    }
    if (gameState == GREENLIGHT || gameState == REDLIGHT)
    {
      gameInPlay = false;
    }
  default:
    break;
  }

  if (menuOption != -1)
  {
    lcdDisplay.clear();

    lcdDisplay.setCursor(0, 0);
    lcdDisplay.print(MenuItems[menuItem]);
    lcdDisplay.setCursor(0, 1);

    if (menuItem == LANGUAGE)
    {
      lcdDisplay.print(Languages[menuOption]);
    }
    else if (menuItem == PLAYTIME)
    {
      lcdDisplay.print(PlayTime[menuOption]);
    }
    else if (menuItem == PLAYTYPE)
    {
      lcdDisplay.print(PlayType[menuOption]);
    }
    else
    {
      lcdDisplay.print("unknown option");
    }
  }
  else
  {
    menuItem = 0;
    menuOption = 0;
  }
}

void handleButtons()
{
  static int buttonPressed = 0;
  int value = analogRead(BUTTONS_IN);

  if (value < 600) // buttons released
  {
    if (buttonPressed != 0)
      updateMenuDisplay(buttonPressed);

    buttonPressed = 0;
    return;
  }
  else if (value < 700)
  {
    Serial.println("button 1");
    buttonPressed = 1;
  }
  else if (value < 900)
  {
    Serial.println("button 2");
    buttonPressed = 2;
  }
  else if (value < 1000)
  {
    Serial.println("button 3");
    buttonPressed = 3;
  }
  else
  {
    Serial.println(value);
    buttonPressed = 0;
  }
}

static int lastSonarValue = 0;
void handleSonar()
{
  int value = sonar.Distance();

  if (value > lastSonarValue + sonarVariance ||
      value < lastSonarValue - sonarVariance)
  {
    Serial.println(value);
    lastSonarValue = value;
  }
}

static int lastMotion = 0;
void handleMotion()
{
  int value = digitalRead(MOTION_IN);

  if (value != lastMotion)
  {
    lastMotion = value;
  }

  if (lastMotion)
    Serial.println("Motion Detected");
}

void handleLeds()
{
  digitalWrite(LED_RED, eyeColor == RED || eyeColor == WHITE || eyeColor == PURPLE || eyeColor == YELLOW ? HIGH : LOW);
  digitalWrite(LED_GREEN, eyeColor == GREEN || eyeColor == WHITE || eyeColor == YELLOW ? HIGH : LOW);
  digitalWrite(LED_BLUE, eyeColor == BLUE || eyeColor == WHITE || eyeColor == PURPLE ? HIGH : LOW);
}

void handleRemote()
{
  // have we received an IR signal?
  if (irRecv.decode(&irResults))
  {
    translateIR();
    irRecv.resume(); // receive the next value
  }
}

// Timer 1 ISR
ISR(TIMER1_COMPA_vect)
{
  // Allow this ISR to be interrupted
  sei();

  updateServoPosition();

  if (timer_1000ms++ == TIMER_FREQUENCY)
  {
    timer_1000ms = 0;
    countDown--;
    if (countDown < 0)
    {
      countDown = 0;
    }
  }

  if (timer_2ms++ == TIMER_2MS)
  {
    timer_2ms = 0;
    if (digit == 0)
      updateTimeDisplay(1, countDown % 10);
    if (digit == 1)
      updateTimeDisplay(2, (countDown / 10) % 10);
    if (digit == 2)
      updateTimeDisplay(3, (countDown / 100) % 10);
    if (digit == 3)
      updateTimeDisplay(4, 16);

    digit = ((digit + 1) % 4);
  }
}

void playGame()
{
  static int sequence = 0;
  static long internalTimer = millis();
  static bool closerClipPlayed = false;
  static bool hurryUpClipPlayed = false;
  static int captureDistance = 0;
  long currentTimer = internalTimer;

  if(isPlayingSound()) return;

  if (gameState == WARMUP)
  {
    // power up sound
    if (sequence == 0)
    {
      Serial.println("Warming Up");
      dfPlayer.playFolder(1, 1);
      faceTree = false;
      eyeColor = YELLOW;
      sequence++;
    }

    // laugh at 30
    else if (sequence == 1 && countDown <= 30)
    {
      Serial.println("Laughing");
      dfPlayer.playFolder(1, 2);
      faceTree = true;
      sequence++;
    }

    else if (sequence == 2 && countDown <= 10)
    {
      Serial.println("Almost ready");
      dfPlayer.playFolder(1, 3);
      sequence++;
    }

    else if (sequence == 3 && countDown == 0)
    {
      Serial.println("All ready, lets play");
      dfPlayer.playFolder(1, 4);
      faceTree = false;
      sequence = 0;
      gameState = WAIT;
      gameInPlay = false;
    }
  }

  else if (gameState == WAIT)
  {
    currentTimer = millis();

    if (gameInPlay)
    {
      gameState = READY;
      remotePlay = false;
      sequence = 0;
    }

    // Every 30 seconds
    else if (currentTimer - internalTimer > 30000 ||
             sequence == 0)
    {
      internalTimer = millis();

      if(playType == AUTO)
      {
        // press the go button when you are ready
        Serial.println("Press the go button when you are ready");
        dfPlayer.playFolder(1, 5);
      }
      else
      {
        Serial.println("Press the power button on the remote when you are ready");
        dfPlayer.playFolder(1, 6);
      }

      // eyes are blue
      eyeColor = BLUE;

      // facing players
      faceTree = false;

      gameInPlay = false;

      sequence++;
    }
  }

  else if (gameState == READY)
  {
    currentTimer = millis();

    if (sequence == 0)
    {
      // get in position, game will start in 10 seconds
      Serial.println("Get in position.");
      dfPlayer.playFolder(1, 7);
      countDown = 10;

      // eyes are green
      eyeColor = WHITE;

      // facing players
      faceTree = false;

      sequence++;

      internalTimer = millis();
    }
    else if (sequence == 1)
    {
      if (playType == REMOTE)
      {
        if (remotePlay)
          sequence++;
      }
      else
        sequence++;
    }
    else if (sequence == 2)
    {
      // at 0 seconds, here we go!
      if (countDown == 0)
      {
        countDown = playTimes[playTime];
        Serial.print("play time set to ");
        Serial.println(countDown);

        Serial.println("Here we go!");
        dfPlayer.playFolder(1, 8);
        gameState = GREENLIGHT;
        sequence = 0;
      }
    }
  }

  else if (gameState == GREENLIGHT)
  {
    currentTimer = millis();

    if (sequence == 0)
    {
      // eyes are green
      eyeColor = GREEN;

      // play green light
      Serial.println("Green Light!");
      dfPlayer.playFolder(1, 9);
      
      sequence++;
    }

    else if(sequence == 1)
    {
      // play motor sound
      dfPlayer.playFolder(1, 19);
      
      // facing tree
      faceTree = true;

      sequence++;

      internalTimer = millis();
    }

    else if (sequence == 2)
    {
      // wait 3 seconds or until remote
      // switch to red light
      if (playType == AUTO && currentTimer - internalTimer > GREEN_LIGHT_MS)
      {
        sequence = 0;
        gameState = REDLIGHT;
      }
      else if (playType == REMOTE && remotePlay == false)
      {
        sequence = 0;
        gameState = REDLIGHT;
      }
      else
      {
        // look for winner button or distance
        if (gameInPlay == false ||
            lastSonarValue < WIN_PROXIMITY_CM)
        {
          sequence = 0;
          gameState = WIN;
        }

        else if (countDown <= 0)
        {
          Serial.println("Out of Time");
          dfPlayer.playFolder(1, 16);
          sequence = 0;
          gameState = LOSE;
        }
        
        // at 2 meters play "your getting closer"
        else if (lastSonarValue < CLOSE_PROXIMITY_CM &&
            closerClipPlayed == false)
        {
          Serial.println("Getting closer!");
          dfPlayer.playFolder(1, 11);
          closerClipPlayed = true;
        }

        // if less than 5 seconds play better hurry
        else if (countDown <= BETTER_HURRY_S &&
            hurryUpClipPlayed == false)
        {
          Serial.println("Better Hurry");
          dfPlayer.playFolder(1, 12);
          hurryUpClipPlayed = true;
        }
      }
    }
  }

  else if (gameState == REDLIGHT)
  {
    currentTimer = millis();

    if (sequence == 0)
    {
      // eyes are red
      eyeColor = RED;

      Serial.println("Red Light!");

      if(language == ENGLISH)
      {
        // play red light English 
        dfPlayer.playFolder(1, 10);
      }
      else
      {
        // play red light Korean  
        dfPlayer.playFolder(1, 18);
      }

      sequence++;
    }

    else if(sequence == 1)
    {
      // play motor sound
      dfPlayer.playFolder(1, 19);

      // facing players
      faceTree = false;

      // Save current distance
      captureDistance = lastSonarValue;

      sequence++;

      internalTimer = millis();
    }

    else if (sequence == 2)
    {
      //wait for motion to settle
      if (lastMotion == 0 || (currentTimer - internalTimer) > WAIT_FOR_STOP_MOTION_MS)
      {
        internalTimer = millis();
        sequence++;
        Serial.println("Done settling");
      }
      Serial.println("Waiting to settle");
    }

    else if (sequence == 3)
    {
      // back to green after 5 seconds
      if (playType == AUTO && currentTimer - internalTimer > RED_LIGHT_MS)
      {
        sequence = 0;
        gameState = GREENLIGHT;
      }
      else if (playType == REMOTE && remotePlay == true)
      {
        sequence = 0;
        gameState = GREENLIGHT;
      }

      else
      {
        // can't push the button while red light
        // detect movement
        // detect distance change
        if (gameInPlay == false ||
            lastMotion == 1 ||
            lastSonarValue < captureDistance)
        {
          Serial.println("Movement detected!");
          dfPlayer.playFolder(1, 15);
          sequence = 0;
          gameState = LOSE;
        }

        if (countDown == 0)
        {
          Serial.println("Out of time");
          dfPlayer.playFolder(1, 16);
          sequence = 0;
          gameState = LOSE;
        }
      }
    }
  }

  else if (gameState == WIN)
  {
    if (sequence == 0)
    {
      // play winner sound
      Serial.println("You Won!");
      dfPlayer.playFolder(1, 13);

      // eyes are white
      eyeColor = WHITE;

      // facing players
      faceTree = false;

      sequence++;
    }

    else if (sequence == 1)
    {
      // wanna play again?
      Serial.println("Play Again?");
      dfPlayer.playFolder(1, 17);

      gameInPlay = false;
      countDown = 0;

      // go to wait
      gameState = WAIT;
      sequence = 0;
    }
  }

  else if (gameState == LOSE)
  {
    if (sequence == 0)
    {
      // sorry better luck next time
      Serial.println("Sorry, you lost");
      dfPlayer.playFolder(1, 14);

      // eyes are purple
      eyeColor = PURPLE;

      // face players
      faceTree = false;

      sequence++;
    }

    else if (sequence == 1)
    {
      // wanna play again?
      Serial.println("Play Again?");
      dfPlayer.playFolder(1, 17);

      gameInPlay = false;
      countDown = 0;

      // go to wait
      gameState = WAIT;
      sequence = 0;
    }
  }
  else
  {
    //Shouldn't ever get here
    gameState = WARMUP;
  }
}

void loop() /*----( LOOP: RUNS CONSTANTLY )----*/
{
  if (task_50ms.fire())
  {
    handleRemote();
    handleButtons();
  }

  if (task_250ms.fire())
  {
    handleSonar();
    handleMotion();
    handleLeds();
    playGame();

    Serial.println(isPlayingSound());
  }
}

// Setup Timer 1 for 2000Hz
void setupTimer()
{
  cli();      //stop interrupts
  TCCR1A = 0; // set entire TCCR1A register to 0
  TCCR1B = 0; // same for TCCR1B
  TCNT1 = 0;  //initialize counter value to 0
  // set compare match register
  OCR1A = TIMER_MATCH; // = (16*10^6) / (2000*64) - 1 (must be <65536), 2000Hz
  // turn on CTC mode
  TCCR1B |= (1 << WGM12);
  // Set CS11 and CS10 bits for 64 prescaler
  TCCR1B |= (1 << CS11) | (1 << CS10);
  // enable timer compare interrupt
  TIMSK1 |= (1 << OCIE1A);
  sei(); //allow interrupts
}

void setup()
{
  Serial.begin(9600);

  pinMode(MOTION_IN, INPUT);
  pinMode(BUTTONS_IN, INPUT);
  pinMode(DFPLAYER_BUSY_IN, INPUT);

  pinMode(SERVO_OUT, OUTPUT);
  pinMode(LED_RED, OUTPUT);
  pinMode(LED_GREEN, OUTPUT);
  pinMode(LED_BLUE, OUTPUT);

  pinMode(SEGMENT_LATCH, OUTPUT);
  pinMode(SEGMENT_CLOCK, OUTPUT);
  pinMode(SEGMENT_DATA, OUTPUT);

  pinMode(SEGMENT_1_OUT, OUTPUT);
  pinMode(SEGMENT_2_OUT, OUTPUT);
  pinMode(SEGMENT_3_OUT, OUTPUT);

  irRecv.enableIRIn();     // Start the receiver
  dfPlayer.begin(Serial);  // Use the standard serial stream for DfPlayer
  dfPlayer.volume(VOLUME); // Set the DfPlay volume
  lcdDisplay.init();       // initialize the lcd
  lcdDisplay.backlight();  // Turn on backlight
  setupTimer();            // Start the high resolution timer ISR

  // Display welcome message
  lcdDisplay.setCursor(0, 0);
  lcdDisplay.print("Welcome to the");
  lcdDisplay.setCursor(0, 1);
  lcdDisplay.print("Squid Games!");

  // short delay to display welcome screen 
  delay(1000);

  task_50ms.begin(50);   // Start the 50ms timer task
  task_250ms.begin(250); // Start the 250ms timer task
}

The article was first published in hackster, December 7, 2021

cr: https://www.hackster.io/codemakesitgo/squid-games-doll-build-using-arduino-uno-b2984c

author: codemakesitgo

License
All Rights
Reserved
licensBg
0