Wall dodging game utilising a joystick and Nokia 5110 LCD display

Dependencies:   N5110 mbed

main.cpp

Committer:
el14moh
Date:
2016-05-05
Revision:
10:dd2886f3ac0a
Parent:
9:ca800196baeb

File content as of revision 10:dd2886f3ac0a:


#include "main.h"

int main()
{
    initSerial();               // Initialises serial port
    srand(time(NULL));          // Generate seed for random number generation
    calibrateJoystick();        // Zeroes current position of joystick
    button.rise(&button_isr);   // Attach button to ISR function
    button.mode(PullDown);      // Select button mode
    initDisplay();              // Initialise LCD display
    initHiscores();             // Initialise highscores
    introScreen();              // Plays intro animation with title
    game_running = 0;

    while(1) {
        g_button_flag = 0;      // force flag off to prevent menu items being accidently selected
        setTickers(10);         // calls ticker functions 10 times per second (slower speed to make menu control easier)
        state = 0;              // start on top menu item by default

        menu();                 // Brings up game menu (Help and Scores contained in menu() function, selecting a gameplay option moves on from menu)

        setTickers(20);         // Calls ticker functions 20 times per second

        playGame();             // Begins gameplay
        resultsScreen();        // Show final player score and previous highscore
    }
}

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
{
    // Random variables to determine if wall moves this loop or not
    leftwall.random = rand()%20;    // 1/20 chance
    rightwall.random = rand()%20;
    downwall.random = rand()%50;    // 1/50 chance
    upwall.random = rand()%50;

    // LEFT WALL
    if (leftwall.moveFlag == 1) {                                   // if wall is moving
        leftwall.x-=1;                                                  // move wall left
        if (leftwall.x<8) {                                             // 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 (note - range of random values prevents holes in the walls going beyond border)
            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 new wall hasnt started moving in 4 seconds, force it to move
                leftwall.moveFlag = 1;                                          // start wall moving
                leftwall.genFlag = 0;                                           // clear 'wall generated' flag
            } else if ((leftwall.random == 1)&&(rightwall.cooldown > 60)) { // else 2 second window in which wall may randomly start moving before it is forced to
                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) {    // After 10 seconds of gameplay
        if (rightwall.moveFlag == 1) {
            rightwall.x+=1;                     // move wall right
            if (rightwall.x>80) {               // if wall goes off right edge (accounting for border)
                rightwall.moveFlag = 0;
                rightwall.cooldown = 0;
            }
        } else {
            if ((rightwall.genFlag == 0)) {
                rightwall.y = rand() % 27+8;    // random y-coordinate (note - range of random values prevents holes in the walls going beyond border)
                rightwall.x = 6;                // moves wall back to left side
                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) {    // After 30 seconds of gameplay
        if (downwall.moveFlag == 1) {
            if (counter % 2 == 1) {             // horizontal walls move half the speed of vertical walls
                downwall.y+=1;                  // move wall down
            }
            if (downwall.y>44) {                // if wall goes off bottom edge (accounting for border)
                downwall.moveFlag = 0;
            }
        } else {
            if (downwall.genFlag == 0) {
                downwall.x = rand() % 52+19;    // random x-coordinate (note - range of random values prevents holes in the walls going beyond border)
                downwall.y = 1;
                downwall.genFlag = 1;
            } else {
                if (downwall.cooldown > 120) {  // 6s cooldown
                    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 > 1200) {   // After 60 seconds of gameplay
        if (upwall.moveFlag == 1) {
            if (counter % 2 == 1) {             // horizontal walls move half the speed of vertical walls
                upwall.y-=1;                    // move wall up
            }
            if (upwall.y<3) {                   // if wall goes off bottom edge (accounting for border)
                upwall.moveFlag = 0;
            }
        } else {
            if (upwall.genFlag == 0) {
                upwall.x = rand() % 52+19;      // random x-coordinate (note - range of random values prevents holes in the walls going beyond border)
                upwall.y = 46;
                upwall.genFlag = 1;
            } else {
                if (upwall.cooldown > 120) {    // 6s cooldown
                    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 checkBorderCollision()   // Checks if player has hit border
{
    // if floor
    if ( j >= 47 - (PLAYERRADIUS+3)) {
        j = 47 - (PLAYERRADIUS+3);  // Forces player position
    }

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

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

    // if left wall
    if ( i <= (PLAYERRADIUS+8)) {
        i = (PLAYERRADIUS+8);   // Forces player position
    }
}

void invincible()   // Briefly allows player to move through walls
{
    if (g_button_flag) {
        g_button_flag = 0;
        if ((saves > 0) && (mortal == true)) { // Invincibility is available and not currently used
            saves_cool=0;
            mortal = false;
            saves_cool++;
            saves--;
        }
    }
    if (mortal == false) {
        saves_cool++;           // Countdown variable for invincibility
        if (saves_cool > 30) {  // Ticker called 20 times per second, therefore 1.5s of invincibility
            mortal = true;
            saves_cool=0;       // Resets countdown variable
        }
    }
}

void checkWallCollision()   // Checks for any collisions (i.e. player has hit a wall or side of the screen)
{
    if (mortal) {   // If player has not triggered invincibility

        // LEFT WALL
        //  (- - - - - - - If wall has passed inside of player radius - - - - - - -)      (- - If player has hit side of wall gap - -)
        if ((((i - PLAYERRADIUS) <= leftwall.x) && (leftwall.x <= (i + PLAYERRADIUS))) && (j > (leftwall.y+3) || j < (leftwall.y-3))) {
            game_running = 0;
        }

        // RIGHT WALL
        //  (- - - - - - - If wall has passed inside of player radius - - - - - - -)      (- - If player has hit side of wall gap - -)
        if ((((i - PLAYERRADIUS) <= rightwall.x) && (rightwall.x <= (i + PLAYERRADIUS))) && (j > (rightwall.y+3) || j < (rightwall.y-3))) {
            game_running = 0;
        }

        // DOWN WALL
        //  (- - - - - - - If wall has passed inside of player radius - - - - - - -)      (- - If player has hit side of wall gap - -)
        if ((((j - PLAYERRADIUS) <= downwall.y) && (downwall.y-1 <= (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_running = 0;
        }

        // UP WALL
        //  (- - - - - - - If wall has passed inside of player radius - - - - - - -)      (- - If player has hit side of wall gap - -)
        if ((((j - PLAYERRADIUS) <= upwall.y+1) && (upwall.y <= (j + PLAYERRADIUS))) && (i > (upwall.x+9) || i < (upwall.x-9))) {
            game_running = 0;
        }
    }
}

void updateScreen()   // refreshes the screen, redraws player and walls
{
    lcd.clear();
    if (mortal) {
        lcd.drawCircle(i,j,PLAYERRADIUS,1);
    } else {
        if (counter % 2 == 0) { // Make player blink if invincible
            lcd.drawCircle(i,j,PLAYERRADIUS,1);
        }
    }

    // Draws remaining invincibilty indicators
    if (saves > 0) {
        lcd.drawCircle(2,7,1,1);
    }
    if (saves > 1) {
        lcd.drawCircle(2,15,1,1);
    }
    if (saves > 2) {
        lcd.drawCircle(2,23,1,1);
    }
    if (saves > 3) {
        lcd.drawCircle(2,31,1,1);
    }
    if (saves > 4) {
        lcd.drawCircle(2,39,1,1);
    }

    // Draw border
    draw_border();

    // 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) {    // After 10 seconds of gameplay
        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) {    // After 30 seconds of gameplay
        lcd.drawLine(downwall.x+11,downwall.y,80,downwall.y,1);
        lcd.drawLine(downwall.x-11,downwall.y,8,downwall.y,1);
        lcd.drawLine(downwall.x+11,downwall.y-1,80,downwall.y-1,1);
        lcd.drawLine(downwall.x-11,downwall.y-1,8,downwall.y-1,1);

    }
    // UP WALL
    if (counter > 1200) {   // After 60 seconds of gameplay
        lcd.drawLine(upwall.x+11,upwall.y,80,upwall.y,1);
        lcd.drawLine(upwall.x-11,upwall.y,8,upwall.y,1);
        lcd.drawLine(upwall.x+11,upwall.y+1,80,upwall.y+1,1);
        lcd.drawLine(upwall.x-11,upwall.y+1,8,upwall.y+1,1);
    }

    // Flash screen if a wall is about to appear from a new direction
    if (counter == 170) {
        lcd.inverseMode();
    } else if (counter == 570) {
        lcd.inverseMode();
    } else if (counter == 1470) {
        lcd.inverseMode();
    } else {
        lcd.normalMode();
    }
    lcd.refresh();
}

void gameTicker_isr()   // Sets flag for timer interrupt
{
    g_timer_flag = 1;
}

void button_isr()   // Sets flag for button interrupt
{
    g_button_flag = 1;
}

void initDisplay()   // initialises the LCD display
{
    lcd.init();
    wait(0.5);  // wait for LCD to initialise
    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;
    }
}

void initSerial()   // Sets baud rate for serial
{
    serial.baud(115200);
}
void initGame()   // Initialises gameplay variables
{
    g_button_flag = 0;
    leftwall.y = rand() % 27+8;
    leftwall.x = 82;
    rightwall.y = rand() % 27+8;
    rightwall.x = 6;
    downwall.x = rand() % 52+19;
    downwall.y = 0;
    upwall.x = rand() % 52+19;
    upwall.y = 0;
    rightwall.cooldown = 61;
    counter = 0;
    score = 0;
    saves = 5;
    i = 42;
    j = 24;

}

void flash()    // Produces a brief flash on screen
{
    lcd.inverseMode();
    wait(0.2);
    lcd.normalMode();
}

void printHelp()    // Prints instructions
{
    while (1) {
        lcd.printString("Try and",0,1);
        lcd.printString("survive for",0,2);
        lcd.printString("as long as ",0,3);
        lcd.printString("possible!",0,4);
        lcd.refresh();
        if (g_button_flag) {
            playNote(NOTE_E2,0.1);
            flash();
            lcd.clear();
            break;
        }
    }
    g_button_flag = 0;
    while(1) {
        lcd.printString("Get through",0,1);
        lcd.printString("the holes in",0,2);
        lcd.printString("the walls with",0,3);
        lcd.printString("the joystick",0,4);
        lcd.refresh();
        if (g_button_flag) {
            playNote(NOTE_E2,0.1);
            flash();
            lcd.clear();
            break;
        }
    }
    g_button_flag = 0;
    while(1) {
        lcd.printString("Press the",0,1);
        lcd.printString("stick to",0,2);
        lcd.printString("be briefly",0,3);
        lcd.printString("untouchable",0,4);
        lcd.refresh();
        if (g_button_flag) {
            playNote(NOTE_E2,0.1);
            flash();
            lcd.clear();
            break;
        }
    }
    g_button_flag = 0;
    while(1) {
        lcd.printString("But don't use",0,1);
        lcd.printString("it too much!",0,2);
        lcd.printString("you can only",0,3);
        lcd.printString("do it 5 times",0,4);
        lcd.refresh();
        if (g_button_flag) {
            playNote(NOTE_E2,0.1);
            flash();
            lcd.clear();
            break;
        }
    }
    g_button_flag = 0;
    while(1) {
        lcd.printString("Lastly, keep",0,1);
        lcd.printString("an eye on the",0,2);
        lcd.printString("borders of",0,3);
        lcd.printString("the screen...",0,4);
        lcd.refresh();
        if (g_button_flag) {
            playNote(NOTE_E2,0.1);
            flash();
            lcd.clear();
            break;
        }
    }
    g_button_flag = 0;
    while(1) {
        lcd.printString("See where the",0,1);
        lcd.printString("holes are",0,2);
        lcd.printString("before the",0,3);
        lcd.printString("walls move!",0,4);
        lcd.refresh();
        if (g_button_flag) {
            g_button_flag = 0;
            playNote(NOTE_E2,0.1);
            flash();
            lcd.clear();
            break;
        }
    }
}

void printScores()   // Prints previous scores
{
    while(1) {
        lcd.printString("Highscores",13,0);
        lcd.printString("1st:",6,2);
        lcd.printString("2nd:",6,3);
        lcd.printString("3rd:",6,4);
        char first[14];
        char second[14];
        char third[14];
        int one = sprintf(first,"%d",hiscore.one);
        int two = sprintf(second,"%d",hiscore.two);
        int three = sprintf(third,"%d",hiscore.three);

        lcd.printString(first,30,2);
        lcd.printString(second,30,3);
        lcd.printString(third,30,4);

        lcd.refresh();

        if (g_button_flag) {
            g_button_flag = 0;
            playNote(NOTE_E2,0.1);
            flash();
            lcd.clear();
            break;
        }
    }
}

void draw_border()   // Draws game border
{
    lcd.drawLine(7,2,81,2,1);
    lcd.drawLine(7,2,7,45,1);
    lcd.drawLine(81,2,81,45,1);
    lcd.drawLine(7,45,81,45,1);

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

    lcd.drawRect(6,0,1,2,1);
    lcd.drawRect(6,45,1,2,1);
    lcd.drawRect(81,0,1,2,1);
    lcd.drawRect(81,45,1,2,1);

    lcd.refresh();
}

void calculateHighscores()   // Determines if player score is a new highscore
{
    if (score > hiscore.one) {
        hiscore.three = hiscore.two;
        hiscore.two = hiscore.one;
        hiscore.one = score;
    } else if ((hiscore.one > score)&&(score > hiscore.two)) {
        hiscore.three = hiscore.two;
        hiscore.two = score;
    } else if ((hiscore.two > score)&&(score > hiscore.three)) {
        hiscore.three = score;
    }
}

void initHiscores()   // Initialises highscores
{
    hiscore.one = 0;
    hiscore.two = 0;
    hiscore.three = 0;
}

void playNote(int freq, float time)   // Plays a tone of specific frequency and duration
{
    if(backlight < 0.5F) { // Use potentiometer to switch sound on and off
        buzzer.period(1.0/freq);
        buzzer.write(0.0);
        buzzer.write(0.5);
        wait(time);
        buzzer.write(0.0);
    } else {    // If muted
        wait(time); // Wait included so that function still takes the same time to execute
    }
}

void countdown()    // Countdown for start of game
{
    wait(0.5);
    lcd.printString("3",40,2);
    playNote(NOTE_C4,0.2);
    wait(0.3);
    lcd.drawRect(10,10,64,28,2);
    lcd.refresh();
    wait(0.5);
    lcd.printString("2",40,2);
    playNote(NOTE_C4,0.2);
    wait(0.3);
    lcd.drawRect(10,10,64,28,2);
    lcd.refresh();
    wait(0.5);
    lcd.printString("1",40,2);
    playNote(NOTE_C4,0.2);
    wait(0.3);
    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);
    playNote(NOTE_G4,0.5);
    lcd.drawRect(10,10,64,28,2);
    lcd.refresh();
}

void introScreen()   // Introduction animation
{
    wait (1.0);
    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);
    flash();
}

void proceed()   // Moves to selected menu option
{
    if (menu_item == 1) {
        playNote(NOTE_B2,0.1);
        initGame();
        game_running = 1;
    } else if (menu_item == 2) {
        playNote(NOTE_B2,0.1);
        initGame();
        counter = 1200;         // Note - initGame must be called here as calling it after the menu loop will reset the counter, ergo no hard mode
        game_running= 1;
    } else if (menu_item == 3) {
        playNote(NOTE_B2,0.1);
        flash();
        lcd.clear();
        printHelp();
    } else if (menu_item == 4) {
        playNote(NOTE_B2,0.1);
        flash();
        lcd.clear();
        printScores();
    }
}

void setTickers(int speed)   // Dettaches tickers and reattaches them with specified speed
{
    gameTicker.detach();
    gameTicker.attach(&gameTicker_isr,1.0/speed);
    pollJoystick.detach();
    pollJoystick.attach(&updateJoystick,1.0/speed);
}

void congratulate()    // Congratulates player if they got a highscore
{
    if (score >= hiscore.one) { // New highscore
        wait(1.0);
        lcd.printString("HIGHSCORE!",13,5);
        // Highscore melody
        playNote(NOTE_E3,0.1);
        wait(0.1);
        playNote(NOTE_C3,0.05);
        wait(0.05);
        playNote(NOTE_C3,0.05);
        wait(0.05);
        playNote(NOTE_C3,0.05);
        wait(0.15);
        playNote(NOTE_G3,0.1);
        wait(0.1);
        playNote(NOTE_E3,0.1);
        wait(0.1);
        playNote(NOTE_G3,0.1);
        wait(0.1);
        playNote(NOTE_C4,0.5);
        wait(0.1);
        lcd.printString("CONGRATS!!",13,5);
        playNote(NOTE_E2,0.2);

    } else if ((score >= hiscore.two) && (score < hiscore.one)) { // Second place
        // Endgame melody
        playNote(NOTE_A2,0.05);
        wait(0.05);
        playNote(NOTE_A2,0.05);
        wait(0.05);
        playNote(NOTE_A2,0.05);
        wait(0.05);
        playNote(NOTE_A2,0.3);

    } else if ((score >= hiscore.three) && (score < hiscore.two)) { // Third place
        // Endgame melody
        playNote(NOTE_A2,0.05);
        wait(0.05);
        playNote(NOTE_A2,0.05);
        wait(0.05);
        playNote(NOTE_A2,0.05);
        wait(0.05);
        playNote(NOTE_A2,0.3);

    } else { // No highscore
        // Endgame melody
        playNote(NOTE_A2,0.05);
        wait(0.05);
        playNote(NOTE_A2,0.05);
        wait(0.05);
        playNote(NOTE_A2,0.05);
        wait(0.05);
        playNote(NOTE_A2,0.3);
    }
}

void menu()
{
    while(game_running == 0) {    // If game is not started
        if (g_timer_flag) {
            g_timer_flag = 0;

            if ((joystick.direction == UP)||(joystick.direction == UPRIGHT)||(joystick.direction == UPLEFT)) {
                menu_direction = 2;
            } else if ((joystick.direction == DOWN)||(joystick.direction == DOWNRIGHT)||(joystick.direction == DOWNLEFT)) {
                menu_direction = 1;
            } else {
                menu_direction = 0;
            }

            menu_item = fsm[state].output;                  // Which menu item the cursor is on
            state = fsm[state].next_state[menu_direction];  // Moves up or down the menu, or stays on the same item, depending on joystick direction
            lcd.clear();

            lcd.printString("Start Easy",12,1);
            lcd.printString("Start Hard",12,2);
            lcd.printString("Help",12,3);
            lcd.printString("Scores",12,4);

            // Place cursor next to appropriate menu item
            if (menu_item == 1) {
                lcd.drawCircle(6,11,2,1);
            } else if (menu_item == 2) {
                lcd.drawCircle(6,19,2,1);
            } else if (menu_item == 3) {
                lcd.drawCircle(6,27,2,1);
            } else {
                lcd.drawCircle(6,35,2,1);
            }
            lcd.refresh();

            if (((menu_direction == 1)&&(menu_item != 4))||((menu_direction == 2)&&(menu_item != 1))) {
                playNote(NOTE_G2,0.05);    // Menu scrolling sound
            }

            if (g_button_flag) {    // If button press
                g_button_flag = 0;
                if (menu_item == 1) {
                    playNote(NOTE_B2,0.1);
                    initGame();
                    game_running = 1;
                } else if (menu_item == 2) {
                    playNote(NOTE_B2,0.1);
                    initGame();
                    counter = 1200;         // Note - initGame must be called here as calling it after the menu loop will reset the counter, ergo no hard mode
                    game_running= 1;
                } else if (menu_item == 3) {
                    playNote(NOTE_B2,0.1);
                    flash();
                    lcd.clear();
                    printHelp();
                } else if (menu_item == 4) {
                    playNote(NOTE_B2,0.1);
                    flash();
                    lcd.clear();
                    printScores();
                }          
            }
        }
    }

    // When one of the two gameplay options are selected, the loop is broken and moves onto countdown/gameplay
    flash();
    lcd.clear();
    draw_border();          // Draw game border
    countdown();            // Countdown to game start
}

void playGame()
{
    while (game_running == 1) {   // Gameplay loop

        if ( g_timer_flag ) {       // Ticker interrupt
            g_timer_flag = 0;       // Clear flag
            moveWall();             // Move wall obstacles across screen
            moveBall();             // Move player
            invincible();           // Check if invincibility has been enabled
            checkBorderCollision(); // Check if player has hit a border
            checkWallCollision();   // Check if player has hit a wall
            updateScreen();         // Redraw screen with new object positions

            counter++;              // Increment counter each cycle (approx. 20 points a second)
            score++;                // This is seperate from counter for the purposes of keeping score 0 when Hard Start is selected to allow 4 walls from the start

            // Increase wall cooldown variable
            leftwall.cooldown++;
            rightwall.cooldown++;
            downwall.cooldown++;
            upwall.cooldown++;
        }
        sleep();    // Put processor to sleep till next interrupt
    }

    // Briefly freezes screen and plays game-over melody if player hits wall
    flash();
    playNote(NOTE_E4,0.1);
    playNote(NOTE_C4,0.1);
    playNote(NOTE_A3,0.1);
    playNote(NOTE_A2,0.3);
    wait(0.9);
    lcd.clear();
    wait(0.2);
}

void resultsScreen()
{
    calculateHighscores();  // Determines if player has a new highscore
    lcd.printString("Game Over!",14,0);
    lcd.refresh();
    wait(1.0);
    lcd.printString("HiScore:",3,3);
    lcd.printString("Score:",3,2);

    char score_buffer[14];
    char hiscore_buffer[14];
    int length_one = sprintf(score_buffer,"%d",score);
    int length_two = sprintf(hiscore_buffer,"%d",hiscore.one);
    if (score <= 9999) {    // 9999 is highest number that fits on screen
        lcd.printString(score_buffer,54,2);
        lcd.printString(hiscore_buffer,54,3);
    } else {
        lcd.printString ("Wow!",54,2);  // if score is too large to fit in box, print text instead (would require 8.3 minutes of gameplay...)
        lcd.printString ("Wow!",54,3);
    }

    lcd.refresh();
    congratulate(); // Congratulates the player if they have a new highscore
    lcd.refresh();
    while(1) {  // Remain on screen until button is pressed
        if (g_button_flag) {
            g_button_flag = 0;
            playNote(NOTE_E2,0.1);
            flash();
            lcd.clear();
            break;
        }
    }
}