Simple implementation of Guitar Hero with mbed.

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

Revision:
0:58d424fe40b9
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main.cpp	Mon Mar 14 17:59:32 2016 +0000
@@ -0,0 +1,533 @@
+/*===================================================================
+--------------------- 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)
+    {    }
+}