//importing libraries:
#include <LiquidCrystal.h>
#include <Keypad.h>
#include <Wire.h>
// I2C address for DS1307:
const int rtc_address = 0x68;
//Configuring the Button:
const int BUTTON = 2;
volatile boolean ON = false;
//Configuring the Buzzer:
const int BUZZER = 11;
//Configuring the LCD:
const int RS = 4;
const int E = 5;
const int D4 = 6;
const int D5 = 7;
const int D6 = 8;
const int D7 = 9;
const int A = 10;
LiquidCrystal lcd(RS, E, D4, D5, D6, D7);
//Configuring the joystick:
const int SEL = A0;
const int HORZ = A1;
const int VERT = A2;
int horzValue;
int vertValue;
int SelValue;
//Configuring the cursor position:
int cursorPosition = 0;
//Configuring the keypad:
const int ROWS = 4;
const int COLS = 4;
char keys[ROWS][COLS] = {
{ '1', '2', '3', '+' },
{ '4', '5', '6', '-' },
{ '7', '8', '9', '*' },
{ 'D', '0', '=', '/' }
};
const byte rowPins[ROWS] = { 22, 24, 26, 28 }; // Pins connected to R1, R2, R3, R4
const byte colPins[COLS] = { 30, 32, 34, 36 }; // Pins connected to C1, C2, C3, C4
Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS);
//Configuring the calculator:
double num1 = 0;
double num2 = 0;
double result = 0;
String input1 = "";
String input2 = "";
String operation;
boolean isSecondNumber = false;
//A special character for the calculator:
byte arrowSymbol[8] = {
B00100,
B01110,
B10101,
B00100,
B00100,
B00100,
B00100,
B00100
};
//Configuring the ability to switch modes and only getting some messages ones:
int mode;
boolean START = true;
boolean Begin = true;
// Game settings
int currentScore = 0;
const int MAX_LEVEL = 5; // Maximum levels in the game
const int DISPLAY_TIME = 1000; // Time in milliseconds for showing random number sequences
int sequence[MAX_LEVEL]; // Array for storing the number sequences
int userInput[MAX_LEVEL]; // Array for storing user input
int currentLevel = 1; // Current game level
//this function only runs once in the beginning
void setup() {
lcd.begin(16, 2);
Wire.begin();
//All pins on arduino are default at input therfore we need to declare the pins we need for output:
pinMode(RS, OUTPUT);
pinMode(E, OUTPUT);
pinMode(D4, OUTPUT);
pinMode(D5, OUTPUT);
pinMode(D6, OUTPUT);
pinMode(D7, OUTPUT);
pinMode(A, OUTPUT);
pinMode(BUZZER, OUTPUT);
//Declaring an interrupt, for Button, that happens when it goes from low to high:
attachInterrupt(digitalPinToInterrupt(BUTTON), ISR_Button_Pressed, RISING);
//Declaing the special character as 0:
lcd.createChar(0, arrowSymbol);
}
//Debounce timer:
long last_interrupt_time = 0;
//200ms debounce delay (0,2 sec):
const long debounce_delay = 200;
//This is the ISR function for the interrupt:
void ISR_Button_Pressed() {
long interrupt_time = millis();
if (interrupt_time - last_interrupt_time > debounce_delay) {
//Switching the state of the boolean to its oppesite everytime the button is triggered:
ON = !ON;
//Update debounce time:
last_interrupt_time = interrupt_time;
}
}
//This function will always run as long as the arduino is turned on:
void loop() {
//If our button is triggered and the boolean "ON" is true then it will give power to the system:
if (ON) {
lcd.display();
digitalWrite(BUZZER, HIGH);
digitalWrite(A, HIGH);
//This displays the menu where its possible to chance modes, also it triggeres the function time:
if (START) {
time();
lcd.setCursor(0, 1);
lcd.print("Mode: 1:Ga, 2:Ca");
}
//This captures every key of keypad is pressed, and makes a sound every time triggered:
char key = keypad.getKey();
if (key && START) {
tone(BUZZER, 1000, 100);
//If pressed "1" then sets the mode to 1 and cleares the menu:
if (key == '1') {
mode = 1;
START = false;
lcd.clear();
//If pressed "2" then sets the mode to 2 and cleares the menu:
} else if (key == '2') {
mode = 2;
START = false;
lcd.clear();
}
}
//If pressed "1" then the game mode will start:
if (mode == 1 && !START) {
if (Begin) {
randomSeed(analogRead(0)); // Initialisere random numre
lcd.print("Memory Game");
delay(2000);
lcd.clear();
Begin = false;
}
Game();
//If pressed "2" then the calculator mode will start:
} else if (mode == 2 && !START) {
if (Begin) {
lcd.print("Calculator");
delay(2000);
lcd.clear();
Begin = false;
}
Calculator();
}
//If our button is triggered and the boolean "ON" is false then it will turn off power to the system, and resets the boolean for menu etc.:
} else {
digitalWrite(BUZZER, LOW);
digitalWrite(A, LOW);
Begin = true;
START = true;
lcd.noDisplay();
noTone(BUZZER);
lcd.clear();
}
}
void time() {
//Request communication at a specific register from the DS1307 RTC:
Wire.beginTransmission(rtc_address);
//Declaing that the register using will be at 0:
Wire.write(0);
//ends the communication at that register:
Wire.endTransmission();
//Request 7 bytes of data from the DS1307 (seconds, minutes, hours, day, date, month, year)
int returned_bytes = Wire.requestFrom(rtc_address, 7);
//If the data isnt matched and error will be prinet, and will not make it possible to move forward as it will loop forever:
if (returned_bytes < 7) {
lcd.print("I2C Error");
while (1)
;
}
//Read the time data for the RTC, and converts the data from bcd (Binary-Coded Decimal) to decimals in a function called bcdToDec:
int seconds = bcdToDec(Wire.read());
int minutes = bcdToDec(Wire.read());
int hours = bcdToDec(Wire.read());
// Print the time to on the LCD:
lcd.setCursor(0, 0);
lcd.print("Time: {");
lcd.print(hours);
lcd.print(":");
// Add an extra 0 to keep a the format of to decimals for each data:
if (minutes < 10) lcd.print("0");
lcd.print(minutes);
lcd.print(":");
// Add an extra 0 to keep a the format of to decimals for each data:
if (seconds < 10) lcd.print("0");
lcd.print(seconds);
lcd.print("}");
}
//This function converts the data from RTC, from bcd(Binary-Coded Decimal) to decimals:
int bcdToDec(int val) {
return ((val / 16 * 10) + (val % 16));
}
//This function is for reading data from the joystick and handling it:
void Joystick() {
// Reads the analog values of the joystick on the horizontal level:
horzValue = analogRead(HORZ);
// Reads the analog values of the joystick on the vertical level:
vertValue = analogRead(VERT);
// Reads the analog values of the joystick for the button:
SelValue = analogRead(SEL);
// This moved the cursor for calculator when turning to the right:
if (horzValue > 750) {
cursorPosition++;
lcd.setCursor(cursorPosition, 0);
delay(150);
//This limit the cursor so it cant to outside the screen:
if (cursorPosition > 15) {
cursorPosition = 15;
}
//This moved the cursor for calculator when turning to the left:
} else if (horzValue < 250) {
cursorPosition--;
lcd.setCursor(cursorPosition, 0);
delay(150);
//This limit the cursor so it cant to outside the screen:
if (cursorPosition < 0) {
cursorPosition = 0;
}
}
//This keeps the cursor for calculator at a fixed position so it cant make the cursor go up/down:
if (vertValue > 750) {
lcd.setCursor(cursorPosition, 0);
} else if (vertValue < 250) {
lcd.setCursor(cursorPosition, 0);
}
//This resets the calculator when the joystick button is pressed:
if (SelValue == 0) {
resetCalculator();
}
}
//This is the function for the calculator that makes it possible to do some calculations:
void Calculator() {
//This calls the Joystick so that the cursor can be updated when using the joystick
Joystick();
//Removing the last letter:
lcd.setCursor(cursorPosition - 1, 1);
lcd.print(" ");
//Removing the last letter
lcd.setCursor(cursorPosition + 1, 1);
lcd.print(" ");
//This prints the cursor as the already defined special character on the second line, to make it easy to were the user is writting:
lcd.setCursor(cursorPosition, 1);
lcd.write(byte(0));
//Goes back to the first line:
lcd.setCursor(cursorPosition, 0);
//This captures every key of keypad is pressed, and makes a sound every time triggered:
char key = keypad.getKey();
if (key) {
tone(BUZZER, 1000, 100);
if (key >= '0' && key <= '9') {
//This devide the input in two part the value before the operation and the value after:
if (isSecondNumber) {
input2 += key;
} else {
input1 += key;
}
//Print the key and updates the cursor position:
cursorPosition++;
lcd.print(key);
//This is how the value is split into to seperate values before and after operation:
} else if (key == '+' || key == '-' || key == '*' || key == '/') {
//Checks for format:
if (input1 != "") {
//Prints the value, updates the cursor position and changes the boolean that determine which value should we added a number to
//the one before or after operation:
operation = key;
isSecondNumber = true;
lcd.print(key);
cursorPosition++;
//If wrong format triggeres the function error, and reset:
} else {
error();
}
} else if (key == '=') {
//This checks for correct format:
if (input1 != "" && input2 != "" && operation != "") {
//If the format is correct it will convert the numbers which in reality
//begins as a string, but then now converts to real numbers to calculate:
num1 = input1.toDouble();
num2 = input2.toDouble();
//This is how the calculation is made, it takes all the input and
//then through softare calculates the result and saves it as result to be printed later:
if (operation == "+") {
result = num1 + num2;
} else if (operation == "-") {
result = num1 - num2;
} else if (operation == "*") {
result = num1 * num2;
} else if (operation == "/") {
result = num1 / num2;
}
//If the result is invalid it will call the error function:
if (operation == "/" && num2 == 0 || (input1.length() + input2.length() + operation.length()) > 16) {
error();
//Else it will print the calulation and result in a specific format:
} else {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(input1);
lcd.print(operation);
lcd.print(input2);
lcd.setCursor(0, 1);
lcd.print("=");
lcd.print(result);
playSuccessTone();
delay(1000);
resetCalculator();
}
} else {
error();
}
//This is a backspace:
} else if (key == 'D') {
lcd.print(" ");
//This removes the data too on the given cursor position:
input1.remove(cursorPosition, 1);
operation.remove(abs(cursorPosition - input1.length() - operation.length()), 1);
input2.remove(abs(cursorPosition - input1.length() - operation.length()), 1);
}
}
}
//This function plays some sound effects when getting a valid result:
void playSuccessTone() {
tone(BUZZER, 523, 200);
delay(200);
tone(BUZZER, 659, 200);
delay(200);
tone(BUZZER, 784, 300);
delay(300);
noTone(BUZZER);
delay(3000);
}
//This function Plays some sound effects, and prints "ERROR" when triggered when trying to do an invalid calculation:
void error() {
lcd.clear();
lcd.setCursor(5, 0);
lcd.print("ERROR!");
lcd.setCursor(5, 1);
lcd.print("ERROR!");
tone(BUZZER, 1000, 200);
delay(100);
tone(BUZZER, 600, 200);
delay(100);
tone(BUZZER, 400, 300);
delay(300);
noTone(BUZZER);
resetCalculator();
}
//This function resets all variables and screen too, to make it possible to do a new calculation:
void resetCalculator() {
input1 = "";
input2 = "";
isSecondNumber = false;
lcd.clear();
cursorPosition = 0;
}
void Game() {
if (ON) {
generateSequence(); // Generate the random number sequence
displaySequence(); // Show the number sequence on the LCD
if (getUserInput()) { // Get user input and check if it's correct
currentScore++; // Increase the score if the user input is correct
currentLevel++; // Go to the next level
if (currentLevel > MAX_LEVEL) { // Check if the player won
lcd.clear();
lcd.print("You Win!");
lcd.setCursor(0, 1);
lcd.print("Score: ");
lcd.print(currentScore); // Show the score when the player wins
tone(BUZZER, 1000, 500); // Play the "You Win" sound
delay(3000);
resetGame(); // Reset the game for the next session
}
} else {
lcd.clear();
lcd.print("Game Over");
lcd.setCursor(0, 1);
lcd.print("Score: ");
lcd.print(currentScore); // Show the score when the player loses
tone(BUZZER, 200, 1000); // Play the "Game Over" sound
delay(3000);
resetGame(); // Reset the game for the next session
}
}
}
// The function generates random numbers
void generateSequence() {
for (int i = 0; i < currentLevel; i++) {
sequence[i] = random(0, 10); // Generate a random number from 0-9
}
}
// The function displays the sequence
void displaySequence() {
lcd.clear();
lcd.print("Remember:");
delay(1000);
for (int i = 0; i < currentLevel; i++) {
lcd.clear();
lcd.print(sequence[i]);
tone(BUZZER, 500, 200); // Make sound for each key press
delay(DISPLAY_TIME);
lcd.clear();
delay(500); // Delay between displayed numbers
}
}
// The function checks/validates input from the user
boolean getUserInput() {
lcd.clear();
lcd.print("Your Turn:");
int index = 0;
while (index < currentLevel && ON) {
char key = keypad.getKey(); // Read keypad input
if (key) { // Check if a key was pressed
if (key >= '0' && key <= '9') { // Validate number input
tone(BUZZER, 1000, 100);
userInput[index] = key - '0'; // Convert char to int
lcd.setCursor(index, 1); // Move cursor to input position
lcd.print(key); // Display the key pressed
index++;
}
}
}
delay(500);
// Validate if the input is correct
for (int i = 0; i < currentLevel; i++) {
if (userInput[i] != sequence[i]) {
return false; // Input is incorrect
}
}
return true; // Input is correct
}
// Function to reset the game
void resetGame() {
// Reset game-specific variables
currentScore = 0; // Reset score
currentLevel = 1; // Reset level
// Set the system back to the main screen (mode selection and time)
START = true; // Set to show the main menu
mode = 0; // Reset mode to 0 (indicating no game is running)
// Turn off any active game-related functionalities
lcd.clear(); // Clear the screen
lcd.print("Returning..."); // Show a brief message
delay(2000); // Show message for 2 seconds
// Show the time and mode options on the main screen again
lcd.clear();
time(); // Display current time
lcd.setCursor(0, 1); // Move cursor to second line
lcd.print("Mode: 1:Ga, 2:Ca"); // Show mode options
}