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

Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers Game.cpp Source File

Game.cpp

00001 // RETRO Invaders!
00002 // Written by Chris Favreau
00003 // cfavreau@fab-favreau.com
00004 // for the RetroMbuino development board from outrageouscircuits.com
00005 //
00006 
00007 #include "Game.h"
00008 
00009 enum TILE_INDEX
00010 {
00011     eTileBlank,
00012     eTilePlayer,
00013     eTilePlayerShot,
00014     eTileInvaderBolt,
00015     eTileShield1,
00016     eTileShield2,
00017     eTileShield3,
00018     eTileShield4,
00019     eTileSaucer,
00020     eTileInvader1,
00021     eTileInvader2,
00022     eTileInvader3,
00023     eTileInvader4,
00024     eTileStarfield1,
00025     eTileStarfield2,
00026     eTileStarfield3,
00027     eTileStarfield4,
00028     eTileStarfield5,
00029     eTileCount,
00030 };
00031 
00032 enum SPRITE_INDEX       // Up to 64 sprites
00033 {
00034     eSpritePlayer = 0,
00035     eSpritePlayerShot,
00036     eSpriteShield1,
00037     eSpriteShield2,
00038     eSpriteShield3,
00039     eSpriteShield4,
00040     eSpriteSaucer,
00041     eSpriteInvaderShot1,
00042     eSpriteInvaderShot2,
00043     eSpriteInvaderShot3,
00044     eSpriteInvaderShot4,
00045     eSpriteInvaderStart,
00046     
00047 };
00048     
00049 unsigned char TILE_DATA[]={
00050 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,    // Blank
00051 0x18,0x3C,0x7E,0x7E,0x7E,0x3C,0x66,0x42,    // Player
00052 0x00,0x00,0x18,0x3C,0x3C,0x18,0x00,0x00,    // Player Shot
00053 0x00,0x00,0x10,0x18,0x18,0x08,0x00,0x00,    // Invader Bolt
00054 0x3C,0x7E,0x7E,0xFF,0xFF,0xFF,0xFF,0x81,    // Shield 1
00055 0x34,0x76,0x76,0xFF,0xFF,0xFF,0xFF,0x81,    // Shield 2
00056 0x00,0x52,0x56,0xDF,0xFF,0xFF,0xFF,0x81,    // Shield 3
00057 0x00,0x00,0x14,0x5D,0x7D,0xFF,0xFF,0x81,    // Shield 4
00058 0x00,0x3C,0x7E,0xFF,0x7E,0x00,0x00,0x00,    // Saucer 
00059 0x00,0x3C,0x52,0x3C,0x42,0x24,0x00,0x00,    // Invader 1
00060 0x00,0x3C,0x4A,0x3C,0x42,0x42,0x00,0x00,    // Invader 2
00061 0x00,0x3C,0x46,0x3C,0x42,0x81,0x00,0x00,    // Invader 3
00062 0x00,0x3C,0x62,0x3C,0x42,0x42,0x00,0x00,    // Invader 4
00063 0x00,0x40,0x00,0x00,0x00,0x04,0x20,0x00,    // Star Field 1
00064 0x00,0x08,0x00,0x00,0x44,0x00,0x00,0x00,    // Star Field 2
00065 0x00,0x00,0x00,0x20,0x00,0x00,0x02,0x00,    // Star Field 3
00066 0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x80,    // Star Field 4
00067 0x00,0x00,0x00,0x00,0x08,0x00,0x00,0x00};   // Star Field 5
00068 
00069 char intro_song[] = "8g5,8f#5,8f5,8e.5,8d#5,8d.5,8c#5,4b4,1p";
00070 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
00071 uint16_t invader_march[4] = {NOTE_G3, NOTE_F3, NOTE_E3, NOTE_D3};
00072 
00073 Game::Game() : ain(P0_15)
00074 {
00075     initialize();
00076 }
00077 
00078 void Game::initialize()
00079 {    
00080     // Seed the random number generator
00081     srand(this->ain.read_u16());
00082     
00083     // Start the timer
00084     timer.start();
00085     frame_timeout = timer.read_ms() + MS_PER_FRAME;
00086     frame_count = 0;
00087     
00088     // Setup the customer character buffer with our sprites
00089     disp.SetCustomChar(0, TILE_DATA, eTileCount, true);
00090     
00091     // Start the sound driver
00092     sound.Init();
00093     sound.SetInstrument(0, SOUND_SQUARE, 255, 10, 0, 0, 0, 0, 0, 0);   // Music?
00094     sound.SetInstrument(1, SOUND_NOISE, 255, 8, 0, 0, 0, 0, 0, 0);      // Explosion?
00095     sound.SetInstrument(2, SOUND_SQUARE, 255, 0, -50, 0, 0, 0, 0, 0);   // Pew Pew
00096     
00097     
00098     // Set the game mode
00099     mode = eGameTitle;
00100     // Set the step state machine to 0
00101     game_step = 0;
00102     // Not paused
00103     paused = false;
00104     
00105     // Initialize the scores
00106     score = 0;
00107     hiscore = 32;    // TODO - store this value in the onboard EEPROM ... no mbed class to do that at the present... outta time
00108     
00109     // Initialize the level
00110     level = 1;
00111     
00112     // Initialize our star map
00113     star_field_timer = 0;
00114 }
00115 
00116 bool Game::dotick()
00117 {
00118     now = timer.read_ms();
00119     if (now < frame_timeout) return false;
00120     frame_timeout = now + MS_PER_FRAME;
00121     frame_count++;
00122     return true;
00123 }
00124 
00125 void Game::tick()
00126 {
00127     if (!dotick()) return;
00128     
00129     lights.tick();
00130     inputs.tick();
00131     
00132     if (!paused)
00133     {
00134         switch(mode)
00135         {
00136         case (eGameTitle): DoGameTitle(); break;
00137         case (eGamePlay): DoGamePlay(); break;
00138         case (eGameOver): DoGameOver(); break;
00139         }
00140     }
00141     else
00142     {
00143         disp.printat((CHAR_MAP_WIDTH >> 1) - 6, 8, ".oOPausedOo.");
00144         if (inputs.hit(eCircle) || (inputs.hit(eSquare)))
00145         {
00146             paused = false;
00147         }
00148     }
00149     
00150     disp.render();
00151 }
00152 
00153 int ax, ay, avx, avy, af;
00154 int mx, my;
00155 bool missle;
00156 int await;
00157 int selection = 0;
00158 
00159 void Game::DoGameTitle()
00160 {
00161     switch(game_step)
00162     {
00163     default:
00164     case (0):
00165         // Setup the title animation sprite
00166         disp.AddSprite(0, eTileInvader1, 0, 0, PAL_GREEN, PAL_TRANSPARENT, true);   // Invader
00167         disp.AddSprite(1, eTileInvaderBolt, 0, 0, PAL_YELLOW, PAL_TRANSPARENT, true);  // Invader Bolt
00168         // Setup some sounds
00169         sound.SetInstrument(1, SOUND_NOISE, 255, 8, 0, 0, 0, 0, 0, 0);      // Explosion?
00170         sound.SetInstrument(2, SOUND_SQUARE, 255, 0, -50, 0, 0, 0, 0, 0);   // Pew Pew
00171         // Setup the sound for the invader
00172         sound.SetInstrument(0, SOUND_SQUARE, 200, 0, -1, 0, 0, 0, 255, 100);    // Vibrato + SLide down
00173         // Next step
00174         game_step++;
00175         break;
00176     case (1):
00177         // Setup the invader's position and frame
00178         ax = 16; ay = 8;
00179         avx = 1;
00180         af = 0;
00181         disp.SetSpritePos(0, ax, ay);
00182         disp.EnableSprite(0, true);
00183         game_step++;
00184         break;
00185     case (2):
00186         // Draw the score (current and hi score)
00187         UpdateScore();
00188         // Display the title and the invader coming down...
00189         disp.clear();
00190         disp.printat(10 - 8, 6, "RETRO Invaders!");
00191         if (selection == 0) disp.setforecolor(PAL_YELLOW);
00192         disp.printat(10 - 3, 8, "%cPlay%c", (selection == 0) ? '>' : ' ', (selection == 0) ? '<' : ' ');
00193         disp.setforecolor(PAL_WHITE);
00194         if (selection == 1) disp.setforecolor(PAL_YELLOW);
00195         disp.printat(10 - 5, 9, "%cSound %s%c", (selection == 1) ? '>' : ' ', sound.IsMuted() ? "Off" : "On ", (selection == 1) ? '<' : ' ');
00196         disp.setforecolor(PAL_WHITE);
00197         disp.SetSpritePos(0, ax, ay);
00198         disp.SetSpriteChar(0, eTileInvader1 + af);
00199         af++;
00200         if (af > 3) af = 0;
00201         ax += avx;
00202         if ((ax > 128) || (ax < 16))
00203         {
00204             missle = true;
00205             mx = ax + 4; my = ay;
00206             disp.EnableSprite(1, true);
00207             avx *= -1;
00208             ay += 8;
00209             sound.PlaySound(0, NOTE_C4, 40);
00210             lights.set(1, true, 5);
00211             lights.set(2, true, 5);
00212         }
00213         if (ay >= 48)
00214         {
00215             // Hide the missle sprite
00216             disp.EnableSprite(1, false);
00217             missle = false;
00218             // Hide the invader
00219             disp.EnableSprite(0, false);
00220             // Setup a wait timer count 3 seconds
00221             await = 3 * FRAMES_PER_SECOND;
00222             // Play an explosion sound
00223             sound.PlaySound(1, NOTE_C7, 20);
00224             // Setup the leds to blink
00225             lights.blink(1, 5, 1, -1);
00226             lights.blink(2, 5, 0, -1);
00227             // go to the next animated sequence step
00228             game_step++;
00229         }
00230         if (missle)
00231         {
00232             my +=2;
00233             disp.SetSpritePos(1, mx, my);
00234             if (my > LCD_HEIGHT)
00235             {
00236                 missle = false;
00237                 disp.EnableSprite(1, false);
00238             }
00239         }
00240         if (inputs.hit(eCircle) || inputs.hit(eSquare))
00241         {
00242             sound.PlaySound(0, NOTE_C6, 10);
00243             
00244             // Do the selection
00245             if (selection == 1)
00246             {
00247                 // Toggle the sound on/off
00248                 sound.Mute(!sound.IsMuted());
00249             }
00250             if (selection == 0)
00251             {
00252                 sound.SetInstrument(0, SOUND_SQUARE, 255, 0, 0, 0, 0, 0, 0, 0);   // Music?
00253                 // Play the intro song
00254                 sound.PlaySong(intro_song, 0, 130, false);
00255                 // Let's play the game! goto step 4
00256                 game_step = 4;
00257             }
00258         }
00259         if (inputs.hit(eUp))
00260         {
00261             selection++;
00262             if (selection > 1) selection = 0;
00263         }
00264         if (inputs.hit(eDown))
00265         {
00266             selection--;
00267             if (selection < 0) selection = 1;
00268         }
00269         break;
00270     case (3):
00271         // Flash the game logo red
00272         if ((frame_count % FRAMES_PER_SECOND) >= 15)
00273             disp.setforecolor(PAL_RED);
00274         else
00275             disp.setforecolor(PAL_WHITE);
00276         disp.clear();
00277         disp.printat((CHAR_MAP_WIDTH >> 1) - 8, 6, "RETRO Invaders!");
00278         // subtract one from the wait counter
00279         await--;
00280         if (await < 1)
00281         {
00282             // Stop the leds from blinking
00283             lights.set(1, false, 1);
00284             lights.set(2, false, 1);
00285             // Set the text color back to white
00286             disp.setforecolor(PAL_WHITE);
00287             // do the previous animation sequence
00288             game_step = 1;
00289         }
00290         break;
00291     case (4):
00292         disp.EnableSprite(0, false);    // Hide the invader sprite
00293         mode = eGamePlay;
00294         game_step = 0;
00295         break;
00296     }    
00297 }
00298 
00299 void Game::SetupStarfield()
00300 {
00301     uint8_t color = 0;
00302     
00303     for (int y = 0; y < CHAR_MAP_HEIGHT; y++)
00304     for (int x = 0; x < CHAR_MAP_WIDTH; x++)
00305     {
00306         // Pick random number between 0 and 4
00307         uint8_t num = rand() % 5;
00308         // Pick a color based on the random number
00309         switch(num)
00310         {
00311         case (0):
00312         case (1):
00313         case (2):
00314             color = PAL_BLUE;
00315             break;
00316         case (3):
00317         case (4):
00318             color = PAL_LIGHTBLUE;
00319             break;
00320         }
00321         // Set the startfield tile based on the random number and random color
00322         disp.set_map(128 + (eTileStarfield1 + num), x, y, color, PAL_BLACK);
00323     }
00324 }
00325 
00326 void Game::UpdateStarfield()
00327 {
00328     // Check to see if it is time to update the starfield
00329     star_field_timer++;
00330     if (star_field_timer < 5) return;
00331     star_field_timer = 0;
00332  
00333     uint8_t y = 0;
00334     uint8_t color = 0;
00335        
00336     // Replace the Score and Lives with Star tiles before we scroll
00337     // otherwise they will scroll with us...
00338     for (int x = 0; x < CHAR_MAP_WIDTH; x++)
00339     {
00340         // Pick random number between 0 and 4
00341         uint8_t num = rand() % 5;
00342         // Pick a color based on the random number
00343         switch(num)
00344         {
00345         case (0):
00346         case (1):
00347         case (2):
00348             color = PAL_BLUE;
00349             break;
00350         case (3):
00351         case (4):
00352             color = PAL_LIGHTBLUE;
00353             break;
00354         }
00355         // Set the startfield tile based on the random number and random color
00356         disp.set_map(128 + (eTileStarfield1 + num), x, 0, color, PAL_BLACK);                    // Top line of the screen
00357     }
00358     
00359     // Scroll the whole field down 1 tile
00360     disp.shift_map(eShiftDown);
00361     
00362     for (int x = 0; x < CHAR_MAP_WIDTH; x++)
00363     {
00364         // Pick random number between 0 and 4
00365         uint8_t num = rand() % 5;
00366         // Pick a color based on the random number
00367         switch(num)
00368         {
00369         case (0):
00370         case (1):
00371         case (2):
00372             color = PAL_BLUE;
00373             break;
00374         case (3):
00375         case (4):
00376             color = PAL_LIGHTBLUE;
00377             break;
00378         }
00379         // Set the startfield tile based on the random number and random color
00380         disp.set_map(128 + (eTileStarfield1 + num), x, y, color, PAL_BLACK);
00381     }
00382 }
00383 
00384 void Game::SetupInvaders()
00385 {
00386     invaders_x = 10;
00387     invaders_y = 10;
00388     invaders_inc = 4;
00389     invaders_frame = 0;
00390     invaders_timer = 0;
00391     active_invaders = 40;
00392     invaders_wait_time = 5;
00393     uint8_t color = 0;
00394     for (int i = 0; i < 40; i++)
00395     {
00396         switch(i / 8)
00397         {
00398         case (0): color = PAL_WHITE; break;
00399         case (1): color = PAL_YELLOW; break;
00400         case (2): color = PAL_ORANGE; break;
00401         case (3): color = PAL_RED; break;
00402         case (4): color = PAL_GREEN; break;
00403         case (5): color = PAL_LIGHTBLUE; break;
00404         case (6): color = PAL_BLUE; break;
00405         case (7): color = PAL_MAGENTA; break;
00406         }
00407         
00408         uint8_t x = invaders_x + ((i % 8) * 10);
00409         uint8_t y = invaders_y + ((i / 8) * 10);
00410         disp.AddSprite(eSpriteInvaderStart + i, eTileInvader1, x, y, color, PAL_TRANSPARENT);
00411         invaders[i] = true;
00412     }
00413     
00414     for (int i = 0; i < 4; i++)
00415     {
00416         invader_active_shot[i] = false;
00417         invader_shot_x[i] = 0;
00418         invader_shot_y[i] = 0;
00419         disp.AddSprite(eSpriteInvaderShot1 + i, eTileInvaderBolt, invader_shot_x[i], invader_shot_y[i], PAL_WHITE, PAL_TRANSPARENT);
00420     }
00421     invader_shot_interval = FRAMES_PER_SECOND;
00422     invader_shot_timer = invader_shot_interval;
00423     invader_march_note = 0;
00424 }
00425 
00426 void Game::UpdateInvaders()
00427 {
00428     // Check to see if it is time to advance the frame
00429     invaders_timer++;
00430     if (invaders_timer > invaders_wait_time)
00431     {
00432         invaders_timer = 0;
00433         invaders_frame++;
00434         if (invaders_frame > 3) invaders_frame = 0; 
00435         // Play the invader march sound
00436         if ((invaders_frame == 0) || (invaders_frame == 2))
00437         {
00438             sound.PlaySound(0, invader_march[invader_march_note++], 10);
00439             if (invader_march_note >= 4) invader_march_note = 0;
00440         }
00441         // Move the invaders
00442         invaders_x += invaders_inc;
00443     }
00444     // Update all of the invaders
00445     bool move_down = false;
00446     for (int i = 0; i < 40; i++)
00447     {
00448         // Check to see if this invader is active and not blown up
00449         if (invaders[i])
00450         {
00451             // Calculate the position of this invader
00452             uint8_t x = invaders_x + ((i % 8) * 10);
00453             uint8_t y = invaders_y + ((i / 8) * 10);
00454             
00455             // Check to see if the invader has reach the edge of the play area
00456             if (x >= 150) move_down = true;
00457             if (x < 10) move_down = true;
00458         
00459             disp.SetSpriteChar(eSpriteInvaderStart + i, eTileInvader1 + invaders_frame);
00460             disp.SetSpritePos(eSpriteInvaderStart + i, x, y);
00461         }
00462         disp.EnableSprite(eSpriteInvaderStart + i, invaders[i]);
00463     }
00464     // Check to see if we should move the invaders down one notch
00465     if (move_down)
00466     {
00467         invaders_y += 5;    // Half the height of the invader space (10 x 10?)
00468         invaders_inc *= -1;
00469         invaders_x += invaders_inc;
00470         // Increase the invader speed if we need to
00471         if (active_invaders <= 30) invaders_wait_time = 4;
00472         if (active_invaders <= 20) invaders_wait_time = 3;
00473         if (active_invaders <= 10) invaders_wait_time = 2;
00474         if (active_invaders <= 5) invaders_wait_time = 1;
00475         if (active_invaders <= 2) invaders_wait_time = 0;
00476     }
00477         
00478     // Service any active shots
00479     for (int shot = 0; shot < 4; shot++)
00480     {
00481         if (invader_active_shot[shot])
00482         {
00483             // Advance downwards
00484             invader_shot_y[shot] += (2 * level);    // Speed the shots up the higher up we get in the levels ... that is the difficulty setting
00485             disp.SetSpritePos(eSpriteInvaderShot1 + shot, invader_shot_x[shot], invader_shot_y[shot]);
00486             // Check to see if we are off the screen
00487             if (invader_shot_y[shot] > (LCD_HEIGHT - 4))
00488             {
00489                 invader_active_shot[shot] = false;
00490                 disp.EnableSprite(eSpriteInvaderShot1 + shot, false);
00491             }
00492         }
00493     }
00494     // Do the invader shooting
00495     invader_shot_timer--;
00496     if (invader_shot_timer < 0)
00497     {
00498         // Reset the shot timer
00499         invader_shot_timer = invader_shot_interval;
00500         // Find an inactive shot to use (up to 4 active at a time)
00501         int shot;
00502         for (shot = 0; shot < 4; shot++)
00503         {
00504             if (!invader_active_shot[shot])
00505             {
00506                 // Pick a random invader for it to come from (there are 40 of them)
00507                 int invader = rand() % active_invaders;
00508                 // Figure out the active invader's coordinates
00509                 for (int i = 0; i < 40; i++)
00510                 {
00511                     int index = (i + invader) % 40;
00512                     if (invaders[index])
00513                     {
00514                         uint8_t color = 0;
00515                         switch(index / 8)
00516                         {
00517                         case (0): color = PAL_WHITE; break;
00518                         case (1): color = PAL_YELLOW; break;
00519                         case (2): color = PAL_ORANGE; break;
00520                         case (3): color = PAL_RED; break;
00521                         case (4): color = PAL_GREEN; break;
00522                         case (5): color = PAL_LIGHTBLUE; break;
00523                         case (6): color = PAL_BLUE; break;
00524                         case (7): color = PAL_MAGENTA; break;
00525                         }
00526                         // Initialize the shot
00527                         invader_active_shot[shot] = true;
00528                         invader_shot_x[shot] = invaders_x + ((index % 8) * 10) + 4;
00529                         invader_shot_y[shot] = invaders_y + ((index / 8) * 10) + 4;
00530                         disp.EnableSprite(eSpriteInvaderShot1 + shot, true);
00531                         disp.SetSpriteColor(eSpriteInvaderShot1 + shot, color, PAL_TRANSPARENT);
00532                         disp.SetSpritePos(eSpriteInvaderShot1 + shot, invader_shot_x[shot], invader_shot_y[shot]);
00533                         sound.PlaySound(2, NOTE_C7, 20);
00534                         break;
00535                     }
00536                 }
00537                 break;
00538             }
00539         }
00540     }
00541 }
00542 
00543 void Game::SetupPlayer()
00544 {
00545     player_x = LCD_WIDTH >> 1;
00546     player_y = LCD_HEIGHT - 10;
00547     disp.AddSprite(eSpritePlayer, eTilePlayer, player_x, player_y, PAL_LIGHTGREY, PAL_TRANSPARENT, true);   // auto midhandle
00548     player_shot_active = false;
00549     player_shot_x = 0;
00550     player_shot_y = 0;
00551     disp.AddSprite(eSpritePlayerShot, eTilePlayerShot, player_shot_x, player_shot_y, PAL_WHITE, PAL_TRANSPARENT, true); // auto midhandle
00552 }
00553 
00554 void Game::UpdatePlayer()
00555 {
00556     // Check to see if the left and right buttons have been hit
00557     if (inputs.pressed(eLeft))
00558     {
00559         player_x -= 2;
00560         if (player_x < 5) player_x = 5;
00561     }
00562     if (inputs.pressed(eRight))
00563     {
00564         player_x += 2;
00565         if (player_x > (LCD_WIDTH - 5)) player_x = (LCD_WIDTH - 5); 
00566     }
00567     disp.SetSpritePos(eSpritePlayer, player_x, player_y);
00568     if ((inputs.hit(eSquare) || inputs.hit(eCircle)) && !player_shot_active)
00569     {
00570         player_shot_x = player_x;
00571         player_shot_y = player_y;
00572         player_shot_active = true;
00573         disp.EnableSprite(eSpritePlayerShot, true);
00574         sound.PlaySound(2, NOTE_C7, 20);
00575     }
00576     if (inputs.pressed(eSquare) && inputs.pressed(eCircle))
00577     {
00578         // Pause the game
00579         paused = true;
00580     }
00581     if (player_shot_active)
00582     {
00583         player_shot_y -= 5;
00584         if (player_shot_y < 5)
00585         {
00586             player_shot_active = false;
00587             disp.EnableSprite(eSpritePlayerShot, false);
00588         }
00589         disp.SetSpritePos(eSpritePlayerShot, player_shot_x, player_shot_y);
00590     }
00591 }
00592 
00593 void Game::SetupSaucer()
00594 {
00595     saucer_x = 0;
00596     saucer_y = 10;
00597     disp.AddSprite(eSpriteSaucer, eTileSaucer, saucer_x, saucer_y, PAL_YELLOW, PAL_TRANSPARENT, true);
00598     disp.EnableSprite(eSpriteSaucer, false);
00599     saucer_timer = (rand() % 10) * FRAMES_PER_SECOND;  // Random time every 10 seconds
00600     saucer_active = false;
00601 }
00602 
00603 void Game::UpdateSaucer()
00604 {
00605     if (!saucer_active)
00606     {
00607         saucer_timer--;
00608         if (saucer_timer > 1) return;
00609         // Calculate a new time for the saucer to be active (after it completes its sequence)
00610         saucer_timer = (rand() % 10) * FRAMES_PER_SECOND;  // Random time every 10 seconds
00611         // Time to go.. setup the saucer to go
00612         // Set the saucer active flag
00613         saucer_active = true;
00614         // Set the saucer movement increment (same speed and opposite direction as the invaders are currently moving)
00615         saucer_inc = invaders_inc * -1;
00616         // Set the saucers starting coordiantes
00617         if (saucer_inc > 0)
00618             saucer_x = 0;               // left to right movement
00619         else
00620             saucer_x = LCD_WIDTH - 4;   // right to left movement
00621         // Set the sprite position and enable the sprite
00622         disp.SetSpritePos(eSpriteSaucer, saucer_x, saucer_y);
00623         disp.EnableSprite(eSpriteSaucer, true);
00624         // Play the saucer song (looped)
00625         sound.PlaySong(saucer_song, 0, 130, true);
00626         // Setup the leds to blink
00627         lights.blink(1, 5, 1, -1);
00628         lights.blink(2, 5, 0, -1);
00629         // Set the saucer timer .. we will use it to divide the frame counter
00630         saucer_timer = 0;
00631         return;
00632     }
00633     
00634     // Divide the frame counter to slow things down (same speed as invaders)
00635     saucer_timer++;
00636     if (saucer_timer < 5) return;
00637     saucer_timer = 0;
00638     
00639     // Move the saucer
00640     saucer_x += saucer_inc;
00641     if ((saucer_x < 1) || (saucer_x > (LCD_WIDTH - 1)))
00642     {
00643         lights.set(1, false, 1);
00644         lights.set(2, false, 1);
00645         saucer_active = false;
00646         disp.EnableSprite(eSpriteSaucer, false);
00647         sound.StopSong();
00648         saucer_timer = (rand() % 10) * FRAMES_PER_SECOND;  // Random time every 10 seconds
00649     }
00650     disp.SetSpritePos(eSpriteSaucer, saucer_x, saucer_y);
00651 }
00652 
00653 void Game::SetupShields()
00654 {
00655     shield_x = LCD_WIDTH >> 1;
00656     shield_y = LCD_HEIGHT - 20;
00657     disp.AddSprite(eSpriteShield1, eTileShield1, shield_x - 45, shield_y, PAL_GREEN, PAL_TRANSPARENT, true);
00658     disp.AddSprite(eSpriteShield2, eTileShield1, shield_x - 15, shield_y, PAL_GREEN, PAL_TRANSPARENT, true);
00659     disp.AddSprite(eSpriteShield3, eTileShield1, shield_x + 15, shield_y, PAL_GREEN, PAL_TRANSPARENT, true);
00660     disp.AddSprite(eSpriteShield4, eTileShield1, shield_x + 45, shield_y, PAL_GREEN, PAL_TRANSPARENT, true);
00661     for (int i = 0; i < 4; i++)
00662         shield_points[i] = 4;
00663 }
00664 
00665 void Game::UpdateShields()
00666 {
00667     // Todo - they don't move but they have hit points...
00668     for (int i = 0; i < 4; i++)
00669     {
00670         disp.SetSpriteChar(eSpriteShield1 + i, eTileShield1 + (4 - shield_points[i]));
00671     }
00672 }
00673 
00674 void Game::CheckCollisions()
00675 {
00676     // Check the player's shot to see if it hits any:
00677     // Invader
00678     for (int i = 0; i < 40; i++)
00679     {
00680         // Check to see if this invader is active
00681         if (invaders[i])
00682         {
00683             if (disp.SpriteCollision(eSpritePlayerShot, eSpriteInvaderStart + i))
00684             {
00685                 // Destroy the invader
00686                 disp.EnableSprite(eSpriteInvaderStart + i, false);
00687                 invaders[i] = false;
00688                 active_invaders--;
00689                 // Destroy the player's shot
00690                 sound.PlaySound(1, NOTE_C6, 30);
00691                 disp.EnableSprite(eSpritePlayerShot, false);
00692                 player_shot_active = false;
00693                 // Increment the score (more points for the ones at the top)
00694                 // 5 points for the top row, 4 points for the next, etc...
00695                 IncScore(5 - (i / 8));
00696                 // Check to see if all of the invaders were destroyed
00697                 if (active_invaders < 1)
00698                 {
00699                     // Level Up and go to the next step (for going to the next level)
00700                     level++;
00701                     game_step = 2;
00702                 }
00703             }
00704         }
00705     }
00706     // Shield
00707     for (int i = 0; i < 4; i++)
00708     {
00709         if (disp.SpriteCollision(eSpritePlayerShot, eSpriteShield1 + i))
00710         {
00711             // Subtract 1 from the shield's points
00712             shield_points[i]--;
00713             if (shield_points[i] < 1)
00714             {
00715                 // Disable this shield
00716                 disp.EnableSprite(eSpriteShield1 + i, false);
00717                 // TODO - Maybe make a noise...
00718             }
00719             // Destroy the player's shot
00720             sound.PlaySound(1, NOTE_C6, 30);
00721             disp.EnableSprite(eSpritePlayerShot, false);
00722             player_shot_active = false;
00723         }
00724     }
00725     // Invader Shot
00726     for (int i = 0; i < 4; i++)
00727     {
00728         if (disp.SpriteCollision(eSpritePlayerShot, eSpriteInvaderShot1 + i))
00729         {
00730             // Disable this invader shot
00731             invader_active_shot[i] = false;
00732             disp.EnableSprite(eSpriteInvaderShot1 + i, false);
00733             // Destroy the player's shot
00734             sound.PlaySound(1, NOTE_C6, 30);
00735             disp.EnableSprite(eSpritePlayerShot, false);
00736             player_shot_active = false;
00737             // 1 point for shooting an invader bullet
00738             IncScore(1);
00739         }
00740     }
00741     // Saucer
00742     if (disp.SpriteCollision(eSpritePlayerShot, eSpriteSaucer))
00743     {
00744         // Destroy the saucer
00745         lights.set(1, false, 1);
00746         lights.set(2, false, 1);
00747         saucer_active = false;
00748         disp.EnableSprite(eSpriteSaucer, false);
00749         sound.StopSong();
00750         saucer_timer = (rand() % 10) * FRAMES_PER_SECOND;  // Random time every 10 seconds
00751         // Destroy the player's shot
00752         sound.PlaySound(1, NOTE_C6, 30);
00753         disp.EnableSprite(eSpritePlayerShot, false);
00754         player_shot_active = false;
00755         // 10 points for a saucer
00756         IncScore(10);
00757     }
00758     
00759     // Check to see if the invader's shots hit any:
00760     // Shields
00761     // Player Ships
00762     for (int shot = 0; shot < 4; shot++)
00763     {
00764         if (!invader_active_shot[shot]) continue;
00765         
00766         // Check the player
00767         if (disp.SpriteCollision(eSpritePlayer, eSpriteInvaderShot1 + shot))
00768         {
00769             // Play the Explosion sound
00770             sound.PlaySound(1, NOTE_C6, 30);
00771             // Destroy the shot
00772             invader_active_shot[shot] = false;
00773             disp.EnableSprite(eSpriteInvaderShot1 + shot, false);
00774             // Decrement the player lives
00775             lives--;
00776             // Go to the player blow up sequence
00777             game_step = 4; // we will check to see if the lives are 0 there!
00778         }
00779         
00780         // Check the shields
00781         for (int shield = 0; shield < 4; shield++)
00782         {
00783             if (disp.SpriteCollision(eSpriteInvaderShot1 + shot, eSpriteShield1 + shield))
00784             {
00785                 // Subtract 1 from the shield's points
00786                 shield_points[shield]--;
00787                 if (shield_points[shield] < 1)
00788                 {
00789                     // Disable this shield
00790                     disp.EnableSprite(eSpriteShield1 + shield, false);
00791                     // TODO - Maybe make a noise...
00792                 }
00793                 sound.PlaySound(1, NOTE_C6, 30);
00794                 // Destroy the shot
00795                 invader_active_shot[shot] = false;
00796                 disp.EnableSprite(eSpriteInvaderShot1 + shot, false);
00797             }
00798         } 
00799     }
00800     // Check to see if the invaders have reached the shields
00801     // Or if the invaders have reached the ship!
00802     for (int i = 0 ; i < 40; i++)
00803     {
00804         // Check only active invaders
00805         if (!invaders[i]) continue;
00806         
00807         for (int shield = 0; shield < 4; shield++)
00808         {
00809             // Check only active shields
00810             if (shield_points[shield] < 1) continue;
00811             if (disp.SpriteCollision(eSpriteInvaderStart + i, eSpriteShield1 + shield))
00812             {
00813                 // Destroy the shield
00814                 shield_points[shield] = 0;
00815                 disp.EnableSprite(eSpriteShield1 + shield, false);
00816                 sound.PlaySound(1, NOTE_C6, 10);
00817             }
00818         }
00819         
00820         if (disp.SpriteCollision(eSpriteInvaderStart + i, eSpritePlayer))
00821         {
00822             // Game Over!!!!
00823             lives = 0;
00824            // Go to the player blow up sequence
00825             game_step = 4; // we will check to see if the lives are 0 there!
00826         }
00827     }
00828 }
00829 
00830 void Game::IncScore(int points)
00831 {
00832     // Increment the score
00833     score += points;
00834     // Update the hi score if necessary
00835     if (score > hiscore) hiscore = score;
00836 }
00837 
00838 void Game::UpdateScore()
00839 {
00840     // Hi Score in the middle
00841     disp.printat(7, 0, "HI%04d", hiscore);
00842     // Current Score on the left
00843     disp.printat(0, 0, "%04d", score);
00844 }
00845 
00846 void Game::UpdateLives()
00847 {
00848     // Men left in the upper right
00849     if (lives >= 1) disp.setcharat(CHAR_MAP_WIDTH - 1, 0, 128 + eTilePlayer, PAL_LIGHTBLUE, PAL_BLACK);
00850     if (lives >= 2) disp.setcharat(CHAR_MAP_WIDTH - 2, 0, 128 + eTilePlayer, PAL_LIGHTBLUE, PAL_BLACK);
00851     if (lives >= 3) disp.setcharat(CHAR_MAP_WIDTH - 3, 0, 128 + eTilePlayer, PAL_LIGHTBLUE, PAL_BLACK);
00852 }
00853 
00854 void Game::DoGamePlay()
00855 {
00856     switch(game_step)
00857     {
00858     case (0):
00859         disp.clear();
00860         // Setup the score and lives
00861         score = 0;
00862         lives = 3;
00863         // DEBUG
00864         //mode = eGameOver; break;
00865         // Sprites
00866         // Invaders ( 8 x 5 = 40 total - starts at sprite #10)
00867         SetupInvaders();
00868         // Shields  ( 3 shields)
00869         SetupShields();
00870         // Player     ( 1 player)
00871         SetupPlayer();
00872         // Saucer   ( 1 saucer)
00873         SetupSaucer();
00874         // Setup the playfield
00875         SetupStarfield();
00876         // Draw the taunt
00877         disp.printat((CHAR_MAP_WIDTH >> 1) - 8, 8, "We Are Coming...");
00878         // DEBUG - kill most of the invaders
00879         /*(
00880         for (int i = 0; i < 35; i++)
00881         {
00882             invaders[i] = false;
00883             active_invaders--;
00884         }
00885         */
00886         // Next step
00887         game_step = 1;
00888         break;
00889     case (1):
00890         // This is the main game loop here
00891         // Check for Input
00892         // Update the star field
00893         UpdateStarfield();
00894         // Move Invaders
00895         UpdateInvaders();
00896         // Move Player
00897         UpdatePlayer();
00898         // Update the shields
00899         UpdateShields();
00900         // Update the saucer
00901         UpdateSaucer();
00902         // Update the rest of the screen
00903         UpdateScore();
00904         UpdateLives();
00905         // Make things shoot each other
00906         // Check for collisions
00907         CheckCollisions();
00908         // Check for Game Over
00909         
00910         // dEBUG
00911         //disp.printat(0, 15, "%d", active_invaders);
00912         break;
00913     case (2):
00914         // Next Level
00915         UpdateStarfield();
00916         UpdateScore();
00917         UpdateLives();
00918         // Play the intro song
00919         sound.PlaySong(intro_song, 0, 130, false);
00920         // Print out a taunt
00921         disp.printat((CHAR_MAP_WIDTH >> 1) - 3, 8, "Ready?");
00922         // Set the wait timer
00923         wait_timer = 20;
00924         // Next step to do the waiting
00925         game_step = 3;
00926         break;
00927     case (3):
00928         // Wait a few ticks before putting the invaders up
00929         UpdateStarfield();
00930         UpdateScore();
00931         UpdateLives();
00932         wait_timer--;
00933         if (wait_timer == 0)
00934         {
00935             // Initialize the board again
00936             // Sprites
00937             // Invaders ( 8 x 5 = 40 total - starts at sprite #10)
00938             SetupInvaders();
00939             // Shields  ( 3 shields)
00940             SetupShields();
00941             // Player     ( 1 player)
00942             SetupPlayer();
00943             // Saucer   ( 1 saucer)
00944             SetupSaucer();
00945             // Setup the playfield
00946             SetupStarfield();
00947             // Draw the taunt
00948             disp.printat((CHAR_MAP_WIDTH >> 1) - 8, 8, "We Are Coming...");
00949             // Next step
00950             game_step = 1;
00951         }
00952         break;
00953     case (4):
00954         // Set the wait timer
00955         wait_timer = 20;
00956         // Play a LOOONG blow up sound
00957         sound.PlaySound(1, NOTE_C6, 60);
00958         // Taunt the player!
00959         disp.setforecolor(PAL_RED);
00960         disp.printat((CHAR_MAP_WIDTH >> 1) - 4, 8, "Winning!");
00961         disp.setforecolor(PAL_WHITE);
00962         // Next step to wait and do the player explosion sequence
00963         game_step = 5;
00964         break;
00965     case (5):
00966         // Do the player blow up sequence
00967         disp.SetSpriteChar(eSpritePlayer, eTileStarfield1 + (wait_timer % 3));
00968         disp.SetSpriteColor(eSpritePlayer, PAL_RED, PAL_TRANSPARENT);
00969         wait_timer--;
00970         // When we are done go back to the play the game step
00971         if (wait_timer < 1)
00972         {
00973             game_step = 1;
00974             // Set the player sprite back to normal
00975             disp.SetSpriteChar(eSpritePlayer, eTilePlayer);
00976             disp.SetSpriteColor(eSpritePlayer, PAL_GREY, PAL_TRANSPARENT);
00977             // check to see if the lives have reached 0
00978             if (lives < 1)
00979             {
00980                 // Disable the player sprite
00981                 disp.EnableSprite(eSpritePlayer, false);
00982                 // Switch the mode to Game Over
00983                 game_step = 0;
00984                 mode = eGameOver;
00985                 // The Invaders have Arrived!
00986             }
00987         }
00988         break;
00989     }
00990 }
00991 
00992 void Game::UpdateGameOverInvaders()
00993 {
00994     uint8_t color = 0;
00995     
00996     // Scroll the whole field up 1 tile
00997     disp.shift_map(eShiftUp);
00998     
00999     // Pick a color    
01000     switch(invaders_frame % 8)
01001     {
01002     case (0): color = PAL_WHITE; break;
01003     case (1): color = PAL_YELLOW; break;
01004     case (2): color = PAL_ORANGE; break;
01005     case (3): color = PAL_RED; break;
01006     case (4): color = PAL_GREEN; break;
01007     case (5): color = PAL_LIGHTBLUE; break;
01008     case (6): color = PAL_BLUE; break;
01009     case (7): color = PAL_MAGENTA; break;
01010     }
01011 
01012     // Fill the top with invaders of a color
01013     for (int i = 0; i < CHAR_MAP_WIDTH; i++)
01014     {
01015         disp.setcharat(i, CHAR_MAP_HEIGHT - 1, 128 + eTileInvader1 + (invaders_frame % 4), color, PAL_BLACK);
01016     }
01017     
01018     // Next frame
01019     invaders_frame++;
01020 }
01021 
01022 void Game::DoGameOver()
01023 {
01024     switch (game_step)
01025     {
01026     case (0):
01027         // Disable all the sprites
01028         for (int i = 0; i < MAX_SPRITES; i++)
01029             disp.EnableSprite(i, false);
01030         // Turn off the leds
01031         lights.set(1, false, 1);
01032         lights.set(2, false, 1);
01033         // Stop the song
01034         sound.StopSong();
01035         // initialize the game over screen stuff...
01036         // play the game over tune
01037         wait_timer = 0;
01038         star_field_timer = 0;
01039         invader_march_note = 0;
01040         invaders_frame = 0;
01041         game_over_msg = 0;
01042         // Setup the coin sound
01043         sound.SetInstrument(2, SOUND_SQUARE, 128, 10, 0, 120, 6, 0, 0, 0);    // Coin Sound (Change + Decay)
01044         // Next step
01045         game_step = 1;
01046         break;
01047     case (1):
01048         // Scroll the screen down and fill with invaders
01049         // While playing the march
01050         
01051         // Use the starfield timer to slow down the scroll
01052         star_field_timer++;
01053         if (star_field_timer < 10) return;
01054         star_field_timer = 0;
01055     
01056         UpdateGameOverInvaders();
01057         
01058         // Play the march sound
01059         sound.PlaySound(0, invader_march[invader_march_note++], 10);
01060         if (invader_march_note >= 4) invader_march_note = 0;
01061         
01062         // Subtract one from the wait timer
01063         wait_timer++;
01064         if (wait_timer >= CHAR_MAP_HEIGHT)
01065         {
01066             game_step = 2;
01067             wait_timer = 0;
01068         }
01069         break;
01070     case (2):
01071         // Empty step
01072         // Clear the wait timer
01073         wait_timer = 0;
01074         // Next step
01075         game_step = 3;
01076         break;
01077     case (3):
01078     
01079         // Use the starfield timer to slow down the scroll
01080         star_field_timer++;
01081         if (star_field_timer < 10) return;
01082         star_field_timer = 0;
01083         
01084         UpdateGameOverInvaders();
01085         
01086         wait_timer++;
01087         if (wait_timer > 3)
01088         {
01089             wait_timer = 0;
01090             game_step = 4;
01091         }
01092         // Wait a few frames... then print a thanks to outrageous circuits / GHI
01093         break;
01094     case (4):
01095     {
01096         char msg[16] = "";
01097         
01098         switch (game_over_msg)
01099         {
01100         case (0): sprintf(msg, "The Invaders..."); break;
01101         case (1): sprintf(msg, "Have Arrived!"); break;
01102         case (2): sprintf(msg, "Game Over!"); break;
01103         case (3): sprintf(msg, "Thanks To:"); break;
01104         case (4): sprintf(msg, "outrageous"); break;
01105         case (5): sprintf(msg, "circuits.com"); break;
01106         case (6): sprintf(msg, "GHI"); break;
01107         case (7): sprintf(msg, ""); break;
01108         case (8): sprintf(msg, "written by"); break;
01109         case (9): sprintf(msg, "cfavreau"); break;
01110         case (10): game_step = 5; return;
01111         }
01112         uint8_t len = strlen(msg);
01113         disp.printat((CHAR_MAP_WIDTH >> 1) - (len >> 1), CHAR_MAP_HEIGHT - 1, msg);
01114         // Play the coin sound
01115         sound.PlaySound(2, NOTE_Fs6, 20);
01116         // Next message
01117         game_over_msg++;
01118         game_step = 3;
01119     }
01120         break;
01121     default:
01122     case (5):
01123         // Reset the game to the title sequence
01124         mode = eGameTitle;
01125         game_step = 0;
01126         // Clear the button hits
01127         inputs.clear();
01128         break;
01129     }
01130 }
01131