#include <Arduino.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <Keypad.h>
// =====================
// LCD I2C (ton câblage)
// =====================
const int LCD_SDA = 8;
const int LCD_SCL = 9;
LiquidCrystal_I2C lcd(0x27, 16, 2); // LCD1602 I2C Wokwi [web:42]
// =====================
// Boutons (ton câblage)
// =====================
const int BTN1_PIN = 19;
const int BTN2_PIN = 18;
// =====================
// LED RGB (ton câblage)
// =====================
struct RgbPins { uint8_t r, g, b; };
RgbPins leds[5] = {
{14, 15, 16}, // LED1 rgb1
{17, 18, 19}, // LED2 rgb2 (partage BTN2=18 et BTN1=19, OK pour test Wokwi)
{20, 21, 38}, // LED3 rgb3
{39, 40, 41}, // LED4 rgb4
{42, 47, 2}, // LED5 rgb5
};
enum Team : uint8_t { TEAM_NEUTRAL=0, TEAM_RED=1, TEAM_BLUE=2 };
Team objectiveOwner[5] = {TEAM_NEUTRAL, TEAM_NEUTRAL, TEAM_NEUTRAL, TEAM_NEUTRAL, TEAM_NEUTRAL};
// =====================
// Keypad 4x4 (ton câblage)
// =====================
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] = {4, 5, 6, 7};
byte colPins[COLS] = {10, 11, 12, 13};
Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS); // [web:43]
// =====================
// Modes de jeu
// =====================
enum GameMode : uint8_t { MODE_DOMINATION, MODE_CTF, MODE_TDM, MODE_KOTH, MODE_COUNT };
const char* modeName(GameMode m) {
switch (m) {
case MODE_DOMINATION: return "Domination";
case MODE_CTF: return "CTF";
case MODE_TDM: return "TDM";
case MODE_KOTH: return "KOTH";
default: return "?";
}
}
GameMode currentMode = MODE_DOMINATION;
bool gameRunning = false;
unsigned long lastTick = 0;
const unsigned long TICK_MS = 1000;
// Scores (2 equipes)
uint16_t scoreRed = 0;
uint16_t scoreBlue = 0;
// Pour CTF (captures)
uint8_t redCaptures = 0;
uint8_t blueCaptures = 0;
// =====================
// UI / Menu
// =====================
enum UiState : uint8_t { UI_GAME, UI_MENU_MAIN, UI_MENU_MODE, UI_MENU_RESET_CONFIRM };
UiState ui = UI_GAME;
int menuIndex = 0;
int modeIndex = 0;
void setLedColor(uint8_t i, Team t) {
// COM -> GND dans ton schéma => HIGH allume le canal
digitalWrite(leds[i].r, (t == TEAM_RED) ? HIGH : LOW);
digitalWrite(leds[i].g, LOW); // pas utilisé pour 2 équipes (simple)
digitalWrite(leds[i].b, (t == TEAM_BLUE) ? HIGH : LOW);
// Neutre: éteint
if (t == TEAM_NEUTRAL) {
digitalWrite(leds[i].r, LOW);
digitalWrite(leds[i].g, LOW);
digitalWrite(leds[i].b, LOW);
}
}
Team nextTeam(Team t) {
return (Team)((t + 1) % 3); // Neutral -> Red -> Blue -> Neutral
}
void applyAllLeds() {
for (int i = 0; i < 5; i++) setLedColor(i, objectiveOwner[i]);
}
void resetGameState() {
scoreRed = scoreBlue = 0;
redCaptures = blueCaptures = 0;
for (int i = 0; i < 5; i++) objectiveOwner[i] = TEAM_NEUTRAL;
applyAllLeds();
}
void lcdPrint16(const char* s) {
// print max 16 char (simple)
char buf[17];
snprintf(buf, sizeof(buf), "%-16.16s", s);
lcd.print(buf);
}
void drawGameScreen() {
lcd.setCursor(0, 0);
char line1[32];
snprintf(line1, sizeof(line1), "%s %s", modeName(currentMode), gameRunning ? "RUN" : "PAUSE");
lcdPrint16(line1);
lcd.setCursor(0, 1);
char line2[32];
if (currentMode == MODE_CTF) {
snprintf(line2, sizeof(line2), "R%u B%u", redCaptures, blueCaptures);
} else {
snprintf(line2, sizeof(line2), "R:%u B:%u", scoreRed, scoreBlue);
}
lcdPrint16(line2);
}
void drawMenuMain() {
// Menu principal (2 lignes): titre + item
const char* items[] = { "Mode", "Start/Pause", "Reset", "Back" };
const int itemCount = 4;
if (menuIndex < 0) menuIndex = itemCount - 1;
if (menuIndex >= itemCount) menuIndex = 0;
lcd.setCursor(0, 0);
lcdPrint16("Menu");
lcd.setCursor(0, 1);
char line[32];
snprintf(line, sizeof(line), "> %s", items[menuIndex]);
lcdPrint16(line);
}
void drawMenuMode() {
lcd.setCursor(0, 0);
lcdPrint16("Choisir mode");
if (modeIndex < 0) modeIndex = MODE_COUNT - 1;
if (modeIndex >= MODE_COUNT) modeIndex = 0;
lcd.setCursor(0, 1);
char line[32];
snprintf(line, sizeof(line), "> %s", modeName((GameMode)modeIndex));
lcdPrint16(line);
}
void drawResetConfirm() {
lcd.setCursor(0, 0);
lcdPrint16("Reset partie?");
lcd.setCursor(0, 1);
lcdPrint16("D=Oui C=Non");
}
// =====================
// Logique des modes
// =====================
void gameTick() {
if (!gameRunning) return;
switch (currentMode) {
case MODE_DOMINATION:
// Score: chaque objectif contrôlé donne 1 point/sec à l'équipe
for (int i = 0; i < 5; i++) {
if (objectiveOwner[i] == TEAM_RED) scoreRed++;
if (objectiveOwner[i] == TEAM_BLUE) scoreBlue++;
}
break;
case MODE_KOTH:
// KOTH: on n'utilise que l'objectif 1 (LED1) comme "hill"
if (objectiveOwner[0] == TEAM_RED) scoreRed += 2;
if (objectiveOwner[0] == TEAM_BLUE) scoreBlue += 2;
break;
case MODE_TDM:
// TDM: pas d'objectifs auto; score via boutons (kills simulés)
// => géré dans les événements boutons
break;
case MODE_CTF:
// CTF: captures via boutons (flag returned/captured simulé)
// => géré dans les événements boutons
break;
default:
break;
}
}
void handleObjectiveKey(char k) {
if (k < '1' || k > '5') return;
int idx = (k - '1');
objectiveOwner[idx] = nextTeam(objectiveOwner[idx]);
setLedColor(idx, objectiveOwner[idx]);
}
// =====================
// Entrées
// =====================
bool lastBtn1 = HIGH, lastBtn2 = HIGH;
void handleButtons() {
bool b1 = digitalRead(BTN1_PIN);
bool b2 = digitalRead(BTN2_PIN);
// Front descendant = press (INPUT_PULLUP)
if (lastBtn1 == HIGH && b1 == LOW) {
if (currentMode == MODE_TDM) scoreRed++;
else if (currentMode == MODE_CTF) redCaptures++;
else objectiveOwner[0] = nextTeam(objectiveOwner[0]); // bouton 1 -> LED1
applyAllLeds();
}
if (lastBtn2 == HIGH && b2 == LOW) {
if (currentMode == MODE_TDM) scoreBlue++;
else if (currentMode == MODE_CTF) blueCaptures++;
else objectiveOwner[1] = nextTeam(objectiveOwner[1]); // bouton 2 -> LED2
applyAllLeds();
}
lastBtn1 = b1;
lastBtn2 = b2;
}
void handleKeypad() {
char k = keypad.getKey(); // renvoie NO_KEY si rien [web:69]
if (k == NO_KEY) return; // [web:69]
// Raccourcis globaux
if (k == '#') { gameRunning = !gameRunning; drawGameScreen(); return; }
if (k == '*') {
ui = (ui == UI_GAME) ? UI_MENU_MAIN : UI_GAME;
lcd.clear();
(ui == UI_GAME) ? drawGameScreen() : drawMenuMain();
return;
}
// En jeu: 1..5 = change owner d'un objectif
if (ui == UI_GAME) {
handleObjectiveKey(k);
drawGameScreen();
return;
}
// Menu navigation
if (ui == UI_MENU_MAIN) {
if (k == 'A') { menuIndex--; drawMenuMain(); return; }
if (k == 'B') { menuIndex++; drawMenuMain(); return; }
if (k == 'C') { ui = UI_GAME; lcd.clear(); drawGameScreen(); return; }
if (k == 'D') {
switch (menuIndex) {
case 0: // Mode
ui = UI_MENU_MODE;
modeIndex = (int)currentMode;
lcd.clear();
drawMenuMode();
return;
case 1: // Start/Pause
gameRunning = !gameRunning;
ui = UI_GAME;
lcd.clear();
drawGameScreen();
return;
case 2: // Reset
ui = UI_MENU_RESET_CONFIRM;
lcd.clear();
drawResetConfirm();
return;
case 3: // Back
ui = UI_GAME;
lcd.clear();
drawGameScreen();
return;
}
}
}
if (ui == UI_MENU_MODE) {
if (k == 'A') { modeIndex--; drawMenuMode(); return; }
if (k == 'B') { modeIndex++; drawMenuMode(); return; }
if (k == 'C') { ui = UI_MENU_MAIN; lcd.clear(); drawMenuMain(); return; }
if (k == 'D') {
currentMode = (GameMode)modeIndex;
resetGameState();
ui = UI_GAME;
lcd.clear();
drawGameScreen();
return;
}
}
if (ui == UI_MENU_RESET_CONFIRM) {
if (k == 'C') { ui = UI_MENU_MAIN; lcd.clear(); drawMenuMain(); return; }
if (k == 'D') {
resetGameState();
ui = UI_GAME;
lcd.clear();
drawGameScreen();
return;
}
}
}
// =====================
// Setup / Loop
// =====================
void setup() {
Serial.begin(115200);
Wire.begin(LCD_SDA, LCD_SCL);
lcd.init();
lcd.backlight();
lcd.clear();
pinMode(BTN1_PIN, INPUT_PULLUP);
pinMode(BTN2_PIN, INPUT_PULLUP);
for (int i = 0; i < 5; i++) {
pinMode(leds[i].r, OUTPUT);
pinMode(leds[i].g, OUTPUT);
pinMode(leds[i].b, OUTPUT);
}
resetGameState();
drawGameScreen();
}
void loop() {
handleButtons();
handleKeypad();
unsigned long now = millis();
if (now - lastTick >= TICK_MS) {
lastTick = now;
gameTick();
if (ui == UI_GAME) drawGameScreen();
}
}
Loading
esp32-s3-devkitc-1
esp32-s3-devkitc-1