#include <Arduino.h>
#include <Wire.h>
#include <EasyButton.h>
#include <LiquidCrystal_I2C.h>
#include <EEPROM.h>
// Piny i stałe konfiguracyjne
const uint8_t BUTTON_PIN_D2 = 2;
const uint8_t BUTTON_PIN_D3 = 3;
const uint8_t I2C_PIN_SDA = A4;
const uint8_t I2C_PIN_SCL = A5;
const uint8_t LCD_SLAVE_ADDRESS = 39;
const uint8_t RELAY_PIN = A0;
const uint8_t RED_LED_PIN = A1;
const uint8_t YELLOW_LED_PIN = A2;
const uint8_t BUZZER_PIN = 10;
// Stałe czasowe i konfiguracyjne
const int KEY_TONE_INTERVAL = 50;
const int MAX_RESULT = 999999;
const int NUM_TEAMS = 2;
const int BASE_POINTS_MIN = 7;
const int BASE_POINTS_MAX = 77;
const unsigned long EVENT_CHECK_INTERVAL = 30000; // Co 30 sekund szansa na event
const int COMBO_INCREASE_TIME = 5000; // Czas na zwiększenie combo (5 sekund)
// System balansowania
const float BALANCE_THRESHOLD = 700; // Próg różnicy punktów
const float CATCH_UP_MULTIPLIER = 2.5; // Mnożnik dla drużyny zostającej w tyle
const float LEADER_PENALTY = 0.3; // Kara dla prowadzącej drużyny
// System eventów
enum EventType {
NO_EVENT,
BONUS_KASA,
ZLOTA_MINUTA,
SUPER_SKOK,
ZWROT_AKCJI,
ODWROCENIE_PRZYCISKOW,
ZMNIEJSZENIE_ZAROBKOW,
ZMNIEJSZENIE_COMBO,
ZMNIEJSZENIE_CZASU_NACISKU
};
struct GameEvent {
EventType type;
unsigned long duration;
float multiplier;
unsigned long startTime;
bool isActive;
const char* message; // Dodana zmienna do przechowywania wiadomości o evencie
};
// System osiągnięć
struct Achievement {
bool pierwszaSetka; // Pierwszy 1000 PLN
bool szybkiStart; // 500 PLN w pierwsze 2 minuty
bool pewnaReka; // Przytrzymanie przycisku przez 20 sekund
bool powrotKrola; // Wygrana po byciu w tyle o 2000 PLN
};
// Globalne zmienne
LiquidCrystal_I2C lcd(LCD_SLAVE_ADDRESS, 16, 2);
EasyButton greenButton(BUTTON_PIN_D2);
EasyButton redButton(BUTTON_PIN_D3);
int results[NUM_TEAMS] = {0, 0};
unsigned long buttonPressStartTime[NUM_TEAMS] = {0, 0};
unsigned long gameStartTime = 0;
unsigned long lastEventTime = 0;
Achievement achievements[NUM_TEAMS] = {{false}, {false}};
GameEvent currentEvent = {NO_EVENT, 0, 1.0, 0, false, ""}; // Inicjalizacja zmiennej message
int combo[NUM_TEAMS] = {1, 1};
unsigned long comboStartTime[NUM_TEAMS] = {0, 0};
const unsigned long EVENT_MIN_INTERVAL = 30000; // Minimum 30 sekund między eventami
const unsigned long EVENT_MAX_INTERVAL = 90000; // Maksimum 90 sekund między eventami
unsigned long nextEventTime = 0;
unsigned long lastDisplayUpdateTime = 0; // Dodana zmienna do śledzenia ostatniego odświeżenia ekranu
// Początkowa ilość pieniędzy w bankomacie
int bankAmount = 0;
// Nowa zmienna do śledzenia stanu migania diody
bool ledBlinking[NUM_TEAMS] = {false, false};
// Dodajemy zmienną do śledzenia czasu wywołania alarmu
unsigned long alarmTriggerTime = 0;
bool alarmActivated = false; // Zmienna do śledzenia, czy alarm został już aktywowany
// Funkcje pomocnicze
float calculateBalanceMultiplier(int teamIndex) {
int difference = abs(results[0] - results[1]);
if (difference > BALANCE_THRESHOLD) {
bool isLeading = (teamIndex == 0) ? (results[0] > results[1]) : (results[1] > results[0]);
if (isLeading) {
return LEADER_PENALTY;
} else {
return CATCH_UP_MULTIPLIER;
}
}
return 1.0;
}
void triggerEvent() {
if (currentEvent.isActive) return;
unsigned long currentTime = millis();
int eventRoll = random(100);
// Eventy z różnymi szansami
if (eventRoll < 10) {
currentEvent = {BONUS_KASA, 12000, 2.0, currentTime, true, " BONUS KASA x2 "};
displayEventMessage(" BONUS KASA x2 ", "-WTYKA W BANKU!-");
} else if (eventRoll < 20) {
currentEvent = {ZLOTA_MINUTA, 17000, 3.0, currentTime, true, " MEGA SEKUNDY! "};
displayEventMessage(" MEGA SEKUNDY! ", "- 3X ZAROBEK! -");
} else if (eventRoll < 30) {
currentEvent = {SUPER_SKOK, 15000, 1.5, currentTime, true, " SUPER SKOK "};
displayEventMessage(" SUPER SKOK ", "DODATKOWY HAJS!");
} else if (eventRoll < 50) {
currentEvent = {ZWROT_AKCJI, 10000, 1.0, currentTime, true, "!!!! ALARM !!!!!"};
displayEventMessage("!!!! ALARM !!!!", "-PUSC PRZYCISK!-");
playZwrotAkcjiSound();
alarmTriggerTime = currentTime; // Ustawiamy czas wywołania alarmu
alarmActivated = false; // Resetujemy stan alarmu
} else if (eventRoll < 60) {
currentEvent = {ODWROCENIE_PRZYCISKOW, 12000, 1.0, currentTime, true, " ZAMIANA STRON "};
displayEventMessage(" ZAMIANA STRON ", "-ODWRONE GUZIKI-");
} else if (eventRoll < 80) {
currentEvent = {ZMNIEJSZENIE_ZAROBKOW, 12000, 0.5, currentTime, true, " BLAD SYSTEMU "};
displayEventMessage(" BLAD SYSTEMU ", "-PUSC PRZYCISK!-");
} else if (eventRoll < 85) {
currentEvent = {ZMNIEJSZENIE_COMBO, 12000, 1.0, currentTime, true, " BRAK BONUSOW "};
displayEventMessage(" BRAK BONUSOW ", "-COMBO WYLACZONE-");
} else {
currentEvent = {ZMNIEJSZENIE_CZASU_NACISKU, 12000, 1.0, currentTime, true, " TRZYMAJ DLUZEJ "};
displayEventMessage(" TRZYMAJ DLUZEJ ", "- DLUGI NACISK -");
}
playEventSound();
// Ustaw następny event za losowy czas
nextEventTime = millis() + random(EVENT_MIN_INTERVAL, EVENT_MAX_INTERVAL + 1);
}
void updateCombo(int team) {
unsigned long currentTime = millis();
if (currentTime - comboStartTime[team] < COMBO_INCREASE_TIME) {
if (random(100) < 5) { // 5% szansa na zwiększenie combo
combo[team]++;
if (combo[team] > 5) combo[team] = 5;
}
} else {
combo[team] = 1;
}
comboStartTime[team] = currentTime;
}
void checkAchievements(int team) {
unsigned long gameTime = millis() - gameStartTime;
if (!achievements[team].pierwszaSetka && results[team] >= 5000) {
achievements[team].pierwszaSetka = true;
results[team] += 410; // Dodanie bonusu
displayAchievement(team, "-5 TYSI WASZE!-");
}
if (!achievements[team].szybkiStart && gameTime <= 120000 && results[team] >= 1000) {
achievements[team].szybkiStart = true;
results[team] += 300; // Dodanie bonusu
displayAchievement(team, "-SZYBKI START!-");
}
if (!achievements[team].pewnaReka && (millis() - buttonPressStartTime[team]) >= 10000) {
achievements[team].pewnaReka = true;
results[team] += 200; // Dodanie bonusu
displayAchievement(team, "-PEWNA REKA!-");
}
if (!achievements[team].powrotKrola &&
results[team] > results[1-team] &&
(results[team] - results[1-team]) >= 2000) {
achievements[team].powrotKrola = true;
results[team] += 130; // Dodanie bonusu
displayAchievement(team, "-MEGA PRZEWAGA!-");
}
}
void displayEventMessage(const char* line1, const char* line2) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(line1);
lcd.setCursor(0, 1);
lcd.print(line2);
delay(2500);
}
void displayAchievement(int team, const char* achievement) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(team == 0 ? " ekipa ZOLTA " : " ekipa CZERWONA ");
lcd.setCursor(0, 1);
lcd.print(achievement);
playAchievementSound();
delay(2000);
}
void playEventSound() {
tone(BUZZER_PIN, 2000, 200);
delay(250);
tone(BUZZER_PIN, 2500, 200);
}
void playAchievementSound() {
tone(BUZZER_PIN, 1500, 100);
delay(150);
tone(BUZZER_PIN, 2000, 100);
delay(150);
tone(BUZZER_PIN, 2500, 200);
}
void playZwrotAkcjiSound() {
for (int i = 0; i < 5; i++) {
tone(BUZZER_PIN, 1000, 200);
delay(200);
tone(BUZZER_PIN, 500, 200);
delay(200);
}
}
bool updateResult(EasyButton &button, int team, uint8_t ledPin) {
unsigned long currentTime = millis();
bool changed = false;
if (button.isPressed()) {
if (buttonPressStartTime[team] == 0) {
buttonPressStartTime[team] = currentTime;
digitalWrite(ledPin, HIGH);
}
unsigned long requiredPressTime = 1000;
if (currentEvent.isActive && currentEvent.type == ZMNIEJSZENIE_CZASU_NACISKU) {
requiredPressTime = 2000;
}
if (currentTime - buttonPressStartTime[team] >= requiredPressTime) {
if (currentEvent.isActive && currentEvent.type == ZWROT_AKCJI) {
// Nic nie rób, jeśli event ZWROT_AKCJI jest aktywny
} else {
float balanceMultiplier = calculateBalanceMultiplier(team);
float eventMultiplier = currentEvent.isActive ? currentEvent.multiplier : 1.0;
float comboMultiplier = 1.0 + ((combo[team] - 1) * 0.2); // Wolniejszy wzrost mnożnika
if (currentEvent.isActive && currentEvent.type == ZMNIEJSZENIE_COMBO) {
comboMultiplier = 1.0;
}
int basePoints = random(BASE_POINTS_MIN, BASE_POINTS_MAX + 1);
int finalPoints = basePoints * balanceMultiplier * eventMultiplier * comboMultiplier;
if (bankAmount >= finalPoints) {
results[team] += finalPoints;
bankAmount -= finalPoints;
updateCombo(team);
checkAchievements(team);
changed = true;
} else {
// Kradzież pieniędzy od drużyny przeciwnej
int stolenPoints = finalPoints;
if (results[1 - team] >= stolenPoints) {
results[team] += stolenPoints;
results[1 - team] -= stolenPoints;
changed = true;
} else {
results[team] += results[1 - team];
results[1 - team] = 0;
changed = true;
}
}
buttonPressStartTime[team] = currentTime;
// Ustawienie migania diody, jeśli event jest aktywny
if (currentEvent.isActive) {
ledBlinking[team] = true;
}
}
}
} else {
buttonPressStartTime[team] = 0;
digitalWrite(ledPin, LOW);
combo[team] = 1; // Resetowanie combo po puszczeniu przycisku
ledBlinking[team] = false; // Wyłączenie migania diody po puszczeniu przycisku
}
return changed;
}
void displayResults() {
lcd.clear();
// Pierwsza linia
if (currentEvent.isActive) {
lcd.setCursor(0, 0);
lcd.print(currentEvent.message);
} else {
lcd.setCursor(0, 0);
lcd.print(" BANK ");
lcd.print(bankAmount);
lcd.print(" CBL");
}
// Druga linia: [wynik czerwony] [wynik zolty]
lcd.setCursor(0, 1);
lcd.print("< ");
lcd.print(results[1]);
lcd.setCursor(7, 1);
lcd.print("|");
lcd.setCursor(14, 1);
lcd.print(" >");
// Oblicz pozycję startową dla wyniku żółtego
int yellowResult = results[0];
int yellowResultLength = (yellowResult > 9999) ? 5 : (yellowResult > 999) ? 4 : (yellowResult > 99) ? 3 : (yellowResult > 9) ? 2 : 1;
int yellowResultStartPos = 14 - yellowResultLength;
lcd.setCursor(yellowResultStartPos, 1);
lcd.print(results[0]);
// Dodanie combo, jeśli jest większe niż 1 i nie ma aktywnego eventu
if (!currentEvent.isActive && (combo[1] > 1 || combo[0] > 1)) {
lcd.setCursor(0, 0);
lcd.print("...PREMIA x");
lcd.print(combo[1] > combo[0] ? combo[1] : combo[0]);
lcd.print("....");
}
}
void setup() {
pinMode(RELAY_PIN, OUTPUT);
pinMode(RED_LED_PIN, OUTPUT);
pinMode(YELLOW_LED_PIN, OUTPUT);
pinMode(BUZZER_PIN, OUTPUT);
lcd.begin(16, 2);
lcd.setBacklight(HIGH);
greenButton.begin();
redButton.begin();
// Ustawienie seedu dla generatora liczb losowych
unsigned long seed = millis();
seed ^= micros();
seed ^= analogRead(A0);
seed ^= analogRead(A1);
seed ^= analogRead(A2);
seed ^= analogRead(A3);
seed ^= analogRead(A4);
seed ^= analogRead(A5);
seed ^= analogRead(A6);
seed ^= analogRead(A7);
randomSeed(seed);
// Losowanie początkowej ilości pieniędzy w bankomacie
bankAmount = random(7777, 13000);
// Sekwencja startowa
displayEventMessage(" CSarena ELBLAG", " CEBULIOMAT ");
playEventSound();
delay(2000);
gameStartTime = millis();
nextEventTime = millis() + random(EVENT_MIN_INTERVAL, EVENT_MAX_INTERVAL + 1);
}
void loop() {
unsigned long currentTime = millis();
greenButton.read();
redButton.read();
// Sprawdzanie eventów co 45 sekund (rzadziej niż wcześniej)
if (currentTime - lastEventTime >= EVENT_CHECK_INTERVAL) {
triggerEvent();
lastEventTime = currentTime;
}
// Aktualizacja statusu eventu
if (currentEvent.isActive && currentTime - currentEvent.startTime >= currentEvent.duration) {
currentEvent.isActive = false;
noTone(BUZZER_PIN);
ledBlinking[0] = false; // Wyłączenie migania diody po zakończeniu eventu
ledBlinking[1] = false; // Wyłączenie migania diody po zakończeniu eventu
}
bool resultsChanged = false;
if (currentEvent.isActive && currentEvent.type == ODWROCENIE_PRZYCISKOW) {
resultsChanged |= updateResult(greenButton, 1, RED_LED_PIN);
resultsChanged |= updateResult(redButton, 0, YELLOW_LED_PIN);
} else {
resultsChanged |= updateResult(greenButton, 0, YELLOW_LED_PIN);
resultsChanged |= updateResult(redButton, 1, RED_LED_PIN);
}
if (resultsChanged || currentTime - lastDisplayUpdateTime >= 1000) {
displayResults();
lastDisplayUpdateTime = currentTime;
}
// Sprawdzanie czy event ZWROT_AKCJI jest aktywny
if (currentEvent.isActive && currentEvent.type == ZWROT_AKCJI) {
handleZwrotAkcji();
}
// Miganie diodami, jeśli event jest aktywny
if (ledBlinking[0]) {
digitalWrite(YELLOW_LED_PIN, (currentTime / 500) % 2 == 0 ? LOW : HIGH);
}
if (ledBlinking[1]) {
digitalWrite(RED_LED_PIN, (currentTime / 500) % 2 == 0 ? LOW : HIGH);
}
// Obsługa eventu BLAD SYSTEMU
if (currentEvent.isActive && currentEvent.type == ZMNIEJSZENIE_ZAROBKOW) {
handleBladSystemu();
}
}
void handleZwrotAkcji() {
unsigned long currentTime = millis();
unsigned long elapsedTime = currentTime - currentEvent.startTime;
// Dodajemy opóźnienie przed rozpoczęciem odjęcia wartości
const unsigned long REACTION_DELAY = 1000; // 1 sekunda opóźnienia
const unsigned long DEDUCTION_INTERVAL = 1000; // 1 sekunda
if (elapsedTime < currentEvent.duration) {
// Sprawdzamy, czy minęło REACTION_DELAY od rozpoczęcia eventu
if (elapsedTime >= REACTION_DELAY && (elapsedTime / DEDUCTION_INTERVAL) % 2 == 0) {
for (int team = 0; team < NUM_TEAMS; team++) {
EasyButton &button = (team == 0) ? greenButton : redButton;
if (button.isPressed()) {
if (results[team] >= 333) {
results[team] -= 333;
bankAmount += 333;
}
}
}
}
// Miganie diodami
digitalWrite(RED_LED_PIN, (currentTime / 500) % 2 == 0 ? HIGH : LOW);
digitalWrite(YELLOW_LED_PIN, (currentTime / 500) % 2 == 0 ? HIGH : LOW);
// Dźwięk
tone(BUZZER_PIN, 1000, 200);
delay(200);
tone(BUZZER_PIN, 500, 200);
delay(200);
// Wyświetlanie komunikatu
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("!!!! ALARM !!!!");
} else {
currentEvent.isActive = false;
noTone(BUZZER_PIN);
alarmActivated = false; // Resetujemy stan alarmu
}
}
void handleBladSystemu() {
unsigned long currentTime = millis();
unsigned long elapsedTime = currentTime - currentEvent.startTime;
// Odejmowanie wartości co sekundę
const unsigned long INTERVAL = 1000; // 1 sekunda
if (elapsedTime < currentEvent.duration) {
for (int team = 0; team < NUM_TEAMS; team++) {
EasyButton &button = (team == 0) ? greenButton : redButton;
if (button.isPressed() && (currentTime / INTERVAL) % 2 == 0) {
if (results[team] >= 50) {
results[team] -= 50;
results[1 - team] += 50;
}
}
}
// Miganie diodami
digitalWrite(RED_LED_PIN, (currentTime / 500) % 2 == 0 ? HIGH : LOW);
digitalWrite(YELLOW_LED_PIN, (currentTime / 500) % 2 == 0 ? HIGH : LOW);
// Wyświetlanie komunikatu
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(" BLAD SYSTEMU ");
} else {
currentEvent.isActive = false;
noTone(BUZZER_PIN);
}
}