/* ------------------------------------------------------------------------------------------------------------------------------------------ */
/* SpeedMath Game */
/* ------------------------------------------------------------------------------------------------------------------------------------------ */
/* SpeedMath Game is a fun way to test your abilities at how fast you can do math in your head. */
/* The game contains multiple ways of displaying arithmetic problems, including visually, and auditorily. */
/* ------------------------------------------------------------------------------------------------------------------------------------------ */
/* Instructions: */
/* 1. Start the game by waving at the IR sensor. The LED would turn green. */
/* 2. The game has 3 levels (Easy, Medium, and Hard). */
/* 3. Click on 1 on the keypad to play the easy level, 2 for the medium level, and 3 for the hard level. */
/* 4. There are 10 questions in each game. */
/* 5. Make sure to answer quickly, the game is set by a timer for every question. The timer is displayed on the LCD. */
/* 6. If you click the wrong key, you can click on "D" on they keypad to erase the last typed number. */
/* 7. To check your answer and move on to the next question before the timer ends, click on "#" on the keypad. */
/* 8. If your answer is correct, the green RGB LED lights up. If it is wrong, the LED turns red. */
/* 9. You can adjust the brightness of the LED by turning the knob of the potentiometer. */
/* 10. If you choose the easy level, you have 20 seconds to answer a "+", "-", "*", or "" math question displayed on the LCD. */
/* 11. If you choose the medium level, only the operation is displayed on the LCD. You have to observe how many times the blue LED blinks. */
/* 12. The hard level is similar, except that you have to listen carefully to the speaker and count how many times it "buzzes"! */
/* 13. For medium and hard levels, a duration of a second separates the first number from the second. */
/* 14. In these two levels, you only have 15 and 10 seconds to perform math operations in your head on the two numbers and type your answers. */
/* 15. At the end of each game, your game score is displayed on the LCD, and then your total game scores. */
/* 16. If you decide to stop the game at any time, you can click on "*" on the keypad. */
/* 17. Don't worry, you can play the game again at any time by waving at the IR sensor. */
/* 18. Have fun! */
/* ------------------------------------------------------------------------------------------------------------------------------------------ */
/* Made by Mariam Alzaabi */
/* 23/11/2020 */
/* ------------------------------------------------------------------------------------------------------------------------------------------ */
#include <Keypad.h> //Keypad library
#include <LiquidCrystal_I2C.h> //LCD library
#include <EEPROM.h> //EEPROM library
// Defining Arduino pins
const byte bluePin = 10; //PB2 attach pin D12 (PB2) Arduino to pin Blue of RGB LED
const byte greenPin = 11; //PB3 attach pin D12 (PB3) Arduino to pin Green of RGB LED
const byte redPin = 12; //PB4 attach pin D12 (PB4) Arduino to pin Red of RGB LED
const byte speaker = 13; //PB5 attach pin D13 (PB5) Arduino to speaker pin
const byte potentiometerPin = A0; //attach pin A0 Arduino to potentiometer pin
// Creating pointers to port D and B registers
byte *ptr_to_PORTD;
byte *ptr_to_PIND;
byte *ptr_to_DDRB;
byte *ptr_to_PORTB;
// New shapes created for the LCD screen
byte smileyFace[] = { //Smiley face used for answers checking
B00000,
B00000,
B01010,
B00000,
B10001,
B01110,
B00000,
B00000
};
byte sadFace[] = { //Sad face used for answers checking
B00000,
B00000,
B01010,
B00000,
B01110,
B10001,
B00000,
B00000
};
byte blankChar[] = { //Blank character used for M/H game levels
B11111,
B11111,
B11111,
B11111,
B11111,
B11111,
B11111,
B11111
};
// Creating object from LCD library to control LCD
LiquidCrystal_I2C lcd(0x27, 16, 2);
// Configuring Keypad using keypad library
const byte ROWS = 4; //four rows
const byte COLS = 4; //four columns
// Defining the keymap
char keys[ROWS][COLS] = {
{'1', '2', '3', 'A'},
{'4', '5', '6', 'B'},
{'7', '8', '9', 'C'},
{'*', '0', '#', 'D'}
};
byte rowPins[ROWS] = {2, 3, 4, 5}; //connect to the row pinouts of the keypad
byte colPins[COLS] = {6, 7, 8, 9}; //connect to the column pinouts of the keypad
// Creating the keypad
Keypad keypad = Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS );
// Code for game implementation
namespace Game {
class SpeedMath
{
public:
String input = ""; //used to get a string of number inputs from the user
int num1, num2, correctValue, op; //initialization of int type
String operation = ""; //type of operation used in the game ("+", "-", "*", or "/")
int numChar = 0; //used for determining the number of characters used in typing a question
char difficulty; //level of difficuty chosen by the user (1-E, 2-M, or 3-H)
byte score = 0; //score is initially 0
byte numQuestions = 10; //number of questioned asked, max is 255
bool playMode = false; //if user is in play mode
bool isExited = false; //if the game has already been exited
bool levelChosen = false; //if the level is chosen by the user
bool setUp = false; //if the game has already been exited
bool gameStopped = false; //if the game has been stopped
unsigned long delayStart = 0; //time the delay started
bool delayRunning = false; //true if still waiting for delay to finish
int totScore = 0; //total score of the player
int addTotScore = 0; //address of the total score
// Function used to generate random questions for the easy level game
void generateEasy(void) {
lcd.clear(); //clear LCD screen and position the cursor in the upper-left corner
generateNumbers(1, 100, 1, 100, 1, 20, 1, 100); //the range of randomly generated numbers
lcd.print(String(num1) + operation + String(num2) + "="); //print the question to the LCD
numChar = countDigit(num1) + countDigit(num2) + 2; //count the number of characters displayed on the LCD
delayStart = millis(); //start delay
delayRunning = true; //delay is not finished yet
}
// Function used to generate random questions for the medium level game
void generateMed(void) {
lcd.clear(); //clear LCD screen and position the cursor in the upper-left corner
generateNumbers(1, 10, 1, 10, 1, 10, 1, 10); //the range is [1, 10]
lcd.print("Look carefully!"); //print to the LCD screen for 2 seconds
delay(2000);
lcd.clear(); //clear LCD screen and position the cursor in the upper-left corner
for (int i = 1; i <= num1; i++) { //used for blinking num1
analogWrite(bluePin, 255); //writes an analog value (PWM wave) to blue pin for 250 ms
delay(250);
analogWrite(bluePin, 0); //turn it off for 250 ms
delay(250);
}
delay(1000); //wait for a second before blinking num2
for (int i = 1; i <= num2; i++) { //used for blinking num2
analogWrite(bluePin, 255); //writes an analog value (PWM wave) to blue pin for 250 ms
delay(250);
analogWrite(bluePin, 0); //turn it off for 250 ms
delay(250);
}
delay(1000); //wait for a second before displaying the operation
lcd.createChar(0, blankChar); //create a custom character for displaying the question
lcd.home(); //positions the cursor in the upper-left of the LCD
lcd.write(0); //write the custom character to the LCD
lcd.print(operation); //print the operation to the LCD
lcd.write(0); //write the custom character to the LCD again
lcd.print("="); //print the operation to the LCD
numChar = 4; //number of characters on the LCD
delayStart = millis(); //start delay
delayRunning = true; //delay not finished yet
}
// Function used to generate random questions for the hard level game
void generateHard(void) {
lcd.clear(); //clear LCD screen and position the cursor in the upper-left corner
generateNumbers(1, 10, 1, 10, 1, 10, 1, 10); //the range is [1, 10]
lcd.print("Listen carefully!"); //print to the LCD for 2 seconds
delay(2000);
lcd.clear(); //clear LCD screen and position the cursor in the upper-left corner
for (int i = 1; i <= num1; i++) { //used for "buzzing" num1
tone(speaker, 1000); //start speaker sound for 250ms
delay(250);
noTone(speaker); //Stop buzzing
}
delay(1000); //wait for a second before "buzzing" num2
for (int i = 1; i <= num2; i++) { //used for "buzzing" num1
tone(speaker, 1000); //start speaker sound for 250ms
delay(250);
noTone(speaker); //Stop buzzing
}
delay(1000); //wait for a second before displaying the operation
lcd.createChar(0, blankChar); //create a custom character for displaying the question
lcd.home(); //positions the cursor in the upper-left of the LCD
lcd.write(0); //write the custom character to the LCD
lcd.print(operation); //print the operation to the LCD
lcd.write(0); //write the custom character to the LCD again
lcd.print("="); //print the operation to the LCD
numChar = 4; //number of characters on the LCD
delayStart = millis(); //start delay
delayRunning = true; //delay not finished yet
}
// Function used to reset values after each question
void clean() {
num1 = num2 = correctValue = 0;
input = "";
numChar = 0;
}
// Function used to count the number of digits in a number
int countDigit(int n) {
int count = 0;
while (n != 0) {
n = n / 10;
++count;
}
return count;
}
// Function used to generate the numbers for the game in the range [1, 255]
void generateNumbers(byte minAdd, byte maxAdd, byte minSub, byte maxSub, byte minMul, byte maxMul, byte minDiv, byte maxDiv) {
op = random(1, 5); //generate a random number [1,4] for the type of operation
// Switch statement used for performing mathematical calculation
switch (op) {
case (1): //if the random number is "1", perform addition
operation = "+"; //type of operation is addition
num1 = random(minAdd, maxAdd); //generate a random number [1-99] for the first operand
num2 = random(minAdd, maxAdd); //generate a random number [1-99] for the second operand
correctValue = num1 + num2; //perform addition
break;
case (2): //if the random number is "2", perform subtraction
operation = "-"; //type of operation is subtraction
num1 = random(minSub, maxSub); //generate a random number [1-99] for the first operand
num2 = random(minSub, maxSub); //generate a random number [1-99] for the second operand
while (num1 < num2) { //calculation results cannot be negative
num2 = random(minSub, maxSub);
}
correctValue = num1 - num2; //perform subtraction
break;
case (3): //if the random number is "3", perform multiplication
operation = "x"; //type of operation is multiplication
num1 = random(minMul, maxMul); //generate a random number [1-19] for the first operand
num2 = random(minMul, maxMul); //generate a random number [1-19] for the second operand
correctValue = num1 * num2; //perform multiplication
break;
case (4): //if the random number is "4", perform division
operation = "/"; //type of operation is division
num1 = random(minDiv, maxDiv); //generate a random number [1-99] for the first operand
num2 = random(minDiv, maxDiv); //generate a random number [1-99] for the second operand
while ((num1 < num2) || ((num1 % num2) != 0)) { //calculation results cannot be decimal
num2 = random(minDiv, maxDiv);
}
correctValue = num1 / num2; //perform division
break;
}
}
// Function used for initializing the game
void initGame() {
lcd.clear(); //clear LCD screen and position the cursor in the upper-left corner
int potValue = analogRead(potentiometerPin) / 4; //measure the potentiometer value (max 255)
setColor(0, potValue, 0); //light up the RGB LED with green color
lcd.print("Hello!"); //print to the LCD screen
tone(speaker, 3000, 1000); //start speaker sound for 1 second
delay(1000);
setColor(0, 0, 0); //turn off green color
lcd.clear(); //clear LCD screen and position the cursor in the upper-left corner
lcd.print("Difficulty "); //print to the LCD screen
lcd.setCursor(2, 1); //sLevelet cursor to the third position from the bottom
lcd.print("1-E 2-M 3-H"); //print to the LCD screen
}
// Function used for setting up the game
void setUpGame() {
lcd.backlight(); //turn on blacklight
setUp = true; //the game has been set up
isExited = false; //the game has not been exited
initGame(); //initialize the game
char key;
do { //keep looping until a level is chosen and the user has not chosen to exit the game
key = keypad.getKey(); //value of a key being pressed
if ((key == '1') || (key == '2') || (key == '3')) { //if a level is chosen
delay(1000); //wait for a second before starting
lcd.clear(); //clear LCD screen and position the cursor in the upper-left corner
levelChosen = true; //level has been chosen
difficulty = key; //sets difficulty level
playMode = true; //user is now in play mode
playGame(); //play the game
}
else if (key == '*') { //if user has chosen to stop the game
stopGame(); //stop the game
}
} while (((key != '1') && (key != '2') && (key != '3') && (levelChosen != true)) && (!isExited));
}
// Function for playing the game based on difficulty level
void playGame() {
numQuestions -= 1; //decrement number of questions
// Switch statement used for generating questions based on difficulty level
switch (difficulty) {
case ('1'): //if difficulty is easy
generateEasy(); //generate random easy questions
break;
case ('2'): //if difficulty is medium
generateMed(); //generate random medium questions
break;
case ('3'): //if difficulty is hard
generateHard(); //generate random hard questions
break;
}
}
// Function used to check if the inputted values match the correct values
void checkAnswer() {
lcd.clear(); //clear LCD screen and position the cursor in the upper-left corner
int potValue = analogRead(potentiometerPin) / 4; //measure the potentiometer value (max 255)
if (input.toInt() == correctValue) { //if they match
score += 1; //increment the score value
tone(speaker, 4500, 800); //start speaker sound for 1 second
lcd.createChar(0, smileyFace); //create a custom character (smiley face)
lcd.home(); //positions the cursor in the upper-left of the LCD
lcd.print("Correct!"); //print to the LCD screen
lcd.write(0); //write the custom character to the LCD
setColor(0, potValue, 0); //light up the RGB LED with green color
} else { //if they do not match
tone(speaker, 500, 800); //start speaker sound for 1 second
lcd.createChar(0, sadFace); //create a custom character (sad face)
lcd.home(); //positions the cursor in the upper-left of the LCD
lcd.print("Incorrect!"); //print to the LCD screen
lcd.write(0); //write the custom character to the LCD
setColor(potValue, 0, 0); //light up the RGB LED with red color
}
delay(1000); //wait for 1 second
setColor(0, 0, 0); //turn off RGB LED color
lcd.clear(); //clear LCD screen and position the cursor in the upper-left corner
clean(); //reset values
}
//Function to check if there are questions left
void continueGame() {
if (numQuestions > 0) { //if there are questions left
checkAnswer(); //check the answer
playGame(); //keep playing the game
} else if (numQuestions == 0 && playMode == true) { //if no questions left
checkAnswer(); //check the answer
playMode = false; //user has finished the game
lcd.setCursor(5, 0); //set cursor to the sixth position from the top
lcd.print("Score:" + String(score) + "/10"); //print to the LCD screen
delay(1500); //wait for 1.5 seconds
lcd.clear(); //clear LCD screen and position the cursor in the upper-left corner
lcd.print("Total Score"); //print to the LCD screen
lcd.setCursor(0, 1); //set cursor to the first position from the bottom
totScore = EEPROM.get(addTotScore, totScore) + score; //update total score
EEPROM.put(addTotScore, totScore); //write total score to the EEPROM
lcd.print(String(EEPROM.get(addTotScore, totScore))); //print total score on the LCD from EEPROM
}
}
// Function for stopping the game
void stopGame() {
if (!isExited) { //stop the game if it has not been already stopped
clean(); //reset values
lcd.clear(); //clear LCD screen and position the cursor in the upper-left corner
setColor(255, 0, 0); //light up the RGB LED with red color
lcd.setCursor(6, 0); //set cursor to the seventh position from the top
lcd.print("Good"); //print to the LCD screen
lcd.setCursor(6, 1); //set cursor to the seventh position from the bottom
lcd.print("Bye!"); //print to the LCD screen
delay(2000); //clear everything after 2 seconds
setColor(0, 0, 0); //turn off red color
lcd.clear(); //clear LCD screen and position the cursor in the upper-left corner
clearGame(); //reset values
}
}
// Function for resetting values after every game
void clearGame() {
lcd.noBacklight(); //turn off backlight
isExited = true; //the game has been stopped
setUp = false; //the game can be set up when it is on again
numQuestions = 10; //the number of questions is back to 10
difficulty = NULL; //difficulty can be chosen again later
levelChosen = false; //level can be chosen again later
score = 0; //reset the score
}
// Function used when a number is pressed on the Keypad
void numPress(char num) {
if (playMode) { //if the user is in play mode
lcd.setCursor(numChar, 0); //set the cursor to the position after the printed characters
input += num; //convert the numbers to a string of numbers
numChar += 1; //increment the number of characters displayed on the LCD
lcd.write(num); //write the number to the LCD
tone(speaker, 1000, 300); //start speaker sound for 1 second
}
}
// Function used to delete a character typed during the game
void deleteChar() {
if (input.length() != 0) { //if there are characters typed by the user
numChar -= 1; //decrement the number of characters displayed on the LCD
input.remove(input.length() - 1); //remove the last character from the input string
lcd.setCursor(numChar, 0); //set the cursor to the last position
lcd.print(" "); //hide the character from the LCD
lcd.setCursor(numChar, 0); //set the cursor to the last position
}
}
// Use PWM to control the brightness of the RGB LED [0-255]
void setColor(int redValue, int greenValue, int blueValue) {
analogWrite(redPin, redValue); //writes an analog value (PWM wave) to red pin
analogWrite(greenPin, greenValue); //writes an analog value (PWM wave) to green pin
analogWrite(bluePin, blueValue); //writes an analog value (PWM wave) to blue pin
}
//Function used to set up the timer based on difficulty level
void setUpTimer() {
// Switch statement used for setting the timer based on difficulty level
switch (difficulty) {
case ('1'): //if difficulty is easy
setTimer(20); //sets a timer of 20 seconds
break;
case ('2'): //if difficulty is medium
setTimer(15); //sets a timer of 10 seconds
break;
case ('3'): //if difficulty is hard
setTimer(10); //sets a timer of 10 seconds
break;
}
}
//Function used to create the timer
void setTimer(unsigned long inSeconds) {
if (playMode) { //if the game is in play mode
if (inSeconds < 86400) { //maximum is 23h:59m:59s = 86399 seconds
bool secondPrinted = false; //can only be printed once
// Check if delay has timed out after 5 seconds
if (delayRunning && ((millis() - delayStart) >= (inSeconds * 1000))) {
delayRunning = false; //this code can only run once
continueGame(); //continue playing the game
}
else { //if the timer has not timed out
for (int i = 1; i <= inSeconds; i++) { //check if another second has passed
if ((delayRunning && ((millis() - delayStart) >= ((inSeconds - i) * 1000))) && !secondPrinted) {
secondPrinted = true; //only check for the first correct condition
displayTimer(i); //display the timer on the LCD
}
}
}
}
}
}
//Function used to display the timer on the LCD
void displayTimer(int totalSecond) {
lcd.setCursor(0, 1); //set the cursor to the first position from the bottom
char timeFormat[8]; //for formatting the time
//Return a formatted string in the form HH:MM:SS
sprintf(timeFormat, "%02d:%02d:%02d", totalSecond / 3600, (totalSecond % 3600) / 60, totalSecond % 60);
lcd.print(timeFormat); //print it to the LCD
}
};
}
// Initializing an object of class SpeedMath
Game::SpeedMath my_game;
// Setup code here, to run once
void setup() {
lcd.init(); //initialize the LCD
ptr_to_PORTD = 0x2B; //PORTD (port D) register address
ptr_to_PIND = 0x29; //PIND (port D) register address
*ptr_to_PORTD = B00000010; //sets IR pin in PD1 as input
ptr_to_DDRB = 0x24; //DDRB (port B) register address
ptr_to_PORTB = 0x25; //PORTB (port B) register address
*ptr_to_DDRB = B00111100; //sets speaker pin in PB5 and RGB pins in PB2/3/4 as output
randomSeed(analogRead(0)); //seeds the random number generator (AnalogRead on pin 0)
}
// Main code, to run repeatedly
void loop() {
// Set up a timer based on difficulty level
my_game.setUpTimer();
byte statusIRSensor = *ptr_to_PIND; //digitalRead(IRSensor);
if (!my_game.setUp && ((statusIRSensor & B00000010) == LOW)) //if the game has not been set up and an object has been detected
{
my_game.setUpGame(); //set up the game
}
char key = keypad.getKey(); //value of a key being pressed
if (key) { //if a key has been pressed
// Switch statement used when a key is pressed on the Keypad
switch (key) {
case 'A': //if any of these are pressed nothing happens
case 'B':
case 'C':
break;
case 'D': //if 'D' is pressed
my_game.deleteChar(); //delete the last character typed
break;
case '#': //if '#' is pressed
my_game.continueGame(); //check the answer then continue the game if there are questions left
break;
case '*': //if '*' is pressed
my_game.stopGame(); //stop the game
break;
default: //if a number is pressed
my_game.numPress(key); //write the number to the LCD and retrieve a string of the numbers pressed
break;
}
}
}
Comments