#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
// Pines de Hardware
#define BTN_SI 2 // Botón SÍ / Confirmar
#define BTN_NO 3 // Botón NO / Cancelar
#define BUZZER_PIN 8 // Buzzer
// Dimensiones de los ojos
int eyeW = 42;
int eyeH = 38;
int leftX = 8;
int rightX = 78;
int eyeY = 13;
// Estados del sistema
enum GameState { STATE_WELCOME, STATE_PET, STATE_QUIZ, STATE_RESULT };
GameState currentState = STATE_WELCOME;
// Estructura de Preguntas Dinámicas
struct DynamicQuestion {
char textOp[16];
char textAns[16];
bool isTrue;
};
DynamicQuestion currentQ;
int currentQuestionIndex = 0;
int score = 0;
unsigned long lastBlinkTime = 0;
// --- MELODÍA CONSTANTE EN SEGUNDO PLANO (NON-BLOCKING) --- //
int melody[] = { 262, 294, 330, 392, 330, 294, 262, 196, 220, 247, 262, 262 };
int noteDurations[] = { 200, 200, 200, 300, 200, 200, 200, 300, 200, 200, 250, 250 };
int totalNotes = sizeof(melody) / sizeof(melody[0]);
int currentNote = 0;
unsigned long lastNoteTime = 0;
bool isMelodyActive = true;
void playBackgroundMelody() {
if (!isMelodyActive) return;
unsigned long currentMillis = millis();
if (currentMillis - lastNoteTime >= noteDurations[currentNote] + 40) {
lastNoteTime = currentMillis;
tone(BUZZER_PIN, melody[currentNote], noteDurations[currentNote]);
currentNote = (currentNote + 1) % totalNotes;
}
}
// --- DIBUJO DE OJOS Y ANIMACIONES --- //
void drawEyes(int hLeft, int hRight, int yOffset = 0) {
display.clearDisplay();
if (hLeft > 0) {
display.fillRoundRect(leftX, eyeY + yOffset + (eyeH - hLeft) / 2, eyeW, hLeft, 8, SSD1306_WHITE);
}
if (hRight > 0) {
display.fillRoundRect(rightX, eyeY + yOffset + (eyeH - hRight) / 2, eyeW, hRight, 8, SSD1306_WHITE);
}
display.display();
}
void blink() {
for (int h = eyeH; h >= 4; h -= 6) {
drawEyes(h, h);
delay(15);
}
drawEyes(2, 2);
delay(60);
for (int h = 4; h <= eyeH; h += 6) {
drawEyes(h, h);
delay(15);
}
drawEyes(eyeH, eyeH);
}
void drawHeart(int x, int y) {
display.fillCircle(x - 8, y - 5, 8, SSD1306_WHITE);
display.fillCircle(x + 8, y - 5, 8, SSD1306_WHITE);
display.fillTriangle(x - 16, y - 2, x + 16, y - 2, x, y + 16, SSD1306_WHITE);
}
void animHappy() {
for (int i = 0; i < 2; i++) {
drawEyes(eyeH, 6);
delay(150);
drawEyes(eyeH, eyeH);
delay(150);
}
}
void animSad() {
display.clearDisplay();
display.fillRoundRect(leftX, eyeY + 10, eyeW, 12, 4, SSD1306_WHITE);
display.fillRoundRect(rightX, eyeY + 10, eyeW, 12, 4, SSD1306_WHITE);
display.display();
delay(500);
}
void animWin() {
for (int i = 0; i < 2; i++) {
drawEyes(eyeH, 3);
delay(300);
drawEyes(eyeH, eyeH);
delay(150);
}
display.clearDisplay();
drawHeart(29, 32);
drawHeart(99, 32);
display.display();
}
void animLaugh() {
for (int i = 0; i < 4; i++) {
drawEyes(6, 6, -6);
delay(80);
drawEyes(6, 6, 6);
delay(80);
}
drawEyes(eyeH, eyeH);
}
// --- EFECTOS DE SONIDO --- //
void soundCorrect() {
isMelodyActive = false;
tone(BUZZER_PIN, 523, 100); delay(120);
tone(BUZZER_PIN, 784, 150); delay(160);
noTone(BUZZER_PIN);
isMelodyActive = true;
}
void soundWrong() {
isMelodyActive = false;
tone(BUZZER_PIN, 300, 150); delay(180);
tone(BUZZER_PIN, 150, 250); delay(260);
noTone(BUZZER_PIN);
isMelodyActive = true;
}
void soundWinMelody() {
isMelodyActive = false;
int notes[] = {523, 659, 784, 1046};
for (int i = 0; i < 4; i++) {
tone(BUZZER_PIN, notes[i], 120);
delay(140);
}
noTone(BUZZER_PIN);
isMelodyActive = true;
}
void soundLaughBurla() {
isMelodyActive = false;
for (int i = 0; i < 3; i++) {
tone(BUZZER_PIN, 800, 70); delay(90);
tone(BUZZER_PIN, 600, 90); delay(110);
}
tone(BUZZER_PIN, 300, 300); delay(320);
noTone(BUZZER_PIN);
isMelodyActive = true;
}
// --- GENERADOR ALEATORIO DE PREGUNTAS --- //
void generateRandomQuestion() {
int opType = random(0, 4); // 0: Suma, 1: Resta, 2: Multiplicación, 3: División
int a = 0, b = 0, realResult = 0;
bool forceCorrect = (random(0, 2) == 1);
switch (opType) {
case 0: // Suma
a = random(1, 20);
b = random(1, 20);
realResult = a + b;
snprintf(currentQ.textOp, sizeof(currentQ.textOp), "%d + %d =", a, b);
break;
case 1: // Resta
a = random(5, 25);
b = random(1, a);
realResult = a - b;
snprintf(currentQ.textOp, sizeof(currentQ.textOp), "%d - %d =", a, b);
break;
case 2: // Multiplicación
a = random(2, 10);
b = random(2, 10);
realResult = a * b;
snprintf(currentQ.textOp, sizeof(currentQ.textOp), "%d x %d =", a, b);
break;
case 3: // División
b = random(2, 9);
realResult = random(2, 10);
a = b * realResult;
snprintf(currentQ.textOp, sizeof(currentQ.textOp), "%d / %d =", a, b);
break;
}
int displayedResult = realResult;
if (!forceCorrect) {
int offset = random(1, 4);
displayedResult = (random(0, 2) == 0) ? (realResult + offset) : (realResult - offset);
if (displayedResult < 0) displayedResult = realResult + offset;
}
snprintf(currentQ.textAns, sizeof(currentQ.textAns), "Es %d?", displayedResult);
currentQ.isTrue = (displayedResult == realResult);
}
// --- INTERFAZ Y FLUJO --- //
void showWelcomeScreen() {
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(20, 10);
display.println(F("¡BIENVENIDO!"));
display.setCursor(10, 30);
display.println(F("ROBOTICA EDUCATIVA"));
display.display();
soundWinMelody();
delay(1500);
blink();
currentState = STATE_PET;
}
void showQuestion() {
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.print(F("PREGUNTA "));
display.print(currentQuestionIndex + 1);
display.print(F("/10"));
display.drawLine(0, 10, 128, 10, SSD1306_WHITE);
display.setCursor(0, 18);
display.println(currentQ.textOp);
display.setCursor(0, 28);
display.println(currentQ.textAns);
// Botones
display.drawRect(0, 44, 58, 18, SSD1306_WHITE);
display.setCursor(12, 49);
display.print(F("[SI]"));
display.drawRect(70, 44, 58, 18, SSD1306_WHITE);
display.setCursor(82, 49);
display.print(F("[NO]"));
display.display();
}
void startQuiz() {
currentState = STATE_QUIZ;
currentQuestionIndex = 0;
score = 0;
generateRandomQuestion();
showQuestion();
}
void processAnswer(bool userAns) {
bool isCorrect = (userAns == currentQ.isTrue);
display.clearDisplay();
display.setTextSize(2);
display.setCursor(5, 20);
if (isCorrect) {
score++;
display.println(F("CORRECTO!"));
display.display();
soundCorrect();
animHappy();
} else {
display.println(F("INCORRECTO"));
display.display();
soundWrong();
animSad();
}
delay(600);
currentQuestionIndex++;
if (currentQuestionIndex < 10) {
generateRandomQuestion();
showQuestion();
} else {
currentState = STATE_RESULT;
display.clearDisplay();
display.setTextSize(1);
display.setCursor(15, 10);
display.print(F("Puntaje: "));
display.print(score);
display.println(F(" / 10"));
display.display();
delay(1500);
if (score >= 7) {
soundWinMelody();
animWin();
} else {
animLaugh();
soundLaughBurla();
}
delay(2000);
currentState = STATE_PET;
drawEyes(eyeH, eyeH);
}
}
// --- SETUP Y LOOP --- //
void setup() {
pinMode(BTN_SI, INPUT_PULLUP);
pinMode(BTN_NO, INPUT_PULLUP);
pinMode(BUZZER_PIN, OUTPUT);
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
while (true);
}
// Genera semilla aleatoria usando ruido analógico
randomSeed(analogRead(A0));
showWelcomeScreen();
}
void loop() {
playBackgroundMelody();
if (currentState == STATE_PET) {
if (millis() - lastBlinkTime > random(2000, 4500)) {
blink();
lastBlinkTime = millis();
}
if (digitalRead(BTN_SI) == LOW || digitalRead(BTN_NO) == LOW) {
delay(200); // Debounce
startQuiz();
}
}
else if (currentState == STATE_QUIZ) {
if (digitalRead(BTN_SI) == LOW) {
delay(200);
processAnswer(true);
} else if (digitalRead(BTN_NO) == LOW) {
delay(200);
processAnswer(false);
}
}
}