John Bradnam
Published © GPL3+

Tacoyaki (Lights Out) Game

Play three variants of the game Tacoyaki (also known as Lights Out). Full instructions and code included.

IntermediateFull instructions provided8 hours2,895
Tacoyaki (Lights Out) Game

Things used in this project

Hardware components

Arduino Pro Mini 328 - 5V/16MHz
SparkFun Arduino Pro Mini 328 - 5V/16MHz
×1
MAX7219 24 Pin DIL IC
×1
LED White 1210 SMD
×16
12mm x 12mm Tactile switch with button cap
×16
2 Digit 7 Segment 0.56in Common Cathode Display
×1
Buzzer
Buzzer
×1
10uF Ceramic capacitor 1206 SMD
×1
12 pin machined header for IC socket
×2

Software apps and online services

Arduino IDE
Arduino IDE

Hand tools and fabrication machines

3D Printer (generic)
3D Printer (generic)

Story

Read more

Custom parts and enclosures

Case - Top

Case - Bottom

Schematics

Schematic

PCB

Eagle Files

Schematic and PCB layout in Eagle Format

Code

TacoyakiV2.ino

C/C++
#include "Display.h"
#include "Buttons.h"
#include "Music.h"

// Define Sound pins
#define SPEAKER 10

// LED display
#define DATA 11
#define CLOCK 13
#define LOAD 12

// Switches
#define COL0 2
#define COL1 3
#define COL2 4
#define COL3 5
#define ROW0 6
#define ROW1 7
#define ROW2 8
#define ROW3 9

// Noise pin
#define RANDOM_SEED_PIN A0

#define TOTAL_LEVELS 3
#define MAX_TRIES 25
enum LevelEnum { NOT_USED, TOYAKI_PLUS, TOYAKI_PLUS_WRAP, TOYAKI };
LevelEnum currentLevel = TOYAKI_PLUS;
int totalTries = 0;

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

  randomSeed(analogRead(RANDOM_SEED_PIN));
  
  //Setup display
  initDisplay(DATA, CLOCK, LOAD);
  //Setup switches
  initButtons(ROW0, ROW1, ROW2, ROW3, COL0, COL1, COL2, COL3);
  //Setup buzzer
  initMusic(SPEAKER);
}

void loop()
{
  //Display current level
  showLevel((int)currentLevel);

  //Light up LEDs the user can select for the level  
  uint16_t flashMask = 0;
  for (int i = 0; i < TOTAL_LEVELS; i++)
  {
    flashMask = flashMask | (0x0001 << i);
  }
  uint16_t buttonOnStates = flashMask;
  showLedStates(buttonOnStates);
  
  //Wait until button 0, 1, 2, 3 is pressed.
  #define FLASH_PERIOD 200
  unsigned long flashTime = millis() + FLASH_PERIOD;
  uint16_t button = NO_BUTTON;
  while (button == NO_BUTTON || button >= TOTAL_LEVELS)
  {
    button = buttonPressed();
    delay(10);
    if (millis() > flashTime)
    {
      buttonOnStates = buttonOnStates ^ flashMask;
      showLedStates(buttonOnStates);
      flashTime = millis() + FLASH_PERIOD;
    }
  }
  currentLevel = (LevelEnum)(button + 1);
  showLevel((int)currentLevel);
  delay(500);
  
  //Start by creating a random board.
  buttonOnStates = 0;
  uint16_t nextMove = 0;
  for (int i = 0; i < 16; i++)
  {
    buttonOnStates = buttonOnStates ^ getChangesForNextMove(1 << (random(16)));
  }
  Serial.println("buttonOnStates: " + String(buttonOnStates, 16));
  showLedStates(buttonOnStates);

  //Play game until solved or tries is MAX_TRIES
  totalTries = 0;
  do
  {
    uint16_t buttonMask = 0;
    while (buttonMask == 0)
    {
      buttonMask = maskPressed();
      delay(10);
    }
    Serial.println("buttonMask: " + String(buttonMask, 16));
    buttonOnStates = buttonOnStates ^ getChangesForNextMove(buttonMask);
    Serial.println("buttonOnStates: " + String(buttonOnStates, 16));
    showLedStates(buttonOnStates);
    totalTries++;
    showValue(totalTries, true);
    playTurnTone();
    delay(100);
  }
  while (buttonOnStates != 0 && totalTries < MAX_TRIES);

  if (totalTries == MAX_TRIES)
  {
    //Lose game
    flashValue(totalTries, 10, 200);
    playLoseMusic();
  }
  else
  {
    //Display win
    buttonOnStates = 0x5A5A;
    showLedStates(buttonOnStates);
    playWinMusic();
    for (int i = 0; i < 10; i++) 
    {
      showValue(totalTries, (i & 1) == 0);
      buttonOnStates = buttonOnStates ^ 0xFFFF;
      showLedStates(buttonOnStates);
      delay(300);
    }
  }
  clearLeds();
}

//------------------------------------------------------------------------------------------------------------------

//Return a mask for the LEDS that need to change from the given button mask
uint16_t getChangesForNextMove(uint16_t button)
{
  uint16_t changes = 0;  

  switch(currentLevel)
  {
    case TOYAKI_PLUS: changes = moveToyakiPlus(button); break;
    case TOYAKI_PLUS_WRAP: changes = moveToyakiPlusWrap(button); break;
    case TOYAKI: changes = moveToyaki(button); break;
  }
  return changes;
}

//------------------------------------------------------------------------------------------------------------------
// Calculate next ToyakiPlus position
// button - mask of button pressed
// returns - mask of leds to change
//
//Switches and LED masks
//0001 0002 0004 0008
//0010 0020 0040 0080
//0100 0200 0400 0800
//1000 2000 4000 8000
//UP - X >> 4
//DN - X << 4
//<- - X >> 1 & 0x7777; 
//-> - X << 1 & 0xEEEE;
uint16_t moveToyakiPlus(uint16_t button)
{
  uint16_t up = button >> 4;
  uint16_t dn = button << 4;
  uint16_t le = (button >> 1) & 0x7777;
  uint16_t rg = (button << 1) & 0xEEEE;
  return (button | up | dn | le | rg);
}

//------------------------------------------------------------------------------------------------------------------
// Calculate next ToyakiPlusWrap position
// button - mask of button pressed
// returns - mask of leds to change
//
//Switches and LED masks
//0001 0002 0004 0008
//0010 0020 0040 0080
//0100 0200 0400 0800
//1000 2000 4000 8000
uint16_t moveToyakiPlusWrap(uint16_t button)
{
  uint16_t up = (button < 0x0010) ? button << 12 : button >> 4;
  uint16_t dn = (button >= 0x1000) ? button >> 12 : button << 4;
  uint16_t le = (button & 0x1111) ? button << 3 : (button >> 1) & 0x7777;
  uint16_t rg = (button & 0x8888) ? button >> 3 : (button << 1) & 0xEEEE;
  return (button | up | dn | le | rg);
}

//------------------------------------------------------------------------------------------------------------------
// Calculate next Toyaki position
// button - mask of button pressed
// returns - mask of leds to change
//
//Switches and LED masks
//0001 0002 0004 0008
//0010 0020 0040 0080
//0100 0200 0400 0800
//1000 2000 4000 8000
uint16_t moveToyaki(uint16_t button)
{
  uint16_t mask = button;
  uint16_t nw = button, ne = button, se = button, sw = button;
  for (int i = 0; i < 3; i++) 
  {
    nw = (nw & 0xEEEE) >> 5; mask |= nw;
    ne = (ne & 0x7777) >> 3; mask |= ne;
    se = (se & 0x7777) << 5; mask |= se;
    sw = (sw & 0xEEEE) << 3; mask |= sw;
  }
  return mask;
}

Buttons.h

C/C++
#pragma once

#define NO_BUTTON -1

uint16_t _switchStates = 0;
int _nextColToScan = 0;
int _nextRowToScan = 0;
uint8_t _colPins[4];
uint8_t _rowPins[4];

//Initialse button routines
//Buttons numbered top to bottom, left to right
//row0, col0 is button 0
//row3, col3 is button 15
void initButtons(uint8_t row0,uint8_t row1,uint8_t row2,uint8_t row3,uint8_t col0,uint8_t col1,uint8_t col2,uint8_t col3);

//Tests if any of the buttons have been pressed and released
//  returns the button that was pressed or -1
int buttonPressed();

//Tests if any of the buttons have been pressed and released
//  returns the mask of button that was pressed or 0
uint16_t maskPressed();

//------------------------------------------------------------------------------------------------------------------

//Initialse button routines
//Buttons numbered top to bottom, left to right
//row0, col0 is button 0
//row3, col3 is button 15
void initButtons(uint8_t row0,uint8_t row1,uint8_t row2,uint8_t row3,uint8_t col0,uint8_t col1,uint8_t col2,uint8_t col3)
{
  _colPins[0] = col0;
  _colPins[1] = col1;
  _colPins[2] = col2;
  _colPins[3] = col3;
  _rowPins[0] = row0;
  _rowPins[1] = row1;
  _rowPins[2] = row2;
  _rowPins[3] = row3;

  //Setup switches
  for (int i = 0; i < 4; i++)
  {
    pinMode(_colPins[i], INPUT);
    pinMode(_rowPins[i], OUTPUT);
    digitalWrite(_rowPins[i], LOW);
  }

  // Switches
  // Delay between each interrupt - eg: 1 sec ==> (16*10^6) / (1*1024) - 1 (must be <65536) = 15640
  // 1mS - 15.640
  // 5mS - 78.2
  #define SCAN_SPEED 78

  // Set up timer for background switch scanning
  cli();  //stop interrupts
  //set timer1 interrupt for flash
  TCCR1A = 0;// set entire TCCR1A register to 0
  TCCR1B = 0;// same for TCCR1B
  TCNT1  = 0;//initialize counter value to 0
  // set compare match register for 1hz increments
  OCR1A = SCAN_SPEED;
  // turn on CTC mode
  TCCR1B |= (1 << WGM12);
  // Set CS10 and CS12 bits for 1024 prescaler
  TCCR1B |= (1 << CS12) | (1 << CS10);  
  // enable timer compare interrupt
  TIMSK1 |= (1 << OCIE1A);    
  // allow interrupts
  sei();
  
}

//------------------------------------------------------------------------------------------------------------------

//timer1 interrupt for switch scanning 5mS
//Each iteration of the timer tests a single row and column. The state
//of all switches are held in switchStates.
ISR(TIMER1_COMPA_vect)
{
  //Work out which bit in switchStates reflect this row and column
  uint16_t mask = (0x0001 << _nextColToScan) << (_nextRowToScan << 2);

  //Set the row pin high
  digitalWrite(_rowPins[_nextRowToScan], HIGH);
  //Read the column pin
  if (digitalRead(_colPins[_nextColToScan]) == HIGH)
  {
    //Pressed - Set the bit that matches this button
    _switchStates = _switchStates | mask;
  }
  else
  {
    //Not pressed - Clear the bit that matches this button
    _switchStates = _switchStates & ~mask;
  }
  //Set the row pin low again
  digitalWrite(_rowPins[_nextRowToScan], LOW);

  //Update the row and column counters for next timer interrupt
  _nextColToScan = (_nextColToScan + 1) & 0x03;
  if (_nextColToScan == 0)
  {
    _nextRowToScan = (_nextRowToScan + 1) & 0x03;
  }
}

//------------------------------------------------------------------------------------------------------------------

//Tests if any of the buttons have been pressed and released
//  returns the button that was pressed or -1
int buttonPressed()
{
  int button = NO_BUTTON;   //Result store
  uint16_t mask = 0x0001;   //Mask for switchStates
  int btn = 0;              //Button/LED button number
  //Cycle through all 16 buttons
  while (btn < 16 && button == NO_BUTTON)
  {
    //Test if pressed
    if (_switchStates & mask)
    {
      //Delay for debouncing
      _delay_ms(10);
      //Test if it is still pressed otherwise it is a bounce
      if (_switchStates & mask)
      {
        //Wait until button is released
        while (_switchStates & mask)
        {
          _delay_ms(50);
        }
        //We have a valid button press
        button = btn;
      }
    }
    //Set up for next button to test
    mask = mask << 1;
    btn++;
  }
  return button;
}

//------------------------------------------------------------------------------------------------------------------

//Tests if any of the buttons have been pressed and released
//  returns mask of button that was pressed or 0
uint16_t maskPressed()
{
  uint16_t button = 0;      //Result store
  uint16_t mask = 0x0001;   //Mask for switchStates
  int btn = 0;              //Button/LED button number
  //Cycle through all 16 buttons
  while (btn < 16 && button == 0)
  {
    //Test if pressed
    if (_switchStates & mask)
    {
      //Delay for debouncing
      _delay_ms(10);
      //Test if it is still pressed otherwise it is a bounce
      if (_switchStates & mask)
      {
        //Wait until button is released
        while (_switchStates & mask)
        {
          _delay_ms(50);
        }
        //We have a valid button press
        button = mask;
      }
    }
    //Set up for next button to test
    mask = mask << 1;
    btn++;
  }
  return button;
}

Music.h

C/C++
#pragma once

uint8_t _speakerPin;

// Initialise Music player
void initMusic(uint8_t speakerPin);

//Play a sound of a frequency in Hz for a duration in mS
void playSound(double freqHz, int durationMs);

//Play turn tone
void playTurnTone();

//Play wah wah wah wahwahwahwahwahwah
void playLoseMusic();

//Play winning music
void playWinMusic();

//------------------------------------------------------------------------------------------------------------------

// Initialise Music player
void initMusic(uint8_t speakerPin)
{
  _speakerPin = speakerPin;
  pinMode(_speakerPin, OUTPUT);
  
}

//------------------------------------------------------------------------------------------------------------------

//Play a sound of a frequency in Hz for a duration in mS
void playSound(double freqHz, int durationMs)
{
  //Calculate the period in microseconds
  int periodMicro = int((1/freqHz)*1000000);
  int halfPeriod = periodMicro/2;
   
  //store start time
  long startTime = millis();
   
  //(millis() - startTime) is elapsed play time
  while((millis() - startTime) < durationMs)
  {
    digitalWrite(_speakerPin, HIGH);
    delayMicroseconds(halfPeriod);
    digitalWrite(_speakerPin, LOW);
    delayMicroseconds(halfPeriod);
  }
}

//------------------------------------------------------------------------------------------------------------------

//Play turn tone
void playTurnTone()
{
  playSound(300,50); 
}

//------------------------------------------------------------------------------------------------------------------

//Play wah wah wah wahwahwahwahwahwah
void playLoseMusic()
{
  delay(400);
  //wah wah wah wahwahwahwahwahwah
  for(double wah=0; wah<4; wah+=6.541)
  {
    playSound(440+wah, 50);
  }
  playSound(466.164, 100);
  delay(80);
  for(double wah=0; wah<5; wah+=4.939)
  {
    playSound(415.305+wah, 50);
  }
  playSound(440.000, 100);
  delay(80);
  for(double wah=0; wah<5; wah+=4.662)
  {
    playSound(391.995+wah, 50);
  }
  playSound(415.305, 100);
  delay(80);
  for(int j=0; j<7; j++)
  {
    playSound(391.995, 70);
    playSound(415.305, 70);
  }
  delay(400);
}

//------------------------------------------------------------------------------------------------------------------

//Play winning music
void playWinMusic()
{
  playSound(880,100); //A5
  playSound(988,100); //B5
  playSound(523,100); //C5
  playSound(988,100); //B5
  playSound(523,100); //C5
  playSound(587,100); //D5
  playSound(523,100); //C5
  playSound(587,100); //D5
  playSound(659,100); //E5
  playSound(587,100); //D5
  playSound(659,100); //E5
  playSound(659,100); //E5
  playSound(880,100); //A5
  playSound(988,100); //B5
  playSound(523,100); //C5
  playSound(988,100); //B5
  playSound(523,100); //C5
  playSound(587,100); //D5
  playSound(523,100); //C5
  playSound(587,100); //D5
  playSound(659,100); //E5
  playSound(587,100); //D5
  playSound(659,100); //E5
  playSound(659,100); //E5
  delay(250);
}

Display.h

C/C++
#pragma once
#include <LedControl.h>

//Because there is no default constructor for he LedControl, we can't create an instance of it
//without passing some parameters. This instance will be replaced in initDisplay and the garbage
//collector can clean up this instance.
LedControl _lc = LedControl(11, 13, 12);
 
//Initilaise MAX7219. This drives the two digit display on Digit 0 and Digit 1 and also the switch LEDs. Row 0 is
//Digit 3, Row 1 is Digit 4, Row 2 is Digit 5 and Row 3 is Digit 6. The Switch LEDs using segments d thru g
//representing columns 0 to 3.
void initDisplay(uint8_t dataPin, uint8_t clockPin, uint8_t loadPin);

//Turn off all button LEDs
void clearLeds();

//Turn on or off one of the button LEDs
// MAX2819 segment order is dp a b c d e f g
// Columns are d e f g
// Rows are Digit 3 4 5 6
// led - LED to switch on or off (0..15)
// on - True to switch on, False to switch off
void showLed(int led, bool on);

//Turn on or off the button LEDs based on ledStates
void showLedStates(uint16_t ledStates);

//Flashes a value on the display
// value - number to flash
// repeat - number of times to flash
// delta - time in mS between each state
void flashValue(int value, int repeat, int delta);

//Display a numeric value
// value - 0 to 99
// on - true to show value, false to turn off display
void showValue(int value, bool on);

//Display a numeric value
// level - Level number (0-9)
void showLevel(int level);

//------------------------------------------------------------------------------------------------------------------

//Initilaise MAX7219. This drives the two digit display on Digit 0 and Digit 1 and also the switch LEDs. Row 0 is
//Digit 3, Row 1 is Digit 4, Row 2 is Digit 5 and Row 3 is Digit 6. The Switch LEDs using segments d thru g
//representing columns 0 to 3.
void initDisplay(uint8_t dataPin, uint8_t clockPin, uint8_t loadPin)
{
  _lc = LedControl(dataPin,clockPin,loadPin,1);
  
  //Setup LEDs
  _lc.shutdown(0,false);     //Wakeup call
  _lc.setScanLimit(0, 7);    //Number of digits
  _lc.setIntensity(0, 15);    //Brightness
  _lc.clearDisplay(0);

}

//------------------------------------------------------------------------------------------------------------------

//Turn off all button LEDs
void clearLeds()
{
  showLedStates(0);
}

//------------------------------------------------------------------------------------------------------------------

//Turn on or off one of the button LEDs
// MAX2819 segment order is dp a b c d e f g
// Columns are d e f g
// Rows are Digit 3 4 5 6
// led - LED to switch on or off (0..15)
// on - True to switch on, False to switch off
void showLed(int led, bool on)
{
  _lc.setLed(0, (led >> 2) + 3, (led & 3) + 4, on);
}

//------------------------------------------------------------------------------------------------------------------

//Turn on or off the button LEDs based on ledStates
void showLedStates(uint16_t ledStates)
{
  uint16_t mask = 0x0001;
  for (int i = 0; i < 16; i++)
  {
    showLed(i, ((ledStates & mask) != 0));
    mask = mask << 1;
  }
}

//------------------------------------------------------------------------------------------------------------------

//Flashes a value on the display
// value - number to flash
// repeat - number of times to flash
// delta - time in mS between each state
void flashValue(int value, int repeat, int delta)
{
  for (int x=0; x < repeat;x++)
  {
    showValue(value, (x & 1) != 0);
    delay(delta);
  }
  showValue(value, true);
}

//------------------------------------------------------------------------------------------------------------------

//Display a numeric value
// value - 0 to 99
// on - true to show value, false to turn off display
void showValue(int value, bool on)
{
  value = min(value, 99);
  int tens = value / 10;
  int units = value % 10;
  if (on)
  {
    _lc.setDigit(0,1,units,false);
  }
  else 
  {
    _lc.setChar(0,1,' ', false);
  }
  if (on && tens != 0)
  {
    _lc.setDigit(0,0,tens,false);
  }
  else
  {
    _lc.setChar(0,0,' ', false);
  }
}

//------------------------------------------------------------------------------------------------------------------

//Display a numeric value
// level - Level number (0-9)
void showLevel(int level)
{
  _lc.setDigit(0,1,min(level, 9),false);
  _lc.setChar(0,0,'L', false);
}

Credits

John Bradnam
160 projects • 205 followers

Comments