BlueBomb Console este o miniconsola ce pune la dispozitie jocul de MineSweeper multiplayer prin Bluetooth. Cu ajutorul unei aplicatii Android, jucatorii se pot conecta la consola prin intermediul Bluetooth si se pot duela pentru titlul de cel mai bun <insert titulatura pozitiva>.
Scopul proiectului este de a crea un portable entertainment device, care sa nu depinda de conexiunea la reteaua de WiFi.
Ideea Generala a proiectului consta in proiectarea unui device ce va putea fi folosit atat pentru un joc single player de Minesweeper, cat si a unuia multiplayer. Device-ul este echipat cu un ecran LCD pe care va fi proiectat atat jocul cat si mesaje pentru utilizatori si un minispeaker pentru a crea o experienta unica de joc.
Exemplu de schemă bloc: http://www.robs-projects.com/mp3proj/newplayer.html
Componente folosite:
Nume Componenta | Descriere | Datasheet | |
---|---|---|---|
TTGO T-Display | Placa de dezvoltare integrata | ||
ESPRESSIF-ESP32 240MHz Xtensa* | Microcontroler care gestioneaza interactiunea cu clientii si da comenzi catre celelalte componente | Datasheet | |
ST7789V IPS LCD* | Display | Datasheet | |
LM386 | Audio Power Amplifier | Datasheet | |
Butoane | |||
Fire de legatura |
* - Componentele marcate sunt integrate in placa de dezvoltare TTGO T-Display. Datasheet-ul este util pentru urmarirea implementarii functionalitatilor pe aceste componente. }
Arhitectura software este împărțită în următoarele componente principale:
* Logica de joc - implementată în clasa Minesweeper
* Comunicație Bluetooth - gestionată prin BLE
* Interfață grafică - utilizând biblioteca TFT_eSPI
* Gestionarea mesajelor - coadă de mesaje pentru procesare asincronă
* Handlere de evenimente - pentru întreruperi hardware și evenimente Bluetooth
Acest fișier conține programul principal și coordonează celelalte componente.
Includerea bibliotecilor necesare:
#include <Arduino.h> // functii de baza folosite pentru simplitate #include <BLEDevice.h> #include <BLEServer.h> // Pentru folosirea modulului esp32 pe post de server #include <BLE2902.h> #include <TFT_eSPI.h> // functionalitati display #include "minesweeper.h" #include "bt_commands.h" // Traduce comenzile de la aplicatia de pe smartphone
Inițializarea obiectelor principale:
TFT_eSPI tft = TFT_eSPI(); // Obiect pentru controlul ecranului TFT Minesweeper game; // Obiect pentru logica jocului
Coada de mesaje pentru procesarea asincronă:
#define MAX_MESSAGES 10 #define MAX_MESSAGE_LENGTH 100 typedef struct { uint16_t length; uint8_t data[MAX_MESSAGE_LENGTH]; } message_t; message_t messageQueue[MAX_MESSAGES]; int messageQueueHead = 0; int messageQueueTail = 0; portMUX_TYPE messageQueueMux = portMUX_INITIALIZER_UNLOCKED;
Funcții pentru gestionarea cozii de mesaje:
Aceasta permite o experienta fluida in cadrul jocului, mesajele fiind procesate atunci cand
microcontrollerul are timp pentru ele.
bool addMessageToQueue(uint8_t *data, size_t length) { if (length > MAX_MESSAGE_LENGTH) { length = MAX_MESSAGE_LENGTH; // Truncate if too long } portENTER_CRITICAL(&messageQueueMux); int nextHead = (messageQueueHead + 1) % MAX_MESSAGES; if (nextHead == messageQueueTail) { // Queue is full portEXIT_CRITICAL(&messageQueueMux); return false; } messageQueue[messageQueueHead].length = length; memcpy(messageQueue[messageQueueHead].data, data, length); messageQueueHead = nextHead; portEXIT_CRITICAL(&messageQueueMux); return true; } bool getMessageFromQueue(message_t *message) { portENTER_CRITICAL(&messageQueueMux); if (messageQueueHead == messageQueueTail) { // Queue is empty portEXIT_CRITICAL(&messageQueueMux); return false; } *message = messageQueue[messageQueueTail]; messageQueueTail = (messageQueueTail + 1) % MAX_MESSAGES; portEXIT_CRITICAL(&messageQueueMux); return true; }
Callback pentru evenimente Bluetooth:
// Server & Characteristic callbacks class MyServerCallbacks : public BLEServerCallbacks { // onConnect, onDisconnect } BLEServer *pServer = NULL; BLECharacteristic *pCharacteristic = NULL; class MyCallbacks : public BLECharacteristicCallbacks { void onWrite(BLECharacteristic *pCharacteristic, esp_ble_gatts_cb_param_t *param) { std::string value = pCharacteristic->getValue(); if (value.length() > 0) { Serial.print("Received Value: "); for (int i = 0; i < value.length(); i++) { Serial.print(value[i]); } // Send back the received value Serial.printf(" From MAC: %02X:%02X:%02X:%02X:%02X:%02X\n", param->write.bda[0], param->write.bda[1], param->write.bda[2], param->write.bda[3], param->write.bda[4], param->write.bda[5]); // Send back the received value // Add to message queue if (!addMessageToQueue(param->write.bda, (uint8_t *)value.c_str(), value.length())) { Serial.println("Message queue is full, dropping message"); } pCharacteristic->setValue(value); pCharacteristic->notify(); Serial.println(); } } };
#define WIDTH 8 #define HEIGHT 16 #define NUM_BOMBS (WIDTH * HEIGHT / 10) class Minesweeper { private: uint8_t bombs[NUM_BOMBS]; uint8_t flag_is_revealed[(WIDTH * HEIGHT + 7) / 8]; // common for both players uint8_t player_position[2]; uint8_t marked_as_bomb[2][(WIDTH * HEIGHT + 7) / 8]; // For marking positions as bombs int player_turn; // 0 or 1, which player is currently playing bool is_lost; void _reveal_until_neighbouring_bomb(uint8_t position); public: Minesweeper(); static inline uint8_t get_x_pos(uint8_t position); static inline uint8_t get_y_pos(uint8_t position); uint8_t is_bomb(uint8_t position) { { for (int i = 0; i < NUM_BOMBS; i++) { if (bombs[i] == position) { return 1; } } return 0; } } void move_player(command_t command); uint8_t get_player_position() { return player_position[player_turn]; } inline bool is_revealed(uint8_t position); inline void set_revealed(uint8_t position); bool is_marked_as_bomb(uint8_t position); void set_marked_as_bomb(uint8_t position); void builtin_button_pressed(); bool shoot(); uint8_t how_many_neighbouring_bombs(uint8_t position); inline bool is_game_over() { return is_lost; } void draw_map(TFT_eSPI &tft); bool won(); inline void set_player_turn(int turn) { player_turn = turn; } bool displayed_final = false; };
Pentru a economisi memorie, starea hărții este stocată eficient folosind biți individuali:
bool Minesweeper::is_revealed(uint8_t position) { int32_t x = get_x_pos(position); int32_t y = get_y_pos(position); return (flag_is_revealed[x] & (1 << y)) != 0; }
Pentru a asigura faptul ca microcontrollerul poate primi mesaje, executa operatii in timp ce canta, initializam un timer si in cod ne vom referi mereu la el:
* Cod pentru folosirea unui timer custom:
hw_timer_t *my_timer = NULL; uint32_t timerCounter = 0; // this increments every milisecond void IRAM_ATTR timerISR() { timerCounter++; } // Initializare: my_timer = timerBegin(0, 80, true); // Timer 0, prescaler 80 (1 tick = 1 us) timerAttachInterrupt(my_timer, &timerISR, true); // Attach the interrupt timerAlarmWrite(my_timer, 1000000 / 1000, true); // Set alarm for 1/1000 second timerAlarmEnable(my_timer); // Enable the alarm
* Cod in bucla main pentru buzzer
if (play_song) { if (currentNote < note_size) { int noteDuration = 1000 / curr_durations[currentNote]; if (!action_playing) { action_playing = true; // Set the action as playing tone(BUZZZER_PIN, curr_notes[currentNote], noteDuration); } if (timerCounter - lastTimerCheck >= noteDuration + noteDuration / 4) // Check if enough time has passed { lastTimerCheck = timerCounter; // Update the last check time noTone(BUZZZER_PIN); currentNote++; Serial.printf("Current note: %d, Duration: %d ms\n", currentNote, noteDuration); action_playing = false; } } else { play_song = false; // Stop playing when all notes are done currentNote = 0; // Reset for next time } }
Coada de mesaje este implementată într-un mod thread-safe, folosind secțiuni critice pentru a preveni condițiile de cursă:
portENTER_CRITICAL(&messageQueueMux); // Operațiuni pe coadă portEXIT_CRITICAL(&messageQueueMux);
Concluzii:
Fişierele se încarcă pe wiki folosind facilitatea Add Images or other files. Namespace-ul în care se încarcă fişierele este de tipul :pm:prj20??:c? sau :pm:prj20??:c?:nume_student (dacă este cazul). Exemplu: Dumitru Alin, 331CC → :pm:prj2009:cc:dumitru_alin.
Link catre codul ESP32: https://github.com/HoriaMercan/PM_project
Link catre aplicatia self-made pentru comunicatie: https://github.com/HoriaMercan/android_joystick_for_esp32_bluetooth
Laboratoare folosite:
- GPIO
- Intreruperi
- Timere
- Bonus: BLE communication
### 30 aprilie 2025 - Configurare Inițială Commit: `7dfe52f2b2996f25b5107adbca77ea00c26fe5f4`
### 5 mai 2025 - Integrarea Componentelor Principale Commit: `7a76a08a1dfcdb8888a6efa2abc4b1a0aec38723`
### 11 mai 2025 - Mecanicile Jocului și Comunicarea Commit: `e679104dedf167d8ba7de339504adad6fdf984ae`
### 12 mai 2025 - Optimizare Cod și Migrare BLE Commit: `c557d4cb5135d9c70ee7b7e118a7e770c31b7958`
Commit: `310101410c4b5ee8bb2b291ea0f39cd7c4ef2ba9`
### 18 mai 2025 - Punct de Referință Stabilitate Commit: `a5f01f2c6d7bfe9e641f310086ed531b068262ae`
### 22 mai 2025 - Experiență de Utilizare Îmbunătățită Commit: `9f0fe7772a90b76d46d15b94bf353dacd6cc0f4c`
### 23 mai 2025 - Succes BLE și Sistem de Meniu Commit: `5919cd7aad8ce22be359a6508cbd5c36bf833d39`
Commit: `71f9cb224292d314461519b63771af14ee53feb2`
### 24 mai 2025 - Multiplayer și Îmbunătățiri Audio Commit: `36b101f67488fbbf002290adc2a992c876b03d19`
Commit: `cb3467eaed00221d5fe8b6c4c2e69cd391d1cfcb`