-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcode.c
505 lines (471 loc) · 25.2 KB
/
code.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
/* ------------------------------------------------------------------------------------------------------------------------------------------ */
/* 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;
}
}
}