#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#define LCD_ADDR 0x27
#define LCD_COLS 20
#define LCD_ROWS 4
#define BUTTON_A 12
#define BUTTON_B 13
#define BUTTON_C 14
#define BUTTON_D 27
#define BUZZER_BUTTON_1 26
#define BUZZER_BUTTON_2 25
#define GREEN_LED 33
#define RED_LED 32
#define BUZZER 2
#define POT_PIN 34
LiquidCrystal_I2C lcd(LCD_ADDR, LCD_COLS, LCD_ROWS);
enum GameMode { EASY, NORMAL, HARD };
GameMode gameMode;
struct Question {
const char* correct;
const char* wrong[3];
const char* clues[3];
int difficulty;
};
const Question questionBank[54] = {
{"Japan", {"Brazil","Egypt","Norway"}, {"Island nation","Capital Tokyo","Uses Yen"},2},
{"Brazil", {"Japan","Peru","Egypt"}, {"South America","Amazon river","Football"},1},
{"Egypt", {"Norway","Japan","Brazil"}, {"Pyramids","Nile River","Sphinx"},1},
{"France", {"Spain","Italy","Germany"}, {"Eiffel Tower","Paris","Wine country"},2},
{"Italy", {"France","Germany","Japan"}, {"Rome","Pasta","Colosseum"},2},
{"Canada", {"USA","Mexico","Brazil"}, {"Maple syrup","Niagara Falls","Cold climate"},1},
{"Australia", {"New Zealand","Canada","Japan"}, {"Sydney Opera","Kangaroos","Great Barrier Reef"},2},
{"India", {"Pakistan","Bangladesh","Nepal"}, {"Taj Mahal","Bollywood","Spices"},1},
{"China", {"Japan","Thailand","India"}, {"Great Wall","Pandas","Beijing"},2},
{"Norway", {"Sweden","Finland","Russia"}, {"Fjords","Northern Lights","Oslo"},3},
{"Norway", {"Sweden","Finland","Russia"}, {"Northern European country","Known for fjords","Capital is Oslo"},3},
{"Germany", {"Austria","France","Italy"}, {"European country","Oktoberfest celebration","Berlin is capital"},2},
{"Russia", {"China","Canada","USA"}, {"Largest country by land area","Capital is Moscow","Cold winters in Siberia"},3},
{"Kenya", {"Uganda","Tanzania","South Africa"}, {"East African country","Known for safaris","Mount Kenya"},2},
{"Mexico", {"Brazil","Argentina","Peru"}, {"North American country","Day of the Dead celebration","Tacos are famous"},1},
{"Thailand", {"Vietnam","Malaysia","Japan"}, {"Southeast Asian country","Capital is Bangkok","Famous for temples"},2},
{"Greece", {"Italy","Turkey","Spain"}, {"European country","Olympic Games origin","Athens is capital"},2},
{"Peru", {"Chile","Brazil","Argentina"}, {"South American country","Home to Machu Picchu","Andes Mountains run through"},2},
{"South Korea", {"North Korea","Japan","China"}, {"East Asian country","Seoul is capital","K-pop originates here"},3},
{"Spain", {"Portugal","France","Italy"}, {"European country","Famous dance: Flamenco","Capital is Madrid"},2},
{"USA", {"Canada","Mexico","Brazil"}, {"North American country","Hollywood is here","Statue of Liberty"},1},
{"United Kingdom", {"Germany","France","Italy"}, {"Island country in Europe","Big Ben is famous","London is capital"},2},
{"Argentina", {"Brazil","Peru","Chile"}, {"South American country","Tango dance origin","Buenos Aires is capital"},2},
{"Sweden", {"Norway","Finland","Denmark"}, {"Northern European country","Capital is Stockholm","Famous for IKEA"},2},
{"Egypt", {"Greece","Turkey","Israel"}, {"African country","Desert land","Nile River flows"},1},
{"South Africa", {"Nigeria","Kenya","Egypt"}, {"Southern African country","Cape Town location","Table Mountain landmark"},2},
{"Vietnam", {"Thailand","China","Japan"}, {"Southeast Asian country","Ho Chi Minh City","Famous rice fields"},2},
{"Italy", {"France","Spain","Germany"}, {"European country","Venice is famous","Colosseum in Rome"},1},
{"Poland", {"Germany","Ukraine","Czech"}, {"Central European country","Warsaw is capital","Pierogi are famous"},2},
{"Norway", {"Sweden","Finland","Denmark"}, {"Northern Europe","Northern Lights visible","Fjords present"},2},
{"India", {"China","Pakistan","Bangladesh"}, {"South Asian country","Spices and cuisine","Taj Mahal location"},1},
{"China", {"Japan","Thailand","Vietnam"}, {"East Asian country","Great Wall","Beijing capital"},1},
{"Japan", {"China","South Korea","Thailand"}, {"East Asian island nation","Sushi originates here","Tokyo capital"},1},
{"Brazil", {"Argentina","Peru","Chile"}, {"South America","Amazon rainforest","Football culture"},1},
{"Mexico", {"USA","Brazil","Spain"}, {"North America","Day of the Dead","Tacos"},1},
{"Canada", {"USA","Russia","Germany"}, {"North America","Cold winters","Maple syrup"},1},
{"Australia", {"New Zealand","Fiji","Indonesia"}, {"Oceania country","Kangaroos","Sydney Opera House"},2},
{"Greece", {"Italy","Turkey","Spain"}, {"European country","Olympics origin","Acropolis"},2},
{"France", {"Italy","Spain","Germany"}, {"Europe","Eiffel Tower","Paris capital"},1},
{"United States", {"Canada","Mexico","Brazil"}, {"North America","Washington DC","Hollywood"},1},
{"South Korea", {"North Korea","China","Japan"}, {"East Asia","Seoul capital","Samsung brand"},2},
{"Norway", {"Sweden","Finland","Denmark"}, {"Northern Europe","Fjords","Oslo capital"},3},
{"Egypt", {"Greece","Turkey","Israel"}, {"Africa","Nile River","Pyramids"},1},
{"Italy", {"France","Germany","Spain"}, {"Europe","Venice canals","Colosseum"},2},
{"Brazil", {"Argentina","Peru","Chile"}, {"South America","Carnival","Amazon"},1},
{"Japan", {"China","Korea","Thailand"}, {"East Asia","Samurai origin","Tokyo"},2},
{"India", {"Pakistan","Nepal","Bangladesh"}, {"South Asia","Taj Mahal","Bollywood"},1},
{"Australia", {"New Zealand","Fiji","Indonesia"}, {"Oceania","Great Barrier Reef","Kangaroo"},2},
{"Germany", {"France","Italy","Austria"}, {"Europe","Berlin","Oktoberfest"},2},
{"Russia", {"China","USA","Canada"}, {"Largest country","Moscow","Cold winters"},3},
{"Canada", {"USA","Mexico","Brazil"}, {"North America","Maple syrup","Niagara Falls"},1},
{"South Africa", {"Nigeria","Kenya","Egypt"}, {"Africa","Table Mountain","Safari"},2},
{"China", {"Japan","Thailand","Vietnam"}, {"East Asia","Great Wall","Beijing"},1},
{"Mexico", {"USA","Brazil","Spain"}, {"North America","Sombrero","Tacos"},1}
};
Question currentQuestion;
const char* answers[4];
int correctIndex = 0;
bool buzzed = false;
int currentClue = 0;
int currentPlayer = -1;
const int QUESTIONS_PER_ROUND = 10;
int questionCount = 0;
int playerScore[2] = {0,0};
String playerNames[2] = {"Player 1","Player 2"};
int titleMelody[] = {523, 659, 784, 880, 784, 659, 523, 659,
587, 698, 783, 880, 783, 698, 587, 523,
659, 784, 880, 988, 880, 784, 659, 587,
523, 659, 784, 880, 784, 659, 523, 0};
int noteDurations[] = {200, 200, 200, 300, 200, 200, 200, 200,
200, 200, 300, 300, 200, 200, 200, 200,
200, 200, 300, 300, 200, 200, 200, 200,
200, 200, 200, 300, 200, 200, 200, 400};
int melodyLength = sizeof(titleMelody)/sizeof(titleMelody[0]);
int currentNote = 0;
unsigned long noteStart = 0;
bool notePlaying = false;
int baseClueTime = 5;
int decayRate = 0;
void setup() {
Serial.begin(115200);
lcd.init();
lcd.backlight();
pinMode(BUTTON_A, INPUT_PULLUP);
pinMode(BUTTON_B, INPUT_PULLUP);
pinMode(BUTTON_C, INPUT_PULLUP);
pinMode(BUTTON_D, INPUT_PULLUP);
pinMode(BUZZER_BUTTON_1, INPUT_PULLUP);
pinMode(BUZZER_BUTTON_2, INPUT_PULLUP);
pinMode(GREEN_LED, OUTPUT);
pinMode(RED_LED, OUTPUT);
pinMode(BUZZER, OUTPUT);
getPlayerNames();
gameMode = readDifficultyFromPot();
applyGameMode();
titleScreen();
}
String readNameFromSerial(uint8_t lcdRow) {
String name = "";
lcd.setCursor(0, lcdRow);
lcd.print(" "); // clear row
lcd.setCursor(0, lcdRow);
while (true) {
if (Serial.available()) {
char c = Serial.read();
if (c == '\n' || c == '\r') {
if (name.length() > 0) break;
} else if (c == 8 || c == 127) {
if (name.length() > 0) {
name.remove(name.length() - 1);
lcd.setCursor(name.length(), lcdRow);
lcd.print(" ");
lcd.setCursor(name.length(), lcdRow);
}
} else if (name.length() < 20 && isPrintable(c)) {
name += c;
lcd.print(c);
}
}
}
return name;
}
void getPlayerNames() {
lcd.clear();
lcd.setCursor(0,0);
lcd.print("Enter Player 1 name:");
playerNames[0] = readNameFromSerial(1);
lcd.clear();
lcd.setCursor(0,0);
lcd.print("Enter Player 2 name:");
playerNames[1] = readNameFromSerial(1);
lcd.clear();
lcd.setCursor(0,0);
lcd.print("Welcome!");
lcd.setCursor(0,1);
lcd.print(playerNames[0]);
lcd.setCursor(0,2);
lcd.print(playerNames[1]);
delay(1500);
}
GameMode readDifficultyFromPot() {
lcd.clear();
lcd.setCursor(0,0);
lcd.print("Set Difficulty:");
lcd.setCursor(0,1);
lcd.print("Turn Pot to select");
unsigned long startTime = millis();
unsigned long lastMoveTime = millis();
int lastPot = analogRead(POT_PIN);
GameMode currentMode = EASY;
while (millis() - startTime < 15000) {
int pot = analogRead(POT_PIN);
GameMode mode;
if (pot < 1365) mode = EASY;
else if (pot < 2730) mode = NORMAL;
else mode = HARD;
if (mode != currentMode) {
currentMode = mode;
lcd.setCursor(0,2);
switch (currentMode) {
case EASY: lcd.print("EASY "); break;
case NORMAL: lcd.print("NORMAL "); break;
case HARD: lcd.print("HARD "); break;
}
lastMoveTime = millis();
}
if (millis() - lastMoveTime >= 4000) {
lcd.setCursor(0,3);
lcd.print("Selected!");
delay(2000);
return currentMode;
}
delay(100);
}
lcd.setCursor(0,3);
lcd.print("Selected!");
delay(1000);
return currentMode;
}
void applyGameMode() {
switch(gameMode) {
case EASY: baseClueTime=6; decayRate=0; break;
case NORMAL: baseClueTime=4; decayRate=1; break;
case HARD: baseClueTime=4; decayRate=2; break;
}
}
void titleScreen() {
lcd.clear();
lcd.setCursor(0,1);
lcd.print("Initializing....");
delay(1300);
lcd.clear();
lcd.setCursor(0,0);
lcd.print("GeoChallenge ;)");
lcd.setCursor(0,1);
lcd.print("--------------------");
lcd.setCursor(0,2);
lcd.print("Press buzzer 2 start");
while(digitalRead(BUZZER_BUTTON_1) == HIGH && digitalRead(BUZZER_BUTTON_2) == HIGH){
unsigned long currentMillis = millis();
if(!notePlaying || currentMillis - noteStart >= noteDurations[currentNote] + 20){
if(titleMelody[currentNote] != 0){
tone(BUZZER, titleMelody[currentNote], noteDurations[currentNote]);
} else {
noTone(BUZZER);
}
noteStart = currentMillis;
notePlaying = true;
currentNote++;
if(currentNote >= melodyLength) currentNote = 0;
}
delay(10);
}
noTone(BUZZER);
playStartSound();
}
// ====================== SOUNDS ======================
void playStartSound() { int startMelody[] = {500,700,1000}; for(int i=0;i<3;i++){ tone(BUZZER, startMelody[i], 150); delay(160); } }
void playCorrectSound() { int freqs[] = {800,1000,1200}; for(int i=0;i<3;i++){ tone(BUZZER,freqs[i],150); delay(160); } }
void playWrongSound() { int freqs[] = {600,400,300}; for(int i=0;i<3;i++){ tone(BUZZER,freqs[i],150); delay(160); } }
// ====================== ANSWERS ======================
void shuffleAnswers() {
answers[0] = currentQuestion.correct;
for(int i=0;i<3;i++) answers[i+1] = currentQuestion.wrong[i];
for(int i=3;i>0;i--){
int j = random(i+1);
const char* temp = answers[i];
answers[i] = answers[j];
answers[j] = temp;
}
for(int i=0;i<4;i++) if(answers[i]==currentQuestion.correct) correctIndex=i;
}
int readClueInterval() {
int potVal = analogRead(POT_PIN);
int potAdjust = map(potVal,0,4095,-1,2);
int time = baseClueTime + potAdjust - (currentClue*decayRate);
return max(1,time);
}
// ====================== DISPLAY ======================
void displayClue(int idx){
lcd.clear();
lcd.setCursor(0,0);
lcd.print("Clue "); lcd.print(idx+1); lcd.print("/3:");
lcd.setCursor(0,1); lcd.print(currentQuestion.clues[idx]);
}
void printWrapped(const char* text, int col, int row, int maxWidth) {
lcd.setCursor(col,row);
int len = strlen(text);
for(int i=0;i<len;i++){
if(i>0 && i%maxWidth==0){ row++; lcd.setCursor(col,row); }
lcd.print(text[i]);
}
}
void displayAnswers() {
lcd.clear();
lcd.setCursor(0,0); lcd.print("A:"); printWrapped(answers[0],2,0,7);
lcd.setCursor(10,0); lcd.print("B:"); printWrapped(answers[1],12,0,8);
lcd.setCursor(0,2); lcd.print("C:"); printWrapped(answers[2],2,2,7);
lcd.setCursor(10,2); lcd.print("D:"); printWrapped(answers[3],12,2,8);
}
// ====================== FEEDBACK ======================
void showFeedback(bool correct){
lcd.clear();
if(correct){ digitalWrite(GREEN_LED,HIGH); playCorrectSound(); lcd.setCursor(0,0); lcd.print(playerNames[currentPlayer]); lcd.print(" Correct!"); }
else { digitalWrite(RED_LED,HIGH); playWrongSound(); lcd.setCursor(0,0); lcd.print(playerNames[currentPlayer]); lcd.print(" Wrong!"); lcd.setCursor(0,1); lcd.print("Answer:"); lcd.print(currentQuestion.correct); }
delay(800);
digitalWrite(GREEN_LED,LOW); digitalWrite(RED_LED,LOW);
}
// ====================== GAME LOOP ======================
void loop() {
while(true){
lcd.clear(); lcd.setCursor(0,0); lcd.print("Starting Round..."); delay(1000);
questionCount=0; playerScore[0]=0; playerScore[1]=0;
while(questionCount<QUESTIONS_PER_ROUND){
int qIndex = random(0,54); currentQuestion = questionBank[qIndex]; shuffleAnswers();
currentClue=0; buzzed=false; currentPlayer=-1;
while(currentClue<3 && currentPlayer==-1){
displayClue(currentClue);
unsigned long start = millis();
int interval = readClueInterval()*1000;
while((millis()-start)<interval){
if(digitalRead(BUZZER_BUTTON_1)==LOW){currentPlayer=0;buzzed=true; break;}
if(digitalRead(BUZZER_BUTTON_2)==LOW){currentPlayer=1;buzzed=true; break;}
delay(10);
}
if(!buzzed) currentClue++;
}
if(currentPlayer==-1){
lcd.clear(); lcd.setCursor(0,0); lcd.print("All clues shown!"); lcd.setCursor(0,1); lcd.print("Buzz to answer!");
while(currentPlayer==-1){
if(digitalRead(BUZZER_BUTTON_1)==LOW) currentPlayer=0;
if(digitalRead(BUZZER_BUTTON_2)==LOW) currentPlayer=1;
delay(10);
}
}
displayAnswers();
int selected=-1;
while(selected==-1){
if(digitalRead(BUTTON_A)==LOW) selected=0;
if(digitalRead(BUTTON_B)==LOW) selected=1;
if(digitalRead(BUTTON_C)==LOW) selected=2;
if(digitalRead(BUTTON_D)==LOW) selected=3;
delay(10);
}
bool correct = (selected==correctIndex);
if(correct && currentPlayer>=0) playerScore[currentPlayer]++;
showFeedback(correct);
questionCount++;
if(questionCount<QUESTIONS_PER_ROUND){ lcd.clear();
lcd.setCursor(0,0);
lcd.print("Next Question..."); delay(1000); }
}
lcd.clear();
lcd.setCursor(0,0);
lcd.print("Round Over!");
lcd.setCursor(0,2);
lcd.print(playerNames[0]);
lcd.print(": ");
lcd.print(playerScore[0]);
lcd.setCursor(0,3);
lcd.print(playerNames[1]);
lcd.print(": ");
lcd.print(playerScore[1]);
delay(3000);
lcd.clear();
lcd.setCursor(0,0);
lcd.print("Press buzzer to start again!");
while(digitalRead(BUZZER_BUTTON_1)==HIGH && digitalRead(BUZZER_BUTTON_2)==HIGH) delay(10);
playStartSound();
}
}