/*
Reaction time game
Made by adrianponiat, wponiewierka, AGH Krakow
*/
/* Required imports */
#include <EEPROM.h>
#include "sketch.h"
#include "display.h"
#include "reaction_game.h"
/* X - macro defining interrupt functions for all game buttons */
#define FUNC_LIST \
INTERRUPT_FUNC(2) \
INTERRUPT_FUNC(3) \
INTERRUPT_FUNC(18) \
INTERRUPT_FUNC(19) \
INTERRUPT_FUNC(20) \
#define INTERRUPT_FUNC(pin_nr) \
static void handle_int_pin_##pin_nr(void); \
FUNC_LIST
#undef INTERRUPT_FUNC
/* Global variables */
uint64_t display_counter = 0U;
uint8_t menu_window = MAIN_MENU;
const LedButton_t game_buttons_pins[] = {
{2, 5, handle_int_pin_2},
{3, 6, handle_int_pin_3},
{18, 4, handle_int_pin_18},
{19, 16, handle_int_pin_19},
{20, 17, handle_int_pin_20}
};
/* Static function declarations */
static void menu_button_interrupt_handler(void);
static void save_best_score(void);
static int16_t get_best_score(void);
static void show_last_score(void);
static void show_countdown(void);
/* Arduino SETUP function */
void setup()
{
// Enable serial port in debug mode
if (DEBUG_MODE == true){
Serial.begin(9600);
}
// Shift register - display pins
pinMode(LATCH_PIN, OUTPUT);
pinMode(CLOCK_PIN, OUTPUT);
pinMode(DATA_PIN, OUTPUT);
// Buttons and led pins
for (int i = 0; i < sizeof(game_buttons_pins) / sizeof(game_buttons_pins[0]); i++) {
pinMode(game_buttons_pins[i].button_pin, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(game_buttons_pins[i].button_pin), game_buttons_pins[i].pin_interrupt_handler, RISING);
pinMode(game_buttons_pins[i].led_pin, OUTPUT);
}
// Menu pin interrupt
pinMode(MENU_INTERRUPT_PIN, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(MENU_INTERRUPT_PIN), menu_button_interrupt_handler, RISING);
// Set random seed
randomSeed(analogRead(A5));
}
/* Arduino LOOP function */
void loop()
{
// display info on screen - 30Hz
if (true == time_passed(REFRESH_RATE, &display_counter))
{
if (MAIN_MENU == menu_window)
{
display_text(false);
}
else if (BEST_SCORE == menu_window)
{
display_number(get_best_score());
}
else if (IN_GAME == menu_window)
{
display_number(current_score);
}
else if (SHOW_LAST_SCORE == menu_window)
{
show_last_score();
}
else if (COUNTDOWN == menu_window)
{
show_countdown();
}
else
{
/* Do nothing */
}
}
}
/* Static functions implementations */
/* ----------------------------------------------------------------------- *\
FUNCTION: menu_button_interrupt_handler
This is interrupt handler for menu button (pin 21)
- if current menu_window is IN_GAME: button should do nothing
- if current menu_window is MAIN_MENU: button enters BEST_SCORE state
- if current menu_window is BEST_SCORE: button enters MAIN_MENU state
\* ----------------------------------------------------------------------- */
void menu_button_interrupt_handler(void)
{
if (MAIN_MENU == menu_window)
{
menu_window = BEST_SCORE;
}
else if (BEST_SCORE == menu_window)
{
menu_window = MAIN_MENU;
}
else /* IN_GAME */
{
/* Do nothing */
}
}
/* ----------------------------------------------------------------------- *\
FUNCTION: save_best_score
This function checks if current score from past game is higher than
overall high score and saves it if true.
\* ----------------------------------------------------------------------- */
void save_best_score(void)
{
byte lower = current_score >> 0x08;
byte higher = current_score & 0xFF;
EEPROM.write(EEPROM_addr_start, lower);
EEPROM.write(EEPROM_addr_start + 1, higher);
}
/* ----------------------------------------------------------------------- *\
FUNCTION: get_best_score
This function reads best score from EEPROM and returns it.
\* ----------------------------------------------------------------------- */
int16_t get_best_score(void)
{
byte lower = EEPROM.read(EEPROM_addr_start);
byte higher = EEPROM.read(EEPROM_addr_start + 1);
return (int16_t) (lower << 0x08) | higher;
}
/* ----------------------------------------------------------------------- *\
FUNCTION: show_last_score
Shows last score with blinking, lasts for 3 seconds.
\* ----------------------------------------------------------------------- */
void show_last_score(void)
{
static uint8_t current_score_counter = 0U;
if (current_score_counter % 15 <= 8)
{
display_number(current_score);
}
else
{
display_text(true);
}
current_score_counter++;
if (CURRENT_SCORE_TIME == current_score_counter)
{
menu_window = MAIN_MENU;
current_score_counter = 0U;
if ((current_score < get_best_score()) || (get_best_score() < 0))
{
save_best_score();
}
}
}
/* ----------------------------------------------------------------------- *\
FUNCTION: show_countdown
Shows countdown a the start of the game.
\* ----------------------------------------------------------------------- */
void show_countdown(void)
{
static uint8_t countdown_number = 3U;
static uint8_t countdown_counter = 1U;
if (countdown_counter % 30 == 0)
{
countdown_number--;
}
countdown_counter++;
display_number(countdown_number);
if (COUNTDOWN_TIME == countdown_counter)
{
menu_window = IN_GAME;
countdown_counter = 1U;
countdown_number = 3U;
digitalWrite(current_button.led_pin, HIGH);
game_timestamp = millis();
}
}
// Define interrupt functions for game buttons using xmacro
#define INTERRUPT_FUNC(pin_nr) \
void handle_int_pin_##pin_nr(void) \
{ \
game_button_interrupt_handler(pin_nr); \
} \
FUNC_LIST
#undef INTERRUPT_FUNC