Daniel Martin / Mbed 2 deprecated space_invaders_pvp

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

Files at this revision

API Documentation at this revision

Comitter:
dmartin99
Date:
Sun May 02 19:01:00 2021 +0000
Parent:
11:0309bef74ba8
Commit message:
Working Code of PvP Space Invaders

Changed in this revision

4DGL-uLCD-SE.lib Show annotated file Show diff for this revision Revisions of this file
SongPlayer.h Show annotated file Show diff for this revision Revisions of this file
main.cpp Show annotated file Show diff for this revision Revisions of this file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/4DGL-uLCD-SE.lib	Sun May 02 19:01:00 2021 +0000
@@ -0,0 +1,1 @@
+http://developer.mbed.org/users/4180_1/code/4DGL-uLCD-SE/#e39a44de229a
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/SongPlayer.h	Sun May 02 19:01:00 2021 +0000
@@ -0,0 +1,41 @@
+#include "mbed.h"
+// new class to play a note on Speaker based on PwmOut class
+class SongPlayer
+{
+public:
+    SongPlayer(PinName pin) : _pin(pin) {
+// _pin(pin) means pass pin to the constructor
+    }
+// class method to play a note based on PwmOut class
+    void PlaySong(float frequency[], float duration[], float volume=1.0) {
+        vol = volume;
+        notecount = 0;
+        _pin.period(1.0/frequency[notecount]);
+        _pin = volume/2.0;
+        noteduration.attach(this,&SongPlayer::nextnote, duration[notecount]);
+        // setup timer to interrupt for next note to play
+        frequencyptr = frequency;
+        durationptr = duration;
+        //returns after first note starts to play
+    }
+    void nextnote();
+private:
+    Timeout noteduration;
+    PwmOut _pin;
+    int notecount;
+    float vol;
+    float * frequencyptr;
+    float * durationptr;
+};
+//Interrupt Routine to play next note
+void SongPlayer::nextnote()
+{
+    _pin = 0.0;
+    notecount++; //setup next note in song
+    if (durationptr[notecount]!=0.0) {
+        _pin.period(1.0/frequencyptr[notecount]);
+        noteduration.attach(this,&SongPlayer::nextnote, durationptr[notecount]);
+        _pin = vol/2.0;
+    } else
+        _pin = 0.0; //turn off on last note
+}
--- 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