#include <WiFi.h>
#include <ESPAsyncWebSrv.h>
#include <FS.h>
#include <SD.h>
#include <SPI.h>
#include <ArduinoJson.h>
#include <Wire.h>
#include <LiquidCrystal.h>
#include <Keypad.h>
#include <Arduino.h>
// #include <Tone32.h>
#include <functional>
// #include <ESPAsyncWebServer.h>
// #include <SPIFFS.h>
// #define FORMAT_SPIFFS_IF_FAILED true
struct ResultDefuseBomb
{
bool gameFinished;
String teamName;
};
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;
};
void startSabotageGame();
void startDominationGame();
void startSearchDestroyGame();
void beep(unsigned int frequency, unsigned long duration = 0);
void noBeep();
void showRemainingTime(unsigned long &remainingGameTime);
void difusedBomb(ResultDefuseBomb &result, int stepTime);
void bombExploded(ResultDefuseBomb &result);
void updatePasswordDisplay(String &inputPassword, char &key);
bool checkPassword(String &inputPassword);
void handleIncorrectPassword(String &inputPassword, bool &defuseBomb, int &remainingDefuseTime);
ResultDefuseBomb 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 disableInputEffect();
void inputEffect();
void internalError(ResultDefuseBomb &result);
uint8_t BEEP = 22;
uint8_t RELAY = 2;
uint8_t LED_SUCCESS = 16;
uint8_t LED_FAILURE = 17;
uint8_t LCD_LENGTH = 16;
// LiquidCrystal lcd(19, 23, 18, 21, 5, 15);
LiquidCrystal lcd(13, 12, 14, 27, 26, 25);
String generatedPassword = "";
bool isAuthenticated = true;
const byte ROWS = 4;
const byte COLS = 4;
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};
byte rowPins[ROWS] = {33, 32, 16, 17};
byte colPins[COLS] = {15, 5, 3, 1};
Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS);
const char BTN_UP = 'A';
const char BTN_DOWN = 'B';
const char BTN_CANCEL = 'C';
const char BTN_CONFIRM = 'D';
const char BTN_ACTIVE = '#';
const char BTN_SHOW_TIME = '*';
Messages messages;
MenuOption menuOptions[] = {
{messages.pageInit, nullptr, 0},
{messages.searchDestroy, startSearchDestroyGame, 1},
{messages.sabotage, startSabotageGame, 1},
{messages.domination, startDominationGame, 2},
};
JsonDocument configDoc;
const char *ssid = "NomeDoAP"; // nome do AP
const char *password = "SenhaDoAP"; // senha do AP
AsyncWebServer server(80);
struct GameConfig
{
int gameDuration = 10;
int timeToArmBomb = 5;
int timeToDefuseBomb = 15;
int incorrectPasswordPenalty = 5;
int timeToActiveRelay = 10;
String password = "12345";
bool needPassword = true;
bool useSound = true;
bool useLED = true;
bool useRelay = true;
String specialForcesName = "SpecialForces";
String terroristsName = "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 readFile(fs::FS &fs, const char *path)
{
Serial.printf("Reading file: %s\r\n", path);
File file = fs.open(path);
if (!file || file.isDirectory())
{
Serial.println("- failed to open file for reading");
return;
}
Serial.println("- read from file:");
while (file.available())
{
Serial.write(file.read());
}
file.close();
}
void setup()
{
Serial.begin(115200);
WiFi.mode(WIFI_AP); // configura o ESP como AP
WiFi.softAP(ssid, password); // inicia o AP
Serial.println("AP iniciado!");
Serial.print("Endereço IP: ");
Serial.println(WiFi.softAPIP());
// if (!SPIFFS.begin(FORMAT_SPIFFS_IF_FAILED))
// {
// Serial.println("SPIFFS Mount Failed");
// return;
// }
if (!SD.begin(4))
{
Serial.println("Card Mount Failed");
return;
}
readFile(SD, "/aud.mp3");
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);
}
server.serveStatic("/", SD, "/data/").setDefaultFile("index.html");
server.begin();
}
void loop()
{
handleKeypadForMenu();
}
void displayMenu(int currentMenu)
{
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(menuOptions[currentMenu].message);
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));
}
}
void displayIPAndPassword()
{
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("IP:");
lcd.setCursor(0, 1);
lcd.print("Senha:");
lcd.print(generatedPassword);
delay(2000);
}
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);
}
}
}
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");
}
String generateRandomPassword()
{
String password = "";
for (int i = 0; i < 4; i++)
{
password += String(random(0, 10));
}
return password;
}
void playbeep()
{
tone(BEEP, 1000);
delay(100);
}
void startSabotageGame()
{
}
void startDominationGame()
{
}
void loading(int stepTime, bool reverse = false)
{
lcd.setCursor(0, 1);
if (!reverse)
{
for (int i = 0; i < LCD_LENGTH; i++)
{
lcd.write(2);
delay(stepTime);
}
}
else
{
for (int i = 0; i < LCD_LENGTH; i++)
{
lcd.write(2);
}
for (int i = LCD_LENGTH; i >= 0; i--)
{
lcd.setCursor(i, 1);
lcd.write(' ');
delay(stepTime);
}
}
}
void displayTime(unsigned long milliseconds)
{
unsigned long hours = milliseconds / 3600000;
milliseconds %= 3600000;
unsigned long minutes = milliseconds / 60000;
milliseconds %= 60000;
unsigned long seconds = milliseconds / 1000;
unsigned long ms = milliseconds % 1000;
char buffer[13];
sprintf(buffer, "%02lu:%02lu:%02lu:%03lu", hours, minutes, seconds, ms);
display(1, buffer, true);
}
void startSearchDestroyGame()
{
startGame(messages.searchDestroy);
display(0, "Jogo Iniciado:", true, true);
unsigned long startTime = millis();
unsigned long elapsedTime = 0;
bool plantedBomb = false;
int totalGameDuration = gameConfig.gameDuration * 1000;
int totalArmTime = gameConfig.timeToArmBomb * 1000;
int stepTime = totalArmTime / LCD_LENGTH;
ResultDefuseBomb result;
while (elapsedTime < totalGameDuration)
{
elapsedTime = millis() - startTime;
int remainingGameTime = totalGameDuration - elapsedTime;
char key = keypad.getKey();
if (!plantedBomb)
{
displayTime(remainingGameTime);
if (key == BTN_ACTIVE)
{
display(0, "Armando Bomba...", true, true);
loading(stepTime);
plantedBomb = true;
}
}
else
{
result = handleDefuseBomb(startTime, totalGameDuration, stepTime);
break;
}
}
if (plantedBomb && result.gameFinished)
{
handleGameFinished(result.teamName);
}
playAgain(startSearchDestroyGame);
}
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);
}
void internalError(ResultDefuseBomb &result)
{
display(0, "Erro Interno:", true, true);
display(1, "Tempo Esgotado!", true, true);
delay(2000);
result.gameFinished = false;
result.teamName = "";
}
ResultDefuseBomb handleDefuseBomb(unsigned long startTime, unsigned long totalGameDuration, int stepTime)
{
unsigned long defuseStartTime = millis();
unsigned long defuseElapsedTime = 0;
int remainingDefuseTime = gameConfig.timeToDefuseBomb * 1000;
bool defuseBomb = false;
String inputPassword;
ResultDefuseBomb result;
display(0, "Desarme a Bomba:", true, true);
while (defuseElapsedTime < remainingDefuseTime)
{
defuseElapsedTime = millis() - defuseStartTime;
unsigned long remainingGameTime = totalGameDuration - (millis() - startTime);
if (defuseElapsedTime >= remainingGameTime)
{
internalError(result);
return result;
}
if (defuseElapsedTime >= remainingDefuseTime)
{
bombExploded(result);
return result;
}
if (!defuseBomb)
{
displayTime(remainingDefuseTime - defuseElapsedTime);
}
char key = keypad.getKey();
if (key == BTN_SHOW_TIME)
{
showRemainingTime(remainingGameTime);
}
else if (key == BTN_CONFIRM)
{
defuseBomb = true;
if (gameConfig.needPassword)
{
passwordDisplay();
}
else
{
difusedBomb(result, stepTime);
return result;
}
}
else if (defuseBomb && gameConfig.needPassword)
{
if (isDigit(key))
{
updatePasswordDisplay(inputPassword, key);
}
else if (key == BTN_CANCEL)
{
deleteLastDigit(inputPassword);
}
if (inputPassword.length() == gameConfig.password.length())
{
if (checkPassword(inputPassword))
{
difusedBomb(result, stepTime);
return result;
}
else
{
handleIncorrectPassword(inputPassword, defuseBomb, remainingDefuseTime);
}
}
}
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();
}
}
bombExploded(result);
return result;
}
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);
inputEffect();
}
}
void inputEffect()
{
lcd.noCursor();
lcd.noBlink();
}
void disableInputEffect()
{
lcd.noCursor();
lcd.noBlink();
}
void bombExploded(ResultDefuseBomb &result)
{
disableInputEffect();
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);
delay(2000);
result.gameFinished = true;
result.teamName = gameConfig.terroristsName;
}
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 &remainingGameTime)
{
display(0, "Tempo Restante:", true, true);
unsigned long endTime = millis() + 2000;
unsigned long lastUpdateTime = 0;
while (millis() < endTime)
{
if (millis() - lastUpdateTime >= 50)
{
displayTime(remainingGameTime);
lastUpdateTime = millis();
}
}
display(0, "Desarme a Bomba:", true, true);
}
bool checkPassword(String &inputPassword)
{
disableInputEffect();
return inputPassword == gameConfig.password;
}
void handleIncorrectPassword(String &inputPassword, bool &defuseBomb, int &remainingDefuseTime)
{
disableInputEffect();
display(0, "Senha Incorreta.", true, true);
display(1, "Tente Novamente!", true);
beep(2000, 500);
delay(100);
remainingDefuseTime -= gameConfig.incorrectPasswordPenalty * 1000;
inputPassword = "";
defuseBomb = false;
display(0, "Desarme a Bomba:", true, true);
}
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();
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);
inputEffect();
}
}
void difusedBomb(ResultDefuseBomb &result, int stepTime)
{
display(0, "Bomba Desarmada!", true, true);
loading(stepTime, true);
if (gameConfig.useLED)
{
digitalWrite(LED_FAILURE, LOW);
digitalWrite(LED_SUCCESS, HIGH);
}
beep(2000, 500);
if (gameConfig.useRelay)
{
digitalWrite(RELAY, LOW);
}
delay(2000);
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 == BTN_CONFIRM)
{
func();
}
else if (key == BTN_CANCEL)
{
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();
}
scrollText("Pressione qualquer", 0, 1, 100);
scrollText("tecla para comecar", 1, 1, 100);
keypad.waitForKey();
}
void handleKeypadForMenu()
{
char key = keypad.getKey();
static int currentMenu = 0;
if (key)
{
tone(BEEP, 2400, 30);
switch (key)
{
case BTN_UP:
if (currentMenu > 0)
{
currentMenu--;
displayMenu(currentMenu);
}
break;
case BTN_DOWN:
if (currentMenu < (sizeof(menuOptions) / sizeof(menuOptions[0])) - 1)
{
currentMenu++;
displayMenu(currentMenu);
}
break;
case BTN_CANCEL:
displayMenu(0);
break;
case BTN_CONFIRM:
if (menuOptions[currentMenu].function != nullptr)
{
lcd.clear();
delay(100);
menuOptions[currentMenu].function();
}
break;
}
}
}