Retro Invaders a space invaders clone by Chris Favreau. Written for the RetroMbuino development board from outrageouscircuits.com for the game programming contest.
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
Generated on Thu Jul 14 2022 20:06:44 by 1.7.2