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;
        }
    }
}