Snake and Physics based jumping game with high scores and different difficulties.

Dependencies:   N5110 SDFileSystem mbed

main.cpp

Committer:
el14jw
Date:
2016-05-04
Revision:
5:ae641b1d04fa
Parent:
4:c2d920b17b14

File content as of revision 5:ae641b1d04fa:

/**
2645 Project Games
@file main.cpp

@brief Program Implementation
@brief Revision 1.1
@author Joel W. Webb
*/

#include "main.h"


// Joystick Polling code taken from joystick example code
/// Enumerated type to store joystick direction
enum DirectionName {
    UP,
    DOWN,
    LEFT,
    RIGHT,
    CENTRE,
    UNKNOWN
};

/// Struct for storing data about default Joystick position and Current position
struct JoyStick {
    float x;    // current x value
    float x0;   // 'centred' x value
    float y;    // current y value
    float y0;   // 'centred' y value
    DirectionName direction;  // current direction
};
typedef struct JoyStick Joystick;
/// Global Joystick struct for storing data about default Joystick position and Current position
Joystick joystick;

/// Ticker interrput for polling the joystick
Ticker pollJoystick;
/// Ticker for controlling refresh rate of games
Ticker gametick;
/// Ticker for sctrolling the refresh rate of the display
Ticker screentick;
/// File pointer for accessing SD card locations
FILE *fp = NULL;


int main()
{
    initInputs();
    playSound(tune_intro);

    int snakeDifficulty = 1;
    int plinkDifficulty = 0;
    while (true) {

        // Main menu screen
        int select = menu(menuList,3);

        // Selected Snake
        if (select == 0) {
            // If select = 3 or -1 then user has chosen 'Back' button
            while( !(select == 3 || select ==-1) ) {

                // Snake selection screen
                select = menu(snakeList,5);
                if (select == 0) {
                    // Play Snake game
                    snake(snakeDifficulty);
                }
                // Selected Difficulty
                else if (select == 1) {
                    // Difficulty selection screen
                    int tempdiff = snakeDifficulty;
                    snakeDifficulty = menu(difficultyList,4);
                    if (snakeDifficulty == -1) {
                        snakeDifficulty = tempdiff;
                    }
                }
                // Highscore
                else if (select == 2) {
                    fp = fopen("/sd/snakehighscore.txt", "r");
                    wait(0.05);
                    int stored_top_score = -1;  // -1 to demonstrate it has changed after reading

                    lcd.clear();
                    if (fp == NULL) {  // if it can't open the file then print error message
                        lcd.printString("No Results",0,0);
                        wait(2.0);
                    } else {  // opened file so can write
                        while ( ! (g_buttonA_flag | g_buttonjoy_flag)) {
                            fscanf(fp,"%d",&stored_top_score); // ensure data type matches - note address operator (&)
                            fclose(fp);  // ensure you close the file after reading
                            lcd.printString("Highscore:",10,0);
                            if (stored_top_score != -2) {
                                char buffer[14];
                                sprintf(buffer,"%d",stored_top_score);
                                lcd.printString(buffer,0,1);
                            } else {
                                lcd.printString("No Saved Score",0,1);
                            }
                            lcd.printString(">> Back",0,2);
                            sleep();
                        }
                        g_buttonjoy_flag = 0;
                        g_buttonA_flag = 0;
                    }
                }

            }
        }

        // Selected Plink
        else if (select == 1) {
            
            // If select = 3 or -1 then user has chosen 'Back' button
            while( !(select == 3 || select ==-1) ) {

                select = menu(plinkList,5);
                // Start Plink
                if (select == 0) {
                    playSound(tune_intro);
                    plink(plinkDifficulty);
                }
                // Difficulty selection
                else if (select == 1) {
                    int tempdiff = plinkDifficulty;
                    plinkDifficulty = menu(difficultyList,4);
                    if (plinkDifficulty == -1) {
                        plinkDifficulty = tempdiff;
                    }
                }
                // Highscore
                else if (select == 2) {
                    fp = fopen("/sd/plinkhighscore.txt", "r");
                    wait(0.05);
                    int stored_top_score = -2;  // -2 to demonstrate it has changed after reading

                    lcd.clear();
                    if (fp == NULL) {  // if it can't open the file then print error message
                        lcd.printString("No Results",0,0);
                        wait(2.0);
                    } else {  // opened file so can read
                        while ( ! (g_buttonA_flag || g_buttonjoy_flag)) {
                            fscanf(fp,"%d",&stored_top_score); // ensure data type matches - note address operator (&)
                            fclose(fp);  // ensure you close the file after reading
                            lcd.printString("Highscore",10,0);
                            if (stored_top_score !=-2) {
                                char buffer[14];
                                sprintf(buffer,"%d",stored_top_score);
                                lcd.printString(buffer,0,1);
                            } else {
                                lcd.printString("No Saved Score",0,1);
                            }
                            lcd.printString(">> Back",0,2);
                            sleep();
                        }
                        g_buttonjoy_flag = 0;
                        g_buttonA_flag = 0;
                    }
                }
            }
        }
    }
}


//                                  SNAKE

void snake(int difficulty)
{
    // Play the snake intro tune
    playSound(tune_snakeintro);
    // Creates an array for storing the snake body x and y data. The snake can be up to 880 cells long
    cell snake[880];
    // Clears data from previous games
    for (int i=0; i<880; i++) {
        snake[i].x = 0;
        snake[i].y = 0;
    }

    cell food;
    // Starting length is 5 cells
    int length = 5;
    // Starting 5 cells are x = 20 and y = 11,12,13,14 and 15
    for (int i=0; i<length; i++) {
        snake[i].x = 20;
        snake[i].y = 11+i;
    }

    // Creates timer based on how hard you set the difficulty (default Medium)
    gametick.attach(&gametick_isr,(0.4-(difficulty*0.15)));
    // Initialise the snake_direction parameter as UP
    DirectionName snake_direction = UP;
    // Set the game over condition before while loop
    int gameOver = 0;

    // Create the random food location flag (0 if food needs to be randomised and 1 if food still exists)
    int rand_food = 0;

    while(!gameOver) {
        // When the game ticker updates
        if (g_gametick_flag) {
            // Reset the ticker flag
            g_gametick_flag = 0;

            // If the current snake_direction is the opposite of the joystick direction or the joystick direction is centerd or unknown then keep current direction
            if ( !( (snake_direction == UP && joystick.direction == DOWN) ||
                    (snake_direction == DOWN && joystick.direction == UP) ||
                    (snake_direction == LEFT && joystick.direction == RIGHT) ||
                    (snake_direction == RIGHT && joystick.direction == LEFT) ||
                    (joystick.direction == CENTRE || joystick.direction == UNKNOWN) )) {
                snake_direction = joystick.direction;
            }
            // Iterate through array and move each cell backwards as far as the length of the snake
            for (int i=(length-1); i>0; i--) {
                snake[i] = snake[i-1];
            }
            // Then change the x or y coordinate of the first cell of the snake depending on which direction it is moving in
            switch(snake_direction) {
                case UP:
                    snake[0].y = snake[0].y - 1;
                    break;
                case DOWN:
                    snake[0].y = snake[0].y + 1;
                    break;
                case LEFT:
                    snake[0].x = snake[0].x - 1;
                    break;
                case RIGHT:
                    snake[0].x = snake[0].x + 1;
                    break;
                default:
                    error();
            }

            // If snake hits wall then game over
            if ( (snake[0].x == 41) || (snake[0].x == 0) || (snake[0].y == 23) || (snake[0].y == 0 ) ) {
                gameOver = 1;
            }
            // If snake hits self then game over
            for (int i=1; i<length; i++) {
                if (snake[0].x == snake[i].x && snake[0].y == snake[i].y) {
                    gameOver = 1;
                }
            }

            // If the snake head is at the same value of x and y as the food then play tune and increase length by 2
            if (food.x == snake[0].x && food.y == snake[0].y) {
                playSound(tune_snakeEat);
                length += 2;
                // Also set new random food to be generated
                rand_food = 0;
            }

            // Create a random location for the food that is not the same as a snake location
            while(rand_food == 0) {
                rand_food = 1;
                food.x = rand();
                food.x = food.x %40 +1;
                food.y = rand();
                food.y = food.y %22 +1;
                for (int i=0; i<length; i++) {
                    if(food.x == snake[i].x && food.y == snake[i].y) {
                        rand_food = 0;
                    }
                }
            }

            // Drawing Display
            lcd.clear();
            // Draws Boundary 2 thick
            lcd.drawRect(0,0,83,47,0);
            lcd.drawRect(1,1,81,45,0);
            // Draws snake
            for (int i=0; i<length; i++) {
                lcd.drawRect(snake[i].x*2,snake[i].y*2,1,1,1);
            }
            // Draws food
            lcd.drawRect(food.x*2,food.y*2,1,1,1);
            lcd.refresh();

        }
        sleep();
    }
    // End game ticker
    gametick.detach();
    int score = length -5; // length -5 because starting length is 5 and a score without collecting anything should be 0
    lcd.clear();

    // Read previous highscore if one exists
    fp = fopen("/sd/snakehighscore.txt", "r");
    int current_high_score = -1;  // -1 to demonstrate it has changed after reading

    if (fp == NULL) {  // if it can't open the file then print error message
        lcd.printString("Failed Read",0,5);
    } else {  // opened file so can write
        fscanf(fp,"%d",&current_high_score); // ensure data type matches - note address operator (&)
        fclose(fp);  // ensure you close the file after reading
    }

    // Writing score to sd card if new high score
    if (current_high_score < score && current_high_score != -1) {
        fp = fopen("/sd/snakehighscore.txt", "w");
        if (fp == NULL) {  // if it can't open the file then print error message
            lcd.printString("No SD Card",0,5);
        } else {  // opened file so can write
            fprintf(fp,"%d",score); // ensure data type matches
            fclose(fp);  // ensure you close the file after writing
        }
    }

    // Appropriate strings printed to LCD
    lcd.printString("Game Over",20,0);
    if (current_high_score == -2) {
        lcd.printString("New High Score:",0,1);
    } else if (current_high_score != -1 && current_high_score < score) {
        lcd.printString("New High Score",0,1);
        lcd.printString("Previously",0,3);
    } else if (current_high_score != -1 && current_high_score >= score) {
        lcd.printString("Score",0,1);
        lcd.printString("High Score",0,3);
    } else {
        lcd.printString("Score:",0,1);
        lcd.printString("No Previous",0,3);
    }
    // Print score
    char buffer[14];
    sprintf(buffer,"%d",score);
    lcd.printString(buffer,20,2);
    // Print previous high-score
    if (current_high_score != -1 && current_high_score != -2) {
        char str[14];
        sprintf(str,"%d",current_high_score);
        lcd.printString(str,20,4);
    }

    playSound(tune_gameOver);
    wait(3.0);
    g_buttonA_flag = 0;
    g_buttonB_flag = 0;
    g_buttonjoy_flag = 0;
}


//                                      PLINK

void plink(int difficulty)
{
    playSound(tune_plinkintro);
    lcd.clear();
    // Instead of damaging the program flow with global variables I pass a struct full of variables from one plink function to another.

    plinkvar pvar;
    pvar.difficulty = difficulty;

    // Initialise plink variables
    pvar = initpvar(pvar);
    // Game ticker
    int refreshRate = 100;
    float tickerDelay = 1.0/refreshRate;
    pvar.tickerDelay = tickerDelay;
    gametick.attach(&gametick_isr,tickerDelay);
    // Update screen 30 fps
    tickerDelay = 1.0/30.0;
    screentick.attach(&screentick_isr,tickerDelay);
    wait(0.25);

    while(!pvar.gameOver) {
        if (g_gametick_flag) {
            g_gametick_flag = 0;

            // Screen scrolling
            pvar = plinkScroll(pvar);
            // Platform generation
            pvar = plinkPlatGen(pvar);
            // Collisions
            pvar = plinkCollisions(pvar);
            // Physics Engine
            pvar = plinkPhysicsEngine(pvar);
            // LCD drawing
            pvar = plinkDrawScreen(pvar);

        }
        // Sleep to conserve power until next interrupt
        sleep();
    }

    gametick.detach();
    lcd.clear();

    // Handle high scores and printing game over messages to LCD
    pvar = plinkGameOver(pvar);

}

plinkvar initpvar(plinkvar pvar)
{
    // Initial physics conditions
    vector pos = {41,47};
    vector vel = {0,-25};
    vector acc = {0,10};
    pvar.pos = pos;
    pvar.vel = vel;
    pvar.acc = acc;

    // Initialising game parameters
    pvar.height = 0;             // This is the number of pixels that the lcd has 'scrolled' by
    pvar.gameOver = 0;           // When something consitutes a game over parameter this is set to 1 and the while loop is broken out of
    pvar.platformWidth = 7 - (pvar.difficulty * 2); // Platform width is based on difficulty and also changed by power ups
    cell powerUp = {42,-100};   // The first power up can be found at these coordinates
    pvar.powerUp = powerUp;
    pvar.powerUpHeight = 0;      // This parameter keeps tabs on the number of pixels scrolled while a power up is active (after 100 picels the power up wears off)
    pvar.powerUpRadius = 5;      // Helpful parameter for detecting collisions
    pvar.ballRadius = 2;         // Also helpful parameter for detecting collisions and is changed in 2 power up cases

    // Initialising platforms
    pvar.platforms[0].x = 42;
    pvar.platforms[0].y = 46;
    for (int i=1; i<20; i++) {
        pvar.platforms[i].x = rand()%(84-pvar.platformWidth) + (pvar.platformWidth-1)/2;
        pvar.platforms[i].y = pvar.platforms[i-1].y - (rand()%10 + 5);
    }

    return pvar;
}
plinkvar plinkScroll(plinkvar pvar)
{
    // Scrolling Block
    while (pvar.pos.y < 14) {
        pvar.pos.y++;
        pvar.height++;
        pvar.powerUpHeight++;
        for (int i=0; i<20; i++) {
            pvar.platforms[i].y++;
        }
        pvar.powerUp.y++;
    }
    return pvar;
}
plinkvar plinkPlatGen(plinkvar pvar)
{
    // Platform generation
    for (int i=0; i<20; i++) {
        if (pvar.platforms[i].y > 47) {
            // Stage 1
            if (pvar.height <= 500) {
                if (i == 0) {
                    pvar.platforms[0].x = rand()%(84-pvar.platformWidth) + (pvar.platformWidth-1)/2;
                    pvar.platforms[0].y = pvar.platforms[19].y - (rand()%10 + 5);
                } else {
                    pvar.platforms[i].x = rand()%(84-pvar.platformWidth) + (pvar.platformWidth-1)/2;
                    pvar.platforms[i].y = pvar.platforms[i-1].y - (rand()%10 + 5);
                }
            }
            // Stage 2
            else if (500 < pvar.height <= 1000) {
                if (i == 0) {
                    pvar.platforms[0].x = rand()%(84-pvar.platformWidth) + (pvar.platformWidth-1)/2;
                    pvar.platforms[0].y = pvar.platforms[19].y - (rand()%10 + 10);
                } else {
                    pvar.platforms[i].x = rand()%(84-pvar.platformWidth) + (pvar.platformWidth-1)/2;
                    pvar.platforms[i].y = pvar.platforms[i-1].y - (rand()%10 + 10);
                }
            }
            //Stage 3
            else if (pvar.height > 1000) {
                if (i == 0) {
                    pvar.platforms[0].x = rand()%(84-pvar.platformWidth) + (pvar.platformWidth-1)/2;
                    pvar.platforms[0].y = pvar.platforms[19].y - (rand()%10 + 15);
                } else {
                    pvar.platforms[i].x = rand()%(84-pvar.platformWidth) + (pvar.platformWidth-1)/2;
                    pvar.platforms[i].y = pvar.platforms[i-1].y - (rand()%10 + 15);
                }
            }
        }
    }
    return pvar;
}
plinkvar plinkCollisions(plinkvar pvar)
{
    // Collisions
    // Platforms
    for (int i=0; i<20; i++) {
        if (  (int)pvar.pos.y == (pvar.platforms[i].y - 1)  &&  (int)pvar.pos.x > pvar.platforms[i].x-pvar.platformWidth-2  &&  (int)pvar.pos.x < pvar.platforms[i].x+pvar.platformWidth+2  ) {
            pvar.vel.y = -25;
            // Play appropriate sound when bouncing
            switch (pvar.ballRadius) {
                case 1:
                    playSound(tune_plinkjump_small);
                    break;
                case 2:
                    playSound(tune_plinkjump_normal);
                    break;
                case 4:
                    playSound(tune_plinkjump_big);
                    break;
            }
        }
    }
    // Walls
    if (pvar.pos.x <= pvar.ballRadius) {
        pvar.vel.x = abs(pvar.vel.x);
    } else if (pvar.pos.x >= 83 - pvar.ballRadius) {
        pvar.vel.x = -abs(pvar.vel.x);
    }
    // Power Up
    if ((int)pvar.pos.x+pvar.ballRadius >= (pvar.powerUp.x-pvar.powerUpRadius)
            && (int)pvar.pos.x-pvar.ballRadius <= (pvar.powerUp.x+pvar.powerUpRadius)
            && (int)pvar.pos.y+pvar.ballRadius >= (pvar.powerUp.y-pvar.powerUpRadius)
            && (int)pvar.pos.y-pvar.ballRadius <= (pvar.powerUp.y+pvar.powerUpRadius)) {
        // Create new powerUp box at the next 100 mark
        pvar.powerUp.x = rand()%(84-1-(pvar.powerUpRadius*2)) + pvar.powerUpRadius;
        pvar.powerUp.y = -100;

        //reset power up height
        pvar.powerUpHeight = 0;

        // Choose new power up randomly
        switch (rand()%4) {
            case 0:
                pvar.ballRadius = 4;
                break;
            case 1:
                pvar.ballRadius = 1;
                break;
            case 2:
                pvar.platformWidth = 11;
                break;
            case 3:
                pvar.platformWidth = 5;
                break;
            default:
                error();
        }
    }
    // If the powerup has been missed then generate new one
    else if (pvar.powerUp.y > 47+pvar.powerUpRadius) {
        pvar.powerUp.x = rand()%(84-1-(pvar.powerUpRadius*2)) + pvar.powerUpRadius;
        pvar.powerUp.y = -150;
    }
    // After certain height achieved then cancel power up
    if (pvar.powerUpHeight > 100) {
        pvar.ballRadius = 2;
        pvar.platformWidth = 7 - (pvar.difficulty * 2);
    }
    return pvar;
}
plinkvar plinkPhysicsEngine(plinkvar pvar)
{
    // Physics engine
    // Acceleration due to joystick
    if (g_joystick_flag) {
        if (joystick.direction == LEFT) {
            pvar.acc.x = -15;
        } else if (joystick.direction == RIGHT) {
            pvar.acc.x = 15;
        } else {
            pvar.acc.x = 0;
        }
    }
    // physics iteration
    pvar.vel.y = pvar.vel.y + (pvar.acc.y * pvar.tickerDelay * 4);
    pvar.vel.x = 0.97F*(pvar.vel.x + (pvar.acc.x * pvar.tickerDelay)*4); // 0.97 adds slight damping factor to horizontal movement
    pvar.pos.y = pvar.pos.y + (pvar.vel.y * pvar.tickerDelay * 4);
    pvar.pos.x = pvar.pos.x + (pvar.vel.x * pvar.tickerDelay * 4);
    // Boundaries of physics engine
    if (pvar.pos.y > 48) {
        pvar.gameOver = 1;
    }
    // If ball is out of boundaries then place it back in boundaries
    if (pvar.pos.x - pvar.ballRadius < 0) {
        pvar.pos.x = pvar.ballRadius;
    } else if (pvar.pos.x + pvar.ballRadius > 83) {
        pvar.pos.x = 83-pvar.ballRadius;
    }
    return pvar;
}
plinkvar plinkDrawScreen(plinkvar pvar)
{
    if (g_screentick_flag) {
        g_screentick_flag = 0;
        // Drawing on lcd
        lcd.clear();
        // Print Current Score
        char buffer[14];
        sprintf(buffer,"Score: %d",pvar.height);
        lcd.printString(buffer,0,0);
        // Drawing ball/blob
        // If second blob stage
        int noPlatform = 1;
        if (pvar.ballRadius == 2) {
            for (int i=0; i<20; i++) {
                if (  (int)pvar.pos.y == (pvar.platforms[i].y - 2)  &&  (int)pvar.pos.x > pvar.platforms[i].x-pvar.platformWidth-2  &&  (int)pvar.pos.x < pvar.platforms[i].x+pvar.platformWidth+2   ) {
                    noPlatform = 0;
                    lcd.drawRect(pvar.pos.x-2,pvar.pos.y-1,4,2,1); // X, Y, Width, Height, Fill
                    lcd.setPixel(pvar.pos.x-1,pvar.pos.y-2);
                    lcd.setPixel(pvar.pos.x,pvar.pos.y-2);
                    lcd.setPixel(pvar.pos.x+1,pvar.pos.y-2);
                    lcd.setPixel(pvar.pos.x-3,pvar.pos.y+1);
                    lcd.setPixel(pvar.pos.x+3,pvar.pos.y+1);
                }
                // If third blob stage
                else if ( (int)pvar.pos.y == (pvar.platforms[i].y - 1)  &&  (int)pvar.pos.x > pvar.platforms[i].x-pvar.platformWidth-2  &&  (int)pvar.pos.x < pvar.platforms[i].x+pvar.platformWidth+2  ) {
                    noPlatform = 0;
                    lcd.drawRect(pvar.pos.x-3,pvar.pos.y-1,6,1,1); // X, Y, Width, Height, Fill
                    lcd.setPixel(pvar.pos.x-4,pvar.pos.y);
                    lcd.setPixel(pvar.pos.x+4,pvar.pos.y);
                    lcd.setPixel(pvar.pos.x-2,pvar.pos.y-2);
                    lcd.setPixel(pvar.pos.x-1,pvar.pos.y-2);
                    lcd.setPixel(pvar.pos.x,pvar.pos.y-2);
                    lcd.setPixel(pvar.pos.x+1,pvar.pos.y-2);
                    lcd.setPixel(pvar.pos.x+2,pvar.pos.y-2);
                }
            }
        }
        // Else default blob stage
        if (noPlatform == 1) {
            lcd.drawCircle((int)pvar.pos.x,(int)pvar.pos.y,pvar.ballRadius,1); // X, Y, Radius, Fill
            lcd.refresh();
        }
        // Platforms and boundaries drawing
        for (int i=0; i<20; i++) {
            if (pvar.platforms[i].y > 0) {
                lcd.drawRect( pvar.platforms[i].x-pvar.platformWidth ,pvar.platforms[i].y,pvar.platformWidth*2,1,1); // X, Y, Width, Height, Fill
            }
        }
        lcd.drawLine(0,0,0,47,1); // x0,y0,x1,y1,type
        lcd.drawLine(83,0,83,47,1);
        // PowerUp drawing
        if (pvar.powerUp.y > 0-pvar.powerUpRadius && pvar.powerUp.y < 47+pvar.powerUpRadius) {
            lcd.drawRect(pvar.powerUp.x-pvar.powerUpRadius,pvar.powerUp.y-pvar.powerUpRadius,10,10,0); // X, Y, Width, Height, Fill
            lcd.setPixel(pvar.powerUp.x,pvar.powerUp.y+3);
            lcd.setPixel(pvar.powerUp.x,pvar.powerUp.y+1);
            lcd.setPixel(pvar.powerUp.x+1,pvar.powerUp.y);
            lcd.setPixel(pvar.powerUp.x+2,pvar.powerUp.y-1);
            lcd.setPixel(pvar.powerUp.x+2,pvar.powerUp.y-2);
            lcd.setPixel(pvar.powerUp.x+1,pvar.powerUp.y-3);
            lcd.setPixel(pvar.powerUp.x,pvar.powerUp.y-3);
            lcd.setPixel(pvar.powerUp.x-1,pvar.powerUp.y-3);
            lcd.setPixel(pvar.powerUp.x-2,pvar.powerUp.y-2);
        }

        lcd.refresh();
        // Looks more pleasing when the ball pauses to bounce
        if (noPlatform == 0) {
            wait(0.03);
        }
    }
    return pvar;
}
plinkvar plinkGameOver(plinkvar pvar)
{
    // Handle saving high scores to SD card
    // Read previous highscore if one exists
    fp = fopen("/sd/plinkhighscore.txt", "r");
    int current_high_score = -1;  // -1 to demonstrate it has changed after reading

    if (fp == NULL) {  // if it can't open the file then print error message
        lcd.printString("Failed Read",0,5);
    } else {  // opened file so can write
        fscanf(fp,"%d",&current_high_score); // ensure data type matches - note address operator (&)
        fclose(fp);  // ensure you close the file after reading
    }

    // Writing score to sd card if new high score
    if (current_high_score < pvar.height && current_high_score != -1) {
        fp = fopen("/sd/plinkhighscore.txt", "w");
        if (fp == NULL) {  // if it can't open the file then print error message
            lcd.printString("No SD Card    ",0,5);
        } else {  // opened file so can write
            fprintf(fp,"%d",pvar.height); // ensure data type matches
            fclose(fp);  // ensure you close the file after writing
        }
    }

    // Appropriate strings printed to LCD
    lcd.printString("Game Over",20,0);
    if (current_high_score == -2) {
        lcd.printString("New High Score:",0,1);
    } else if (current_high_score != -1 && current_high_score < pvar.height) {
        lcd.printString("New High Score",0,1);
        lcd.printString("Previously",0,3);
    } else if (current_high_score != -1 && current_high_score >= pvar.height) {
        lcd.printString("Score:",0,1);
        lcd.printString("High Score",0,3);
    } else {
        lcd.printString("Score:",0,1);
        lcd.printString("No Previous",0,3);
    }
    // Print score
    char buffer[14];
    sprintf(buffer,"%d",pvar.height);
    lcd.printString(buffer,20,2);
    // Print previous high-score
    if (current_high_score != -1 && current_high_score != -2) {
        char str[14];
        sprintf(str,"%d",current_high_score);
        lcd.printString(str,20,4);
    }

    playSound(tune_gameOver);
    lcd.refresh();
    wait(3.0);
    // Reset all flags so no unexpected behaviour occurs
    g_buttonA_flag = 0;
    g_buttonB_flag = 0;
    g_buttonjoy_flag = 0;
    return pvar;
}


// ISR's
void buttonA_isr()
{
    g_buttonA_flag = 1;
}
void buttonB_isr()
{
    g_buttonB_flag = 1;
}
void buttonjoy_isr()
{
    g_buttonjoy_flag = 1;
}
void gametick_isr()
{
    g_gametick_flag = 1;
}
void screentick_isr()
{
    g_screentick_flag = 1;
}


// Initialises Inputs
void initInputs()
{

    errorLED = 1;

    /// Setting up Interrupt service routines for input buttons
    /// PullDown mode is assumed
    buttonA.rise(&buttonA_isr);
    buttonB.rise(&buttonB_isr);
    buttonjoy.rise(&buttonjoy_isr);
    buttonA.mode(PullDown);
    buttonB.mode(PullDown);
    buttonjoy.mode(PullDown);

    /// Testing LCD display works (splash screen)
    lcd.init();
    wait(0.5);
    lcd.setBrightness(0.5); // Brightness set to maximum duty cycle so it isn't affected by changing frequency of buzzer
    lcd.printString("Calibrating",8,0);
    lcd.printString("Do not move",8,1);
    lcd.printString("Joystick",18,2);
    lcd.refresh();

    /// Calibrating the Joystick on startup
    calibrateJoystick();
    /// read joystick 10 times per second
    pollJoystick.attach(&updateJoystick,1.0/10.0);

    // Patch for sd card access not working first time
    fp = fopen("/sd/test.txt", "w");
    if (fp != NULL) {
        fclose(fp);
    }
    fp = fopen("/sd/test.txt", "w");
    if (fp != NULL) {
        fclose(fp);  // ensure you close the file after writing
    }

    // If file does not exist then create it
    fp = fopen("/sd/plinkhighscore.txt","r");
    if (fp == NULL) {
        fp = fopen("/sd/plinkhighscore.txt","w");
        if (fp != NULL) {
            fprintf(fp,"%d",-2);
            fclose(fp);
        }
    } else {
        fclose(fp);
    }

    fp = fopen("/sd/snakehighscore.txt","r");
    if (fp == NULL) {
        fp = fopen("/sd/snakehighscore.txt","w");
        if (fp != NULL) {
            fprintf(fp,"%d",-2);
            fclose(fp);
        }
    } else {
        fclose(fp);
    }

    wait(1.0);
}

// Hangs on an error
void error()
{
    while(1) {
        errorLED = !errorLED;
        wait(0.2);
    }
}

// Menu function for handling the printing and selecting of menu screens
int menu(const stringList* menuList,int lines)
{
    int select = 0;
    // While either joystick button or button A or button B are not on then continue selection process
    // Once either button is on the the selected value is returned
    while(g_buttonjoy_flag == 0 && g_buttonA_flag == 0 && g_buttonB_flag == 0) {

        // Changes the value of variable select determined by what the joystick value is
        if (g_joystick_flag) {
            if (joystick.direction == UP) {
                if (select == 0) {
                    select = lines-2;
                } else {
                    select--;
                }
            } else if(joystick.direction == DOWN) {
                if (select == lines-2) {
                    select = 0;
                } else {
                    select++;
                }
            }
        }

        // Prints the correct strings
        drawStrings(menuList,lines);
        int line = select + 1;
        char buffer[14];
        sprintf(buffer,">> %s",menuList[line].str);
        lcd.printString(buffer,menuList[line].offset,line);

        if (g_joystick_flag) {
            g_joystick_flag = 0;
            // Adds acceptable scrolling delay
            wait(0.2);
        }
        sleep();
    }
    // Debouncing
    wait(0.2);
    g_buttonjoy_flag = 0;
    g_buttonA_flag = 0;
    // Returns -1 if B is pressed to automatically go back a menu
    if (g_buttonB_flag) {
        g_buttonB_flag = 0;
        select = -1;
    }
    return select;
}
// Draw Strings function used for plotting an array of stringList to the LCD
void drawStrings(const stringList* list,int lines)
{
    lcd.clear();
    for (int i=0; i<lines; i++) {
        lcd.printString(list[i].str,list[i].offset,i);
    }
}


/// Read default positions of the joystick to calibrate later readings
void calibrateJoystick()
{
    // 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()
{
    /// 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;

    // 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 = RIGHT;
    } else if ( joystick.x < DIRECTION_TOLERANCE && fabs(joystick.y) < DIRECTION_TOLERANCE) {
        joystick.direction = LEFT;
    } else {
        joystick.direction = UNKNOWN;
    }

    // set flag for printing
    g_joystick_flag = 1;
}