Wall dodging game utilising a joystick and Nokia 5110 LCD display

Dependencies:   N5110 mbed

main.cpp

Committer:
el14moh
Date:
2016-04-22
Revision:
1:26ebbb94cf36
Parent:
0:6b29f9c29a2a
Child:
2:602e9bb053a0

File content as of revision 1:26ebbb94cf36:

/* Joystick
Max Hamilton

My project will be designing a game in which the player must avoid a number
of obstacles coming from different locations for as long as possible

Week 19 Code - Initial test of mbed screen and initilisations
Week 20 Code - Add code to move a player controlled ball around the screen
Week 21 Code - Add code to generate and move an obstacle down the screen

Week 22 Code - Significant progress over easter holiday
    - Diagonal directions added to joystick
    - Collisions implemented. Game over triggers if character hits a wall
    - Walls/obstacles developed greatly
        - Obstacle extended into full wall. Wall structs created and walls travel from all sides of the screen
        - Walls start of appearing from one side of the screen. As time goes on, more will appear from differnt sides, with multiple walls on screen at once
        - Cooldown added to walls to ensure a new one will not instantly appear from a side that another wall has just travelled to.
        - visual warning on screen when walls are about to appear from a new direction
    - Game now keeps track of score and displays it at the end of the game
    - Intro screen added

*/

#include "mbed.h"
#include "N5110.h"
#include "tones.h"

#define DIRECTION_TOLERANCE 0.05    // tolerance of joystick direction
#define PLAYERRADIUS 2              // size of player ball
#define GAMESPEED 20                // game timer speed
#define JOYSPEED 20                 // rate at which the joystick is read
#define PI 3.14159265359

//         VCC,    SCE,   RST,   D/C,   MOSI,  SCLK,   LED
N5110 lcd (PTD3 , PTA0 , PTC4 , PTD0 , PTD2 , PTD1 , PTC3);

AnalogIn backlight(PTB2);  // pot to control brightness
DigitalIn button(PTB3);     // joystick button object
AnalogIn xPot(PTB11);       // joystick x direction object
AnalogIn yPot(PTB10);       // joystick y direction object
DigitalOut buzzer(PTA2);    // buzzer object

Ticker pollJoystick;    // timer to regularly read the joystick
Ticker game_timer;      // timer to regularly update the screen
Timer noteTimer;       // timer for note tones

Serial serial(USBTX,USBRX); // Serial for debug

// create enumerated type (0,1,2,3 etc. for direction)
enum DirectionName {
    UP,
    DOWN,
    LEFT,
    RIGHT,
    UPRIGHT,    // Diagonally up + right
    UPLEFT,     // Diagonally up + left
    DOWNRIGHT,  // Diagonally down + right
    DOWNLEFT,   // Diagonally down + left
    CENTRE,
    UNKNOWN
};

typedef struct JoyStick Joystick;   // struct for Joystick
typedef struct Wall Wall;           // struct for Walls

struct JoyStick {
    float x;                    // current x value
    float x0;                   // 'centred' x value
    float y;                    // current y value
    float y0;                   // 'centred' y value
    int button;                 // button state (assume pull-down used, so 1 = pressed, 0 = unpressed)
    DirectionName direction;    // current direction
};

struct Wall {
    int x;                      // x-coordinate of wall (realtive to centre of the gap)
    int y;                      // y-coordinate of wall (relative to centre of the gap)
    DirectionName direction;    // Direction the wall travels in
    int random;                 // randomly generated integer to determine when a wall begins to travel
    int cooldown;               // stops a wall respawning before a certain amount of time
    volatile bool moveFlag;     // flag to determine if wall is on screen
    volatile bool genFlag;      // flag to determine if wall has been generated
};

// struct variable for joystick
Joystick joystick;
// struct variable for moving walls
Wall leftwall;
Wall rightwall;
Wall downwall;
Wall upwall;

void calibrateJoystick();   // read default positions of the joystick to calibrate later readings
void updateJoystick();      // reads direction the joystick has been moved
void initDisplay();         // initialises the LCD display
void game_timer_isr();      // sets flag for timer interrupt
void moveBall();            // reads joystick direction and moves position of the player
void moveWall();            // moves walls along the screen
void checkCollision();      // checks for any collisions (i.e. player has hit a wall or side of the screen)
void updateScreen();        // refreshes the screen, redraws player and walls
void warning();             // flashes screen when a new wall is ready to appear
void init_serial();         // sets baud rate for serial
void debug();               // prints for debug purposes

float refresh_rate = GAMESPEED; // how often to update display (Hz)
float g_dt = 1.0F/refresh_rate; // global to store time step (F makes it a float, gets rid of compiler warning)
volatile int g_timer_flag = 0;  // flag for timer interrupt
int printFlag = 0;              // flag for printing
volatile int game_over_flag = 0;         // flag to signal game over
int game_start_flag = 0;        // flag to start the game
int i = 42;                     // x-coordinate value of player
int j = 24;                     // y-coordinate value of player
int counter = 0;                // number of times code has looped



int main()
{
    init_serial();
    srand(time(NULL));                                  // generate seed for random number generation
    calibrateJoystick();
    pollJoystick.attach(&updateJoystick,1.0/JOYSPEED);  // read joystick (JOYSPEED) times per second
    initDisplay();


    wait (1.0);

    game_timer.attach(&game_timer_isr,g_dt);

    while(1) {

        lcd.printString("Dodgemania",13,2); // Print game title on screen
        wait (2.5);
        for (int z=0; z<88; z++) {
            lcd.drawCircle(z,20,4,1);

            lcd.clearPixel(z-3,16);
            lcd.clearPixel(z-4,17);
            lcd.clearPixel(z-4,18);
            lcd.clearPixel(z-5,19);
            lcd.clearPixel(z-5,20);
            lcd.clearPixel(z-5,21);
            lcd.clearPixel(z-4,22);
            lcd.clearPixel(z-4,23);
            lcd.clearPixel(z-3,24);
            lcd.refresh();
            wait(0.01);
        }

        lcd.clear();
        wait(0.5);

        lcd.inverseMode();
        wait(0.2);
        lcd.normalMode();

        lcd.printString("Press button",6,2);
        lcd.printString("to start",18,3);

        while(game_start_flag == 0) { // replace with interrupt?
            if(button) {
                game_start_flag = 1;
                lcd.clear();
                wait(0.5);
            }
        }

        lcd.inverseMode();
        wait(0.2);
        lcd.normalMode();

        // Draw game border
        lcd.drawLine(2,2,81,2,1);
        lcd.drawLine(2,2,2,45,1);
        lcd.drawLine(81,2,81,45,1);
        lcd.drawLine(2,45,81,45,1);

        lcd.drawLine(0,0,83,0,1);
        lcd.drawLine(0,0,0,47,1);
        lcd.drawLine(83,0,83,47,1);
        lcd.drawLine(0,47,83,47,1);

        lcd.refresh();

        // Countdown
        wait(0.5);
        lcd.printString("3",40,2);
        wait(0.5);
        lcd.drawRect(10,10,64,28,2);
        lcd.refresh();
        wait(0.5);
        lcd.printString("2",40,2);
        wait(0.5);
        lcd.drawRect(10,10,64,28,2);
        lcd.refresh();
        wait(0.5);
        lcd.printString("1",40,2);
        wait(0.5);
        lcd.drawRect(10,10,64,28,2);
        lcd.refresh();
        wait(0.5);
        lcd.drawRect(10,10,64,28,2);
        lcd.refresh();
        lcd.printString("Go!",36,2);
        wait(0.5);
        lcd.drawRect(10,10,64,28,2);
        lcd.refresh();


        leftwall.y = rand() % 27+8;
        rightwall.y = rand() % 27+8;
        downwall.x = rand() % 27+8;
        upwall.x = rand() % 27+8;
        rightwall.cooldown = 61;

        while (game_over_flag == 0) {

            if ( g_timer_flag ) {  // ticker interrupt
                g_timer_flag = 0;  // clear flag
                moveWall();
                moveBall();
                
                checkCollision();
                
                updateScreen();
                warning();
                debug();

                counter++; // increment counter each cycle (approx. 20 points a second)

                // wall cooldowns increased. N.B these are set to 0 when a wall finishes moving
                leftwall.cooldown++;
                rightwall.cooldown++;
                downwall.cooldown++;
                upwall.cooldown++;
            }
            sleep();
        }

        lcd.inverseMode();
        wait(0.1);
        lcd.normalMode();
        wait(2.0);
        lcd.clear();
        wait(0.2);

        lcd.drawRect(11,5,62,11,0);
        lcd.printString("Game Over!",14,1);
        lcd.refresh();

        wait(1.0);
        lcd.drawRect(11,21,62,21,0);
        lcd.printString("Score:",14,3);
        char buffer[14];
        int length = sprintf(buffer,"%d",counter);
        if (counter <= 999999999) { // 999999999 is highest number that fits in score display box
            lcd.printString(buffer,14,4);
        } else {
            lcd.printString ("Too High!",14,4); // if score is too large to fit in box (unlikely!)
        }
        lcd.refresh();

        wait(5.0);
        lcd.inverseMode();
        wait(0.2);
        lcd.normalMode();
        lcd.clear();

        return 0;
    }

}

void moveBall() // reads joystick direction and moves position of the player
{
    if (joystick.direction == UP) {
        j-=1;
    } else if (joystick.direction == DOWN) {
        j+=1;
    } else if (joystick.direction == LEFT) {
        i-=1;
    } else if (joystick.direction == RIGHT) {
        i+=1;
    } else if (joystick.direction == UPRIGHT) {
        j-=1;
        i+=1;
    } else if (joystick.direction == UPLEFT) {
        j-=1;
        i-=1;
    } else if (joystick.direction == DOWNRIGHT) {
        j+=1;
        i+=1;
    } else if (joystick.direction == DOWNLEFT) {
        j+=1;
        i-=1;
    }
}

void moveWall() // moves walls along the screen
{
    leftwall.random = rand()%20;
    rightwall.random = rand()%20;
    downwall.random = rand()%50;
    upwall.random = rand()%50;

    // LEFT WALL
    if (leftwall.moveFlag == 1) {           // if wall is moving
        leftwall.x-=1;                          // move wall left
        if (leftwall.x<3) {                     // if wall hits a border
            leftwall.moveFlag = 0;                  // stop wall moving
            leftwall.cooldown = 0;                  // reset the cooldown for the wall
        }
    } else {                              // if wall has stopped moving
        if (leftwall.genFlag == 0) {            // if a new wall HASN'T been generated
            leftwall.y = rand() % 27+8;             // make new random y-coordinate
            leftwall.x = 82;                        // reset x-coordinate to rightmost position
            leftwall.genFlag = 1;                   // wall has been generated
        } else {                                // if a new wall HAS been generated
            if (leftwall.cooldown > 80) {          // if a new wall hasnt started moving in 4 seconds, force it to move
                leftwall.moveFlag = 1;
                leftwall.genFlag = 0;
            } else if ((leftwall.random == 1)&&(rightwall.cooldown > 60)) {      // if wall starts moving again
                leftwall.moveFlag = 1;                  // start wall moving
                leftwall.genFlag = 0;                   // clear 'wall generated' flag
            } else {                                // else if wall has not started moving again
                leftwall.moveFlag = 0;              // wall is stopped
            }
        }
    }

// RIGHT WALL
    if (counter > 200) {
        if (rightwall.moveFlag == 1) {
            rightwall.x+=1;
            if (rightwall.x>80) {
                rightwall.moveFlag = 0;
                rightwall.cooldown = 0;
            }
        } else {
            if ((rightwall.genFlag == 0)) {
                rightwall.y = rand() % 27+8;
                rightwall.x = 1;
                rightwall.genFlag = 1;
            } else {
                if (rightwall.cooldown > 80) {
                    rightwall.moveFlag = 1;
                    rightwall.genFlag = 0;
                } else if ((rightwall.random == 1) && (leftwall.cooldown > 60)) {
                    rightwall.moveFlag = 1;
                    rightwall.genFlag = 0;
                } else {
                    rightwall.moveFlag = 0;
                }
            }
        }
    }

// DOWN WALL
    if (counter > 600) {
        if (downwall.moveFlag == 1) {
            if (upwall.cooldown > 60) {
                if (counter % 2 == 1) { // horizontal walls move half the speed of vertical walls
                    downwall.y+=1;
                }
            }
            if (downwall.y>44) {
                downwall.moveFlag = 0;
            }
        } else {
            if (downwall.genFlag == 0) {
                downwall.x = rand() % 63+8;
                downwall.y = 1;
                downwall.genFlag = 1;
            } else {
                if (downwall.cooldown > 80) {
                    downwall.moveFlag = 1;
                    downwall.genFlag = 0;
                } else if ((downwall.random == 1)&&(upwall.cooldown > 60)) {
                    downwall.moveFlag = 1;
                    downwall.genFlag = 0;
                } else {
                    downwall.moveFlag = 0;
                }
            }
        }
    }

// UP WALL
    if (counter > 1500) {
        if (upwall.moveFlag == 1) {
            if (downwall.cooldown > 60) {
                if (counter % 2 == 1) { // horizontal walls move half the speed of vertical walls
                    upwall.y-=1;
                }
            }
            if (upwall.y<3) {
                upwall.moveFlag = 0;
            }
        } else {
            if (upwall.genFlag == 0) {
                upwall.x = rand() % 63+8;
                upwall.y = 46;
                upwall.genFlag = 1;
            } else {
                if (upwall.cooldown > 80) {
                    upwall.moveFlag = 1;
                    upwall.genFlag = 0;
                } else if ((upwall.random == 1)&&(downwall.cooldown > 60)) {
                    upwall.moveFlag = 1;
                    upwall.genFlag = 0;
                } else {
                    upwall.moveFlag = 0;
                }
            }
        }
    }
}

void checkCollision()   // checks for any collisions (i.e. player has hit a wall or side of the screen)
{
    // if floor
    if ( j >= 47 - (PLAYERRADIUS+3)) {
        j = 47 - (PLAYERRADIUS+3);
    }

    // if roof
    if ( j <= (PLAYERRADIUS+3)) {
        j = (PLAYERRADIUS+3);
    }

    // if right wall
    if ( i >= 83 - (PLAYERRADIUS+3)) {
        i = 83 - (PLAYERRADIUS+3);
    }

    // if left wall
    if ( i <= (PLAYERRADIUS+3)) {
        i = (PLAYERRADIUS+3);
    }

    // LEFT WALL
    if ((((i - PLAYERRADIUS) <= leftwall.x) && (leftwall.x <= (i + PLAYERRADIUS))) && (j > (leftwall.y+3) || j < (leftwall.y-3))) {
        game_over_flag = 1;
    }

    // RIGHT WALL
    if ((((i - PLAYERRADIUS) <= rightwall.x) && (rightwall.x <= (i + PLAYERRADIUS))) && (j > (rightwall.y+3) || j < (rightwall.y-3))) {
        game_over_flag = 1;
    }

    // DOWN WALL
    if ((((j - PLAYERRADIUS) <= downwall.y) && (downwall.y <= (j - PLAYERRADIUS))) && (i > (downwall.x+9) || i < (downwall.x-9))) { // if player x is 4 or more than wall x, it has missed the gap
        game_over_flag = 1;
    }

    // UP WALL
    if (((j + PLAYERRADIUS) == upwall.y || (j - PLAYERRADIUS) == upwall.y) && (i > (upwall.x+9) || i < (upwall.x-9))) { // if player x is 4 or more than wall x, it has missed the gap
        game_over_flag = 1;
    }
}

void updateScreen()   // refreshes the screen, redraws player and walls
{
    lcd.clear();
    lcd.drawCircle(i,j,PLAYERRADIUS,1);
    lcd.refresh();  // update display

    // draw Border
    lcd.drawLine(2,2,81,2,1);
    lcd.drawLine(2,2,2,45,1);
    lcd.drawLine(81,2,81,45,1);
    lcd.drawLine(2,45,81,45,1);

    lcd.drawLine(0,0,83,0,1);
    lcd.drawLine(0,0,0,47,1);
    lcd.drawLine(83,0,83,47,1);
    lcd.drawLine(0,47,83,47,1);

    // draw walls
    // LEFT WALL
    lcd.drawLine(leftwall.x,leftwall.y+5,leftwall.x,44,1);
    lcd.drawLine(leftwall.x,leftwall.y-5,leftwall.x,3,1);
    lcd.drawLine(leftwall.x+1,leftwall.y+5,leftwall.x+1,44,1);
    lcd.drawLine(leftwall.x+1,leftwall.y-5,leftwall.x+1,3,1);

    // RIGHT WALL
    if (counter > 200) {
        lcd.drawLine(rightwall.x,rightwall.y+5,rightwall.x,44,1);
        lcd.drawLine(rightwall.x,rightwall.y-5,rightwall.x,3,1);
        lcd.drawLine(rightwall.x-1,rightwall.y+5,rightwall.x-1,44,1);
        lcd.drawLine(rightwall.x-1,rightwall.y-5,rightwall.x-1,3,1);
    }

    // DOWN WALL
    if (counter > 600) {
        lcd.drawLine(downwall.x+11,downwall.y,80,downwall.y,1);
        lcd.drawLine(downwall.x-11,downwall.y,3,downwall.y,1);
        lcd.drawLine(downwall.x+11,downwall.y-1,80,downwall.y-1,1);
        lcd.drawLine(downwall.x-11,downwall.y-1,3,downwall.y-1,1);
    }

    // UP WALL
    if (counter > 1500) {
        lcd.drawLine(upwall.x+11,upwall.y,80,upwall.y,1);
        lcd.drawLine(upwall.x-11,upwall.y,3,upwall.y,1);
        lcd.drawLine(upwall.x+11,upwall.y+1,80,upwall.y+1,1);
        lcd.drawLine(upwall.x-11,upwall.y+1,3,upwall.y+1,1);
    }

    lcd.refresh();
}

void warning ()
{
    if (counter == 170) {
        lcd.inverseMode();
    } else if (counter == 570) {
        lcd.inverseMode();
    } else if (counter == 1470) {
        lcd.inverseMode();
    } else {
        lcd.normalMode();
    }
}

void game_timer_isr()   // sets flag for timer interrupt
{
    g_timer_flag = 1;
}

void initDisplay()   // initialises the LCD display
{
    lcd.init();
    lcd.normalMode();
    lcd.setBrightness(1.0F-backlight); // brightness pot on PCB is soldered in the wrong direction, (1.0F-backlight) inverts the reading
}

void calibrateJoystick()    // read default positions of the joystick to calibrate later readings
{
    button.mode(PullDown);
    // must not move during calibration
    joystick.x0 = xPot;     // initial positions in the range 0.0 to 1.0 (0.5 if centred exactly)
    joystick.y0 = yPot;
}
void updateJoystick()   // reads direction the joystick has been moved
{
    // read current joystick values relative to calibrated values (in range -0.5 to 0.5, 0.0 is centred)
    joystick.x = xPot - joystick.x0;
    joystick.y = yPot - joystick.y0;
    // read button state
    joystick.button = button;

    // calculate direction depending on x,y values
    // tolerance allows a little lee-way in case joystick not exactly in the stated direction
    if ( fabs(joystick.y) < DIRECTION_TOLERANCE && fabs(joystick.x) < DIRECTION_TOLERANCE) {
        joystick.direction = CENTRE;
    } else if ( joystick.y > DIRECTION_TOLERANCE && fabs(joystick.x) < DIRECTION_TOLERANCE) {
        joystick.direction = UP;
    } else if ( joystick.y < DIRECTION_TOLERANCE && fabs(joystick.x) < DIRECTION_TOLERANCE) {
        joystick.direction = DOWN;
    } else if ( joystick.x > DIRECTION_TOLERANCE && fabs(joystick.y) < DIRECTION_TOLERANCE) {
        joystick.direction = LEFT;
    } else if ( joystick.x < DIRECTION_TOLERANCE && fabs(joystick.y) < DIRECTION_TOLERANCE) {
        joystick.direction = RIGHT;
    } else if ( joystick.y > DIRECTION_TOLERANCE && joystick.x > DIRECTION_TOLERANCE) {
        joystick.direction = UPLEFT;
    } else if ( joystick.y > DIRECTION_TOLERANCE && joystick.x < DIRECTION_TOLERANCE) {
        joystick.direction = UPRIGHT;
    } else if ( joystick.x > DIRECTION_TOLERANCE && joystick.y < DIRECTION_TOLERANCE) {
        joystick.direction = DOWNLEFT;
    } else if ( joystick.x < DIRECTION_TOLERANCE && joystick.y < DIRECTION_TOLERANCE) {
        joystick.direction = DOWNRIGHT;
    }

    else {
        joystick.direction = UNKNOWN;
    }


    printFlag = 1;  // set flag for printing
}

void debug()   // prints for debug purposes
{
    if (printFlag) {  // if flag set, clear flag and print joystick values to serial port
        printFlag = 0;
        /*
        if (joystick.direction == UP)
            serial.printf(" UP\n");
        if (joystick.direction == DOWN)
            serial.printf(" DOWN\n");
        if (joystick.direction == LEFT)
            serial.printf(" LEFT\n");
        if (joystick.direction == RIGHT)
            serial.printf(" RIGHT\n");
        if (joystick.direction == UPRIGHT)
            serial.printf(" UPRIGHT\n");    // Diagonally up + right
        if (joystick.direction == UPLEFT)
            serial.printf(" UPLEFT\n");     // Diagonally up + left
        if (joystick.direction == DOWNRIGHT)
            serial.printf(" DOWNRIGHT\n");  // Diagonally down + right
        if (joystick.direction == DOWNLEFT)
            serial.printf(" DOWNLEFT\n");   // Diagonally down + left
        if (joystick.direction == CENTRE)
            serial.printf(" CENTRE\n");
        if (joystick.direction == UNKNOWN)
            serial.printf(" Unsupported direction\n");
            */
        serial.printf("Left-wall Cooldown = %d \n",leftwall.cooldown);
        serial.printf("Right-wall Cooldown = %d \n",rightwall.cooldown);
        serial.printf("counter = %d \n",counter);

    }
}

void init_serial()   // sets baud rate for serial
{
    // set to highest baud - ensure terminal software matches
    serial.baud(115200);
}