// Updated and working 1/22/25 V1.0
// Fixed Tie on Time Game
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 16, 2); // Initialize the LCD
// Pin out
int buzzer = 2;
int btnA = 5;
int btnB = 4;
int redled = 7;
int blueled = 8;
// Global Variables
int gameTime = 60 * 30; // Default game duration (30 minutes)
int timeA = 0; // Time accumulated by Alpha Team
int timeB = 0; // Time accumulated by Bravo Team
bool dominationA = false; // Indicates if Alpha Team is dominating
bool dominationB = false; // Indicates if Bravo Team is dominating
int gameType = 0; // Game Type
long currentMillis; // Current elapsed milliseconds
long blinkMillisA = 0;
long blinkMillisB = 0;
bool ledStateA = LOW;
bool ledStateB = LOW;
bool gameOver = false; // Flag to indicate game over
/*
Game Type:
1 - TIME: Over a certain period of time, see which team dominates the most time, shows the winning teams time and flashes winning team color.
Press both button once in position to start the countdown game timer.
2 - DOMINATION: See which team is the first to dominate a certain amount of time
Press both buttons then prop can be placed at locations. First team to control point for the set amount of time.
Shows winning team at the end and flashes winning team color and times.
*/
/**
String getFormattedTime(int totalSeconds)
Receives an integer representing the number of seconds and returns a string in the form "MM:SS".
*/
String getFormattedTime(int totalSeconds) {
int minutes = totalSeconds / 60;
int seconds = totalSeconds % 60;
String strMin = minutes < 10 ? "0" + String(minutes) : String(minutes);
String strSec = seconds < 10 ? "0" + String(seconds) : String(seconds);
return strMin + ":" + strSec;
}
/**
void showGameStatus()
Displays the current status of the game counters on the screen.
*/
void showGameStatus() {
lcd.clear();
lcd.setCursor(5, 0);
lcd.print(getFormattedTime(gameTime));
lcd.setCursor(0, 0);
lcd.print("RED");
lcd.setCursor(0, 1);
lcd.print(getFormattedTime(timeA));
lcd.setCursor(11, 1);
lcd.print(getFormattedTime(timeB));
lcd.setCursor(12, 0);
lcd.print("BLUE");
}
/**
void showTakenPoint()
Displays a message on the screen indicating that the point has already been taken.
*/
void showTakenPoint() {
lcd.clear();
lcd.setCursor(2, 0);
lcd.print("POINT TAKEN!");
}
/**
int getUserInput()
Waits for the user to press a button and returns which button was pressed:
1 - BtnA
2 - BtnB
3 - Both at the same time
*/
int getUserInput() {
int userInput = 0;
while (true) {
if (digitalRead(btnA) == LOW) {
tone(buzzer, 5000, 100);
delay(150);
if (digitalRead(btnB) == LOW) {
tone(buzzer, 6000, 100);
userInput = 3;
break;
}
userInput = 1;
break;
}
if (digitalRead(btnB) == LOW) {
tone(buzzer, 5000, 100);
delay(150);
if (digitalRead(btnA) == LOW) {
tone(buzzer, 6000, 100);
userInput = 3;
break;
}
userInput = 2;
break;
}
}
while (digitalRead(btnA) == LOW || digitalRead(btnB) == LOW) {}
return userInput;
}
/**
void errorTone()
Plays a pair of consecutive low tones on the buzzer indicating an error.
*/
void errorTone() {
tone(buzzer, 250, 50);
delay(100);
tone(buzzer, 250, 50);
delay(100);
}
/**
void initialSetup()
Manages the initial setup, establishing the type of game and duration.
*/
void initialSetup() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("R: TIME");
lcd.setCursor(0, 1);
lcd.print("B: DOMINATION");
int userInput = 0;
while (userInput != 1 && userInput != 2) {
userInput = getUserInput();
}
gameType = userInput;
lcd.clear();
lcd.setCursor(0, 0);
String msg = gameType == 1 ? "MAX TIME" : "GOAL TIME";
lcd.print(msg);
bool setupDone = false;
while (!setupDone) {
lcd.setCursor(0, 1);
lcd.print(getFormattedTime(gameTime));
userInput = getUserInput();
switch (userInput) {
case 1:
if (gameTime > 300)
gameTime -= 300;
else
errorTone();
break;
case 2:
if (gameTime < 30000)
gameTime += 300;
else
errorTone();
break;
case 3:
setupDone = true;
break;
default:
break;
}
}
}
/**
bool isGameOver()
Checks the current state of the game to determine if it is finished.
Returns true if the game is over.
*/
bool isGameOver() {
if (gameType == 1) {
if (timeA > gameTime + timeB || timeB > gameTime + timeA)
return true;
} else if (gameType == 2) {
if (timeA >= gameTime || timeB >= gameTime)
return true;
}
return false;
}
// End of game announcements and presents the winning team on the screen along with total captured times for both teams.
void showGameResult() {
String winner;
if (timeA > timeB) {
winner = "RED";
} else if (timeB > timeA) {
winner = "BLUE";
} else {
winner = "TIE"; // Declare a tie if times are equal
}
lcd.clear();
lcd.setCursor(3, 0);
lcd.print("WINNER: " + winner);
lcd.setCursor(0, 1);
lcd.print("R:" + getFormattedTime(timeA));
lcd.setCursor(9, 1);
lcd.print("B:" + getFormattedTime(timeB));
// Continuous LED flashing for the winning team or both if it's a tie
while (true) {
if (timeA > timeB) {
digitalWrite(redled, HIGH);
delay(500);
digitalWrite(redled, LOW);
delay(500);
} else if (timeB > timeA) {
digitalWrite(blueled, HIGH);
delay(500);
digitalWrite(blueled, LOW);
delay(500);
} else {
// Blink both LEDs for a tie
digitalWrite(redled, HIGH);
digitalWrite(blueled, HIGH);
delay(500);
digitalWrite(redled, LOW);
digitalWrite(blueled, LOW);
delay(500);
}
}
}
/**
bool conqer(int teamButton)
Point conquest process. Returns true if the point was conquered.
*/
bool conqer(int teamButton) {
lcd.clear();
lcd.setCursor(3, 0);
lcd.print("CAPTURING");
int progress = 0;
while (digitalRead(teamButton) == LOW) {
lcd.setCursor(progress, 1);
lcd.print("*");
tone(buzzer, 500, 100);
delay(400);
progress++;
if (progress == 16)
return true;
}
return false;
}
/**
void setup()
Program entry point.
*/
void setup() {
pinMode(buzzer, OUTPUT);
pinMode(btnA, INPUT_PULLUP);
pinMode(btnB, INPUT_PULLUP);
pinMode(redled, OUTPUT);
pinMode(blueled, OUTPUT);
lcd.begin(16, 2);
for (int i = 0; i < 3; i++) {
tone(buzzer, 5000, 50);
delay(100);
}
initialSetup();
currentMillis = millis();
}
/**
void loop()
Eternal cycle of execution.
*/
void loop() {
if (gameOver) return; // Stop execution if the game is over
if (digitalRead(btnA) == LOW) {
if (dominationA) {
tone(buzzer, 100);
showTakenPoint();
while (digitalRead(btnA) == LOW) {}
noTone(buzzer);
} else if (conqer(btnA)) {
digitalWrite(redled, HIGH);
digitalWrite(blueled, LOW);
dominationA = true;
dominationB = false;
}
} else if (digitalRead(btnB) == LOW) {
if (dominationB) {
tone(buzzer, 100);
showTakenPoint();
while (digitalRead(btnB) == LOW) {}
noTone(buzzer);
} else if (conqer(btnB)) {
digitalWrite(redled, LOW);
digitalWrite(blueled, HIGH);
dominationA = false;
dominationB = true;
}
}
if (millis() - currentMillis > 1000) {
currentMillis = millis();
if (gameType == 1) gameTime--;
if (dominationA) timeA++;
if (dominationB) timeB++;
showGameStatus();
if (isGameOver()) {
gameOver = true;
showGameResult();
}
}
// Blink LEDs when the team has control
if (dominationA) {
if (millis() - blinkMillisA >= 500) { // 500 ms interval
blinkMillisA = millis();
ledStateA = !ledStateA;
digitalWrite(redled, ledStateA);
}
} else {
digitalWrite(redled, LOW);
}
if (dominationB) {
if (millis() - blinkMillisB >= 500) { // 500 ms interval
blinkMillisB = millis();
ledStateB = !ledStateB;
digitalWrite(blueled, ledStateB);
}
}
}