Piano Tile Racer Game for ECE 4180
Dependencies: mbed mbed-rtos 4DGL-uLCD-SE SDFileSystem
main.cpp
- Committer:
- alanjko9
- Date:
- 2021-05-04
- Revision:
- 0:eb011b288325
File content as of revision 0:eb011b288325:
#include "mbed.h" #include "SDFileSystem.h" #include "uLCD_4DGL.h" #include <string> #include <list> #include <mpr121.h> #include "rtos.h" #include <sstream> // new class to play a note on Speaker based on PwmOut class class Speaker { public: Speaker(PinName pin) : _pin(pin) { // _pin(pin) means pass pin to the Speaker Constructor } // class method to play a note based on PwmOut class void playNote(float frequency) { _pin.period(1.0/frequency); _pin = 0.1; } void off() { _pin = 0.0; } private: // sets up specified pin for PWM using PwmOut class PwmOut _pin; }; //set up sd card for storing high scores SDFileSystem sd(p5, p6, p7, p8, "sd"); //SD card //leds to correspond to the key presses for debugging DigitalOut led1(LED1); DigitalOut led2(LED2); DigitalOut led3(LED3); DigitalOut led4(LED4); // Create the interrupt receiver object on pin 26 InterruptIn interrupt(p26); // Setup the i2c bus on pins 9 and 10 I2C i2c(p9, p10); // Setup the Mpr121: // constructor(i2c object, i2c address of the mpr121) Mpr121 mpr121(&i2c, Mpr121::ADD_VSS); //set up uLCD uLCD_4DGL uLCD(p28,p27,p11); // serial tx, serial rx, reset pin; //set up speaker on pin 21 Speaker speaker(p21); //GLOBAL VARIABLES HERE //keycode for touchpad press volatile int key_code = 0; volatile int keyPressed = -1; //bool variables indicating when to update game and screen volatile bool updateGame = false; //state stores the current game state enum State {MENU, PLAYING_GAME, GAME_OVER, SET_MUSIC, SET_DURATION, HIGH_SCORES} state; //the total game duration (default 30 sec. can be 15, 30, 45, 60, 120 sec) float gameDuration = 30.0; //stores the randomly generated tiles in the game (value of 0 is far left, 3 is far right) int tileRow0; int tileRow1; int tileRow2; int tileRow3; //keeps track of the current game score int scoreCount; //Note frequencies for songs float C=261.63, CX=277.18, D=293.66, DX=311.13, E=329.63, F=349.23, FX=369.99, G=392, GX=415.3, A=440, AX=466.16, B=493.88, C1=523.25; //different song arrays float soundOfMusic[] = {C, D, E, C, E, C, E, D, E, F, F, E, D, F, E, F, G, E, G, E, G, F, G, A, A, G, F, A, G, C, D, E, F, G, A, A, D, E, F, G, A, B, B, E, F, G, A, B, C1, C1, B, A, F, B, G, C1, G, E, D}; float threeBlindMice[] = {E, D, C, E, D, C, G, F, E, G, F, E, G, C1, C1, B, A, B, C1, G, G, G, C1, C1, B, A, B, C1, G, G, G, C1, C1, B, A, B, C1, G, G, F}; float flyMeToTheMoon[] = {C1, B, A, G, F, G, A, C1, B, A, G, F, E, A, G, F, E, D, E, F, A, GX, F, E, D, C, C, D, A, A, C1, B, B, G, C, C, F, F, A, G, E, E}; float halfStepScale[] = {C, CX, D, DX, E, F, FX, G, GX, A, AX, B, C1, C1, B, AX, A, GX, G, FX, F, E, DX, D, CX, C}; //int used to determine which song is chosen (0-3) int soundToggle = 0; //bool to play song or not bool isSoundOn = false; //int used to toggle through highscores by game duration (0-4) corresponding to increasing time durations) int highScoreToggle = 0; //high score arrays pulled from SD card storage. only need top 5 scores std::list<int> hs15; std::list<int> hs30; std::list<int> hs45; std::list<int> hs60; std::list<int> hs120; //game timer Timer t; //threads Thread gameTimerThread; Thread playSoundThread; Mutex m; //below are various draw fuctions to draw on the uLCD //draw the white grid for the piano tiles void refreshGrid() { uLCD.filled_rectangle(0, 0, 80, 127, 0xFFFFFF); } //draw the 4 black tiles corresponding to tileRow0-3 void drawTiles() { uLCD.filled_rectangle(20 * tileRow0, 96, 20 * tileRow0 + 20, 127, 0x000000); uLCD.filled_rectangle(20 * tileRow1, 64, 20 * tileRow1 + 20, 95, 0x000000); uLCD.filled_rectangle(20 * tileRow2, 32, 20 * tileRow2 + 20, 63, 0x000000); uLCD.filled_rectangle(20 * tileRow3, 0, 20 * tileRow3 + 20, 31, 0x000000); } //draw the initialized game with all components void drawInitGame() { m.lock(); uLCD.cls(); refreshGrid(); drawTiles(); uLCD.color(WHITE); uLCD.text_height(2); uLCD.locate(12,0); uLCD.printf("Timer:"); uLCD.locate(12, 2); uLCD.printf(" %.0f ", gameDuration); uLCD.locate(12,4); uLCD.printf("Score:"); uLCD.locate(12,6); uLCD.printf(" %d ", scoreCount); m.unlock(); } //draw only the updated components during gameplay void drawPlayingGame() { m.lock(); refreshGrid(); drawTiles();/* uLCD.locate(12, 2); uLCD.printf(" %.0f ", gameDuration - t.read());*/ uLCD.locate(12,6); uLCD.printf(" %d ", scoreCount); m.unlock(); } //draw the game over screen void drawGameOver() { m.lock(); uLCD.cls(); uLCD.text_height(2); uLCD.text_width(2); uLCD.printf("Game Over\n\n"); uLCD.text_height(1); uLCD.text_width(1); uLCD.printf("\nYour score: %d\n", scoreCount); uLCD.printf("\nPress 4 to return to menu"); m.unlock(); } //draw static menu screen void drawMenu() { m.lock(); uLCD.cls(); uLCD.color(WHITE); uLCD.text_height(2); uLCD.printf(" Piano Tile Racer \n"); uLCD.color(16757350); uLCD.text_height(1); uLCD.printf("\n\n0. Start Game\n"); uLCD.color(6730495); uLCD.printf("\n1. Change Song\n"); uLCD.color(6736896); uLCD.printf("\n2. Change Duration\n"); uLCD.color(16751001); uLCD.printf("3. See Highscores\n"); uLCD.color(13408767); uLCD.printf("\nSound: Duration:\n"); if(isSoundOn) { uLCD.printf("[On] [%.0f]", gameDuration); } else { uLCD.printf("[Off] [%.0f]", gameDuration); } m.unlock(); } //draw static song selection screen void drawSetMusic() { m.lock(); uLCD.cls(); uLCD.color(6730495); uLCD.text_height(2); uLCD.printf(" Song Selection \n"); uLCD.text_height(1); uLCD.printf("\n0. Back\n"); uLCD.printf("\n1. No Music\n"); uLCD.printf("\n2. Sound of Music\n"); uLCD.printf("\n3. 3 Blind Mice\n"); uLCD.printf("\n4. Fly Me to the Moon\n"); uLCD.printf("\n5. Half Step Scale\n"); m.unlock(); } //draw static set duration screen void drawSetDuration() { m.lock(); uLCD.cls(); uLCD.color(6736896); uLCD.text_height(2); uLCD.printf(" Game Duration \n"); uLCD.text_height(1); uLCD.printf("\n\n0. Back\n"); uLCD.printf("\n1. 15 seconds\n"); uLCD.printf("\n2. 30 seconds\n"); uLCD.printf("\n3. 45 seconds\n"); uLCD.printf("\n4. 60 seocnds\n"); uLCD.printf("\n5. 120 seconds\n"); m.unlock(); } //draw highscores screen void drawHighScores() { m.lock(); uLCD.cls(); uLCD.color(16751001); uLCD.text_height(2); uLCD.printf(" High Scores \n"); uLCD.text_height(1); uLCD.printf("\n0. Back\n"); uLCD.printf("\n1. Toggle Duration\n"); uLCD.text_height(2); std::list<int>::iterator it; if (highScoreToggle == 0) { uLCD.printf(" 15 Seconds: \n"); uLCD.locate(0,5); for (it=hs15.begin(); it!=hs15.end(); ++it) { printf("%d, ", *it); uLCD.printf("%d, ", *it); } } else if (highScoreToggle == 1) { uLCD.printf(" 30 Seconds: \n"); uLCD.locate(0,5); for (it=hs30.begin(); it!=hs30.end(); ++it) { printf("%d, ", *it); uLCD.printf("%d, ", *it); } } else if (highScoreToggle == 2) { uLCD.printf(" 45 Seconds: \n"); uLCD.locate(0,5); for (it=hs45.begin(); it!=hs45.end(); ++it) { printf("%d, ", *it); uLCD.printf("%d, ", *it); } } else if (highScoreToggle == 3) { uLCD.printf(" 60 Seconds: \n"); uLCD.locate(0,5); for (it=hs60.begin(); it!=hs60.end(); ++it) { printf("%d, ", *it); uLCD.printf("%d, ", *it); } } else if (highScoreToggle == 4) { uLCD.printf(" 120 Seconds: \n"); uLCD.locate(0,5); for (it=hs120.begin(); it!=hs120.end(); ++it) { printf("%d, ", *it); uLCD.printf("%d, ", *it); } } printf("\n"); m.unlock(); } //thread called in main to continuously update game timer void gameTimer() { t.reset(); t.start(); while(1) { //The game duration is reached and the game is over if (t.read() >= gameDuration) { speaker.off(); state = GAME_OVER; drawGameOver(); gameTimerThread.terminate(); playSoundThread.terminate(); } m.lock(); uLCD.locate(12, 2); uLCD.printf(" %.0f ", gameDuration - t.read()); m.unlock(); Thread::wait(100); } } //Thread called in main to play sound void playSound() { int prevScoreCount = 0; Timer s; while(1) { //means correct key has been pressed and next note should play if (prevScoreCount != scoreCount) { if (soundToggle == 0) speaker.playNote(soundOfMusic[(scoreCount-1) % 59]); else if (soundToggle == 1) speaker.playNote(threeBlindMice[(scoreCount-1) % 40]); else if (soundToggle == 2) speaker.playNote(flyMeToTheMoon[(scoreCount-1) % 42]); else if (soundToggle == 3) speaker.playNote(halfStepScale[(scoreCount-1) % 26]); s.reset(); s.start(); prevScoreCount = scoreCount; } //only allow each note to play a max of 0.3 seconds else if (s.read() > 0.3) { speaker.off(); s.stop(); } Thread::wait(100); } } //function called inside updateHighScoreArrays to update SD card //dur specifies which duration's highscores have been updated void updateHighScoreSD(int dur) { //mkdir("/sd/PianoTileRacer", 0777); std::list<int>::iterator iterator; if (dur == 15) { FILE *fp = fopen("/sd/PianoTileRacer/highScores15.txt", "w"); if(fp == NULL) { error("Could not open file for write\n"); } for (iterator = hs15.begin(); iterator != hs15.end(); ++iterator) { fprintf(fp, "%d ", *iterator); } fclose(fp); return; } else if (dur == 30) { FILE *fp = fopen("/sd/PianoTileRacer/highScores30.txt", "w"); if(fp == NULL) { error("Could not open file for write\n"); } for (iterator = hs30.begin(); iterator != hs30.end(); ++iterator) { fprintf(fp, "%d ", *iterator); } fclose(fp); return; } else if (dur == 45) { FILE *fp = fopen("/sd/PianoTileRacer/highScores45.txt", "w"); if(fp == NULL) { error("Could not open file for write\n"); } for (iterator = hs45.begin(); iterator != hs45.end(); ++iterator) { fprintf(fp, "%d ", *iterator); } fclose(fp); return; } else if (dur == 60) { FILE *fp = fopen("/sd/PianoTileRacer/highScores60.txt", "w"); if(fp == NULL) { error("Could not open file for write\n"); } for (iterator = hs60.begin(); iterator != hs60.end(); ++iterator) { fprintf(fp, "%d ", *iterator); } fclose(fp); return; } else if (dur == 120) { FILE *fp = fopen("/sd/PianoTileRacer/highScores120.txt", "w"); if(fp == NULL) { error("Could not open file for write\n"); } for (iterator = hs120.begin(); iterator != hs120.end(); ++iterator) { fprintf(fp, "%d ", *iterator); } fclose(fp); return; } } //update highscores to arrays void updateHighScoreArrays(int score) { std::list<int>::iterator iterator; if (gameDuration == 15.0) { //empty list case if (hs15.empty()) { hs15.push_front(score); updateHighScoreSD(15); return; } //insert somewhere in the middle case for (iterator = hs15.begin(); iterator != hs15.end(); ++iterator) { if(score > *iterator) { hs15.insert(iterator, score); //make sure list is a max size of 5 if (hs15.size() > 5) { hs15.pop_back(); } updateHighScoreSD(15); return; } } //insert at the end of list case if (hs15.size() < 5) { hs15.push_back(score); updateHighScoreSD(15); } return; } else if (gameDuration == 30.0) { //empty list case if (hs30.empty()) { hs30.push_front(score); updateHighScoreSD(30); return; } //insert somewhere in the middle case for (iterator = hs30.begin(); iterator != hs30.end(); ++iterator) { if(score > *iterator) { hs30.insert(iterator, score); //make sure list is a max size of 5 if (hs30.size() > 5) { hs30.pop_back(); } updateHighScoreSD(30); return; } } //insert at the end of list case if (hs30.size() < 5) { hs30.push_back(score); updateHighScoreSD(30); } return; } else if (gameDuration == 45.0) { //empty list case if (hs45.empty()) { hs45.push_front(score); updateHighScoreSD(45); return; } //insert somewhere in the middle case for (iterator = hs45.begin(); iterator != hs45.end(); ++iterator) { if(score > *iterator) { hs45.insert(iterator, score); //make sure list is a max size of 5 if (hs45.size() > 5) { hs45.pop_back(); } updateHighScoreSD(45); return; } } //insert at the end of list case if (hs45.size() < 5) { hs45.push_back(score); updateHighScoreSD(45); } return; } else if (gameDuration == 60.0) { //empty list case if (hs60.empty()) { hs60.push_front(score); updateHighScoreSD(60); return; } //insert somewhere in the middle case for (iterator = hs60.begin(); iterator != hs60.end(); ++iterator) { if(score > *iterator) { hs60.insert(iterator, score); //make sure list is a max size of 5 if (hs60.size() > 5) { hs60.pop_back(); } updateHighScoreSD(60); return; } } //insert at the end of list case if (hs60.size() < 5) { hs60.push_back(score); updateHighScoreSD(60); } return; } else if (gameDuration == 120.0) { //empty list case if (hs120.empty()) { hs120.push_front(score); updateHighScoreSD(120); return; } //insert somewhere in the middle case for (iterator = hs120.begin(); iterator != hs120.end(); ++iterator) { if(score > *iterator) { hs120.insert(iterator, score); //make sure list is a max size of 5 if (hs120.size() > 5) { hs120.pop_back(); } updateHighScoreSD(120); return; } } //insert at the end of list case if (hs120.size() < 5) { hs120.push_back(score); updateHighScoreSD(120); } return; } } //reads in highscores to the arrays. This is only called once in the very beginning of main void readInHighScores() { int tempScore; char c; std::string scoreStr = ""; //15 second highscores FILE *fp = fopen("/sd/PianoTileRacer/highScores15.txt", "r"); if(fp == NULL) { error("Could not open file for read 15\n"); } while (!feof(fp)){ // while not end of file c = fgetc(fp); if (c != ' ') { scoreStr = scoreStr + c; } else { stringstream ss(scoreStr); ss >> tempScore; hs15.push_back(tempScore); scoreStr = ""; } } fclose(fp); //30 second highscores tempScore = -1; c = NULL; scoreStr = ""; fp = fopen("/sd/PianoTileRacer/highScores30.txt", "r"); if(fp == NULL) { error("Could not open file for read 30\n"); } while (!feof(fp)){ // while not end of file c = fgetc(fp); if (c != ' ') { scoreStr = scoreStr + c; } else { stringstream ss(scoreStr); ss >> tempScore; hs30.push_back(tempScore); scoreStr = ""; } } fclose(fp); //45 second highscores tempScore = -1; c = NULL; scoreStr = ""; fp = fopen("/sd/PianoTileRacer/highScores45.txt", "r"); if(fp == NULL) { error("Could not open file for read 45\n"); } while (!feof(fp)){ // while not end of file c = fgetc(fp); if (c != ' ') { scoreStr = scoreStr + c; } else { stringstream ss(scoreStr); ss >> tempScore; hs45.push_back(tempScore); scoreStr = ""; } } fclose(fp); //60 second highscores tempScore = -1; c = NULL; scoreStr = ""; fp = fopen("/sd/PianoTileRacer/highScores60.txt", "r"); if(fp == NULL) { error("Could not open file for read 60\n"); } while (!feof(fp)){ // while not end of file c = fgetc(fp); if (c != ' ') { scoreStr = scoreStr + c; } else { stringstream ss(scoreStr); ss >> tempScore; hs60.push_back(tempScore); scoreStr = ""; } } fclose(fp); //120 second highscores tempScore = -1; c = NULL; scoreStr = ""; fp = fopen("/sd/PianoTileRacer/highScores120.txt", "r"); if(fp == NULL) { error("Could not open file for read 120\n"); } while (!feof(fp)){ // while not end of file c = fgetc(fp); if (c != ' ') { scoreStr = scoreStr + c; } else { stringstream ss(scoreStr); ss >> tempScore; hs120.push_back(tempScore); scoreStr = ""; } } fclose(fp); m.unlock(); } //function called upon touchpad press //this function is responsible for managing backend updates and calling the correct draw functions //returning 1 means the actual game is starting and starts the game timer thread int updateGameInfo() { //playing game state if(state == PLAYING_GAME) { //if the right key is pressed if (keyPressed == tileRow0) { //increase score count m.lock(); scoreCount++; m.unlock(); //shift down the rows and generate a new row tileRow0 = tileRow1; tileRow1 = tileRow2; tileRow2 = tileRow3; tileRow3 = rand() % 4; drawPlayingGame(); return 0; } else { //if the wrong key is pressed speaker.off(); state = GAME_OVER; gameTimerThread.terminate(); playSoundThread.terminate(); drawGameOver(); return 0; } } //game over state, keypress 4 returns to menu and writes highscores to arrays and SD card else if (state == GAME_OVER && keyPressed == 4) { updateHighScoreArrays(scoreCount); state = MENU; drawMenu(); return 0; } //menu state else if (state == MENU) { if (keyPressed == 0){ //initialize tile rows srand (time(NULL)); tileRow0 = rand() % 4; tileRow1 = rand() % 4; tileRow2 = rand() % 4; tileRow3 = rand() % 4; scoreCount = 0; state = PLAYING_GAME; drawInitGame(); return 1; } else if (keyPressed == 1) { state = SET_MUSIC; drawSetMusic(); return 0; } else if (keyPressed == 2) { state = SET_DURATION; drawSetDuration(); return 0; } else if (keyPressed == 3) { state = HIGH_SCORES; drawHighScores(); return 0; } else if (keyPressed == 11) { readInHighScores(); drawMenu(); return 0; } } //changing music screen else if (state == SET_MUSIC) { if (keyPressed == 1) { isSoundOn = false; } else if (keyPressed == 2) { isSoundOn = true; soundToggle = 0; } else if (keyPressed == 3) { isSoundOn = true; soundToggle = 1; } else if (keyPressed == 4) { isSoundOn = true; soundToggle = 2; } else if (keyPressed == 5) { isSoundOn = true; soundToggle = 3; } state = MENU; drawMenu(); return 0; } //changing duration screen else if (state == SET_DURATION) { if (keyPressed == 1) { gameDuration = 15.0; } else if (keyPressed == 2) { gameDuration = 30.0; } else if (keyPressed == 3) { gameDuration = 45.0; } else if (keyPressed == 4) { gameDuration = 60.0; } else if (keyPressed == 5) { gameDuration = 120.0; } state = MENU; drawMenu(); return 0; } //viewing highscores screen else if (state == HIGH_SCORES) { if (keyPressed == 0) { highScoreToggle = 0; state = MENU; drawMenu(); return 0; } else if (keyPressed == 1) { highScoreToggle = (highScoreToggle + 1) % 5; drawHighScores(); return 0; } } return 0; } // Key hit/release interrupt routine void fallInterrupt() { key_code = 0; int i=0; int value=mpr121.read(0x00); value +=mpr121.read(0x01)<<8; // LED demo mod i=0; // puts key number out to LEDs for demo for (i=0; i<12; i++) { if (((value>>i)&0x01)==1) key_code=i+1; } if (key_code != 0) { updateGame = true; } led4=key_code & 0x01; led3=(key_code>>1) & 0x01; led2=(key_code>>2) & 0x01; led1=(key_code>>3) & 0x01; } int main() { interrupt.fall(&fallInterrupt); interrupt.mode(PullUp); state = MENU; while (1) { if (updateGame) { keyPressed = key_code - 1; if (updateGameInfo() == 1) { gameTimerThread.start(gameTimer); if(isSoundOn) { playSoundThread.start(playSound); //playSoundThread.set_priority(osPriorityHigh); } } updateGame = false; } } }