#include <Wire.h>
#include <Keypad.h>
#include <LiquidCrystal_I2C.h>
byte customChars[20][8] = {
{B00000,B01010,B11111,B11111,B01110,B00100,B00000,B00000}, // Сердце (0)
{B11110,B10000,B10000,B11110,B10001,B10001,B11110,B00000}, // Буква "Б" (1)
{B11111,B10001,B10000,B10000,B10000,B10000,B10000,B00000}, // Буква "Г" (2)
{B01111,B00101,B00101,B01001,B10001,B11111,B10001,B00000}, // Буква "Д" (3)
{B10101,B10101,B10101,B11111,B10101,B10101,B10101,B00000}, // Буква "Ж" (4)
{B01110,B10001,B00001,B00010,B00001,B10001,B01110,B00000}, // Буква "З" (5)
{B10001,B10011,B10011,B10101,B11001,B11001,B10001,B00000}, // Буква "И" (6)
{B01110,B00000,B10001,B10011,B10101,B11001,B10001,B00000}, // Буква "Й" (7)
{B00011,B00111,B00101,B00101,B01101,B01001,B11001,B00000}, // Буква "Л" (8)
{B11111,B10001,B10001,B10001,B10001,B10001,B10001,B00000}, // Буква "П" (9)
{B10001,B10001,B10001,B01010,B00100,B01000,B10000,B00000}, // Буква "У" (10)
{B00100,B11111,B10101,B10101,B11111,B00100,B00100,B00000}, // Буква "Ф" (11)
{B10010,B10010,B10010,B10010,B10010,B10010,B11111,B00001}, // Буква "Ц" (12)
{B10001,B10001,B10001,B01111,B00001,B00001,B00001,B00000}, // Буква "Ч" (13)
{B10101,B10101,B10101,B10101,B10101,B10101,B11111,B00000}, // Буква "Ш" (14)
{B10101,B10101,B10101,B10101,B10101,B10101,B11111,B00001}, // Буква "Щ" (15)
{B10000,B10000,B10000,B11110,B10001,B10001,B11110,B00000}, // Буква "Ь" (16)
{B10001,B10001,B10001,B11001,B10101,B10101,B11001,B00000}, // Буква "Ы" (17)
{B10010,B10101,B10101,B11101,B10101,B10101,B10010,B00000}, // Буква "Ю" (18)
{B01111,B10001,B10001,B01111,B00101,B01001,B10001,B00000} // Буква "Я" (19)
};
// Конфигурация
#define LCD_ADDR 0x27
#define LCD_COLS 16
#define LCD_ROWS 2
#define BUZZER_PIN 2
#define MAX_LIVES 3 //Жизней на старте
#define BONUS_CONSECUTIVE 3 // прапвильх ответов за жизнь
#define DEFAULT_TIME_LIMIT 10 // таймер
// Инициализация устройств
LiquidCrystal_I2C lcd(LCD_ADDR, LCD_COLS, LCD_ROWS);
const byte ROWS = 4, COLS = 4;
char keys[ROWS][COLS] = {
{'1','2','3','A'},
{'4','5','6','B'},
{'7','8','9','C'},
{'*','0','#','D'}
};
byte rowPins[ROWS] = {10, 9, 8, 7};
byte colPins[COLS] = {6, 5, 4, 3};
Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS);
// Состояния игры
enum GameState { MAIN_MENU, STATS, GAME_A, GAME_B, GAME_C, GAME_D, GAME_OVER, SET_TIME };
GameState currentState = MAIN_MENU;
// Настройки игры
struct Settings {
uint8_t timeLimit; // в секундах
bool timerEnabled;
} settings = {DEFAULT_TIME_LIMIT, false};
// Статистика
struct Stats {
uint8_t correct;
uint8_t wrong;
uint8_t consecutive; // Для бонуса после 3 правильных ответов
uint8_t lives; // Количество жизней (максимум 16)
} stats = {0, 0, 0, MAX_LIVES};
// Данные игры
struct GameData {
int8_t num1, num2;
int16_t result;
int8_t answers[4];
char input[16];
uint8_t inputPos;
bool cursorVisible;
uint32_t blinkTime;
uint32_t questionStartTime;
uint32_t lastTimerUpdate;
char timeInput[3]; // Для ввода времени
uint8_t timeInputPos;
} game;
void setup() {
pinMode(BUZZER_PIN, OUTPUT);
lcd.init();
lcd.backlight();
randomSeed(analogRead(0));
showMainMenu();
// for(byte i=0;i<8;i++) lcd.createChar(i, customChars[i]);
lcd.createChar(0, customChars[6]);//и
lcd.createChar(1, customChars[11]);//ф
lcd.createChar(2, customChars[8]);//л
lcd.createChar(3, customChars[19]);//я
lcd.createChar(4, customChars[0]);//серце
lcd.createChar(5, customChars[9]);//п
lcd.createChar(6, customChars[1]);//б
lcd.createChar(7, customChars[3]);//д
}
void loop() {
char key = keypad.getKey();
if (key) {
game.questionStartTime = millis();
handleKey(key);
}
updateGameScreen();
}
void handleKey(char key) {
static uint32_t lastKeyTime = 0;
if (millis() - lastKeyTime < 200) return;
lastKeyTime = millis();
switch (currentState) {
case MAIN_MENU: handleMainMenu(key); break;
case STATS: handleStatsKey(key); break;
case GAME_A: handleGameA(key); break;
case GAME_B: handleGameB(key); break;
case GAME_C: handleGameC(key); break;
case GAME_D: handleGameD(key); break;
case GAME_OVER: if (key == '*') transitionToMain(); break;
case SET_TIME: handleTimeInput(key); break;
}
}
void updateGameScreen() {
if ((currentState == GAME_C || currentState == GAME_D) &&
millis() - game.blinkTime > 300) {
game.blinkTime = millis();
game.cursorVisible = !game.cursorVisible;
updateCursor();
}
if ((currentState == GAME_A || currentState == GAME_B ||
currentState == GAME_C || currentState == GAME_D) &&
millis() - game.lastTimerUpdate > 1000) {
game.lastTimerUpdate = millis();
updateTimerDisplay();
}
if ((currentState == GAME_A || currentState == GAME_B ||
currentState == GAME_C || currentState == GAME_D) &&
settings.timerEnabled &&
millis() - game.questionStartTime > settings.timeLimit * 1000) {
handleTimeOut();
}
}
void updateTimerDisplay() {
if (!settings.timerEnabled) {
lcd.setCursor(12, 0);
return;
}
uint32_t timeLeft = settings.timeLimit - (millis() - game.questionStartTime) / 1000;
if (timeLeft > 99) timeLeft = 99;
lcd.setCursor(13, 0);
if (timeLeft < 10) {
lcd.print(" ");
lcd.print(timeLeft);
lcd.print(" ");
} else {
lcd.print(timeLeft);
}
}
void showMainMenu() {
currentState = MAIN_MENU;
lcd.clear();
lcd.print("+ AP");lcd.write(0);lcd.write(1);lcd.print("MET");lcd.write(0);lcd.print("KA *");
lcd.setCursor(0, 1);
lcd.print("- A B C D /");
}
void showStats() {
currentState = STATS;
lcd.clear();
lcd.print("BPEM");lcd.write(3);lcd.print(" :");
if (settings.timerEnabled) {
lcd.print(settings.timeLimit);
lcd.print(" cek");
} else {
lcd.print("OTK");lcd.write(2);
}
lcd.setCursor(0, 1);
lcd.print(" * 0 #");
}
void handleStatsKey(char key) {
static uint32_t lastKeyTime = 0;
if (millis() - lastKeyTime < 200) return;
lastKeyTime = millis();
if (key == '*') {
transitionToMain();
}
else if (key == '#') {
showTimeInputScreen();
}
else if (key == '0') {
settings.timerEnabled = !settings.timerEnabled;
showStats();
}
}
void showTimeInputScreen() {
currentState = SET_TIME;
game.timeInputPos = 0;
memset(game.timeInput, 0, sizeof(game.timeInput));
lcd.clear();
lcd.print("Set time (0-99):");
lcd.setCursor(0, 1);
lcd.print(">");
}
void handleTimeInput(char key) {
if (key == '*') {
showStats();
return;
}
if (key == '#') {
if (game.timeInputPos > 0) {
settings.timeLimit = atoi(game.timeInput);
settings.timerEnabled = (settings.timeLimit > 0);
}
showStats();
return;
}
if (key >= '0' && key <= '9' && game.timeInputPos < 2) {
game.timeInput[game.timeInputPos++] = key;
game.timeInput[game.timeInputPos] = '\0';
lcd.setCursor(1, 1);
lcd.print(" ");
lcd.setCursor(1, 1);
lcd.print(game.timeInput);
}
}
void transitionToMain() {
playPageTurnSound();
showMainMenu();
}
void startGame(GameState gameType) {
currentState = gameType;
stats.correct = 0;
stats.wrong = 0;
stats.consecutive = 0;
stats.lives = MAX_LIVES; // Начинаем с максимального количества жизней
game.inputPos = 0;
memset(game.input, 0, sizeof(game.input));
nextQuestion();
}
void nextQuestion() {
game.questionStartTime = millis();
game.lastTimerUpdate = millis();
bool isMultiplication = (currentState == GAME_B || currentState == GAME_D);
game.num1 = random(isMultiplication ? 1 : 0, isMultiplication ? 10 : 20);
game.num2 = random(1, isMultiplication ? 10 : 20);
if (isMultiplication) {
game.result = game.num1 * game.num2;
} else {
if (random(2) == 0) {
game.result = game.num1 + game.num2;
} else {
if (game.num1 < game.num2) {
int8_t temp = game.num1;
game.num1 = game.num2;
game.num2 = temp;
}
game.result = game.num1 - game.num2;
}
}
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(game.num1);
lcd.print(isMultiplication ? "x" : (game.result == game.num1 + game.num2 ? "+" : "-"));
lcd.print(game.num2);
lcd.print("=");
updateTimerDisplay();
if (currentState == GAME_A || currentState == GAME_B) {
generateAnswers();
displayAnswers();
} else {
game.inputPos = 0;
updateCursor();
}
}
void generateAnswers() {
for (int i = 0; i < 4; i++) {
game.answers[i] = INT8_MIN;
}
game.answers[0] = game.result;
for (uint8_t i = 1; i < 4; i++) {
do {
game.answers[i] = game.result + random(-5, 6);
if (game.answers[i] < 0) game.answers[i] = 0;
} while (game.answers[i] == game.result || containsAnswer(game.answers, i, game.answers[i]));
}
for (uint8_t i = 0; i < 4; i++) {
uint8_t j = random(4);
int8_t temp = game.answers[i];
game.answers[i] = game.answers[j];
game.answers[j] = temp;
}
}
void displayAnswers() {
for (uint8_t i = 0; i < 4; i++) {
lcd.setCursor(i*4, 1);
lcd.print(game.answers[i]);
}
}
void updateCursor() {
lcd.setCursor(game.inputPos, 1);
lcd.print(game.cursorVisible ? "_" : " ");
}
void checkAnswer(int16_t userAnswer) {
bool isCorrect = (userAnswer == game.result);
if (isCorrect) {
stats.correct++;
stats.consecutive++;
// Каждые 3 правильных ответа добавляем жизнь (но не больше MAX_LIVES)
if (stats.consecutive >= BONUS_CONSECUTIVE) {
stats.consecutive = 0;
if (stats.lives < MAX_LIVES) {
stats.lives++;
playBonusSound();
}
}
} else {
stats.wrong++;
stats.consecutive = 0;
// Уменьшаем количество жизней
if (stats.lives > 0) {
stats.lives--;
}
}
lcd.setCursor(6, 0);
lcd.print(game.result);
// Отображаем строку жизней
lcd.setCursor(13, 0);
for (uint8_t i = 0; i < MAX_LIVES; i++) {
if (i < stats.lives) {
lcd.write(4);
//lcd.print("+");
} else {
lcd.print(" ");
}
}
if (isCorrect) {
playCorrectSound();
} else {
playWrongSound();
}
delay(2000);
if (stats.lives == 0) {
gameOver();
} else {
game.inputPos = 0;
memset(game.input, 0, sizeof(game.input));
nextQuestion();
}
}
void gameOver() {
currentState = GAME_OVER;
lcd.clear();
lcd.print(" KOHE");lcd.write(0);
lcd.setCursor(6, 1);
lcd.print(stats.correct);
lcd.print("/");
lcd.print(stats.wrong);
playGameOverSound();
}
void checkVictory() {
currentState = GAME_OVER;
lcd.createChar(4, customChars[15]);//щ
lcd.clear();
// Переменные для управления анимацией
static unsigned long previousMillis = 0;
static int animationPhase = 0;
static int blinkCount = 0;
static int starAnimationPos = 0;
const long interval = 200; // Интервал анимации
// Начало анимации победы
playMarioVictory();
while (true) {
unsigned long currentMillis = millis();
char key = keypad.getKey();
// Проверка нажатия для выхода
if (key == '*') {
transitionToMain();
return;
}
// Анимация мигания текста
if (blinkCount < 6) {
if (currentMillis - previousMillis >= interval) {
previousMillis = currentMillis;
if (animationPhase == 0) {
lcd.clear();
lcd.print(" ");
lcd.write(5); lcd.print("O"); lcd.write(6); lcd.print("E"); lcd.write(7); lcd.print("A");
lcd.setCursor(0, 1);
lcd.print("*");
animationPhase = 1;
} else {
lcd.clear();
if (blinkCount % 2 == 1) {
lcd.print(" + - * / ");
lcd.write(5); lcd.print(" O "); lcd.write(6); lcd.print(" E "); lcd.write(7); lcd.print(" A");
lcd.setCursor(0, 1);
lcd.print(" + - * /");
}
animationPhase = 0;
blinkCount++;
}
}
}
// Анимация звездочек после завершения мигания
else {
if (currentMillis - previousMillis >= 100) {
previousMillis = currentMillis;
lcd.clear();
lcd.setCursor(starAnimationPos, 0);
lcd.print("*");
lcd.setCursor(15 - starAnimationPos, 1);
lcd.print("*");
if (starAnimationPos < 3 || starAnimationPos > 13) {
lcd.setCursor(6, 0);
lcd.print("E "); lcd.write(4); lcd.print(" E");
lcd.setCursor(6, 1);
lcd.print("P A 3");
}
starAnimationPos++;
if (starAnimationPos > 15) {
starAnimationPos = 0;
}
}
}
}
}
void handleMainMenu(char key) {
static uint32_t lastKeyTime = 0;
if (millis() - lastKeyTime < 200) return;
lastKeyTime = millis();
switch (key) {
case 'A': playCoinSound(); startGame(GAME_A); break;
case 'B': playCoinSound(); startGame(GAME_B); break;
case 'C': playCoinSound(); startGame(GAME_C); break;
case 'D': playCoinSound(); startGame(GAME_D); break;
case '*': playCoinSound(); showStats(); break;
}
}
void handleGameA(char key) {
static uint32_t lastKeyTime = 0;
if (millis() - lastKeyTime < 200) return;
lastKeyTime = millis();
if (key == '*') {
transitionToMain();
}
else if (key >= 'A' && key <= 'D') {
if (game.answers[0] != INT8_MIN) {
checkAnswer(game.answers[key-'A']);
}
}
}
void handleGameB(char key) {
static uint32_t lastKeyTime = 0;
if (millis() - lastKeyTime < 200) return;
lastKeyTime = millis();
handleGameA(key);
}
void handleGameC(char key) {
static uint32_t lastKeyTime = 0;
if (millis() - lastKeyTime < 200) return;
lastKeyTime = millis();
if (game.inputPos == 0 && key == '*') {
transitionToMain();
return;
}
if (key >= '0' && key <= '9' && game.inputPos < 5) {
game.input[game.inputPos++] = key;
game.input[game.inputPos] = '\0';
lcd.setCursor(game.inputPos-1, 1);
lcd.print(key);
updateCursor();
}
else if (key == '*' && game.inputPos > 0) {
game.input[--game.inputPos] = '\0';
lcd.setCursor(game.inputPos, 1);
lcd.print(" ");
updateCursor();
}
else if (key == '#') {
if (game.inputPos > 0) {
int userAnswer = atoi(game.input);
checkAnswer(userAnswer);
}
}
}
void handleGameD(char key) {
static uint32_t lastKeyTime = 0;
if (millis() - lastKeyTime < 200) return;
lastKeyTime = millis();
if (game.inputPos == 0 && key == '*') {
transitionToMain();
return;
}
if (key >= '0' && key <= '9' && game.inputPos < 3) {
game.input[game.inputPos++] = key;
game.input[game.inputPos] = '\0';
lcd.setCursor(game.inputPos-1, 1);
lcd.print(key);
updateCursor();
}
else if (key == '*' && game.inputPos > 0) {
game.input[--game.inputPos] = '\0';
lcd.setCursor(game.inputPos, 1);
lcd.print(" ");
updateCursor();
}
else if (key == '#') {
if (game.inputPos > 0) {
checkAnswer(atoi(game.input));
}
}
}
bool containsAnswer(int8_t arr[], uint8_t size, int8_t value) {
for (uint8_t i = 0; i < size; i++) {
if (arr[i] == value) return true;
}
return false;
}
void handleTimeOut() {
static uint32_t lastKeyTime = 0;
if (millis() - lastKeyTime < 200) return;
lastKeyTime = millis();
// Уменьшаем количество жизней
if (stats.lives > 0) {
stats.lives--;
}
stats.consecutive = 0;
lcd.setCursor(6, 0);
lcd.print(game.result);
// Отображаем строку жизней
lcd.setCursor(0, 1);
for (uint8_t i = 0; i < MAX_LIVES; i++) {
if (i < stats.lives) {
lcd.print("+");
} else {
lcd.print("-");
}
}
playTimeOutSound();
delay(2000);
if (stats.lives == 0) {
gameOver();
} else {
nextQuestion();
}
}
void playPageTurnSound() {
tone(BUZZER_PIN, 523, 100);
delay(100);
tone(BUZZER_PIN, 659, 100);
delay(100);
tone(BUZZER_PIN, 784, 100);
delay(100);
noTone(BUZZER_PIN);
}
void playCoinSound() {
tone(BUZZER_PIN, 784, 100);
delay(100);
tone(BUZZER_PIN, 1046, 100);
delay(100);
noTone(BUZZER_PIN);
}
void playCorrectSound() {
tone(BUZZER_PIN, 1046, 100);
delay(100);
tone(BUZZER_PIN, 1175, 100);
delay(100);
tone(BUZZER_PIN, 1318, 200);
delay(200);
noTone(BUZZER_PIN);
}
void playWrongSound() {
for (int i = 0; i < 3; i++) {
tone(BUZZER_PIN, 200, 200);
delay(200);
noTone(BUZZER_PIN);
delay(100);
}
}
void playBonusSound() {
tone(BUZZER_PIN, 1318, 150);
delay(150);
tone(BUZZER_PIN, 1568, 150);
delay(150);
tone(BUZZER_PIN, 1760, 150);
delay(150);
tone(BUZZER_PIN, 1975, 300);
delay(300);
noTone(BUZZER_PIN);
}
void playGameOverSound() {
tone(BUZZER_PIN, 220, 400);
delay(400);
tone(BUZZER_PIN, 196, 400);
delay(400);
tone(BUZZER_PIN, 175, 400);
delay(400);
tone(BUZZER_PIN, 165, 800);
delay(800);
noTone(BUZZER_PIN);
}
void playMarioVictory() {
const int m[] = {
523,392,330,440,494,466,440,392,659,784,880,698,784,659,523,587,494,
523,392,330,440,494,466,440,392,659,784,880,698,784,659,523,587,494,
784,740,698,622,659,415,440,523,440,523,587,784,740,698,622,659,
1047,1047,1047,1047,1047,1047
};
const byte d[] = {
200,100,200,200,200,100,200,125,125,125,200,100,200,200,100,200,200,
200,100,200,200,200,100,200,125,125,125,200,100,200,200,100,200,200,
100,100,100,200,100,100,100,100,100,100,100,100,100,100,200,100,200,200,200,100,400
};
for (int i = 0; i < 56; i++) {
(i%2==0) ? lcd.noBacklight() : lcd.backlight();
tone(BUZZER_PIN, m[i], d[i]);
delay(d[i] * 1.3);
noTone(BUZZER_PIN);
}
}
void playTimeOutSound() {
tone(BUZZER_PIN, 100, 500);
delay(500);
tone(BUZZER_PIN, 80, 500);
delay(500);
noTone(BUZZER_PIN);
}