ArtemTechnologies

ATWatch - Arduino совместимая носимая платформа для обучения робототехнике и программированию на языке C++

Space Invaders

Основной код взят отсюда

  1. #include <Adafruit_SH1106.h>
  2. #include <Adafruit_GFX.h>
  3. //#include <toneAC.h>
  4. #include <EEPROM.h>
  5.  
  6. // DISPLAY SETTINGS
  7. #define SCREEN_WIDTH 128
  8. #define SCREEN_HEIGHT 64
  9.  
  10. #define OLED_DC          26
  11. #define OLED_CS          31
  12. #define OLED_RESET       24
  13. #define BuzzerPin        15
  14.  
  15. // Input settings
  16. #define FIRE_BUT 12
  17. #define RIGHT_BUT 13
  18. #define LEFT_BUT 2
  19.  
  20.  
  21. // Mothership
  22. #define MOTHERSHIP_HEIGHT 4
  23. #define MOTHERSHIP_WIDTH 16
  24. #define MOTHERSHIP_SPEED 2
  25. #define MOTHERSHIP_SPAWN_CHANCE 250         //HIGHER IS LESS CHANCE OF SPAWN
  26. #define DISPLAY_MOTHERSHIP_BONUS_TIME 20      // how long bonus stays on screen for displaying mothership
  27.  
  28. // Alien Settings
  29. #define ALIEN_HEIGHT 8
  30. #define NUM_ALIEN_COLUMNS 7
  31. #define NUM_ALIEN_ROWS 3
  32. #define X_START_OFFSET 6
  33. #define SPACE_BETWEEN_ALIEN_COLUMNS 5
  34. #define LARGEST_ALIEN_WIDTH 11
  35. #define SPACE_BETWEEN_ROWS 9
  36. #define INVADERS_DROP_BY 4            // pixel amount that invaders move down by
  37. #define INVADERS_SPEED 12             // speed of movement, lower=faster.
  38. #define INVADER_HEIGHT 8
  39. #define EXPLOSION_GFX_TIME 7  // How long an ExplosionGfx remains on screen before dissapearing
  40. #define INVADERS_Y_START MOTHERSHIP_HEIGHT-1
  41. #define AMOUNT_TO_DROP_BY_PER_LEVEL 4   //NEW How much farther down aliens start per new level
  42. #define LEVEL_TO_RESET_TO_START_HEIGHT 4  // EVERY MULTIPLE OF THIS LEVEL THE ALIEN y START POSITION WILL RESET TO TOP
  43. #define ALIEN_X_MOVE_AMOUNT 1                       //number of pixels moved at start of wave
  44. #define CHANCEOFBOMBDROPPING 20                     // Higher the number the rarer the bomb drop, 
  45. #define BOMB_HEIGHT 4
  46. #define BOMB_WIDTH 2
  47. #define MAXBOMBS 3   // Max number of bombs allowed to drop at a time
  48. // These two lines are for when bombs collide with the bases
  49. #define CHANCE_OF_BOMB_DAMAGE_TO_LEFT_OR_RIGHT 3    // higher more chance
  50. #define CHANCE_OF_BOMB_PENETRATING_DOWN 3           // higher more chance
  51.  
  52. // Player settingsc
  53. #define TANKGFX_WIDTH 13
  54. #define TANKGFX_HEIGHT 8
  55. #define PLAYER_X_MOVE_AMOUNT 2
  56. #define LIVES 3                 //NEW
  57. #define PLAYER_EXPLOSION_TIME 10  // How long an ExplosionGfx remains on screen before dissapearing
  58. #define PLAYER_Y_START 56
  59. #define PLAYER_X_START 0
  60. #define BASE_WIDTH 16
  61. #define BASE_WIDTH_IN_BYTES 2
  62. #define BASE_HEIGHT 8
  63. #define BASE_Y 46
  64. #define NUM_BASES 0
  65. #define BASE_MARGINS 10
  66.  
  67. #define MISSILE_HEIGHT 4
  68. #define MISSILE_WIDTH 1
  69. #define MISSILE_SPEED 4
  70.  
  71. // Status of a game object constants
  72. #define ACTIVE 0
  73. #define EXPLODING 1
  74. #define DESTROYED 2
  75.  
  76. // background dah dah dah sound setting
  77. #define NOTELENGTH 1                      // larger means play note longer
  78.  
  79.  
  80. // graphics
  81. // aliens
  82.  
  83. const unsigned char MotherShipGfx [] PROGMEM = {
  84.   B00111111, B11111100,
  85.   B01101101, B10110110,
  86.   B11111111, B11111111,
  87.   B00111001, B10011100
  88. };
  89.  
  90. const unsigned char InvaderTopGfx [] PROGMEM = {
  91.   B00011000,
  92.   B00111100,
  93.   B01111110,
  94.   B11011011,
  95.   B11111111,
  96.   B00100100,
  97.   B01011010,
  98.   B10100101
  99. };
  100.  
  101. const unsigned char InvaderTopGfx2 [] PROGMEM = {
  102.   B00011000,
  103.   B00111100,
  104.   B01111110,
  105.   B11011011,
  106.   B11111111,
  107.   B01011010,
  108.   B10000001,
  109.   B01000010
  110. };
  111.  
  112. const unsigned char PROGMEM InvaderMiddleGfx [] =
  113. {
  114.   B00100000, B10000000,
  115.   B00010001, B00000000,
  116.   B00111111, B10000000,
  117.   B01101110, B11000000,
  118.   B11111111, B11100000,
  119.   B10111111, B10100000,
  120.   B10100000, B10100000,
  121.   B00011011, B00000000
  122. };
  123.  
  124. const unsigned char PROGMEM InvaderMiddleGfx2 [] = {
  125.   B00100000, B10000000,
  126.   B00010001, B00000000,
  127.   B10111111, B10100000,
  128.   B10101110, B10100000,
  129.   B11111111, B11100000,
  130.   B00111111, B10000000,
  131.   B00100000, B10000000,
  132.   B01000000, B01000000
  133. };
  134.  
  135. const unsigned char PROGMEM InvaderBottomGfx [] = {
  136.   B00001111, B00000000,
  137.   B01111111, B11100000,
  138.   B11111111, B11110000,
  139.   B11100110, B01110000,
  140.   B11111111, B11110000,
  141.   B00111001, B11000000,
  142.   B01100110, B01100000,
  143.   B00110000, B11000000
  144. };
  145.  
  146. const unsigned char PROGMEM InvaderBottomGfx2 [] = {
  147.   B00001111, B00000000,
  148.   B01111111, B11100000,
  149.   B11111111, B11110000,
  150.   B11100110, B01110000,
  151.   B11111111, B11110000,
  152.   B00111001, B11000000,
  153.   B01000110, B00100000,
  154.   B10000000, B00010000
  155. };
  156.  
  157. static const unsigned char PROGMEM ExplosionGfx [] = {
  158.   B00001000, B10000000,
  159.   B01000101, B00010000,
  160.   B00100000, B00100000,
  161.   B00010000, B01000000,
  162.   B11000000, B00011000,
  163.   B00010000, B01000000,
  164.   B00100101, B00100000,
  165.   B01001000, B10010000
  166. };
  167.  
  168. // Player grafix
  169. const unsigned char PROGMEM TankGfx [] = {
  170.   B00000010, B00000000,
  171.   B00000111, B00000000,
  172.   B00000111, B00000000,
  173.   B01111111, B11110000,
  174.   B11111111, B11111000,
  175.   B11111111, B11111000,
  176.   B11111111, B11111000,
  177.   B11111111, B11111000,
  178. };
  179.  
  180. static const unsigned char PROGMEM MissileGfx [] = {
  181.   B10000000,
  182.   B10000000,
  183.   B10000000,
  184.   B10000000
  185.  
  186. };
  187.  
  188. static const unsigned char PROGMEM AlienBombGfx [] = {
  189.   B10000000,
  190.   B01000000,
  191.   B10000000,
  192.   B01000000
  193.  
  194. };
  195.  
  196. static const unsigned char PROGMEM BaseGfx [] = {
  197.   B00011111, B11111000,
  198.   B01111111, B11111110,
  199.   B11111111, B11111111,
  200.   B11111111, B11111111,
  201.   B11111111, B11111111,
  202.   B11111000, B00011111,
  203.   B11100000, B00000111,
  204.   B11100000, B00000111
  205. };
  206.  
  207.  
  208. // Game structures
  209.  
  210. struct GameObjectStruct  {
  211.   // base object which most other objects will include
  212.   signed int X;
  213.   signed int Y;
  214.   unsigned char Status;  //0 active, 1 exploding, 2 destroyed
  215. };
  216.  
  217.  
  218. struct BaseStruct {
  219.   GameObjectStruct Ord;
  220.   unsigned char Gfx[16];
  221. };
  222.  
  223.  
  224. struct AlienStruct  {
  225.   GameObjectStruct Ord;
  226.   unsigned char ExplosionGfxCounter; // how long we want the ExplosionGfx to last
  227. };
  228.  
  229. struct PlayerStruct  {
  230.   GameObjectStruct Ord;
  231.   unsigned int Score;
  232.   unsigned char Lives;
  233.   unsigned char Level;
  234.   unsigned char AliensDestroyed;    // count of how many killed so far
  235.   unsigned char AlienSpeed;         // higher the number slower they go, calculated when ever alien destroyed
  236.   unsigned char ExplosionGfxCounter; // how long we want the ExplosionGfx to last
  237. };
  238.  
  239.  
  240. // general global variables
  241. //Adafruit_SSD1306 display(1);
  242. Adafruit_SH1106 display(OLED_DC, OLED_RESET, OLED_CS);
  243.  
  244.  
  245. //alien global vars
  246. //The array of aliens across the screen
  247. AlienStruct  Alien[NUM_ALIEN_COLUMNS][NUM_ALIEN_ROWS];
  248. AlienStruct MotherShip;
  249. GameObjectStruct AlienBomb[MAXBOMBS];
  250. BaseStruct Base[NUM_BASES];
  251.  
  252. static const int TOTAL_ALIENS = NUM_ALIEN_COLUMNS * NUM_ALIEN_ROWS; //NEW
  253.  
  254.  
  255. // widths of aliens
  256. // as aliens are the same type per row we do not need to store their graphic width per alien in the structure above
  257. // that would take a byte per alien rather than just three entries here, 1 per row, saving significnt memory
  258. byte AlienWidth[] = {8, 11, 12}; // top, middle ,bottom widths
  259.  
  260.  
  261. char AlienXMoveAmount = 2;
  262. signed char InvadersMoveCounter;            // counts down, when 0 move invaders, set according to how many aliens on screen
  263. bool AnimationFrame = false; // two frames of animation, if true show one if false show the other
  264.  
  265. // Mothership
  266. signed char MotherShipSpeed;
  267. unsigned int MotherShipBonus;
  268. signed int MotherShipBonusXPos;            // pos to display bonus at
  269. unsigned char MotherShipBonusCounter;         // how long bonus amount left on screen
  270.  
  271.  
  272. // Player global variables
  273. PlayerStruct Player;
  274. GameObjectStruct Missile;
  275.  
  276. // game variables
  277. unsigned int HiScore;
  278. bool GameInPlay = false;
  279.  
  280. // Sound settings and variables
  281.  
  282. // music (dah dah dah sound) control, these are the "pitch" of the four basic notes
  283. const unsigned char Music[] =  {
  284.   160, 100, 80, 62
  285. };
  286.  
  287. unsigned char MusicIndex;                     // index pointer to next note to play
  288. unsigned MusicCounter;                        // how long note plays for countdown timer, is set to
  289. // NOTELENGTH define above
  290.  
  291. bool PlayerExplosionNoiseCompleted = false;   // flag to indicate when noise has completed
  292. bool ShootCompleted = true;                   // stops music when this is false, so we can here shoot sound
  293.  
  294.  
  295.  
  296.  
  297.  
  298. void setup()
  299. {
  300.   //display.begin(SSD1306_SWITCHCAPVCC,OLED_ADDRESS);
  301.   display.begin(SH1106_SWITCHCAPVCC);
  302.   InitAliens(0);
  303.   InitPlayer();
  304.  
  305.   pinMode(RIGHT_BUT, INPUT);
  306.   pinMode(LEFT_BUT, INPUT);
  307.   pinMode(FIRE_BUT, INPUT);
  308.  
  309.   display.setTextSize(1);
  310.   display.setTextColor(WHITE);
  311.  
  312.   EEPROM.get(0, HiScore);
  313.   if (HiScore == 65535) // new unit never written to
  314.   {
  315.     HiScore = 0;
  316.     EEPROM.put(0, HiScore);
  317.   }
  318. }
  319.  
  320. void loop()
  321. {
  322.   //NEW
  323.   if (GameInPlay)
  324.   {
  325.     Physics();
  326.     UpdateDisplay();
  327.   }
  328.   else
  329.     AttractScreen();
  330. }
  331.  
  332.  
  333. void AttractScreen()
  334. {
  335.   display.clearDisplay();
  336.   CentreText("Play", 0);
  337.   CentreText("Space Invaders", 12);
  338.   CentreText("Press Fire to start", 24);
  339.   CentreText("Hi Score     ", 36);
  340.   display.setCursor(80, 36);
  341.   display.print(HiScore);
  342.   display.display();
  343.   if (digitalRead(FIRE_BUT) == 0) {
  344.     GameInPlay = true;
  345.     NewGame();
  346.   }
  347.  
  348.   // CHECK FOR HIGH SCORE RESET, PLAYER MUST HOLD LEFT AND RIGHT TOGETHER
  349.   if ((digitalRead(LEFT_BUT) == 1) & (digitalRead(RIGHT_BUT) == 1))
  350.   {
  351.     // Reset high score, don't need to worry about debounce as will ony write to EEPROM if changed, so writes a 0 then won't write again
  352.     // if hiscore still 0 which it will be
  353.     HiScore = 0;
  354.     EEPROM.put(0, HiScore);
  355.   }
  356. }
  357.  
  358. void Physics()  {
  359.   if (Player.Ord.Status == ACTIVE) {
  360.     AlienControl();
  361.     MotherShipPhysics();
  362.     PlayerControl();
  363.     MissileControl();
  364.     CheckCollisions();
  365.   }
  366. }
  367.  
  368. unsigned char GetScoreForAlien(int RowNumber)
  369. {
  370.   // returns value for killing and alien at the row indicated
  371.   switch (RowNumber)
  372.   {
  373.     case 0: return 30;
  374.     case 1: return 20;
  375.     case 2: return 10;
  376.   }
  377. }
  378.  
  379. void MotherShipPhysics()  {
  380.   if (MotherShip.Ord.Status == ACTIVE)  { // spawned, move it
  381. //    toneAC((MotherShip.Ord.X % 8) * 500, 7, 200, true);
  382. tone (BuzzerPin, (MotherShip.Ord.X % 8) * 500, 200);
  383.  
  384.     MotherShip.Ord.X += MotherShipSpeed;
  385.     if (MotherShipSpeed > 0) // going left to right , check if off right hand side
  386.     {
  387.       if (MotherShip.Ord.X >= SCREEN_WIDTH)
  388.       {
  389.         MotherShip.Ord.Status = DESTROYED;
  390.         // noToneAC();
  391.         noTone (BuzzerPin);
  392.       }
  393.     }
  394.     else    // going right to left , check if off left hand side
  395.     {
  396.       if (MotherShip.Ord.X + MOTHERSHIP_WIDTH < 0)
  397.       {
  398.         MotherShip.Ord.Status = DESTROYED;
  399.         //noToneAC();
  400.         noTone(BuzzerPin);
  401.       }
  402.     }
  403.  
  404.   }
  405.   else  {
  406.     // try to spawn mothership
  407.     if (random(MOTHERSHIP_SPAWN_CHANCE) == 1)
  408.     {
  409.       // Spawn a mother ship, starts just off screen at top
  410.       MotherShip.Ord.Status = ACTIVE;
  411.       // need to set direction
  412.       if (random(2) == 1) // values between 0 and 1
  413.       {
  414.         MotherShip.Ord.X = SCREEN_WIDTH;
  415.         MotherShipSpeed = -MOTHERSHIP_SPEED;       // if we go in here swaps to right to left
  416.       }
  417.       else
  418.       {
  419.         MotherShip.Ord.X = -MOTHERSHIP_WIDTH;
  420.         MotherShipSpeed = MOTHERSHIP_SPEED; // set to go left ot right
  421.       }
  422.     }
  423.   }
  424. }
  425.  
  426. void PlayerControl()  {
  427.   // user input checks
  428.   if ((digitalRead(RIGHT_BUT) == 1) & (Player.Ord.X + TANKGFX_WIDTH < SCREEN_WIDTH))
  429.     Player.Ord.X += PLAYER_X_MOVE_AMOUNT;
  430.   if ((digitalRead(LEFT_BUT) == 1) & (Player.Ord.X > 0))
  431.     Player.Ord.X -= PLAYER_X_MOVE_AMOUNT;
  432.   if ((digitalRead(FIRE_BUT) == 1) & (Missile.Status != ACTIVE))
  433.   {
  434.     Missile.X = Player.Ord.X + (TANKGFX_WIDTH / 2);
  435.     Missile.Y = PLAYER_Y_START;
  436.     Missile.Status = ACTIVE;
  437.     //    noiseAC(200,10,&ShootCompleted);
  438.     tone (BuzzerPin, 200, 10);
  439.   }
  440. }
  441.  
  442. void MissileControl()
  443. {
  444.   if (Missile.Status == ACTIVE)
  445.   {
  446.     Missile.Y -= MISSILE_SPEED;
  447.     if (Missile.Y + MISSILE_HEIGHT < 0)  // If off top of screen destroy so can be used again
  448.       Missile.Status = DESTROYED;
  449.   }
  450. }
  451.  
  452.  
  453. void AlienControl()
  454. {
  455.   if ((InvadersMoveCounter--) < 0)
  456.   {
  457.     bool Dropped = false;
  458.     if ((RightMostPos() + AlienXMoveAmount >= SCREEN_WIDTH) | (LeftMostPos() + AlienXMoveAmount < 0)) // at edge of screen
  459.     {
  460.       AlienXMoveAmount = -AlienXMoveAmount;             // reverse direction
  461.       Dropped = true;                                   // and indicate we are dropping
  462.     }
  463.  
  464.     // play background music note if other higher priority sounds not playing
  465.     if ((ShootCompleted) & (MotherShip.Ord.Status != ACTIVE))  {
  466.       //      toneAC(Music[MusicIndex], 2, 100, true);
  467.       tone (BuzzerPin, Music[MusicIndex], 100);
  468.       MusicIndex++;
  469.       if (MusicIndex == sizeof(Music))
  470.         MusicIndex = 0;
  471.       MusicCounter = NOTELENGTH;
  472.     }
  473.     // update the alien postions
  474.     for (int Across = 0; Across < NUM_ALIEN_COLUMNS; Across++)
  475.     {
  476.       for (int Down = 0; Down < 3; Down++)
  477.       {
  478.         if (Alien[Across][Down].Ord.Status == ACTIVE)
  479.         {
  480.           if (Dropped == false)
  481.             Alien[Across][Down].Ord.X += AlienXMoveAmount;
  482.           else
  483.             Alien[Across][Down].Ord.Y += INVADERS_DROP_BY;
  484.         }
  485.       }
  486.     }
  487.     InvadersMoveCounter = Player.AlienSpeed;
  488.     AnimationFrame = !AnimationFrame; ///swap to other frame
  489.   }
  490.   // should the alien drop a bomb
  491.   if (random(CHANCEOFBOMBDROPPING) == 1)
  492.     DropBomb();
  493.   MoveBombs();
  494. }
  495.  
  496.  
  497. void MoveBombs() {
  498.   for (int i = 0; i < MAXBOMBS; i++) {
  499.     if (AlienBomb[i].Status == ACTIVE)
  500.       AlienBomb[i].Y += 2;
  501.   }
  502. }
  503.  
  504.  
  505. void DropBomb()  {
  506.   // if there is a free bomb slot then drop a bomb else nothing happens
  507.   bool Free = false;
  508.   unsigned char ActiveCols[NUM_ALIEN_COLUMNS];
  509.   unsigned char BombIdx = 0;
  510.   // find a free bomb slot
  511.   while ((Free == false) & (BombIdx < MAXBOMBS)) {
  512.     if (AlienBomb[BombIdx].Status == DESTROYED)
  513.       Free = true;
  514.     else
  515.       BombIdx++;
  516.   }
  517.   if (Free)  {
  518.     unsigned char Columns = 0;
  519.     // now pick and alien at random to drop the bomb
  520.     // we first pick a column, obviously some columns may not exist, so we count number of remaining cols
  521.     // first, this adds time but then also picking columns randomly that don't exist may take time also
  522.     unsigned char ActiveColCount = 0;
  523.     signed char Row;
  524.     unsigned char ChosenColumn;
  525.  
  526.     while (Columns < NUM_ALIEN_COLUMNS) {
  527.       Row = 2;
  528.       while (Row >= 0)  {
  529.         if (Alien[Columns][Row].Ord.Status == ACTIVE)
  530.         {
  531.           ActiveCols[ActiveColCount] = Columns;
  532.           ActiveColCount++;
  533.           break;
  534.         }
  535.         Row--;
  536.       }
  537.       Columns++;
  538.     }
  539.     // we have ActiveCols array filled with the column numbers of the active cols and we have how many
  540.     // in ActiveColCount, now choose a column at random
  541.     ChosenColumn = random(ActiveColCount); // random number between 0 and the amount of columns
  542.     // we now find the first available alien in this column to drop the bomb from
  543.     Row = 2;
  544.     while (Row >= 0)  {
  545.       if (Alien[ActiveCols[ChosenColumn]][Row].Ord.Status == ACTIVE)  {
  546.         // Set the bomb from this alien
  547.         AlienBomb[BombIdx].Status = ACTIVE;
  548.         AlienBomb[BombIdx].X = Alien[ActiveCols[ChosenColumn]][Row].Ord.X + int(AlienWidth[Row] / 2);
  549.         // above sets bomb to drop around invaders centre, here we add a litrle randomness around this pos
  550.         AlienBomb[BombIdx].X = (AlienBomb[BombIdx].X - 2) + random(0, 4);
  551.         AlienBomb[BombIdx].Y = Alien[ActiveCols[ChosenColumn]][Row].Ord.Y + 4;
  552.         break;
  553.       }
  554.       Row--;
  555.     }
  556.   }
  557. }
  558.  
  559.  
  560.  
  561. void BombCollisions()
  562. {
  563.   //check bombs collisions
  564.   for (int i = 0; i < MAXBOMBS; i++)
  565.   {
  566.     if (AlienBomb[i].Status == ACTIVE)
  567.     {
  568.       if (AlienBomb[i].Y > 64)          // gone off bottom of screen
  569.         AlienBomb[i].Status = DESTROYED;
  570.       else
  571.       {
  572.         // HAS IT HIT PLAYERS missile
  573.         if (Collision(AlienBomb[i], BOMB_WIDTH, BOMB_HEIGHT, Missile, MISSILE_WIDTH, MISSILE_HEIGHT))
  574.         {
  575.           // destroy missile and bomb
  576.           AlienBomb[i].Status = EXPLODING;
  577.           Missile.Status = DESTROYED;
  578.         }
  579.         else
  580.         {
  581.           // has it hit players ship
  582.           if (Collision(AlienBomb[i], BOMB_WIDTH, BOMB_HEIGHT, Player.Ord, TANKGFX_WIDTH, TANKGFX_HEIGHT))
  583.           {
  584.             PlayerHit();
  585.             AlienBomb[i].Status = DESTROYED;
  586.           }
  587.           else
  588.             BombAndBasesCollision(&AlienBomb[i]);
  589.         }
  590.       }
  591.     }
  592.   }
  593. }
  594.  
  595. void BombAndBasesCollision(GameObjectStruct *Bomb)  {
  596.   // check and handle any bomb and base collision
  597.   for (int i = 0; i < NUM_BASES; i++)
  598.   {
  599.     if (Collision(*Bomb, BOMB_WIDTH, BOMB_HEIGHT, Base[i].Ord, BASE_WIDTH, BASE_HEIGHT))
  600.     {
  601.       // Now get the position of the bomb within the structure so we can destroy it bit by bit
  602.       // we first get the relative position from the left hand side of the base
  603.       // then we divide this by 2 (X>>1)  , this gives us a position with 2bit resolution
  604.       unsigned char X = Bomb->X - Base[i].Ord.X;
  605.       X = X >> 1;
  606.  
  607.       // Because the bomb is 2 pixels wide it's X pos could have been less than the bases XPos, if this is the case the above substaction
  608.       // on an unsigned char (byte) will cause a large number to occur (255), we check for this and if this large number has
  609.       // resulted we just set X to 0 for the start of the bases structure
  610.       if (X > 7)  X = 0;
  611.  
  612.       // We now look at wether the bomb is hitting a part of the structure below it.
  613.       // the bomb may be past the top of the base (even on first collision detection) as it moves in Y amounts greater than 1 pixel to give a higher speed
  614.       // so we start from top of the base and destroy any structure of the base that comes before or equal to the
  615.       // bombs Y pos, if we go past the bomb Y without finding any bit of base then stop looking and bomb is allowed to progress further down
  616.       signed char Bomb_Y = (Bomb->Y + BOMB_HEIGHT) - Base[i].Ord.Y;
  617.       unsigned char Base_Y = 0;
  618.  
  619.       while ((Base_Y <= Bomb_Y) & (Base_Y < BASE_HEIGHT) & (Bomb->Status == ACTIVE))
  620.       {
  621.         unsigned char Idx = (Base_Y * BASE_WIDTH_IN_BYTES) + (X >> 2); // this gets the index for the byte in question from the gfx array
  622.         unsigned char TheByte = Base[i].Gfx[Idx]; // we now have the byte to inspect, but need to only look at the 2 bits where the bomb is colliding
  623.         // now isolate the 2 bits in question, we have the correct byte and we only have need the bottom 2 bits of X now to denote which bits
  624.         // we need to destroy in the byte itself
  625.         unsigned char BitIdx = X & 3;     // this preseves the bottom 2 bits and removes the rest
  626.         // A value of X of 0 (zero) would mean we want to look at the first 2 bits of the byte
  627.         // A value of X of 1 (zero) would mean we want to look at the second 2 bits of the byte
  628.         // A value of X of 2 (zero) would mean we want to look at the third 2 bits of the byte
  629.         // A value of X of 3 (zero) would mean we want to look at the fourth 2 bits of the byte
  630.  
  631.         // So we need an AND mask to isolate these bits depending on the value of X
  632.         // Here it is and we initially set it to the first 2 bits
  633.         unsigned char Mask = B11000000;
  634.         // now we need to move those 2 "11"'s to the right depending on the value of X as noted above
  635.         // the next line may look odd but all we're doing is multiplying X by 2 initially, so the values above would become,0,2,4,6 rather than 0,1,2,3
  636.         // Then we move the bits in the mask by this new amount, check it above,a value of X of 2 would shift the bits 4 places, whichis correct!
  637.         Mask = Mask >> (BitIdx << 1);
  638.  
  639.         //now we peform a logical and to remove all bits (pixels) from around these two bits
  640.         TheByte = TheByte & Mask;
  641.  
  642.         // and if there are any set pixels (bits) then they must be destroyed, else the bomb is allowed to continue
  643.         if (TheByte > 0)
  644.         {
  645.           // There are some pixels to destroy
  646.           //We need to remove the pixels(bits) where there were "11"'s in the mask and leave those intact where there were 0's
  647.           //easiest way, flip all 0's in the mask to ones and all 1's to 0's, the tilde "~" means invert (swap), reffered to as logical NOT
  648.           Mask = ~Mask;
  649.           // we can then "AND" the byte with the Mask to remove the bits but leave the rest intact
  650.           Base[i].Gfx[Idx] = Base[i].Gfx[Idx] & Mask;
  651.  
  652.           // now do some collateral damage to surrounding bricks, try left hand side first
  653.           if (X > 0) {      // not at far left, if we were there would be nothing to destroy to this left
  654.             if (random(CHANCE_OF_BOMB_DAMAGE_TO_LEFT_OR_RIGHT))  // if true blow some bricks to the left hand side
  655.             {
  656.               if (X != 4)    // we not at start of second byte
  657.               {
  658.                 Mask = (Mask << 1) | 1;
  659.                 Base[i].Gfx[Idx] = Base[i].Gfx[Idx] & Mask;
  660.               }
  661.               else          // we are at very start of second byte, so wipe out adjacent pixel first byte
  662.               {
  663.                 Base[i].Gfx[Idx - 1] = Base[i].Gfx[Idx - 1] & B11111110;
  664.               }
  665.             }
  666.           }
  667.  
  668.           // now do some collateral damage right hand side first
  669.           if (X < 7) {      // not at far right, if we were there would be nothing to destroy to this right
  670.             if (random(CHANCE_OF_BOMB_DAMAGE_TO_LEFT_OR_RIGHT))  // if true blow some bricks to the left hand side
  671.             {
  672.               if (X != 3)    // not at last pixel of left of first byte
  673.               {
  674.                 Mask = (Mask >> 1) | 128;
  675.                 Base[i].Gfx[Idx] = Base[i].Gfx[Idx] & Mask;
  676.               }
  677.               else          // we are at last pixel of first byte so destroy pixil in adjacent byte
  678.               {
  679.                 Base[i].Gfx[Idx + 1] = Base[i].Gfx[Idx + 1] & B01111111;
  680.               }
  681.             }
  682.           }
  683.           if (random(CHANCE_OF_BOMB_PENETRATING_DOWN) == false) // if false BOMB EXPLODES else carries on destroying more
  684.             Bomb->Status = EXPLODING;
  685.         }
  686.         else
  687.           Base_Y++;
  688.       }
  689.     }
  690.   }
  691. }
  692.  
  693. void MissileAndBasesCollisions()  {
  694.   // check and handle any player missile and base collision
  695.   for (int i = 0; i < NUM_BASES; i++)
  696.   {
  697.     if (Collision(Missile, MISSILE_WIDTH, MISSILE_HEIGHT, Base[i].Ord, BASE_WIDTH, BASE_HEIGHT))
  698.     {
  699.       // Now get the position of the bomb within the structure so we can destroy it bit by bit
  700.       // we first get the relative position from the left hand side of the base
  701.       // then we divide this by 2 (X>>1)  , this gives us a position with 2bit resolution
  702.       unsigned char X = Missile.X - Base[i].Ord.X;
  703.       X = X >> 1;
  704.  
  705.       // Because the bomb is 2 pixels wide it's X pos could have been less than the bases XPos, if this is the case the above substaction
  706.       // on an unsigned char (byte) will cause a large number to occur (255), we check for this and if this large number has
  707.       // resulted we just set X to 0 for the start of the bases structure
  708.       if (X > 7)  X = 0;
  709.  
  710.       // We now look at wether the bomb is hitting a part of the structure above it.
  711.       // the bomb may be past the top of the base (even on first collision detection) as it moves in Y amounts greater than 1 pixel to give a higher speed
  712.       // so we start from top of the base and destroy any structure of the base that comes before or equal to the
  713.       // bombs Y pos, if we go past the bomb Y without finding any bit of base then stop looking and bomb is allowed to progress further down
  714.       signed char Missile_Y = Missile.Y - Base[i].Ord.Y;
  715.       signed char Base_Y = BASE_HEIGHT - 1;
  716.       while ((Base_Y >= Missile_Y) & (Base_Y >= 0) & (Missile.Status == ACTIVE))
  717.       {
  718.         // if(Base_Y<0) {Serial.println("oop"); delay(999999);}
  719.         unsigned char Idx = (Base_Y * BASE_WIDTH_IN_BYTES) + (X >> 2); // this gets the index for the byte in question from the gfx array
  720.         unsigned char TheByte = Base[i].Gfx[Idx]; // we now have the byte to inspect, but need to only look at the 2 bits where the bomb is colliding
  721.  
  722.         // now isolate the 2 bits in question, we have the correct byte and we only have need the bottom 2 bits of X now to denote which bits
  723.         // we need to destroy in the byte itself
  724.         unsigned char BitIdx = X & 3;     // this preseves the bottom 2 bits and removes the rest
  725.         // A value of X of 0 (zero) would mean we want to look at the first 2 bits of the byte
  726.         // A value of X of 1 (zero) would mean we want to look at the second 2 bits of the byte
  727.         // A value of X of 2 (zero) would mean we want to look at the third 2 bits of the byte
  728.         // A value of X of 3 (zero) would mean we want to look at the fourth 2 bits of the byte
  729.  
  730.         // So we need an AND mask to isolate these bits depending on the value of X
  731.         // Here it is and we initially set it to the first 2 bits
  732.         unsigned char Mask = B11000000;
  733.  
  734.         // now we need to move those 2 "11"'s to the right depending on the value of X as noted above
  735.         // the next line may look odd but all we're doing is multiplying X by 2 initially, so the values above would become,0,2,4,6 rather than 0,1,2,3
  736.         // Then we move the bits in the mask by this new amount, check it above,a value of X of 2 would shift the bits 4 places, whichis correct!
  737.         Mask = Mask >> (BitIdx << 1);
  738.  
  739.         //now we peform a logical AND to remove all bits (pixels) from around these two bits
  740.         TheByte = TheByte & Mask;
  741.  
  742.         // and if there are any set pixels (bits) then they must be destroyed, else the bomb is allowed to continue
  743.         if (TheByte > 0)
  744.         {
  745.           // There are some pixels to destroy
  746.           //We need to remove the pixels(bits) where there were "11"'s in the mask and leave those intact where there were 0's
  747.           //easiest way, flip all 0's in the mask to ones and all 1's to 0's, the tilde "~" means invert (swap), reffered to as logical NOT
  748.           Mask = ~Mask;
  749.           // we can then "AND" the byte with the Mask to remove the bits but leave the rest intact
  750.           Base[i].Gfx[Idx] = Base[i].Gfx[Idx] & Mask;
  751.  
  752.           // now do some collateral damage to surrounding bricks, try left hand side first
  753.           if (X > 0) {      // not at far left, if we were there would be nothing to destroy to this left
  754.             if (random(CHANCE_OF_BOMB_DAMAGE_TO_LEFT_OR_RIGHT))  // if true blow some bricks to the left hand side
  755.             {
  756.               if (X != 4)    // we not at start of second byte
  757.               {
  758.                 Mask = (Mask << 1) | 1;
  759.                 Base[i].Gfx[Idx] = Base[i].Gfx[Idx] & Mask;
  760.               }
  761.               else          // we are at very start of second byte, so wipe out adjacent pixel first byte
  762.               {
  763.                 Base[i].Gfx[Idx - 1] = Base[i].Gfx[Idx - 1] & B11111110;
  764.               }
  765.             }
  766.           }
  767.  
  768.           // now do some collateral damage right hand side first
  769.           if (X < 7) {      // not at far right, if we were there would be nothing to destroy to this right
  770.             if (random(CHANCE_OF_BOMB_DAMAGE_TO_LEFT_OR_RIGHT))  // if true blow some bricks to the left hand side
  771.             {
  772.               if (X != 3)    // not at last pixel of left of first byte
  773.               {
  774.                 Mask = (Mask >> 1) | 128;
  775.                 Base[i].Gfx[Idx] = Base[i].Gfx[Idx] & Mask;
  776.               }
  777.               else          // we are at last pixel of first byte so destroy pixil in adjacent byte
  778.               {
  779.                 Base[i].Gfx[Idx + 1] = Base[i].Gfx[Idx + 1] & B01111111;
  780.               }
  781.             }
  782.           }
  783.           if (random(CHANCE_OF_BOMB_PENETRATING_DOWN) == false) // if false BOMB EXPLODES else carries on destroying more
  784.             Missile.Status = EXPLODING;
  785.         }
  786.         else
  787.           Base_Y--;
  788.       }
  789.     }
  790.   }
  791. }
  792.  
  793. void PlayerHit()  {
  794.   Player.Ord.Status = EXPLODING;
  795.   Player.ExplosionGfxCounter = PLAYER_EXPLOSION_TIME;
  796.   Missile.Status = DESTROYED;
  797.   //  noiseAC(500,10,&PlayerExplosionNoiseCompleted);
  798. }
  799.  
  800. void CheckCollisions()
  801. {
  802.   MissileAndAlienCollisions();
  803.   MotherShipCollisions();
  804.   MissileAndBasesCollisions();
  805.   BombCollisions();
  806.   AlienAndBaseCollisions();
  807. }
  808.  
  809. char GetAlienBaseCollisionMask(int AlienX, int AlienWidth, int BaseX)
  810. {
  811.   signed int DamageWidth;
  812.   unsigned char LeftMask, RightMask, FullMask;
  813.  
  814.  
  815.   // this routine uses a 1 to mean remove bit and 0 to preserve, this is kind of opposite of what we would
  816.   // normally think of, but it's done this way as when we perform bit shifting to show which bits are preserved
  817.   // it will shift in 0's in (as that's just was the C shift operater ">>" and "<<" does
  818.   // at the end we will flip all the bits 0 becomes 1, 1 becomes 0 etc. so that the mask then works correctly
  819.   // with the calling code
  820.  
  821.   LeftMask = B11111111;   // initially set to remove all
  822.   RightMask = B11111111;  // unless change in code below
  823.   // if Alien X more than base x then some start bits are unaffected
  824.   if (AlienX > BaseX)
  825.   {
  826.     // we shift the bits above to the right by the amount unaffected, thus putting 0's into the bits
  827.     // not to delete
  828.     DamageWidth = AlienX - BaseX;
  829.     LeftMask >>= DamageWidth;
  830.   }
  831.  
  832.   // now work out how much of remaining byte is affected
  833.  
  834.   // if right hand side of alien is less than BaseX right hand side then some preserved at the right hand end
  835.   if (AlienX + AlienWidth < BaseX + (BASE_WIDTH / 2))
  836.   {
  837.     DamageWidth = (BaseX + (BASE_WIDTH / 2)) - (AlienX + AlienWidth);
  838.     RightMask <<= DamageWidth;
  839.  
  840.   }
  841.   // we now have two masks, one showing which bits to preserve on left of the byte, the other the right hand side,
  842.   // we need to combine them to one mask, the code in the brackets does this combining
  843.  
  844.   // at the moment a 0 means keep, 1 destroy, but this is actually makes it difficult to implement later on, so we flip
  845.   // the bits to be a more logical 1= keep bit and 0 remove bit (pixel) and then return the mask
  846.   // the tilde (~) flips the bits that resulted from combining the two masks
  847.  
  848.   return ~(LeftMask & RightMask);
  849. }
  850.  
  851. void DestroyBase(GameObjectStruct *Alien, BaseStruct *Base, char Mask, int BaseByteOffset)
  852. {
  853.   signed char Y;
  854.   // go down "removing" bits to the depth that the alien is down into the base
  855.   Y = (Alien->Y + ALIEN_HEIGHT) - Base->Ord.Y;
  856.   if (Y > BASE_HEIGHT - 1) Y = BASE_HEIGHT - 1;
  857.   for (; Y >= 0; Y--) {
  858.     Base->Gfx[(Y * 2) + BaseByteOffset] = Base->Gfx[(Y * 2) + BaseByteOffset] & Mask;
  859.   }
  860. }
  861.  
  862. void AlienAndBaseCollisions()
  863. {
  864.   unsigned char Mask;
  865.   // checks if aliens are in collision with the tank
  866.   // start at bottom row as they are most likely to be in contact or not and if not then none further up are either
  867.   for (int row = 2; row >= 0; row--)
  868.   {
  869.     for (int column = 0; column < NUM_ALIEN_COLUMNS; column++)
  870.     {
  871.       if (Alien[column][row].Ord.Status == ACTIVE)
  872.       {
  873.         // now scan for a collision with each base in turn
  874.         for (int BaseIdx = 0; BaseIdx < NUM_BASES; BaseIdx++)
  875.         {
  876.           if (Collision(Alien[column][row].Ord, AlienWidth[row], ALIEN_HEIGHT, Base[BaseIdx].Ord, BASE_WIDTH, BASE_HEIGHT))
  877.           {
  878.             // WE HAVE A COLLSISION, REMOVE BITS OF BUILDING
  879.             // process left half (first byte) of base first
  880.             Mask = GetAlienBaseCollisionMask(Alien[column][row].Ord.X, AlienWidth[row], Base[BaseIdx].Ord.X);
  881.             DestroyBase(&Alien[column][row].Ord, &Base[BaseIdx], Mask, 0);
  882.  
  883.             // do right half, second byte of base
  884.             Mask = GetAlienBaseCollisionMask(Alien[column][row].Ord.X, AlienWidth[row], Base[BaseIdx].Ord.X + (BASE_WIDTH / 2));
  885.             DestroyBase(&Alien[column][row].Ord, &Base[BaseIdx], Mask, 1);
  886.           }
  887.         }
  888.       }
  889.     }
  890.   }
  891. }
  892.  
  893. void MotherShipCollisions()
  894. {
  895.   if ((Missile.Status == ACTIVE) & (MotherShip.Ord.Status == ACTIVE))
  896.   {
  897.     if (Collision(Missile, MISSILE_WIDTH, MISSILE_HEIGHT, MotherShip.Ord, MOTHERSHIP_WIDTH, MOTHERSHIP_HEIGHT))
  898.     {
  899.       MotherShip.Ord.Status = EXPLODING;
  900.       MotherShip.ExplosionGfxCounter = EXPLOSION_GFX_TIME;
  901.       Missile.Status = DESTROYED;
  902.       // generate the score for the mothership hit, note in the real arcade space invaders the score was not random but
  903.       // just appeared so, a player could infulence its value with clever play, but we'll keep it a little simpler
  904.       MotherShipBonus = random(4); // a random number between 0 and 3
  905.       switch (MotherShipBonus)
  906.       {
  907.         case 0: MotherShipBonus = 50; break;
  908.         case 1: MotherShipBonus = 100; break;
  909.         case 2: MotherShipBonus = 150; break;
  910.         case 3: MotherShipBonus = 300; break;
  911.       }
  912.       Player.Score += MotherShipBonus;
  913.       MotherShipBonusXPos = MotherShip.Ord.X;
  914.       if (MotherShipBonusXPos > 100)                // to ensure isn't half off right hand side of screen
  915.         MotherShipBonusXPos = 100;
  916.       if (MotherShipBonusXPos < 0)                // to ensure isn't half off right hand side of screen
  917.         MotherShipBonusXPos = 0;
  918.       MotherShipBonusCounter = DISPLAY_MOTHERSHIP_BONUS_TIME;
  919.     }
  920.   }
  921. }
  922.  
  923.  
  924. void MissileAndAlienCollisions()
  925. {
  926.   for (int across = 0; across < NUM_ALIEN_COLUMNS; across++)
  927.   {
  928.     for (int down = 0; down < NUM_ALIEN_ROWS; down++)
  929.     {
  930.       if (Alien[across][down].Ord.Status == ACTIVE)
  931.       {
  932.         if (Missile.Status == ACTIVE)
  933.         {
  934.           if (Collision(Missile, MISSILE_WIDTH, MISSILE_HEIGHT, Alien[across][down].Ord, AlienWidth[down], INVADER_HEIGHT))
  935.           {
  936.             // missile hit
  937.             Alien[across][down].Ord.Status = EXPLODING;
  938.             // toneAC(700, 10, 100, true);
  939.             tone (BuzzerPin, 700, 100);
  940.             Missile.Status = DESTROYED;
  941.             Player.Score += GetScoreForAlien(down);
  942.             Player.AliensDestroyed++;
  943.             // calc new speed of aliens, note (float) must be before TOTAL_ALIENS to force float calc else
  944.             // you will get an incorrect result
  945.             Player.AlienSpeed = ((1 - (Player.AliensDestroyed / (float)TOTAL_ALIENS)) * INVADERS_SPEED);
  946.             // for very last alien make to  fast!
  947.             if (Player.AliensDestroyed == TOTAL_ALIENS - 2)
  948.               if (AlienXMoveAmount > 0)
  949.                 AlienXMoveAmount = ALIEN_X_MOVE_AMOUNT * 2;
  950.               else
  951.                 AlienXMoveAmount = -(ALIEN_X_MOVE_AMOUNT * 2);
  952.             // for very last alien make to super fast!
  953.             if (Player.AliensDestroyed == TOTAL_ALIENS - 1)
  954.               if (AlienXMoveAmount > 0)
  955.                 AlienXMoveAmount = ALIEN_X_MOVE_AMOUNT * 4;
  956.               else
  957.                 AlienXMoveAmount = -(ALIEN_X_MOVE_AMOUNT * 4);
  958.  
  959.             if (Player.AliensDestroyed == TOTAL_ALIENS)
  960.               NextLevel(&Player);
  961.           }
  962.         }
  963.         if (Alien[across][down].Ord.Status == ACTIVE)   // double check still active afer above code
  964.         {
  965.           // check if this alien is in contact with TankGfx
  966.           if (Collision(Player.Ord, TANKGFX_WIDTH, TANKGFX_HEIGHT, Alien[across][down].Ord, AlienWidth[down], ALIEN_HEIGHT))
  967.             PlayerHit();
  968.           else
  969.           {
  970.             // check if alien is below bottom of screen
  971.             if (Alien[across][down].Ord.Y + 8 > SCREEN_HEIGHT)
  972.               PlayerHit();
  973.           }
  974.         }
  975.       }
  976.     }
  977.   }
  978. }
  979.  
  980. bool Collision(GameObjectStruct Obj1, unsigned char Width1, unsigned char Height1, GameObjectStruct Obj2, unsigned char Width2, unsigned char Height2)
  981. {
  982.   return ((Obj1.X + Width1 > Obj2.X) & (Obj1.X < Obj2.X + Width2) & (Obj1.Y + Height1 > Obj2.Y) & (Obj1.Y < Obj2.Y + Height2));
  983. }
  984.  
  985. int RightMostPos()  {
  986.   //returns x pos of right most alien
  987.   int Across = NUM_ALIEN_COLUMNS - 1;
  988.   int Down;
  989.   int Largest = 0;
  990.   int RightPos;
  991.   while (Across >= 0) {
  992.     Down = 0;
  993.     while (Down < NUM_ALIEN_ROWS) {
  994.       if (Alien[Across][Down].Ord.Status == ACTIVE)
  995.       {
  996.         // different aliens have different widths, add to x pos to get rightpos
  997.         RightPos = Alien[Across][Down].Ord.X + AlienWidth[Down];
  998.         if (RightPos > Largest)
  999.           Largest = RightPos;
  1000.       }
  1001.       Down++;
  1002.     }
  1003.     if (Largest > 0) // we have found largest for this coloum
  1004.       return Largest;
  1005.     Across--;
  1006.   }
  1007.   return 0;  // should never get this far
  1008. }
  1009.  
  1010. int LeftMostPos()  {
  1011.   //returns x pos of left most alien
  1012.   int Across = 0;
  1013.   int Down;
  1014.   int Smallest = SCREEN_WIDTH * 2;
  1015.   while (Across < NUM_ALIEN_COLUMNS) {
  1016.     Down = 0;
  1017.     while (Down < 3) {
  1018.       if (Alien[Across][Down].Ord.Status == ACTIVE)
  1019.         if (Alien[Across][Down].Ord.X < Smallest)
  1020.           Smallest = Alien[Across][Down].Ord.X;
  1021.       Down++;
  1022.     }
  1023.     if (Smallest < SCREEN_WIDTH * 2) // we have found smalest for this coloum
  1024.       return Smallest;
  1025.     Across++;
  1026.   }
  1027.   return 0;  // should nevr get this far
  1028. }
  1029.  
  1030. void UpdateDisplay()
  1031. {
  1032.   int i;
  1033.  
  1034.   display.clearDisplay();
  1035.  
  1036.   // Mothership bonus display if required
  1037.   if (MotherShipBonusCounter > 0)
  1038.   {
  1039.     // mothership bonus
  1040.     display.setCursor(MotherShipBonusXPos, 0);
  1041.     display.print(MotherShipBonus);
  1042.     MotherShipBonusCounter--;
  1043.   }
  1044.   else
  1045.   {
  1046.     // draw score and lives, anything else can go above them
  1047.     display.setCursor(0, 0);
  1048.     display.print(Player.Score);
  1049.     display.setCursor(SCREEN_WIDTH - 7, 0);
  1050.     display.print(Player.Lives);
  1051.   }
  1052.  
  1053.   //BOMBS
  1054.   // draw bombs next as aliens have priority of overlapping them
  1055.   for (i = 0; i < MAXBOMBS; i++)  {
  1056.     if (AlienBomb[i].Status == ACTIVE)
  1057.       display.drawBitmap(AlienBomb[i].X, AlienBomb[i].Y,  AlienBombGfx, 2, 4, WHITE);
  1058.     else  {// must be destroyed
  1059.       if (AlienBomb[i].Status == EXPLODING)
  1060.         display.drawBitmap(AlienBomb[i].X - 4, AlienBomb[i].Y,  ExplosionGfx, 4, 8, WHITE);
  1061.       // Ensure on next draw that ExplosionGfx dissapears
  1062.       AlienBomb[i].Status = DESTROYED;
  1063.     }
  1064.   }
  1065.  
  1066.   //Invaders
  1067.   for (int across = 0; across < NUM_ALIEN_COLUMNS; across++)
  1068.   {
  1069.     for (int down = 0; down < NUM_ALIEN_ROWS; down++)
  1070.     {
  1071.       if (Alien[across][down].Ord.Status == ACTIVE) {
  1072.         switch (down)  {
  1073.           case 0:
  1074.             if (AnimationFrame)
  1075.               display.drawBitmap(Alien[across][down].Ord.X, Alien[across][down].Ord.Y,  InvaderTopGfx, AlienWidth[down], INVADER_HEIGHT, WHITE);
  1076.             else
  1077.               display.drawBitmap(Alien[across][down].Ord.X, Alien[across][down].Ord.Y,  InvaderTopGfx2, AlienWidth[down], INVADER_HEIGHT, WHITE);
  1078.             break;
  1079.           case 1:
  1080.             if (AnimationFrame)
  1081.               display.drawBitmap(Alien[across][down].Ord.X, Alien[across][down].Ord.Y,  InvaderMiddleGfx, AlienWidth[down], INVADER_HEIGHT, WHITE);
  1082.             else
  1083.               display.drawBitmap(Alien[across][down].Ord.X, Alien[across][down].Ord.Y,  InvaderMiddleGfx2, AlienWidth[down], INVADER_HEIGHT, WHITE);
  1084.             break;
  1085.           default:
  1086.             if (AnimationFrame)
  1087.               display.drawBitmap(Alien[across][down].Ord.X, Alien[across][down].Ord.Y,  InvaderBottomGfx, AlienWidth[down], INVADER_HEIGHT, WHITE);
  1088.             else
  1089.               display.drawBitmap(Alien[across][down].Ord.X, Alien[across][down].Ord.Y,  InvaderBottomGfx2, AlienWidth[down], INVADER_HEIGHT, WHITE);
  1090.         }
  1091.       }
  1092.       else  {
  1093.         if (Alien[across][down].Ord.Status == EXPLODING) {
  1094.           Alien[across][down].ExplosionGfxCounter--;
  1095.           if (Alien[across][down].ExplosionGfxCounter > 0)  {
  1096.             //            toneAC(Alien[across][down].ExplosionGfxCounter * 100, 10, 100, true);
  1097.             tone (BuzzerPin, Alien[across][down].ExplosionGfxCounter * 100, 100);
  1098.             display.drawBitmap(Alien[across][down].Ord.X, Alien[across][down].Ord.Y,  ExplosionGfx, 13, 8, WHITE);
  1099.           }
  1100.           else
  1101.             Alien[across][down].Ord.Status = DESTROYED;
  1102.         }
  1103.       }
  1104.     }
  1105.   }
  1106.  
  1107.   // player
  1108.   if (Player.Ord.Status == ACTIVE)
  1109.     display.drawBitmap(Player.Ord.X, Player.Ord.Y,  TankGfx, TANKGFX_WIDTH, TANKGFX_HEIGHT, WHITE);
  1110.   else {
  1111.     if (Player.Ord.Status == EXPLODING)  {
  1112.       for (i = 0; i < TANKGFX_WIDTH; i += 2)  {
  1113.         display.drawBitmap(Player.Ord.X + i, Player.Ord.Y,  ExplosionGfx, random(4) + 2, 8, WHITE);
  1114.       }
  1115.       Player.ExplosionGfxCounter--;
  1116.       if (Player.ExplosionGfxCounter == 0)  {
  1117.         Player.Ord.Status = DESTROYED;
  1118.         LoseLife();
  1119.       }
  1120.     }
  1121.   }
  1122.   //missile
  1123.   if (Missile.Status == ACTIVE)
  1124.     display.drawBitmap(Missile.X, Missile.Y,  MissileGfx, MISSILE_WIDTH, MISSILE_HEIGHT, WHITE);
  1125.  
  1126.   // mothership (not bonus if hit)
  1127.   if (MotherShip.Ord.Status == ACTIVE)
  1128.     display.drawBitmap(MotherShip.Ord.X, MotherShip.Ord.Y,  MotherShipGfx, MOTHERSHIP_WIDTH, MOTHERSHIP_HEIGHT, WHITE);
  1129.   else
  1130.   {
  1131.     if (MotherShip.Ord.Status == EXPLODING)
  1132.     {
  1133.       for (i = 0; i < MOTHERSHIP_WIDTH; i += 2)  {
  1134.         display.drawBitmap(MotherShip.Ord.X + i, MotherShip.Ord.Y,  ExplosionGfx, random(4) + 2, MOTHERSHIP_HEIGHT, WHITE);
  1135.       }
  1136.       //      toneAC(MotherShip.ExplosionGfxCounter*50,10,100,true);
  1137.       tone (BuzzerPin, MotherShip.ExplosionGfxCounter * 50, 100);
  1138.  
  1139.       MotherShip.ExplosionGfxCounter--;
  1140.       if (MotherShip.ExplosionGfxCounter == 0)  {
  1141.         MotherShip.Ord.Status = DESTROYED;
  1142.       }
  1143.     }
  1144.   }
  1145.  
  1146.   // plot bases
  1147.  
  1148.   for (i = 0; i < NUM_BASES; i++)  {
  1149.     if (Base[i].Ord.Status == ACTIVE)
  1150.       display.drawBitmap(Base[i].Ord.X, Base[i].Ord.Y,  Base[i].Gfx, BASE_WIDTH, BASE_HEIGHT, WHITE, true);
  1151.   }
  1152.   display.display();
  1153. }
  1154.  
  1155. void LoseLife()  {
  1156.   Player.Lives--;
  1157.   if (Player.Lives > 0)  {
  1158.     DisplayPlayerAndLives(&Player);
  1159.     // clear alien missiles
  1160.     for (int i = 0; i < MAXBOMBS; i++)  {
  1161.       AlienBomb[i].Status = DESTROYED;
  1162.       AlienBomb[i].Y = 0;
  1163.     }
  1164.     // ENABLE PLAYER
  1165.     Player.Ord.Status = ACTIVE;
  1166.     Player.Ord.X = 0;
  1167.   }
  1168.   else  {
  1169.     GameOver();
  1170.   }
  1171. }
  1172.  
  1173.  
  1174. void GameOver()  {
  1175.   GameInPlay = false;
  1176.   display.clearDisplay();
  1177.   CentreText("Player 1", 0);
  1178.   CentreText("Game Over", 12);
  1179.   CentreText("Score ", 24);
  1180.   display.print(Player.Score);
  1181.   if (Player.Score > HiScore)
  1182.   {
  1183.     CentreText("NEW HIGH SCORE!!!", 36);
  1184.     CentreText("**CONGRATULATIONS**", 48);
  1185.   }
  1186.   display.display();
  1187.   if (Player.Score > HiScore) {
  1188.     HiScore = Player.Score;
  1189.     EEPROM.put(0, HiScore);
  1190.     PlayRewardMusic();
  1191.   }
  1192.   delay(2500);
  1193. }
  1194.  
  1195.  
  1196. void PlayRewardMusic()
  1197. {
  1198.   unsigned char Notes[] = { 26, 20, 18, 22, 20, 0, 26, 0, 26 };
  1199.   unsigned char NoteDurations[] = { 40, 20, 20, 40, 30, 50, 30, 10, 30 };
  1200.   for (int i = 0; i < 9; i++)
  1201.   {
  1202.     // toneAC(Notes[i]*10,10,0,true);
  1203.     tone (BuzzerPin, Notes[i] * 10, 10);
  1204.     delay(NoteDurations[i] * 10); // time not plays for
  1205.     // noToneAC();                   // stop note
  1206.     noTone (BuzzerPin);
  1207.     delay(20);                    // small delay between notes
  1208.   }
  1209. //  noToneAC();
  1210. noTone (BuzzerPin);
  1211. }
  1212.  
  1213.  
  1214. void DisplayPlayerAndLives(PlayerStruct *Player)  {
  1215.   display.clearDisplay();
  1216.   CentreText("Player 1", 0);
  1217.   CentreText("Score ", 12);
  1218.   display.print(Player->Score);
  1219.   CentreText("Lives ", 24);
  1220.   display.print(Player->Lives);
  1221.   CentreText("Level ", 36);
  1222.   display.print(Player->Level);
  1223.   display.display();
  1224.   delay(2000);
  1225.   Player->Ord.X = PLAYER_X_START;
  1226. }
  1227.  
  1228.  
  1229. void CentreText(const char *Text, unsigned char Y)  {
  1230.   // centres text on screen
  1231.   display.setCursor(int((float)(SCREEN_WIDTH) / 2 - ((strlen(Text) * 6) / 2)), Y);
  1232.   display.print(Text);
  1233. }
  1234.  
  1235.  
  1236. void InitPlayer()  {
  1237.   Player.Ord.Y = PLAYER_Y_START;
  1238.   Player.Ord.X = PLAYER_X_START;
  1239.   Player.Ord.Status = ACTIVE;
  1240.   Player.Lives = LIVES;
  1241.   Player.Level = 0;
  1242.   Missile.Status = DESTROYED;
  1243.   Player.Score = 0;
  1244. }
  1245.  
  1246.  
  1247. void NextLevel(PlayerStruct *Player)  {
  1248.   // reset any dropping bombs
  1249.   int YStart;
  1250.   for (int i = 0; i < MAXBOMBS; i++)
  1251.     AlienBomb[i].Status = DESTROYED;
  1252.   AnimationFrame = false;
  1253.   Player->Level++;
  1254.   YStart = ((Player->Level - 1) % LEVEL_TO_RESET_TO_START_HEIGHT) * AMOUNT_TO_DROP_BY_PER_LEVEL;
  1255.   InitAliens(YStart);
  1256.   AlienXMoveAmount = ALIEN_X_MOVE_AMOUNT;
  1257.   Player->AlienSpeed = INVADERS_SPEED;
  1258.   Player->AliensDestroyed = 0;
  1259.   MotherShip.Ord.X = -MOTHERSHIP_WIDTH;
  1260.   MotherShip.Ord.Status = DESTROYED;
  1261.   Missile.Status = DESTROYED;
  1262.   randomSeed(100);
  1263.   InitBases();
  1264.   DisplayPlayerAndLives(Player);
  1265.   MusicIndex = 0;
  1266.   MusicCounter = NOTELENGTH;
  1267. }
  1268.  
  1269.  
  1270. void InitBases()  {
  1271.   // Bases need to be re-built!
  1272.   uint8_t TheByte;
  1273.   int Spacing = (SCREEN_WIDTH - (NUM_BASES * BASE_WIDTH)) / NUM_BASES;
  1274.   for (int i = 0; i < NUM_BASES; i++)
  1275.   {
  1276.     for (int DataIdx = 0; DataIdx < BASE_HEIGHT * BASE_WIDTH_IN_BYTES; DataIdx++)
  1277.     {
  1278.       TheByte   = pgm_read_byte(BaseGfx + DataIdx);
  1279.       Base[i].Gfx[DataIdx] = TheByte;
  1280.     }
  1281.     Base[i].Ord.X = (i * Spacing) + (i * BASE_WIDTH) + (Spacing / 2);
  1282.     Base[i].Ord.Y = BASE_Y;
  1283.     Base[i].Ord.Status = ACTIVE;
  1284.   }
  1285. }
  1286.  
  1287.  
  1288. void NewGame() {
  1289.   InitPlayer();
  1290.   NextLevel(&Player);
  1291. }
  1292.  
  1293. void InitAliens(int YStart)  {
  1294.   for (int across = 0; across < NUM_ALIEN_COLUMNS; across++)  {
  1295.     for (int down = 0; down < 3; down++)  {
  1296.       // we add down to centralise the aliens, just happens to be the right value we need per row!
  1297.       // we need to adjust a little as row zero should be 2, row 1 should be 1 and bottom row 0
  1298.       Alien[across][down].Ord.X = X_START_OFFSET + (across * (LARGEST_ALIEN_WIDTH + SPACE_BETWEEN_ALIEN_COLUMNS)) - (AlienWidth[down] / 2);
  1299.       Alien[across][down].Ord.Y = YStart + (down * SPACE_BETWEEN_ROWS);
  1300.       Alien[across][down].Ord.Status = ACTIVE;
  1301.       Alien[across][down].ExplosionGfxCounter = EXPLOSION_GFX_TIME;
  1302.     }
  1303.   }
  1304.   MotherShip.Ord.Y = 0;
  1305.   MotherShip.Ord.X = -MOTHERSHIP_WIDTH;
  1306.   MotherShip.Ord.Status = DESTROYED;
  1307. }