/**
* Quiz Buzzer System
* Modified by Greg Jonah
4 player game show buzzer system , with 4 player scoreboard , timer and host controler (Theoretically expandable to more players)
Host box contains ability to change Game mode with 4 spdt switched and control the mode the game boots in or what mode the game will reset to when the reset is triggered
Host box has speaker that can play SFX from sound dy-sv5w sound module, the speaker can be shut off with hardware switch and output can be routed through Aux cable to sound system
Host can start stop game, and validate player answers with arcade buttons on top of box
Host can also award or remove additional points at certain states in the game if there is a conflic to resolve.
Modification arcade buttons and a reset button are also on the back that allow various settings to be changed in different game modes
Buzzers have a light and switch, can use 100mm arcade box slap button, or can plug in handheld thumb style buzzer like jeopardy.
* Inspired by many projects on the internet - Huge thank you to those who have inspired this project, here are just a few links to share the inspiration and get you started.
RobTillaart's TM1637_RT Libraries!
https://github.com/RobTillaart/Arduino/tree/36289cc00fc44989d067c14c71e0a630fe626c4d/libraries/TM1637_RT
Playfultechnology - arduino-quizbuzzer
https://www.youtube.com/watch?v=S2eK29do-z0
https://github.com/playfultechnology/arduino-quizbuzzer
Nick Elliott - Quiz Buttons and Scoreboard
https://youtu.be/KBwTI1ZGz9o?si=K4gbzR4Xy9AJqfqz
hardware involved..
Arduino Mega
DY-SV5W - Audio player
100mm Arcade buttons
Regular Arcade buttons
LEDs, wires, resistors, spst and 2 position momentary switches...
Lots of bits and peices from Dollar Stores
Etc..
*/
/** List of Game modes
0000 (0) Basic Lockout trivia, players can answer anytime, players illiminated with wrong answers
0001 (1) Lockout trivia with Answer Timer, players can Buzz to answer anytime, but have a timer once buzzed where host must hit correct before timer runs out.players illiminated with wrong answers
0010 (2) Lockout Trivia with Question Timer, players cannot buzz untill host hits start / Stop, wrong answer illiminates player, timer resets each time (Optionally host can enable answer timer with S/S)
0011 (3) Lockout trivia with Q timer, and Answer Timer, Player cannot buzz untill host hits S/S , player has a time to answer once buzz, auto wrong if timer runs out, and wrong answer illiminates player
0100 (4) Time Challenge - Player timers count up till player presses button. Host can pause/Reset
0101 (5) Time Challenge - With game time limit
0110 (6) Time Challenge with Host Validation for points - players must complete a challenge within a time limit,
0111 (7) ??
1000 (8) 3 Round basic lockout Trivia with low score illimination (Optionally host can enable answer timer with S/S)
1001 (9) 3 Round lockout low score illimination with Q timer (Optionally host can enable answer timer with S/S)
1010 (10) 3 Round trivia low score illimination with Q timer and A Timer.
1011 (11) Random Player Answer - Host asks question, and hits button to see who answers
1100 (12) Basic Rotating Host - 3 player answer.. good for 4 players (host buzzer when pressed moves to next host red and green on box still work)
1101 (13) Rotating host with Q timer (optional host controled Answer Timer)
1110 (14) Rotating host with Q and Q timers
1111 (15) Classic Chess timer , track player time and move to next player.
*/
// INCLUDES
#include <TM1637.h> // Library for the 4 Digit 7 Segment Displays seems to have the best compatibility.
// this might not be the right library we want to use so here are some alternates
// Library: "TM1637_RT", https://github.com/RobTillaart/TM1637_RT
//#include <DYPlayerArduino.h> //Abstracted UART Control of DY-XXXX mp3 modules
// https://github.com/SnijderC/dyplayer/tree/main?tab=readme-ov-file#arduino
// Macros to retreive the fractional seconds and minute parts of a time supplied in ms
#define numberOfMinutes(_time_) (((_time_ / 1000) / 60) % 60)
#define numberOfSeconds(_time_) ((_time_ / 1000) % 60)
#define numberOfTenths(_time_) ((_time_ / 100) % 10)
// Class delarations and enumerations
enum BtnName {
btnStartStop,
btnCorrect,
btnWrong,
btnReset,
btnYellow,
btnBlue,
btnMod1Up,
btnMod2Up,
btnMod3Up,
btnMod4Up,
btnMod1Dwn,
btnMod2Dwn,
btnMod3Dwn,
btnMod4Dwn,
};
enum GameType {
trivia = 0,
challenge,
turns,
};
enum playerBtnName {
btnPlayer1,
btnPlayer2,
btnPlayer3,
btnPlayer4,
};
enum playerDispName {
dispPlayer1,
dispPlayer2,
dispPlayer3,
dispPlayer4,
};
enum class GS : uint8_t {
reset = 0, // Starts here, reads switches for game mode and then moves to confGame automatically
confGame, // Base Startup state - Wait while settings for #rounds, #Players, and Timer Lenght is configured if Required
questionStart, //**
askQuestion, // Wait for host to ask question, and hit Start/Stop button
waitPlayerBuzz, // Wait for player to buzz or Timer to run out
waitPlayerChallenge, // wait for player to buzz during a challenge game where we are timing them, allows player to buzz which deactivate them and wait for more
waitPlayerTurn, // waits for player on turn base game like chess clock
validateAnswer, // Wait for host to validate the answer
gameOver, // Game ends - wait for reset
};
enum class LFXS : uint8_t { //Led FX State - these States will allow different Light effects to be running in tandem with game states
hold = 0, // do nothing
confTrivia, // lighting effects when configuring trivia
confChallenge, // lighting effects when configuring Challenge games
confTurns, // lighting effects when configuring Turn based games
urgentAnswer, // When 10% of answer timer left, flash urgently the correct and wrong LED's AND the 4 player LEDs to remind everyone to answer/validate fast
urgentQuestion, // When 10% of question timer left OR Challenge game timer left, flash active Player Button and status LEDs to remind players to Buzz!
urgentChallenge, // when 10% of Game time left to complete a challenge , flash the lights fast
urgentTurn, // when Turn taking tool long blink player button..
hostStartStop,
blinkActivePlayer, // blinks the current player in Chess timer mode
};
enum class DS : uint8_t { //Display State - these States allow the displays to be refreshed while other game code is running in tandem
hold = 0, // do nothing
confTrivia, //
confChallenge,
confTurns,
triviaQTimer,
triviaATimer,
turnsActive, //display count up timers for all players in Game
};
//Global Variables*********************************************************
/* Player Specific Variables */
const int8_t NumPlayers = 4; // Number of players
const int8_t NumCtrlBtns = 14; // number of other control buttons
const int8_t NumTimers = 7; // number of timers tracked
//Timer Indexes
const int8_t t_pl1 = 0; // player 1 timer index
const int8_t t_pl2 = 1; // player 2 timer index
const int8_t t_pl3 = 2; // player 3 timer index
const int8_t t_pl4 = 3; // player 4 timer index
const int8_t t_game = 4; // game timer index
const int8_t t_question = 5; // question timer index
const int8_t t_answer = 6; // answer timer index
// bool playerActive[NumPlayers] = {true, true, true, true}; // set if the player is active or deactivated from play
int8_t playerWhoBuzzed = -1; // set to player number if player has buzzed in , -1 for no one currenly buzzed
int8_t playerScore[NumPlayers] = {0, 0, 0, 0};
bool playerInGame[NumPlayers] = {true, true, true, true};
int8_t numActivePlayers = NumPlayers; // how many currently active players do we have
/* Time and timer Specific Variables*/
unsigned long currentMillis = 0; // snapshot taken every cycle of Loop
unsigned long displayMillis = 0; // how many MS since last update of displays
unsigned long LedFXMillis = 0; // how many MS since the last update of the LEDFX
unsigned long standardPause = 1000; // Standard pause one second
int8_t btnDebounce = 10;
int btnLongPress = 1500;
unsigned long displayUpdateFrequency = 1000; // frequency to update the displays for timers in ms (100 is 10x/s)
unsigned long LedFXUpdateFrequency = 1000; // frequency to update the LED Blinking FX
unsigned long gameTime = 0; // Current Game time in Millis
//unsigned long PlayerGameTime[NumPlayers] = 0; // Individual PLayer timers current game time in Millis
unsigned long gameStartT0 = 0; // STart time of current game
unsigned long gameAllowedTime = 0; //variable for allowed time for game (Countown for the game).
unsigned long questionStartT0 = 0; // Start time of current Question
unsigned long questionTime = 0; // elapsed time for question, can be countdown
unsigned long questionAllowedTime = 10000; // variable for allowed time to buzz in question in ms if enabled, default is 10s
unsigned long questionTimeRemaining = 0;
unsigned long answerStartT0 = 0; //Start time of current Answer
unsigned long answerTime = 0; // elapsed time for Answer, can be countdown
unsigned long answerAllowedTime = 10000; // variable for allowed time to answer question in ms if enabled, default is 10s
unsigned long answerTimeRemaining = 0;
/*unsigned long initPlayerTimerS[NumPlayers] = {0, 0, 0, 0}; // Initial length of Individual Player countdown Timers in ms
unsigned long curPlayerTimerS[NumPlayers] = {0, 0, 0, 0}; // current running time of individual player timers in ms
*/
/* Game Mode and Setting specific Variables*/
int8_t gameMode = 0;
int8_t gameType = 0;
int8_t numRounds = 0 ; //Variable that defines the Rounds / game
int8_t numQuestions = 0; //variable that defines how many Questions / Round the game has
int8_t currRound = 0; // Current Game Round
int8_t currQuest = 0; // Current Game Question
int8_t currPlayer = 0; //current Player
bool rotateHost = false; //if in trivia game this will rotate the host every new Round
bool randomAnswer = false; // this will pick a random person to answer instead of waiting for someone to buzz in - Note: need special SFX for this to anounce which player needs to answer :)
bool waitQuestion = false; // set to true if players need to wait for host to ask question and hit start/stop before buzzing
bool questionTimer = false; // Set to true if question countdown timmer should be active for players to buzz
bool answerTimer = false; // set to true if player has a time limit after buzzing to answer, before its auto wrong . Timer canceled by Host right wrong button.
bool capturePlayerTime = false ; // set to true if playing a game that tracks how long something takes for player
bool gameHasRounds = false ; // set to tru if game has rounds setting
bool gameHasQuestionLimit = false ; // set to true if game has limit on # questions
bool gameHasTimer = false; // set true if game has a limit or timer
bool AnyActivePlayer = true;
/*Control Button pins */
const int8_t btnPin[NumCtrlBtns] = { A4, A5, A6, A7, A9, A8, 22, 24, 26, 28, 30, 32, 34, 36};
/* Player Button Pins */
const int8_t playerBtnPin[NumPlayers] = {A0, A1, A2, A3};
/*Game Mode Switch Pins */
const int8_t SwGMPin[NumPlayers] = {38, 40, 42, 44};
const int8_t LEDAnimation[14] = { 23, 25, 27, 29, 31, 33, 35, 37, 39, 41, 43, 45, 47, 49};
/* Output LED Pins */
const int8_t LedStartStop = 23;
const int8_t LedCorrect = 25;
const int8_t LedWrong = 27;
const int8_t LedReset = 29;
const int8_t LedBlue = 31;
const int8_t LedYellow = 10;
const int8_t LedPLbtn[NumPlayers] = {35, 37, 39, 41};
const int8_t LedPL[NumPlayers] = {43, 45, 47, 49};
/* 7 Segment Display Pins */
const int8_t DispClk = 7;
const int8_t DispTimer = 2;
const int8_t DispData[NumPlayers] = {3, 4, 5, 6};
//*****************************************************************
//Copy Below Here
// SFX file numbers
/* For the SFX we will use arrays for the themes*/
//int8_t sfxTheme[5] = {0,1,2,3,4,5); //sfx bank or theme to load
//enumerate the individual sfx below , that need to be played, and then load theme arrays of file locations
//
int8_t sfxVolume = 20;
int8_t sfxMaxVolume = 25;
int8_t sfxThemeId = 0;
int8_t sfxThemeCount = 2;
/*int8_t sfxIntro = 5; // SFX played when game starts
int8_t sfxBuzzer[NumPlayers] = {1, 2, 3, 4}; // Index of sound fx file to play for each player's buzzer
int8_t sfxWrong = 13; // SFX for incorrect answer
int8_t sfxCorrect = 12; // SFX for correct answer
int8_t sfxTimerEnd = 17; // SFX for when a timer ends
int8_t sfxTimerClose = 0; // sfx to be played if the timer is CLose to finished, a last warning to buzz in or complete challenge
*/
// Create an object to address the audio player using the secondary serial interface
//SoftwareSerial softSerial(30, 31); // RX, TX
//DY::Player player(&softSerial);
// SFX Theme Array of filenames that can be played or should be available in each Theme
enum SFXName {
Player1Buzz,
Player2Buzz,
Player3Buzz,
Player4Buzz,
GamePwrOn, //5
SFXThemeAnounce,
GameModeAnnounce,
ConfigConfirm, //8
ConfigDissable,
ReadyToBuzz,
Ready_To_Answer,
CorrectAnswer,
WrongAnswer,
BuzzTimer,
BuzzUrgent, //15
AnswerUrgent,
QuestionTimerEnd,
AnswerTimerEnd,
PointAdded,
PointRemoved,
};
// List of SFX Themes consisting of the Index of the SFX Filename ID/Position in the order of the Enumerated SFXName
// Might need to change this from Int to other type depending on what needs to be passed to the Player.play function
const int BaseSFX[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20};
const int Theme2SFX[] = {21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40};
const int Theme3SFX[] = {31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50};
// Array of pointers to SFX Theme filname arrays
const int* SFXTheme[] = {BaseSFX, Theme2SFX, Theme3SFX};
// Get the current SFX Theme array using the pointer array - In code set the current theme with 'currentSFXTheme = SFXTheme[0...10...];'
const int* currentSFXTheme = SFXTheme[sfxThemeId]; //use number instead of enumeration name call the animation
// Function to play a particular SFX that can be placed in code
// player.play(currentSFXTheme[GamePwrOn]); // REplace 'Player.play' with the DY player Actual PLay command, Replace SfXName with the Name of the Enumerated SFXName like 'GamePwrOn'
char dispBuff[20]; //character buffer to run through the display for text
/* Display Animation Array of char buffers
const char* Ani_DashesLeft[] = {" ", " -", " --", " ---", "----"};
const char* Ani_DashesRight[] = {" ", "- ", "-- ", "--- ", "----"};
const char* Ani_1[] = {"0123", "5678", "9012", "3456", "7890"};
const char* Ani_2[] = {"ABCD", "EFGH", "IJKL", "MNOP", "QRST"};
const char* Ani_3[] = {"!@#$", "%^&*", "()[]", "{}", "~`"};
//Enumerate these names
// Array of pointers to animation arrays
const char** animations[] = {Ani_DashesLeft, Ani_DashesRight, Ani_1, Ani_2, Ani_3};
// Get the current animation array using the pointer array
char** currentAnimation = animations[0]; //use enumeration name instead of number to call the animation
// display.display(currentAnimation[f]); // f is the frame of the animation
*/
/* Class for the buttons with Integrated State machine*/
class PushButton {
public:
PushButton(int pin) : pin_(pin) {
pinMode(pin_, INPUT_PULLUP);
state = 0;
t_0 = 0;
active_ = false; // Default to active
}
bool isPressed() {
return !digitalRead(pin_);
}
void run_SM() {
if (active_) { // Only run state machine if button is active
state_prev = state;
switch (state) {
case 0: // RESET!
state = 1;
break;
case 1: // WAIT
if (isPressed()) {
state = 2;
}
break;
case 2: // ARMING!
t_0 = millis();
state = 3;
break;
case 3: // ARMED
t = millis();
if (t - t_0 > btnDebounce) {
state = 4;
}
if (!isPressed()) {
state = 0;
}
break;
case 4: // DRAWN
t = millis();
if (!isPressed()) {
state = 5;
}
if (t - t_0 > btnLongPress) {
state = 6;
}
break;
case 5: // TRIGGERED!
state = 0;
break;
case 6: // HOLD!
state = 7;
break;
case 7: // LOW WAIT
if (!isPressed()) {
state = 0;
}
break;
}
}
}
int getState() {
return state;
}
int getPrevState() {
return state_prev;
}
void setActive(bool active) {
active_ = active;
}
bool isActive() {
return active_;
}
private:
int pin_;
int state;
int state_prev;
unsigned long t_0;
unsigned long t;
bool active_;
};
/*Create Objects - Displays = Buttons*/
TM1637 displayTimer;
TM1637 display[NumPlayers];
PushButton playerBtn[NumPlayers] = {
playerBtnPin[btnPlayer1],
playerBtnPin[btnPlayer2],
playerBtnPin[btnPlayer3],
playerBtnPin[btnPlayer4]
};
PushButton btn[NumCtrlBtns] = {
btnPin[btnStartStop], // 47
btnPin[btnCorrect], //48
btnPin[btnWrong], //46
btnPin[btnReset], //A13
btnPin[btnYellow], //A14 bottom
btnPin[btnBlue], //A12 top
btnPin[btnMod1Up], //A7
btnPin[btnMod2Up], //A6
btnPin[btnMod3Up], //A5
btnPin[btnMod4Up], //A4
btnPin[btnMod1Dwn], //A3
btnPin[btnMod2Dwn], //A2
btnPin[btnMod3Dwn], //A1
btnPin[btnMod4Dwn] //A0
};
// Define a struct to hold timer data
struct Timer {
bool enabled; // Whether the timer is running
unsigned long limit; // The time limit in milliseconds
unsigned long startTime; // Time when the timer started
bool countUp; // Whether to count up or down
unsigned long elapsedTime; // Elapsed time since start
unsigned long pausedTime; // Amount of time the timer has been in a paused state
unsigned long runTime;
bool expired;
};
// Create an array to hold 7 timers for 4 player timer objects a question timer an answer timer and a game timer
Timer timers[NumTimers];
// Function to initialize a timer
void initTimer(int timerIndex) {
timers[timerIndex].enabled = false;
timers[timerIndex].limit = 0;
timers[timerIndex].startTime = 0;
timers[timerIndex].countUp = false;
timers[timerIndex].elapsedTime = 0;
timers[timerIndex].expired = false;
timers[timerIndex].runTime = 0;
Serial.print("Initialize timer: ");
Serial.println(timerIndex);
}
// Function to start a timer
void startTimer(int timerIndex, unsigned long limit, bool countUp) {
timers[timerIndex].enabled = true;
timers[timerIndex].limit = limit;
timers[timerIndex].startTime = currentMillis;
timers[timerIndex].countUp = countUp;
timers[timerIndex].pausedTime = 0;
timers[timerIndex].elapsedTime = 0;
timers[timerIndex].expired = false;
timers[timerIndex].runTime = 0;
Serial.print("Start timer: ");
Serial.println(timerIndex);
}
void resumeTimer(int timerIndex) {
timers[timerIndex].runTime += timers[timerIndex].elapsedTime;
timers[timerIndex].startTime = currentMillis;
timers[timerIndex].enabled = true;
Serial.print("Resume timer: ");
Serial.print(timerIndex);
Serial.print(" PausedTime: ");
Serial.print(timers[timerIndex].pausedTime);
Serial.print(" startTime: ");
Serial.println(timers[timerIndex].startTime);
}
void pauseTimer(int timerIndex) {
timers[timerIndex].pausedTime = currentMillis;
timers[timerIndex].enabled = false;
Serial.print("Pause timer: ");
Serial.print(timerIndex);
Serial.print(" startTime: ");
Serial.println(timers[timerIndex].startTime);
}
// Function to stop a timer
void stopTimer(int timerIndex) {
timers[timerIndex].pausedTime = 0;
timers[timerIndex].startTime = 0;
timers[timerIndex].enabled = false;
}
// Function to update all timers
void updateTimers() {
for (int i = 0; i < NumTimers; i++) {
if (timers[i].enabled) {
timers[i].elapsedTime = (currentMillis - timers[i].startTime);
// Check if the timer has reached the limit
if ((timers[i].elapsedTime + timers[i].runTime) >= timers[i].limit) {
if (timers[i].limit != 0) {
Serial.println("Timer Limit: ");
timers[i].expired = true;
stopTimer(i);
}
// Handle timer expiration (e.g., trigger an interrupt)
// ...
}
}
}
}
/* Declare and set up some enumerated variables */
GS state = GS::reset;
DS dispState = DS::hold;
LFXS ledFXState = LFXS::hold;
GS next_state = GS::reset;
DS next_dispState = DS::hold;
LFXS next_ledFXState = LFXS::hold;
// Additional setup code that runs once when powered on before the main loop runs
void setup() {
Serial.begin(115200); //start serial interface for debugging
Serial.println("Start Setup"); //debug
/*// Start the software interface to the DY audio player
softSerial.begin(9600);
// Attach the player object to the serial interface
player.begin();
// Set OneOff playback mode (i.e. non-looping)
player.setCycleMode(DY::PlayMode::OneOff);
// Volume is a value from 0-30
player.setVolume(sfxVolume);
// Ensure the player is not currently playing anything
player.stop();
// Play the intro theme music
player.playSpecified(currentSFXTheme[GamePwrOn]);
*/
//Game mode initial Configuration
gameMode = (!digitalRead(SwGMPin[3]) & 0x1) | ((!digitalRead(SwGMPin[2]) & 0x1)<<1) | ((!digitalRead(SwGMPin[1]) & 0x1) << 2) |((!digitalRead(SwGMPin[0]) & 0x1)<< 3);
//Start and Initialize Displays by turning them on, setting Prightness and displaying an initial Value to test all segments!
displayTimer.begin(DispClk,DispTimer,4);
for (int i=0; i < NumPlayers; i++) { display[i].begin(DispClk,DispData[i],4); display[i].setBrightness(4); }
displayTimer.setBrightness(4);
// Configure the initial states of the Arduino I/O pins
for (int i=0; i < NumPlayers; i++) {
//Arrays of Input Pins
pinMode(SwGMPin[i], INPUT_PULLUP); // Game Mode Switchs
//Arrays of Output Pins
pinMode(LedPLbtn[i], OUTPUT); // LED on the Player Button
pinMode(LedPL[i], OUTPUT); // LED Indicator lights for each Player
};
//Additional individual Output Pins
pinMode(LedStartStop, OUTPUT); // LED Indicator light for the START/STOP button
pinMode(LedCorrect, OUTPUT); // LED Indicator light for the Correct Answer Button
pinMode(LedWrong, OUTPUT); // LED Indicator light for the Wrong Answer Button
pinMode(LedReset, OUTPUT); // LED Indicator light for the Reset Button
pinMode(LedBlue, OUTPUT); // LED Indicator light for the Blue Button
pinMode(LedYellow, OUTPUT); // LED Indicator light for the Yellow Button
// Initialize all timers
for (int i = 0; i < 7; i++) { initTimer(i);}
displayMillis = millis();
updateGameModeVariables();
Serial.print("Game Mode: ");
Serial.println(gameMode);
}
void loop() { // Main loop, Put any state machines here that need to be monitored every cycle
currentMillis = millis();
updateTimers();
ledFXState = next_ledFXState;
state = next_state;
dispState = next_dispState;
runLEDFXSwitch();
runDisplaySwitch();
runPlayerButtonsSM();
runControlButtonsSM();
runGameSwitch();
}
/*State Machine Section*/
void runPlayerButtonsSM() { //runs any active player button state machines each loop of the system, ignores inactive code
for (int i=0; i < NumPlayers; i++) {
if (playerBtn[i].isActive() && playerInGame[i]) {playerBtn[i].run_SM();};
};
}
void runControlButtonsSM() { //runs any active Control button state machines each loop of the system, ignores inactive code
for (int i=0; i < NumCtrlBtns; i++) {
if (btn[i].isActive()) {btn[i].run_SM();};
};
}
void runLEDFXSwitch() { // LED FX State machine - use this to run animations or set LED's based on varioous game modes and transitions
switch (ledFXState)
{
case LFXS::hold: // do nothing
break;
case LFXS::confTrivia: // do nothing
break;
case LFXS::confChallenge: // do nothing
break;
case LFXS::confTurns: // do nothing
break;
case LFXS::hostStartStop: // blink the Start Stop button 2x per second to alert host that they need to do something
if ((currentMillis - LedFXMillis) >= displayUpdateFrequency/2){
LedFXMillis = currentMillis;
digitalWrite(LedStartStop, !digitalRead(LedStartStop));
}
break;
case LFXS::urgentQuestion: // When 20% of question timer left OR Challenge game timer left, flash active Player Button and status LEDs to remind players to Buzz!
if ((currentMillis - LedFXMillis) >= (displayUpdateFrequency/10)){
LedFXMillis = currentMillis;
for (int i=0; i<NumPlayers; i++) {
digitalWrite(LedPLbtn[i], playerBtn[i].isActive() && !digitalRead(LedPLbtn[i]) ? HIGH : LOW);
digitalWrite(LedPL[i], playerBtn[i].isActive() && !digitalRead(LedPL[i]) ? HIGH : LOW);
}
}
break;
case LFXS::urgentAnswer: // When 10% of answer timer left, flash urgently the correct and wrong LED's AND the 4 player LEDs to remind everyone to answer/validate fast
if ((currentMillis - LedFXMillis) >= (displayUpdateFrequency/10)){
LedFXMillis = currentMillis;
digitalWrite(LedCorrect, !digitalRead(LedCorrect) ? HIGH : LOW);
digitalWrite(LedWrong, !digitalRead(LedWrong) ? HIGH : LOW);
}
break;
case LFXS::urgentChallenge: // Blink the winning player
break;
case LFXS::urgentTurn: // light just the wrong button and player who buzzed, off for all other lights and goto hold
break;
case LFXS::blinkActivePlayer: // blink the active player every half second
if ((currentMillis - LedFXMillis) >= (displayUpdateFrequency/2)){
LedFXMillis = currentMillis;
for (int i=0; i<NumPlayers; i++) {
digitalWrite(LedPLbtn[i], playerBtn[i].isActive() && !digitalRead(LedPLbtn[i]) ? HIGH : LOW);
digitalWrite(LedPL[i], playerBtn[i].isActive() && !digitalRead(LedPL[i]) ? HIGH : LOW);
}
}
break;
}
}
void runDisplaySwitch() { //Display update state machine - uses these states to run animations or set Displays to show certain things based on various game modes
switch (dispState)
{
case DS::hold:
break;
case DS::confTrivia: //
if ((currentMillis - displayMillis) >= displayUpdateFrequency){
displayMillis = currentMillis;
sprintf(dispBuff,"Triv", numRounds );
displayTimer.displayPChar(dispBuff);
sprintf(dispBuff,"R-%1d", numRounds );
display[0].displayPChar(dispBuff);
sprintf(dispBuff,"Q-%1d", numQuestions );
display[1].displayPChar(dispBuff);
sprintf(dispBuff,"FX%1d", sfxThemeId );
display[2].displayPChar(dispBuff);
Serial.println("Trivia Configuration Mode"); //debug
}
break;
case DS::confChallenge: //
if ((currentMillis - displayMillis) >= displayUpdateFrequency){
displayMillis = currentMillis;
sprintf(dispBuff,"CHAL", numRounds );
displayTimer.displayPChar(dispBuff);
sprintf(dispBuff,"R-%1d", numRounds );
display[0].displayPChar(dispBuff);
sprintf(dispBuff,"Q-%1d", numQuestions );
display[1].displayPChar(dispBuff);
sprintf(dispBuff,"FX%1d", sfxThemeId );
display[2].displayPChar(dispBuff);
sprintf(dispBuff,"----", sfxThemeId );
display[3].displayPChar(dispBuff);
Serial.println("Challenge Configuration Mode"); //debug
}
break;
case DS::confTurns: //
if ((currentMillis - displayMillis) >= displayUpdateFrequency){
displayMillis = currentMillis;
sprintf(dispBuff,"TURN", numRounds );
displayTimer.displayPChar(dispBuff);
sprintf(dispBuff,"R-%1d", numRounds );
display[0].displayPChar(dispBuff);
sprintf(dispBuff,"Q-%1d", numQuestions );
display[1].displayPChar(dispBuff);
sprintf(dispBuff,"FX%1d", sfxThemeId );
display[2].displayPChar(dispBuff);
sprintf(dispBuff,"----", sfxThemeId );
display[3].displayPChar(dispBuff);
Serial.println("Player Turns Configuration Mode"); //debug
}
break;
case DS::triviaQTimer: // update main display with question timer based on frequency - trigger 10% Urgent buzz, trigger question timer end
/*Updates the Question timer by calculating the remaining time, display is updated based on the display update frequency and if the timer hits 0 the display goes into hold and the gamestate is set to timer ran out*/
if ((currentMillis - displayMillis) >= displayUpdateFrequency){
displayMillis = currentMillis;
Serial.println("Trivia Question Timer update"); //debug
// Trigger TRansition - Question Timer ends
if (timers[t_question].expired) {
// this should move to triviaQuestionEnd()
triviaQuestionTimerEnd();
} else {
if ( (timers[t_question].elapsedTime + timers[t_question].runTime) >= (questionAllowedTime * 0.8)){
next_ledFXState = LFXS::urgentQuestion;
//player.playSpecified(currentSFXTheme[BuzzUrgent]);
Serial.println(questionAllowedTime *0.2);
Serial.println("20% question timer remaining"); //debug
}}
updateGameTimer((questionAllowedTime-(timers[t_question].elapsedTime + timers[t_question].runTime)));
}
break;
case DS::triviaATimer: // update main display with question timer based on frequency - trigger 10% Urgent buzz, trigger question timer end
/*Updates the Question timer by calculating the remaining time, display is updated based on the display update frequency and if the timer hits 0 the display goes into hold and the gamestate is set to timer ran out*/
if ((currentMillis - displayMillis) >= displayUpdateFrequency){
displayMillis = currentMillis;
Serial.println("Trivia Answer Timer update"); //debug
// Trigger TRansition - Question Timer ends
if (timers[t_answer].expired) {
triviaAnswerTimerEnd();
} else {
if ( (timers[t_answer].elapsedTime + timers[t_answer].runTime) >= (answerAllowedTime * 0.8)){
next_ledFXState = LFXS::urgentAnswer;
//player.playSpecified(currentSFXTheme[AnswerUrgent]);
Serial.println((timers[t_answer].elapsedTime + timers[t_answer].runTime));
Serial.println(answerAllowedTime *0.2);
Serial.println("20% question timer remaining"); //debug
}}
updateGameTimer((answerAllowedTime-(timers[t_answer].elapsedTime + timers[t_answer].runTime)));
}
break;
case DS::turnsActive:
if ((currentMillis - displayMillis) >= displayUpdateFrequency/10){
displayMillis = currentMillis;
updateAllPlTimers();
}
break;
}
}
void runGameSwitch() { // Main Game state machine - used to control what happens , lights are flashed, displays updated SFX are played or what controls are monitored
switch (state)
{
case GS::reset: //reset game functions by reading mode switches - same as Power on State
// Detect current Game Mode
gameMode = (!digitalRead(SwGMPin[3]) & 0x1) | ((!digitalRead(SwGMPin[2]) & 0x1)<<1) | ((!digitalRead(SwGMPin[1]) & 0x1) << 2) |((!digitalRead(SwGMPin[0]) & 0x1)<< 3);
activateAllPlayers(); //activate all players to begin with
updateGameModeVariables();
delay(1000); // just give a little delay when resetting the game.
//Transition to Configure Game State
Serial.println("Transition to Configure Game"); //debug
Game_confGame();
break;
case GS::confGame: //Pause to allow host to configure additional game functions and goto next state.
//check button states and perform actions add a delay if not changing state
if(btn[btnWrong].getState() == 5 && gameHasRounds) { GameAddRound();}
if(btn[btnCorrect].getState() == 5 && gameHasQuestionLimit) { GameAddQuestion();}
// Blue cycles the SFX theme and plays ID 0 which is the name of the theme
if(btn[btnBlue].getState() == 5 ) { GameCycleSFXThemeID();}
// white/reset increase volume
if(btn[btnReset].getState() == 5 ) { gameVolUp();}
// Yellow decrease volume
if(btn[btnYellow].getState() == 5 ) { gameVolDwn();}
//State 7 is the long press and is only triggered if button held down for xxxms
if(btn[btnReset].getState() == 7) { Game_reset();}
//Configure Players with Up down switches
if(btn[btnMod1Up].getState() == 5) { /*player.playSpecified(currentSFXTheme[ConfigConfirm]);*/ playerInGame[0] = true; activatePlayer(0); LED_activePlayers(); Serial.println("Player 1 Active"); delay(100);}
if(btn[btnMod2Up].getState() == 5) { /*player.playSpecified(currentSFXTheme[ConfigConfirm]); */ playerInGame[1] = true; activatePlayer(1); LED_activePlayers(); Serial.println("Player 2 Active"); delay(100);}
if(btn[btnMod3Up].getState() == 5) { /*player.playSpecified(currentSFXTheme[ConfigConfirm]); */ playerInGame[2] = true; activatePlayer(2); LED_activePlayers(); Serial.println("Player 3 Active"); delay(100);}
if(btn[btnMod4Up].getState() == 5) { /*player.playSpecified(currentSFXTheme[ConfigConfirm]); */ playerInGame[3] = true; activatePlayer(3); LED_activePlayers(); Serial.println("Player 4 Active"); delay(100);}
if(btn[btnMod1Dwn].getState() == 5) { /*player.playSpecified(currentSFXTheme[ConfigDissable]); */ playerInGame[0] = false; deactivatePlayer(0); LED_activePlayers(); Serial.println("Player 1 Deactivated"); delay(100);}
if(btn[btnMod2Dwn].getState() == 5) { /*player.playSpecified(currentSFXTheme[ConfigDissable]); */ playerInGame[1] = false; deactivatePlayer(1); LED_activePlayers(); Serial.println("Player 2 Deactivated"); delay(100);}
if(btn[btnMod3Dwn].getState() == 5) { /*player.playSpecified(currentSFXTheme[ConfigDissable]); */ playerInGame[2] = false; deactivatePlayer(2); LED_activePlayers(); Serial.println("Player 3 Deactivated"); delay(100);}
if(btn[btnMod4Dwn].getState() == 5) { /*player.playSpecified(currentSFXTheme[ConfigDissable]);*/ playerInGame[3] = false; deactivatePlayer(3); LED_activePlayers(); Serial.println("Player 4 Deactivated"); delay(100);}
// if Triggered, lock in config and setup any remaining variables to start game
if(btn[btnStartStop].getState() == 5) { gameStartGame();}
break;
case GS::askQuestion: // Host asks a question and this state monitors button press
//Start Game Timer and wait player buzz
if(btn[btnStartStop].getState() == 5) { Game_waitPlayerBuzz();}
//State 7 is the long press and is only triggered if button held down for xxxms
if(btn[btnReset].getState() == 7) { Game_reset();}
//Configure Players scores with Up down switches
if(btn[btnMod1Up].getState() == 5) { if ( playerInGame[0] ) {/*player.playSpecified(currentSFXTheme[PointAdded]); */playerScore[0]++; dispUpdatePlayerScores(); Serial.println("Player 1 +1"); delay(100);}}
if(btn[btnMod2Up].getState() == 5) { if ( playerInGame[1] ) {/*player.playSpecified(currentSFXTheme[PointAdded]); */playerScore[1]++; dispUpdatePlayerScores(); Serial.println("Player 2 +1"); delay(100);}}
if(btn[btnMod3Up].getState() == 5) { if ( playerInGame[2] ) {/*player.playSpecified(currentSFXTheme[PointAdded]); */playerScore[2]++; dispUpdatePlayerScores(); Serial.println("Player 3 +1"); delay(100);}}
if(btn[btnMod4Up].getState() == 5) { if ( playerInGame[3] ) {/*player.playSpecified(currentSFXTheme[PointAdded]); */playerScore[3]++; dispUpdatePlayerScores(); Serial.println("Player 4 +1"); delay(100);}}
if(btn[btnMod1Dwn].getState() == 5) { if ( playerInGame[0] & playerScore[0] != 0) {/*player.playSpecified(currentSFXTheme[PointRemoved]); */playerScore[0]--; dispUpdatePlayerScores(); Serial.println("Player 1 -1"); delay(100);}}
if(btn[btnMod2Dwn].getState() == 5) { if ( playerInGame[1] & playerScore[1] != 0) {/*player.playSpecified(currentSFXTheme[PointRemoved]); */playerScore[1]--; dispUpdatePlayerScores(); Serial.println("Player 2 -1"); delay(100);}}
if(btn[btnMod3Dwn].getState() == 5) { if ( playerInGame[2] & playerScore[2] != 0) {/*player.playSpecified(currentSFXTheme[PointRemoved]); */playerScore[2]--; dispUpdatePlayerScores(); Serial.println("Player 3 -1"); delay(100);}}
if(btn[btnMod4Dwn].getState() == 5) { if ( playerInGame[3] & playerScore[3] != 0) {/*player.playSpecified(currentSFXTheme[PointRemoved]); */playerScore[3]--; dispUpdatePlayerScores(); Serial.println("Player 4 -1"); delay(100);}}
break;
case GS::waitPlayerBuzz: // Put the Player Buzz in code here
// Also Allow the host to Hit start stop to start a timer if players taking too long or end the question (wont work if mode already has a timer)
for (int i=0; i<NumPlayers; i++) {
if (digitalRead(playerBtnPin[i]) == LOW && playerBtn[i].isActive() && playerInGame[i]) {
playerWhoBuzzed = i;
//player.playSpecified(currentSFXTheme[Player1Buzz]);
//startTimer(int timerIndex, unsigned long limit, bool countUp)
switch (gameType){
case trivia:
if (questionTimer) {stopTimer(t_question);}
if (answerTimer) {armAnswerTimer(); }
next_ledFXState = LFXS::hold;
next_dispState = DS::hold;
LED_playerWhoBuzzed(playerWhoBuzzed); //Update LEDs for players
Game_validateAnswer();
break;
case challenge:
break;
case turns:
playerBtn[playerWhoBuzzed].setActive(false);
pauseTimer(playerWhoBuzzed);
setNextPlayer();
break;
}
//stop the question timer and Hold the Display
}
}
//State 7 is the long press and is only triggered if button held down for xxxms
if(btn[btnReset].getState() == 7) { Game_reset();}
// Adds another Full amount of Answer time (Default is 10s) to the current Answer timer
if(btn[btnStartStop].getState() == 5) { if (questionTimer) {timers[t_question].limit = timers[t_question].limit + questionAllowedTime;}}
//Configure Players scores with Up down switches
if(btn[btnMod1Up].getState() == 5) { if ( playerInGame[0] ) {/*player.playSpecified(currentSFXTheme[PointAdded]); */ playerScore[0]++; dispUpdatePlayerScores(); Serial.println("Player 1 +1"); delay(100);}}
if(btn[btnMod2Up].getState() == 5) { if ( playerInGame[1] ) {/*player.playSpecified(currentSFXTheme[PointAdded]); */ playerScore[1]++; dispUpdatePlayerScores(); Serial.println("Player 2 +1"); delay(100);}}
if(btn[btnMod3Up].getState() == 5) { if ( playerInGame[2] ) {/*player.playSpecified(currentSFXTheme[PointAdded]); */ playerScore[2]++; dispUpdatePlayerScores(); Serial.println("Player 3 +1"); delay(100);}}
if(btn[btnMod4Up].getState() == 5) { if ( playerInGame[3] ) {/*player.playSpecified(currentSFXTheme[PointAdded]); */ playerScore[3]++; dispUpdatePlayerScores(); Serial.println("Player 4 +1"); delay(100);}}
if(btn[btnMod1Dwn].getState() == 5) { if ( playerInGame[0] & playerScore[0] != 0) {/*player.playSpecified(currentSFXTheme[PointRemoved]); */ playerScore[0]--; dispUpdatePlayerScores(); Serial.println("Player 1 -1"); delay(100);}}
if(btn[btnMod2Dwn].getState() == 5) { if ( playerInGame[1] & playerScore[1] != 0) {/*player.playSpecified(currentSFXTheme[PointRemoved]); */ playerScore[1]--; dispUpdatePlayerScores(); Serial.println("Player 2 -1"); delay(100);}}
if(btn[btnMod3Dwn].getState() == 5) { if ( playerInGame[2] & playerScore[2] != 0) {/*player.playSpecified(currentSFXTheme[PointRemoved]); */ playerScore[2]--; dispUpdatePlayerScores(); Serial.println("Player 3 -1"); delay(100);}}
if(btn[btnMod4Dwn].getState() == 5) { if ( playerInGame[3] & playerScore[3] != 0) {/*player.playSpecified(currentSFXTheme[PointRemoved]); */ playerScore[3]--; dispUpdatePlayerScores(); Serial.println("Player 4 -1"); delay(100);}}
break;
case GS::validateAnswer:
//add code to be able to restart last question
if (questionTimer) {
if (timers[t_question].expired) {
dispUpdatePlayerScores();
activateAllPlayers();
//Play no one answered SFX
// change code to "Next Question routine"
Game_askQuestion();
}
}
// Adds another Full amount of Answer time (Default is 10s) to the current Answer timer
if(btn[btnStartStop].getState() == 5) { if (answerTimer) {timers[t_answer].limit = timers[t_answer].limit + answerAllowedTime;}}
//correct Answer Registered
if(btn[btnCorrect].getState() == 5) { gameCorrect();}
//Wrong Answer!
if(btn[btnWrong].getState() == 5) { gameWrong();}
break;
case GS::waitPlayerChallenge:
break;
case GS::waitPlayerTurn:
break;
case GS::gameOver:
//State 7 is the long press and is only triggered if button held down for xxxms
if(btn[btnReset].getState() == 7) { Game_reset();}
break;
}
}
/**********************************/
/* Game Update Functions */
/**********************************/
// Function to activate buzzer for a specific player if their player is In game/Enabled
void activatePlayer(int player) {if(playerInGame[player]) {playerBtn[player].setActive(true);}}
void setNextPlayer() {
Serial.print("currPlayer :");
Serial.print(currPlayer);
Serial.print(" Elapsed Time:");
Serial.println(timers[currPlayer].elapsedTime);
//playerBtn[currPlayer].setActive(false);
//if (timers[currPlayer].enabled) {pauseTimer(currPlayer);}
Serial.print("Current Player: ");
Serial.println(currPlayer);
if (currPlayer + 1 < NumPlayers) {
for (int8_t i=currPlayer + 1; i<NumPlayers; i++) {
if(playerInGame[i]) {
currPlayer = i;
break;
}
}
} else {
for (int8_t i=0; i<currPlayer; i++) {
if(playerInGame[i]) {
currPlayer = i;
break;
}
}
}
Serial.print("Next Player: ");
Serial.println(currPlayer);
Serial.print("Next player Elapsed time");
Serial.println(timers[currPlayer].elapsedTime);
playerBtn[currPlayer].setActive(true);
if (timers[currPlayer].elapsedTime > 0 ) {
resumeTimer(currPlayer);
} else {
startTimer(currPlayer, 0, true);
}
}
void deactivatePlayer(int player) {playerBtn[player].setActive(false);} // Function to deactivate buzzer for a specific player
void IsPlaying(int player) {playerInGame[player] = true;} // Enable a Player
void NotPlaying(int player) { playerInGame[player] = false;} // Dissable a Player
void activateAllPlayers() {for (int8_t i=0; i<NumPlayers; i++) {activatePlayer(i);}} // Activate all Enabled players buzzers
void deactivateAllPlayers() { for (int i=0; i<NumPlayers; i++) {deactivatePlayer(i);}} // Deactivate all player buzzers
void deactivateCtrlBtns() {for (int i=0; i<NumCtrlBtns; i++) {btn[i].setActive(false);}} // DeActivate all control Buttons
void activateCtrlBtns() {for (int i=0; i<NumCtrlBtns; i++) {btn[i].setActive(true);}} // DeActivate all control Buttons
void armQuestionTimer() {updateGameTimer(questionAllowedTime);} // Get Question Timer Ready and display initial Question allowed time on screen
void armAnswerTimer() {updateGameTimer(answerAllowedTime);} // Get Answer Timer Ready and display initial Question allowed time on screen
// Update Player Score display for all players
void dispAllPlScores() {for (int8_t i=0; i<NumPlayers; i++) {if(playerInGame[i]) {display[i].displayInt(playerScore[i]); display[i].hideMultiSegment(0b00001100);}}}
// Update Player Score display single player
void dispPlScore(int8_t player, int score) {if(playerInGame[player]) {display[player].displayInt(score);}}
// Light Player Who Buzzed LED and Dissable all other PLayer LEDS
void LED_playerWhoBuzzed(int8_t player) {for (int8_t i=0; i<NumPlayers; i++) { digitalWrite(LedPLbtn[i], (playerWhoBuzzed == i)); digitalWrite(LedPL[i], (playerWhoBuzzed == i));}}
// Update main game countdown timer on the display
void updateGameTimer(unsigned long gameTime) {displayTimer.displayTime(numberOfMinutes(gameTime), numberOfSeconds(gameTime),1);}
void updateAllPlTimers() {
for (int8_t i=0; i<NumPlayers; i++) {
if (playerInGame[i]) {
display[i].displayTime(numberOfMinutes((timers[i].elapsedTime + timers[i].runTime)), numberOfSeconds((timers[i].elapsedTime + timers[i].runTime)),1);
} else {
display[i].displayClear();
}
}
}
//Add Round if game allows Rounds
void GameAddRound() {
if (numRounds < 10) {
numRounds++;
} else {
numRounds = 1;
}
Serial.println("Round Added");
delay(50);
}
void GameAddQuestion() {
if (numQuestions < 15) {
numQuestions++;
} else {
numQuestions = 1;
}
Serial.println("Question Added");
delay(50);
}
void GameCycleSFXThemeID() {
if (sfxThemeId < sfxThemeCount) {
sfxThemeId++;
} else {
sfxThemeId = 0;
}
//const int arrLen = sizeof(array) / sizeof(array[0]);
Serial.println("SFX Theme change Added");
delay(50);
}
void gameVolUp() {
if (sfxVolume < sfxMaxVolume) {
sfxVolume++;
}
// add command to send volume to SFX
//player.setVolume(sfxVolume);
Serial.println("Volume UP");
delay(50);
}
void gameVolDwn(){
if (sfxVolume > 0) {
sfxVolume--;
}
// add command to send volume to SFX
//player.setVolume(sfxVolume);
Serial.println("Volume Dwn");
delay(50);
}
void gameCorrect(){
Serial.println("Correct Answer!!");
//play correct sfx
//update Scores
playerScore[playerWhoBuzzed]++;
//player.playSpecified(currentSFXTheme[CorrectAnswer]);
Serial.println("Updating LED's of player who buzzed");
dispUpdatePlayerScores();
activateAllPlayers();
if (answerTimer) {armAnswerTimer(); }
LED_activeButtons();
LED_activePlayers();
if (waitQuestion) {
Game_askQuestion();
} else {
Game_waitPlayerBuzz();
}
}
void gameWrong(){
Serial.println("Wrong!");
//play wrong sfx
//Deactivate PLayer
deactivatePlayer(playerWhoBuzzed);
//player.playSpecified(currentSFXTheme[WrongAnswer]);
//check if any players active
numActivePlayers = 0;
for (int i=0; i<NumPlayers; i++) {
if (playerInGame[i]) {numActivePlayers++;}
}
if(numActivePlayers > 0) {
LED_activePlayers();
if (answerTimer) {armAnswerTimer(); }
Game_waitPlayerBuzz();
} else {
if (waitQuestion) {
activateAllPlayers();
LED_activeButtons();
next_dispState = DS::hold;
next_ledFXState = LFXS::hold;
stopTimer(t_question);
Game_askQuestion();
} else {
activateAllPlayers();
LED_activePlayers();
Game_waitPlayerBuzz();
}
}
/*dispUpdatePlayerScores();
activateAllPlayers();
LED_activeButtons();
LED_activePlayers();
*/
/* change the above to this if you want the host to have to hit start/stop after each wrong answer or if it goes right to player buzz
if (waitQuestion) {
Game_askQuestion();
} else {
Game_waitPlayerBuzz();
}*/
}
// Light all active Players
void LED_activePlayers(){for (int i=0; i<NumPlayers; i++) { digitalWrite(LedPLbtn[i], playerBtn[i].isActive() && playerInGame[i] ? HIGH : LOW); digitalWrite(LedPL[i], playerBtn[i].isActive() && playerInGame[i] ? HIGH : LOW);}
}
//Light all active Buttons
void LED_activeButtons(){
digitalWrite(LedStartStop, btn[btnStartStop].isActive() ? HIGH : LOW);
digitalWrite(LedCorrect, btn[btnCorrect].isActive() ? HIGH : LOW);
digitalWrite(LedWrong, btn[btnWrong].isActive() ? HIGH : LOW);
digitalWrite(LedBlue, btn[btnBlue].isActive() ? HIGH : LOW);
digitalWrite(LedReset, btn[btnReset].isActive() ? HIGH : LOW);
digitalWrite(LedYellow, btn[btnYellow].isActive() ? HIGH : LOW);
}
//clear all displays
void Reset_Displays() { displayTimer.displayClear(); for (int i=0; i < NumPlayers; i++) { display[i].displayClear();}}
//refresh PLayer scores
void dispUpdatePlayerScores() { for (int i=0; i<NumPlayers; i++) { if ( playerInGame[i]) {dispPlScore(i,playerScore[i]); display[i].hideMultiSegment(0b00001100);} else {display[i].displayClear();}}}
void activateScoreSwitches(){
btn[btnMod1Up].setActive(true);
btn[btnMod1Dwn].setActive(true);
btn[btnMod2Up].setActive(true);
btn[btnMod2Dwn].setActive(true);
btn[btnMod3Up].setActive(true);
btn[btnMod3Dwn].setActive(true);
btn[btnMod4Up].setActive(true);
btn[btnMod4Dwn].setActive(true);
}
void deactivateScoreSwitches(){
btn[btnMod1Up].setActive(false);
btn[btnMod1Dwn].setActive(false);
btn[btnMod2Up].setActive(false);
btn[btnMod2Dwn].setActive(false);
btn[btnMod3Up].setActive(false);
btn[btnMod3Dwn].setActive(false);
btn[btnMod4Up].setActive(false);
btn[btnMod4Dwn].setActive(false);
}
/*Game state Transition Functions */
//activate all buttons, run LED test animation and move to confGame game state
void Game_confGame() {
//setup buttons for next state
btn[btnStartStop].setActive(true);
btn[btnYellow].setActive(true);
btn[btnBlue].setActive(true);
btn[btnCorrect].setActive(true);
btn[btnWrong].setActive(true);
btn[btnReset].setActive(true);
btn[btnMod1Up].setActive(true);
btn[btnMod1Dwn].setActive(true);
btn[btnMod2Up].setActive(true);
btn[btnMod2Dwn].setActive(true);
btn[btnMod3Up].setActive(true);
btn[btnMod3Dwn].setActive(true);
btn[btnMod4Up].setActive(true);
btn[btnMod4Dwn].setActive(true);
animation_All_LED_blink();
LED_activeButtons();
LED_activePlayers();
next_state = GS::confGame;
Serial.println("Moving to confGame");
}
void Game_reset() {
for (int i=0; i < NumPlayers; i++) { playerScore[i] = 0; playerInGame[i] = true;}
Reset_Displays();
activateAllPlayers();
next_dispState = DS::hold;
next_ledFXState = LFXS::hold;
next_state = GS::reset;
// Play the intro theme music
//player.playSpecified(currentSFXTheme[GamePwrOn]);
//next_dispState = DS::reset;
LED_activePlayers();
Serial.println("Resetting the Game");
}
void Game_askQuestion() {
//deactivateCtrlBtns();
activateScoreSwitches();
btn[btnStartStop].setActive(true);
btn[btnReset].setActive(true);
if (questionTimer) {armQuestionTimer();}
next_dispState = DS::hold;
next_ledFXState = LFXS::hostStartStop;
next_state = GS::askQuestion;
LED_activeButtons();
LED_activePlayers();
// code to deal with Questions and Rounds
if ( gameHasRounds || gameHasQuestionLimit){
if (currQuest < numQuestions) {
newQuestion();
} else {
currQuest = 1;
if (currRound < numRounds) {
newRound();
} else {
Game_gameOver();
}
}
}
Serial.println("Host can now ask Question");
}
void Game_waitPlayerBuzz() {
//deactivateCtrlBtns(); //Turn off all Ctrl buttons before next mode
activateScoreSwitches();
playerWhoBuzzed = -1;
btn[btnReset].setActive(true);
btn[btnStartStop].setActive(true);
LED_activeButtons();
if (questionTimer) {
startTimer(t_question, questionAllowedTime, false);
//player.playSpecified(currentSFXTheme[BuzzTimer]);
next_dispState = DS::triviaQTimer;
}else {
next_dispState = DS::hold;
}
next_state = GS::waitPlayerBuzz;
next_ledFXState = LFXS::hold;
LED_activePlayers();
Serial.println("Waiting for Active Player to Buzz in");
}
void Game_waitPlayerChallenge() {
next_state = GS::waitPlayerChallenge;
Serial.println("Game State Change to waitPlayerChallenge");
}
void Game_waitPlayerTurn() {
next_state = GS::waitPlayerTurn;
Serial.println("Game State Change to waitPlayerTurn");
}
void Game_validateAnswer() {
btn[btnCorrect].setActive(true);
btn[btnStartStop].setActive(true);
btn[btnWrong].setActive(true);
activateScoreSwitches();
if (answerTimer) {
startTimer(t_answer, answerAllowedTime, false);
next_dispState = DS::triviaATimer;
}else {
next_dispState = DS::hold;
next_ledFXState = LFXS::hold;
}
LED_activeButtons();
next_state = GS::validateAnswer;
}
void Game_gameOver() {
next_state = GS::gameOver;
next_dispState = DS::hold;
next_ledFXState = LFXS::hold;
btn[btnReset].setActive(true);
LED_activeButtons();
sprintf(dispBuff,"OVER");
displayTimer.displayPChar(dispBuff);
Serial.println("Game Over!!");
}
void NextQuestion() {
// reset game for host to ask next question
}
void addQuestion() {
// add 1 to the Question Limit if Game allows and roll back to 1 after limit
}
void addRound() {
// add 1 to the Round Limit if Game allows and roll back to 1 after limit
}
void newQuestion() {
currQuest++;
//Play new Question SFX
sprintf(dispBuff,"NewQ");
displayTimer.displayPChar(dispBuff);
delay(750);
sprintf(dispBuff,"Q %2d", currQuest );
displayTimer.displayPChar(dispBuff);
delay(750); //make sure total Animation delay is long enough for SFX
}
void newRound() {
currRound++;
//Play new Round SFX
sprintf(dispBuff,"NewR");
displayTimer.displayPChar(dispBuff);
delay(750);
sprintf(dispBuff,"R %2d", currRound );
displayTimer.displayPChar(dispBuff);
delay(750); //make sure total Animation delay is long enough for SFX
}
void triviaQuestionTimerEnd () {
Serial.println("Question timer ran out - Moving to next question.. same as correct without points");
//player.playSpecified(currentSFXTheme[QuestionTimerEnd]); //PLay question timer end SFX
Game_validateAnswer();
}
void triviaAnswerTimerEnd() {
Serial.println("Answer timer ran out, Same as WRONG");
//player.playSpecified(currentSFXTheme[AnswerTimerEnd]);
next_dispState = DS::hold;
next_ledFXState = LFXS::hold;
//play answer timer end SFX
deactivatePlayer(playerWhoBuzzed);
if (waitQuestion) {
Game_askQuestion();
} else {
Game_waitPlayerBuzz();
}
}
//after configuration screen on reboot / reset, this function finalizes settings and starts the game
void gameStartGame(){
//check if any players active
numActivePlayers = 0;
for (int i=0; i<NumPlayers; i++) { if (playerInGame[i]) {numActivePlayers++;}}
if(numActivePlayers>0 ){
//reset displays
Reset_Displays();
next_dispState = DS::hold;
/***********************************/
/* Change this switch to gametype instead */
/************************************/
switch (gameMode){ // Use this switch know which transition to use to start the game
case 0://0000 (0) Basic Lockout trivia, players can answer anytime, players illiminated with wrong answers
Serial.println("Mode 0: no timer, no rounds");
sprintf(dispBuff,"Triv", gameMode );
displayTimer.displayPChar(dispBuff);
dispAllPlScores();
Game_waitPlayerBuzz();
break;
case 1: //0001 (1) Lockout trivia with Answer Timer, players can Buzz to answer anytime, but have a timer once buzzed where host must hit correct before timer runs out.players illiminated with wrong answers
Serial.println("Mode 1: Answer Timer Enabled, no rounds");
sprintf(dispBuff,"G-%1d", gameMode );
displayTimer.displayPChar(dispBuff);
next_dispState = DS::hold;
dispAllPlScores();
Game_waitPlayerBuzz();
break;
case 2: //0010 (2) Lockout Trivia with Question Timer, players cannot buzz untill host hits start / Stop, wrong answer illiminates player, timer resets each time (Optionally host can enable answer timer with S/S)
Serial.println("Mode 2: Question Timer enabled, no rounds");
next_dispState = DS::hold;
dispAllPlScores();
Game_askQuestion();
break;
case 3://0011 (3) Lockout trivia with Q timer, and Answer Timer, Player cannot buzz untill host hits S/S , player has a time to answer once buzz, auto wrong if timer runs out, and wrong answer illiminates player
Serial.println("Mode 3: Question Timer enabled, no rounds");
next_dispState = DS::hold;
dispAllPlScores();
Game_askQuestion();
break;
case 4://0100 (4) Time Challenge - Player timers count up till player presses button. Host can pause/Reset
break;
case 5://0101 (5) Time Challenge - With game time limit
break;
case 6://0110 (6) Time Challenge with Host Validation for points - players must complete a challenge within a time limit,
break;
case 7://0111 (7) Round trivia with low score illimination
break;
case 8://1000 (8) 3 Round basic lockout Trivia with low score illimination (Optionally host can enable answer timer with S/S)
break;
case 9://1001 (9) 3 Round lockout low score illimination with Q timer (Optionally host can enable answer timer with S/S)
Serial.println("Mode 9: round trivia");
next_dispState = DS::hold;
dispAllPlScores();
Game_askQuestion();
break;
case 10://1010 (10) 3 Round trivia low score illimination with Q timer and A Timer.
break;
case 11://1011 (11) Random Player Answer - Host asks question, and hits button to see who answers
break;
case 12://1100 (12) Basic Rotating Host - 3 player answer.. good for 4 players (host buzzer when pressed moves to next host red and green on box still work)
break;
case 13://1101 (13) Rotating host with Q timer (optional host controled Answer Timer)
break;
case 14://1110 (14) Rotating host with Q and Q timers
break;
case 15://1111 (15) Classic Chess timer , track player time and move to next player.
Serial.println("Mode 15: Chess Timer");
sprintf(dispBuff,"G-%1d", gameMode );
deactivateAllPlayers();
//deactivateCtrlBtns(); //Turn off all Ctrl buttons before next mode
playerWhoBuzzed = -1;
btn[btnReset].setActive(true);
btn[btnStartStop].setActive(true);
LED_activeButtons();
currPlayer = 3;
setNextPlayer();
next_ledFXState = LFXS::blinkActivePlayer;
next_state = GS::waitPlayerBuzz;
next_dispState = DS::turnsActive;
break;
default:
Serial.println("DEFAULD MODE: no timer, no rounds");
break;
}
} else {
//play SFX for No active players to start game
}
}
void updateGameModeVariables() {
//default state of config variables switch case will only change the ones that are different
gameMode = (!digitalRead(SwGMPin[3]) & 0x1) | ((!digitalRead(SwGMPin[2]) & 0x1)<<1) | ((!digitalRead(SwGMPin[1]) & 0x1) << 2) |((!digitalRead(SwGMPin[0]) & 0x1)<< 3); //Game mode initial Configuration
rotateHost = false;
randomAnswer = false;
waitQuestion = false;
questionTimer = false;
answerTimer = false;
capturePlayerTime = false ;
gameHasRounds = false ;
gameHasQuestionLimit = false ;
gameHasTimer = false;
numRounds = 0; //default @ rounds
numQuestions = 0; // DEfault # questions /round in this game
questionAllowedTime = 10000; //default question time is 10s
gameAllowedTime =0; // default overall Game timer
answerAllowedTime = 0;
switch (gameMode){ // Use this switch to Configure Game mode specific Variables
case 0:
gameType = trivia;
break;
case 1:
gameType = trivia;
answerTimer = true;
answerAllowedTime = 10000;
break;
case 2:
gameType = trivia;
waitQuestion = true;
questionTimer = true;
questionAllowedTime = 10000;
break;
case 3:
gameType = trivia;
waitQuestion = true;
questionTimer = true;
answerTimer = true;
questionAllowedTime = 10000;
answerAllowedTime = 10000;
break;
case 4:
gameType = challenge;
capturePlayerTime = true ;
break;
case 5:
gameType = challenge;
capturePlayerTime = true ;
break;
case 6:
gameType = challenge;
capturePlayerTime = true ;
gameHasTimer = true;
gameAllowedTime =300000; // 5 min = 300000
break;
case 7:
gameType = trivia;
gameHasRounds = true ;
gameHasQuestionLimit = true ;
numRounds = 3;
numQuestions = 10;
break;
case 8:
gameType = trivia;
gameHasRounds = true ;
gameHasQuestionLimit = true ;
numRounds = 3;
numQuestions = 10;
break;
case 9:
gameType = trivia;
waitQuestion = true;
questionTimer = true;
gameHasRounds = true ;
gameHasQuestionLimit = true ;
numRounds = 3;
numQuestions = 10;
questionAllowedTime = 10000; //default question time
answerAllowedTime = 10000;
break;
case 10:
gameType = trivia;
waitQuestion = true;
questionTimer = true;
answerTimer = true;
gameHasRounds = true ;
gameHasQuestionLimit = true ;
numRounds = 3;
numQuestions = 10;
questionAllowedTime = 10000; //default question time
answerAllowedTime = 10000;
break;
case 11:
gameType = trivia;
waitQuestion = true;
capturePlayerTime = true ;
break;
case 12:
gameType = trivia;
rotateHost = true;
gameHasRounds = true ;
gameHasQuestionLimit = true ;
numRounds = 4;
numQuestions = 10;
break;
case 13:
gameType = trivia;
rotateHost = true;
waitQuestion = true;
questionTimer = true;
questionAllowedTime = 10000; //default question time
gameHasRounds = true ;
gameHasQuestionLimit = true ;
numRounds = 4;
numQuestions = 10;
break;
case 14:
gameType = trivia;
waitQuestion = true;
questionTimer = true;
answerTimer = true;
questionAllowedTime = 10000; //default question time
answerAllowedTime = 10000;
gameHasRounds = true ;
gameHasQuestionLimit = true ;
numRounds = 4;
numQuestions = 10;
break;
case 15:
gameType = turns;
capturePlayerTime = true ;
break;
default:
break;
}
switch(gameType) {
case 0:
next_ledFXState = LFXS::confTrivia;
next_dispState = DS::confTrivia;
Serial.println("gametype selected is Trivia");
break;
case 1:
next_ledFXState = LFXS::confChallenge;
next_dispState = DS::confChallenge;
Serial.println("gametype selected is Challenge");
break;
case 2:
next_ledFXState = LFXS::confTurns;
next_dispState = DS::confTurns;
Serial.println("gametype selected is Turns");
break;
}
}
void animation_All_LED_blink() {
for (int i=0; i<14; i++) {
digitalWrite(LEDAnimation[i], !digitalRead(LEDAnimation[i]) ? HIGH : LOW);
delay(75);
digitalWrite(LEDAnimation[i], !digitalRead(LEDAnimation[i]) ? HIGH : LOW);
delay(75);
digitalWrite(LEDAnimation[i], !digitalRead(LEDAnimation[i]) ? HIGH : LOW);
delay(75);
}
}