Code for 4180 mini project

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

Fork of Pacman by Shawn Rigdon

main.cpp

Committer:
rollschild
Date:
2015-10-21
Revision:
2:610d5194c64e
Parent:
1:b86030cf57c4

File content as of revision 2:610d5194c64e:

#include "mbed.h"
#include "rtos.h"
#include "uLCD_4DGL.h"
#include "SDFileSystem.h"
#include "wave_player.h"

#define PAC_SIZE    5   // The radius of Pacman and the ghost
#define STEP_SIZE   8   // The number of pixels each character moves at once
#define CLEARANCE   12  // The number of pixels each character checks ahead before moving

DigitalIn left_pb(p21);  // push button
DigitalIn right_pb(p22); // push button
DigitalIn up_pb(p23);    // push button
DigitalIn down_pb(p24);  // push button
SDFileSystem sd(p5, p6, p7, p8, "sd"); //SD card
AnalogOut DACout(p18);
wave_player waver(&DACout); // wave player
uLCD_4DGL uLCD(p28, p27, p29);
Mutex lcd_mutex;

void checkMOVE(void);   // This function is defined below.  It was written here since other functions return to it that it also calls.

// several variables are used by multiple threads
volatile bool win=false;    // True when pacman has eaten all coins
volatile bool lose=false;   // True when the position of the ghost and pacman are the same
volatile int x = 64;        // x and y are pacman's position.  The starting position is defined here.
volatile int y = 88;
volatile int gx1 = 64;      // Starting position of the blue ghost
volatile int gy1 = 40;
int i;
bool clearRIGHT,clearLEFT,clearUP,clearDOWN,bgcr,bgcl,bgcu,bgcd;

// An array containing the locations of the 81 coins pacman must eat
int coins[81][2] = {
        {40,88},{48,88},{56,88},{72,88},{80,88},{88,88},
        {40,40},{48,40},{56,40},{64,40},{72,40},{80,40},{88,40},
        {40,48},{40,56},{40,64},{40,72},{40,80},
        {88,48},{88,56},{88,64},{88,72},{88,80},
        {56,96},{56,104},{56,112},
        {48,112},{40,112},{32,112},{24,112},{16,112},
        {16,104},{16,96},{16,88},{16,80},{16,72},
        {24,64},{32,64},
        {16,64},{16,56},{16,48},{16,40},{16,32},{16,24},{16,16},
        {24,16},{32,16},{40,16},{48,16},{56,16},
        {56,24},{56,32},
        {72,96},{72,104},{72,112},
        {80,112},{88,112},{96,112},{104,112},{112,112},
        {112,104},{112,96},{112,88},{112,80},{112,72},
        {104,64},{96,64},
        {112,64},{112,56},{112,48},{112,40},{112,32},{112,24},{112,16},
        {104,16},{96,16},{88,16},{80,16},{72,16},
        {72,24},{72,32}
    };

// This function is used in the ghost thread to replace coins as it passes over them
void replaceCOINS(void)
{
    for(int n=0; n<81; n++)
    {
        lcd_mutex.lock();   //The coins array is used by both threads
        if(gx1 == coins[n][0] && gy1 == coins[n][1])
        {
            uLCD.filled_circle(gx1,gy1,1,0xFFFF00);     //compare the set of coins to the ghost's previous position and if there is a match redraw coin   
        }
        lcd_mutex.unlock();
    }
}

// Checks if the ghost can move right (there is no boundary immediately to the right)
void BGclearRIGHT(void)
{
    bgcr = true;
    for(int p=gx1; p <= gx1+CLEARANCE; p++)
    {
        lcd_mutex.lock();
        if(uLCD.read_pixel(p,gy1)==uLCD.read_pixel(4,4))
        {
            bgcr = false;   // compare the pixels immediately in front of the ghost to the boundary up to the spec. clearance
        }                   // if they are the same color, determine the ghost can't move right
        lcd_mutex.unlock();
    }
}

//Checks if ghost can move left
void BGclearLEFT(void)
{
    bgcl = true;
    for(int p=gx1; p >= gx1-CLEARANCE; p--)
    {
        lcd_mutex.lock();
        if(uLCD.read_pixel(p,gy1)==uLCD.read_pixel(4,4))
        {
            bgcl = false;
        }
        lcd_mutex.unlock();
    }
}

//Checks if ghost can move up
void BGclearUP(void)
{
    bgcu = true;
    for(int p=gy1; p >= gy1-CLEARANCE; p--)
    {
        lcd_mutex.lock();
        if(uLCD.read_pixel(gx1,p)==uLCD.read_pixel(4,4))
        {
            bgcu = false;
        }
        lcd_mutex.unlock();
    }
}

//Checks if ghost can move down
void BGclearDOWN(void)
{
    bgcd = true;
    for(int p=gy1; p <= gy1+CLEARANCE; p++)
    {
        lcd_mutex.lock();
        if(uLCD.read_pixel(gx1,p)==uLCD.read_pixel(4,4))
        {
            bgcd = false;
        }
        lcd_mutex.unlock();
    }
}

//Moves the blue ghost to the right
void bgRIGHT(void)
{
    Thread::wait(50);
    lcd_mutex.lock();
    uLCD.filled_rectangle(gx1-PAC_SIZE,gy1-PAC_SIZE,gx1+PAC_SIZE,gy1+PAC_SIZE,BLACK);   //erase the previous ghost drawing
    lcd_mutex.unlock();
    replaceCOINS();     //replace the coin the ghost was just on if there was one
    if(gx1>124)         //This will cause the ghost to wrap around to the left side of the screen if there were no boundary on the far right
    {
        gx1 = 0;
    }
    gx1 = gx1+STEP_SIZE;    //Move one step size in the x direction
    lcd_mutex.lock();
    //redraw the ghost at the new position
    uLCD.filled_circle(gx1,gy1,PAC_SIZE,BLUE);
    uLCD.filled_rectangle(gx1-PAC_SIZE,gy1,gx1+PAC_SIZE,gy1+PAC_SIZE,BLUE);
    uLCD.filled_circle(gx1+2,gy1-2,1,BLACK);
    uLCD.filled_circle(gx1-2,gy1-2,1,BLACK);
    lcd_mutex.unlock();
}

//Moves the blue ghost left
void bgLEFT(void)
{
    Thread::wait(50);
    lcd_mutex.lock();
    uLCD.filled_rectangle(gx1-PAC_SIZE,gy1-PAC_SIZE,gx1+PAC_SIZE,gy1+PAC_SIZE,BLACK);
    lcd_mutex.unlock();
    replaceCOINS();
    if(gx1<4)
    {
        gx1 = 124;
    }
    gx1 = gx1-STEP_SIZE;
    lcd_mutex.lock();
    uLCD.filled_circle(gx1,gy1,PAC_SIZE,BLUE);
    uLCD.filled_rectangle(gx1-PAC_SIZE,gy1,gx1+PAC_SIZE,gy1+PAC_SIZE,BLUE);
    uLCD.filled_circle(gx1+2,gy1-2,1,BLACK);
    uLCD.filled_circle(gx1-2,gy1-2,1,BLACK);
    lcd_mutex.unlock();
}

//Moves the blue ghost up
void bgUP(void)
{
    Thread::wait(50);
    lcd_mutex.lock();
    uLCD.filled_rectangle(gx1-PAC_SIZE,gy1-PAC_SIZE,gx1+PAC_SIZE,gy1+PAC_SIZE,BLACK);
    lcd_mutex.unlock();
    replaceCOINS();
    if(gy1<4)
    {
        gy1 = 124;
    }
    gy1 = gy1-STEP_SIZE;
    lcd_mutex.lock();
    uLCD.filled_circle(gx1,gy1,PAC_SIZE,BLUE);
    uLCD.filled_rectangle(gx1-PAC_SIZE,gy1,gx1+PAC_SIZE,gy1+PAC_SIZE,BLUE);
    uLCD.filled_circle(gx1+2,gy1-2,1,BLACK);
    uLCD.filled_circle(gx1-2,gy1-2,1,BLACK);
    lcd_mutex.unlock();
}

//Moves the blue ghost down
void bgDOWN(void)
{
    Thread::wait(50);
    lcd_mutex.lock();
    uLCD.filled_rectangle(gx1-PAC_SIZE,gy1-PAC_SIZE,gx1+PAC_SIZE,gy1+PAC_SIZE,BLACK);
    lcd_mutex.unlock();
    replaceCOINS();
    if(gy1>124)
    {
        gy1 = 0;
    }
    gy1 = gy1+STEP_SIZE;
    lcd_mutex.lock();
    uLCD.filled_circle(gx1,gy1,PAC_SIZE,BLUE);
    uLCD.filled_rectangle(gx1-PAC_SIZE,gy1,gx1+PAC_SIZE,gy1+PAC_SIZE,BLUE);
    uLCD.filled_circle(gx1+2,gy1-2,1,BLACK);
    uLCD.filled_circle(gx1-2,gy1-2,1,BLACK);
    lcd_mutex.unlock();
}

//Force ghost to chase Pacman
void follow(void)
{
    if((gx1==x) && (y == gy1) )    //if the ghost and Pacman are at the same position trigger losing condition
    {
        win = true;         //This is set to true just to exit the check for a win loop and terminate other loops without writing additional conditions
        lose = true;
        if(lose)            //Print game over message if lose (determined in the follow function)
        {
            uLCD.cls();
            uLCD.printf("Sorry\nGame Over");
            // if loss, ring the buzzer
            FILE *wave_file;
            wave_file=fopen("/sd/test.wav","r");
            waver.play(wave_file);
            fclose(wave_file);
            Thread::wait(1000);
            return void();
        }

    }
    while(x==gx1 && gy1<y && !win)  //If the ghost is directly above Pacman check to see if moving down is possible, then move down
    {
        BGclearDOWN();  
        bgDOWN();
    }
    while(x==gx1 && gy1>y && !win)
    {
        BGclearUP();
        bgUP();
    }
    while(y==gy1 && gx1<x && !win)
    {
        BGclearRIGHT();
        bgRIGHT();
    }
    while(y==gy1 && gx1>x && !win)
    {
        BGclearLEFT();
        bgLEFT();
    }
}

//Ghost selects a direction to move
void pickMOVE(void)
{   
    
    while((gx1==x || gy1==y) && abs(x-gx1)+abs(y-gy1)<=16 && !win)  //If Pacman is close by give chase
    {
        follow();
    }
    int dec = rand()%4; //randomly generate a number from the set 0,1,2,3, which serves as the direction decision
    if(dec == 0)
    {
        BGclearRIGHT();
        while(bgcr && !win)     //If decision 0 was reached, check to the the move right until a boundary is reached
        {
            bgRIGHT();
            BGclearRIGHT();
        }
    }
    else if(dec == 1)
    {
        BGclearLEFT();
        while(bgcl && !win)
        {
            bgLEFT();
            BGclearLEFT();
        }
    }
    else if(dec == 2)
    {
        BGclearUP();
        while(bgcu && !win)
        {
            bgUP();
            BGclearUP();
        }
    }
    else
    {
        BGclearDOWN();
        while(bgcd && !win)
        {
            bgDOWN();
            BGclearDOWN();
        }
    }
}        

//Check if Pacman can move one step size to the right (Essentially the same as checking for the ghost)
void CHECKclearRIGHT(void)
{
    clearRIGHT = true;
    for(i=x; i <= x+CLEARANCE; i++)
    {
        lcd_mutex.lock();
        if(uLCD.read_pixel(i,y)==uLCD.read_pixel(4,4))
        {
            clearRIGHT = false;
        }
        lcd_mutex.unlock();
    }
}

//Check if Pacman can move left
void CHECKclearLEFT(void)
{
    clearLEFT = true;
    for(i=x; i >= x-CLEARANCE; i--)
    {
        lcd_mutex.lock();
        if(uLCD.read_pixel(i,y)==uLCD.read_pixel(4,4))
        {
            clearLEFT = false;
        }
        lcd_mutex.unlock();
    }
}

//Check if Pacman can move up
void CHECKclearUP(void)
{
    clearUP = true;
    for(i=y; i >= y-CLEARANCE; i--)
    {
        lcd_mutex.lock();
        if(uLCD.read_pixel(x,i)==uLCD.read_pixel(4,4))
        {
            clearUP = false;
        }
        lcd_mutex.unlock();
    }
}

//Check if Pacman can move down
void CHECKclearDOWN(void)
{
    clearDOWN = true;
    for(i=y; i <= y+CLEARANCE; i++)
    {
        lcd_mutex.lock();
        if(uLCD.read_pixel(x,i)==uLCD.read_pixel(4,4))
        {
            clearDOWN = false;
        }
        lcd_mutex.unlock();
    }
}

//This function tracks the coin Pacman eats as he passes over it
void changeCOINS(void)
{
    for(int m=0; m<81; m++)
    {
        lcd_mutex.lock();
        if(x == coins[m][0] && y == coins[m][1])    //Compare Pacman's position to the set of coins
        {
            coins[m][0]=64;                         //If there is a match, change that coins location to the center of the board where Pacman
            coins[m][1]=64;                         //cannot go, but do not draw the coin
        }
        lcd_mutex.unlock();
    }
}

//Move Pacman one step size to the right
void PACmoveRIGHT(void)
{
    while(clearRIGHT && !win)   //Not win indicates the game has not ended
    {
        lcd_mutex.lock();
        uLCD.filled_circle(x,y,PAC_SIZE,BLACK);     //Erase Pacman at his last location
        lcd_mutex.unlock();
        if(x>124)       //wrap around if moving off the board
        {
            x = 0;
        }
        x = x+STEP_SIZE;    //move Pacman one step size to the right
        changeCOINS();  //Track the coin that was eaten at the last location
        
        if(x%(2*STEP_SIZE) == 0)    //There are two drawings provided for Pacman.  The if statement causes Pacman to open his mouth every other move.
        {
            lcd_mutex.lock();
            uLCD.filled_circle(x,y,PAC_SIZE,0xFFFF00);
            uLCD.filled_rectangle(x+2,y-2,x+PAC_SIZE,y+2,BLACK);
            lcd_mutex.unlock();
        }
        else
        {
            lcd_mutex.lock();
            uLCD.filled_circle(x,y,PAC_SIZE,0xFFFF00);
            uLCD.filled_rectangle(x+2,y,x+PAC_SIZE,y+1,BLACK);
            lcd_mutex.unlock();
        }
        
        CHECKclearRIGHT();  //If the user remains in the loop, check for a boundary to the right
        Thread::wait(10);
        break;
    }
}

//Move Pacman left
void PACmoveLEFT(void)
{
    while(clearLEFT && !win)
    {
        lcd_mutex.lock();
        uLCD.filled_circle(x,y,PAC_SIZE,BLACK);
        lcd_mutex.unlock();
        if(x<4)
        {
            x = 128;
        }
        x = x-STEP_SIZE;
        
        changeCOINS();
        if(x%(2*STEP_SIZE) == 0)
        {
            lcd_mutex.lock();
            uLCD.filled_circle(x,y,PAC_SIZE,0xFFFF00);
            uLCD.filled_rectangle(x-2,y-2,x-PAC_SIZE,y+2,BLACK);
            lcd_mutex.unlock();
        }
        else
        {
            lcd_mutex.lock();
            uLCD.filled_circle(x,y,PAC_SIZE,0xFFFF00);
            uLCD.filled_rectangle(x-2,y,x-PAC_SIZE,y+1,BLACK);
            lcd_mutex.unlock();
        }
        
        CHECKclearLEFT();
        Thread::wait(10);
        break;
    }
}

//Move Pacman up
void PACmoveUP(void)
{
    while(clearUP && !win)
    {
        lcd_mutex.lock();
        uLCD.filled_circle(x,y,PAC_SIZE,BLACK);
        lcd_mutex.unlock();
        if(y<4)
        {
            y = 128;
        }
        y = y-STEP_SIZE;
        changeCOINS();
        if(y%(2*STEP_SIZE) == 0)
        {
            lcd_mutex.lock();
            uLCD.filled_circle(x,y,PAC_SIZE,0xFFFF00);
            uLCD.filled_rectangle(x-2,y-2,x+2,y-PAC_SIZE,BLACK);
            lcd_mutex.unlock();
        }
        else
        {
            lcd_mutex.lock();
            uLCD.filled_circle(x,y,PAC_SIZE,0xFFFF00);
            uLCD.filled_rectangle(x,y-2,x+1,y-PAC_SIZE,BLACK);
            lcd_mutex.unlock();
        }
        
        CHECKclearUP();
        Thread::wait(10);
        break;
    }
}

//Move Pacman down
void PACmoveDOWN(void)
{
    while(clearDOWN && !win)
    {
        lcd_mutex.lock();
        uLCD.filled_circle(x,y,PAC_SIZE,BLACK);
        lcd_mutex.unlock();
        if(y>124)
        {
            y = 0;
        }
        y = y+STEP_SIZE;
        changeCOINS();
        if(y%(2*STEP_SIZE) == 0)
        {
            lcd_mutex.lock();
            uLCD.filled_circle(x,y,PAC_SIZE,0xFFFF00);
            uLCD.filled_rectangle(x-2,y+2,x+2,y+PAC_SIZE,BLACK);
            lcd_mutex.unlock();
        }
        else
        {
            lcd_mutex.lock();
            uLCD.filled_circle(x,y,PAC_SIZE,0xFFFF00);
            uLCD.filled_rectangle(x,y+2,x+1,y+PAC_SIZE,BLACK);
            lcd_mutex.unlock();
        }
        
        CHECKclearDOWN();
        Thread::wait(10);
        break;
    }
}

//Read the input from pushbuttons
void checkMOVE(void)
{
    if(!right_pb) {
        CHECKclearRIGHT();
        PACmoveRIGHT();
    } else if(!left_pb) {
        CHECKclearLEFT();
        PACmoveLEFT();
    } else if(!up_pb){
        CHECKclearUP();
        PACmoveUP();
    } else if(!down_pb){
        CHECKclearDOWN();
        PACmoveDOWN();
    }
}

//Draw the boudaries for the game using the uLCD graphics commands
void drawBORDERS(void)
{
    //Outer Border
    uLCD.rectangle(4,4,124,124,RED);
    uLCD.line(8,8,8,120,RED);
    uLCD.line(8,8,62,8,RED);
    uLCD.line(62,8,62,32,RED);
    uLCD.line(62,32,66,32,RED);
    uLCD.line(66,32,66,8,RED);
    uLCD.line(66,8,120,8,RED);
    uLCD.line(120,8,120,120,RED);
    uLCD.line(120,120,66,120,RED);
    uLCD.line(66,120,66,96,RED);
    uLCD.line(66,96,62,96,RED);
    uLCD.line(62,96,62,120,RED);
    uLCD.line(62,120,8,120,RED);
    //Inner Rectangle
    uLCD.rectangle(52,52,76,76,RED);
    uLCD.rectangle(48,48,80,80,RED);
    //Upper Left Corner
    uLCD.line(48,24,24,24,RED);
    uLCD.line(24,24,24,56,RED);
    uLCD.line(24,56,32,56,RED);
    uLCD.line(32,56,32,32,RED);
    uLCD.line(32,32,48,32,RED);
    uLCD.line(48,32,48,24,RED);
    //Upper Right Corner
    uLCD.line(80,24,104,24,RED);
    uLCD.line(104,24,104,56,RED);
    uLCD.line(104,56,96,56,RED);
    uLCD.line(96,56,96,32,RED);
    uLCD.line(96,32,80,32,RED);
    uLCD.line(80,32,80,24,RED);
    //Lower Left Corner
    uLCD.line(48,104,24,104,RED);
    uLCD.line(24,104,24,72,RED);
    uLCD.line(24,72,32,72,RED);
    uLCD.line(32,72,32,96,RED);
    uLCD.line(32,96,48,96,RED);
    uLCD.line(48,96,48,104,RED);
    //Lower Right Corner
    uLCD.line(80,104,104,104,RED);
    uLCD.line(104,104,104,72,RED);
    uLCD.line(104,72,96,72,RED);
    uLCD.line(96,72,96,96,RED);
    uLCD.line(96,96,80,96,RED);
    uLCD.line(80,96,80,104,RED);
}

void placeCOINS(void)
{
    for(int j=0; j<81; j++)
    {
        uLCD.filled_circle(coins[j][0],coins[j][1],1,0xFFFF00);     //Draw the coins in their initial locations
    }
}

//Draw all the initial states of the game
void initialize(void)
{
    drawBORDERS();
    placeCOINS();
    uLCD.filled_circle(x,y,PAC_SIZE,0xFFFF00);
    uLCD.filled_rectangle(x-2,y-2,x-PAC_SIZE,y+2,BLACK);
}

//Check to see if all the coins have been eaten
void checkWIN(void)
{
    win = true;
    for(int k=0; k<81; k++)
    {
        lcd_mutex.lock();
        if(coins[k][0]!=64 || coins[k][1]!=64)  //Check the locations of all coins and if 1 has coordinates other than (64,64) the user has not won
        {
            win = false;
        }
        lcd_mutex.unlock();
    }
}

//Thread supervising the joystick inputs and moving Pacman accordingly
void pacMOVE(void const *args)
{
    while(!win)
    {
        checkMOVE();
        Thread::wait(1);
    }
}

//Thread controlling the movement of the blue ghost
void blueGHOST(void const *args)
{
    while(!win)
    {
        pickMOVE();
    }
}

int main()
{
    left_pb.mode(PullUp);  // The variable left_pb will be zero when the pushbutton for moving the player left is pressed    
    right_pb.mode(PullUp); // The variable rightt_pb will be zero when the pushbutton for moving the player right is pressed        
    up_pb.mode(PullUp);    // the variable fire_pb will be zero when the pushbutton for firing a missile is pressed
    down_pb.mode(PullUp);  // the variable fire_pb will be zero when the pushbutton for firing a missile is pressed
    uLCD.cls();
    uLCD.baudrate(BAUD_3000000);
    initialize();                   // Draw the level setup
    Thread pm(pacMOVE);             // Start the thread for moving Pacman
    Thread bg(blueGHOST);           // Start the thread for moving the blue ghost
    
    Thread::wait(3000000000);       // Wait some time before checking the win conditions since it will take around 30 secs to eat all 81 coins
    while(!win)                     // Check to see if there was a win once every  tenth of a second
    {
        checkWIN();
        Thread::wait(1);
    }
    
    Thread::wait(500);              // Wait .5 second before displaying end message
    
    if(lose)                        // Print game over message if lose (determined in the follow function)
    {
        uLCD.cls();
        uLCD.printf("Sorry\nGame Over");
    }
    else
    {
        uLCD.cls();
        uLCD.printf("Congratulations!\nYou Won!");
    }
    
    
    while(1){Thread::wait(1);}
}