#include <Arduino.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Encoder.h>
#define BUTTON_PLAYER1 12
#define BUTTON_PLAYER2 14
#define DEBOUNCE_DELAY 50
#define ENCODER_CLK 19
#define ENCODER_DT 18
#define ENCODER_BUTTON 20
#define OLED_SCL 21
#define OLED_SDA 26
#define OLED_RESET -1
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
// Bitmaps
const unsigned char arrowRight[] PROGMEM = {
0b00011000,
0b00001100,
0b00000110,
0b11111111,
0b11111111,
0b00000110,
0b00001100,
0b00011000
};
const unsigned char arrowLeft[] PROGMEM = {
0b00011000,
0b00110000,
0b01100000,
0b11111111,
0b11111111,
0b01100000,
0b00110000,
0b00011000
};
// Game Logic
enum DeviceState {
SLEEPING,
MENU,
GAME,
STATISTICS,
};
enum GameState {
PREPARING,
WAITING,
REACT,
DONE
};
enum StatisticsState {
WINNER,
EACH_ROUND_STATISTICS
};
enum DeviceState currentDeviceState = SLEEPING;
enum GameState currentGameState = PREPARING;
enum StatisticsState currentStatisticState = WINNER;
int selectedRounds = 2;
int currentRound = 1;
int waitingTime = 0;
unsigned long waitingStartTime = 0;
bool waiting = false;
int roundWinner = 0;
int p1Score = 0;
int p2Score = 0;
// Button states
bool p1Pressing = false;
bool p2Pressing = false;
bool encoderPressing = false;
// Previous button readings
int p1LastReading = HIGH;
int p2LastReading = HIGH;
int encoderLastReading = HIGH;
// Timing for debounce
unsigned long p1LastChangeTime = 0;
unsigned long p2LastChangeTime = 0;
unsigned long encoderLastChangeTime = 0;
// OLED Display
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
// Encoder
Encoder myEnc(ENCODER_CLK, ENCODER_DT);
long oldPosition = -999;
void initOLED() {
Wire.begin(OLED_SDA, OLED_SCL);
if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println(F("SSD1306 allocation failed"));
for(;;);
}
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
display.setCursor(0,0);
display.setTextSize(0.25);
display.display();
}
void setup() {
Serial.begin(115200);
Serial.println("Hello World!");
initOLED();
pinMode(BUTTON_PLAYER1, INPUT_PULLUP);
pinMode(BUTTON_PLAYER2, INPUT_PULLUP);
pinMode(ENCODER_CLK, INPUT);
pinMode(ENCODER_DT, INPUT);
pinMode(ENCODER_BUTTON, INPUT_PULLUP);
}
// MISC
int calculateOverallWinner() {
int overallWinner = 0;
if (p1Score > p2Score) {
overallWinner = 1;
} else if (p1Score < p2Score) {
overallWinner = 2;
} else if (p1Score == p2Score) {
overallWinner = -1;
}
return overallWinner;
}
// DISPLAY
void oledPrint(String message, int x, int y, bool clear, bool newline = false, int size = 1, int color = WHITE) {
if (clear) { display.clearDisplay(); }
display.setCursor(x, y);
display.setTextColor(color);
display.setTextSize(size);
if (newline) {
display.println(message);
} else {
display.print(message);
}
display.display();
}
void oledPrintCentered(String message, int y, bool clear, bool newline = false, int size = 1, int color = WHITE) {
int16_t x1, y1;
uint16_t w, h;
display.setTextSize(size);
display.getTextBounds(message, 0, 0, &x1, &y1, &w, &h);
int16_t x_center = (display.width() - w) / 2;
oledPrint(message, x_center, y, clear, newline, size, color);
}
void renderMenu() {
oledPrintCentered("SELECT ROUNDS", 0, true, true, 1.8);
display.fillRoundRect(44, 20, 40, 40, 5, SSD1306_WHITE);
oledPrintCentered(String(selectedRounds), 35, false, true, 2, BLACK);
if (selectedRounds > 1) {
oledPrint(String(selectedRounds - 1), 20, 35, false, true, 1.5);
}
if (selectedRounds < 20) {
oledPrint(String(selectedRounds + 1), 100, 35, false, true, 1.5);
}
display.display();
}
void renderGame() {
switch (currentGameState)
{
case PREPARING:
for (int i = 3; i >= 0; i--) {
oledPrintCentered(("PREPARE " + ((selectedRounds == 1) ? "" : "FOR ROUND " + String(currentRound))), 5, true, true, 1.5);
oledPrintCentered((i == 0) ? "GO!" : String(i), 25, false, true, 3);
oledPrintCentered((String(p1Score) + " : " + String(p2Score)), 55, false, true, 0.75);
delay(750);
}
waitingTime = random(1000, 6000);
waitingStartTime = millis();
waiting = true;
currentGameState = WAITING;
break;
case WAITING:
if (waiting) {
oledPrintCentered("Tick Tack Tick Tack..", 25, true, true, 1.3);
}
if (millis() - waitingStartTime >= waitingTime) {
currentGameState = REACT;
waiting = false;
}
oledPrintCentered((String(p1Score) + " : " + String(p2Score)), 55, false, true, 0.75);
break;
case REACT:
oledPrintCentered("REACT NOW!", 25, true, true, 2);
oledPrintCentered((String(p1Score) + " : " + String(p2Score)), 55, false, true, 0.75);
break;
case DONE:
oledPrintCentered(("WINNER OF ROUND " + String(currentRound)), 5, true, true, 1.2);
oledPrintCentered(("PLAYER " + String(roundWinner)), 25, false, false, 2);
delay(1500);
if (currentRound == selectedRounds) {
currentDeviceState = STATISTICS;
} else {
currentRound++;
currentGameState = PREPARING;
}
break;
}
}
void renderStatistics() {
switch (currentStatisticState)
{
case WINNER: {
int overallWinner = calculateOverallWinner();
if (overallWinner != -1) {
oledPrintCentered("OVERALL WINNER IS", 5, true, true, 1.5);
oledPrintCentered("PLAYER " + String(overallWinner), 25, false, true, 2.5);
} else {
oledPrintCentered("EQUAL!", 25, true, true, 3);
}
display.drawBitmap(115, 51, arrowRight, 8, 8, SSD1306_WHITE);
display.display();
break;
}
case EACH_ROUND_STATISTICS: {
int p1RowY = 8;
int colonRowY = 23;
int p2RowY = 34;
int startX = 5;
display.clearDisplay();
for (int i = 0; i < 3; i++) {
if (i == 0) {
oledPrint("P1:", startX, p1RowY, false, true);
for (int p1Column = 0; p1Column < selectedRounds; p1Column++) {
oledPrint("L", (startX + 20 + 10 * p1Column), p1RowY, false);
}
} else if (i == 1) {
for (int column = 0; column < selectedRounds; column++) {
oledPrint("|", (startX + 20 + 10 * column), colonRowY, false);
}
} else if (i == 2) {
oledPrint("P2:", startX, p2RowY, false, true);
for (int p2Column = 0; p2Column < selectedRounds; p2Column++) {
oledPrint("W", (startX + 20 + 10 * p2Column), p2RowY, false);
}
}
}
display.drawBitmap(5, 51, arrowLeft, 8, 8, SSD1306_WHITE);
display.display();
break;
}
}
}
void handleUI() {
switch (currentDeviceState) {
case SLEEPING:
display.clearDisplay();
display.display();
break;
case MENU:
renderMenu();
break;
case GAME:
renderGame();
break;
case STATISTICS:
renderStatistics();
break;
}
}
// ROTARY ENCODER
void rotatedClockwise() {
switch (currentDeviceState) {
case SLEEPING:
break;
case MENU:
if (selectedRounds > 1) {
selectedRounds--;
}
break;
case GAME:
break;
case STATISTICS:
if (currentStatisticState == EACH_ROUND_STATISTICS) {
currentStatisticState = WINNER;
}
break;
}
}
void rotatedCounterclockwise() {
switch (currentDeviceState) {
case SLEEPING:
break;
case MENU:
if (selectedRounds < 20) {
selectedRounds++;
}
break;
case GAME:
break;
case STATISTICS:
if (currentStatisticState == WINNER) {
currentStatisticState = EACH_ROUND_STATISTICS;
}
break;
}
}
void handleRotaryEncoder() {
long newPosition = myEnc.read() / 4;
if (newPosition != oldPosition) {
if (newPosition > oldPosition) {
Serial.println("ENCODER: ROTATED CLOCKWISE");
rotatedClockwise();
} else {
Serial.println("ENCODER: ROTATED COUNTERCLOCKWISE");
rotatedCounterclockwise();
}
oldPosition = newPosition;
}
}
// BUTTONS
void player1ButtonPressed() {
if (waiting) {
roundWinner = 2;
p2Score++;
currentGameState = DONE;
}
if (currentGameState == REACT) {
roundWinner = 1;
p1Score++;
currentGameState = DONE;
}
}
void player2ButtonPressed() {
if (waiting) {
roundWinner = 1;
p1Score++;
currentGameState = DONE;
}
if (currentGameState == REACT) {
roundWinner = 2;
p2Score++;
currentGameState = DONE;
}
}
void encoderButtonPressed() {
switch (currentDeviceState) {
case SLEEPING:
currentDeviceState = MENU;
break;
case MENU:
currentDeviceState = GAME;
break;
case GAME:
break;
case STATISTICS:
currentDeviceState = SLEEPING;
break;
}
}
void handleButtonPresses() {
unsigned long currentTime = millis();
// Read current button states
int p1Reading = digitalRead(BUTTON_PLAYER1);
int p2Reading = digitalRead(BUTTON_PLAYER2);
int encoderButtonReading = digitalRead(ENCODER_BUTTON);
// Handle Player 1 button
if (p1Reading != p1LastReading) {
p1LastChangeTime = currentTime;
}
if ((currentTime - p1LastChangeTime) > DEBOUNCE_DELAY) {
// Button state has been stable for debounce period
if (p1Reading == LOW && !p1Pressing) {
p1Pressing = true;
Serial.println("P1: PRESSED");
player1ButtonPressed();
} else if (p1Reading == HIGH && p1Pressing) {
p1Pressing = false;
Serial.println("P1: RELEASED");
}
}
// Handle Player 2 button
if (p2Reading != p2LastReading) {
p2LastChangeTime = currentTime;
}
if ((currentTime - p2LastChangeTime) > DEBOUNCE_DELAY) {
// Button state has been stable for debounce period
if (p2Reading == LOW && !p2Pressing) {
p2Pressing = true;
Serial.println("P2: PRESSED");
player2ButtonPressed();
} else if (p2Reading == HIGH && p2Pressing) {
p2Pressing = false;
Serial.println("P2: RELEASED");
}
}
// Handle Encoder button
if (encoderButtonReading != encoderLastReading) {
encoderLastChangeTime = currentTime;
}
if ((currentTime - encoderLastChangeTime) > DEBOUNCE_DELAY) {
// Button state has been stable for debounce period
if (encoderButtonReading == LOW && !encoderPressing) {
encoderPressing = true;
Serial.println("ENCODER: PRESSED");
encoderButtonPressed(); // You'll need to implement this function
} else if (encoderButtonReading == HIGH && encoderPressing) {
encoderPressing = false;
Serial.println("ENCODER: RELEASED");
}
}
// Update last readings
p1LastReading = p1Reading;
p2LastReading = p2Reading;
encoderLastReading = encoderButtonReading;
}
void loop() {
handleButtonPresses();
handleRotaryEncoder();
handleUI();
}
Loading
esp32-s2-devkitm-1
esp32-s2-devkitm-1
Mitte: OK
Uhrzeigersinn: Menü Weiter
Gegen Uhrzeigersinn: Menü Zurück
Spieler 1
Spieler 2
Simulation Starten!!