#include <Wire.h>
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 20, 4);
// === PIN SETUP ===
#define JOY_HORZ A0
#define JOY_VERT A1
#define JOY_SW 7
#define LED_RED 2
#define LED_YELLOW 3
#define LED_GREEN 4
#define BUZZER 8
// === VARIABEL GLOBAL ===
int selectedGame = 0;
bool inMenu = true;
int score = 0;
// === FUNGSI ===
void menu();
void gameColorRunner();
void gameReactionTimer();
void gameLEDMemory();
void endGame(int score);
void beep(int freq, int dur);
// === SETUP ===
void setup() {
lcd.init();
lcd.backlight();
pinMode(LED_RED, OUTPUT);
pinMode(LED_YELLOW, OUTPUT);
pinMode(LED_GREEN, OUTPUT);
pinMode(JOY_SW, INPUT_PULLUP);
pinMode(BUZZER, OUTPUT);
randomSeed(analogRead(A2));
lcd.clear();
lcd.setCursor(4, 1);
lcd.print(" WELCOME!");
beep(800, 100);
delay(100);
beep(1000, 100);
delay(800);
}
void loop() {
if (inMenu) menu();
else {
switch (selectedGame) {
case 0: gameColorRunner(); break;
case 1: gameReactionTimer(); break;
case 2: gameLEDMemory(); break;
}
}
}
// === MENU ===
void menu() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("=== PILIH GAME ===");
const char* games[] = {"1. COLOR RUNNER", "2. Reaction Timer", "3. LED Memory"};
lcd.setCursor(0, 2);
lcd.print(games[selectedGame]);
delay(200);
int horz = analogRead(JOY_HORZ);
if (horz < 300) { selectedGame = (selectedGame + 2) % 3; beep(700, 80); }
else if (horz > 700) { selectedGame = (selectedGame + 1) % 3; beep(900, 80); }
if (digitalRead(JOY_SW) == LOW) {
lcd.clear();
lcd.setCursor(5, 1);
lcd.print("Loading...");
beep(1000, 100);
delay(500);
inMenu = false;
}
}
// === SUARA ===
void beep(int freq, int dur) {
tone(BUZZER, freq, dur);
delay(dur);
noTone(BUZZER);
}
// === GAME 1: COLOR RUNNER ===
void gameColorRunner() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Game: COLOR RUNNER");
delay(1000);
int lives = 3;
score = 0;
int speed = 600;
while (lives > 0 && score < 25) {
int target = random(0, 3);
lcd.setCursor(0, 1);
lcd.print("Nyawa: ");
for (int i = 0; i < lives; i++) lcd.print((char)3);
lcd.print(" ");
lcd.setCursor(0, 2);
lcd.print("Score: "); lcd.print(score);
lcd.setCursor(0, 3);
lcd.print("Arah!");
digitalWrite(LED_RED, target == 0);
digitalWrite(LED_YELLOW, target == 1);
digitalWrite(LED_GREEN, target == 2);
beep(600 + target * 200, 100);
unsigned long startTime = millis();
bool answered = false;
bool correct = false;
while (millis() - startTime < speed) {
int horz = analogRead(JOY_HORZ);
int vert = analogRead(JOY_VERT);
int move = -1;
if (horz < 300) move = 0; // kiri = merah
else if (vert > 700) move = 1; // atas = kuning
else if (horz > 700) move = 2; // kanan = hijau
if (move != -1) {
answered = true;
if (move == target) {
correct = true;
break;
} else break;
}
}
digitalWrite(LED_RED, LOW);
digitalWrite(LED_YELLOW, LOW);
digitalWrite(LED_GREEN, LOW);
if (answered && correct) {
score++;
lcd.setCursor(0, 3);
lcd.print("Benar! ");
beep(1200, 100);
if (speed > 200) speed -= 30;
}
else if (answered && !correct) {
lives--;
lcd.setCursor(0, 3);
lcd.print("Salah! ");
beep(300, 200);
}
else {
lives--;
lcd.setCursor(0, 3);
lcd.print("Terlambat! ");
beep(250, 200);
}
delay(600);
}
endGame(score);
}
// === GAME 2: REACTION TIMER ===
void gameReactionTimer() {
score = 0;
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Game: Reaction Timer");
delay(1000);
while (score < 25) {
int target = random(0, 3);
digitalWrite(LED_RED, target == 0);
digitalWrite(LED_YELLOW, target == 1);
digitalWrite(LED_GREEN, target == 2);
beep(700 + target * 150, 150);
unsigned long startTime = millis();
bool reacted = false;
while (!reacted) {
int horz = analogRead(JOY_HORZ);
int vert = analogRead(JOY_VERT);
int move = -1;
if (horz < 300) move = 0;
else if (vert > 700) move = 1;
else if (horz > 700) move = 2;
if (move != -1) {
reacted = true;
unsigned long reaction = millis() - startTime;
lcd.clear();
lcd.setCursor(0, 1);
if (move == target) {
lcd.print("Waktu: "); lcd.print(reaction); lcd.print(" ms");
beep(1000, 100);
if (reaction < 1000) score += 2;
else if (reaction < 2000) score += 1;
} else {
lcd.print("Salah arah!");
beep(300, 200);
score--;
}
digitalWrite(LED_RED, LOW);
digitalWrite(LED_YELLOW, LOW);
digitalWrite(LED_GREEN, LOW);
delay(800);
}
}
}
endGame(score);
}
// === GAME 3: LED MEMORY ===
void gameLEDMemory() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Game: LED Memory");
delay(1000);
int sequence[15];
int length = 3;
score = 0;
for (int i = 0; i < 15; i++) sequence[i] = random(0, 3);
while (score < 25) {
lcd.clear();
lcd.setCursor(0, 1);
lcd.print("Tonton Pola...");
delay(800);
for (int i = 0; i < length; i++) {
digitalWrite(LED_RED, sequence[i] == 0);
digitalWrite(LED_YELLOW, sequence[i] == 1);
digitalWrite(LED_GREEN, sequence[i] == 2);
beep(500 + sequence[i] * 200, 150);
delay(300);
digitalWrite(LED_RED, LOW);
digitalWrite(LED_YELLOW, LOW);
digitalWrite(LED_GREEN, LOW);
delay(250);
}
lcd.clear();
lcd.setCursor(0, 1);
lcd.print("Ulangi Pola!");
bool correct = true;
for (int i = 0; i < length && correct; i++) {
int move = -1;
while (move == -1) {
int horz = analogRead(JOY_HORZ);
int vert = analogRead(JOY_VERT);
if (horz < 300) move = 0;
else if (vert > 700) move = 1;
else if (horz > 700) move = 2;
}
beep(500 + move * 200, 100);
if (move != sequence[i]) correct = false;
delay(400);
}
if (correct) {
score += 3;
length++;
lcd.setCursor(0, 3);
lcd.print("Benar!");
beep(1200, 150);
} else {
lcd.setCursor(0, 3);
lcd.print("Salah!");
beep(300, 300);
break;
}
delay(800);
}
endGame(score);
}
// === AKHIR GAME ===
void endGame(int score) {
lcd.clear();
if (score >= 25) {
lcd.setCursor(4, 1);
lcd.print("VICTORY");
beep(800, 100); beep(1000, 100); beep(1200, 100);
} else {
lcd.setCursor(4, 1);
lcd.print("DEFEAT");
beep(400, 200);
}
lcd.setCursor(3, 2);
lcd.print("Score: "); lcd.print(score);
lcd.setCursor(0, 3);
lcd.print("Kanan=Lagi Tekan=Menu");
// tunggu aksi pemain
while (true) {
int horz = analogRead(JOY_HORZ);
if (horz > 700) { // joystick kanan → ulang
beep(900, 100);
lcd.clear();
inMenu = false;
delay(400);
return;
}
if (digitalRead(JOY_SW) == LOW) { // tombol tekan → menu
beep(700, 100);
inMenu = true;
delay(400);
return;
}
}
}