Mastermind Game on the NeoPixel8x8

EL Wire/Tape/Panels, LEDs, pixels and strips, LCDs and TFTs, etc products from Adafruit

Moderators: adafruit_support_bill, adafruit

Please be positive and constructive with your questions and comments.
Locked
User avatar
sausage
 
Posts: 23
Joined: Thu Sep 12, 2013 5:37 pm

Mastermind Game on the NeoPixel8x8

Post by sausage »

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

#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: 23
Joined: Thu Sep 12, 2013 5:37 pm

Re: Mastermind Game on the NeoPixel8x8

Post by sausage »


User avatar
adafruit_support_bill
 
Posts: 86257
Joined: Sat Feb 07, 2009 10:11 am

Re: Mastermind Game on the NeoPixel8x8

Post by adafruit_support_bill »

Nicely done! Thanks for posting. :D

User avatar
sausage
 
Posts: 23
Joined: Thu Sep 12, 2013 5:37 pm

Re: Mastermind Game on the NeoPixel8x8

Post by sausage »

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
adafruit_support_bill
 
Posts: 86257
Joined: Sat Feb 07, 2009 10:11 am

Re: Mastermind Game on the NeoPixel8x8

Post by adafruit_support_bill »

Very nice!

User avatar
hiduino
 
Posts: 862
Joined: Sat Sep 01, 2012 7:05 pm

Re: Mastermind Game on the NeoPixel8x8

Post by hiduino »

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?

User avatar
29holden
 
Posts: 1
Joined: Tue Jun 14, 2016 10:42 am

Re: Mastermind Game on the NeoPixel8x8

Post by 29holden »

How did you wire this up? I wired it up like it showed in the picture, except I connected the LED screen to ground, and connected the buttons to both 3/2 and 1/0, and to the same ground. All that happens though, is the red light bounces on and off by itself. Please respond back, thanks.

User avatar
sausage
 
Posts: 23
Joined: Thu Sep 12, 2013 5:37 pm

Re: Mastermind Game on the NeoPixel8x8

Post by sausage »

hiduino wrote: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?
Score lights are neopixel pcbs https://www.adafruit.com/product/1426 (not all lights on the stick are used, just those that align with the score peg holes)

The "diffusers" are clear vinyl furniture bumpers commonly used on the inside of cabinet doors.

User avatar
sausage
 
Posts: 23
Joined: Thu Sep 12, 2013 5:37 pm

Re: Mastermind Game on the NeoPixel8x8

Post by sausage »

29holden wrote:How did you wire this up? I wired it up like it showed in the picture, except I connected the LED screen to ground, and connected the buttons to both 3/2 and 1/0, and to the same ground. All that happens though, is the red light bounces on and off by itself. Please respond back, thanks.

Sounds like you have it working, it is awaiting input. One button will scroll thru the colors, the other will advance to the next column.
Check out this video

Locked
Please be positive and constructive with your questions and comments.

Return to “Glowy things (LCD, LED, TFT, EL) purchased at Adafruit”