Wall dodging game utilising a joystick and Nokia 5110 LCD display

Dependencies:   N5110 mbed

Revision:
10:dd2886f3ac0a
Parent:
9:ca800196baeb
--- a/main.cpp	Sun Apr 24 16:17:55 2016 +0000
+++ b/main.cpp	Thu May 05 14:52:59 2016 +0000
@@ -1,430 +1,33 @@
-/* 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
-
-Week 23 Code    - Brief invincibility added by pressing joybutton
-                - invincibilty limit added, indicators added to the side
-                - game loops succesfully
-                - Menu added with 4 options
-                    - DodgeMILD, start the game easy with one wall
-                    - DodgeMANIA, start the game hard with all four walls
-                    - Help, get instructions on the game
-                    - Scores, see the top 3 highscore, will be used for saved scores if SD card is implemented
-
-NOTES   - top wall collision seems to be off
-        - horizontal walls still seem to go beyond the left border occasinanly
-
-*/
-
-#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 MUTE 0                      // 0 - buzzer plays, 1 - buzzer muted
-
-//         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
-PwmOut buzzer(PTA2);    // buzzer object
-InterruptIn flick (PTB3);   // interruptin instance of button
-
-Ticker pollJoystick;    // timer to regularly read the joystick
-Ticker menu_pollJoystick; // slower ticker to move joystick through menu
-Ticker game_timer;      // timer to regularly update the screen
-
-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
-typedef const struct State STyp;
-typedef struct Highscore Highscore;
-
-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 State {
-    int output;  // output value for current state
-    int next_state[3]; // next state (depending on direction 0 - UP, 1 - DOWN)
-};
-
-struct Highscore {
-    int one;
-    int two;
-    int three;
-};
-
-STyp fsm[4] = {
-    {1,{0,1,0}},
-    {2,{1,2,0}},
-    {4,{2,3,1}},
-    {8,{3,3,2}}
-};
-
-// struct variable for joystick
-Joystick joystick;
-// struct variable for moving walls
-Wall leftwall;
-Wall rightwall;
-Wall downwall;
-Wall upwall;
-
-Highscore hiscore;
-
-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 initGame();
-void moveBall();            // reads joystick direction and moves position of the player
-void moveWall();            // moves walls along the screen
-void invincible();             // makes player briefly invincible
-void checkWallCollision();      // checks for any collisions with wall
-void checkBorderCollision();           // checks for any collisions with border
-void updateScreen();        // refreshes the screen, redraws player and walls
-void warning();             // flashes screen when a new wall is ready to appear
-void initSerial();         // sets baud rate for serial
-void debug();               // prints for debug purposes
-void button_isr();
-void blink();               // function to make screen flash (do not put in isr unless breaking the loop)
-void printHelp();          // prints game help
-void printScores();
-void draw_border();
-void calculateHighscores();
-void initHiscores();
-void playNote(int freq, float time);
-
-//float refresh_rate = 20; // 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
-volatile int game_over_flag = 0;         // flag to signal game over
-volatile int g_button_flag = 0;
-int printFlag = 0;              // flag for printing
-volatile int game_start_flag = 0;        // flag to start the game
-int i;                     // x-coordinate value of player
-int j;                     // y-coordinate value of player
-int counter = 0;                // number of times code has looped
-volatile bool mortal = 1;
-int invun_cool = 0;
-int saves = 5;
-int joyspeed = 5;
-int score = 0;
-
-int direction;
-int state;
-int output;
+#include "main.h"
 
 int main()
 {
-    initSerial();
-    srand(time(NULL));      // generate seed for random number generation
-    calibrateJoystick();
-    initDisplay();
-    initHiscores();
-
-    wait (1.0);
-
-    flick.rise(&button_isr);
-    flick.mode(PullDown);
-
-    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);
-
-    blink();
+    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;
-
-        joyspeed = 10;
-        game_timer.detach();
-        game_timer.attach(&game_timer_isr,1.0/joyspeed);
-
-        pollJoystick.detach();
-        pollJoystick.attach(&updateJoystick,1.0/joyspeed);  // read joystick (JOYSPEED) times per second
-
-        state = 0;
-
-
-        while(game_start_flag == 0) // ticker interrupt
-            if (g_timer_flag) {
-                g_timer_flag = 0;  // clear flag
-                
-                if (joystick.direction == UP) {
-                    direction = 2;
-                } else if (joystick.direction == DOWN) {
-                    direction = 1;
-                } else {
-                    direction = 0;
-                }
-                
-                output = fsm[state].output;
-                state = fsm[state].next_state[direction];
-
-                lcd.clear();
-
-                lcd.printString("Dodgemild",12,1);
-                lcd.printString("DodgeMANIA!",12,2);
-                lcd.printString("Help",12,3);
-                lcd.printString("Scores",12,4);
-
-                if (output == 1) {
-                    lcd.drawCircle(6,11,2,1);
-                } else if (output == 2) {
-                    lcd.drawCircle(6,19,2,1);
-                } else if (output == 4) {
-                    lcd.drawCircle(6,27,2,1);
-                } else {
-                    lcd.drawCircle(6,35,2,1);
-                }
-                lcd.refresh();
-                
-                if (((direction == 1)&&(output != 8))||((direction == 2)&&(output != 1))) {
-                    playNote(NOTE_G2,0.05);
-                }
+        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
 
-                if (g_button_flag) {
-                    if (output == 1) {
-                        g_button_flag = 0;
-                        playNote(NOTE_B2,0.1);
-                        initGame();
-                        game_start_flag = 1;
-                    } else if (output == 2) {
-                        g_button_flag = 0;
-                        playNote(NOTE_B2,0.1);
-                        initGame();
-                        counter = 1501;
-                        game_start_flag = 1;
-                    } else if (output == 4) {
-                        g_button_flag = 0;
-                        playNote(NOTE_B2,0.1);
-                        blink();
-                        lcd.clear();
-                        printHelp();
-                    } else if (output == 8) {
-                        g_button_flag = 0;
-                        playNote(NOTE_B2,0.1);
-                        blink();
-                        lcd.clear();
-                        printScores();
-                    }
-                }
-            }
-        blink();
-        lcd.clear();
-// Draw game border
-        draw_border();
-// Countdown
-        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();
+        menu();                 // Brings up game menu (Help and Scores contained in menu() function, selecting a gameplay option moves on from menu)
 
-        joyspeed = 20;
-        game_timer.detach();
-        game_timer.attach(&game_timer_isr,1.0/joyspeed);
-        pollJoystick.detach();
-        pollJoystick.attach(&updateJoystick,1.0/joyspeed);
-
-        while (game_over_flag == 0) {
-
-            if ( g_timer_flag ) {  // ticker interrupt
-                g_timer_flag = 0;  // clear flag
-                moveWall();
-                moveBall();
-                invincible();
-                checkBorderCollision();
-                checkWallCollision();
-                updateScreen();
-                warning();
-                debug();
-
-                counter++; // increment counter each cycle (approx. 20 points a second)
-                score++; // this is seperate from counter for the purposes of keeping score 0 while all walls are present on DodgeMANIA
-
-                // wall cooldowns increased. N.B these are set to 0 when a wall finishes moving
-                leftwall.cooldown++;
-                rightwall.cooldown++;
-                downwall.cooldown++;
-                upwall.cooldown++;
-            }
-            sleep();
-        }
-
-        game_over_flag = 0;
-
-        blink();
-        playNote(NOTE_E4,0.1);
-        playNote(NOTE_C4,0.1);
-        playNote(NOTE_A3,0.1);
-        playNote(NOTE_A2,0.3);
-        wait(0.8);
-        lcd.clear();
-        wait(0.2);
-
-        calculateHighscores();
-
-        lcd.printString("Game Over!",14,0);
-        lcd.refresh();
-
-        wait(1.0);
-
-        lcd.printString("HiScore:",3,3);
-        lcd.printString("Score:",3,2);
+        setTickers(20);         // Calls ticker functions 20 times per second
 
-        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
-            lcd.printString ("Wow!",54,3);
-        }
-
-        lcd.refresh();
-
-        if (score >= hiscore.one) {
-            wait(1.0);
-            lcd.printString("HIGHSCORE!",13,5);
-            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 {
-            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);
-        }
-        lcd.refresh();
-        while(1) {
-            if (g_button_flag) {
-                g_button_flag = 0;
-                playNote(NOTE_E2,0.1);
-                blink();
-                lcd.clear();
-                break;
-            }
-        }
-        game_start_flag = 0;
-
-//return 0;
+        playGame();             // Begins gameplay
+        resultsScreen();        // Show final player score and previous highscore
     }
-
 }
 
-void moveBall()   // reads joystick direction and moves position of the player
+void moveBall()   // Reads joystick direction and moves position of the player
 {
     if (joystick.direction == UP) {
         j-=1;
@@ -449,50 +52,51 @@
     }
 }
 
-void moveWall()   // moves walls along the screen
+void moveWall()   // Moves walls along the screen
 {
-    leftwall.random = rand()%20;
+    // 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;
+    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
+    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
-            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
+    } 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) {
+    if (counter > 200) {    // After 10 seconds of gameplay
         if (rightwall.moveFlag == 1) {
-            rightwall.x+=1;
-            if (rightwall.x>80) {
+            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;
-                rightwall.x = 6;
+                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) {
@@ -509,23 +113,21 @@
     }
 
 // DOWN WALL
-    if (counter > 600) {
+    if (counter > 600) {    // After 30 seconds of gameplay
         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 (counter % 2 == 1) {             // horizontal walls move half the speed of vertical walls
+                downwall.y+=1;                  // move wall down
             }
-            if (downwall.y>44) {
+            if (downwall.y>44) {                // if wall goes off bottom edge (accounting for border)
                 downwall.moveFlag = 0;
             }
         } else {
             if (downwall.genFlag == 0) {
-                downwall.x = rand() % 58+13;
+                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 > 80) {
+                if (downwall.cooldown > 120) {  // 6s cooldown
                     downwall.moveFlag = 1;
                     downwall.genFlag = 0;
                 } else if ((downwall.random == 1)&&(upwall.cooldown > 60)) {
@@ -538,24 +140,22 @@
         }
     }
 
-// UP WALL
-    if (counter > 1500) {
+    // UP WALL
+    if (counter > 1200) {   // After 60 seconds of gameplay
         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 (counter % 2 == 1) {             // horizontal walls move half the speed of vertical walls
+                upwall.y-=1;                    // move wall up
             }
-            if (upwall.y<3) {
+            if (upwall.y<3) {                   // if wall goes off bottom edge (accounting for border)
                 upwall.moveFlag = 0;
             }
         } else {
             if (upwall.genFlag == 0) {
-                upwall.x = rand() % 58+13;
+                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 > 80) {
+                if (upwall.cooldown > 120) {    // 6s cooldown
                     upwall.moveFlag = 1;
                     upwall.genFlag = 0;
                 } else if ((upwall.random == 1)&&(downwall.cooldown > 60)) {
@@ -568,71 +168,75 @@
         }
     }
 }
-void checkBorderCollision()
+void checkBorderCollision()   // Checks if player has hit border
 {
     // if floor
     if ( j >= 47 - (PLAYERRADIUS+3)) {
-        j = 47 - (PLAYERRADIUS+3);
+        j = 47 - (PLAYERRADIUS+3);  // Forces player position
     }
 
     // if roof
     if ( j <= (PLAYERRADIUS+3)) {
-        j = (PLAYERRADIUS+3);
+        j = (PLAYERRADIUS+3);   // Forces player position
     }
 
     // if right wall
     if ( i >= 83 - (PLAYERRADIUS+3)) {
-        i = 83 - (PLAYERRADIUS+3);
+        i = 83 - (PLAYERRADIUS+3);  // Forces player position
     }
 
     // if left wall
     if ( i <= (PLAYERRADIUS+8)) {
-        i = (PLAYERRADIUS+8);
+        i = (PLAYERRADIUS+8);   // Forces player position
     }
 }
 
-void invincible()
+void invincible()   // Briefly allows player to move through walls
 {
     if (g_button_flag) {
         g_button_flag = 0;
-        if ((saves > 0) && (mortal == true)) { // saves are available and not currently used
-            invun_cool=0;
+        if ((saves > 0) && (mortal == true)) { // Invincibility is available and not currently used
+            saves_cool=0;
             mortal = false;
-            invun_cool++;
+            saves_cool++;
             saves--;
         }
     }
     if (mortal == false) {
-        invun_cool++;
-        if (invun_cool > 30) { // ticker called 20 times per second, therefore 1.5s of invincibility
+        saves_cool++;           // Countdown variable for invincibility
+        if (saves_cool > 30) {  // Ticker called 20 times per second, therefore 1.5s of invincibility
             mortal = true;
-            invun_cool=0;
+            saves_cool=0;       // Resets countdown variable
         }
     }
 }
 
-void checkWallCollision()   // checks for any collisions (i.e. player has hit a wall or side of the screen)
+void checkWallCollision()   // Checks for any collisions (i.e. player has hit a wall or side of the screen)
 {
-    if (mortal) {
+    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_over_flag = 1;
+            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_over_flag = 1;
+            game_running = 0;
         }
 
         // 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;
+        //  (- - - - - - - 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 (((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;
+        //  (- - - - - - - 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;
         }
     }
 }
@@ -643,12 +247,12 @@
     if (mortal) {
         lcd.drawCircle(i,j,PLAYERRADIUS,1);
     } else {
-        if (counter % 2 == 0) {
+        if (counter % 2 == 0) { // Make player blink if invincible
             lcd.drawCircle(i,j,PLAYERRADIUS,1);
         }
     }
-    lcd.refresh();  // update display
 
+    // Draws remaining invincibilty indicators
     if (saves > 0) {
         lcd.drawCircle(2,7,1,1);
     }
@@ -665,11 +269,10 @@
         lcd.drawCircle(2,39,1,1);
     }
 
-
-    // draw Border
+    // Draw border
     draw_border();
 
-    // draw walls
+    // 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);
@@ -677,7 +280,7 @@
     lcd.drawLine(leftwall.x+1,leftwall.y-5,leftwall.x+1,3,1);
 
     // RIGHT WALL
-    if (counter > 200) {
+    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);
@@ -685,26 +288,22 @@
     }
 
     // DOWN WALL
-    if (counter > 600) {
+    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 > 1500) {
+    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);
     }
 
-    lcd.refresh();
-}
-
-void warning ()
-{
+    // Flash screen if a wall is about to appear from a new direction
     if (counter == 170) {
         lcd.inverseMode();
     } else if (counter == 570) {
@@ -714,14 +313,15 @@
     } else {
         lcd.normalMode();
     }
+    lcd.refresh();
 }
 
-void game_timer_isr()   // sets flag for timer interrupt
+void gameTicker_isr()   // Sets flag for timer interrupt
 {
     g_timer_flag = 1;
 }
 
-void button_isr()
+void button_isr()   // Sets flag for button interrupt
 {
     g_button_flag = 1;
 }
@@ -729,27 +329,28 @@
 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
+    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
+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)
+    // 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
+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)
+    // 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
+    // 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
+    // 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) {
@@ -773,54 +374,40 @@
     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;
-        serial.printf("saves = %d \n",saves);
-    }
-}
-
-void initSerial()   // sets baud rate for serial
+void initSerial()   // Sets baud rate for serial
 {
     serial.baud(115200);
 }
-void initGame()
+void initGame()   // Initialises gameplay variables
 {
-
     g_button_flag = 0;
     leftwall.y = rand() % 27+8;
-    leftwall.x = 0;
+    leftwall.x = 82;
     rightwall.y = rand() % 27+8;
     rightwall.x = 6;
-    downwall.x = rand() % 58+13;
+    downwall.x = rand() % 52+19;
     downwall.y = 0;
-    upwall.x = rand() % 58+13;
+    upwall.x = rand() % 52+19;
     upwall.y = 0;
     rightwall.cooldown = 61;
     counter = 0;
     score = 0;
-
     saves = 5;
-
     i = 42;
     j = 24;
 
 }
 
-void blink()   // command for brief flash
+void flash()    // Produces a brief flash on screen
 {
     lcd.inverseMode();
     wait(0.2);
     lcd.normalMode();
 }
 
-void printHelp()
+void printHelp()    // Prints instructions
 {
     while (1) {
         lcd.printString("Try and",0,1);
@@ -829,9 +416,8 @@
         lcd.printString("possible!",0,4);
         lcd.refresh();
         if (g_button_flag) {
-            //g_button_flag = 0;
             playNote(NOTE_E2,0.1);
-            blink();
+            flash();
             lcd.clear();
             break;
         }
@@ -844,9 +430,8 @@
         lcd.printString("the joystick",0,4);
         lcd.refresh();
         if (g_button_flag) {
-            //g_button_flag = 0;
             playNote(NOTE_E2,0.1);
-            blink();
+            flash();
             lcd.clear();
             break;
         }
@@ -859,9 +444,8 @@
         lcd.printString("untouchable",0,4);
         lcd.refresh();
         if (g_button_flag) {
-            // g_button_flag = 0;
             playNote(NOTE_E2,0.1);
-            blink();
+            flash();
             lcd.clear();
             break;
         }
@@ -874,9 +458,8 @@
         lcd.printString("do it 5 times",0,4);
         lcd.refresh();
         if (g_button_flag) {
-            //  g_button_flag = 0;
             playNote(NOTE_E2,0.1);
-            blink();
+            flash();
             lcd.clear();
             break;
         }
@@ -889,9 +472,8 @@
         lcd.printString("the screen...",0,4);
         lcd.refresh();
         if (g_button_flag) {
-            //  g_button_flag = 0;
             playNote(NOTE_E2,0.1);
-            blink();
+            flash();
             lcd.clear();
             break;
         }
@@ -906,14 +488,14 @@
         if (g_button_flag) {
             g_button_flag = 0;
             playNote(NOTE_E2,0.1);
-            blink();
+            flash();
             lcd.clear();
             break;
         }
     }
 }
 
-void printScores()
+void printScores()   // Prints previous scores
 {
     while(1) {
         lcd.printString("Highscores",13,0);
@@ -936,14 +518,14 @@
         if (g_button_flag) {
             g_button_flag = 0;
             playNote(NOTE_E2,0.1);
-            blink();
+            flash();
             lcd.clear();
             break;
         }
     }
 }
 
-void draw_border()   // draws game border
+void draw_border()   // Draws game border
 {
     lcd.drawLine(7,2,81,2,1);
     lcd.drawLine(7,2,7,45,1);
@@ -955,10 +537,15 @@
     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()
+void calculateHighscores()   // Determines if player score is a new highscore
 {
     if (score > hiscore.one) {
         hiscore.three = hiscore.two;
@@ -972,22 +559,307 @@
     }
 }
 
-void initHiscores()
+void initHiscores()   // Initialises highscores
 {
     hiscore.one = 0;
     hiscore.two = 0;
     hiscore.three = 0;
 }
 
-void playNote(int freq, float time)
+void playNote(int freq, float time)   // Plays a tone of specific frequency and duration
 {
-    if(!MUTE) { // no sound if MUTE
-        buzzer.period(1.0/freq); // 1 KHz
+    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 {
-        wait(time); // wait included so that function still takes the same time to execute
+        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;
+        }
     }
 }
\ No newline at end of file