// The BLA game.
// By Koepel.
// Public Domain
//
// 25 September 2024
// Version 1, it seems to work.
//
// A project to celibrate the historic event that
// someone made 1000 posts on the Wokwi Discord
// channel.
#include <Wire.h>
#include <hd44780.h>
#include <hd44780ioClass/hd44780_I2Cexp.h>
int signalLedPin = 2;
int buzzerPin = 3;
int hackLedPin = 4;
int rows = 4;
int cols = 20;
hd44780_I2Cexp lcd;
// Variables for the BLA game
byte occupied[4][20];
char blaText[] = "BLA";
int blaCount;
int printCount;
int printCountMax;
// A buffer and index for serial input
char buffer[20];
int index;
// The score is how many times in a row the answer is right.
unsigned long score;
// Special mode, score increases much more and any answer is right.
bool hackMode = false;
unsigned long previousMillis;
unsigned long previousMillisInactivity;
unsigned long inactivityTime = 1000UL * 60UL * 2UL; // 2 minutes
unsigned long previousMillisBlink;
const unsigned long intervalBlink = 800;
bool Blink;
enum
{
IDLE,
START,
WAIT_BEFORE_RUN,
RUNNING,
PREPARE_FOR_INPUT,
INPUT_NUMBER,
} state;
void setup()
{
Serial.begin(115200);
Serial.println(F("Welcome to the \"BLA\" game."));
pinMode(signalLedPin,OUTPUT);
pinMode(hackLedPin,OUTPUT);
// note that pinMode is not needed for tone() on buzzerPin.
randomSeed(noiseToRandom());
lcd.begin(cols, rows);
score = 0UL;
state = IDLE;
}
void loop()
{
unsigned long currentMillis = millis();
switch(state)
{
case IDLE:
// The game is not started by a button.
// Continue to start.
state = START;
break;
case START:
lcd.clear();
lcd.setCursor(3,0);
lcd.print(F("The \"BLA\" game"));
lcd.setCursor(2,1);
lcd.print(F("Count the \"BLA\"."));
lcd.setCursor(0,3);
lcd.print(F("Score = "));
lcd.print(score);
// Check if there was inactivity for a long time.
if(currentMillis - previousMillisInactivity >= inactivityTime)
{
previousMillisInactivity = currentMillis; // reset timer
playInactivityMelody();
}
previousMillis = currentMillis;
state = WAIT_BEFORE_RUN;
break;
case WAIT_BEFORE_RUN:
if(currentMillis - previousMillis >= 3000UL)
{
tone(buzzerPin,400,150); // start tone
lcd.clear();
printCount = 0;
printCountMax = random(5,15);
blaCount = 0;
clearOccupied();
previousMillis = currentMillis;
state = RUNNING;
}
break;
case RUNNING:
if(currentMillis - previousMillis >= 1000UL)
{
previousMillis = currentMillis;
if(printSomething())
blaCount++;
printCount++;
if(printCount > printCountMax)
{
state = PREPARE_FOR_INPUT;
}
}
break;
case PREPARE_FOR_INPUT:
tone(buzzerPin,1850,120); // input a number tone
Serial.println(F("Enter the result within 5 seconds"));
previousMillis = currentMillis;
// clear the buffer for serial input
index = 0;
buffer[0] = '\0';
// Remove everything from the Serial input.
while( Serial.available() > 0)
Serial.read();
digitalWrite(signalLedPin,HIGH);
state = INPUT_NUMBER;
break;
case INPUT_NUMBER:
if(currentMillis - previousMillis >= 5000UL)
{
// Timeout.
Serial.print(F("Timeout! "));
Serial.print(F("There were "));
Serial.print(blaCount);
Serial.print(F(" \"BLA\"."));
Serial.println();
digitalWrite(signalLedPin,LOW);
// A timeout is wrong, so the score is reset.
score = 0UL;
// Play a timeout tune
for(float f=800.0; f>50.0; f*=0.85)
{
tone(buzzerPin,(int)f);
delay(30 + int(f/10.0));
}
noTone(buzzerPin);
state = IDLE;
}
if(readInput())
{
// something was entered.
// Reset the inactivity checker
previousMillisInactivity = currentMillis;
// Check first the command for the hack mode.
// Once the hack mode is set, skip the check.
if(strncmp(buffer,"BLA",3)==0)
{
if(!hackMode)
{
hackMode = true;
Serial.println(F("Special hack mode activated."));
}
else
{
hackMode = false;
Serial.println(F("Hack mode turned off."));
}
digitalWrite(signalLedPin,LOW);
state = IDLE;
}
else
{
// Get the number that was entered.
// If it is not numbers, then atoi() will return zero.
int answer = atoi(buffer);
if(answer == blaCount || hackMode)
{
Serial.print(F("Correct. "));
Serial.print(F("There were indeed "));
Serial.print(blaCount);
if(hackMode)
{
Serial.print(F(" or "));
Serial.print(answer);
}
Serial.print(F(" \"BLA\"."));
Serial.println();
// The score is increased.
score++;
if(hackMode)
score += random(0,1000);
// Play scoring tune
for(int i=0; i<3; i++)
{
for(float f=650.0; f<3000; f*=1.5)
{
tone(buzzerPin,(int)f);
delay(40);
}
}
noTone(buzzerPin);
}
else
{
Serial.print(F("Wrong. "));
Serial.print(F("You entered "));
Serial.print(answer);
Serial.print(F(", but it was "));
Serial.print(blaCount);
Serial.print(F("."));
Serial.println();
// Bad answer, the score is reset.
score = 0UL;
// Play bad input tune
tone(buzzerPin,660);
delay(100);
tone(buzzerPin,440);
delay(200);
noTone(buzzerPin);
}
digitalWrite(signalLedPin,LOW);
state = IDLE;
}
}
break;
default:
Serial.println(F("Bug"));
break;
}
// Millis timer to blink the blue led
if(hackMode)
{
if(currentMillis - previousMillisBlink >= intervalBlink)
{
previousMillisBlink = currentMillis;
if(Blink)
{
digitalWrite(hackLedPin,LOW);
Blink = false;
}
else
{
digitalWrite(hackLedPin,HIGH);
Blink = true;
}
}
}
}
// The function printSomething()
// might print "BLA" or something else.
bool printSomething()
{
bool itWasBla = false;
bool success = false;
for(int tries=0; tries<10 and !success; tries++)
{
int r = random(0,rows);
int c = random(0,cols-2);
if(occupied[r][c] == 0 and occupied[r][c+1] == 0 and occupied[r][c+2] == 0)
{
char outputText[4];
if(random(0,2) == 0)
{
strcpy(outputText,blaText);
}
else
{
outputText[0] = blaText[random(0,3)];
outputText[1] = blaText[random(0,3)];
outputText[2] = blaText[random(0,3)];
outputText[3] = '\0';
}
lcd.setCursor(c,r);
lcd.print(outputText);
if(strncmp(outputText,blaText,3)==0)
itWasBla = true;
occupied[r][c] = 1;
occupied[r][c+1] = 1;
occupied[r][c+2] = 1;
success = true;
}
}
return(itWasBla);
}
void clearOccupied()
{
// A single memset() would do.
for(int r=0; r<rows; r++)
for(int c=0; c<cols; c++)
occupied[r][c] = 0;
}
bool readInput()
{
bool dataEntered = false;
if( Serial.available() > 0) // new data received ?
{
int inChar = Serial.read();
if( inChar == '\n' || inChar == '\r') // end of line ?
{
dataEntered = true;
}
else
{
buffer[index] = (char) inChar; // put new data in the buffer
if( index < (int)(sizeof( buffer) - 1)) // the 'sizeof' returns an unsigned size_t
{
index++;
}
else
{
// The buffer is full, too much data was received.
// Process the command and (for safety) clear
// the remaining of the data.
// With a low baudrate, it is possible that still
// extra characters will come in after this.
dataEntered = true;
while( Serial.available() > 0)
{
Serial.read();
}
}
buffer[index] = '\0'; // set a new zero-terminator
}
}
return(dataEntered);
}
long noiseToRandom()
{
long data;
for(int pin=A0; pin<=A5; pin++)
{
data += (long) analogRead(pin);
}
return(data);
}
void playInactivityMelody()
{
for(int i=0; i<30; i++)
{
int f = random(100,4000);
int d = random(20,180);
int p = random(20,100);
tone(buzzerPin,f);
delay(d);
noTone(buzzerPin);
delay(p);
}
}