// ================= B KODU: TFT_eSPI + SPRITE OPTİMİZE =================
#include <FastLED.h>
#include <TFT_eSPI.h> // Değişti: Adafruit_GFX yerine TFT_eSPI
// ================= LED =================
#define LED_PIN 14
#define NUM_LEDS 20
CRGB leds[NUM_LEDS];
// ================= BUZZER =================
#define BUZZER_PIN 46
unsigned long buzzerStart = 0;
bool buzzerActive = false;
// ================= BUTTONS =================
const int playerBtns[4] = {36, 37, 38, 39};
const int startBtn = 35;
bool lastPlayerBtn[4] = {HIGH, HIGH, HIGH, HIGH};
bool lastStartBtn = HIGH;
// DEBOUNCE
unsigned long lastDebounceTime[4] = {0, 0, 0, 0};
bool debouncedPlayerBtn[4] = {HIGH, HIGH, HIGH, HIGH};
unsigned long lastStartDebounceTime = 0;
bool debouncedStartBtn = HIGH;
const unsigned long debounceDelay = 30;
// ================= TFT_eSPI & SPRITES =================
// Her ekran için bir TFT nesnesi ve bir SPRITE (hafıza ekranı)
TFT_eSPI tft1 = TFT_eSPI();
TFT_eSPI tft2 = TFT_eSPI();
TFT_eSPI tft3 = TFT_eSPI();
TFT_eSPI tft4 = TFT_eSPI();
TFT_eSprite sprite1 = TFT_eSprite(&tft1);
TFT_eSprite sprite2 = TFT_eSprite(&tft2);
TFT_eSprite sprite3 = TFT_eSprite(&tft3);
TFT_eSprite sprite4 = TFT_eSprite(&tft4);
// Diziler
TFT_eSPI* tfts[4] = {&tft1, &tft2, &tft3, &tft4};
TFT_eSprite* sprites[4] = {&sprite1, &sprite2, &sprite3, &sprite4};
// ================= PRE-RENDERED DIGITS =================
// GFXcanvas16 yerine TFT_eSprite kullanacağız
TFT_eSprite* digitSprite[10] = {nullptr, nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr, nullptr};
const uint16_t DIGIT_WIDTH = 60;
const uint16_t DIGIT_HEIGHT = 100;
// ================= GAME VARIABLES =================
const int playerLed[4] = {4, 9, 14, 19};
int scores[4] = {0, 0, 0, 0};
int lastDisplayedScores[4] = {-1, -1, -1, -1};
bool showingResults = false;
unsigned long resultsStartTime = 0;
const unsigned long RESULTS_DURATION = 3000; // 3 saniye
int winnerIndex = -1;
int maxScore = 0;
// ================= COUNTDOWN SYSTEM =================
enum CountdownState {
COUNTDOWN_IDLE,
COUNTDOWN_ANIM_GO
};
CountdownState countdownState = COUNTDOWN_IDLE;
unsigned long countdownStartTime = 0;
const unsigned long GO_ANIMATION_DURATION = 500; // 500 ms
unsigned long lastCountdownBlinkTime = 0;
bool countdownLedsOn = false;
// Kazanan LED grupları
const int winnerLedGroups[4][5] = {
{0, 1, 2, 3, 4},
{5, 6, 7, 8, 9},
{10, 11, 12, 13, 14},
{15, 16, 17, 18, 19}
};
// ================= CACHED SCREEN DATA =================
struct CachedScreenData {
struct {
int16_t x;
int16_t y;
uint16_t w;
uint16_t h;
} goData;
struct {
int16_t scoreTitleX;
uint16_t scoreTitleY;
int16_t scoreDigitX;
uint16_t scoreDigitY;
} gameScreen;
struct {
int16_t playerTextX;
uint16_t playerTextY;
int16_t playerNumX;
uint16_t playerNumY;
} idleScreen;
struct {
int16_t plus100X;
int16_t plus100Y;
int16_t minus50X;
int16_t minus50Y;
} feedbackData;
struct {
int16_t youX;
int16_t winX;
int16_t scoreX;
int16_t gameX;
int16_t overX;
} resultData;
};
CachedScreenData cachedData[4];
struct PlayerFeedback {
bool active;
unsigned long startTime;
bool isPositive;
};
PlayerFeedback playerFeedbacks[4] = {
{false, 0, false},
{false, 0, false},
{false, 0, false},
{false, 0, false}
};
// Oyun kontrol
int currentLedIndex = 0;
int currentRound = 0;
bool gameStarted = false;
bool gameEnded = false;
unsigned long ledTimer = 0;
// LED zamanlama
unsigned long nextLedChangeTime = 0;
int currentActiveLed = -1;
// Ekran güncelleme kontrolü
bool screenUpdateNeeded[4] = {true, true, true, true};
unsigned long lastScreenUpdate[4] = {0, 0, 0, 0};
const unsigned long SCREEN_UPDATE_INTERVAL = 4;
// Feedback sonrası ekran temizleme kontrolü
bool needsScreenClear[4] = {false, false, false, false};
// ----------------- Tur Dizileri -----------------
const int round1[] = {0,1,2,3,4,5,6,7,8,9,10,11,12,0,1,2,3,13,14,15,16,17,18,19};
const int round2[] = {0,1,2,5,6,7,8,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,10,11,12,13,18,19};
const int round3[] = {0,1,5,6,7,8,2,3,4,5,6,7,15,16,17,18,8,9,10,11,12,13,14,15,16,17,18,19};
const int round4[] = {0,1,2,3,9,5,6,7,8,19,10,11,12,13,4,15,16,17,18,14};
const int round5[] = {0,1,2,3,5,6,7,8,10,11,12,13,14,15,12,13,14,15,16,17,18,19,17,18,19};
const int round6[] = {0,1,2,3,4,5,2,3,4,5,6,7,8,9,10,11,12,7,8,10,11,12,13,15,16,17,18,19};
const int round7[] = {0,1,2,3,4,5,6,7,7,8,9,10,11,12,12,13,14,15,16,17,18,19};
const int round8[] = {0,1,2,2,3,4,5,6,7,8,9,10,11,12,12,13,14,15,16,17,18,19};
const int* roundSequences[8] = {round1, round2, round3, round4, round5, round6, round7, round8};
const int roundLengths[8] = {24, 26, 28, 20, 25, 28, 22, 22};
const int roundDelays[8] = {1000, 750, 500, 500, 1000, 750, 500, 250};
// ================= PRE-RENDERED DIGITS (TFT_eSPI versiyonu) =================
void createPreRenderedDigits() {
Serial.println("Pre-rendering score digits (TFT_eSPI)...");
for (int digit = 0; digit < 10; digit++) {
// GFXcanvas16 yerine TFT_eSprite oluştur
digitSprite[digit] = new TFT_eSprite(tfts[0]); // Ana TFT nesnesini referans al
if (!digitSprite[digit]) {
Serial.print("ERROR: Failed to create sprite for digit ");
Serial.println(digit);
continue;
}
// Sprite'ı oluştur ve siyah yap
digitSprite[digit]->createSprite(DIGIT_WIDTH, DIGIT_HEIGHT);
digitSprite[digit]->fillSprite(TFT_BLACK);
// Rakamı çiz
digitSprite[digit]->setTextSize(9);
digitSprite[digit]->setTextColor(TFT_RED, TFT_BLACK);
digitSprite[digit]->setCursor(0, 0);
digitSprite[digit]->print(digit);
}
Serial.println("All digits pre-rendered!");
}
void drawScoreDigitFast(int player, int digit) {
if (digit < 0 || digit > 9) return;
if (!digitSprite[digit]) return;
int x = cachedData[player].gameScreen.scoreDigitX;
int y = cachedData[player].gameScreen.scoreDigitY;
// Sprite'ı hedef sprite'a kopyala
digitSprite[digit]->pushToSprite(sprites[player], x, y, TFT_BLACK);
}
// ================= HIZLI ÇİZİM FONKSİYONLARI (TFT_eSPI versiyonu) =================
void precalculateAllScreenData() {
Serial.println("Precalculating all screen data (TFT_eSPI)...");
for (int player = 0; player < 4; player++) {
TFT_eSprite* spr = sprites[player];
// GO! animasyonu için
spr->setTextSize(13);
spr->getTextBounds("GO", 0, 0,
&cachedData[player].goData.x,
&cachedData[player].goData.y,
&cachedData[player].goData.w,
&cachedData[player].goData.h);
cachedData[player].goData.x = (240 - cachedData[player].goData.w) / 2;
cachedData[player].goData.y = (320 - cachedData[player].goData.h) / 2;
// Oyun ekranı için
spr->setTextSize(6);
String scoreText = "SCORE";
int16_t x1, y1;
uint16_t w, h;
spr->getTextBounds(scoreText, 0, 0, &x1, &y1, &w, &h);
cachedData[player].gameScreen.scoreTitleX = (240 - w) / 2;
cachedData[player].gameScreen.scoreTitleY = 40;
cachedData[player].gameScreen.scoreDigitX = (240 - DIGIT_WIDTH) / 2;
cachedData[player].gameScreen.scoreDigitY = 140;
// Idle ekranı için
spr->setTextSize(5);
String playerText = "PLAYER";
spr->getTextBounds(playerText, 0, 0, &x1, &y1, &w, &h);
cachedData[player].idleScreen.playerTextX = (240 - w) / 2;
cachedData[player].idleScreen.playerTextY = 60;
spr->setTextSize(14);
String playerNum = String(player + 1);
spr->getTextBounds(playerNum, 0, 0, &x1, &y1, &w, &h);
cachedData[player].idleScreen.playerNumX = (240 - w) / 2;
cachedData[player].idleScreen.playerNumY = 160;
// Feedback ekranları için
spr->setTextSize(8);
spr->getTextBounds("+100", 0, 0, &x1, &y1, &w, &h);
cachedData[player].feedbackData.plus100X = (240 - w) / 2;
cachedData[player].feedbackData.plus100Y = (320 - h) / 2;
spr->setTextSize(10);
spr->getTextBounds("-50", 0, 0, &x1, &y1, &w, &h);
cachedData[player].feedbackData.minus50X = (240 - w) / 2;
cachedData[player].feedbackData.minus50Y = (320 - h) / 2;
// Sonuç ekranları için
spr->setTextSize(7);
spr->getTextBounds("YOU", 0, 0, &x1, &y1, &w, &h);
cachedData[player].resultData.youX = (240 - w) / 2;
spr->getTextBounds("WIN", 0, 0, &x1, &y1, &w, &h);
cachedData[player].resultData.winX = (240 - w) / 2;
spr->getTextBounds("GAME", 0, 0, &x1, &y1, &w, &h);
cachedData[player].resultData.gameX = (240 - w) / 2;
spr->getTextBounds("OVER", 0, 0, &x1, &y1, &w, &h);
cachedData[player].resultData.overX = (240 - w) / 2;
spr->setTextSize(5);
String scoreStr = "8888";
spr->getTextBounds(scoreStr, 0, 0, &x1, &y1, &w, &h);
cachedData[player].resultData.scoreX = (240 - w) / 2;
}
Serial.println("Precalculation completed!");
}
void drawIdleScreenFast(int player) {
TFT_eSprite* spr = sprites[player];
spr->fillSprite(TFT_RED); // fillRect yerine fillSprite
spr->setTextSize(5);
spr->setTextColor(TFT_YELLOW, TFT_RED);
spr->setCursor(cachedData[player].idleScreen.playerTextX,
cachedData[player].idleScreen.playerTextY);
spr->print("PLAYER");
spr->setTextSize(14);
spr->setTextColor(TFT_BLACK, TFT_RED);
spr->setCursor(cachedData[player].idleScreen.playerNumX,
cachedData[player].idleScreen.playerNumY);
spr->print(String(player + 1));
// SPRITE'ı ekrana HIZLICA gönder (En önemli kısım!)
spr->pushSprite(0, 0);
lastScreenUpdate[player] = millis();
screenUpdateNeeded[player] = false;
}
void drawScoreFast(int player, int score) {
TFT_eSprite* spr = sprites[player];
String scoreStr = String(score);
// Skor alanını temizle
spr->fillRect(cachedData[player].gameScreen.scoreDigitX,
cachedData[player].gameScreen.scoreDigitY,
DIGIT_WIDTH, DIGIT_HEIGHT, TFT_BLACK);
if (score >= 0 && score <= 9) {
// Tek basamaklı için pre-rendered sprite kullan
drawScoreDigitFast(player, score);
} else {
// Çok basamaklı için normal yazı
spr->setTextSize(9);
spr->setTextColor(TFT_RED, TFT_BLACK);
int16_t x1, y1;
uint16_t w, h;
spr->getTextBounds(scoreStr, 0, 0, &x1, &y1, &w, &h);
int x = (240 - w) / 2;
int y = cachedData[player].gameScreen.scoreDigitY + (DIGIT_HEIGHT - h) / 2;
spr->setCursor(x, y);
spr->print(scoreStr);
}
}
void updateScoreOnly(int player) {
if (lastDisplayedScores[player] != scores[player]) {
int newScore = scores[player];
drawScoreFast(player, newScore);
sprites[player]->pushSprite(0, 0); // Güncellemeyi ekrana yansıt
lastDisplayedScores[player] = newScore;
}
screenUpdateNeeded[player] = false;
}
void drawScoreTitle(int player) {
TFT_eSprite* spr = sprites[player];
spr->setTextSize(6);
spr->setTextColor(TFT_YELLOW, TFT_BLACK);
spr->setCursor(cachedData[player].gameScreen.scoreTitleX,
cachedData[player].gameScreen.scoreTitleY);
spr->print("SCORE");
}
void drawGameScreenFast(int player) {
TFT_eSprite* spr = sprites[player];
if (playerFeedbacks[player].active) {
if (playerFeedbacks[player].isPositive) {
spr->fillSprite(TFT_BLACK);
spr->setTextSize(8);
spr->setTextColor(TFT_GREEN, TFT_BLACK);
spr->setCursor(cachedData[player].feedbackData.plus100X,
cachedData[player].feedbackData.plus100Y);
spr->print("+100");
} else {
spr->fillSprite(TFT_BLACK);
spr->setTextSize(10);
spr->setTextColor(TFT_RED, TFT_BLACK);
spr->setCursor(cachedData[player].feedbackData.minus50X,
cachedData[player].feedbackData.minus50Y);
spr->print("-50");
}
} else {
if (needsScreenClear[player]) {
spr->fillSprite(TFT_BLACK);
drawScoreTitle(player);
drawScoreFast(player, scores[player]);
needsScreenClear[player] = false;
} else {
updateScoreOnly(player);
return; // updateScoreOnly zaten pushSprite yapıyor
}
}
// Ekrana yansıt
spr->pushSprite(0, 0);
lastScreenUpdate[player] = millis();
screenUpdateNeeded[player] = false;
}
void drawInitialGameScreenFast(int player) {
static bool firstDraw[4] = {true, true, true, true};
TFT_eSprite* spr = sprites[player];
if (firstDraw[player]) {
spr->fillSprite(TFT_BLACK);
drawScoreTitle(player);
drawScoreFast(player, 0);
spr->pushSprite(0, 0);
firstDraw[player] = false;
}
scores[player] = 0;
lastDisplayedScores[player] = 0;
screenUpdateNeeded[player] = false;
lastScreenUpdate[player] = millis();
needsScreenClear[player] = false;
}
void resetFirstDraw() {
static bool firstDraw[4] = {true, true, true, true};
for (int i = 0; i < 4; i++) {
firstDraw[i] = true;
}
}
void drawGoAnimationFast(int player) {
TFT_eSprite* spr = sprites[player];
spr->fillSprite(TFT_BLUE);
spr->setTextSize(13);
spr->setTextColor(TFT_BLACK, TFT_BLUE);
spr->setCursor(cachedData[player].goData.x, cachedData[player].goData.y);
spr->print("GO");
spr->pushSprite(0, 0);
lastScreenUpdate[player] = millis();
screenUpdateNeeded[player] = false;
}
void drawWinnerScreenFast(int player) {
TFT_eSprite* spr = sprites[player];
spr->fillSprite(TFT_GREEN);
spr->setTextSize(7);
spr->setTextColor(tft1.color565(16, 64, 80), TFT_GREEN); // 0x104050
String youText = "YOU";
int16_t x1, y1;
uint16_t w, h;
spr->getTextBounds(youText, 0, 0, &x1, &y1, &w, &h);
int youY = 80;
spr->setCursor(cachedData[player].resultData.youX, youY);
spr->print("YOU");
String winText = "WIN";
spr->getTextBounds(winText, 0, 0, &x1, &y1, &w, &h);
int winY = youY + h + 20;
spr->setCursor(cachedData[player].resultData.winX, winY);
spr->print("WIN");
String scoreStr = String(scores[player]);
spr->setTextSize(5);
spr->setTextColor(TFT_RED, TFT_GREEN);
spr->getTextBounds(scoreStr, 0, 0, &x1, &y1, &w, &h);
int scoreY = winY + h + 30;
spr->setCursor((240 - w) / 2, scoreY);
spr->print(scoreStr);
spr->pushSprite(0, 0);
lastScreenUpdate[player] = millis();
screenUpdateNeeded[player] = false;
}
void drawLoserScreenFast(int player) {
TFT_eSprite* spr = sprites[player];
spr->fillSprite(TFT_RED);
spr->setTextSize(7);
spr->setTextColor(tft1.color565(16, 64, 80), TFT_RED); // 0x104050
String gameText = "GAME";
int16_t x1, y1;
uint16_t w, h;
spr->getTextBounds(gameText, 0, 0, &x1, &y1, &w, &h);
int gameY = 80;
spr->setCursor(cachedData[player].resultData.gameX, gameY);
spr->print("GAME");
String overText = "OVER";
spr->getTextBounds(overText, 0, 0, &x1, &y1, &w, &h);
int overY = gameY + h + 20;
spr->setCursor(cachedData[player].resultData.overX, overY);
spr->print("OVER");
String scoreStr = String(scores[player]);
spr->setTextSize(5);
spr->setTextColor(TFT_YELLOW, TFT_RED);
spr->getTextBounds(scoreStr, 0, 0, &x1, &y1, &w, &h);
int scoreY = overY + h + 30;
spr->setCursor((240 - w) / 2, scoreY);
spr->print(scoreStr);
spr->pushSprite(0, 0);
lastScreenUpdate[player] = millis();
screenUpdateNeeded[player] = false;
}
// ================= LED ANİMASYON FONKSİYONLARI =================
void clearAllLeds() {
for (int i = 0; i < NUM_LEDS; i++) {
leds[i] = CRGB::Black;
}
FastLED.show();
}
void flashAllLeds(CRGB color, int flashes = 2, int duration = 100) { // flashes=2 yaptım
for (int flash = 0; flash < flashes; flash++) {
for (int i = 0; i < NUM_LEDS; i++) {
leds[i] = color;
}
FastLED.show();
delay(duration);
clearAllLeds();
FastLED.show();
delay(duration);
}
}
void updateGoAnimationLEDs() {
if (countdownState != COUNTDOWN_ANIM_GO) return;
unsigned long currentTime = millis();
if (currentTime - lastCountdownBlinkTime >= 250) {
lastCountdownBlinkTime = currentTime;
countdownLedsOn = !countdownLedsOn;
if (countdownLedsOn) {
for (int i = 0; i < NUM_LEDS; i++) {
leds[i] = CRGB::Blue;
}
} else {
clearAllLeds();
}
FastLED.show();
}
}
void updateResultsAnimation() {
if (!showingResults) return;
static unsigned long lastBlinkTime = 0;
static bool ledsOn = false;
static bool initialized = false;
if (!initialized) {
lastBlinkTime = millis();
ledsOn = false;
initialized = true;
clearAllLeds();
}
if (winnerIndex == -1) {
if (millis() - lastBlinkTime >= 300) {
lastBlinkTime = millis();
ledsOn = !ledsOn;
if (ledsOn) {
for (int i = 0; i < NUM_LEDS; i++) leds[i] = CRGB::Yellow;
} else {
clearAllLeds();
}
FastLED.show();
}
} else {
if (millis() - lastBlinkTime >= 500) {
lastBlinkTime = millis();
ledsOn = !ledsOn;
if (ledsOn) {
for (int i = 0; i < NUM_LEDS; i++) {
leds[i] = CRGB::Green; // Arka plan yeşil
}
for (int i = 0; i < 5; i++) {
int ledPos = winnerLedGroups[winnerIndex][i];
if (ledPos >= 0 && ledPos < NUM_LEDS) {
leds[ledPos] = CRGB::Red; // Kazanan LED'ler kırmızı
}
}
} else {
clearAllLeds();
}
FastLED.show();
}
}
}
void runIdleAnimation() {
static unsigned long lastIdleTime = 0;
static int idlePosition = 0;
const unsigned long IDLE_SPEED = 50;
if (millis() - lastIdleTime >= IDLE_SPEED) {
lastIdleTime = millis();
clearAllLeds();
for (int i = 0; i < 7; i++) {
int pos = (idlePosition + i) % NUM_LEDS;
int brightness = 255 - (i * 40);
if (brightness < 50) brightness = 50;
leds[pos] = CRGB(brightness, 0, 0);
}
FastLED.show();
idlePosition = (idlePosition + 1) % NUM_LEDS;
}
}
// ================= GO! ANİMASYON FONKSİYONLARI =================
void startGoAnimation() {
Serial.println("=== GO! ANIMATION STARTING ===");
// ÖNEMLİ: Oyun durumunu tamamen sıfırla
gameStarted = false;
gameEnded = false;
showingResults = false;
countdownState = COUNTDOWN_IDLE;
winnerIndex = -1;
// Skorları sıfırla
for (int i = 0; i < 4; i++) {
scores[i] = 0;
lastDisplayedScores[i] = -1;
screenUpdateNeeded[i] = true;
}
// LED pozisyonlarını sıfırla
currentLedIndex = 0;
currentRound = 0;
currentActiveLed = -1;
nextLedChangeTime = 0;
ledTimer = 0;
// firstDraw flag'lerini sıfırla
resetFirstDraw();
// Tüm ekranlara GO! yaz
for (int i = 0; i < 4; i++) {
drawGoAnimationFast(i);
}
// LED'leri ayarla
countdownStartTime = millis();
lastCountdownBlinkTime = millis();
countdownLedsOn = true;
clearAllLeds();
for (int i = 0; i < NUM_LEDS; i++) {
leds[i] = CRGB::Blue;
}
FastLED.show();
// Ses ve bekleme
tone(BUZZER_PIN, 1800, 200);
buzzerStart = millis();
buzzerActive = true;
delay(500); // GO animasyonu 500ms görünsün
// Direkt oyuna geç
Serial.println("=== GO ANIMATION BİTTİ, OYUN BAŞLIYOR ===");
countdownState = COUNTDOWN_IDLE;
gameStarted = true;
// Skor ekranlarını göster
for (int i = 0; i < 4; i++) {
drawInitialGameScreenFast(i);
}
// Oyun LED'lerini başlat
clearAllLeds();
// Player LED introduction
for (int i = 0; i < 4; i++) {
leds[playerLed[i]] = CRGB::Blue;
FastLED.show();
tone(BUZZER_PIN, 1200 + i * 200, 60);
buzzerStart = millis();
buzzerActive = true;
delay(30);
leds[playerLed[i]] = CRGB::Black;
FastLED.show();
delay(10);
}
tone(BUZZER_PIN, 1800, 200);
buzzerStart = millis();
buzzerActive = true;
// Oyun zamanlayıcılarını ayarla
nextLedChangeTime = millis();
ledTimer = millis();
Serial.println("=== OYUN BAŞARIYLA BAŞLATILDI ===");
}
// ================= SCREEN UPDATE =================
void updateScreens() {
unsigned long currentTime = millis();
for (int i = 0; i < 4; i++) {
if (screenUpdateNeeded[i] && (currentTime - lastScreenUpdate[i] >= SCREEN_UPDATE_INTERVAL)) {
if (showingResults) {
if (i == winnerIndex) {
drawWinnerScreenFast(i);
} else {
drawLoserScreenFast(i);
}
} else if (countdownState == COUNTDOWN_ANIM_GO) {
drawGoAnimationFast(i);
} else if (!gameStarted) {
drawIdleScreenFast(i);
} else {
drawGameScreenFast(i);
}
}
}
}
// ================= GAME FUNCTIONS =================
void updateLeds() {
if (!gameStarted) return;
if (millis() >= nextLedChangeTime) {
if (currentActiveLed >= 0) {
leds[currentActiveLed] = CRGB::Black;
}
currentActiveLed = roundSequences[currentRound][currentLedIndex];
leds[currentActiveLed] = CRGB::Green; // Oyun LED'i YEŞİL
FastLED.show();
for (int i = 0; i < 4; i++) {
if (currentActiveLed == playerLed[i]) {
tone(BUZZER_PIN, 1500, 150);
buzzerStart = millis();
buzzerActive = true;
}
}
nextLedChangeTime = millis() + roundDelays[currentRound];
ledTimer = millis();
currentLedIndex++;
if (currentLedIndex >= roundLengths[currentRound]) {
currentLedIndex = 0;
currentRound++;
if (currentRound >= 8) {
endGame();
return;
}
}
}
}
void startActualGame() {
// Bu fonksiyon artık startGoAnimation içinde çağrılıyor
Serial.println("DEBUG: startActualGame çağrıldı (eskiden kullanılıyordu)");
}
void processPlayerInput(int player) {
bool reading = digitalRead(playerBtns[player]);
if (reading != lastPlayerBtn[player]) {
lastDebounceTime[player] = millis();
}
if ((millis() - lastDebounceTime[player]) > debounceDelay) {
if (reading != debouncedPlayerBtn[player]) {
debouncedPlayerBtn[player] = reading;
if (debouncedPlayerBtn[player] == LOW) {
unsigned long pressTime = millis();
bool isValid = false;
if (currentActiveLed >= 0) {
unsigned long timeSinceLedOn = pressTime - ledTimer;
if (timeSinceLedOn > 50 && timeSinceLedOn < (roundDelays[currentRound] * 0.9)) {
if (currentActiveLed == playerLed[player]) {
isValid = true;
}
}
}
if (isValid) {
scores[player] += 100;
playerFeedbacks[player].active = true;
playerFeedbacks[player].startTime = pressTime;
playerFeedbacks[player].isPositive = true;
Serial.print("Player ");
Serial.print(player);
Serial.println(" +100 feedback!");
flashAllLeds(CRGB::Green, 2, 100); // Doğru: YEŞİL, 2 kere
tone(BUZZER_PIN, 2500, 100);
buzzerStart = millis();
buzzerActive = true;
} else {
scores[player] = max(0, scores[player] - 50);
playerFeedbacks[player].active = true;
playerFeedbacks[player].startTime = pressTime;
playerFeedbacks[player].isPositive = false;
Serial.print("Player ");
Serial.print(player);
Serial.println(" -50 feedback!");
flashAllLeds(CRGB::Red, 2, 100); // Yanlış: KIRMIZI, 2 kere
tone(BUZZER_PIN, 800, 150);
buzzerStart = millis();
buzzerActive = true;
}
screenUpdateNeeded[player] = true;
}
}
}
lastPlayerBtn[player] = reading;
}
void checkPlayerButtons() {
if (!gameStarted) return;
for (int i = 0; i < 4; i++) {
processPlayerInput(i);
}
}
void updatePlayerFeedbacks() {
for (int i = 0; i < 4; i++) {
if (playerFeedbacks[i].active && millis() - playerFeedbacks[i].startTime >= 1000) {
playerFeedbacks[i].active = false;
needsScreenClear[i] = true;
screenUpdateNeeded[i] = true;
Serial.print("Player ");
Serial.print(i);
Serial.println(" feedback ended.");
}
}
}
void checkStartButton() {
bool reading = digitalRead(startBtn);
if (reading != lastStartBtn) {
lastStartDebounceTime = millis();
}
if ((millis() - lastStartDebounceTime) > debounceDelay) {
if (reading != debouncedStartBtn) {
debouncedStartBtn = reading;
if (debouncedStartBtn == LOW && !gameStarted && !gameEnded && countdownState == COUNTDOWN_IDLE && !showingResults) {
startGoAnimation();
}
}
}
lastStartBtn = reading;
}
// ================= GAME END FUNCTION =================
void endGame() {
Serial.println("\n=== GAME END FUNCTION RUNNING ===");
gameStarted = false;
gameEnded = false;
maxScore = 0;
winnerIndex = -1;
Serial.println("Scores:");
for (int i = 0; i < 4; i++) {
Serial.print("Player ");
Serial.print(i);
Serial.print(": ");
Serial.println(scores[i]);
}
for (int i = 0; i < 4; i++) {
if (scores[i] > maxScore) {
maxScore = scores[i];
winnerIndex = i;
}
}
int winnerCount = 0;
for (int i = 0; i < 4; i++) {
if (scores[i] == maxScore) {
winnerCount++;
}
}
if (winnerCount > 1) {
winnerIndex = -1;
Serial.println("Result: DRAW!");
} else {
Serial.print("Result: Player ");
Serial.print(winnerIndex);
Serial.print(" WON! Score: ");
Serial.println(maxScore);
}
showingResults = true;
resultsStartTime = millis();
for (int i = 0; i < 4; i++) {
if (i == winnerIndex) {
drawWinnerScreenFast(i);
} else {
drawLoserScreenFast(i);
}
}
clearAllLeds();
if (winnerIndex != -1) {
for (int i = 0; i < NUM_LEDS; i++) {
leds[i] = CRGB::Green; // Arka plan YEŞİL
}
for (int i = 0; i < 5; i++) {
int ledPos = winnerLedGroups[winnerIndex][i];
if (ledPos >= 0 && ledPos < NUM_LEDS) {
leds[ledPos] = CRGB::Red; // Kazanan LED'ler KIRMIZI
}
}
FastLED.show();
tone(BUZZER_PIN, 3000, 500);
buzzerStart = millis();
buzzerActive = true;
} else {
for (int i = 0; i < NUM_LEDS; i++) {
leds[i] = CRGB::Yellow;
}
FastLED.show();
tone(BUZZER_PIN, 1000, 500);
buzzerStart = millis();
buzzerActive = true;
}
}
void checkResultsTimeout() {
if (showingResults && millis() - resultsStartTime >= RESULTS_DURATION) {
Serial.println("=== RESULT TIME OUT ===");
showingResults = false;
gameStarted = false;
gameEnded = false;
countdownState = COUNTDOWN_IDLE;
for (int i = 0; i < 4; i++) {
scores[i] = 0;
playerFeedbacks[i].active = false;
needsScreenClear[i] = false;
lastDisplayedScores[i] = -1;
}
for (int i = 0; i < 4; i++) {
drawIdleScreenFast(i);
}
clearAllLeds();
noTone(BUZZER_PIN);
}
}
void updateBuzzer() {
if (buzzerActive && millis() - buzzerStart >= 200) {
noTone(BUZZER_PIN);
buzzerActive = false;
}
}
// ================= SETUP =================
void setup() {
Serial.begin(115200);
Serial.println("=== SYSTEM STARTING (B KODU - TFT_eSPI) ===");
FastLED.addLeds<WS2811, LED_PIN, GRB>(leds, NUM_LEDS);
FastLED.setBrightness(255);
clearAllLeds();
pinMode(BUZZER_PIN, OUTPUT);
pinMode(startBtn, INPUT_PULLUP);
for (int i = 0; i < 4; i++) {
pinMode(playerBtns[i], INPUT_PULLUP);
}
Serial.println("Initializing TFT_eSPI displays and sprites...");
// TFT'leri ve Sprite'ları başlat
for (int i = 0; i < 4; i++) {
tfts[i]->init();
tfts[i]->setRotation(0);
// Sprite'ları oluştur
sprites[i]->createSprite(240, 320);
sprites[i]->setTextDatum(TL_DATUM); // Sol üst köşe referans
}
// Ön hesaplamaları yap
precalculateAllScreenData();
createPreRenderedDigits();
// IDLE ekranlarını göster
for (int i = 0; i < 4; i++) {
drawIdleScreenFast(i);
}
delay(1000);
Serial.println("\n=== SHORT TEST ===");
for (int i = 0; i < 3; i++) {
for (int j = 0; j < NUM_LEDS; j++) {
leds[j] = CRGB::White;
}
FastLED.show();
delay(300);
clearAllLeds();
delay(300);
}
Serial.println("=== SYSTEM READY ===");
Serial.println("Press START button");
}
// ================= LOOP =================
void loop() {
static unsigned long lastDebugTime = 0;
if (millis() - lastDebugTime >= 1000) {
lastDebugTime = millis();
Serial.print("Status: gameStarted=");
Serial.print(gameStarted);
Serial.print(", showingResults=");
Serial.print(showingResults);
Serial.print(", winnerIndex=");
Serial.print(winnerIndex);
Serial.print(", countdownState=");
Serial.println(countdownState);
}
checkStartButton();
if (countdownState == COUNTDOWN_ANIM_GO) {
// Artık startGoAnimation içinde yönetiliyor
updateGoAnimationLEDs();
} else if (gameStarted) {
updateLeds();
checkPlayerButtons();
updatePlayerFeedbacks();
} else {
if (showingResults) {
updateResultsAnimation();
checkResultsTimeout();
} else {
runIdleAnimation();
}
}
updateBuzzer();
updateScreens();
delay(2);
}