Space Invaders - Embedded Systems Project 15/16 - Avinash Patel 200860407

Dependencies:   Joystick N5110 SDFileSystem mbed

main.cpp

Committer:
avi23
Date:
2016-05-05
Revision:
9:cbb982b7e353
Parent:
8:b2faec20ed8f
Child:
11:eab3fb334bde

File content as of revision 9:cbb982b7e353:

/**
@file main.cpp
@brief Implementation File
*/

/*
Space Invaders - Avinash Patel 200860407

Week 19 - Set up joystick class
Week 20 - Changed to space invaders as constrained too much by screen resolution
        - Core cannon is drawn and can move, enemies are visible and they switch between states every second
Week 21 - Begun to set up barriers
Easter  - Barriers work, invader and player can shoot. Begun setting up menus
Week 22 - Most menus work
Week 23 - Menus work
*/
#include "mbed.h"
#include "main.h"

int main()
{
    ///Wait for 2 seconds to allow power to settle
    wait(1);
    ///Initalises the board and perhiperals
    init_K64F();
    init_serial();
    init_shoot();
    init_rng();
    joystick.init();
    lcd.init();
    lcd.clear();

    LoadHighScores();

    ///Configures the function pointer for the invaders normal missile
    move_invader_normal_missile_isr[0] = &move_invader_normal_missile_0_isr;
    move_invader_normal_missile_isr[1] = &move_invader_normal_missile_1_isr;

    ///Samples joystick every 0.05 second
    move_joystick.attach(&move_joystick_isr, 0.05);

    ///Splash Screen
    SplashScreen();

    while (true) {
        if (game_state == menu) { ///Menu screen
            lcd.clear();
            fsm_state = 0; ///Sets the fsm state to 0
            MenuScreen();
        } else if (game_state == paused) { ///Paused screen
            ///Clears the screen
            lcd.clear();
            fsm_state = 0; ///Ssets the fsm state to 0
            PauseScreen();
        } else if (game_state == game) { ///Game screen
            AttachTickers();
            Game();
        } else if (game_state == save) {
            SaveGameData();
        } else if (game_state == load) {
            LoadGameData();
        } else if (game_state == over) {
            lcd.clear();
            GameOverScreen();            
        } else if (game_state == scores) {
            lcd.clear();
            ScoreScreen();               
        } else if (game_state == settings) {
            lcd.clear();
            fsm_state = 0;
            SettingsScreen();
        }

        sleep();
    }
}

void init_K64F()
{
    /// on-board LEDs are active-low, so set pin high to turn them off.
    r_led = 1;
    g_led = 1;
    b_led = 1;

    /// since the on-board switches have external pull-ups, we should disable the internal pull-down
    /// resistors that are enabled by default using InterruptIn
    sw2.mode(PullNone);
    sw3.mode(PullNone);
}

void error()
{
    while(1) {  /// if error, hang while flashing error message
        r_led = 0;
        wait(0.2);
        r_led = 1;
        wait(0.2);
    }
}

void init_serial()
{
    /// set to highest baud - ensure terminal software matches
    pc.baud(115200);
}

///Seeds the random number generator with noise from an analog in pin
void init_rng()
{
    AnalogIn rng_seed(PTC10); ///Creates a AnalogIn on a unused pin
    srand(floor(10000*rng_seed.read())); ///Sets the seed as 10000x the input of the analog in
}

void init_shoot()
{
    shoot_button.mode(PullUp);
    shoot_button.fall(&shoot_pressed_isr);
    shoot_button.rise(&shoot_pressed_rise_isr);
}

void update_screen_isr()
{
    g_update_screen_flag = true;
}

void move_enemies_isr()
{
    g_move_enemies_flag = true;
}

void shoot_pressed_isr()
{
    ///Only sets the shoot pressed flag 0.1s after the last press
    if (!g_shoot_button_debounce_flag) {
        g_shoot_pressed_flag = true;
        g_shoot_button_debounce_flag = true;
    }
}

void move_cannon_missile_isr()
{
    g_move_cannon_missile_flag = true;
}

void move_joystick_isr()
{
    ///Always set the move flag in a game
    if (game_state == game) {
        g_move_joystick_flag = true;
    } else if (!g_joystick_cursor_regulator_flag) {
        ///Only sets the flag if the regulator is not set
        g_move_joystick_flag = true;
        g_joystick_cursor_regulator_flag = true;

        ///Attachs a timeout to clear the regulator in 0.1s to prevent the cursor from behaving erratically
        joystick_cursor_regulator.attach(&joystick_cursor_regulator_isr, 0.1);
    }
}

void joystick_cursor_regulator_isr()
{
    g_joystick_cursor_regulator_flag = false;
}

void shoot_button_debounce_isr()
{
    g_shoot_button_debounce_flag = false;
}

void move_invader_normal_missile_0_isr()
{
    g_move_invader_normal_missile_flag[0] = true;
}

void move_invader_normal_missile_1_isr()
{
    g_move_invader_normal_missile_flag[1] = true;
}

void cannon_hit_isr()
{
    g_cannon_hit_flag = false;
}

void move_ufo_isr()
{
    g_move_ufo_flag = true;
}

void fire_buzzer_isr()
{
    g_fire_buzzer_flag = true;
}

void lp_wait_isr()
{
    g_lp_wait_flag = false;
}

void shoot_pressed_rise_isr()
{
    ///Attaches a timeout to clear the debounce flag in 0.125 seconds
    shoot_button_debounce.attach(&shoot_button_debounce_isr, 0.125);
}

void Game()
{
    ///Stays within the loop while the selected state is game
    while (game_state == game) {
        pc.printf("Mute: %d\n", is_muted);
        ///If the game is over detach all the tickers and set game state to over
        if (number_of_lives == 0) {
            DetachTickers();
            game_state = over;
        } else if (no_of_alive_invaders == 0) { ///If the player wins a round
            ///Resets the no of alive invaders
            no_of_alive_invaders = 15;
            down_count = 0;
            ///Detaches the enemy ticker while reinitalising invaders
            move_enemies.detach();
            ///Reinitalises objects
            InitSmallInvaders();
            InitMediumInvaders();
            InitLargeInvaders();
            ///Reattaches enemy ticker
            move_enemies.attach(&move_enemies_isr, 1);
        } else {
            ///Updates pixels on the screen
            if (g_update_screen_flag) {
                g_update_screen_flag = false;

                UpdateScreen();
            }

            ///Controls cannon movement
            if (g_move_joystick_flag) {
                g_move_joystick_flag = false;

                MoveCannon();
                DrawBarriers();
            }

            ///Controls enemy movement
            if (g_move_enemies_flag) {
                g_move_enemies_flag = false;

                ///Increses the speed the invaders move
                move_enemies.detach();
                ticker_period = 0.1+(no_of_alive_invaders*0.06);
                move_enemies.attach(&move_enemies_isr, ticker_period);

                //Attach the buzzer timeout if mute is unselected
                if (!is_muted) {
                    fire_buzzer.attach(&fire_buzzer_isr, ticker_period/2.0f);
                }

                ///Clears the old bitmaps
                ClearSmallInvaders();
                ClearMediumInvaders();
                ClearLargeInvaders();

                MoveInvaderXPositions();
                ///Switches the bitmap state
                invaders_in_state2 = !invaders_in_state2;

                ///Draws the invaders
                DrawSmallInvaders();
                DrawMediumInvaders();
                DrawLargeInvaders();

                ///Attempts to fire the invaders missiles
                AttemptToFireInvaderNormalMissiles();

                ///Attempts to spawn UFO
                AttemptToSpawnUFO();
            }

            if (g_fire_buzzer_flag && !is_muted) {
                g_fire_buzzer_flag = false;

                PlayBuzzer();
            }

            ///Spawns a player bullet if the shoot button is pressed and there isn't a bullet on the screen
            if (g_shoot_pressed_flag) {
                g_shoot_pressed_flag = false;

                if (!cannon_missile_on_screen) {
                    FireCannonMissile();

                    ///Counts fired missiles to see if the ufo bonus can be applied
                    if (ufo_bonus && shot_count > 14) {
                        shot_count = 0;
                    } else if (shot_count > 22) {
                        shot_count = 0;
                    } else {
                        ++shot_count;
                    }
                }
            }

            ///Move the cannon shot
            if (g_move_cannon_missile_flag) {
                g_move_cannon_missile_flag = false;

                MoveCannonMissile();
            }

            ///Moves the invaders 1st normal missile
            if (g_move_invader_normal_missile_flag[0]) {
                g_move_invader_normal_missile_flag[0] = false;

                MoveInvaderNormalMissile(0);
            }

            ///Moves the invaders 2nd normal missile
            if (g_move_invader_normal_missile_flag[1]) {
                g_move_invader_normal_missile_flag[1] = false;

                MoveInvaderNormalMissile(1);
            }

            ///Moves the UFO
            if (g_move_ufo_flag) {
                g_move_ufo_flag = false;

                ///Clears the UFO
                ClearUFO();

                ///Moves the UFO in the correct direction
                if (ufo_direction == RIGHT) {
                    ufo_x_pos += 2;
                } else {
                    ufo_x_pos -= 2;
                }

                ///Draws the UFO
                DrawUFO();
            }

            if (joystick.get_button_flag()) {
                joystick.set_button_flag(0);

                ///Detach all game tickers and turn off PWM
                DetachTickers();
                buzzer.write(0.0);
                game_state = paused;
            }
        }

        sleep();
    }
}

void InitaliseGame()
{
    ///Clears the screen buffer and runs init functions
    memset(screen_buffer, 0, sizeof(screen_buffer));
    no_of_alive_invaders = 15;
    score = 0;
    number_of_lives = 3;
    down_count = 0;
    cannon_xpos = 13;
    ufo_on_screen = false;
    ufo_bonus = false;
    shot_count = 0;
    InitSmallInvaders();
    InitMediumInvaders();
    InitLargeInvaders();
    InitBarriers();
    ///If the sound is set activate the buzzer flag
    if (!is_muted) {
        g_fire_buzzer_flag = true;
    }
    ///Sets the flags so enemies pop up straight away
    g_update_screen_flag = true;
    g_move_joystick_flag = true;
    g_move_enemies_flag = true;
    g_fire_buzzer_flag = true;
    ///Forces the missiles to have the fired flags to flase
    cannon_missile_on_screen = false;
    invader_normal_missile[0].fired = false;
    invader_normal_missile[1].fired = false;
}

void UpdateScreen()
{
    ///Loops through the screen buffer and sets pixels on the LCD
    for (int col = 0; col < 84; ++col) {
        for (int row = 0; row < 48; ++row) {
            if (screen_buffer[col][row]) {
                lcd.setPixel(col, row);
            } else {
                lcd.clearPixel(col, row);
            }
        }
    }

    lcd.refresh();
}

void MoveCannon()
{
    ///Clears the cannon
    for (int col = 0; col < 9; ++col) {
        for (int row = 0; row < 5; ++row) {
            if(cannon_bitmap[row][col]) {
                screen_buffer[cannon_xpos+col][cannon_ypos+row] = empty_pixel;
            }
        }
    }

    ///Changes the position of the cannon when the joystick is moved, capping at 0 and 75 so it always fits on the screen
    if (joystick.GetXValue() < 0.25f) {
        cannon_xpos--;
        if (cannon_xpos < 0) {
            cannon_xpos = 0;
        }
    } else if (joystick.GetXValue() > 0.75f) {
        cannon_xpos++;
        if (cannon_xpos > 75) {
            cannon_xpos = 75;
        }
    }

    ///Redraws the cannon
    for (int col = 0; col < 9; ++col) {
        for (int row = 0; row < 5; ++row) {
            if(cannon_bitmap[row][col]) {
                screen_buffer[cannon_xpos+col][cannon_ypos+row] = cannon_pixel;
            }
        }
    }
}

///Sets the position and status of the small invaders
void InitSmallInvaders()
{
    for (int i = 0; i < 5; ++i) {
        small_invader[i].x_pos = 2 + (i*13); /// Large invaders are 12 across so add 13 for a gap space
        small_invader[i].y_pos = 1;
        small_invader[i].status = alive;
    }
}

///Cycles through all the small invaders. If they're not already dead clear them
void ClearSmallInvaders()
{
    for (int i = 0; i < 5; ++i) {
        if (small_invader[i].status) {
            ClearSingleSmallInvader(i);
        }
    }
}

///Cycles through the the screen invader bitmap and sets the pixels in the buffer to 0
void ClearSingleSmallInvader(int invader_no)
{
    for (int col = 0; col < 8; ++col) {
        for (int row = 0; row < 6; ++row) {
            if (invaders_in_state2) {
                if (small_invader_bitmap_1[row][col]) {
                    screen_buffer[small_invader[invader_no].x_pos + col][small_invader[invader_no].y_pos + row] = empty_pixel;
                }
            } else {
                if (small_invader_bitmap_2[row][col]) {
                    screen_buffer[small_invader[invader_no].x_pos + col][small_invader[invader_no].y_pos + row] = empty_pixel;
                }
            }
        }
    }

    small_invader[invader_no].status = (small_invader[invader_no].status == dying) ? dead : alive;
}

///Cycles through all the small invaders. If they're alive set them in the screen buffer
void DrawSmallInvaders()
{
    for (int i = 0; i < 5; ++i) {
        if (small_invader[i].status == alive) {
            DrawSingleSmallInvader(i);
        }
    }
}

///Cycles through the the screen invader bitmap and sets the pixels in the buffer
void DrawSingleSmallInvader(int invader_no)
{
    for (int col = 0; col < 8; ++col) {
        for (int row = 0; row < 6; ++row) {
            if (invaders_in_state2) {
                if (small_invader_bitmap_1[row][col]) {
                    screen_buffer[small_invader[invader_no].x_pos + col][small_invader[invader_no].y_pos + row] = first_small_invader_pixel + invader_no;
                }
            } else {
                if (small_invader_bitmap_2[row][col]) {
                    screen_buffer[small_invader[invader_no].x_pos + col][small_invader[invader_no].y_pos + row] = first_small_invader_pixel + invader_no;
                }
            }
        }
    }
}

///Sets the position and aliveness of the medium invaders
void InitMediumInvaders()
{
    for (int i = 0; i < 5; ++i) {
        medium_invader[i].x_pos = 1 + (i*13); /// Large invaders are 12 across so add 13 for a gap space
        medium_invader[i].y_pos = 8;
        medium_invader[i].status = alive;
    }
}

///Cycles through all the medium invaders. If they're not already dead clear them
void ClearMediumInvaders()
{
    for (int i = 0; i < 5; ++i) {
        if (medium_invader[i].status) {
            ClearSingleMediumInvader(i);
        }
    }
}

///Cycles through the the screen invader bitmap and sets the pixels in the buffer to 0
void ClearSingleMediumInvader(int invader_no)
{
    for (int col = 0; col < 10; ++col) {
        for (int row = 0; row < 6; ++row) {
            if (invaders_in_state2) {
                if (medium_invader_bitmap_1[row][col]) {
                    screen_buffer[medium_invader[invader_no].x_pos + col][medium_invader[invader_no].y_pos + row] = empty_pixel;
                }
            } else {
                if (medium_invader_bitmap_2[row][col]) {
                    screen_buffer[medium_invader[invader_no].x_pos + col][medium_invader[invader_no].y_pos + row] = empty_pixel;
                }
            }
        }
    }

    medium_invader[invader_no].status = (medium_invader[invader_no].status == dying) ? dead : alive;
}

///Cycles through all the medium invaders. If they're alive set them in the screen buffer
void DrawMediumInvaders()
{
    for (int i = 0; i < 5; ++i) {
        if (medium_invader[i].status == alive) {
            DrawSingleMediumInvader(i);
        }
    }
}

///Cycles through the the screen invader bitmap and sets the pixels in the buffer
void DrawSingleMediumInvader(int invader_no)
{
    for (int col = 0; col < 10; ++col) {
        for (int row = 0; row < 6; ++row) {
            if (invaders_in_state2) {
                if (medium_invader_bitmap_1[row][col]) {
                    screen_buffer[medium_invader[invader_no].x_pos + col][medium_invader[invader_no].y_pos + row] = first_medium_invader_pixel + invader_no;
                }
            } else {
                if (medium_invader_bitmap_2[row][col]) {
                    screen_buffer[medium_invader[invader_no].x_pos + col][medium_invader[invader_no].y_pos + row] = first_medium_invader_pixel + invader_no;
                }
            }
        }
    }
}

///Sets the position and status of the large invaders
void InitLargeInvaders()
{
    for (int i = 0; i < 5; ++i) {
        large_invader[i].x_pos = 0 + (i*13);
        large_invader[i].y_pos = 15;
        large_invader[i].status = alive;
    }
}

///Cycles through all the large invaders. If they're not already dead clear them
void ClearLargeInvaders()
{
    for (int i = 0; i < 5; ++i) {
        if (large_invader[i].status) {
            ClearSingleLargeInvader(i);
        }
    }
}

///Loops through the large invader bitmap, if the pixel in the bitmap is set to 1, set the pixel in the buffer to 0
void ClearSingleLargeInvader(int invader_no)
{
    for (int col = 0; col < 12; ++col) {
        for (int row = 0; row < 6; ++row) {
            if (invaders_in_state2) {
                if (large_invader_bitmap_1[row][col]) {
                    screen_buffer[large_invader[invader_no].x_pos + col][large_invader[invader_no].y_pos + row] = empty_pixel;
                }
            } else {
                if (large_invader_bitmap_2[row][col]) {
                    screen_buffer[large_invader[invader_no].x_pos + col][large_invader[invader_no].y_pos + row] = empty_pixel;
                }
            }
        }
    }

    large_invader[invader_no].status = (large_invader[invader_no].status == dying) ? dead : alive;
}

///Cycles through all the large invaders. If they're alive set them in the screen buffer
void DrawLargeInvaders()
{
    for (int i = 0; i < 5; ++i) {
        if (large_invader[i].status == alive) {
            DrawSingleLargeInvader(i);
        }
    }
}

///Cycles through the the screen invader bitmap and sets the pixels in the buffer
void DrawSingleLargeInvader(int invader_no)
{
    for (int col = 0; col < 12; ++col) {
        for (int row = 0; row < 6; ++row) {
            if (invaders_in_state2) {
                if (large_invader_bitmap_1[row][col]) {
                    screen_buffer[large_invader[invader_no].x_pos + col][large_invader[invader_no].y_pos + row] = first_large_invader_pixel + invader_no;
                }
            } else {
                if (large_invader_bitmap_2[row][col]) {
                    screen_buffer[large_invader[invader_no].x_pos + col][large_invader[invader_no].y_pos + row] = first_large_invader_pixel + invader_no;
                }
            }
        }
    }
}

///Sets the position and loads the bitmap into the barrier objects
void InitBarriers()
{
    for (int i = 0; i < 3; ++i) {
        barrier[i].x_pos = 10 + (i*25);
        barrier[i].y_pos = 33;
        ///Copies the bitmap into the structs
        memcpy(barrier[i].before_bitmap, barrier_bitmap, sizeof(barrier_bitmap));
        memcpy(barrier[i].after_bitmap, barrier_bitmap, sizeof(barrier_bitmap));
    }
}

void DrawBarriers()
{
    ///Clears the barrier and redraws it with damage applied
    for (int i = 0; i < 3; ++i) {
        for (int col = 0; col < 14; ++col) {
            for (int row = 0; row < 8; ++row) {
                if (barrier[i].before_bitmap[row][col]) {
                    screen_buffer[barrier[i].x_pos + col][barrier[i].y_pos + row] = empty_pixel;
                }
                if (barrier[i].after_bitmap[row][col]) {
                    screen_buffer[barrier[i].x_pos + col][barrier[i].y_pos + row] = first_barrier_pixel + i;
                }
            }
        }
        ///Copies the after array to the before array
        memcpy(barrier[i].before_bitmap, barrier[i].after_bitmap, sizeof(barrier[i].after_bitmap));
    }
}

void MoveInvaderXPositions()
{
    ///Moves the invader in the current direction
    if (invader_direction == RIGHT) {
        ///Checking the right limit
        int right_invader_limit = CalculateInvaderRightLimit();
        MoveInvadersRight(right_invader_limit);
    } else {
        ///Checks the left limit
        int left_invader_limit = CalculateInvaderLeftLimit();
        MoveInvadersLeft(left_invader_limit);
    }
}

///Checks the status off the invaders per column, starting from the left
///If they're alive return the row number
int CalculateInvaderLeftLimit()
{
    for (int i = 0; i < 5; ++i) {
        if (small_invader[i].status == alive || medium_invader[i].status == alive || large_invader[i].status == alive) {
            return i;
        }
    }

    ///Sort gameover out stuff after
    return 4;
}

///Checks the status off the invaders per column, starting from the right
///If they're alive return the row number
int CalculateInvaderRightLimit()
{
    for (int i = 4; i >= 0; --i) {
        if (small_invader[i].status == alive || medium_invader[i].status == alive || large_invader[i].status == alive) {
            return i;
        }
    }

    ///Sort gameover stuff
    return 0;
}

void MoveInvadersLeft(int limit)
{
    ///Checks the first large invader to see if it can travel anymore
    if (large_invader[limit].x_pos > 1) {
        for (int i = 0; i < 5; ++i) {
            ///Moves the invaders 2 to the left
            small_invader[i].x_pos -= 2;
            medium_invader[i].x_pos -= 2;
            large_invader[i].x_pos -= 2;
        }
    } else {
        ///Shifts the Invaders down and passes in the new direction
        MoveInvaderYPositions(RIGHT);
    }
}

void MoveInvadersRight(int limit)
{
    ///Checks the first large invader to see if it can travel anymore
    if (large_invader[limit].x_pos < 71) {
        ///Moves the invaders 2 to the right
        for (int i = 0; i < 5; ++i) {
            small_invader[i].x_pos += 2;
            medium_invader[i].x_pos += 2;
            large_invader[i].x_pos += 2;
        }
    } else {
        ///Shifts the Invaders down and passes in the new direction
        MoveInvaderYPositions(LEFT);
    }
}

void MoveInvaderYPositions(bool new_direction)
{
    ///Finds the invaders lower limit
    Invader lowest_invader = CalculateInvaderYLimit();

    ///When moving down lowest_invader should not equal none
    if (lowest_invader == none) {
        error();
    }

    ///If an invader touches the bottom the game ends, otherwise the invaders descend
    if (small_invader[0].y_pos < 33 - (7*lowest_invader)) {
        for (int i = 0; i < 5; ++i) {
            small_invader[i].y_pos += 3;
            medium_invader[i].y_pos += 3;
            large_invader[i].y_pos += 3;
        }
        ++down_count;
        invader_direction = new_direction;
    } else {
        number_of_lives = 0;
    }
}

Invader CalculateInvaderYLimit()
{
    ///Checks to see which row of invaders are still alive to work out maximum y positions
    if (large_invader[0].status == alive || large_invader[1].status == alive || large_invader[2].status == alive || large_invader[3].status == alive || large_invader[4].status == alive) {
        return large;
    } else if (medium_invader[0].status == alive || medium_invader[1].status == alive || medium_invader[2].status == alive || medium_invader[3].status == alive || medium_invader[4].status == alive) {
        return medium;
    } else {
        return small;
    }
}

///Queries the invaders on their status and returns the type of invader which is alive
Invader LowestInvaderInColumn(int column)
{
    if (large_invader[column].status == alive) {
        return large;
    } else if (medium_invader[column].status == alive) {
        return medium;
    } else if (small_invader[column].status == alive) {
        return small;
    } else {
        return none;
    }
}

void FireCannonMissile()
{
    ///Sets the cannon fired flag to true
    cannon_missile_on_screen = true;

    ///Offset cannon missile x position by 4 of the cannons x pos
    cannon_missile_x_pos = cannon_xpos + 4;
    ///Will always have a starting y of 40
    cannon_missile_y_pos = 40;
    ///Attach the move cannon missile
    move_cannon_missile.attach(&move_cannon_missile_isr, 0.05);
}

void MoveCannonMissile()
{
    ///Checks bullet will not go beyond the bounds of the screen buffer
    if (cannon_missile_y_pos > -1) {
        ///Loops throught the shot bitmap and clears the pixels in the screen buffer
        for (int row = 0; row < 4; ++row) {
            ///Clears the position where the bullet was
            screen_buffer[cannon_missile_x_pos][cannon_missile_y_pos + row] = empty_pixel;
        }

        ///Increments the shot going up the screen
        --cannon_missile_y_pos;

        ///Checks to see if the shot will hit anything
        CollisionDetectionCannonMissile();
    } else {
        ///Loops throught the shot bitmap and clears the pixels in the screen buffer
        for (int row = 1; row < 4; ++row) {
            ///Clears the position where the bullet was
            screen_buffer[cannon_missile_x_pos][cannon_missile_y_pos + row] = empty_pixel;
        }

        cannon_missile_on_screen = false;
        move_cannon_missile.detach();
    }
}

///Checks to see what the shot hits. If it hits nothing the shot gets pushed to the screen buffer
void CollisionDetectionCannonMissile()
{
    for (int row = 0; row < 4; ++row) {
        int object_no;
        int pixel_check = screen_buffer[cannon_missile_x_pos][cannon_missile_y_pos + row];
        if (pixel_check >= first_small_invader_pixel && pixel_check < ufo_pixel) { ///Collides with a small invader
            ///Find the object no of the small invader it hit, clears it and increments the score
            object_no = CannonMissileHitInvader(first_small_invader_pixel, row, small_invader);
            ClearSingleSmallInvader(object_no);
            score += 40;
            break;
        } else if (pixel_check >= first_medium_invader_pixel && pixel_check < first_small_invader_pixel) { ///Collides with a medium invader
            ///Find the object no of the medium invader it hit, clears it and increments the score
            object_no = CannonMissileHitInvader(first_medium_invader_pixel, row, medium_invader);
            ClearSingleMediumInvader(object_no);
            score += 20;
            break;
        } else if (pixel_check >= first_large_invader_pixel && pixel_check < first_medium_invader_pixel) { ///Collides with a large invader
            ///Find the object no of the large invader it hit, clears it and increments the score
            object_no = CannonMissileHitInvader(first_large_invader_pixel, row, large_invader);
            ClearSingleLargeInvader(object_no);
            score += 10;
            break;
        } else if (pixel_check >= first_barrier_pixel && pixel_check < (first_barrier_pixel + 3)) { ///Collides with a barrier
            ///Adds the destruction done to the barrier to the bitmap and redraws the barriers
            CannonMissileHitBarrier(row);
            DrawBarriers();
            break;
        } else if (pixel_check == ufo_pixel) { ///Collides with a UFO
            ///Clears the UFO, calculates score depending on the ufo bonus
            ClearUFO();
            ufo_on_screen = false;
            move_ufo.detach();
            if (ufo_bonus && shot_count == 15) { ///If the shot bonus is active and on the 15th shot, give 300 points
                score += 300;
            } else if (shot_count == 23) { ///If the shot bonus isn't active and on the 23rd shot, give 300 points and activate the bonus
            ufo_bonus = true;
            score += 300;
            } else { ///Randomly assign score between 50, 100, 150 points
                score += 50 * ((rand() % 3) + 1);
            }
            cannon_missile_on_screen = false;
            move_cannon_missile.detach();
            break;
        } else {
            screen_buffer[cannon_missile_x_pos][cannon_missile_y_pos + row] = cannon_missile_pixel;
        }
    }
}

///Finds the invader number the missile hits, sets the hit invader to dying, decrements the no_of_alive_invaders and stops the shot from travalling up the screen
int CannonMissileHitInvader(int first_pixel, int row, struct Invaders (&invader)[5])
{
    int invader_no = screen_buffer[cannon_missile_x_pos][cannon_missile_y_pos + row] - first_pixel;
    invader[invader_no].status = dying;
    cannon_missile_on_screen = false;
    --no_of_alive_invaders;
    move_cannon_missile.detach();

    return invader_no;
}

///Calculates where to start drawing the damage bitmap over the barrier bitmap and performs the operation
void CannonMissileHitBarrier(int row)
{
    int barrier_no = screen_buffer[cannon_missile_x_pos][cannon_missile_y_pos + row] - first_barrier_pixel;
    ///Essentially inverse of barrier init
    int relative_x_pos = cannon_missile_x_pos - 10 - (barrier_no*25) - 1;
    int relative_y_pos = (cannon_missile_y_pos + row) - 33 - 1; ///Don't know why it's -1 and not -2
    ///Loops through the damage bitmap and modifies the barrier's after bitmap
    for (int col = 0; col < 3; ++col) {
        for (int row_bit = 0; row_bit < 3; ++row_bit) {
            ///Makes sure bitmap index does not go out of bounds. If it does go to the next iteration
            ///Element by element multiplication of the 2 bitmaps to clear the required pixals
            if (relative_x_pos + col >= 0 && relative_x_pos + col < 14 && relative_y_pos + row_bit >= 0 && relative_y_pos + row_bit < 8) {
                barrier[barrier_no].after_bitmap[relative_y_pos + row_bit][relative_x_pos + col] *= barrier_cannon_missile_damage_bitmap[row_bit][col];
            }
        }
    }
    cannon_missile_on_screen = false;
    move_cannon_missile.detach();
}

void AttemptToFireInvaderNormalMissiles()
{
    ///Fires the normal missiles
    ///Loops through the 2 allowed missiles and if they're not on the screen, randomly fire
    for (int i = 0; i < 2; ++i) {
        if (!invader_normal_missile[i].fired) {
            ///If the random mumber is 1, fire a missile
            ///Higher chance when there are less invaders on the screen, up to a 1/3 chance
            if ((rand() % (no_of_alive_invaders + 2)) == 1) {
                int fired_column;
                int loop_limit = 0; ///Stops loop from never ending
                Invader missile_source;
                do {
                    fired_column = rand() % 5;
                    missile_source = LowestInvaderInColumn(fired_column);
                    ++loop_limit;
                } while (missile_source == none && loop_limit < 10);

                ///Finds the centre point of the chosen invader and fires the missile
                ///If the loop limit is reached, the missile source will be none and the for loop will jump to the next iteration
                if (missile_source == none) {
                    continue;
                } else if (missile_source == large) {
                    FireNormalInvaderMissile(i, missile_source, large_invader[fired_column]);
                } else if (missile_source == medium) {
                    FireNormalInvaderMissile(i, missile_source, medium_invader[fired_column]);
                } else {
                    FireNormalInvaderMissile(i, missile_source, small_invader[fired_column]);
                }
            }
        }
    }
}

void FireNormalInvaderMissile(int missile_no, Invader source, const struct Invaders (&invader))
{
    ///Finds the centre point of the chosen invader and fires the missile
    ///Enums are implicity converted to ints --> the middle x position of the invader found by offsetting the invader type by 3
    invader_normal_missile[missile_no].x_pos = invader.x_pos + (3 + source);
    invader_normal_missile[missile_no].y_pos = invader.y_pos + 6;
    invader_normal_missile[missile_no].fired = true;

    ///Uses a function pointer to attach the ticker
    move_invader_normal_missile[missile_no].attach(move_invader_normal_missile_isr[missile_no], 0.05);
}

void MoveInvaderNormalMissile(int missile_no)
{
    ///Loops through the bitmap and clears the missile from the screen buffer
    for (int col = 0; col < 3; ++col) {
        for (int row = 0; row < 4; ++row) {
            if (invader_normal_missile_bitmap[row][col]) {
                screen_buffer[invader_normal_missile[missile_no].x_pos + col][invader_normal_missile[missile_no].y_pos + row] = empty_pixel;
            }
        }
    }

    ///Checks missile will not exceed screen buffer
    if (invader_normal_missile[missile_no].y_pos < 44) {
        ///Increments the position of the missile
        ++invader_normal_missile[missile_no].y_pos;

        ///Collision detection
        CollisionDetectionInvaderNormalMissile(missile_no);

    } else {
        ///Sets the missiles fired flag as false and detaches the tickers
        invader_normal_missile[missile_no].fired = false;
        move_invader_normal_missile[missile_no].detach();
    }
}

///Checks the bottom centre point for a collision. If it doesn't push the bitmap to the screen buffer
void CollisionDetectionInvaderNormalMissile(int missile_no)
{
    ///Invader missile coordinates shifted to match centre bottom of bitmap
    int relative_x_pos = invader_normal_missile[missile_no].x_pos + 1;
    int relative_y_pos = invader_normal_missile[missile_no].y_pos + 3;
    if (screen_buffer[relative_x_pos][relative_y_pos] == cannon_pixel) {
        ///Cannon has invunerability for 1s after getting hit
        if (!g_cannon_hit_flag) {
            g_cannon_hit_flag = true;
            InvaderNormalMissileHitCannon();
            ///Attaches a timeout to allow cannon to be hit again
            cannon_hit.attach(&cannon_hit_isr, 1);
        }
        invader_normal_missile[missile_no].fired = false;
        move_invader_normal_missile[missile_no].detach();
    } else if (screen_buffer[relative_x_pos][relative_y_pos] >= first_barrier_pixel && screen_buffer[relative_x_pos][relative_y_pos] < (first_barrier_pixel + 3)) {
        ///Finds barrier number
        InvaderNormalMissileHitBarrier(invader_normal_missile[missile_no]);
        invader_normal_missile[missile_no].fired = false;
        move_invader_normal_missile[missile_no].detach();
    } else {
        for (int col = 0; col < 3; ++col) {
            for (int row = 0; row < 4; ++row) {
                if (invader_normal_missile_bitmap[row][col]) {
                    screen_buffer[invader_normal_missile[missile_no].x_pos + col][invader_normal_missile[missile_no].y_pos + row] = invader_normal_missile_pixel;
                }
            }
        }
    }
}

void InvaderNormalMissileHitCannon()
{
    ///Decrements the number of lives, pauses the game for 2 seconds
    ///Detaches all tickers
    DetachTickers();
    LPWait(2);
    --number_of_lives;
    AttachTickers();   
}

///Calculates where to start drawing the damage bitmap over the barrier bitmap and performs the operation
void InvaderNormalMissileHitBarrier(const struct InvaderNormalMissiles (&missile))
{
    int barrier_no = screen_buffer[missile.x_pos + 1][missile.y_pos + 3] - first_barrier_pixel;
    ///Essentially inverse of barrier init
    int relative_x_pos = (missile.x_pos + 1) - 10 - (barrier_no*25) - 1;
    int relative_y_pos = (missile.y_pos + 3) - 33;
    ///Loops through the damage bitmap and modifies the barrier's after bitmap
    for (int col = 0; col < 3; ++col) {
        for (int row_bit = 0; row_bit < 2; ++row_bit) {
            ///Makes sure bitmap index does not go out of bounds. If it does go to the next iteration
            if (relative_x_pos + col >= 0 && relative_x_pos + col < 14 && relative_y_pos + row_bit >= 0 && relative_y_pos + row_bit < 8) {
                barrier[barrier_no].after_bitmap[relative_y_pos + row_bit][relative_x_pos + col] *= barrier_invader_normal_missile_damage_bitmap[row_bit][col];
            }
        }
    }
}

///Detaches game related tickers
void DetachTickers()
{
    update_screen.detach();
    move_enemies.detach();

    ///Only detaches if are on the screen
    if (cannon_missile_on_screen) {
        move_cannon_missile.detach();
    }
    if (ufo_on_screen) {
        move_ufo.detach();
    }
    for (int i = 0; i < 2; ++i) {
        if (invader_normal_missile[i].fired) {
            move_invader_normal_missile[i].detach();
        }
    }
}

///Attaches game related tickers
void AttachTickers()
{
    update_screen.attach(&update_screen_isr, 0.05);
    move_enemies.attach(&move_enemies_isr, ticker_period);
    ///Only attaches if missiles were on the screen
    if (cannon_missile_on_screen) {
        move_cannon_missile.attach(&move_cannon_missile_isr, 0.05);
    }
    if (ufo_on_screen) {
        move_ufo.attach(&move_ufo_isr, 0.1);
    }
    for (int i = 0; i < 2; ++i) {
        if (invader_normal_missile[i].fired) {
            move_invader_normal_missile[i].attach(move_invader_normal_missile_isr[i], 0.05);
        }
    }
}

void PauseScreen()
{
    ///Prints the pause screen, score etc
    PrintPauseScreen();

    while (game_state == paused) {
        ///Draws the cursor
        cursor_y_pos = ((fsm_state+3)*8)+3; ///Adds 3 to the fsm state to get the bank, multiplies it by 8 to get the pixel no and offsets it by 3
        lcd.drawRect(74, cursor_y_pos, 2, 2, 1); ///Draws the cursor

        if (g_move_joystick_flag) {
            g_move_joystick_flag = false;

            ///Moves the cursor to match the selected option, only if the joystick isn't centred
            MoveCursor(fsm_paused);
        }

        ///If the button is pressed the selected option will appear on the screen
        if (g_shoot_pressed_flag) {
            g_shoot_pressed_flag = false;

            game_state = fsm_paused[fsm_state].output;
        }

        ///Returns to the game state when the joystick is pressed
        if (joystick.get_button_flag()) {
            joystick.set_button_flag(0);

            game_state = game;
        }

        lcd.refresh();
        sleep();
    }
}

void PrintPauseScreen()
{
    ///Prints paused and a line underneath
    lcd.printString("Paused", 24, 0);
    ///Displays the current score
    char buffer[14];
    sprintf(buffer, "Score: %d", score);
    lcd.printString(buffer, 0, 1);
    ///Displays the no of lives
    sprintf(buffer, "Lives: %d", number_of_lives);
    lcd.printString(buffer, 0, 2);
    lcd.drawLine(0, 23, 83, 23, 2);
    ///Prints options on pause menu
    lcd.printString("Resume", 5, 3);
    lcd.printString("Save", 5, 4);
    lcd.printString("Quit", 5, 5);
}

void MoveCursor(const struct FSMMenus *fsm)
{
    ///If the joystick is is pushed down half way clear the cursor and set the next state
    if (joystick.GetYValue() < 0.25f) {
        ///Clears the cursor
        lcd.drawRect(74, cursor_y_pos, 2, 2, 2);
        ///Sets the new state
        fsm_state = fsm[fsm_state].next_state[0];
    } else if (joystick.GetYValue() > 0.75f) {
        ///Clears the cursor
        lcd.drawRect(74, cursor_y_pos, 2, 2, 2);
        ///Sets the new state
        fsm_state = fsm[fsm_state].next_state[1];
    }
}

void MenuScreen()
{
    ///Print the menu text
    PrintMenuScreen();

    while (game_state == menu) {
        ///Draws the cursor
        cursor_y_pos = ((fsm_state+2)*8)+3; ///Adds 2 to the fsm state to get the bank, multiplies it by 8 to get the pixel no and offsets it by 3
        lcd.drawRect(74, cursor_y_pos, 2, 2, 1);

        if (g_move_joystick_flag) {
            g_move_joystick_flag = false;

            ///Moves the cursor to match the selected option, only if the joystick isn't centred
            MoveCursor(fsm_main_menu);
        }

        ///If shoot is pressed go to the selected screen
        if (g_shoot_pressed_flag) {
            g_shoot_pressed_flag = false;

            game_state = fsm_main_menu[fsm_state].output;

            ///If the game state is equal to the game initalise it
            if (game_state == game) {
                InitaliseGame();
            }
        }

        ///Do nothing when the joystick is pressed
        if(joystick.get_button_flag()) {
            joystick.set_button_flag(0);
        }

        lcd.refresh();
        sleep();
    }
}

void PrintMenuScreen()
{
    ///Prints Space invaders, then menu below, then a line underneath
    lcd.printString("Space Invaders", 0, 0);
    lcd.printString("Menu", 28, 1);
    lcd.drawLine(0, 15, 83, 15, 2);

    ///Prints menu options
    lcd.printString("New Game", 5, 2);
    lcd.printString("Load Game", 5, 3);
    lcd.printString("High Scores", 5, 4);
    lcd.printString("Settings", 5, 5);
}

void AttemptToSpawnUFO()
{
    ///1/8 chance of UFO spawning if it hasn't already
    if (!ufo_on_screen && (rand() % 8 == 1) && down_count > 1 && no_of_alive_invaders > 3) {
        ///Marks the UFO as on the screen and sets its position and direction
        ufo_on_screen = true;
        ufo_direction = (rand() % 2);
        if (ufo_direction == RIGHT) {
            ufo_x_pos = -14;
        } else {
            ufo_x_pos = 83;
        }

        ///Attachs the move UFO ticker
        move_ufo.attach(&move_ufo_isr, 0.1);
    }
}

void ClearUFO()
{
    ///Clears the UFO
    for (int col = 0; col < 14; ++col) {
        for (int row = 0; row < 5; ++row) {
            ///Only clear the pixel in the buffer if it is within bounds
            if (ufo_x_pos + col >= 0 && ufo_x_pos + col < 84) {
                if (ufo_bitmap[row][col]) {
                    screen_buffer[ufo_x_pos + col][ufo_y_pos + row] = empty_pixel;
                }
            }
        }
    }
}

void DrawUFO()
{
    ///Checks if the ufo can be within the screen bounds
    ///If it is, push the ufo to the screen buffer
    if (ufo_x_pos > -14 && ufo_x_pos < 84) {
        for (int col = 0; col < 14; ++col) {
            for (int row = 0; row < 5; ++row) {
                ///Only clear the pixel in the buffer if it is within bounds
                if (ufo_x_pos + col >= 0 && ufo_x_pos + col < 84) {
                    if (ufo_bitmap[row][col]) {
                        screen_buffer[ufo_x_pos + col][ufo_y_pos + row] = ufo_pixel;
                    }
                }
            }
        }
    } else {
        ///Otherwise mark it as not on the screen and detach the ticker
        ufo_on_screen = false;
        move_ufo.detach();
    }
}

bool high_scores_sorter(const struct HighScores (&lhs), const struct HighScores (&rhs))
{
    return lhs.score > rhs.score;
}

void ScoreScreen()
{
    lcd.printString("High Scores", 8, 0);
    lcd.drawLine(0, 7, 83, 7, 2);

    PrintHighScores();

    lcd.printString("Back", 5, 5);
    const int cursor_y_pos = 43;
    lcd.drawRect(74, cursor_y_pos, 2, 2, 1);

    lcd.refresh();

    while (game_state == scores) {
        if (joystick.get_button_flag()) {
            joystick.set_button_flag(0);
        }
        if (g_shoot_pressed_flag) {
            g_shoot_pressed_flag = false;

            game_state = menu; // If the button is pressed go to the menu state
        }

        sleep();
    }
}

void PrintHighScores()
{
    ///Loops throught the high scores and prints them to the screen
    for (int i = 0; i < 3; ++i) {
        ///Counts how many digits are in score
        int temp_score = high_scores[i].score;
        int no_of_digits = 0;
        do {
            temp_score /= 10;
            ++no_of_digits;
        } while (temp_score != 0);

        ///Prints name to c-string
        char buffer[14];
        sprintf(buffer, "%s", high_scores[i].name);

        ///Prints correct number of spaces to c-string
        int no_of_spaces = 14 - no_of_digits;
        for (int space = 3; space < no_of_spaces; ++space) {
            sprintf(buffer, "%s%s", buffer, " ");
        }

        ///Prints score to c-string
        sprintf(buffer, "%s%d", buffer, high_scores[i].score);
        lcd.printString(buffer, 0, 2 + i);
    }
}

void GameOverScreen()
{
    lcd.printString("Game Over", 18, 0);
    char buffer[14];
    sprintf(buffer, "Score: %d", score);
    lcd.printString(buffer, 0, 1);
    
    ///Save score input
    if (score > high_scores[2].score) {
        lcd.printString("High Score!", 0, 2);
        lcd.printChar('A', 27, 3);
        lcd.printChar('A', 42, 3);
        lcd.printChar('A', 57, 3);

        int name[3] = {0, 0, 0};
        GetNameForScore(name);        

        SaveHighScore(name);
    }

    ///After high score is inputted or no high score has been reached
    lcd.printString("Quit", 5, 5);
    const int cursor_y_pos = 43;
    lcd.drawRect(74, cursor_y_pos, 2, 2, 1);

    while (game_state == over) {
        if (joystick.get_button_flag()) {
            joystick.set_button_flag(0);
        }

        if (g_shoot_pressed_flag) {
            g_shoot_pressed_flag = false;

            game_state = menu;
        }

        lcd.refresh();
        sleep(); 
    }
}

void GetNameForScore(int (&name)[3])
{
    int name_cursor_pos = 0; ///Cursor is at first letter

    while (name_cursor_pos < 3) {
        ///Prints the name cursor
        lcd.drawRect(27+(15*name_cursor_pos), 32, 4, 2, 1);

        if (g_shoot_pressed_flag) {
            g_shoot_pressed_flag = false;

            ///Clears the name cursor and moves it
            lcd.drawRect(27+(15*name_cursor_pos), 32, 4, 2, 2);
            ++name_cursor_pos;
        }

        if (g_move_joystick_flag) {
            g_move_joystick_flag = false;

            if (joystick.GetYValue() < 0.25f) {
                ///Increments the name position, if it is over 25 set it to 0
                if (++name[name_cursor_pos] > 25) {
                    name[name_cursor_pos] = 0;
                }
            } else if (joystick.GetYValue() > 0.75f) {
                ///Decrements the name position, if it is under 0 set it to 25
                if (--name[name_cursor_pos] < 0) {
                    name[name_cursor_pos] = 25;
                }
            }

            lcd.printChar(name[name_cursor_pos] + 65, 27+(15*name_cursor_pos), 3); ///ASCII is offset by 65
        }

        lcd.refresh();
        sleep(); 
    }
}

void SaveHighScore(const int (&name)[3])
{
    ///Saves name and score to struct array, overwritting lowest score
    for (int letter = 0; letter < 3; ++letter) {
        high_scores[2].name[letter] = name[letter] + 65;
    }
    high_scores[2].score = score;

    ///Sorts struct array
    std::sort(high_scores, high_scores + 3, &high_scores_sorter);

    ///Saves the score
    std::ofstream high_score_data("/sd/high_scores.dat", std::ios::binary);
    if (high_score_data.good()) { ///Checks to see if file opening was good
        high_score_data.write((char *)(&high_scores), sizeof(high_scores));
        ///Flushes the buffer and saves the file
        high_score_data.close();
        lcd.printString("Saved", 27, 4);
    } else {
        lcd.clear();
        lcd.printString("Error", 1, 1);
        lcd.printString("Opening", 1, 2);
        lcd.printString("File.", 1, 3);
        LPWait(2);
    }
}

void SaveGameData()
{
    ///Creates the output file as a binary file
    std::ofstream save_data("/sd/game_save.dat", std::ios::binary);
    if (save_data.good()) { ///Checks to see if file opening was good
        ///Prints booleans to file
        save_data.write((char *)(&invaders_in_state2), sizeof(invaders_in_state2));
        save_data.write((char *)(&invader_direction), sizeof(invader_direction));
        save_data.write((char *)(&cannon_missile_on_screen), sizeof(cannon_missile_on_screen));
        save_data.write((char *)(&ufo_on_screen), sizeof(ufo_on_screen));
        save_data.write((char *)(&ufo_direction), sizeof(ufo_direction));
        save_data.write((char *)(&ufo_bonus), sizeof(ufo_bonus));

        ///Prints integers to file
        save_data.write((char *)(&score), sizeof(score));
        save_data.write((char *)(&shot_count), sizeof(shot_count));
        save_data.write((char *)(&number_of_lives), sizeof(number_of_lives));
        save_data.write((char *)(&cannon_xpos), sizeof(cannon_xpos));
        save_data.write((char *)(&cannon_missile_x_pos), sizeof(cannon_missile_x_pos));
        save_data.write((char *)(&cannon_missile_y_pos), sizeof(cannon_missile_y_pos));
        save_data.write((char *)(&no_of_alive_invaders), sizeof(no_of_alive_invaders));
        save_data.write((char *)(&down_count), sizeof(down_count));
        save_data.write((char *)(&ufo_x_pos), sizeof(ufo_x_pos));

        ///Prints invaders to file
        save_data.write((char *)(&small_invader), sizeof(small_invader));
        save_data.write((char *)(&medium_invader), sizeof(medium_invader));
        save_data.write((char *)(&large_invader), sizeof(large_invader));

        ///Prints invader missiles and barriers to file
        save_data.write((char *)(&invader_normal_missile), sizeof(invader_normal_missile));
        save_data.write((char *)(&barrier), sizeof(barrier));

        ///Flushes the buffer and saves the file
        save_data.close();
    } else {
        lcd.clear();
        lcd.printString("Error", 1, 1);
        lcd.printString("Opening", 1, 2);
        lcd.printString("File.", 1, 3);
        LPWait(2);
    }

    game_state = paused;
}

void LoadGameData()
{
    ///Creates the input binary file object
    std::ifstream save_data("/sd/game_save.dat", std::ios::binary);
    if (save_data.good()) { ///Checks to see if file opening was good
        ///Gets booleans from file
        save_data.read((char *)(&invaders_in_state2), sizeof(invaders_in_state2));
        save_data.read((char *)(&invader_direction), sizeof(invader_direction));
        save_data.read((char *)(&cannon_missile_on_screen), sizeof(cannon_missile_on_screen));
        save_data.read((char *)(&ufo_on_screen), sizeof(ufo_on_screen));
        save_data.read((char *)(&ufo_direction), sizeof(ufo_direction));
        save_data.read((char *)(&ufo_bonus), sizeof(ufo_bonus));

        ///Gets integers from file
        save_data.read((char *)(&score), sizeof(score));
        save_data.read((char *)(&shot_count), sizeof(shot_count));
        save_data.read((char *)(&number_of_lives), sizeof(number_of_lives));
        save_data.read((char *)(&cannon_xpos), sizeof(cannon_xpos));
        save_data.read((char *)(&cannon_missile_x_pos), sizeof(cannon_missile_x_pos));
        save_data.read((char *)(&cannon_missile_y_pos), sizeof(cannon_missile_y_pos));
        save_data.read((char *)(&no_of_alive_invaders), sizeof(no_of_alive_invaders));
        save_data.read((char *)(&down_count), sizeof(down_count));
        save_data.read((char *)(&ufo_x_pos), sizeof(ufo_x_pos));

        ///Gets invaders from file
        save_data.read((char *)(&small_invader), sizeof(small_invader));
        save_data.read((char *)(&medium_invader), sizeof(medium_invader));
        save_data.read((char *)(&large_invader), sizeof(large_invader));

        ///Gets invader missiles and barriers from file
        save_data.read((char *)(&invader_normal_missile), sizeof(invader_normal_missile));
        save_data.read((char *)(&barrier), sizeof(barrier));
        
        ///Closes the file
        save_data.close();

        ///Clears the screen buffer
        memset(screen_buffer, 0, sizeof(screen_buffer));
        ///Sets the game state flags
        game_state = game;
        g_update_screen_flag = true;
        g_move_joystick_flag = true;
        g_move_enemies_flag = true;
        g_fire_buzzer_flag = true;
    } else {
        lcd.clear();
        lcd.printString("Error", 1, 1);
        lcd.printString("Opening", 1, 2);
        lcd.printString("File.", 1, 3);
        LPWait(2);
        game_state = menu;
    }           
}

void LoadHighScores()
{
    ///Clears high score data before loading file
    memset(high_scores, 0, sizeof(high_scores));
    ///Extracts high scores from file
    std::ifstream high_score_data("/sd/high_scores.dat", std::ios::binary);
    if (high_score_data.good()) {
        high_score_data.read((char *)(&high_scores), sizeof(high_scores));
    }
    high_score_data.close();
    ///If there are any empty names, fill them with AAA to prevent crashes using printString
    for (int i = 0; i < 3; ++i) {
        if (high_scores[i].name[0] < 'A') {
            for (int letter = 0; letter < 3; ++letter) {
                high_scores[i].name[letter] = 'A';
            }
        }
    }

}

void SettingsScreen()
{
    PrintSettingsText();

    while (game_state == settings) {
        ///Prints options and cursor
        PrintSettingsOptions();

        ///Returns to menu if joystick button is pressed
        if (joystick.get_button_flag()) {
            joystick.set_button_flag(0);

            game_state = menu;
        }

        ///Moves the cursor when the joystick moves
        if (g_move_joystick_flag) {
            g_move_joystick_flag = false;

            MoveCursor(fsm_settings);
        }

        if (g_shoot_pressed_flag) {
            g_shoot_pressed_flag = false;

            EditOption();
        }

        lcd.refresh();
        sleep();
    }
}

void PrintSettingsText()
{
    ///Prints the options
    lcd.printString("Settings", 17, 0);
    lcd.drawLine(0, 7, 83, 7, 2);
    lcd.printString("Light:", 0, 2);
    lcd.printString("Mute: ", 0, 3);
    lcd.printString("Delete Data", 0, 4);
    lcd.printString("Back", 10, 5);
}

void PrintSettingsOptions()
{
    ///Prints the active options
    char buffer[14] = {0};
    sprintf(buffer, "%d%%", (int)(100*fsm_brightness[brightness_state].output));
    pc.printf("%s\n", buffer);
    lcd.printString(buffer, 42, 2);
    if (is_muted) {
        lcd.printString("On", 42, 3);
    } else {
        lcd.printString("Off", 42, 3);
    }
    ///Draws the cursor
    cursor_y_pos = ((fsm_state+2)*8)+3; ///Adds 2 to the fsm state to get the bank, multiplies it by 8 to get the pixel no and offsets it by 3
    lcd.drawRect(74, cursor_y_pos, 2, 2, 1);
}

void EditOption()
{
    if (fsm_state == 0) { ///Change brightness
        brightness_state = fsm_brightness[brightness_state].next_state;
        lcd.setBrightness(fsm_brightness[brightness_state].output);
        ///Clears displayed brightness
        lcd.drawRect(42, 16, 24, 7, 2);
    } else if (fsm_state == 1) { ///Mute game
        is_muted = !is_muted;
    } else if (fsm_state == 2) { ///Delete save data
        DeleteGameData();
        ///Sets FSM list back to 0 and resets the cursor
        lcd.drawRect(74, cursor_y_pos, 2, 2, 2);
        fsm_state = 0;
    } else if (fsm_state == 3) {
        game_state = menu;
    }
}

void DeleteGameData()
{
    ///Delete game state data
    std::ifstream save_data("/sd/game_save.dat", std::ios::binary);
    if (save_data.good()) {
        ///Close the file and remove it
        save_data.close();
        remove("/sd/game_save.dat");
    }
    ///Deletes high score data
    std::ifstream high_scores_data("/sd/high_scores.dat", std::ios::binary);
    if (high_scores_data.good()) {
        ///Closes the file and remove it
        high_scores_data.close();
        remove("/sd/high_scores.dat");
    }
    ///Reloads high score list
    LoadHighScores();
}

void LPWait(float t)
{
    ///Sets the wait flag
    g_lp_wait_flag = true;
    ///Attachs the timeout to fire at the users request then sleep
    lp_wait.attach(&lp_wait_isr, t);
    while (g_lp_wait_flag) {
        sleep();
    } 
}

void SplashScreen()
{
    ///Prints text and waits for 3 seconds
    lcd.printString("Space Invaders", 0, 1);
    lcd.printString("Avinash Patel", 3, 3);
    lcd.printString("200860407", 15, 4);
    LPWait(3);
}

void PlayBuzzer()
{
    if (play_sound) {
        ///Sets the frequency and activates the buzzer
        buzzer.period(1/fsm_buzzer[buzzer_state].frequency);
        buzzer.write(0.5);
        play_sound = false;
    } else {
        ///Deactivates the buzzer and sets the next frequency
        buzzer.write(0);
        buzzer_state = fsm_buzzer[buzzer_state].next_state;
        play_sound = true;
    }
}