#include <WiFi.h>
// #include "ESPAsyncWebSrv.h" //Wokwi
// #include <ESPAsyncWebServer.h> //NodeMCU
#include <ArduinoJson.h>
#include <Wire.h>
#include <LiquidCrystal.h>
#include <Keypad.h>
#include <Arduino.h>
// #include <Tone32.h>
#include <functional>
struct ResultDefusedBomb
{
bool gameFinished;
String teamName;
};
// Estrutura para armazenar as mensagens carregadas do arquivo JSON
struct Messages
{
const char *pageInit = "Pagina Inicial";
const char *sabotage = "Sabotagem";
const char *domination = "Dominacao";
const char *searchDestroy = "Busca";
const char *startingGame = "Iniciando...";
const char *canceled = "Cancelado";
};
struct MenuOption
{
const char *message;
void (*function)();
int navigationIcon;
};
// Function scopes
void startSabotageGame();
void startDominationGame();
void startSearchDestroyGame();
void beep(unsigned int frequency, unsigned long duration = 0);
void noBeep();
void showRemainingTime(unsigned long totalGameDuration, unsigned long startTime);
void difusedBomb(int stepTime, ResultDefusedBomb &result);
void bombExploded(ResultDefusedBomb &result);
void updatePasswordDisplay(String &inputPassword, char &key);
bool checkPassword(String &inputPassword);
void handleIncorrectPassword(String &inputPassword, bool &defuseBomb, int &remainingDefuseTime);
ResultDefusedBomb handleDefuseBomb(unsigned long startTime, unsigned long totalGameDuration, int stepTime);
void passwordDisplay();
void splashScreen();
String generateRandomPassword();
void displayIPAndPassword();
void displayMenu(int currentMenu);
void handleKeypadForMenu();
void handleGameFinished(String teamName);
void deleteLastDigit(String &inputPassword);
void startGame(String gameName);
void playAgain(std::function<void()> func);
void disableInput();
// Pins
uint8_t BEEP = 22;
uint8_t RELAY = 2;
uint8_t LED_SUCCESS = 16;
uint8_t LED_FAILURE = 17;
uint8_t LCD_LENGTH = 16;
// Defina as credenciais de WiFi
// const char *ssid = "AirBombAjotark";
// const char *password = "ajotark";
// Crie uma instância do servidor
// AsyncWebServer server(80);
// Crie uma instância do display 16x2 (endereço I2C 0x27)
LiquidCrystal lcd(19, 23, 18, 21, 5, 15);
// Variável global de configurações
// JsonDocument configDoc;
// Variável para armazenar a senha gerada
String generatedPassword = "";
// Variável para armazenar se a senha já foi validada
bool isAuthenticated = true;
// Configuração do teclado 4x4
const byte ROWS = 4; // quatro linhas
const byte COLS = 4; // quatro colunas
char keys[ROWS][COLS] = {
{'1', '2', '3', 'A'},
{'4', '5', '6', 'B'},
{'7', '8', '9', 'C'},
{'*', '0', '#', 'D'}};
byte rowPins[ROWS] = {13, 12, 14, 27};
byte colPins[COLS] = {26, 25, 33, 32};
Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS);
Messages messages;
MenuOption menuOptions[] = {
{messages.pageInit, nullptr, 0},
{messages.searchDestroy, startSearchDestroyGame, 1},
{messages.sabotage, startSabotageGame, 1},
{messages.domination, startDominationGame, 2},
};
JsonDocument configDoc;
// Configurações do jogo
struct GameConfig
{
int gameDuration = 30; // 3600; // Duração do jogo em segundos
int timeToArmBomb = 5; // Tempo para armar a bomba em segundos
int timeToDefuseBomb = 15; // Tempo para desarmar a bomba em segundos
int incorrectPasswordPenalty = 5; // Penalização de senha incorreta
int timeToActiveRelay = 10; // Tempo para ativar o rele em segundos
String password = "12345"; // Senha para desarmar a bomba
bool needPassword = true; // Se é necessário senha para desarmar
bool useSound = true; // Interação com som
bool useLED = true; // Interação com LED
bool useRelay = true; // Interação com relé
String specialForcesName = "SpecialForces"; // Nome da equipe Special Forces
String terroristsName = "Terrorists"; // Nome da equipe Terrorists
};
GameConfig gameConfig;
byte ARROW_DOWN[8] = {B00000, B00000, B11111, B11111, B01110, B00100, B00000, B00000};
byte ARROW_UP[8] = {B00000, B00000, B00100, B01110, B11111, B11111, B00000, B00000};
byte LOADING[8] = {B11111, B11111, B11111, B11111, B11111, B11111, B11111, B11111};
void setup()
{
Serial.begin(115200);
pinMode(LED_SUCCESS, OUTPUT);
pinMode(LED_FAILURE, OUTPUT);
pinMode(RELAY, OUTPUT);
lcd.begin(LCD_LENGTH, 2);
lcd.createChar(0, ARROW_DOWN);
lcd.createChar(1, ARROW_UP);
lcd.createChar(2, LOADING);
splashScreen();
generatedPassword = generateRandomPassword();
displayIPAndPassword();
if (isAuthenticated)
{
displayMenu(0);
}
}
void loop()
{
handleKeypadForMenu();
}
// Função para exibir o menu com a seta e o jogo selecionado
void displayMenu(int currentMenu)
{
lcd.clear();
// Exibe o jogo selecionado
lcd.setCursor(0, 0);
lcd.print(menuOptions[currentMenu].message);
// Exibe as setas de navegação
if (menuOptions[currentMenu].navigationIcon == 0)
{
lcd.setCursor(15, 1);
lcd.write(byte(0));
}
else if (menuOptions[currentMenu].navigationIcon == 1)
{
lcd.setCursor(15, 0);
lcd.write(byte(1));
lcd.setCursor(15, 1);
lcd.write(byte(0));
}
else if (menuOptions[currentMenu].navigationIcon == 2)
{
lcd.setCursor(15, 0);
lcd.write(byte(1));
}
}
// Função para exibir o IP e a senha gerada no display
void displayIPAndPassword()
{
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("IP:");
// lcd.print(WiFi.localIP());
lcd.setCursor(0, 1);
lcd.print("Senha:");
lcd.print(generatedPassword);
delay(2000);
}
// Função para rolar o texto no display (marquee effect) com "loop" contínuo
void scrollText(const char *message, int row, int steps = 1, int delayTime = 200)
{
int messageLen = strlen(message);
for (int i = 0; i < steps; i++)
{
for (int i = 0; i < messageLen - LCD_LENGTH + 1; i++)
{
lcd.setCursor(0, row);
for (int j = 0; j < LCD_LENGTH; j++)
{
lcd.print(message[i + j]);
}
delay(delayTime);
}
for (int i = messageLen - LCD_LENGTH; i >= 0; i--)
{
lcd.setCursor(0, row);
for (int j = 0; j < LCD_LENGTH; j++)
{
if (i + j < messageLen)
{
lcd.print(message[i + j]);
}
else
{
lcd.print(message[i + j - messageLen]);
}
}
delay(delayTime);
}
}
}
// Função para exibir informações no display
void display(int row, const char *message, bool center = false, bool clear = false)
{
if (clear)
{
lcd.clear();
}
if (strlen(message) > LCD_LENGTH)
{
scrollText(message, row);
}
else
{
if (center)
{
int len = strlen(message);
int pad = (LCD_LENGTH - len) / 2;
lcd.setCursor(pad, row);
}
else
{
lcd.setCursor(0, row);
}
lcd.print(message);
}
}
void splashScreen()
{
display(0, "AIRBOMB V1", true, true);
display(1, "AIRBOMB V1 - @ajotanc");
}
// Função para gerar uma senha aleatória de 4 dígitos
String generateRandomPassword()
{
String password = "";
for (int i = 0; i < 4; i++)
{
password += String(random(0, 10)); // Gera um número aleatório entre 0 e 9
}
return password;
}
// Função para tocar o beep
void playbeep()
{
tone(BEEP, 1000); // Frequência do som
delay(100); // Duração
// noTone(BEEP);
}
// Funções para iniciar os modos de jogo (exemplo)
void startSabotageGame()
{
// Lógica para iniciar o modo Sabotage
}
void startDominationGame()
{
// Lógica para iniciar o modo Domination
}
void loading(int stepTime, bool reverse = false)
{
lcd.setCursor(0, 1);
if (!reverse)
{
// Preenche a tela com os caracteres
for (int i = 0; i < LCD_LENGTH; i++)
{
lcd.write(2); // Adiciona o caractere de loading
delay(stepTime);
}
}
else
{
// Preenche a tela com os caracteres
for (int i = 0; i < LCD_LENGTH; i++)
{
lcd.write(2); // Adiciona o caractere de loading
}
// Remove os caracteres do final para o início
for (int i = LCD_LENGTH; i >= 0; i--)
{
lcd.setCursor(i, 1); // Volta para o início da linha
lcd.write(' '); // Substitui pelo espaço
delay(stepTime); // Espera um pouco antes de remover o próximo
}
}
}
void displayTime(unsigned long milliseconds)
{
// Cálculos
unsigned long hours = milliseconds / 3600000;
milliseconds %= 3600000;
unsigned long minutes = milliseconds / 60000;
milliseconds %= 60000;
unsigned long seconds = milliseconds / 1000;
unsigned long ms = milliseconds % 1000;
// Formatação e exibição
char buffer[13];
sprintf(buffer, "%02lu:%02lu:%02lu:%03lu", hours, minutes, seconds, ms);
display(1, buffer, true); // Exibe a mensagem na linha 1
}
// Função para iniciar o modo "Busca" (Search & Destroy)
void startSearchDestroyGame()
{
startGame("Busca e Destruicao");
display(0, "Jogo Iniciado", true, true);
unsigned long startTime = millis();
unsigned long elapsedTime = 0;
bool plantedBomb = false;
int totalGameDuration = gameConfig.gameDuration * 1000; // Em milissegundos
int totalArmTime = gameConfig.timeToArmBomb * 1000; // Em milissegundos
int stepTime = totalArmTime / LCD_LENGTH; // Tempo dividido pelos 16 caracteres do display
ResultDefusedBomb result;
while (elapsedTime < totalGameDuration)
{
elapsedTime = millis() - startTime;
int remainingGameTime = totalGameDuration - elapsedTime;
char key = keypad.getKey();
if (!plantedBomb)
{
displayTime(remainingGameTime); // Exibe o tempo restante do jogo
if (key == '#')
{
display(0, "Armando Bomba...", true, true);
loading(stepTime);
display(0, "Bomba Armada!", true, true);
plantedBomb = true;
}
}
else
{
result = handleDefuseBomb(startTime, totalGameDuration, stepTime);
if (result.gameFinished)
{
break;
}
}
}
if (!result.gameFinished)
{
display(0, "Tempo Esgotado!", true, true);
delay(2000);
}
else
{
handleGameFinished(result.teamName);
}
}
void handleGameFinished(String teamName)
{
display(0, "Fim de Jogo!", true, true);
char mensagem[50];
sprintf(mensagem, "O time %s ganhou!", teamName.c_str());
scrollText(mensagem, 1);
playAgain(startSearchDestroyGame);
}
ResultDefusedBomb handleDefuseBomb(unsigned long startTime, unsigned long totalGameDuration, int stepTime)
{
unsigned long defuseStartTime = millis();
unsigned long defuseElapsedTime = 0;
int remainingDefuseTime = gameConfig.timeToDefuseBomb * 1000; // Tempo para desarmar
bool defuseBomb = false;
String inputPassword = "";
ResultDefusedBomb result;
result.gameFinished = false;
display(0, "Desarme a Bomba:", true, true);
while (defuseElapsedTime < remainingDefuseTime)
{
defuseElapsedTime = millis() - defuseStartTime;
char key = keypad.getKey();
if (defuseElapsedTime >= remainingDefuseTime)
{
bombExploded(result);
return result;
}
if (!defuseBomb)
{
displayTime(remainingDefuseTime - defuseElapsedTime);
}
if (defuseBomb && gameConfig.needPassword)
{
if (isDigit(key))
{
updatePasswordDisplay(inputPassword, key);
}
else if (key == 'C')
{
deleteLastDigit(inputPassword);
}
else if (inputPassword.length() == gameConfig.password.length())
{
if (checkPassword(inputPassword))
{
difusedBomb(stepTime, result);
return result;
}
else
{
handleIncorrectPassword(inputPassword, defuseBomb, remainingDefuseTime);
}
}
}
else if (!defuseBomb)
{
if (key == '*')
{
showRemainingTime(totalGameDuration, startTime);
}
else if (key == 'D')
{
defuseBomb = true;
if (gameConfig.needPassword)
{
passwordDisplay();
}
else
{
difusedBomb(stepTime, result);
return result;
}
}
}
if (remainingDefuseTime - defuseElapsedTime <= gameConfig.timeToActiveRelay * 1000 && gameConfig.useRelay)
{
digitalWrite(RELAY, HIGH);
}
if (defuseElapsedTime % 1000 < 50)
{
if (gameConfig.useLED)
{
digitalWrite(LED_FAILURE, HIGH);
}
beep(1000);
}
else
{
if (gameConfig.useLED)
{
digitalWrite(LED_FAILURE, LOW);
}
noBeep();
}
}
noBeep(); // Para o som se não foi desarmada
digitalWrite(RELAY, LOW); // Desliga o relé
digitalWrite(LED_FAILURE, LOW); // Apaga o LED vermelho
digitalWrite(LED_SUCCESS, LOW); // Apaga o LED verde
return result; // Se não foi desarmada, volta para o loop principal
}
void deleteLastDigit(String &inputPassword)
{
int passwordLength = inputPassword.length();
if (passwordLength > 0)
{
inputPassword.remove(passwordLength - 1);
passwordLength = inputPassword.length();
lcd.setCursor(passwordLength - 1, 1);
lcd.print(inputPassword[passwordLength - 1]);
lcd.setCursor(passwordLength, 1);
lcd.print('*');
lcd.setCursor(passwordLength, 1);
lcd.blink();
lcd.cursor();
}
}
void disableInput()
{
lcd.noCursor();
lcd.noBlink();
}
void bombExploded(ResultDefusedBomb &result)
{
disableInput();
beep(1500, 500);
if (gameConfig.useLED)
{
for (int i = 0; i < 10; i++)
{
digitalWrite(LED_FAILURE, HIGH);
delay(100);
digitalWrite(LED_FAILURE, LOW);
delay(100);
}
}
if (gameConfig.useRelay)
{
digitalWrite(RELAY, LOW);
}
display(0, "Bomba explodiu!", true, true);
display(1, "Bomba explodiu!", true);
result.gameFinished = true;
result.teamName = gameConfig.terroristsName;
delay(2000);
}
void beep(unsigned int frequency, unsigned long duration)
{
if (gameConfig.useSound)
{
tone(BEEP, frequency, duration);
}
}
void noBeep()
{
if (gameConfig.useSound)
{
noTone(BEEP);
}
}
void showRemainingTime(unsigned long totalGameDuration, unsigned long startTime)
{
display(0, "Tempo Restante:", true, true);
unsigned long endTime = millis() + 2000;
unsigned long lastUpdateTime = 0;
while (millis() < endTime)
{
if (millis() - lastUpdateTime >= 50)
{
unsigned long remainingTime = totalGameDuration - (millis() - startTime);
displayTime(remainingTime);
lastUpdateTime = millis();
}
}
display(0, "Desarme a Bomba:", true, true);
}
bool checkPassword(String &inputPassword)
{
disableInput();
return inputPassword == gameConfig.password;
}
void handleIncorrectPassword(String &inputPassword, bool &defuseBomb, int &remainingDefuseTime)
{
disableInput();
display(0, "Senha Incorreta.", true, true);
display(1, "Tente Novamente!", true);
beep(2000, 500);
delay(200);
remainingDefuseTime -= gameConfig.incorrectPasswordPenalty * 1000; // Penalidade de 5 segundos
inputPassword = ""; // Limpa o campo da senha
display(0, "Desarme a Bomba:", true, true);
defuseBomb = false;
}
void passwordDisplay()
{
display(0, "Senha:", true, true);
lcd.setCursor(0, 1);
for (int len = 0; len < gameConfig.password.length(); len++)
{
lcd.print('*');
}
lcd.setCursor(0, 1);
lcd.blink(); // Ativa o piscar do cursor
lcd.cursor();
}
void updatePasswordDisplay(String &inputPassword, char &key)
{
if (inputPassword.length() < gameConfig.password.length())
{
inputPassword += key;
lcd.setCursor(inputPassword.length() - 1, 1);
lcd.print(key);
for (int i = 0; i < inputPassword.length() - 1; i++)
{
lcd.setCursor(i, 1);
lcd.print('*');
}
lcd.setCursor(inputPassword.length(), 1);
lcd.blink();
lcd.cursor();
}
}
void difusedBomb(int stepTime, ResultDefusedBomb &result)
{
display(0, "Bomba Desarmada!", true, true);
loading(stepTime, true);
if (gameConfig.useLED)
{
digitalWrite(LED_FAILURE, LOW); // Apaga o LED vermelho
digitalWrite(LED_SUCCESS, HIGH); // Liga o LED verde
}
beep(2000, 500); // Som de desarme
if (gameConfig.useRelay)
{
digitalWrite(RELAY, LOW);
}
result.gameFinished = true;
result.teamName = gameConfig.specialForcesName;
}
/*
playAgain(minhaFuncao);
playAgain([]() { Serial.println("Olá, mundo!"); });
playAgain(std::bind(minhaOutraFuncao, 5, 10));
void minhaFuncao() {
Serial.println("Olá, mundo!");
}
void minhaOutraFuncao(int x, int y) {
Serial.println("Olá, mundo! x = " + String(x) + ", y = " + String(y));
}
*/
void playAgain(std::function<void()> func)
{
display(0, "Jogar novamente?", true, true);
display(1, "D: Sim / C: Nao", true);
char key = keypad.getKey();
if (key == 'D')
{
func();
}
else if (key == 'C')
{
displayMenu(0);
}
}
void startGame(String gameName)
{
if (gameConfig.useLED)
{
digitalWrite(LED_FAILURE, LOW);
digitalWrite(LED_SUCCESS, LOW);
}
if (gameConfig.useRelay)
{
digitalWrite(RELAY, LOW);
}
if (gameConfig.useSound)
{
noBeep();
}
display(0, gameName.c_str(), true, true);
scrollText("Pressione qualquer tecla para comecar", 1, 1, 100);
keypad.waitForKey();
}
void handleKeypadForMenu()
{
char key = keypad.getKey();
static int currentMenu = 0;
if (key)
{
tone(BEEP, 2400, 30); // Toca o beep
switch (key)
{
case 'A': // Mover para cima
if (currentMenu > 0)
{
currentMenu--;
displayMenu(currentMenu);
}
break;
case 'B': // Mover para baixo
if (currentMenu < (sizeof(menuOptions) / sizeof(menuOptions[0])) - 1)
{
currentMenu++;
displayMenu(currentMenu);
}
break;
case 'C': // Cancelar
displayMenu(0);
break;
case 'D': // Confirmar seleção
if (menuOptions[currentMenu].function != nullptr)
{
lcd.clear();
delay(100);
menuOptions[currentMenu].function();
}
break;
}
Serial.println(currentMenu);
}
}