Mastermind Game on the NeoPixel8x8
Moderators: adafruit_support_bill, adafruit

Mastermind Game on the NeoPixel8x8

by Sausage on Fri Oct 04, 2013 11:13 pm

For my first Arduino project I wanted to re-create the classic board game: Mastermind!
Image

The object of the game is to correctly guess the hidden code. The code consists of 4 "digits" of six possible colors: Red, Yellow, Green, Blue, White and Black (here substituted with Magenta). Press the "Enter" button to choose among the six possible colors (advanced in order: Red, Yellow, Green, Blue, Magenta,White), then press the "Select" button to advance selection to the next column (currently selected column blinks). Once all columns are filled with selected colors and you are satisfied with your guess, Hold the "Select" button for 2 seconds and release to confirm your code (row will blink). Press "Select" again to finalize your code and view "score" feedback or "Enter" to cancel, allowing you to modify your code. Feedback is given in 4 columns next to your guess. Red LEDs indicate the number code digits that are the correct color and position, White indicates a correct color, but incorrect position.


Image

Parts list:

Wiring Diagram
Image
The buttons are using interrupts on PIN2 and PIN3, the NeoPixel strip's Data In is connected to PIN6. You can optionally connect another 4 NeoPixels to Data Out pin of the matrix to enable "cheat-mode" and reveal the randomly generated hidden code.

Here is the code:
You'll need the Adafruit Neopixel library as well as the digital de-bounce library from Arduino Playground
Code: Select all | TOGGLE FULL SIZE
#include <Bounce.h> //http://playground.arduino.cc/code/bounce
#include <Adafruit_NeoPixel.h> //https://github.com/adafruit/Adafruit_NeoPixel

#define LED_PIN 6
#define NUM_LED 68
#define PIN_BTN1 3
#define PIN_BTN2 2
#define INTRPT_BTN1 1
#define INTRPT_BTN2 0
uint8_t brightness = 16;

Adafruit_NeoPixel strip = Adafruit_NeoPixel(NUM_LED, LED_PIN, NEO_GRB + NEO_KHZ800);
Bounce selectButton = Bounce( PIN_BTN1, 15 );
Bounce enterButton = Bounce( PIN_BTN2, 15 );

const uint32_t RED = strip.Color(255, 0, 0);
const uint32_t GREEN = strip.Color(0, 255, 0);
const uint32_t BLUE = strip.Color(0, 0, 255);
const uint32_t WHITE = strip.Color(200, 255, 255);
const uint32_t YELLOW = strip.Color(255, 255, 0);
const uint32_t MAGENTA = strip.Color(255, 0, 255);
const uint32_t OFF = strip.Color(0, 0, 0);

const uint32_t colorArray[] = {RED, YELLOW, GREEN, BLUE, MAGENTA, WHITE};
#define totalNumberColors (sizeof(colorArray)/sizeof(uint32_t))
uint8_t colorIndex = 0;

//Gameboard
const uint8_t gameCols = 4; // assumes and equal number of Scoreboard cols
const uint8_t gameRows = 8; // max number of turns
volatile uint8_t turn = 0;

volatile uint32_t guess[gameCols] = {RED, OFF, OFF, OFF};
uint32_t score[gameCols] = {OFF, OFF, OFF, OFF};
uint32_t solution[gameCols] = {RED, YELLOW, GREEN, BLUE};
//uint32_t solution[gameCols] = {GREEN, GREEN, BLUE, BLUE};
uint32_t blank[gameCols] = {OFF, OFF, OFF, OFF};

volatile uint16_t selectIndex = 0;

long timer = 0;
// Game modes:
// 0: Input Code
// 1: Confirm Code
// 2: Game Over
volatile uint8_t mode = 0;
boolean shouldIncrementTurn = false;

void setup()
{
  strip.begin();
  strip.show(); // Initialize all pixels to 'off'
 
  Serial.begin(9600);
  randomSeed(analogRead(0));
 
  strip.setBrightness(brightness);
 
  pinMode(PIN_BTN1, INPUT_PULLUP); 
  pinMode(PIN_BTN2, INPUT_PULLUP); 
 
  attachInterrupt(INTRPT_BTN1, onSelect, HIGH);
  attachInterrupt(INTRPT_BTN2, onEnter, HIGH);
 
  startGame();
}

void loop()
{
 
  if (mode == 0)
  {
    flashPixel(selectIndex, guess[selectIndex-(turn*gameCols*2)]);
  }
  else if (mode == 1)
  {
      flashCode(turn*gameCols*2, guess);
  }
  else if (mode == 2)
  {
    for (uint8_t i = 0 ; i < gameRows ; i++)
    {
      flashCode(i*gameCols*2, solution);
    }
  }
  if (shouldIncrementTurn)
  {
    incrTurn();
  }
}

void onSelect()
{
  if (selectButton.update())
  {
    //Serial.println("Select Button: ");
   
    if (selectButton.read() == HIGH) //true after button press is released
    {
        //Serial.println("HIGH");
        if (mode == 0)
        {
           //next color
          incrSelection();
          //show Guess
          showCode(turn*gameCols*2, guess);
        }
        else if (mode == 1) //cancel
        {
          mode = 0;
        }
    }
    if (selectButton.read() == LOW) //true after button pressed
    {
        //Serial.println("LOW");
    }
  }
}// onSelect

void incrSelection()
{
  colorIndex++;
  if (colorIndex > totalNumberColors-1) colorIndex = 0;
  guess[selectIndex-(turn*gameCols*2)] = colorArray[colorIndex]; 
} //incrSelection

void incrTurn()
{
    if (turn < gameRows-1)
    {
        turn = turn+1;
        selectIndex = turn*gameCols*2;
       
        //blank guess
        for (uint8_t i = 0; i < gameCols; i++)
        {
          guess[i] = OFF;
        }
        guess[0] = RED; //set default color
        colorIndex = 0;
        shouldIncrementTurn = false;
    }
    else
    {
      mode = 2;
    }
}


void onEnter()
{
  if (enterButton.update())
  {
    //Serial.println("Enter Button: ");
   
    if (mode == 0)
    {
      if (enterButton.read() == HIGH) //true after button press is released
      {
          //Serial.println("HIGH");
          if (timer == 0) timer = millis();//HACK: correct startup condition where LOW not always fired on first press
          if ((millis() - timer) >= 500) // long press
          {
            //Serial.println("LONG");
            //Validate code is complete
            uint8_t j = 0;
            for (uint8_t i = 0; i < gameCols; i++)
            {
              if (guess[i] != OFF) j++;
            }
            // start confrimation
            if (j== gameCols) mode = 1;
          } //endif // long press
          else
          {
            //move peg (selectIndex)
            selectIndex++;
            if (selectIndex > (turn*gameCols*2)+gameCols-1) selectIndex = turn*gameCols*2;
           
            if (guess[selectIndex-(turn*gameCols*2)] == OFF)//set default
            {
              guess[selectIndex-(turn*gameCols*2)] = RED;
            }
            colorIndex = getColorIndex(guess[selectIndex-(turn*gameCols*2)]);
           
            //show Guess
            showCode(turn*gameCols*2, guess);
          }
      }
      if (enterButton.read() == LOW) //true after button pressed
      {
          //Serial.println("LOW");
          timer = millis();
      }
    } //endif mode = 0
    else if (mode == 1) //input confirmed, advance turn
    {
      if (enterButton.read() == HIGH)
      {
        mode = 0;
        //compute score
         computeScore();
       
        //advance row
        shouldIncrementTurn = true;
      }//     
    } //endif mode 1
   
  } //endif Enter button updated event
 
} //end onEnter

void startGame()
{
  setRandomCode(solution);
  showCode(gameCols*2*gameRows, solution);
  guess[0] = RED;
  turn = 0;
  showCode(0, guess);
}

uint8_t getColorIndex(uint32_t color)
{
  for (uint8_t i = 0; i < totalNumberColors; i++)
  {
    if (colorArray[i] == color) return i;
  }
  return 0;
}

void flashPixel(uint16_t pixelNum, uint32_t color)
{
  strip.setPixelColor(pixelNum,OFF);
  strip.show();
  delay(100);
  strip.setPixelColor(pixelNum,color);
  strip.show();
  delay(400);
}

void flashCode (uint16_t pixelStart, volatile  uint32_t code[])
{
    showCode(pixelStart, blank);
    delay(100);
    showCode(pixelStart, code);
    delay(400);
}

void showCode (uint16_t pixelStart, volatile uint32_t code[])
{
    for (uint16_t i = 0; i < gameCols; i++)
    {
      strip.setPixelColor(pixelStart+i,code[i]);
    }
    strip.show();
}

void setRandomCode(uint32_t code[])
{
  for (uint16_t i = 0; i < gameCols; i++)
    {
      code[i] = colorArray[random(totalNumberColors)];
    }
}

void computeScore()
{
  uint8_t exactMatch = 0;
  uint8_t colorCorrect = 0;
  //Using a destructive comparison to calc score, should pass by value but always seems to be reference.
  //make local copies of solution and guess arrays 
  uint32_t s[gameCols];
  uint32_t g[gameCols];
  for (uint8_t i = 0; i < gameCols; i++)
  {
    s[i] = solution[i];
    g[i] = guess[i];
  }
 
  for (uint8_t i = 0; i < gameCols; i++)
  {
    if (s[i] == g[i])
    {
      exactMatch++;
      s[i] = g[i] = OFF;
     }
  }
 
  if (exactMatch != gameCols)
  {
   
    for (uint8_t i = 0; i < gameCols; i++)
    {
      if (g[i] != OFF)
      {
        for (uint16_t j = 0; j < 4 ; ++j)
        {
          if (s[j] == g[i])
          {
            s[j] = g[i] = OFF;
            colorCorrect++;
            break;
          }
        }
      }
    }
   
  } //endif not already won
 
 
//blank out score
  for (uint8_t i = 0; i < gameCols; i++)
  {
    score[i] = OFF;
  }
 
  Serial.print("Turn: ");
  Serial.println(turn);
  Serial.print("colorCorrect: ");
  Serial.println(colorCorrect);
  Serial.print("exactMatch: ");
  Serial.println(exactMatch);
 
  for (uint8_t i = 0; i < exactMatch; i++)
  {
    score[i] = RED;
  }
 
  for (uint8_t i = exactMatch; i < colorCorrect+exactMatch; i++)
  {
    score[i] = WHITE;
  }
 
  volatile uint32_t reverse[gameCols];
  uint8_t j =gameCols-1;
  for (uint8_t i = 0; i < gameCols; i++)
  {
    reverse[j] = score[i];
    j--;
  }
 
  showCode((turn*gameCols*2)+gameCols,reverse);
  //showCode((turn*gameCols*2)+gameCols,score);
  if (exactMatch == gameCols) mode = 2;
}


While I did take a class in C many many years ago, I've never done C++. As you can see, I'm really struggling with some basic structures, like how to define a class, and how to pass arrays by value, how to raise and handle events, etc. this is ugly, ugly code. I'm really hoping that the wonderfully helpful and knowledgeable Adafruit community can assist me to greatly improve this. I made with the 8x8 matrix as a proof of concept. For my final build I plan to use the 4 LED segments cut from the 60 NeoPixel LED Flexible Strip and hot-glue them under an actual classic Mastermind de-coder board. I would also like to add a 2 player mode and 2 player score-keeping.
Last edited by Sausage on Fri Oct 04, 2013 11:51 pm, edited 1 time in total.
User avatar
Sausage
 
Posts: 19
Joined: Thu Sep 12, 2013 5:37 pm

Re: Mastermind Game on the NeoPixel8x8

by Sausage on Fri Oct 04, 2013 11:47 pm

User avatar
Sausage
 
Posts: 19
Joined: Thu Sep 12, 2013 5:37 pm

Re: Mastermind Game on the NeoPixel8x8

by adafruit_support_bill on Sat Oct 05, 2013 6:28 am

Nicely done! Thanks for posting. :D
User avatar
adafruit_support_bill
 
Posts: 30794
Joined: Sat Feb 07, 2009 10:11 am

Re: Mastermind Game on the NeoPixel8x8

by Sausage on Thu Oct 31, 2013 8:19 pm

Here is a shot of the finished project:

Image

Neopixel strips and sticks hot-glued to the bottom of an original Mastermind decoder board, controlled using a Mintduino tucked into the orginal game box!

Image
User avatar
Sausage
 
Posts: 19
Joined: Thu Sep 12, 2013 5:37 pm

Re: Mastermind Game on the NeoPixel8x8

by adafruit_support_bill on Fri Nov 01, 2013 6:33 am

Very nice!
User avatar
adafruit_support_bill
 
Posts: 30794
Joined: Sat Feb 07, 2009 10:11 am

Re: Mastermind Game on the NeoPixel8x8

by hiduino on Fri Nov 01, 2013 11:24 pm

Awesome project!

What type of LEDs did you use for the scoring lights?

It looks like you have some kind of diffuser bulb on top of the RGB strips? What are you using for that?
hiduino
 
Posts: 137
Joined: Sat Sep 01, 2012 7:05 pm