Retro Invaders a space invaders clone by Chris Favreau. Written for the RetroMbuino development board from outrageouscircuits.com for the game programming contest.

Dependencies:   mbed

This is a space invaders clone written for the Retro Mbuino from outrageous circuits.

Development board: http://www.outrageouscircuits.com/shop/product/15 ).

The game itself is basic space invaders. Shoot them before they get to the bottom of the screen. It has a UFO saucer which you can shoot for extra points. You get 4 shields and each shield can be hit up to 4 times before it is gone. Hmm... as each level increases the speed of the invaders shots goes up. The invaders only speed up when there is less of them. You complete the level when you shoot all the invaders. The game ends when a) you run out of lives (you start with 3) or the invaders get to the bottom.

The LEDs turned out to be a pretty cool addition to the game. I wrote a class that blinks them and turns them on for a specified amount of time. They add a nice extra to the game. I use them on the intro screen and when the UFO is present.

The sound turned out to be really difficult for a few reasons. The biggest was that I had never written a sound engine before. The interrupt service routine working off the timer was the easier part. I also had a lot of trouble because there is no filter to filter out the PWM frequency to the speaker... so I had to run the PWM frequency way up there 30 kHz.

The graphics turned out to be a bit of a bear too. Thanks to Chris Taylor for his really great LCD API. I picked up a couple of frames per second from that. I had modified the DisplayN18 class for blitting a single line buffer to the LCD panel however his is a little faster for some reason? I used a different approach to doing the graphics (as I have very little experience with anything other than double buffered displays). I have a tile map and a list of sprites. Each tile/sprite is 1 bit 8x8. They could be bigger. I ran out of time. That much is not special. What is different from what I can tell is that I use a 1 line buffer that is 160 shorts long. The render function first adds the tile map data into the line buffer first. Then the sprites are added over the existing data. You can have a great deal of different sprites and maps going to the screen and just have to rewrite the LCD memory once per frame. After each line is composited, the line is then drawn to the LCD. Kind of like an Atari 2600. Each sprite/tile has a foreground and background color and can be different from the other tiles/sprites. There is one color reserved for Transparency.

There are 16 colors to choose from. I chose a palette based on the Macintosh OS 4.1 palette I found on WikiPedia. It is a very nice mix of colors.

I found a sprite editor called SpriteX ( https://code.google.com/p/spritesx-ed/ )... it works nicely except that the 16x16 sprites are in a weird format. Time limited me to 8x8 sprites. Oh well.

I used nokring to make the music. It makes RTTTL formatted ring tones which my sound api can play. Here is a useful site that has lots of arcade/video game ring tones with a link to nokring in the utilities page. http://arcadetones.emuunlim.com/files.htm

Other than all that stuff I used state machines to do most of the game logic. Please excuse the horrible coding as I tried to comment a lot of it however it is not very nice to look at. Lots of long functions...

Revision:
0:c79e1f29f029
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Game.cpp	Tue Mar 03 04:26:01 2015 +0000
@@ -0,0 +1,1131 @@
+// RETRO Invaders!
+// Written by Chris Favreau
+// cfavreau@fab-favreau.com
+// for the RetroMbuino development board from outrageouscircuits.com
+//
+
+#include "Game.h"
+
+enum TILE_INDEX
+{
+    eTileBlank,
+    eTilePlayer,
+    eTilePlayerShot,
+    eTileInvaderBolt,
+    eTileShield1,
+    eTileShield2,
+    eTileShield3,
+    eTileShield4,
+    eTileSaucer,
+    eTileInvader1,
+    eTileInvader2,
+    eTileInvader3,
+    eTileInvader4,
+    eTileStarfield1,
+    eTileStarfield2,
+    eTileStarfield3,
+    eTileStarfield4,
+    eTileStarfield5,
+    eTileCount,
+};
+
+enum SPRITE_INDEX       // Up to 64 sprites
+{
+    eSpritePlayer = 0,
+    eSpritePlayerShot,
+    eSpriteShield1,
+    eSpriteShield2,
+    eSpriteShield3,
+    eSpriteShield4,
+    eSpriteSaucer,
+    eSpriteInvaderShot1,
+    eSpriteInvaderShot2,
+    eSpriteInvaderShot3,
+    eSpriteInvaderShot4,
+    eSpriteInvaderStart,
+    
+};
+    
+unsigned char TILE_DATA[]={
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,    // Blank
+0x18,0x3C,0x7E,0x7E,0x7E,0x3C,0x66,0x42,    // Player
+0x00,0x00,0x18,0x3C,0x3C,0x18,0x00,0x00,    // Player Shot
+0x00,0x00,0x10,0x18,0x18,0x08,0x00,0x00,    // Invader Bolt
+0x3C,0x7E,0x7E,0xFF,0xFF,0xFF,0xFF,0x81,    // Shield 1
+0x34,0x76,0x76,0xFF,0xFF,0xFF,0xFF,0x81,    // Shield 2
+0x00,0x52,0x56,0xDF,0xFF,0xFF,0xFF,0x81,    // Shield 3
+0x00,0x00,0x14,0x5D,0x7D,0xFF,0xFF,0x81,    // Shield 4
+0x00,0x3C,0x7E,0xFF,0x7E,0x00,0x00,0x00,    // Saucer 
+0x00,0x3C,0x52,0x3C,0x42,0x24,0x00,0x00,    // Invader 1
+0x00,0x3C,0x4A,0x3C,0x42,0x42,0x00,0x00,    // Invader 2
+0x00,0x3C,0x46,0x3C,0x42,0x81,0x00,0x00,    // Invader 3
+0x00,0x3C,0x62,0x3C,0x42,0x42,0x00,0x00,    // Invader 4
+0x00,0x40,0x00,0x00,0x00,0x04,0x20,0x00,    // Star Field 1
+0x00,0x08,0x00,0x00,0x44,0x00,0x00,0x00,    // Star Field 2
+0x00,0x00,0x00,0x20,0x00,0x00,0x02,0x00,    // Star Field 3
+0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x80,    // Star Field 4
+0x00,0x00,0x00,0x00,0x08,0x00,0x00,0x00};   // Star Field 5
+
+char intro_song[] = "8g5,8f#5,8f5,8e.5,8d#5,8d.5,8c#5,4b4,1p";
+char saucer_song[] = "8f#4,8g4,8f#4,8g4,8f#4,8g4,8f#4,8g4,1p";      // Add a pause at the end... there is a bug in the PlaySong code that skips the last note
+uint16_t invader_march[4] = {NOTE_G3, NOTE_F3, NOTE_E3, NOTE_D3};
+
+Game::Game() : ain(P0_15)
+{
+    initialize();
+}
+
+void Game::initialize()
+{    
+    // Seed the random number generator
+    srand(this->ain.read_u16());
+    
+    // Start the timer
+    timer.start();
+    frame_timeout = timer.read_ms() + MS_PER_FRAME;
+    frame_count = 0;
+    
+    // Setup the customer character buffer with our sprites
+    disp.SetCustomChar(0, TILE_DATA, eTileCount, true);
+    
+    // Start the sound driver
+    sound.Init();
+    sound.SetInstrument(0, SOUND_SQUARE, 255, 10, 0, 0, 0, 0, 0, 0);   // Music?
+    sound.SetInstrument(1, SOUND_NOISE, 255, 8, 0, 0, 0, 0, 0, 0);      // Explosion?
+    sound.SetInstrument(2, SOUND_SQUARE, 255, 0, -50, 0, 0, 0, 0, 0);   // Pew Pew
+    
+    
+    // Set the game mode
+    mode = eGameTitle;
+    // Set the step state machine to 0
+    game_step = 0;
+    // Not paused
+    paused = false;
+    
+    // Initialize the scores
+    score = 0;
+    hiscore = 32;    // TODO - store this value in the onboard EEPROM ... no mbed class to do that at the present... outta time
+    
+    // Initialize the level
+    level = 1;
+    
+    // Initialize our star map
+    star_field_timer = 0;
+}
+
+bool Game::dotick()
+{
+    now = timer.read_ms();
+    if (now < frame_timeout) return false;
+    frame_timeout = now + MS_PER_FRAME;
+    frame_count++;
+    return true;
+}
+
+void Game::tick()
+{
+    if (!dotick()) return;
+    
+    lights.tick();
+    inputs.tick();
+    
+    if (!paused)
+    {
+        switch(mode)
+        {
+        case (eGameTitle): DoGameTitle(); break;
+        case (eGamePlay): DoGamePlay(); break;
+        case (eGameOver): DoGameOver(); break;
+        }
+    }
+    else
+    {
+        disp.printat((CHAR_MAP_WIDTH >> 1) - 6, 8, ".oOPausedOo.");
+        if (inputs.hit(eCircle) || (inputs.hit(eSquare)))
+        {
+            paused = false;
+        }
+    }
+    
+    disp.render();
+}
+
+int ax, ay, avx, avy, af;
+int mx, my;
+bool missle;
+int await;
+int selection = 0;
+
+void Game::DoGameTitle()
+{
+    switch(game_step)
+    {
+    default:
+    case (0):
+        // Setup the title animation sprite
+        disp.AddSprite(0, eTileInvader1, 0, 0, PAL_GREEN, PAL_TRANSPARENT, true);   // Invader
+        disp.AddSprite(1, eTileInvaderBolt, 0, 0, PAL_YELLOW, PAL_TRANSPARENT, true);  // Invader Bolt
+        // Setup some sounds
+        sound.SetInstrument(1, SOUND_NOISE, 255, 8, 0, 0, 0, 0, 0, 0);      // Explosion?
+        sound.SetInstrument(2, SOUND_SQUARE, 255, 0, -50, 0, 0, 0, 0, 0);   // Pew Pew
+        // Setup the sound for the invader
+        sound.SetInstrument(0, SOUND_SQUARE, 200, 0, -1, 0, 0, 0, 255, 100);    // Vibrato + SLide down
+        // Next step
+        game_step++;
+        break;
+    case (1):
+        // Setup the invader's position and frame
+        ax = 16; ay = 8;
+        avx = 1;
+        af = 0;
+        disp.SetSpritePos(0, ax, ay);
+        disp.EnableSprite(0, true);
+        game_step++;
+        break;
+    case (2):
+        // Draw the score (current and hi score)
+        UpdateScore();
+        // Display the title and the invader coming down...
+        disp.clear();
+        disp.printat(10 - 8, 6, "RETRO Invaders!");
+        if (selection == 0) disp.setforecolor(PAL_YELLOW);
+        disp.printat(10 - 3, 8, "%cPlay%c", (selection == 0) ? '>' : ' ', (selection == 0) ? '<' : ' ');
+        disp.setforecolor(PAL_WHITE);
+        if (selection == 1) disp.setforecolor(PAL_YELLOW);
+        disp.printat(10 - 5, 9, "%cSound %s%c", (selection == 1) ? '>' : ' ', sound.IsMuted() ? "Off" : "On ", (selection == 1) ? '<' : ' ');
+        disp.setforecolor(PAL_WHITE);
+        disp.SetSpritePos(0, ax, ay);
+        disp.SetSpriteChar(0, eTileInvader1 + af);
+        af++;
+        if (af > 3) af = 0;
+        ax += avx;
+        if ((ax > 128) || (ax < 16))
+        {
+            missle = true;
+            mx = ax + 4; my = ay;
+            disp.EnableSprite(1, true);
+            avx *= -1;
+            ay += 8;
+            sound.PlaySound(0, NOTE_C4, 40);
+            lights.set(1, true, 5);
+            lights.set(2, true, 5);
+        }
+        if (ay >= 48)
+        {
+            // Hide the missle sprite
+            disp.EnableSprite(1, false);
+            missle = false;
+            // Hide the invader
+            disp.EnableSprite(0, false);
+            // Setup a wait timer count 3 seconds
+            await = 3 * FRAMES_PER_SECOND;
+            // Play an explosion sound
+            sound.PlaySound(1, NOTE_C7, 20);
+            // Setup the leds to blink
+            lights.blink(1, 5, 1, -1);
+            lights.blink(2, 5, 0, -1);
+            // go to the next animated sequence step
+            game_step++;
+        }
+        if (missle)
+        {
+            my +=2;
+            disp.SetSpritePos(1, mx, my);
+            if (my > LCD_HEIGHT)
+            {
+                missle = false;
+                disp.EnableSprite(1, false);
+            }
+        }
+        if (inputs.hit(eCircle) || inputs.hit(eSquare))
+        {
+            sound.PlaySound(0, NOTE_C6, 10);
+            
+            // Do the selection
+            if (selection == 1)
+            {
+                // Toggle the sound on/off
+                sound.Mute(!sound.IsMuted());
+            }
+            if (selection == 0)
+            {
+                sound.SetInstrument(0, SOUND_SQUARE, 255, 0, 0, 0, 0, 0, 0, 0);   // Music?
+                // Play the intro song
+                sound.PlaySong(intro_song, 0, 130, false);
+                // Let's play the game! goto step 4
+                game_step = 4;
+            }
+        }
+        if (inputs.hit(eUp))
+        {
+            selection++;
+            if (selection > 1) selection = 0;
+        }
+        if (inputs.hit(eDown))
+        {
+            selection--;
+            if (selection < 0) selection = 1;
+        }
+        break;
+    case (3):
+        // Flash the game logo red
+        if ((frame_count % FRAMES_PER_SECOND) >= 15)
+            disp.setforecolor(PAL_RED);
+        else
+            disp.setforecolor(PAL_WHITE);
+        disp.clear();
+        disp.printat((CHAR_MAP_WIDTH >> 1) - 8, 6, "RETRO Invaders!");
+        // subtract one from the wait counter
+        await--;
+        if (await < 1)
+        {
+            // Stop the leds from blinking
+            lights.set(1, false, 1);
+            lights.set(2, false, 1);
+            // Set the text color back to white
+            disp.setforecolor(PAL_WHITE);
+            // do the previous animation sequence
+            game_step = 1;
+        }
+        break;
+    case (4):
+        disp.EnableSprite(0, false);    // Hide the invader sprite
+        mode = eGamePlay;
+        game_step = 0;
+        break;
+    }    
+}
+
+void Game::SetupStarfield()
+{
+    uint8_t color = 0;
+    
+    for (int y = 0; y < CHAR_MAP_HEIGHT; y++)
+    for (int x = 0; x < CHAR_MAP_WIDTH; x++)
+    {
+        // Pick random number between 0 and 4
+        uint8_t num = rand() % 5;
+        // Pick a color based on the random number
+        switch(num)
+        {
+        case (0):
+        case (1):
+        case (2):
+            color = PAL_BLUE;
+            break;
+        case (3):
+        case (4):
+            color = PAL_LIGHTBLUE;
+            break;
+        }
+        // Set the startfield tile based on the random number and random color
+        disp.set_map(128 + (eTileStarfield1 + num), x, y, color, PAL_BLACK);
+    }
+}
+
+void Game::UpdateStarfield()
+{
+    // Check to see if it is time to update the starfield
+    star_field_timer++;
+    if (star_field_timer < 5) return;
+    star_field_timer = 0;
+ 
+    uint8_t y = 0;
+    uint8_t color = 0;
+       
+    // Replace the Score and Lives with Star tiles before we scroll
+    // otherwise they will scroll with us...
+    for (int x = 0; x < CHAR_MAP_WIDTH; x++)
+    {
+        // Pick random number between 0 and 4
+        uint8_t num = rand() % 5;
+        // Pick a color based on the random number
+        switch(num)
+        {
+        case (0):
+        case (1):
+        case (2):
+            color = PAL_BLUE;
+            break;
+        case (3):
+        case (4):
+            color = PAL_LIGHTBLUE;
+            break;
+        }
+        // Set the startfield tile based on the random number and random color
+        disp.set_map(128 + (eTileStarfield1 + num), x, 0, color, PAL_BLACK);                    // Top line of the screen
+    }
+    
+    // Scroll the whole field down 1 tile
+    disp.shift_map(eShiftDown);
+    
+    for (int x = 0; x < CHAR_MAP_WIDTH; x++)
+    {
+        // Pick random number between 0 and 4
+        uint8_t num = rand() % 5;
+        // Pick a color based on the random number
+        switch(num)
+        {
+        case (0):
+        case (1):
+        case (2):
+            color = PAL_BLUE;
+            break;
+        case (3):
+        case (4):
+            color = PAL_LIGHTBLUE;
+            break;
+        }
+        // Set the startfield tile based on the random number and random color
+        disp.set_map(128 + (eTileStarfield1 + num), x, y, color, PAL_BLACK);
+    }
+}
+
+void Game::SetupInvaders()
+{
+    invaders_x = 10;
+    invaders_y = 10;
+    invaders_inc = 4;
+    invaders_frame = 0;
+    invaders_timer = 0;
+    active_invaders = 40;
+    invaders_wait_time = 5;
+    uint8_t color = 0;
+    for (int i = 0; i < 40; i++)
+    {
+        switch(i / 8)
+        {
+        case (0): color = PAL_WHITE; break;
+        case (1): color = PAL_YELLOW; break;
+        case (2): color = PAL_ORANGE; break;
+        case (3): color = PAL_RED; break;
+        case (4): color = PAL_GREEN; break;
+        case (5): color = PAL_LIGHTBLUE; break;
+        case (6): color = PAL_BLUE; break;
+        case (7): color = PAL_MAGENTA; break;
+        }
+        
+        uint8_t x = invaders_x + ((i % 8) * 10);
+        uint8_t y = invaders_y + ((i / 8) * 10);
+        disp.AddSprite(eSpriteInvaderStart + i, eTileInvader1, x, y, color, PAL_TRANSPARENT);
+        invaders[i] = true;
+    }
+    
+    for (int i = 0; i < 4; i++)
+    {
+        invader_active_shot[i] = false;
+        invader_shot_x[i] = 0;
+        invader_shot_y[i] = 0;
+        disp.AddSprite(eSpriteInvaderShot1 + i, eTileInvaderBolt, invader_shot_x[i], invader_shot_y[i], PAL_WHITE, PAL_TRANSPARENT);
+    }
+    invader_shot_interval = FRAMES_PER_SECOND;
+    invader_shot_timer = invader_shot_interval;
+    invader_march_note = 0;
+}
+
+void Game::UpdateInvaders()
+{
+    // Check to see if it is time to advance the frame
+    invaders_timer++;
+    if (invaders_timer > invaders_wait_time)
+    {
+        invaders_timer = 0;
+        invaders_frame++;
+        if (invaders_frame > 3) invaders_frame = 0; 
+        // Play the invader march sound
+        if ((invaders_frame == 0) || (invaders_frame == 2))
+        {
+            sound.PlaySound(0, invader_march[invader_march_note++], 10);
+            if (invader_march_note >= 4) invader_march_note = 0;
+        }
+        // Move the invaders
+        invaders_x += invaders_inc;
+    }
+    // Update all of the invaders
+    bool move_down = false;
+    for (int i = 0; i < 40; i++)
+    {
+        // Check to see if this invader is active and not blown up
+        if (invaders[i])
+        {
+            // Calculate the position of this invader
+            uint8_t x = invaders_x + ((i % 8) * 10);
+            uint8_t y = invaders_y + ((i / 8) * 10);
+            
+            // Check to see if the invader has reach the edge of the play area
+            if (x >= 150) move_down = true;
+            if (x < 10) move_down = true;
+        
+            disp.SetSpriteChar(eSpriteInvaderStart + i, eTileInvader1 + invaders_frame);
+            disp.SetSpritePos(eSpriteInvaderStart + i, x, y);
+        }
+        disp.EnableSprite(eSpriteInvaderStart + i, invaders[i]);
+    }
+    // Check to see if we should move the invaders down one notch
+    if (move_down)
+    {
+        invaders_y += 5;    // Half the height of the invader space (10 x 10?)
+        invaders_inc *= -1;
+        invaders_x += invaders_inc;
+        // Increase the invader speed if we need to
+        if (active_invaders <= 30) invaders_wait_time = 4;
+        if (active_invaders <= 20) invaders_wait_time = 3;
+        if (active_invaders <= 10) invaders_wait_time = 2;
+        if (active_invaders <= 5) invaders_wait_time = 1;
+        if (active_invaders <= 2) invaders_wait_time = 0;
+    }
+        
+    // Service any active shots
+    for (int shot = 0; shot < 4; shot++)
+    {
+        if (invader_active_shot[shot])
+        {
+            // Advance downwards
+            invader_shot_y[shot] += (2 * level);    // Speed the shots up the higher up we get in the levels ... that is the difficulty setting
+            disp.SetSpritePos(eSpriteInvaderShot1 + shot, invader_shot_x[shot], invader_shot_y[shot]);
+            // Check to see if we are off the screen
+            if (invader_shot_y[shot] > (LCD_HEIGHT - 4))
+            {
+                invader_active_shot[shot] = false;
+                disp.EnableSprite(eSpriteInvaderShot1 + shot, false);
+            }
+        }
+    }
+    // Do the invader shooting
+    invader_shot_timer--;
+    if (invader_shot_timer < 0)
+    {
+        // Reset the shot timer
+        invader_shot_timer = invader_shot_interval;
+        // Find an inactive shot to use (up to 4 active at a time)
+        int shot;
+        for (shot = 0; shot < 4; shot++)
+        {
+            if (!invader_active_shot[shot])
+            {
+                // Pick a random invader for it to come from (there are 40 of them)
+                int invader = rand() % active_invaders;
+                // Figure out the active invader's coordinates
+                for (int i = 0; i < 40; i++)
+                {
+                    int index = (i + invader) % 40;
+                    if (invaders[index])
+                    {
+                        uint8_t color = 0;
+                        switch(index / 8)
+                        {
+                        case (0): color = PAL_WHITE; break;
+                        case (1): color = PAL_YELLOW; break;
+                        case (2): color = PAL_ORANGE; break;
+                        case (3): color = PAL_RED; break;
+                        case (4): color = PAL_GREEN; break;
+                        case (5): color = PAL_LIGHTBLUE; break;
+                        case (6): color = PAL_BLUE; break;
+                        case (7): color = PAL_MAGENTA; break;
+                        }
+                        // Initialize the shot
+                        invader_active_shot[shot] = true;
+                        invader_shot_x[shot] = invaders_x + ((index % 8) * 10) + 4;
+                        invader_shot_y[shot] = invaders_y + ((index / 8) * 10) + 4;
+                        disp.EnableSprite(eSpriteInvaderShot1 + shot, true);
+                        disp.SetSpriteColor(eSpriteInvaderShot1 + shot, color, PAL_TRANSPARENT);
+                        disp.SetSpritePos(eSpriteInvaderShot1 + shot, invader_shot_x[shot], invader_shot_y[shot]);
+                        sound.PlaySound(2, NOTE_C7, 20);
+                        break;
+                    }
+                }
+                break;
+            }
+        }
+    }
+}
+
+void Game::SetupPlayer()
+{
+    player_x = LCD_WIDTH >> 1;
+    player_y = LCD_HEIGHT - 10;
+    disp.AddSprite(eSpritePlayer, eTilePlayer, player_x, player_y, PAL_LIGHTGREY, PAL_TRANSPARENT, true);   // auto midhandle
+    player_shot_active = false;
+    player_shot_x = 0;
+    player_shot_y = 0;
+    disp.AddSprite(eSpritePlayerShot, eTilePlayerShot, player_shot_x, player_shot_y, PAL_WHITE, PAL_TRANSPARENT, true); // auto midhandle
+}
+
+void Game::UpdatePlayer()
+{
+    // Check to see if the left and right buttons have been hit
+    if (inputs.pressed(eLeft))
+    {
+        player_x -= 2;
+        if (player_x < 5) player_x = 5;
+    }
+    if (inputs.pressed(eRight))
+    {
+        player_x += 2;
+        if (player_x > (LCD_WIDTH - 5)) player_x = (LCD_WIDTH - 5); 
+    }
+    disp.SetSpritePos(eSpritePlayer, player_x, player_y);
+    if ((inputs.hit(eSquare) || inputs.hit(eCircle)) && !player_shot_active)
+    {
+        player_shot_x = player_x;
+        player_shot_y = player_y;
+        player_shot_active = true;
+        disp.EnableSprite(eSpritePlayerShot, true);
+        sound.PlaySound(2, NOTE_C7, 20);
+    }
+    if (inputs.pressed(eSquare) && inputs.pressed(eCircle))
+    {
+        // Pause the game
+        paused = true;
+    }
+    if (player_shot_active)
+    {
+        player_shot_y -= 5;
+        if (player_shot_y < 5)
+        {
+            player_shot_active = false;
+            disp.EnableSprite(eSpritePlayerShot, false);
+        }
+        disp.SetSpritePos(eSpritePlayerShot, player_shot_x, player_shot_y);
+    }
+}
+
+void Game::SetupSaucer()
+{
+    saucer_x = 0;
+    saucer_y = 10;
+    disp.AddSprite(eSpriteSaucer, eTileSaucer, saucer_x, saucer_y, PAL_YELLOW, PAL_TRANSPARENT, true);
+    disp.EnableSprite(eSpriteSaucer, false);
+    saucer_timer = (rand() % 10) * FRAMES_PER_SECOND;  // Random time every 10 seconds
+    saucer_active = false;
+}
+
+void Game::UpdateSaucer()
+{
+    if (!saucer_active)
+    {
+        saucer_timer--;
+        if (saucer_timer > 1) return;
+        // Calculate a new time for the saucer to be active (after it completes its sequence)
+        saucer_timer = (rand() % 10) * FRAMES_PER_SECOND;  // Random time every 10 seconds
+        // Time to go.. setup the saucer to go
+        // Set the saucer active flag
+        saucer_active = true;
+        // Set the saucer movement increment (same speed and opposite direction as the invaders are currently moving)
+        saucer_inc = invaders_inc * -1;
+        // Set the saucers starting coordiantes
+        if (saucer_inc > 0)
+            saucer_x = 0;               // left to right movement
+        else
+            saucer_x = LCD_WIDTH - 4;   // right to left movement
+        // Set the sprite position and enable the sprite
+        disp.SetSpritePos(eSpriteSaucer, saucer_x, saucer_y);
+        disp.EnableSprite(eSpriteSaucer, true);
+        // Play the saucer song (looped)
+        sound.PlaySong(saucer_song, 0, 130, true);
+        // Setup the leds to blink
+        lights.blink(1, 5, 1, -1);
+        lights.blink(2, 5, 0, -1);
+        // Set the saucer timer .. we will use it to divide the frame counter
+        saucer_timer = 0;
+        return;
+    }
+    
+    // Divide the frame counter to slow things down (same speed as invaders)
+    saucer_timer++;
+    if (saucer_timer < 5) return;
+    saucer_timer = 0;
+    
+    // Move the saucer
+    saucer_x += saucer_inc;
+    if ((saucer_x < 1) || (saucer_x > (LCD_WIDTH - 1)))
+    {
+        lights.set(1, false, 1);
+        lights.set(2, false, 1);
+        saucer_active = false;
+        disp.EnableSprite(eSpriteSaucer, false);
+        sound.StopSong();
+        saucer_timer = (rand() % 10) * FRAMES_PER_SECOND;  // Random time every 10 seconds
+    }
+    disp.SetSpritePos(eSpriteSaucer, saucer_x, saucer_y);
+}
+
+void Game::SetupShields()
+{
+    shield_x = LCD_WIDTH >> 1;
+    shield_y = LCD_HEIGHT - 20;
+    disp.AddSprite(eSpriteShield1, eTileShield1, shield_x - 45, shield_y, PAL_GREEN, PAL_TRANSPARENT, true);
+    disp.AddSprite(eSpriteShield2, eTileShield1, shield_x - 15, shield_y, PAL_GREEN, PAL_TRANSPARENT, true);
+    disp.AddSprite(eSpriteShield3, eTileShield1, shield_x + 15, shield_y, PAL_GREEN, PAL_TRANSPARENT, true);
+    disp.AddSprite(eSpriteShield4, eTileShield1, shield_x + 45, shield_y, PAL_GREEN, PAL_TRANSPARENT, true);
+    for (int i = 0; i < 4; i++)
+        shield_points[i] = 4;
+}
+
+void Game::UpdateShields()
+{
+    // Todo - they don't move but they have hit points...
+    for (int i = 0; i < 4; i++)
+    {
+        disp.SetSpriteChar(eSpriteShield1 + i, eTileShield1 + (4 - shield_points[i]));
+    }
+}
+
+void Game::CheckCollisions()
+{
+    // Check the player's shot to see if it hits any:
+    // Invader
+    for (int i = 0; i < 40; i++)
+    {
+        // Check to see if this invader is active
+        if (invaders[i])
+        {
+            if (disp.SpriteCollision(eSpritePlayerShot, eSpriteInvaderStart + i))
+            {
+                // Destroy the invader
+                disp.EnableSprite(eSpriteInvaderStart + i, false);
+                invaders[i] = false;
+                active_invaders--;
+                // Destroy the player's shot
+                sound.PlaySound(1, NOTE_C6, 30);
+                disp.EnableSprite(eSpritePlayerShot, false);
+                player_shot_active = false;
+                // Increment the score (more points for the ones at the top)
+                // 5 points for the top row, 4 points for the next, etc...
+                IncScore(5 - (i / 8));
+                // Check to see if all of the invaders were destroyed
+                if (active_invaders < 1)
+                {
+                    // Level Up and go to the next step (for going to the next level)
+                    level++;
+                    game_step = 2;
+                }
+            }
+        }
+    }
+    // Shield
+    for (int i = 0; i < 4; i++)
+    {
+        if (disp.SpriteCollision(eSpritePlayerShot, eSpriteShield1 + i))
+        {
+            // Subtract 1 from the shield's points
+            shield_points[i]--;
+            if (shield_points[i] < 1)
+            {
+                // Disable this shield
+                disp.EnableSprite(eSpriteShield1 + i, false);
+                // TODO - Maybe make a noise...
+            }
+            // Destroy the player's shot
+            sound.PlaySound(1, NOTE_C6, 30);
+            disp.EnableSprite(eSpritePlayerShot, false);
+            player_shot_active = false;
+        }
+    }
+    // Invader Shot
+    for (int i = 0; i < 4; i++)
+    {
+        if (disp.SpriteCollision(eSpritePlayerShot, eSpriteInvaderShot1 + i))
+        {
+            // Disable this invader shot
+            invader_active_shot[i] = false;
+            disp.EnableSprite(eSpriteInvaderShot1 + i, false);
+            // Destroy the player's shot
+            sound.PlaySound(1, NOTE_C6, 30);
+            disp.EnableSprite(eSpritePlayerShot, false);
+            player_shot_active = false;
+            // 1 point for shooting an invader bullet
+            IncScore(1);
+        }
+    }
+    // Saucer
+    if (disp.SpriteCollision(eSpritePlayerShot, eSpriteSaucer))
+    {
+        // Destroy the saucer
+        lights.set(1, false, 1);
+        lights.set(2, false, 1);
+        saucer_active = false;
+        disp.EnableSprite(eSpriteSaucer, false);
+        sound.StopSong();
+        saucer_timer = (rand() % 10) * FRAMES_PER_SECOND;  // Random time every 10 seconds
+        // Destroy the player's shot
+        sound.PlaySound(1, NOTE_C6, 30);
+        disp.EnableSprite(eSpritePlayerShot, false);
+        player_shot_active = false;
+        // 10 points for a saucer
+        IncScore(10);
+    }
+    
+    // Check to see if the invader's shots hit any:
+    // Shields
+    // Player Ships
+    for (int shot = 0; shot < 4; shot++)
+    {
+        if (!invader_active_shot[shot]) continue;
+        
+        // Check the player
+        if (disp.SpriteCollision(eSpritePlayer, eSpriteInvaderShot1 + shot))
+        {
+            // Play the Explosion sound
+            sound.PlaySound(1, NOTE_C6, 30);
+            // Destroy the shot
+            invader_active_shot[shot] = false;
+            disp.EnableSprite(eSpriteInvaderShot1 + shot, false);
+            // Decrement the player lives
+            lives--;
+            // Go to the player blow up sequence
+            game_step = 4; // we will check to see if the lives are 0 there!
+        }
+        
+        // Check the shields
+        for (int shield = 0; shield < 4; shield++)
+        {
+            if (disp.SpriteCollision(eSpriteInvaderShot1 + shot, eSpriteShield1 + shield))
+            {
+                // Subtract 1 from the shield's points
+                shield_points[shield]--;
+                if (shield_points[shield] < 1)
+                {
+                    // Disable this shield
+                    disp.EnableSprite(eSpriteShield1 + shield, false);
+                    // TODO - Maybe make a noise...
+                }
+                sound.PlaySound(1, NOTE_C6, 30);
+                // Destroy the shot
+                invader_active_shot[shot] = false;
+                disp.EnableSprite(eSpriteInvaderShot1 + shot, false);
+            }
+        } 
+    }
+    // Check to see if the invaders have reached the shields
+    // Or if the invaders have reached the ship!
+    for (int i = 0 ; i < 40; i++)
+    {
+        // Check only active invaders
+        if (!invaders[i]) continue;
+        
+        for (int shield = 0; shield < 4; shield++)
+        {
+            // Check only active shields
+            if (shield_points[shield] < 1) continue;
+            if (disp.SpriteCollision(eSpriteInvaderStart + i, eSpriteShield1 + shield))
+            {
+                // Destroy the shield
+                shield_points[shield] = 0;
+                disp.EnableSprite(eSpriteShield1 + shield, false);
+                sound.PlaySound(1, NOTE_C6, 10);
+            }
+        }
+        
+        if (disp.SpriteCollision(eSpriteInvaderStart + i, eSpritePlayer))
+        {
+            // Game Over!!!!
+            lives = 0;
+           // Go to the player blow up sequence
+            game_step = 4; // we will check to see if the lives are 0 there!
+        }
+    }
+}
+
+void Game::IncScore(int points)
+{
+    // Increment the score
+    score += points;
+    // Update the hi score if necessary
+    if (score > hiscore) hiscore = score;
+}
+
+void Game::UpdateScore()
+{
+    // Hi Score in the middle
+    disp.printat(7, 0, "HI%04d", hiscore);
+    // Current Score on the left
+    disp.printat(0, 0, "%04d", score);
+}
+
+void Game::UpdateLives()
+{
+    // Men left in the upper right
+    if (lives >= 1) disp.setcharat(CHAR_MAP_WIDTH - 1, 0, 128 + eTilePlayer, PAL_LIGHTBLUE, PAL_BLACK);
+    if (lives >= 2) disp.setcharat(CHAR_MAP_WIDTH - 2, 0, 128 + eTilePlayer, PAL_LIGHTBLUE, PAL_BLACK);
+    if (lives >= 3) disp.setcharat(CHAR_MAP_WIDTH - 3, 0, 128 + eTilePlayer, PAL_LIGHTBLUE, PAL_BLACK);
+}
+
+void Game::DoGamePlay()
+{
+    switch(game_step)
+    {
+    case (0):
+        disp.clear();
+        // Setup the score and lives
+        score = 0;
+        lives = 3;
+        // DEBUG
+        //mode = eGameOver; break;
+        // Sprites
+        // Invaders ( 8 x 5 = 40 total - starts at sprite #10)
+        SetupInvaders();
+        // Shields  ( 3 shields)
+        SetupShields();
+        // Player     ( 1 player)
+        SetupPlayer();
+        // Saucer   ( 1 saucer)
+        SetupSaucer();
+        // Setup the playfield
+        SetupStarfield();
+        // Draw the taunt
+        disp.printat((CHAR_MAP_WIDTH >> 1) - 8, 8, "We Are Coming...");
+        // DEBUG - kill most of the invaders
+        /*(
+        for (int i = 0; i < 35; i++)
+        {
+            invaders[i] = false;
+            active_invaders--;
+        }
+        */
+        // Next step
+        game_step = 1;
+        break;
+    case (1):
+        // This is the main game loop here
+        // Check for Input
+        // Update the star field
+        UpdateStarfield();
+        // Move Invaders
+        UpdateInvaders();
+        // Move Player
+        UpdatePlayer();
+        // Update the shields
+        UpdateShields();
+        // Update the saucer
+        UpdateSaucer();
+        // Update the rest of the screen
+        UpdateScore();
+        UpdateLives();
+        // Make things shoot each other
+        // Check for collisions
+        CheckCollisions();
+        // Check for Game Over
+        
+        // dEBUG
+        //disp.printat(0, 15, "%d", active_invaders);
+        break;
+    case (2):
+        // Next Level
+        UpdateStarfield();
+        UpdateScore();
+        UpdateLives();
+        // Play the intro song
+        sound.PlaySong(intro_song, 0, 130, false);
+        // Print out a taunt
+        disp.printat((CHAR_MAP_WIDTH >> 1) - 3, 8, "Ready?");
+        // Set the wait timer
+        wait_timer = 20;
+        // Next step to do the waiting
+        game_step = 3;
+        break;
+    case (3):
+        // Wait a few ticks before putting the invaders up
+        UpdateStarfield();
+        UpdateScore();
+        UpdateLives();
+        wait_timer--;
+        if (wait_timer == 0)
+        {
+            // Initialize the board again
+            // Sprites
+            // Invaders ( 8 x 5 = 40 total - starts at sprite #10)
+            SetupInvaders();
+            // Shields  ( 3 shields)
+            SetupShields();
+            // Player     ( 1 player)
+            SetupPlayer();
+            // Saucer   ( 1 saucer)
+            SetupSaucer();
+            // Setup the playfield
+            SetupStarfield();
+            // Draw the taunt
+            disp.printat((CHAR_MAP_WIDTH >> 1) - 8, 8, "We Are Coming...");
+            // Next step
+            game_step = 1;
+        }
+        break;
+    case (4):
+        // Set the wait timer
+        wait_timer = 20;
+        // Play a LOOONG blow up sound
+        sound.PlaySound(1, NOTE_C6, 60);
+        // Taunt the player!
+        disp.setforecolor(PAL_RED);
+        disp.printat((CHAR_MAP_WIDTH >> 1) - 4, 8, "Winning!");
+        disp.setforecolor(PAL_WHITE);
+        // Next step to wait and do the player explosion sequence
+        game_step = 5;
+        break;
+    case (5):
+        // Do the player blow up sequence
+        disp.SetSpriteChar(eSpritePlayer, eTileStarfield1 + (wait_timer % 3));
+        disp.SetSpriteColor(eSpritePlayer, PAL_RED, PAL_TRANSPARENT);
+        wait_timer--;
+        // When we are done go back to the play the game step
+        if (wait_timer < 1)
+        {
+            game_step = 1;
+            // Set the player sprite back to normal
+            disp.SetSpriteChar(eSpritePlayer, eTilePlayer);
+            disp.SetSpriteColor(eSpritePlayer, PAL_GREY, PAL_TRANSPARENT);
+            // check to see if the lives have reached 0
+            if (lives < 1)
+            {
+                // Disable the player sprite
+                disp.EnableSprite(eSpritePlayer, false);
+                // Switch the mode to Game Over
+                game_step = 0;
+                mode = eGameOver;
+                // The Invaders have Arrived!
+            }
+        }
+        break;
+    }
+}
+
+void Game::UpdateGameOverInvaders()
+{
+    uint8_t color = 0;
+    
+    // Scroll the whole field up 1 tile
+    disp.shift_map(eShiftUp);
+    
+    // Pick a color    
+    switch(invaders_frame % 8)
+    {
+    case (0): color = PAL_WHITE; break;
+    case (1): color = PAL_YELLOW; break;
+    case (2): color = PAL_ORANGE; break;
+    case (3): color = PAL_RED; break;
+    case (4): color = PAL_GREEN; break;
+    case (5): color = PAL_LIGHTBLUE; break;
+    case (6): color = PAL_BLUE; break;
+    case (7): color = PAL_MAGENTA; break;
+    }
+
+    // Fill the top with invaders of a color
+    for (int i = 0; i < CHAR_MAP_WIDTH; i++)
+    {
+        disp.setcharat(i, CHAR_MAP_HEIGHT - 1, 128 + eTileInvader1 + (invaders_frame % 4), color, PAL_BLACK);
+    }
+    
+    // Next frame
+    invaders_frame++;
+}
+
+void Game::DoGameOver()
+{
+    switch (game_step)
+    {
+    case (0):
+        // Disable all the sprites
+        for (int i = 0; i < MAX_SPRITES; i++)
+            disp.EnableSprite(i, false);
+        // Turn off the leds
+        lights.set(1, false, 1);
+        lights.set(2, false, 1);
+        // Stop the song
+        sound.StopSong();
+        // initialize the game over screen stuff...
+        // play the game over tune
+        wait_timer = 0;
+        star_field_timer = 0;
+        invader_march_note = 0;
+        invaders_frame = 0;
+        game_over_msg = 0;
+        // Setup the coin sound
+        sound.SetInstrument(2, SOUND_SQUARE, 128, 10, 0, 120, 6, 0, 0, 0);    // Coin Sound (Change + Decay)
+        // Next step
+        game_step = 1;
+        break;
+    case (1):
+        // Scroll the screen down and fill with invaders
+        // While playing the march
+        
+        // Use the starfield timer to slow down the scroll
+        star_field_timer++;
+        if (star_field_timer < 10) return;
+        star_field_timer = 0;
+    
+        UpdateGameOverInvaders();
+        
+        // Play the march sound
+        sound.PlaySound(0, invader_march[invader_march_note++], 10);
+        if (invader_march_note >= 4) invader_march_note = 0;
+        
+        // Subtract one from the wait timer
+        wait_timer++;
+        if (wait_timer >= CHAR_MAP_HEIGHT)
+        {
+            game_step = 2;
+            wait_timer = 0;
+        }
+        break;
+    case (2):
+        // Empty step
+        // Clear the wait timer
+        wait_timer = 0;
+        // Next step
+        game_step = 3;
+        break;
+    case (3):
+    
+        // Use the starfield timer to slow down the scroll
+        star_field_timer++;
+        if (star_field_timer < 10) return;
+        star_field_timer = 0;
+        
+        UpdateGameOverInvaders();
+        
+        wait_timer++;
+        if (wait_timer > 3)
+        {
+            wait_timer = 0;
+            game_step = 4;
+        }
+        // Wait a few frames... then print a thanks to outrageous circuits / GHI
+        break;
+    case (4):
+    {
+        char msg[16] = "";
+        
+        switch (game_over_msg)
+        {
+        case (0): sprintf(msg, "The Invaders..."); break;
+        case (1): sprintf(msg, "Have Arrived!"); break;
+        case (2): sprintf(msg, "Game Over!"); break;
+        case (3): sprintf(msg, "Thanks To:"); break;
+        case (4): sprintf(msg, "outrageous"); break;
+        case (5): sprintf(msg, "circuits.com"); break;
+        case (6): sprintf(msg, "GHI"); break;
+        case (7): sprintf(msg, ""); break;
+        case (8): sprintf(msg, "written by"); break;
+        case (9): sprintf(msg, "cfavreau"); break;
+        case (10): game_step = 5; return;
+        }
+        uint8_t len = strlen(msg);
+        disp.printat((CHAR_MAP_WIDTH >> 1) - (len >> 1), CHAR_MAP_HEIGHT - 1, msg);
+        // Play the coin sound
+        sound.PlaySound(2, NOTE_Fs6, 20);
+        // Next message
+        game_over_msg++;
+        game_step = 3;
+    }
+        break;
+    default:
+    case (5):
+        // Reset the game to the title sequence
+        mode = eGameTitle;
+        game_step = 0;
+        // Clear the button hits
+        inputs.clear();
+        break;
+    }
+}
+