Flappy Bird game on mbed with a micro LCD screen, class D amp, speaker, SD card reader/writer, 5-button navigation switch, and potentiometer speed control

Dependencies:   4DGL-uLCD-SE PinDetect SDFileSystem mbed-rtos mbed

main.cpp

Committer:
Mpmart08
Date:
2016-03-15
Revision:
0:cd1d2540aaf4

File content as of revision 0:cd1d2540aaf4:

#include "mbed.h"
#include "rtos.h"
#include "PinDetect.h"
#include "uLCD_4DGL.h"
#include "Speaker.h"
#include "SDFileSystem.h"
#include "pipe.h"
#include "bird.h"
#include "soundBuilder.h"
#include <stdlib.h>

// image sectors on ulcd sd card
#define BACKGROUND 0x0000, 0x0000   // background
#define FLAPPY     0x0000, 0x0041   // bird
#define PIPEUP     0x0000, 0x0043   // pipe pointing up
#define PIPEDOWN16 0x0000, 0x0054   // pipe pointing down with a height of 16
#define PIPEDOWN32 0x0000, 0x0057   // pipe pointing down with a height of 32
#define PIPEDOWN48 0x0000, 0x005C   // pipe pointing down with a height of 48
#define PIPEDOWN64 0x0000, 0x0063   // pipe pointing down with a height of 64
#define PIPEDOWN80 0x0000, 0x006C   // pipe pointing down with a height of 80
#define PIPEDOWN96 0x0000, 0x0077   // pipe pointing down with a height of 96

// Nav Switch
PinDetect nsUp(p16);        // up button
PinDetect nsCenter(p15);    // center button
PinDetect nsDown(p13);      // down button

// uLCD
uLCD_4DGL uLCD(p28, p27, p30);

// Speaker
Speaker mySpeaker(p25);

// SD File System
SDFileSystem sd(p5, p6, p7, p8, "sd");

// Potentiometer
AnalogIn pot(p20);

// Bird
Bird flappyBird;

// Pipe
Pipe pipe;

int score = 0;
int oldScore = 0;
int gameSpeed = 0;
 
// State machine definitions
enum gameStateType {START, WAIT, GAME_SETUP, GAME, LOSE};
/* State Definitions:
 * START -- Creates the start screen
 * WAIT -- After the start screen, goes into wait where mbed waits for player to start the game
 * GAME_SETUP -- Initializes game objects such as the bird and pipe
 * GAME -- When the user actually gets to play
 * LOSE -- clears the screen, prints you lose and if you get a high score, stores it, then goes back to start
 */
 
// Global state machine variable
gameStateType gameState = START;

// function that draws each frame of the game on the lcd
void drawScreen() {
    
    // clears the pipe from its previous location
    uLCD.filled_rectangle(                  // draws a rectangle the size of the pipe
        pipe.getOldX(),                     // the pipe's old x position
        0,                                  // the top of the screen
        pipe.getOldX() + pipe.getWidth(),   // the pipe's old x position + the pipe's width
        127,                                // the bottom of the screen
        0x4EC0CA                            // the color of the background
    );
    
    // draws the lower pipe at its current location
    uLCD.set_sector_address(PIPEUP);
    int x = pipe.getX();
    int y = pipe.getY() + 32;
    uLCD.display_image(x,y);
    
    // draws the upper pipe at its current location
    switch (pipe.getType()) {
        case PIPE16:
            uLCD.set_sector_address(PIPEDOWN16);
            break;
        case PIPE32:
            uLCD.set_sector_address(PIPEDOWN32);
            break;
        case PIPE48:
            uLCD.set_sector_address(PIPEDOWN48);
            break;
        case PIPE64:
            uLCD.set_sector_address(PIPEDOWN64);
            break;
        case PIPE80:
            uLCD.set_sector_address(PIPEDOWN80);
            break;
        case PIPE96:
            uLCD.set_sector_address(PIPEDOWN96);
            break;
    }
    x = pipe.getX();
    y = 0;
    uLCD.display_image(x,y);
    
    // clears the bird from its previous location
    uLCD.filled_rectangle(                              // draws a rectangle the size of the bird
        flappyBird.getX(),                              // the bird's current x position
        flappyBird.getOldY(),                           // the bird's old y position
        flappyBird.getX() + flappyBird.getWidth(),      // the bird's x position + the bird's width
        flappyBird.getOldY() + flappyBird.getHeight(),  // the bird's old y position + the bird's height
        0x4EC0CA                                        // the color of the background
    );
    
    // draws the bird at its current location
    uLCD.set_sector_address(FLAPPY);
    x = flappyBird.getX();
    y = flappyBird.getY();
    uLCD.display_image(x,y);
    
    // prints the player's current score
    uLCD.locate(1,1);
    uLCD.printf("%d", score);
}

// function that determines if the bird has collided with a pipe
bool checkCollision() {

    if (pipe.getX() <= flappyBird.getX() + flappyBird.getWidth()
        && flappyBird.getX() <= pipe.getX() + pipe.getWidth()) {
        return (flappyBird.getY() < pipe.getY() || flappyBird.getY() >= (pipe.getY() + 32));
    } else {
        return false;   
    }
}

// Nav switch callbacks

void nsUp_hit_callback() // the up button is pressed
{
    switch (gameState)
    {
    case GAME: // if game is running, then move the bird upwards
        flappyBird.move(DIRECTION_UP);
        break;
    }
}
 
void nsDown_hit_callback() // the down button is pressed
{
    switch (gameState)
    {
    case GAME: // if game is running, then move the bird downwards
        flappyBird.move(DIRECTION_DOWN);
        break;
    }
}

void nsCenter_hit_callback() // the center button is pressed
{
    switch (gameState)
    {
    case WAIT: // if game is waiting to start, set up the game
        gameState = GAME_SETUP;
        break;
    }
}

// thread that plays game sounds through the speaker
void speaker_thread(void const *argument) {

    Speaker *player = &mySpeaker;

    // Start Song
    float sFreq[] = {550,750,550,750};
    float sDur[] = {.3,.3,.3,.3};
    float sVol[] = {.5,.5,.5,.5};
    SoundBuilder startSong(sFreq, sDur, sVol, sizeof(sFreq)/sizeof(*sFreq), player);
    
    // End Song
    float eFreq[] = {300,200,250,225,200,150,150,100};
    float eDur[] = {.3,.3,.3,.3,.3,.3,.3,.3};
    float eVol[] = {.5,.5,.5,.5,.5,.5,.5,.5};
    SoundBuilder endSong(eFreq, eDur, eVol, sizeof(eFreq)/sizeof(*eFreq), player);

    while (true) {
        switch (gameState) {
        case GAME: // if game is running and user dodges a pipe, play a note
            if (oldScore < score) {
                mySpeaker.PlayNote(440, 0.1, 0.5);
                oldScore = score;
            }
            break;
        case START: // play a song at the start of the game
            startSong.playSong();
            break;
        case LOSE: // play a song when the player loses the game
            endSong.playSong();
            wait(5);
            break;
        }
    }
}

int main() {
    
    // Setup internal pullup resistors for nav switch input pins
    nsUp.mode(PullUp);
    nsDown.mode(PullUp);
    nsCenter.mode(PullUp);
    // Wait for pullup to take effect
    wait(0.1);
    // Attaches nav switch inputs to the callback functions
    nsUp.attach_deasserted(&nsUp_hit_callback);
    nsDown.attach_deasserted(&nsDown_hit_callback);
    nsCenter.attach_deasserted(&nsCenter_hit_callback);
    // Set sample frequency for nav switch interrupts
    nsUp.setSampleFrequency();
    nsDown.setSampleFrequency();
    nsCenter.setSampleFrequency();
    
    // initialize the lcd
    uLCD.media_init();
    uLCD.cls();
    uLCD.background_color(0x4EC0CA);
    uLCD.textbackground_color(0x4EC0CA);
    uLCD.color(WHITE);
    
    int highscore = 0;  // variable to store high score
    int i = 0;          // variable for seeding random pipe generation
    
    Thread thread1(speaker_thread); // start speaker thread
    
    while (1) 
    {   
        switch (gameState)
        {
        case START:
        
            // read current high score from a text file on the sd card
            FILE *fp = fopen("/sd/highscore.txt", "r");
            if(fp == NULL) {
                error("Could not open file for read\n");
            }
            fscanf(fp, "%i", &highscore);
            fclose(fp);
            
            // display start screen on lcd
            uLCD.cls();
            uLCD.locate(0,0);
            uLCD.printf("Flappy Bird(ish)!\n\n");
            uLCD.printf("Press Fire\nto Start\n\n");
            uLCD.printf("High Score: %i", highscore);
            gameState = WAIT;
            break;
            
        case GAME_SETUP:
        
            // initialize game objects and draw initial frame on the lcd
            uLCD.cls();
            pipe = Pipe();
            drawScreen();
            srand(i);
            score = 0;
            oldScore = 0;
            gameState = GAME;
            break;
            
        case GAME:
        
            // read the game speed from the potentiometer
            gameSpeed = (int) (pot + 1) * 5;
            
            // if there is a collision, the player loses
            if (checkCollision()) {
                gameState = LOSE; 
            }
            
            // if the player dodges a pipe, increase the score and create a new pipe
            if (pipe.getX() + pipe.getWidth() < 0) {
                score++;
                pipe = Pipe();   
            }
            
            // draw the current frame of the game on the lcd
            drawScreen();
            
            // move the pipe across the screen according the game speed
            pipe.move(gameSpeed);
            
            // wait for lcd to finish drawing images
            wait(0.25);
            break;
            
        case LOSE:
        
            // display game over screen on lcd
            uLCD.cls();
            uLCD.printf("YOU LOSE D:\n\n");
            
            // if the user beat the high score, save and display high score info
            if (score > highscore){
                uLCD.printf("CONGRATZ THO NEW HIGH SCORE!");
                uLCD.printf("           %i", score);
                // overwrite previous high score
                fp = fopen("/sd/highscore.txt", "w");
                fprintf(fp, "%i", score);
                fclose(fp);
            }
            
            // restart game after 5 seconds
            wait(5.0);
            gameState = START;
            break;
        case WAIT:
        
            i++; // Used to seed the rand() function so we don't get the same positions for the pipes every time
            break;
        }
    }
}