// =========================================
// MEMORY COLOR MATCH GAME
// Arduino Nano + 3x74HC595
// COMMON ANODE VERSION
// PLAYER 2 GETS 3 CHANCES
// =========================================
bool computerMode = false;
int menuOption = 0; // 0 = PvP, 1 = CvP
void selectGameMode();
void showMenu();
void generateComputerPattern();
void playWinSound();
void playLoseSound();
void blinkWrongRGB(bool wrongRGB[]);
const int latchPin = 12;
const int clockPin = 8;
const int dataPin = 11;
const int buzzerPin = 6;
const int selectButton = 13;
const int okButton = 7;
bool lastSelect = HIGH;
bool lastOk = HIGH;
// Common Anode:
// 1 = OFF
// 0 = ON
uint32_t lockedState = 0xFFFFFF;
byte player1Pattern[4];
byte player2Pattern[4];
int currentRGB = 0;
int currentColor = -1;
bool player1Turn = true;
int attemptsLeft = 3;
// Common Anode Colors
byte colors[7] =
{
B110, // Red
B101, // Green
B011, // Blue
B100, // Yellow
B010, // Magenta
B001, // Cyan
B000 // White
};
void setup()
{
Serial.begin(9600);
pinMode(latchPin, OUTPUT);
pinMode(clockPin, OUTPUT);
pinMode(dataPin, OUTPUT);
pinMode(selectButton, INPUT_PULLUP);
pinMode(okButton, INPUT_PULLUP);
pinMode(buzzerPin, OUTPUT);
randomSeed(analogRead(A0));
clearDisplay();
selectGameMode();
if (computerMode)
{
generateComputerPattern();
}
else
{
Serial.println("================================");
Serial.println("PLAYER 1");
Serial.println("Press SELECT to choose RGB1");
Serial.println("================================");
}
}
void loop()
{
bool selectState = digitalRead(selectButton);
bool okState = digitalRead(okButton);
// ==================================
// SELECT BUTTON
// ==================================
if (lastSelect == HIGH && selectState == LOW)
{
if (currentColor == -1)
{
currentColor = 0;
}
else
{
currentColor++;
if (currentColor > 6)
{
currentColor = 0;
}
}
if (player1Turn && !computerMode)
{
showDisplay(currentRGB,
colors[currentColor]);
}
else
{
showDisplay(7 - currentRGB,
colors[currentColor]);
}
delay(200);
}
// ==================================
// OK BUTTON
// ==================================
if (lastOk == HIGH && okState == LOW)
{
if (currentColor == -1)
{
delay(200);
lastSelect = selectState;
lastOk = okState;
return;
}
byte selectedColor = colors[currentColor];
// ==================================
// PLAYER 1
// ==================================
if (player1Turn && !computerMode)
{
player1Pattern[currentRGB] =
selectedColor;
lockRGB(currentRGB,
selectedColor);
currentRGB++;
currentColor = -1;
if (currentRGB < 4)
{
showDisplay(-1, B111);
Serial.print("Select RGB");
Serial.println(currentRGB + 1);
}
else
{
Serial.println("MEMORIZE!");
Serial.println("5 Seconds...");
delay(5000);
lockedState = 0xFFFFFF;
clearDisplay();
player1Turn = false;
currentRGB = 0;
currentColor = -1;
attemptsLeft = 3;
Serial.println();
Serial.println("================================");
Serial.println("PLAYER 2");
Serial.println("Match RGB5-RGB8");
Serial.println("Chances: 3");
Serial.println("================================");
}
}
// ==================================
// PLAYER 2
// ==================================
else
{
player2Pattern[currentRGB] =
selectedColor;
lockRGB(7 - currentRGB,
selectedColor);
currentRGB++;
currentColor = -1;
if (currentRGB < 4)
{
showDisplay(-1, B111);
Serial.print("Select RGB");
Serial.println(8 - currentRGB);
}
else
{
bool win = true;
bool wrongRGB[4] = {false, false, false, false};
for (int i = 0; i < 4; i++)
{
if (player1Pattern[i] != player2Pattern[i])
{
win = false;
wrongRGB[i] = true;
}
}
// ==========================
// WIN
// ==========================
if (win)
{
Serial.println();
Serial.println("MATCH!");
Serial.println("YOU WIN!");
playWinSound();
blinkAllTwice();
delay(1000);
resetGame();
}
// ==========================
// WRONG PATTERN
// ==========================
else
{
attemptsLeft--;
if (attemptsLeft > 0)
{
Serial.println();
Serial.print("WRONG PATTERN!");
Serial.print(" Attempts Left: ");
Serial.println(attemptsLeft);
playLoseSound();
blinkWrongRGB(wrongRGB);
delay(1000);
// Clear only Player 2 LEDs
lockedState = 0xFFFFFF;
clearDisplay();
currentRGB = 0;
currentColor = -1;
for (int i = 0; i < 4; i++)
{
player2Pattern[i] = 0;
}
Serial.println();
Serial.println("TRY AGAIN");
Serial.println("Match RGB5");
}
else
{
Serial.println();
Serial.println("GAME OVER");
Serial.println("NO CHANCES LEFT");
playLoseSound();
blinkWrongRGB(wrongRGB);
delay(1000);
resetGame();
}
}
}
}
delay(200);
}
lastSelect = selectState;
lastOk = okState;
}
// =====================================
// LOCK RGB
// =====================================
void lockRGB(int rgbIndex,
byte pattern)
{
int bitPos = rgbIndex * 3;
lockedState &=
~((uint32_t)0b111 << bitPos);
lockedState |=
((uint32_t)pattern << bitPos);
showDisplay(-1, B111);
}
// =====================================
// DISPLAY
// =====================================
void showDisplay(int activeRGB,
byte activePattern)
{
uint32_t output = lockedState;
if (activeRGB >= 0)
{
int bitPos = activeRGB * 3;
output &=
~((uint32_t)0b111 << bitPos);
output |=
((uint32_t)activePattern
<< bitPos);
}
digitalWrite(latchPin, LOW);
shiftOut(dataPin,
clockPin,
MSBFIRST,
(output >> 16) & 0xFF);
shiftOut(dataPin,
clockPin,
MSBFIRST,
(output >> 8) & 0xFF);
shiftOut(dataPin,
clockPin,
MSBFIRST,
output & 0xFF);
digitalWrite(latchPin, HIGH);
}
// =====================================
// CLEAR DISPLAY
// ALL LEDs OFF
// =====================================
void clearDisplay()
{
digitalWrite(latchPin, LOW);
shiftOut(dataPin,
clockPin,
MSBFIRST,
0xFF);
shiftOut(dataPin,
clockPin,
MSBFIRST,
0xFF);
shiftOut(dataPin,
clockPin,
MSBFIRST,
0xFF);
digitalWrite(latchPin, HIGH);
}
// =====================================
// WIN FLASH
// =====================================
void blinkAllTwice()
{
for (int i = 0; i < 2; i++)
{
digitalWrite(latchPin, LOW);
shiftOut(dataPin,
clockPin,
MSBFIRST,
0x00);
shiftOut(dataPin,
clockPin,
MSBFIRST,
0x00);
shiftOut(dataPin,
clockPin,
MSBFIRST,
0x00);
digitalWrite(latchPin, HIGH);
delay(300);
clearDisplay();
delay(300);
}
}
// =====================================
// LOSE FLASH
// =====================================
void gameOverFlash()
{
for (int i = 0; i < 3; i++)
{
digitalWrite(latchPin, LOW);
shiftOut(dataPin,
clockPin,
MSBFIRST,
0x00);
shiftOut(dataPin,
clockPin,
MSBFIRST,
0x00);
shiftOut(dataPin,
clockPin,
MSBFIRST,
0x00);
digitalWrite(latchPin, HIGH);
delay(150);
clearDisplay();
delay(150);
}
}
void showMenu()
{
Serial.println();
if (menuOption == 0)
{
Serial.println("> PLAYER vs PLAYER");
Serial.println(" COMPUTER vs PLAYER");
}
else
{
Serial.println(" PLAYER vs PLAYER");
Serial.println("> COMPUTER vs PLAYER");
}
Serial.println("OK = START");
}
void selectGameMode()
{
Serial.println();
Serial.println("SELECT GAME MODE");
showMenu();
while (true)
{
bool selectState = digitalRead(selectButton);
bool okState = digitalRead(okButton);
if (lastSelect == HIGH && selectState == LOW)
{
menuOption++;
if (menuOption > 1)
{
menuOption = 0;
}
showMenu();
delay(250);
}
if (lastOk == HIGH && okState == LOW)
{
computerMode = (menuOption == 1);
Serial.println();
if (computerMode)
{
Serial.println("COMPUTER vs PLAYER");
}
else
{
Serial.println("PLAYER vs PLAYER");
}
delay(500);
return;
}
lastSelect = selectState;
lastOk = okState;
}
}
void generateComputerPattern()
{
Serial.println();
Serial.println("COMPUTER CHOOSING...");
Serial.println("MEMORIZE!");
lockedState = 0xFFFFFF;
clearDisplay();
for (int i = 0; i < 4; i++)
{
int randomColor = random(0, 7);
player1Pattern[i] = colors[randomColor];
lockRGB(i, colors[randomColor]);
delay(800);
}
delay(5000);
lockedState = 0xFFFFFF;
clearDisplay();
player1Turn = false;
currentRGB = 0;
currentColor = -1;
attemptsLeft = 3;
Serial.println();
Serial.println("YOUR TURN");
Serial.println("Match RGB5-RGB8");
}
// =====================================
// RESET GAME
// =====================================
void resetGame()
{
lockedState = 0xFFFFFF;
currentRGB = 0;
currentColor = -1;
player1Turn = true;
attemptsLeft = 3;
for (int i = 0; i < 4; i++)
{
player1Pattern[i] = 0;
player2Pattern[i] = 0;
}
clearDisplay();
Serial.println();
Serial.println("================================");
Serial.println("NEW GAME");
Serial.println("================================");
if (computerMode)
{
delay(1000);
generateComputerPattern();
}
else
{
Serial.println("PLAYER 1");
Serial.println("Select RGB1");
}
}
//===============
//WIN
//===============
void playWinSound()
{
int melody[] =
{
523, 659, 784, 1047
};
int duration[] =
{
150, 150, 150, 300
};
for (int i = 0; i < 4; i++)
{
tone(buzzerPin,
melody[i],
duration[i]);
delay(duration[i] + 50);
}
noTone(buzzerPin);
}
//===============
//LOSE
//===============
void playLoseSound()
{
int melody[] =
{
523, 392, 330, 262
};
int duration[] =
{
250, 250, 250, 500
};
for (int i = 0; i < 4; i++)
{
tone(buzzerPin,
melody[i],
duration[i]);
delay(duration[i] + 50);
}
noTone(buzzerPin);
}
//Blink only wrong RGBs (Common Anode version)
void blinkWrongRGB(bool wrongRGB[])
{
uint32_t backup = lockedState;
for (int blink = 0; blink < 2; blink++)
{
uint32_t tempState = backup;
for (int i = 0; i < 4; i++)
{
if (wrongRGB[i])
{
int actualRGB = 7 - i; // RGB8 -> RGB5
int bitPos = actualRGB * 3;
// Common Anode OFF = 111
tempState &= ~((uint32_t)0b111 << bitPos);
tempState |= ((uint32_t)0b111 << bitPos);
}
}
digitalWrite(latchPin, LOW);
shiftOut(dataPin, clockPin,
MSBFIRST,
(tempState >> 16) & 0xFF);
shiftOut(dataPin, clockPin,
MSBFIRST,
(tempState >> 8) & 0xFF);
shiftOut(dataPin, clockPin,
MSBFIRST,
tempState & 0xFF);
digitalWrite(latchPin, HIGH);
delay(300);
showDisplay(-1, B111);
delay(300);
}
lockedState = backup;
showDisplay(-1, B111);
}