Simple implementation of Guitar Hero with mbed.

Dependencies:   4DGL-uLCD-SE MCP23S17 SDFileSystem mbed-rtos mbed wave_player

main.cpp

Committer:
gboggs3
Date:
2016-03-14
Revision:
0:58d424fe40b9

File content as of revision 0:58d424fe40b9:

/*===================================================================
--------------------- Guitar Hero mbed Program ----------------------

This program recreates the classic Guitar Hero game using the mbed
processor, along with various other peripherals, such as capacitive
touch sensors, an IR sensor, and a uLCD display.

Written for:
    Georgia Institute of Technology
    ECE 4180, Lab 4, Section B
    Dr. James Hamblen
    
Authors:
    Garren Boggs
    Anthony Jones
===================================================================*/

#include "mbed.h"
#include "rtos.h" //For threading
#include "uLCD_4DGL.h" //Display
#include "SDFileSystem.h" //SD card functionality
#include "wave_player.h" //For playing music
#include "MCP23S17.h" //Serial PC for debugging
#include <mpr121.h> //Capacitive touch sensors
#include <stdlib.h> 
#include <time.h>

#define NOTE_START 24
#define NOTE_HEIGHT 9
#define NOTE_WIDTH 27
#define MISS 128
#define SPEED 3
#define HIT_TOP 98
#define HIT_BOT 125
#define VICTORY_THRESHOLD 50

bool paused = false; //If paused, stop gameplay and show on screen
bool gameOver = false; //Occurs if HP reaches zero
bool victory = false;
bool resetDisplay = false; //After resetting game or unpausing

bool gHit = false;
bool rHit = false;
bool yHit = false;
bool bHit = false;

int hp = 260; //Hitpoint count
int score = 0;   //Score count
int combo = 0;   //Combo count
int notesCreated = 0;   //Victory after threshold
    
/*  Note locations. The values are the x and y positions respectively.
    A value of -1 for these coordinates indicates that the specific
    note is currently not on the screen. */
int gNote[2]= {0,-1}; //Current GREEN note location. 
int rNote[2]= {NOTE_WIDTH+1,-1}; //Current RED note location. 
int yNote[2]= {2*NOTE_WIDTH+2,-1}; //Current YELLOW note location. 
int bNote[2]= {3*NOTE_WIDTH+3,-1}; //Current BLUE note location.

//-------------------------------------------------------------------
/*  Instantiates the capacitive touch sensors. These will be used for
    determining which notes have been pressed by the user. */
    
/*  Setup the i2c bus on pins 9 and 10 */
    I2C i2c(p9, p10);  
    
/*  Setup the Mpr121: */  
    Mpr121 notes(&i2c, Mpr121::ADD_VSS);
//-------------------------------------------------------------------

//-------------------------------------------------------------------
/*  Create an interrupt receiver to detect when the user has
    strummed. */
    InterruptIn interruptStrum(p26);
//-------------------------------------------------------------------

//-------------------------------------------------------------------
/*  Initialize SD Card Reader to import song to be played during the
    game. */
    SDFileSystem sd(p5, p6, p7, p8, "sd");
    
/*  Initializes the wave player to actually play the song. */
    AnalogOut DACout (p18);
    wave_player player(&DACout);
//-------------------------------------------------------------------

//-------------------------------------------------------------------
/*  Initializes the uLCD display as a UI for the game */
    uLCD_4DGL uLCD(p28,p27,p30);
    
/*  Create an interrupt to allow the user to pause the game, this
    freezing the game UI. */
    InterruptIn interruptPause(p16);
//-------------------------------------------------------------------

//-------------------------------------------------------------------
/*  Initialize the mbed LEDs for testing and debugging purposes. */
    DigitalOut led1(LED1);
    DigitalOut led2(LED2);
    DigitalOut led3(LED3);
    DigitalOut led4(LED4);
    
/*  Setup the Serial to the PC for debugging */
    Serial pc(USBTX, USBRX);
    DigitalIn test(p24);
//-------------------------------------------------------------------   

/*
*   Detects when the user has strum in order to play a note. This occurs
*   when the user waves a hand close to the IR sensor. If close enough,
*   the defined threshold will be passes and registered as a strum.
*/
void strumInterrupt() 
{
    //For resetting a game
    if(gameOver || victory)
    {
        gNote[1] = -1;
        rNote[1] = -1;
        yNote[1] = -1;
        bNote[1] = -1;
        notesCreated = 0;
        score = 0;
        combo = 0;
        hp = 260;
        
        gHit = false;
        rHit = false;
        yHit = false;
        bHit = false;
        
        resetDisplay = true;
        paused = false;
        gameOver = false;
        victory = false;
        return;        
    }
    
    //Read values of selected notes off of capacitive touch sensor
    int value = notes.read(0x00);
    value += notes.read(0x01)<<8;    
    
    //Determine if a note or combination of notes is being pressed
    if((value == 1 || value == 3 || value == 5 || value == 7 || value == 9 || value == 11 || value == 13 || value == 15) && 
        gNote[1] < HIT_BOT && gNote[1] > HIT_TOP)
        gHit = true;
    if((value == 2 || value == 3 || value == 6 || value == 7 || value == 10 || value == 11 || value == 14 || value == 15) && 
        rNote[1] < HIT_BOT && rNote[1] > HIT_TOP)
        rHit = true;
    if((value == 4 || value == 5 || value == 6 || value == 7 || value == 12 || value == 13 || value == 14 || value == 15) && 
        yNote[1] < HIT_BOT && yNote[1] > HIT_TOP)
        yHit = true;
    if((value == 8 || value == 9 || value == 10 || value == 11 || value == 12 || value == 13 || value == 14 || value == 15) && 
        bNote[1] < HIT_BOT && bNote[1] > HIT_TOP)
        bHit = true;
    
}

/*
*   Updates the game display's life bar counter. If life bar drops below zero,
*   game ends.
*/
void updateLifeBar()
{
    int i;
    int offset = 0;
    
    //Creates life bars on the game UI for the user to know how well they are doing.
    //If the user loses HP, the bars will decrease, and as the user repleneshes their
    //HP, the bars will begin to rise.
    for(i = 0; i < 8; i++)
    {
        if(hp < (260 - i*10))
            uLCD.filled_rectangle(4*NOTE_WIDTH+6,3*i+offset,124,3*(i+1)+offset,BLACK);  
        else
            uLCD.filled_rectangle(4*NOTE_WIDTH+6,3*i+offset,124,3*(i+1)+offset,GREEN);
        offset += 2;
    }
    for(i = 8; i < 19; i++)
    {
        if(hp < (260 - i*10))
            uLCD.filled_rectangle(4*NOTE_WIDTH+6,3*i+offset,124,3*(i+1)+offset,BLACK);
        else
            uLCD.filled_rectangle(4*NOTE_WIDTH+6,3*i+offset,124,3*(i+1)+offset,YELLOW);
        offset += 2;
    }
    for(i = 19; i < 26; i++)
    {
        if(hp < (260 - i*10))
            uLCD.filled_rectangle(4*NOTE_WIDTH+6,3*i+offset,124,3*(i+1)+offset,BLACK);
        else
            uLCD.filled_rectangle(4*NOTE_WIDTH+6,3*i+offset,124,3*(i+1)+offset,RED);
        offset += 2;
    }
}

/*
*   Initializes the game UI with the predefined background. There are
*   four specific notes the user can play, and thus there will be four
*   distinct lanes that are drawn initially.
*/
void setupDisplay()
{
    //Background display
    uLCD.cls();
    uLCD.background_color(BLACK);
    uLCD.line(NOTE_WIDTH,NOTE_START-4,NOTE_WIDTH,127,WHITE);
    uLCD.line(2*NOTE_WIDTH+1,NOTE_START-4,2*NOTE_WIDTH+1,127,WHITE);
    uLCD.line(3*NOTE_WIDTH+2,NOTE_START-4,3*NOTE_WIDTH+2,127,WHITE);
    uLCD.filled_rectangle(4*NOTE_WIDTH+3,0,4*NOTE_WIDTH+4,127,WHITE);
    uLCD.filled_rectangle(126,0,127,127,WHITE);
    uLCD.line(0,NOTE_START-4,4*NOTE_WIDTH+3,NOTE_START-4,WHITE);
    uLCD.rectangle(0,127-(NOTE_HEIGHT+2),NOTE_WIDTH-1,127,GREEN);
    uLCD.rectangle(NOTE_WIDTH+1,127-(NOTE_HEIGHT+2),2*NOTE_WIDTH,127,RED);
    uLCD.rectangle(2*NOTE_WIDTH+2,127-(NOTE_HEIGHT+2),3*NOTE_WIDTH+1,127,YELLOW);
    uLCD.rectangle(3*NOTE_WIDTH+3,127-(NOTE_HEIGHT+2),4*NOTE_WIDTH+2,127,BLUE);
   
    //Scoreboard
    uLCD.printf("SCORE: %d\nCOMBO: %d",score,combo);
    
    //Initialize life bar
    updateLifeBar();
}

/*
*   Calculates the dimensions for a specific note, based on its 
*   current position.
*/
void calculateNoteDims(int pos[], int dims[])
{
    //If currently not on screen, do nothing.
    if(pos[1] < 0 || pos[1] > MISS)
    {
        dims[0] = 0;
        dims[1] = 0;
        dims[2] = 0;
        dims[3] = 0;
        return;
    }
    
    //Otherwise calculate dimensions    
    dims[0] = pos[0];   //x-left dimension
    dims[2] = pos[0] + NOTE_WIDTH - 1;  //x-right dimension
    
    dims[1] = (pos[1] - (NOTE_HEIGHT - 1)) > 0 ? pos[1] : 0;   //y-bottom dimension
    dims[3] = (pos[1] - (NOTE_HEIGHT - 1)) > 0 ? dims[1] + (NOTE_HEIGHT - 1) : pos[1]; //y-top dimension

    return;
}

/*
*   Pauses the game.
*/
void pauseGame()
{
    if(paused)
    {
        resetDisplay = true;
    }
    paused = !paused;
    wait(0.2);   
}

/*
*   The game loop. This continuously updates the display with the notes, and
*   acts accordingly if the user has strum, and a note has been hit. If the
*   game is paused, the notes will cease movement, and a text displaying so
*   will appear on the screen.
*/
void playGame(void const *args)
{
    int gDims[4];
    int rDims[4];
    int yDims[4];
    int bDims[4];
    
    while(1)
    {
        if(!gameOver && !victory && !paused)
        {
            //Resets display if game has been reset or unpaused
            if(resetDisplay)
            {
                resetDisplay = false;
                setupDisplay();
            }
                
            //Update life points
            updateLifeBar();
            
            //Calculate respective positions and draw accordingly if notes
            //are going on or off screen.
            calculateNoteDims(gNote, gDims);
            calculateNoteDims(rNote, rDims);
            calculateNoteDims(yNote, yDims);
            calculateNoteDims(bNote, bDims);

            //Draw the notes and erase old positions
            if(gNote[1] >= 0 && gNote[1] < MISS)
            {
                if(gHit)
                {
                    gHit = false;
                    uLCD.filled_rectangle(gNote[0],gNote[1]-SPEED,gNote[0]+NOTE_WIDTH-1,gNote[1]-1,BLACK);
                    uLCD.filled_rectangle(gDims[0],gDims[1],gDims[2],gDims[3],BLACK);
                    gNote[1] = -1;
                    score += 30;
                    combo++;
                    hp = (hp + (combo >= 10 ? 10 : combo)) >= 260 ? 260 : (hp + (combo >= 10 ? 10 : combo));
                }
                else
                {
                    uLCD.filled_rectangle(gNote[0],gNote[1]-SPEED,gNote[0]+NOTE_WIDTH-1,gNote[1]-1,BLACK);
                    uLCD.filled_rectangle(gDims[0],gDims[1],gDims[2],gDims[3],GREEN);
                }
            }
            
            if(rNote[1] >= 0 && rNote[1] < MISS)
            {
                if(rHit)
                {
                    rHit = false;
                    uLCD.filled_rectangle(rNote[0],rNote[1]-SPEED,rNote[0]+NOTE_WIDTH-1,rNote[1]-1,BLACK);
                    uLCD.filled_rectangle(rDims[0],rDims[1],rDims[2],rDims[3],BLACK);
                    rNote[1] = -1;
                    score += 30;
                    combo++;
                    hp = (hp + (combo >= 10 ? 10 : combo)) >= 260 ? 260 : (hp + (combo >= 10 ? 10 : combo));
                }
                else
                {
                    uLCD.filled_rectangle(rNote[0],rNote[1]-SPEED,rNote[0]+NOTE_WIDTH-1,rNote[1]-1,BLACK);
                    uLCD.filled_rectangle(rDims[0],rDims[1],rDims[2],rDims[3],RED);
                }
            }
            
            if(yNote[1] >= 0 && yNote[1] < MISS)
            {
                if(yHit)
                {
                    yHit = false;
                    uLCD.filled_rectangle(yNote[0],yNote[1]-SPEED,yNote[0]+NOTE_WIDTH-1,yNote[1]-1,BLACK);
                    uLCD.filled_rectangle(yDims[0],yDims[1],yDims[2],yDims[3],BLACK);
                    yNote[1] = -1;
                    score += 30;
                    combo++;
                    hp = (hp + (combo >= 10 ? 10 : combo)) >= 260 ? 260 : (hp + (combo >= 10 ? 10 : combo));
                }
                else
                {
                    uLCD.filled_rectangle(yNote[0],yNote[1]-SPEED,yNote[0]+NOTE_WIDTH-1,yNote[1]-1,BLACK);
                    uLCD.filled_rectangle(yDims[0],yDims[1],yDims[2],yDims[3],YELLOW);
                }
            }
            
            if(bNote[1] >= 0 && bNote[1] < MISS)
            {
                if(bHit)
                {
                    bHit = false;
                    uLCD.filled_rectangle(bNote[0],bNote[1]-SPEED,bNote[0]+NOTE_WIDTH-1,bNote[1]-1,BLACK);
                    uLCD.filled_rectangle(bDims[0],bDims[1],bDims[2],bDims[3],BLACK);
                    bNote[1] = -1;
                    score += 30;
                    combo++;
                    hp = (hp + (combo >= 10 ? 10 : combo)) >= 260 ? 260 : (hp + (combo >= 10 ? 10 : combo));
                }
                else
                {
                    uLCD.filled_rectangle(bNote[0],bNote[1]-SPEED,bNote[0]+NOTE_WIDTH-1,bNote[1]-1,BLACK);
                    uLCD.filled_rectangle(bDims[0],bDims[1],bDims[2],bDims[3],BLUE); 
                }
            
            }
            
            //Redraw target boxes at bottom of screen, incase notes are drawn over them
            uLCD.rectangle(0,127-(NOTE_HEIGHT+2),NOTE_WIDTH-1,127,GREEN);
            uLCD.rectangle(NOTE_WIDTH+1,127-(NOTE_HEIGHT+2),2*NOTE_WIDTH,127,RED);
            uLCD.rectangle(2*NOTE_WIDTH+2,127-(NOTE_HEIGHT+2),3*NOTE_WIDTH+1,127,YELLOW);
            uLCD.rectangle(3*NOTE_WIDTH+3,127-(NOTE_HEIGHT+2),4*NOTE_WIDTH+2,127,BLUE);
            
            //Check if each note is still on the screen. If it is, increment the position
            gNote[1] = gNote[1] < 0 ? (rand()%100 > 95 ? NOTE_START : -2) : gNote[1] >= MISS ? -1 : (gNote[1] + SPEED);
            if(gNote[1] == -1)
            {
               hp -= 10;
               combo = 0; 
            }
            if(gNote[1] == NOTE_START)
            {
                notesCreated++;
            }
            
            rNote[1] = rNote[1] < 0 ? (rand()%100 > 95 ? NOTE_START : -2) : rNote[1] >= MISS ? -1 : (rNote[1] + SPEED); 
            if(rNote[1] == -1)
            {
               hp -= 10;
               combo = 0; 
            }
            if(rNote[1] == NOTE_START)
            {
                notesCreated++;
            }
            
            yNote[1] = yNote[1] < 0 ? (rand()%100 > 95 ? NOTE_START : -2) : yNote[1] >= MISS ? -1 : (yNote[1] + SPEED); 
            if(yNote[1] == -1)
            {
               hp -= 10;
               combo = 0; 
            }
            if(yNote[1] == NOTE_START)
            {
                notesCreated++;
            }
            
            bNote[1] = bNote[1] < 0 ? (rand()%100 > 95 ? NOTE_START : -2) : bNote[1] >= MISS ? -1 : (bNote[1] + SPEED);
            if(bNote[1] == -1)
            {
               hp -= 10;
               combo = 0; 
            }  
            if(bNote[1] == NOTE_START)
            {
                notesCreated++;
            }
            
            //Check to make sure HP is above zero, otherwise, Game Over.
            if(hp <= 0)
                gameOver = true;
                
            //Check to see if user has won.
            if(notesCreated >= VICTORY_THRESHOLD)
                victory = true;

            uLCD.locate(0,0);
            uLCD.printf("SCORE: %d\nCOMBO: %d",score,combo);
        }
        else if(gameOver)
        {
            //Sets up the display for a game over.
            uLCD.cls();
            uLCD.background_color(BLACK);
            uLCD.text_width(2);
            uLCD.text_height(2);
            uLCD.locate(0,0);
            uLCD.printf("\n\nGAME OVER");
            uLCD.locate(0,6);
            uLCD.text_width(1.5);
            uLCD.text_height(1.5);
            uLCD.printf("\n\r     Score: %d\n\r     Combo: %d",score,combo);
            uLCD.text_width(1);
            uLCD.text_height(1);
            uLCD.locate(0,11);
            uLCD.printf("\n\r   Strum to reset");
            uLCD.locate(0,0);
            while(gameOver)
            {
                wait(0.1);
            }
        }
        else if(victory)
        {
            //Sets up the display for a victory.
            uLCD.cls();
            uLCD.background_color(BLACK);
            uLCD.text_width(2);
            uLCD.text_height(2);
            uLCD.locate(0,0);
            uLCD.printf("\n\nYOU ROCK!");
            uLCD.locate(0,6);
            uLCD.text_width(1.5);
            uLCD.text_height(1.5);
            uLCD.printf("\n\r     Score: %d\n\r     Combo: %d",score,combo);
            uLCD.text_width(1);
            uLCD.text_height(1);
            uLCD.locate(0,11);
            uLCD.printf("\n\r   Strum to reset");
            uLCD.locate(0,0);
            while(victory)
            {
                wait(0.1);
            }
        }
        else
        {
            //Sets up the display for a paused game.
            uLCD.cls();
            uLCD.background_color(BLACK);
            uLCD.text_width(2);
            uLCD.text_height(2);
            uLCD.locate(0,0);
            uLCD.printf("\n\n\n PAUSED!");
            uLCD.text_width(1);
            uLCD.text_height(1);
            uLCD.locate(0,0);
            while(paused)
            {
                wait(0.1);
            }
        }
    }
}

int main() 
{   
    //Intialize display to show game UI
    setupDisplay();
    
    //Initialize the strum interrupt to act on a rising edge
    interruptStrum.rise(&strumInterrupt);
    
    //Initialize the pause interrupt
    interruptPause.rise(&pauseGame);
    
    //Initialize game loop
    Thread gl(playGame);

    //Play the song
    FILE *fp = fopen("/sd/sound/smoke_on_the_water.wav", "r");
    if(fp == NULL)
    {
        pc.printf("SD Card could not be read!");   
    }
    else
    {
   //     player.play(fp);
        fclose(fp);
    }
        
    //Tasks for main loop (mainly debugging)
    while(1)
    {    }
}