Daniel Martin / Mbed 2 deprecated space_invaders_pvp

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

Revision:
12:f49bae848977
Parent:
11:0309bef74ba8
--- a/main.cpp	Wed Feb 15 14:04:02 2017 -0600
+++ b/main.cpp	Sun May 02 19:01:00 2021 +0000
@@ -1,22 +1,1087 @@
+// 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"
- 
-DigitalOut led1(LED1);
-DigitalOut led2(LED2);
-Thread thread;
- 
-void led2_thread() {
+
+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) {
-        led2 = !led2;
-        Thread::wait(1000);
+        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() {
-    thread.start(led2_thread);
+    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) {
-        led1 = !led1;
-        Thread::wait(500);
+        if (pvp_state != -1)
+            game_loop2();
+        else 
+            game_loop();
+        wait(0.005);
     }
-}
+}
\ No newline at end of file