Daniel Martin / Mbed 2 deprecated space_invaders_pvp

Dependencies:   mbed mbed-rtos 4DGL-uLCD-SE

main.cpp

Committer:
dmartin99
Date:
2021-05-02
Revision:
12:f49bae848977
Parent:
11:0309bef74ba8

File content as of revision 12:f49bae848977:

// Copyright (c) 2015 Devon Cooper and Sidak Dhillon
// 
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

#include "mbed.h"
#include "uLCD_4DGL.h"

// LCD
uLCD_4DGL uLCD(p28, p27, p30); // serial tx, serial rx, reset pin;

// Input Devices
DigitalIn fire_button(p22, PullUp);
// Note: p15 is attached to VERT and p16 is attached to HORZ, however horizontal movement
// triggers VERT and vertical movement triggers HORZ, so they are switched here.
AnalogIn horizontal_in(p15);
AnalogIn vertical_in(p16);

#define SCREEN_MAX_X        128
#define SCREEN_MAX_Y        128

#define BLACK               0x000000
#define SHIP_COLOR          0xFFFFFF
#define SHIP_COLOR2         0xFFF00F
#define SHIP_DAMAGED_COLOR  0xFF0000
#define BARRICADE_COLOR     0xFF00FF
#define BULLET_COLOR        0x00FF00
#define ALIEN_COLOR         0x00FFFF

#define SHIP_WIDTH          16
#define SHIP_HEIGHT         8
#define SHIP_GUN_WIDTH      3
#define SHIP_GUN_HEIGHT     3

#define BULLET_WIDTH        1
#define BULLET_HEIGHT       3

#define BARRICADES_START_Y  (SCREEN_MAX_Y - 32)
#define BARRICADES_END_Y    (SCREEN_MAX_Y - 16)
#define BARRICADE_SIZE      16

#define ALIEN_HEIGHT        8
#define ALIEN_WIDTH         11
#define ALIEN_SCORE_AMOUNT  10

/***** Sprite Data ***********************************************************/

#define _ BLACK
#define X BARRICADE_COLOR
const int barricade_sprite[BARRICADE_SIZE * BARRICADE_SIZE] = {
    _,_,_,X,X,X,X,X,X,X,X,X,X,_,_,_,
    _,_,X,X,X,X,X,X,X,X,X,X,X,X,_,_,
    _,X,X,X,X,X,X,X,X,X,X,X,X,X,X,_,
    X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,
    X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,
    X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,
    X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,
    X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,
    X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,
    X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,
    X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,
    X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,
    X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,
    X,X,X,X,X,X,X,_,_,X,X,X,X,X,X,X,
    X,X,X,X,X,_,_,_,_,_,_,X,X,X,X,X,
    X,X,X,X,_,_,_,_,_,_,_,_,X,X,X,X,
};
#undef _
#undef X

#define _ 0x000000
#define X 0x00FFFF
#define Q 0xFF0F0F
const int alien_sprite_1[ALIEN_HEIGHT * ALIEN_WIDTH] = {
    _,_,X,_,_,_,_,_,X,_,_,
    _,_,_,X,_,_,_,X,_,_,_,
    _,_,X,X,X,X,X,X,X,_,_,
    _,X,X,_,X,X,X,_,X,X,_,
    X,X,X,X,X,X,X,X,X,X,X,
    X,_,X,X,X,X,X,X,X,_,X,
    X,_,X,_,_,_,_,_,X,_,X,
    _,_,_,X,X,_,X,X,_,_,_,
};

const int alien_sprite_flip[ALIEN_HEIGHT * ALIEN_WIDTH] = {
    _,_,_,Q,Q,_,Q,Q,_,_,_,
    Q,_,Q,_,_,_,_,_,Q,_,Q,
    Q,_,Q,Q,Q,Q,Q,Q,Q,_,Q,
    Q,Q,Q,Q,Q,Q,Q,Q,Q,Q,Q,
    _,Q,Q,_,Q,Q,Q,_,Q,Q,_,
    _,_,Q,Q,Q,Q,Q,Q,Q,_,_,
    _,_,_,Q,_,_,_,Q,_,_,_,
    _,_,Q,_,_,_,_,_,Q,_,_,
};

const int alien_sprite_2[ALIEN_HEIGHT * ALIEN_WIDTH] = {
    _,_,_,_,X,X,X,_,_,_,_,
    _,X,X,X,X,X,X,X,X,X,_,
    X,X,X,X,X,X,X,X,X,X,X,
    X,X,X,_,_,X,_,_,X,X,X,
    X,X,X,X,X,X,X,X,X,X,X,
    _,_,_,X,X,_,X,X,_,_,_,
    _,_,X,X,_,_,_,X,X,_,_,
    X,X,_,_,_,X,_,_,_,X,X,
};

const int alien_sprite_3[ALIEN_HEIGHT * ALIEN_WIDTH] = {
    _,_,_,_,_,X,X,_,_,_,_,
    _,_,_,_,X,X,X,X,_,_,_,
    _,_,_,X,X,X,X,X,X,_,_,
    _,_,X,X,_,X,X,_,X,X,_,
    _,_,X,X,X,X,X,X,X,X,_,
    _,_,_,_,X,_,_,X,_,_,_,
    _,_,_,X,_,X,X,_,X,_,_,
    _,_,X,_,X,_,_,X,_,X,_,
};

const int blank[ALIEN_HEIGHT * ALIEN_WIDTH] = {
    _,_,_,_,_,_,_,_,_,_,_,
    _,_,_,_,_,_,_,_,_,_,_,
    _,_,_,_,_,_,_,_,_,_,_,
    _,_,_,_,_,_,_,_,_,_,_,
    _,_,_,_,_,_,_,_,_,_,_,
    _,_,_,_,_,_,_,_,_,_,_,
    _,_,_,_,_,_,_,_,_,_,_,
    _,_,_,_,_,_,_,_,_,_,_,
};
#undef _
#undef X

enum AlienSpecies {
    ALIEN_SPECIES_1, ALIEN_SPECIES_2, ALIEN_SPECIES_3,
};
#define ALIEN_ROW_TO_SPECIES(_row) \
    ((_row) == 0 ? ALIEN_SPECIES_3 : (_row) < 3 ? ALIEN_SPECIES_2 : ALIEN_SPECIES_1)

// Input Device State
volatile bool fire_button_pressed;
volatile bool fire_button_pressed2;
volatile float horizontal_movement;
volatile float vertical_movement;
volatile float horizontal_movement2;

Ticker input_ticker;

// Barricade state
bool barricades[SCREEN_MAX_X][BARRICADE_SIZE];

// Player state
bool player_alive;
bool player_alive2;
int player_x;
int player_x2;
bool player_bullet_exists;
int player_bullet_x;
int player_bullet_y;
bool player_bullet_exists2;
int player_bullet_x2;
int player_bullet_y2;
int player_score;
int player_lives;

// Alien state
bool aliens[5][5];
int aliens_x; // left-most
int aliens_y; // bottom-most
struct { bool exists; int x; int y; } alien_bullets[3];

// Random seed
unsigned int random_seed;
bool random_seeded;

int pvp_state = -1;

RawSerial dev(p13,p14);
BusOut myled(LED1,LED2,LED3,LED4);
DigitalOut them_led(p24);
DigitalOut us_led(p20);

void left_blue() {
    horizontal_movement2 -= 1;
}

void right_blue() {
    horizontal_movement2 += 1;
}

Ticker flipper;

void dev_recv()
{
    char bnum=0;
    while(dev.readable()) {
        if (dev.getc()=='!') {
            if (dev.getc()=='B') { //button data
                bnum = dev.getc(); //button number
                if ((bnum>='5')&&(bnum<='8')) { //is a number button 1..4 
                    player_alive2 = true;
                    bool on = dev.getc()-'0';
                    myled[bnum-'5']= on; //turn on/off that num LED
                    if (on) {
                        if(bnum=='5') 
                            fire_button_pressed2 = 1;
                        else if(bnum=='7') 
                            flipper.attach(&left_blue,  0.02);
                        else if(bnum=='8') 
                            flipper.attach(&right_blue, 0.02);
                    } else {
                        flipper.detach();
                    }
                } 
            }
        }
    }
}

// Audio 
#include "SongPlayer.h"
#include "rtos.h"

float game_over_note[18]= {884.0, 798.45, 722.25, 722.25, 798.45, 884.0, 884.0, 884.0, 798.45, 722.25, 798.45, 884.0, 798.45, 722.25, 687.35, 722.25, 722.25, 100.0 };
float game_over_duration[18]= {0.6384, 0.3192, 0.9576, 0.6384, 0.3192, 0.6384, 0.3192, 0.3192, 0.3192, 0.3192, 0.3192, 0.3192, 0.3192, 0.6384, 0.3192, 0.6384, 0.6384, 0.0 };
                                   
float shootNote[1] = {950.0};
float durationNote[1] = {0.15};

float hitNote[2] = {1400.0, 1400.0};
float hit_durationNote[2] = {0.15, 0.15};

SongPlayer mySpeaker(p21);

volatile int isPlaying = -1;
Mutex isPlaying_mutex;

Thread audio_thread;

void audio_thread_run() {
    while (true) {
        isPlaying_mutex.lock();
        if (isPlaying == -1) {
            isPlaying_mutex.unlock();
        } else  {
            
            int songID = isPlaying;            
            
            isPlaying = 0;
            isPlaying_mutex.unlock();
            
            
            if (songID == 1 ) {
                mySpeaker.PlaySong(shootNote, durationNote);    
            } else if (songID == 2 ) {
                us_led = 1;
                mySpeaker.PlaySong(hitNote, hit_durationNote);  
                Thread::wait(500);
                us_led = 0; 
                
            } else if (songID == 3 ) {
                us_led = 1;
                them_led = 1;
                mySpeaker.PlaySong(game_over_note, game_over_duration);
                Thread::wait(500);
                us_led = 0;  
                them_led = 0;
                  
            }else if (songID == 5 ) {
                them_led = 1;
                mySpeaker.PlaySong(hitNote, hit_durationNote);
                Thread::wait(500);
                them_led = 0;   
            }
            
            //mySpeaker.PlaySong(shootNote, durationNote);
            
            isPlaying_mutex.lock();
            isPlaying = -1;
            isPlaying_mutex.unlock();
        }
        
        Thread::wait(100);   
    }
}


void add_audio(int song_id) {
    isPlaying_mutex.lock();
    int isSongPlaying = isPlaying;
    
    if (isSongPlaying == 0) {
        isPlaying_mutex.unlock();
        return;   
    }
    
    isPlaying = song_id;
    isPlaying_mutex.unlock();
}


/***** Rendering Routines ****************************************************/

/* The player is drawn as two rectangles, one for the main body, and one for
 * the cannon.
 */
void draw_player(int center_x, int color) {
    uLCD.filled_rectangle(
        center_x - (SHIP_WIDTH / 2),
        SCREEN_MAX_Y - SHIP_HEIGHT,
        center_x + (SHIP_WIDTH / 2),
        SCREEN_MAX_Y - 1,
        color);
    uLCD.filled_rectangle(
        center_x - (SHIP_GUN_WIDTH / 2),
        SCREEN_MAX_Y - (SHIP_HEIGHT + SHIP_GUN_HEIGHT),
        center_x + (SHIP_GUN_WIDTH / 2),
        SCREEN_MAX_Y - SHIP_HEIGHT,
        color);
}

/* Aliens are drawn using the sprite data and the uLCD's BLIT mode. */
void draw_alien(AlienSpecies species, int left_x, int bottom_y) {
    const int *sprite =
        species == ALIEN_SPECIES_1 ? alien_sprite_1 :
        species == ALIEN_SPECIES_2 ? alien_sprite_2 :
        alien_sprite_3;
    uLCD.BLIT(left_x, bottom_y, ALIEN_WIDTH, ALIEN_HEIGHT, (int*)sprite);
}

/* Erase an alien. This is a special routine because aliens have sprites
 * and so take more time to draw than other objects.
 */
void erase_alien(int left_x, int bottom_y) {
    // We erase an extra 2 around the sides and bottom to account for movement.
    // This should be safe because the aliens never get closer than 8 pixels to
    // the edge of the LCD.
    uLCD.filled_rectangle(left_x - 2, bottom_y, left_x + 11 + 2, bottom_y + 8 + 2, BLACK);
}

/* Draws all 25 aliens. (5 rows, 5 columns) */
void draw_aliens() {
    uLCD.filled_rectangle(aliens_x, aliens_y, aliens_x + 16 * 5, aliens_y + 12 * 5, BLACK);
    wait(0.05);
    
    for (int x = 0; x < 5; x++) {
        for (int y = 0; y < 5; y++) {
            if (aliens[x][y]) {
                draw_alien(ALIEN_ROW_TO_SPECIES(y), aliens_x + 16 * x, aliens_y + 12 * y);
            }
        }
    }
}

/* Draw a bullet. A bullet is drawn as a 1x3 vertical line. */
void draw_bullet(int center_x, int bottom_y, int color) {
    uLCD.line(
        center_x,
        bottom_y,
        center_x,
        bottom_y + BULLET_HEIGHT,
        color);
}

/***** Initialization ********************************************************/
void initialize_screen() {
    // Attempting to use a higher baud rate causes errors (the LCD freezes).
    uLCD.baudrate(57600);
    uLCD.background_color(BLACK);
    uLCD.cls();
}


volatile float init_movement = 0;
volatile float init_movement2 = 0;
bool first_call = true;
void __input_ticker__() {
//    while(true) {
        if (first_call) {
            init_movement = horizontal_in;
            init_movement2 = vertical_in;
            first_call = false;
        }
        fire_button_pressed |= !fire_button;
        horizontal_movement += horizontal_in - init_movement;
        vertical_movement += vertical_in - init_movement2;
        if (fire_button_pressed)
            player_alive = true;
//        Thread::wait(100); 
//    }
}

Thread movement;

// We use interrupts for measuring input so that input remains responsive even
// when the graphics are lagging.
void initialize_input() {
    input_ticker.detach();
    input_ticker.attach(__input_ticker__, 0.005);
//    movement.start(__input_ticker__);
//    START;
}

void initialize_player() {
    player_x = 2 * SCREEN_MAX_X / 3;
    player_bullet_exists = false;
    draw_player(player_x, SHIP_COLOR);
    player_score = 0;
    player_lives = 2;
    
    uLCD.locate(0, 0);
    uLCD.printf("Score:%i", player_score);
    
    uLCD.locate(10, 0);
    uLCD.printf("Lives:%i", player_lives);
}

void initialize_player2() {
    player_x2 = SCREEN_MAX_X / 3;
    draw_player(player_x2, SHIP_COLOR2);
}

void initialize_player3() {
    player_x = SCREEN_MAX_X/2;
    const int *sprite = alien_sprite_1;
    uLCD.BLIT(player_x, SCREEN_MAX_Y-ALIEN_HEIGHT, ALIEN_WIDTH, ALIEN_HEIGHT, (int*)sprite);
}

void initialize_player4() {
    player_x2 = SCREEN_MAX_X/2;
    const int *sprite = alien_sprite_flip;
    uLCD.BLIT(player_x2, ALIEN_HEIGHT, ALIEN_WIDTH, ALIEN_HEIGHT, (int*)sprite);
}

void initialize_aliens() {
    // Start the aliens in the upper-left corner of the screen
    aliens_x = 8;
    aliens_y = 8;
    
    for (int x = 0; x < 5; x++) {
        for (int y = 0; y < 5; y++) {
            aliens[x][y] = true;
        }
    }
    
    for (int i = 0; i < 3; i++) {
        alien_bullets[i].exists = false;
    }
    
    draw_aliens();
}

void initialize_barricades() {
    memset(barricades, 0, sizeof(bool) * SCREEN_MAX_X * BARRICADE_SIZE);
    
    for (int i = 1; i < 5; i++) {
        int start_x = (SCREEN_MAX_X * i / 5) - (BARRICADE_SIZE / 2);
        
        uLCD.BLIT(start_x, BARRICADES_START_Y, BARRICADE_SIZE, BARRICADE_SIZE, (int*)barricade_sprite);
        
        for (int x = 0; x < BARRICADE_SIZE; x++) {
            for (int y = 0; y < BARRICADE_SIZE; y++) {
                barricades[start_x + x][y] = !!barricade_sprite[y * BARRICADE_SIZE + x];
            }
        }
    }
}

// Devices only need to be initialized once during the program, at start up
void initialize_devices() {
    initialize_screen();
    initialize_input();
}

// Game state needs to be re-initialized whenever a new game is started after
// a game over or win.
void initialize_game_state() {
    initialize_player();
    initialize_player2();
    initialize_barricades();    
    initialize_aliens();
}

void initialize_game_state2() {
    initialize_player3();
    initialize_player4();
}


/***** Logic *****************************************************************/

/* Causes a chunk of the barricade around the given x and y coordinates to
 * be destroyed.
 */
void barricade_impact(int x, int y) {
    // This is supposed to create a diamond pattern around the point of
    // impact, but for some reason it creates a square.
    // Eg, it should be  *   but instead is *****
    //                  ***                 *****
    //                 *****                *****
    //                  ***                 *****
    //                   *                  *****
    
    int x_dist = 2;
    for (int delta_x = -x_dist; delta_x <= x_dist; delta_x++) {
        
        int y_dist = abs(x) == 2 ? 0 : abs(x) == 1 ? 1 : 2;
        for (int delta_y = -y_dist; delta_y <= y_dist; delta_y++) {
            int abs_x = x + delta_x;
            int abs_y = y + delta_y;
            
            if (abs_x >= 0 && abs_x < SCREEN_MAX_X
                    && abs_y >= BARRICADES_START_Y && abs_y < BARRICADES_END_Y) {
                barricades[abs_x][abs_y - BARRICADES_START_Y] = false;
                uLCD.pixel(abs_x, abs_y, BLACK);
            }
        }
    }
}

/* Returns true if a bullet at the given coordinates hits an alien.
 * As a side effect, that alien is destroyed and the player's score is increased.
 */
bool alien_impact(int x, int y) {
    int horizontal_region = (x - aliens_x) / 16;
    int vertical_region = (y - aliens_y) / 12;
    
    int horizontal_offset = (x - aliens_x) % 16;
    int vertical_offset = (y - aliens_y) % 12;
    
    if (horizontal_region >= 0 && horizontal_region < 5
            && vertical_region >= 0 && vertical_region < 5
            && aliens[horizontal_region][vertical_region]
            && horizontal_offset <= 11
            && vertical_offset <= 8) {
        aliens[horizontal_region][vertical_region] = false;
        erase_alien(aliens_x + 16 * horizontal_region, aliens_y + 12 * vertical_region);
        player_score += ALIEN_SCORE_AMOUNT;
        add_audio(5);

        return true;
    } else {
        return false;
    }
}

/* Returns true if a bullet at the given coordinates hits the player.
 * As a side effect, the player loses a life.
 */
bool player_impact(int x, int y) {
    int player_y = SCREEN_MAX_Y;
    int player_y2 = pvp_state == -1 ? SCREEN_MAX_Y : 0;
//    if (y < SCREEN_MAX_Y && y >= SCREEN_MAX_Y - (SHIP_HEIGHT + SHIP_GUN_HEIGHT)) {
            if (x >= player_x - SHIP_WIDTH / 2 && x <= player_x + SHIP_WIDTH / 2 && y >= player_y - SHIP_HEIGHT / 2 && y <= player_y + SHIP_HEIGHT / 2) {
                if (pvp_state == -1)
                    draw_player(player_x, SHIP_DAMAGED_COLOR);
                player_lives -= 1;
                add_audio(2);
                
                return true;
            } else if (x >= player_x2 - SHIP_WIDTH / 2 && x <= player_x2 + SHIP_WIDTH / 2 && y >= player_y2 - SHIP_HEIGHT / 2 && y <= player_y2 + SHIP_HEIGHT / 2){
                if (pvp_state == -1)
                    draw_player(player_x2, SHIP_DAMAGED_COLOR);
                player_lives -= 1;
                add_audio(2);
                return true;
            } 
//    } 
    return false;
}

/* Checks if a barricade exists at the given x and y coordinates. */
bool barricade_exist_at(int x, int y) {
    return y >= BARRICADES_START_Y && y < BARRICADES_END_Y && barricades[x][y - BARRICADES_START_Y];
}

/***** Updates ***************************************************************/

// Uses C++ functors for the updates. The original idea was that each update
// would return a continuation to allow more responsive updates to the player 
// object, but I realized it would be easier to just update the state in-place.
// These should be turned into simple functions, but it works as it is now.

class PlayerUpdate {
  public:
    void operator()() {
        // Sample input devices
        float delta_x = horizontal_movement;
        horizontal_movement = 0;
        
        // Update player
        int old_player_x = player_x;
        player_x += int(delta_x * 2);
        
        if (player_x < SHIP_WIDTH / 2)                      player_x = SHIP_WIDTH / 2;
        if (player_x > SCREEN_MAX_X - (SHIP_WIDTH / 2) - 1) player_x = SCREEN_MAX_X - (SHIP_WIDTH / 2) - 1;
        
        if (old_player_x != player_x) {
            draw_player(old_player_x, BLACK);
            draw_player(player_x, SHIP_COLOR);
            draw_player(player_x2, SHIP_COLOR2);
        }
    }
    
    void alien() {
        // Sample input devices
        float delta_x = horizontal_movement;
        horizontal_movement = 0;
        
        // Update player
        int old_player_x = player_x;
        player_x += int(delta_x * 2);
        
        if (player_x < SHIP_WIDTH / 2)                      player_x = SHIP_WIDTH / 2;
        if (player_x > SCREEN_MAX_X - (SHIP_WIDTH / 2) - 1) player_x = SCREEN_MAX_X - (SHIP_WIDTH / 2) - 1;
        
        if (old_player_x != player_x) {
            uLCD.BLIT(old_player_x, SCREEN_MAX_X - ALIEN_HEIGHT, ALIEN_WIDTH, ALIEN_HEIGHT, (int*)blank);
            uLCD.BLIT(player_x, SCREEN_MAX_X - ALIEN_HEIGHT, ALIEN_WIDTH, ALIEN_HEIGHT, (int*)alien_sprite_1);
        }
    }
};

class PlayerUpdate2 {
  public:
    void operator()() {
        // Sample input devices
        float delta_x2 = horizontal_movement2;
        horizontal_movement2 = 0;
        
        // Update player
        int old_player_x2 = player_x2;
        player_x2 += int(delta_x2 * 2);
        
        if (player_x2 < SHIP_WIDTH / 2)                      player_x2 = SHIP_WIDTH / 2;
        if (player_x2 > SCREEN_MAX_X - (SHIP_WIDTH / 2) - 1) player_x2 = SCREEN_MAX_X - (SHIP_WIDTH / 2) - 1;
        
        if (old_player_x2 != player_x2) {
            draw_player(old_player_x2, BLACK);
            draw_player(player_x, SHIP_COLOR);
            draw_player(player_x2, SHIP_COLOR2);
        }
    }
    
    void alien() {
        // Sample input devices
        float delta_x2 = horizontal_movement2;
        horizontal_movement2 = 0;
        
        // Update player
        int old_player_x2 = player_x2;
        player_x2 += int(delta_x2 * 2);
        
        if (player_x2 < SHIP_WIDTH / 2)                      player_x2 = SHIP_WIDTH / 2;
        if (player_x2 > SCREEN_MAX_X - (SHIP_WIDTH / 2) - 1) player_x2 = SCREEN_MAX_X - (SHIP_WIDTH / 2) - 1;
        
        if (old_player_x2 != player_x2) {
            uLCD.BLIT(old_player_x2, ALIEN_HEIGHT, ALIEN_WIDTH, ALIEN_HEIGHT, (int*)blank);
            uLCD.BLIT(player_x2, ALIEN_HEIGHT, ALIEN_WIDTH, ALIEN_HEIGHT, (int*)alien_sprite_flip);
        }
    }
};

class AlienUpdate {
    // This one is a bit complicated, essentially it is a hard-coded state
    // machine that shifts one alien at a time so that the player can be
    // updated in between rows of aliens shifting.
    
    enum State {
        SHIFTING_LEFT,
        SHIFTING_RIGHT,
        SHIFTING_DOWN_NEXT_IS_RIGHT,
        SHIFTING_DOWN_NEXT_IS_LEFT,
    } state;
    int row_shifting, column_shifting;

  public:
    AlienUpdate() : state(SHIFTING_RIGHT), row_shifting(0), column_shifting(0) {}
    
    void operator()() {
        switch (this->state) {
          case SHIFTING_LEFT:
            if (aliens[column_shifting][row_shifting]) {
                erase_alien(aliens_x + 16 * column_shifting, aliens_y + 12 * row_shifting);
                draw_alien(ALIEN_ROW_TO_SPECIES(row_shifting), aliens_x + 16 * column_shifting - 2, aliens_y + 12 * row_shifting);
            }
            column_shifting -= 1;
            if (column_shifting < 0) {
                if (row_shifting >= 4) {
                    aliens_x -= 2;
                    if (aliens_x < 8) {
                        state = SHIFTING_DOWN_NEXT_IS_RIGHT;
                        row_shifting = 0;
                        column_shifting = 0;
                    } else {
                        row_shifting = 0;
                        column_shifting = 4;
                    }
                } else {
                    row_shifting += 1;
                    column_shifting = 4;
                }
            }
            break;
            
          case SHIFTING_RIGHT:
            if (aliens[column_shifting][row_shifting]) {
                erase_alien(aliens_x + 16 * column_shifting, aliens_y + 12 * row_shifting);
                draw_alien(ALIEN_ROW_TO_SPECIES(row_shifting), aliens_x + 16 * column_shifting + 2, aliens_y + 12 * row_shifting);
            }
            column_shifting += 1;
            if (column_shifting > 4) {
                if (row_shifting >= 4) {
                    aliens_x += 2;
                    if (aliens_x > SCREEN_MAX_X - (16 * 5) - 8) {
                        state = SHIFTING_DOWN_NEXT_IS_LEFT;
                        row_shifting = 0;
                        column_shifting = 0;
                    } else {
                        row_shifting = 0;
                        column_shifting = 0;
                    }
                } else {
                    row_shifting += 1;
                    column_shifting = 0;
                }
            }
            break;
          
          case SHIFTING_DOWN_NEXT_IS_LEFT:
          case SHIFTING_DOWN_NEXT_IS_RIGHT:
            if (aliens[column_shifting][row_shifting]) {
                erase_alien(aliens_x + 16 * column_shifting, aliens_y + 12 * row_shifting);
                draw_alien(ALIEN_ROW_TO_SPECIES(row_shifting), aliens_x + 16 * column_shifting, aliens_y + 12 * row_shifting + 2);
            }
            column_shifting += 1;
            if (column_shifting > 4) {
                if (row_shifting >= 4) {
                    if (this->state == SHIFTING_DOWN_NEXT_IS_LEFT) {
                        state = SHIFTING_LEFT;
                        column_shifting = 4;
                    } else {
                        state = SHIFTING_RIGHT;
                        column_shifting = 0;
                    }
                    row_shifting = 0;
                    aliens_y += 2;
                } else {
                    row_shifting += 1;
                    column_shifting = 0;
                }
            }
            break;
        }
    }
};

class BulletUpdate {
  public:
    void operator()() {
        // Sample input devices
        bool fire_button_pressed_ = fire_button_pressed;
        fire_button_pressed = false;
        
        bool fire_button_pressed_2 = fire_button_pressed2;
        fire_button_pressed2 = false;
        
        // Update player bullet
        if (player_bullet_exists) {
            draw_bullet(player_bullet_x, player_bullet_y, BLACK);
            player_bullet_y -= 2;
            if (player_bullet_y < 0) {
                player_bullet_exists = false;
            }
        } else if (fire_button_pressed_) {
            add_audio(1);
            player_bullet_exists = true;
            player_bullet_x = player_x;
            player_bullet_y = SCREEN_MAX_Y - (SHIP_HEIGHT + SHIP_GUN_HEIGHT) - BULLET_HEIGHT - 1;
        }
        
        if (player_bullet_exists) {
            if (barricade_exist_at(player_bullet_x, player_bullet_y)) {
                barricade_impact(player_bullet_x, player_bullet_y);
                player_bullet_exists = false;
            } else if (alien_impact(player_bullet_x, player_bullet_y)) {
                player_bullet_exists = false;
            } else if (pvp_state != -1 && player_impact(player_bullet_x, player_bullet_y + BULLET_HEIGHT)) {
                pvp_state = 1;
            }else {
                draw_bullet(player_bullet_x, player_bullet_y, BULLET_COLOR);
            } 
        }
        
        // Update player2 bullet
        if (player_bullet_exists2) {
            draw_bullet(player_bullet_x2, player_bullet_y2, BLACK);
            player_bullet_y2 -= pvp_state == -1 ? 2 : -2;
            if (player_bullet_y2 < 0 || player_bullet_y2 >= SCREEN_MAX_Y) {
                player_bullet_exists2 = false;
            }
        } else if (fire_button_pressed_2) {
            add_audio(1);
            player_bullet_exists2 = true;
            player_bullet_x2 = player_x2;
            if (pvp_state != -1)
                player_bullet_y2 = 0 + (SHIP_HEIGHT + SHIP_GUN_HEIGHT) + BULLET_HEIGHT + 1;
            else
                player_bullet_y2 = SCREEN_MAX_Y - (SHIP_HEIGHT + SHIP_GUN_HEIGHT) - BULLET_HEIGHT - 1;
        }
        
        if (player_bullet_exists2) {
            if (barricade_exist_at(player_bullet_x2, player_bullet_y2)) {
                barricade_impact(player_bullet_x2, player_bullet_y2);
                player_bullet_exists2 = false;
            } else if (alien_impact(player_bullet_x2, player_bullet_y2)) {
                player_bullet_exists2 = false;
            } else if (pvp_state != -1 && player_impact(player_bullet_x2, player_bullet_y2 + BULLET_HEIGHT)) {
                pvp_state = 2;
            }else {
                draw_bullet(player_bullet_x2, player_bullet_y2, BULLET_COLOR);
            }
        }
        
        if (pvp_state != -1)
            return;
             
        // Update alien bullets
        for (int i = 0; i < 3; i++) {
            bool alien_fire_button_pressed = rand() % 64 == 0;
            
            if (alien_bullets[i].exists) {
                draw_bullet(alien_bullets[i].x, alien_bullets[i].y, BLACK);
                alien_bullets[i].y += 2;
                if (alien_bullets[i].y >= SCREEN_MAX_Y) {
                    alien_bullets[i].exists = false;
                }
            } else if (alien_fire_button_pressed) {
                int column_firing = rand() % 5;
                int row_firing = -1;
                
                for (int y = 0; y < 5; y++) {
                    if (aliens[column_firing][y]) {
                        row_firing = y;
                    }
                }
                
                if (row_firing != -1) {
                    alien_bullets[i].exists = true;
                    alien_bullets[i].x = aliens_x + column_firing * 16 + (ALIEN_WIDTH / 2);
                    alien_bullets[i].y = aliens_y + row_firing * 12 + ALIEN_HEIGHT + 1;
                }   
            }
            
            if (alien_bullets[i].exists) {
                if (barricade_exist_at(alien_bullets[i].x, alien_bullets[i].y + BULLET_HEIGHT)) {
                    barricade_impact(alien_bullets[i].x, alien_bullets[i].y + BULLET_HEIGHT);
                    alien_bullets[i].exists = false;
                } else if (player_impact(alien_bullets[i].x, alien_bullets[i].y + BULLET_HEIGHT)) {
                    alien_bullets[i].exists = false;
                } else {
                    draw_bullet(alien_bullets[i].x, alien_bullets[i].y, BULLET_COLOR);
                }
            }
        }
    }
};

PlayerUpdate player_updater;
PlayerUpdate2 player_updater2;
AlienUpdate alien_updater;
BulletUpdate bullet_updater;
void game_loop() {
    player_updater();
    player_updater2();
    
    alien_updater();
    bullet_updater();
    
    // Seed random number generator if unseeded
    // Basically, we count the number of cycles until a fire button is pressed.
    // This should be sufficiently random, for game purposes at least.
    if (!random_seeded) {
        if (fire_button_pressed) {
            srand(random_seed);
            random_seeded = true;
        } else {
            random_seed += 1;
        }
    }
    
    uLCD.text_height(1);
    uLCD.text_width(1); 
    uLCD.locate(0, 0);
    uLCD.printf("Score:%i", player_score);
    
    uLCD.locate(10, 0);
    uLCD.printf("Lives:%i", player_lives);
    
    // Check for lose condition
    if (player_lives < 0) {
        uLCD.filled_rectangle(0, 0, SCREEN_MAX_X, BARRICADES_START_Y, BLACK);

        uLCD.locate(0, 5);
        uLCD.printf("    Game Over\n");
        uLCD.printf("   Score: %5d\n", player_score);
        uLCD.printf("\n");
        uLCD.printf("   Press fire to\n");
        uLCD.printf("    play again.\n");
        
        wait(0.6);
        add_audio(3);
        
        wait(0.25);
        while (!(fire_button_pressed || fire_button_pressed2));
        wait(0.25);
        uLCD.cls();
        fire_button_pressed = false;
        fire_button_pressed2 = false;
        player_updater = PlayerUpdate();
        player_updater2 = PlayerUpdate2();
        alien_updater = AlienUpdate();
        bullet_updater = BulletUpdate();
        initialize_game_state();
        return;
    }
    
    // Check for win condition
    bool all_aliens_destroyed = true;
    for (int x = 0; x < 5; x++) {
        for (int y = 0; y < 5; y++) {
            if (aliens[x][y]) {
                all_aliens_destroyed = false;
                break;
            }
        }
    }
    
    if (all_aliens_destroyed) {
        uLCD.filled_rectangle(0, 0, SCREEN_MAX_X, BARRICADES_START_Y, BLACK);

        uLCD.locate(0, 5);
        uLCD.printf("     You Win!\n");
        uLCD.printf("   Score: %5d\n", player_score);
        uLCD.printf("\n");
        uLCD.printf("   Press fire to\n");
        uLCD.printf("    play again.\n");
        
        
        wait(0.25);
        while (!(fire_button_pressed || fire_button_pressed2) );
        wait(0.25);
        uLCD.cls();
        fire_button_pressed = false;
        fire_button_pressed2 = false;
        player_updater = PlayerUpdate();
        player_updater2 = PlayerUpdate2();
        alien_updater = AlienUpdate();
        bullet_updater = BulletUpdate();
        initialize_game_state();
        return;
    }
}

void game_loop2() {
    player_updater.alien();
    player_updater2.alien();
    
    bullet_updater();
    
    // Check for lose condition
    if (pvp_state != 0) {
        uLCD.filled_rectangle(0, 0, SCREEN_MAX_X, BARRICADES_START_Y, BLACK);

        uLCD.locate(0, 5);
        uLCD.printf("    Game Over\n");
        uLCD.printf("   Player %i won", pvp_state);
        uLCD.printf("\n");
        uLCD.printf("   Press fire to\n");
        uLCD.printf("    play again.\n");
        wait(0.6);
        add_audio(3);
        
        wait(0.25);
        while (!(fire_button_pressed || fire_button_pressed2));
        wait(0.25);
        uLCD.cls();
        fire_button_pressed = false;
        fire_button_pressed2 = false;
        player_bullet_exists = false;
        player_bullet_exists2 = false;
        initialize_game_state2();
        pvp_state = 0;
    }
}


int main() {
    initialize_devices();
    dev.attach(&dev_recv, Serial::RxIrq);
    
    uLCD.locate(0, 5);
    uLCD.printf("  Please Connect\n");
    uLCD.printf("  Both Devices!\n");
    
    uLCD.locate(0, 8);
    uLCD.printf("Player 1:Not Ready");
    
    uLCD.locate(0, 9);
    uLCD.printf("Player 2:Not Ready");
    
    while (!(player_alive && player_alive2)) {
        if (player_alive) {
             uLCD.locate(0, 8);
             uLCD.printf("Player 1:Ready    ");
        }
        
        if (player_alive2) {
             uLCD.locate(0, 9);
             uLCD.printf("Player 2:Ready    ");
        }
        wait(0.01);
    }
    fire_button_pressed = false;
    fire_button_pressed2 = false;
    
    uLCD.cls();
    
    uLCD.locate(0, 5);
    uLCD.printf("  Select Mode!\n");
    
    uLCD.locate(0, 8);
    uLCD.printf("Normal -Seleted");
    
    uLCD.locate(0, 9);
    uLCD.printf("PVP            ");
    
    while (!(fire_button_pressed)) {
        if (vertical_movement > 0.1) {
            uLCD.locate(0, 8);
            uLCD.printf("Normal         ");
            uLCD.locate(0, 9);
            uLCD.printf("PVP    -Seleted");
            pvp_state = 0;
        }else if (vertical_movement < 0.1) {
            uLCD.locate(0, 8);
            uLCD.printf("Normal -Seleted");
            uLCD.locate(0, 9);
            uLCD.printf("PVP            ");
            pvp_state = -1;
        }
        vertical_movement = 0;
        wait(0.01);
    }
    fire_button_pressed = false;
    fire_button_pressed2 = false;
    
    uLCD.cls();
    
    if (pvp_state != -1)
        initialize_game_state2();
    else 
        initialize_game_state();

    audio_thread.start(audio_thread_run);
    
    while (true) {
        if (pvp_state != -1)
            game_loop2();
        else 
            game_loop();
        wait(0.005);
    }
}