invaders.ino 32 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196
  1. #include <EEPROM.h>
  2. #include <SPI.h>
  3. #include <GD2.h>
  4. #define UART_SPEED 1000000
  5. #include "invaders_assets.h"
  6. /*---------------------------------------------
  7. Trivia: There is NO random number generator
  8. anywhere in Space Invaders....
  9. ---------------------------------------------*/
  10. /*---------------------------------------------
  11. Global definitions
  12. ---------------------------------------------*/
  13. enum graphic_id {
  14. // Invader sprites - Top, Middle, Bottom, two animation frames each...
  15. GR_INVADER_T,
  16. GR_INVADER_M,
  17. GR_INVADER_B,
  18. GR_BOMB_ZIGZAG, // Zigzag bomb
  19. GR_BOMB_BARS, // The bomb with rolling horizontal bars across it
  20. GR_BOMB_DIAG, // The bomb with diagonal bars across it
  21. GR_BOMB_OTHER, // Other bomb graphics (explosion and blank)
  22. // The player (with bullet)
  23. GR_PLAYER,
  24. GR_BULLET, // nb. Has a '0' in frame 2 (for the saucer...)
  25. // The saucer at the top
  26. GR_SAUCER,
  27. GR_SAUCER_SCORE,
  28. // Shields
  29. GR_SHIELD1,
  30. GR_SHIELD2,
  31. GR_SHIELD3,
  32. GR_SHIELD4
  33. };
  34. #define invaderRows 5
  35. #define invadersPerRow 11
  36. #define numInvaders (invaderRows*invadersPerRow)
  37. // Positions of things on screen
  38. // nb. Space Invaders screen is 256x224 pixels
  39. #define screenTop 8
  40. #define screenWidth 224
  41. #define screenHeight 256
  42. #define screenLeft ((480 - screenWidth) / 2)
  43. // Player
  44. #define playerMinLeft 18
  45. #define playerMaxRight 188
  46. #define playerYpos 216
  47. #define playerSpeed 1
  48. // Bullet
  49. #define bulletHeight 4
  50. #define bulletSpeed 4
  51. #define bulletTop 35
  52. // Invaders
  53. #define invaderAppearX 26
  54. #define invaderAppearY 64
  55. #define invaderXspacing 16
  56. #define invaderYspacing 16
  57. #define invaderXstep 2
  58. #define invaderYstep 8
  59. #define invaderXmin 10
  60. #define invaderXmax 202
  61. // Saucer
  62. #define saucerYpos 42
  63. #define saucerSpeed 1
  64. #define saucerXmin 0
  65. #define saucerXmax (screenWidth-16)
  66. #define saucerSkip 3
  67. #define saucerFrequency (1*60)
  68. // Shields
  69. #define numShields 4
  70. #define shieldXpos 32
  71. #define shieldYpos 192
  72. #define shieldXstep 45
  73. // Bombs
  74. #define bombSpeed 1
  75. #define bombYmax 230
  76. /*---------------------------------------------
  77. Global vars
  78. ---------------------------------------------*/
  79. // This increments once per frame
  80. static unsigned int frameCounter;
  81. // The current wave of invaders [0..n]
  82. static unsigned int invaderWave;
  83. // Number of lives the player has left...
  84. static byte numLives;
  85. // Player's score...
  86. static unsigned int playerScore;
  87. // High score
  88. static unsigned int highScore;
  89. // Number of living space invaders
  90. static unsigned int remainingInvaders;
  91. // Timer for the background heartbeat sound
  92. static int beatCounter;
  93. /*---------------------------------------------
  94. General functions
  95. ---------------------------------------------*/
  96. void printScore(int8_t x, const char *m, unsigned int s, int8_t xoff)
  97. {
  98. GD.cmd_text(screenLeft + 8 * x, screenTop, 16, 0, m);
  99. GD.cmd_number(screenLeft + 8 * (x + xoff), screenTop + 16, 16, 4, s);
  100. }
  101. void updateScore()
  102. {
  103. highScore = max(highScore, playerScore);
  104. printScore(0, "SCORE", playerScore, 0);
  105. printScore(20, "HI-SCORE", highScore, 4);
  106. }
  107. void drawBases()
  108. {
  109. GD.cmd_number(screenLeft, screenTop + 240, 16, 0, numLives);
  110. for (int i = 1; i < numLives; i++)
  111. GD.Vertex2ii(screenLeft + 16 * i, screenTop + 240, 0, CELL_PLAYER);
  112. }
  113. /*---------------------------------------------
  114. A generic object in the game
  115. ---------------------------------------------*/
  116. enum object_status {
  117. S_WAITING,
  118. S_ALIVE,
  119. S_DYING,
  120. S_DEAD
  121. };
  122. struct GameObject {
  123. byte sprite; // Which sprite to use for my graphic (see "sprite_id")
  124. byte status; // What I'm doing at the moment
  125. int xpos,ypos; // Position on screen
  126. byte frame; // Animation frame
  127. byte handle; // Bitmap handle
  128. // State of objects in the game
  129. void initialize(object_status t=S_WAITING, int x=400, int y=0) {
  130. status = t;
  131. xpos = x;
  132. ypos = y;
  133. handle = 0;
  134. frame = 0;
  135. }
  136. void draw() {
  137. if ((status != S_WAITING) && (status != S_DEAD))
  138. GD.Vertex2ii(xpos + screenLeft, ypos + screenTop, handle, frame);
  139. }
  140. byte collision() {
  141. return 0xff;
  142. // return GD.rd(0x2900+sprite);
  143. }
  144. };
  145. /*---------------------------------------------
  146. Player's bullet
  147. ---------------------------------------------*/
  148. // Forward references to functions
  149. bool killInvader(byte spriteNumber);
  150. void shootShield(byte spriteNumber, int bulletX);
  151. void shootSaucer();
  152. void shootBomb(byte spriteNumber);
  153. void incSaucerCounter();
  154. class BulletObject : public GameObject {
  155. byte timer;
  156. bool visibleDeath;
  157. void die(bool v) {
  158. visibleDeath = v;
  159. status = S_DYING;
  160. timer = 12;
  161. }
  162. public:
  163. void reset() {
  164. initialize();
  165. timer = 0;
  166. }
  167. void fire(GameObject& p) {
  168. if (status == S_WAITING){
  169. status = S_ALIVE;
  170. xpos = p.xpos;
  171. ypos = p.ypos+bulletSpeed-bulletHeight;
  172. // playerShootSound = true;
  173. }
  174. }
  175. void update() {
  176. switch (status) {
  177. case S_ALIVE: ypos -= bulletSpeed;
  178. if (ypos <= bulletTop) {
  179. ypos = bulletTop;
  180. die(true);
  181. }
  182. else {
  183. frame = CELL_BULLET;
  184. }
  185. break;
  186. case S_DYING: if (!--timer) {
  187. status = S_WAITING;
  188. incSaucerCounter();
  189. }
  190. else if (visibleDeath) {
  191. frame = CELL_BULLET_BLAST;
  192. }
  193. break;
  194. }
  195. GD.wr16(REG_TAG_X, screenLeft + xpos + 6);
  196. GD.wr16(REG_TAG_Y, screenTop + ypos);
  197. }
  198. void setY(int y) {
  199. if (status == S_DYING) {
  200. ypos = y;
  201. }
  202. }
  203. // See if the bullet hit anything
  204. void collide() {
  205. #if 0
  206. if (status == S_ALIVE) {
  207. byte b = collision();
  208. if (b != 0xff) {
  209. if ((b >= SP_FIRST_INVADER) and (b <= SP_LAST_INVADER)) {
  210. if (killInvader(b)) {
  211. die(false);
  212. }
  213. }
  214. if ((b >= SP_FIRST_SHIELD) and (b <= SP_LAST_SHIELD)) {
  215. shootShield(b,xpos);
  216. die(true);
  217. }
  218. if ((b >= SP_SAUCER1) and (b <= SP_SAUCER2)) {
  219. shootSaucer();
  220. die(false);
  221. }
  222. if ((b >= SP_BOMB1) and (b <= SP_BOMB3)) {
  223. shootBomb(b);
  224. die(false);
  225. }
  226. }
  227. }
  228. #endif
  229. }
  230. } bullet;
  231. /*---------------------------------------------
  232. The player
  233. ---------------------------------------------*/
  234. class Player : public GameObject {
  235. byte timer;
  236. int fake;
  237. public:
  238. void reset() {
  239. timer = 2*numInvaders;
  240. initialize(S_WAITING,playerMinLeft,playerYpos);
  241. frame = CELL_PLAYER;
  242. }
  243. void update() {
  244. int frame = 3;
  245. switch (status) {
  246. case S_WAITING: xpos = playerMinLeft;
  247. ypos = playerYpos;
  248. if (!--timer) {
  249. status = S_ALIVE;
  250. }
  251. break;
  252. case S_ALIVE: /* if (joystick.left()) {
  253. xpos -= playerSpeed;
  254. if (xpos < playerMinLeft) {
  255. xpos = playerMinLeft;
  256. }
  257. }
  258. if (joystick.right()) {
  259. xpos += playerSpeed;
  260. if (xpos > playerMaxRight) {
  261. xpos = playerMaxRight;
  262. }
  263. }
  264. { byte n = Joystick::buttonA|Joystick::buttonB;
  265. if (joystick.isPressed(n) and joystick.changed(n)) {
  266. bullet.fire(*this);
  267. }
  268. }
  269. */
  270. {
  271. byte control_left = 0, control_right = 0, control_fire = 0;
  272. fake = (fake + 1) & 511;
  273. control_right = fake < 256;
  274. control_left = 256 <= fake;
  275. control_fire = (GD.random(70)) == 0;
  276. if (control_left) {
  277. xpos -= playerSpeed;
  278. if (xpos < playerMinLeft) {
  279. xpos = playerMinLeft;
  280. }
  281. }
  282. if (control_right) {
  283. xpos += playerSpeed;
  284. if (xpos > playerMaxRight) {
  285. xpos = playerMaxRight;
  286. }
  287. }
  288. if (control_fire) {
  289. bullet.fire(*this);
  290. }
  291. }
  292. frame = CELL_PLAYER;
  293. break;
  294. case S_DYING: if (!--timer) {
  295. timer = 3 * remainingInvaders;
  296. status = (--numLives > 0) ? S_WAITING : S_DEAD;
  297. }
  298. else {
  299. frame = CELL_PLAYER + ((frameCounter & 4) == 0) ? 1 : 2;
  300. }
  301. break;
  302. }
  303. }
  304. void kill() {
  305. if (status == S_ALIVE) {
  306. status = S_DYING;
  307. timer = 50;
  308. // playerDeathSound = true;
  309. }
  310. }
  311. bool isAlive() {
  312. return (status==S_ALIVE);
  313. }
  314. bool isDying() {
  315. return (status==S_DYING);
  316. }
  317. bool isDead() {
  318. return (status==S_DEAD);
  319. }
  320. void wakeUp() {
  321. }
  322. } player;
  323. /*---------------------------------------------
  324. "Shields" for the player to hide behind
  325. ---------------------------------------------*/
  326. static const PROGMEM uint8_t bomb_blast[] = {
  327. 0x08, 0x22, 0x0d, 0x1e, 0x2e, 0x1f, 0x2e, 0x15
  328. };
  329. static const PROGMEM uint8_t bullet_blast[] = {
  330. 0x89, 0x22, 0x7e, 0xff, 0xff, 0x7e, 0x24, 0x91
  331. };
  332. class Shields {
  333. struct BlastInfo {
  334. byte sprite;
  335. int xpos;
  336. void reset() {
  337. sprite = 255;
  338. }
  339. bool hasBlast() const {
  340. return (sprite!=255);
  341. }
  342. void blast(byte s, int x) {
  343. sprite = s;
  344. xpos = x;
  345. }
  346. };
  347. BlastInfo bulletBlast, bombBlast[3];
  348. void blastShield(BlastInfo& n, bool asBullet) {
  349. if (n.hasBlast()) {
  350. /*
  351. byte s = (n.sprite-SP_FIRST_SHIELD)>>1;
  352. int8_t x = int8_t(n.xpos-(shieldXpos+(s*shieldXstep)));
  353. int8_t y = zapShield(s,x,asBullet);
  354. if (asBullet) {
  355. bullet.setY(shieldYpos+y);
  356. }
  357. */
  358. n.reset();
  359. }
  360. }
  361. public:
  362. void reset() {
  363. // remakeShields();
  364. int x = shieldXpos;
  365. for (int i=0; i<numShields; ++i) {
  366. x += shieldXstep;
  367. }
  368. bulletBlast.reset();
  369. for (int8_t i=0; i<3; ++i) {
  370. bombBlast[i].reset();
  371. }
  372. }
  373. void update() {
  374. blastShield(bulletBlast,true);
  375. for (int8_t i=0; i<3; ++i) {
  376. blastShield(bombBlast[i],false);
  377. }
  378. }
  379. // Zap them in various ways
  380. // nb. We defer the action because updating the sprites
  381. // might be slow and we want to make sure all collision
  382. // detection happens in the vertical blank
  383. void shoot(byte s, int x) {
  384. bulletBlast.blast(s,x);
  385. }
  386. void bomb(byte s, int x) {
  387. for (int8_t i=0; i<3; ++i) {
  388. BlastInfo& b = bombBlast[i];
  389. if (!b.hasBlast()) {
  390. b.blast(s,x);
  391. break;
  392. }
  393. }
  394. }
  395. void draw() {
  396. GD.Tag(1);
  397. GD.Vertex2ii(screenLeft, shieldYpos+screenTop, 4, 0);
  398. }
  399. byte testpixel(int x, int y) {
  400. int s = SHIELDS + (x >> 3) + (y * (SHIELDS_WIDTH / 8));
  401. byte mask = 0x80 >> (x & 7);
  402. return GD.rd(s) & mask;
  403. }
  404. void clearpixel(int x, int y) {
  405. int s = SHIELDS + (x >> 3) + (y * (SHIELDS_WIDTH / 8));
  406. byte mask = 0x80 >> (x & 7);
  407. return GD.wr(s, GD.rd(s) & ~mask);
  408. }
  409. int zap(int x, int y, bool withBullet) {
  410. y -= shieldYpos;
  411. if ((y < 0) || (SHIELDS_HEIGHT <= y))
  412. return 0;
  413. x += 6; // The pixels in the bullet are in column 6 of the graphic
  414. int hy = 99; // collision y coordinate
  415. const PROGMEM uint8_t* blastMap;
  416. if (withBullet) {
  417. // Bullet, so want to find a set pixel below (x,y)
  418. for (int r=SHIELDS_HEIGHT-1; r >= y; --r) {
  419. if (testpixel(x, r)) {
  420. hy = r;
  421. break;
  422. }
  423. }
  424. blastMap = bullet_blast;
  425. } else {
  426. // Bomb, so want to find a set pixel above (x,y)
  427. // XXX - Bombs are wider...we check three columns
  428. for (int r = 0; r < y; ++r) {
  429. if (testpixel(x, r)) {
  430. hy = r - 2;
  431. break;
  432. }
  433. }
  434. blastMap = bomb_blast;
  435. }
  436. if (hy != 99) {
  437. x -= 3;
  438. // Blast a hole in it
  439. for (int j=0; j<8; ++j) { // 8 lines tall
  440. const int py = hy+j;
  441. if ((0 <= py) && (py < SHIELDS_HEIGHT)) {
  442. byte blastMask = 0x80;
  443. byte blastGraphic = pgm_read_word_near(blastMap);
  444. for (int i=0; i<8; ++i) { // 8 pixels wide...
  445. if ((blastGraphic & blastMask) != 0) {
  446. // Set shield pixel to 0 where there's a 1 in the source graphic
  447. clearpixel(x + i, py);
  448. }
  449. blastMask >>= 1;
  450. }
  451. }
  452. ++blastMap;
  453. }
  454. }
  455. return hy != 99;
  456. }
  457. } shields;
  458. void shootShield(byte sprite, int bulletX)
  459. {
  460. shields.shoot(sprite,bulletX);
  461. }
  462. /*---------------------------------------------
  463. Flying saucer
  464. The score for the saucer depends on how
  465. many bullets you've fired. If you want
  466. a good score hit it with bullet 22 and
  467. every 15th bullet after that.
  468. The direction of the saucer also depends
  469. on the bullet count. If you're counting
  470. bullets then note the the saucer will
  471. appear on alternate sides and you can
  472. be ready for it.
  473. Repeat after me: There are NO random
  474. numbers in Space Invaders.
  475. ---------------------------------------------*/
  476. static const PROGMEM uint8_t saucerScores[15] = {
  477. // nb. There's only one '300' here...
  478. 10,5,10,15,10,10,5,30,10,10,10,5,15,10,5
  479. };
  480. class Saucer : public GameObject {
  481. byte timer, scoreTimer;
  482. byte score;
  483. byte bulletCounter;
  484. unsigned int timeUntilNextSaucer;
  485. bool leftRight,goingRight,showingScore;
  486. void startWaiting() {
  487. status = S_WAITING;
  488. timeUntilNextSaucer = saucerFrequency;
  489. }
  490. public:
  491. void reset() {
  492. initialize();
  493. timer = 1;
  494. ypos = saucerYpos;
  495. showingScore = false;
  496. bulletCounter = 0;
  497. leftRight = true;
  498. timeUntilNextSaucer = saucerFrequency;
  499. handle = 1;
  500. }
  501. void update() {
  502. int8_t xoff=0;
  503. byte gr1=GR_SAUCER, gr2=gr1;
  504. byte fr1=3, fr2=fr1; // Blank sprite
  505. switch (status) {
  506. case S_WAITING: if ((remainingInvaders>7) and !--timeUntilNextSaucer) {
  507. status = S_ALIVE;
  508. timer = saucerSkip;
  509. goingRight = leftRight;
  510. if (goingRight) {
  511. xpos = saucerXmin-saucerSpeed;
  512. }
  513. else {
  514. xpos = saucerXmax+saucerSpeed;
  515. }
  516. // saucerSound = true;
  517. }
  518. else {
  519. // stopSaucerSnd = true;
  520. }
  521. break;
  522. case S_ALIVE: if (!--timer) {
  523. // The player has to go faster then the saucer so we skip frames...
  524. timer = saucerSkip;
  525. }
  526. else {
  527. if (goingRight) {
  528. xpos += saucerSpeed;
  529. if (xpos > saucerXmax) {
  530. startWaiting();
  531. }
  532. }
  533. else {
  534. xpos -= saucerSpeed;
  535. if (xpos < saucerXmin) {
  536. startWaiting();
  537. }
  538. }
  539. }
  540. frame = 0; // Normal saucer
  541. break;
  542. case S_DYING: if (!--timer) {
  543. if (showingScore) {
  544. startWaiting();
  545. }
  546. else {
  547. timer = 60;
  548. showingScore = true;
  549. playerScore += score*10;
  550. }
  551. }
  552. else {
  553. if (showingScore) {
  554. xoff = -5;
  555. gr1 = GR_SAUCER_SCORE;
  556. gr2 = GR_BULLET; fr2 = 2;
  557. if (score == 5) { fr1=0; xoff-=4;}
  558. else if (score == 10) { fr1 = 1; }
  559. else if (score == 15) { fr1 = 2; }
  560. else if (score == 30) { fr1 = 3; }
  561. }
  562. else {
  563. fr1 = 1; // Explosion left
  564. fr2 = 2; // Explosion right
  565. xoff = -5; // Move it a bit to the left
  566. }
  567. }
  568. frame = 1; // Dying saucer
  569. break;
  570. }
  571. }
  572. void incCounter() {
  573. if (++bulletCounter == 15) {
  574. bulletCounter = 0;
  575. }
  576. leftRight = !leftRight;
  577. }
  578. void kill() {
  579. status = S_DYING;
  580. timer = 36;
  581. // saucerDieSound = true;
  582. showingScore = false;
  583. score = pgm_read_byte(saucerScores+bulletCounter);
  584. }
  585. } saucer;
  586. void incSaucerCounter()
  587. {
  588. saucer.incCounter();
  589. }
  590. void shootSaucer()
  591. {
  592. saucer.kill();
  593. }
  594. /*---------------------------------------------
  595. A space invader...
  596. ---------------------------------------------*/
  597. enum invader_type {
  598. INVADER_T, // Top-row invader
  599. INVADER_M, // Middle-row invader
  600. INVADER_B, // Bottom-row invader
  601. NUM_INVADER_TYPES
  602. };
  603. static const PROGMEM uint8_t invaderGraphic[NUM_INVADER_TYPES] = {
  604. CELL_INVADERS + 0, CELL_INVADERS + 2, CELL_INVADERS + 4
  605. };
  606. static const PROGMEM uint8_t invaderScore[NUM_INVADER_TYPES] = {
  607. 30, 20, 10
  608. };
  609. class Invader : public GameObject {
  610. // Bitmasks for my vars
  611. enum var_bits {
  612. TYPEMASK = 0x0003, // Type of invader, 0=top row, 1=middle row, 2=bottom row
  613. ANIM = 0x0010, // Flip-flop for animation frame
  614. GO_RIGHT = 0x0020, // Horizontal direction
  615. GO_DOWN = 0x0040, // If I should go downwards next time
  616. };
  617. byte vars; // All my vars, packed together
  618. byte readTable(const PROGMEM uint8_t *t) {
  619. return pgm_read_byte_near(t + (vars&TYPEMASK));
  620. }
  621. void updateTheSprite() {
  622. byte img = readTable(invaderGraphic);
  623. switch (status) {
  624. case S_ALIVE: frame = img + ((vars&ANIM)? 0:1); // Two frame animation
  625. break;
  626. case S_DYING: frame = CELL_INVADER_EXPLOSION; // Explosion graphic
  627. break;
  628. }
  629. }
  630. public:
  631. bool isAlive() const {
  632. return ((status==S_WAITING) or (status==S_ALIVE));
  633. }
  634. void goDown() {
  635. vars |= GO_DOWN;
  636. }
  637. // Put me on screen at (x,y), set my type and sprite number.
  638. // I will be invisible and appear next frame (ie. when you call "update()")
  639. void reset(int x, int y, invader_type t) {
  640. initialize(S_WAITING,x,y);
  641. frame = CELL_INVADERS + (2 * t);
  642. vars = t|GO_RIGHT;
  643. updateTheSprite();
  644. }
  645. // Update me, return "true" if I reach the edge of the screen
  646. bool update() {
  647. bool hitTheEdge = false;
  648. switch (status) {
  649. case S_WAITING: status = S_ALIVE;
  650. break;
  651. case S_ALIVE: if (vars&GO_DOWN) {
  652. ypos += invaderYstep;
  653. vars &= ~GO_DOWN;
  654. vars ^= GO_RIGHT;
  655. }
  656. else {
  657. if (vars&GO_RIGHT) {
  658. xpos += invaderXstep;
  659. hitTheEdge = (xpos >= invaderXmax);
  660. }
  661. else {
  662. xpos -= invaderXstep;
  663. hitTheEdge = (xpos <= invaderXmin);
  664. }
  665. }
  666. vars = vars^ANIM; // Animation flipflop
  667. break;
  668. }
  669. updateTheSprite();
  670. return hitTheEdge;
  671. }
  672. bool die() {
  673. bool result = (status==S_ALIVE);
  674. if (result) {
  675. status = S_DYING;
  676. updateTheSprite();
  677. playerScore += readTable(invaderScore);
  678. // alienDeathSound = true;
  679. }
  680. return result;
  681. }
  682. void kill() {
  683. status = S_DEAD;
  684. updateTheSprite();
  685. --remainingInvaders;
  686. }
  687. };
  688. /*---------------------------------------------
  689. The array of invaders
  690. ---------------------------------------------*/
  691. // Table for starting height of invaders on each level
  692. static const PROGMEM int8_t invaderHeightTable[] = {
  693. 1,2,3,3,3,4,4,4
  694. };
  695. class InvaderList {
  696. byte nextInvader; // The invader to update on the next frame
  697. int8_t dyingInvader; // Which invader is currently dying
  698. int8_t deathTimer; // COuntdown during death phase
  699. bool anInvaderHitTheEdge; // When "true" the invaders should go down a line and change direction
  700. bool anInvaderReachedTheBottom;// When "true" an invader has landed... Game Over!
  701. Invader invader[numInvaders]; // The invaders
  702. bool findNextLivingInvader() {
  703. // Find next living invader in the array
  704. bool foundOne = false;
  705. for (int8_t i=0; i<numInvaders; ++i) {
  706. if (++nextInvader == numInvaders) {
  707. // Actions taken after all the invaders have moved
  708. nextInvader = 0;
  709. if (anInvaderHitTheEdge) {
  710. for (int8_t j=0; j<numInvaders; ++j) {
  711. invader[j].goDown();
  712. }
  713. anInvaderHitTheEdge = false;
  714. }
  715. }
  716. if (invader[nextInvader].isAlive()) {
  717. foundOne = true;
  718. break;
  719. }
  720. }
  721. return foundOne;
  722. }
  723. public:
  724. void reset(int8_t level) {
  725. int y = invaderAppearY+(invaderRows*invaderYspacing);
  726. if (invaderWave > 0) {
  727. int8_t w = pgm_read_byte(invaderHeightTable+((invaderWave-1)&7));
  728. y += w*invaderYstep;
  729. }
  730. for (int8_t row=0; row<invaderRows; ++row) {
  731. int x = invaderAppearX;
  732. for (int8_t col=0; col<invadersPerRow; ++col) {
  733. const int8_t index = (row*invadersPerRow)+col;
  734. Invader& n = invader[index];
  735. invader_type t = INVADER_B;
  736. if (row > 1) { t = INVADER_M; }
  737. if (row > 3) { t = INVADER_T; }
  738. n.reset(x, y, t);
  739. x += invaderXspacing;
  740. }
  741. y -= invaderYspacing;
  742. }
  743. remainingInvaders = numInvaders;
  744. nextInvader = 0; // Start updating them here...
  745. dyingInvader = -1;
  746. deathTimer = 0;
  747. anInvaderHitTheEdge = false;
  748. anInvaderReachedTheBottom = false;
  749. }
  750. void update() {
  751. if (dyingInvader != -1) {
  752. // We stop marching when an invader dies
  753. if (!--deathTimer) {
  754. invader[dyingInvader].kill();
  755. dyingInvader = -1;
  756. }
  757. }
  758. else if (!player.isDying() and (remainingInvaders>0)) {
  759. // Update an invader
  760. Invader& n = invader[nextInvader];
  761. if (n.isAlive()) {
  762. // Move the invader
  763. if (n.update()) {
  764. anInvaderHitTheEdge = true;
  765. }
  766. if ((n.ypos+8) > player.ypos) {
  767. anInvaderReachedTheBottom = true;
  768. }
  769. }
  770. findNextLivingInvader();
  771. }
  772. }
  773. // Kill invader 'n'
  774. bool kill(byte n) {
  775. bool result = invader[n].die();
  776. if (result) {
  777. if (dyingInvader != -1) {
  778. invader[dyingInvader].kill();
  779. }
  780. dyingInvader = n;
  781. deathTimer = 16;
  782. }
  783. return result;
  784. }
  785. int8_t nearestColumnToPlayer() {
  786. Invader& n = invader[nextInvader]; // We know this invader is alive so use it as a reference
  787. int r = nextInvader%invadersPerRow; // The column this invader is in
  788. int left = n.xpos-(r*invaderXspacing);
  789. int c = (((player.xpos-left)+(invaderXspacing/2))/invaderXspacing);
  790. if ((c>=0) and (c<invadersPerRow)) {
  791. return c;
  792. }
  793. return -1;
  794. }
  795. const Invader *getColumn(int8_t c) {
  796. while ((c>=0) and (c<numInvaders)) {
  797. const Invader *v = invader+c;
  798. if (v->isAlive()) {
  799. return v;
  800. }
  801. c += invadersPerRow;
  802. }
  803. return 0;
  804. }
  805. bool haveLanded() {
  806. return anInvaderReachedTheBottom;
  807. }
  808. void draw() {
  809. for (int i = 0; i < numInvaders; i++) {
  810. GD.Tag(100 + i);
  811. invader[i].draw();
  812. }
  813. }
  814. void hit(int tag) {
  815. int c = tag - 100;
  816. if ((0 <= c) && (c < numInvaders)) {
  817. kill(c);
  818. bullet.reset();
  819. }
  820. }
  821. } invaders;
  822. bool killInvader(byte n)
  823. {
  824. return invaders.kill(n);
  825. }
  826. /*---------------------------------------------------------
  827. Space invader bombs
  828. There's three bombs in Space Invaders. Two of them
  829. follow a pattern of columns, the other one always
  830. appears right above the player (to stop you getting
  831. bored...!)
  832. Mantra: There are NO random numbers in Space Invaders...
  833. nb. Column 1 is the most dangerous and column 5
  834. isn't in either table... :-)
  835. ---------------------------------------------------------*/
  836. // Column table for the 'zigzag' bomb
  837. static const PROGMEM int8_t zigzagBombColumns[] = {
  838. 11,1,6,3,1,1,11,9,2,8,2,11,4,7,10,-1
  839. };
  840. // Column table for the bomb with horizontal bars across it
  841. static const PROGMEM int8_t barBombColumns[] = {
  842. 1,7,1,1,1,4,11,1,6,3,1,1,11,9,2,8,-1
  843. };
  844. byte bombTimer; // Countdown until next bomb can be dropped
  845. void resetBombTimer()
  846. {
  847. if (!player.isAlive()) {
  848. bombTimer = 60; // We don't drop for this long after you reanimate
  849. }
  850. else {
  851. // You get more bombs as the game progresses :-)
  852. if (playerScore < 200) { bombTimer = 48; }
  853. else if (playerScore < 1000) { bombTimer = 16; }
  854. else if (playerScore < 2000) { bombTimer = 11; }
  855. else if (playerScore < 3000) { bombTimer = 8; }
  856. else { bombTimer = 7; }
  857. }
  858. }
  859. class Bomb : public GameObject {
  860. byte graphic;
  861. byte timer;
  862. byte cycle;
  863. const PROGMEM int8_t *columnTable, *tablePtr;
  864. bool readyToDrop() {
  865. return (bombTimer==0);
  866. }
  867. int8_t getNextColumn() {
  868. int8_t c = pgm_read_byte(tablePtr);
  869. if (c == -1) {
  870. tablePtr = columnTable;
  871. c = pgm_read_byte(tablePtr);
  872. }
  873. else {
  874. ++tablePtr;
  875. }
  876. return c-1;
  877. }
  878. public:
  879. Bomb() {
  880. tablePtr = 0;
  881. }
  882. bool isAlive() {
  883. return (status!=S_WAITING);
  884. }
  885. void die() {
  886. status = S_DYING;
  887. timer = 12;
  888. }
  889. void reset(byte gr, const int8_t *ct) {
  890. initialize();
  891. graphic = gr;
  892. columnTable = ct;
  893. if (!tablePtr) {
  894. tablePtr = ct; // Only set this the first time...
  895. }
  896. cycle = timer = 0;
  897. }
  898. void update() {
  899. switch (status) {
  900. case S_WAITING: if (bombTimer == 0) {
  901. int8_t c = -1;
  902. if (columnTable) {
  903. // Follow sequence of columns
  904. c = getNextColumn();
  905. }
  906. else {
  907. // Drop me above the player
  908. c = invaders.nearestColumnToPlayer();
  909. }
  910. const Invader *v = invaders.getColumn(c);
  911. if (v) {
  912. status = S_ALIVE;
  913. xpos = v->xpos;
  914. ypos = v->ypos+8;
  915. resetBombTimer();
  916. }
  917. }
  918. break;
  919. case S_ALIVE: ypos += bombSpeed;
  920. if (ypos < bombYmax) {
  921. if (shields.zap(xpos, ypos, false))
  922. die();
  923. } else {
  924. ypos = bombYmax;
  925. die();
  926. }
  927. if (++timer==2) {
  928. ++cycle;
  929. timer = 0;
  930. }
  931. frame = graphic + (cycle & 3);
  932. break;
  933. case S_DYING: if (!--timer) {
  934. status = S_WAITING;
  935. }
  936. else {
  937. frame = CELL_BOMB_BLAST;
  938. }
  939. break;
  940. }
  941. }
  942. void collide() {
  943. if (status==S_ALIVE) {
  944. byte b = collision();
  945. /*
  946. if (b == SP_PLAYER) {
  947. player.kill();
  948. status = S_DYING;
  949. }
  950. if ((b>=SP_FIRST_SHIELD) and (b<=SP_LAST_SHIELD)) {
  951. shields.bomb(b,xpos);
  952. die();
  953. }
  954. */
  955. }
  956. }
  957. };
  958. class Bombs {
  959. Bomb zigzag,bar,diag;
  960. public:
  961. void reset() {
  962. resetBombTimer();
  963. zigzag.reset(CELL_ZIGZAGBOMB, zigzagBombColumns);
  964. bar .reset(CELL_BARBOMB, barBombColumns);
  965. diag .reset(CELL_DIAGBOMB, 0);
  966. }
  967. void update() {
  968. if (player.isAlive()) {
  969. if (bombTimer > 0) {
  970. --bombTimer;
  971. }
  972. zigzag.update();
  973. bar .update();
  974. diag .update();
  975. }
  976. }
  977. void collide() {
  978. zigzag.collide();
  979. bar .collide();
  980. diag .collide();
  981. }
  982. void draw() {
  983. zigzag.draw();
  984. bar .draw();
  985. diag .draw();
  986. }
  987. void shoot(byte s) {
  988. if (zigzag.sprite==s) zigzag.die();
  989. if (bar.sprite ==s) bar.die();
  990. if (diag.sprite ==s) diag.die();
  991. }
  992. } bombs;
  993. void shootBomb(byte s)
  994. {
  995. bombs.shoot(s);
  996. }
  997. /*---------------------------------------------
  998. Start next wave of invaders
  999. ---------------------------------------------*/
  1000. void startNextWave()
  1001. {
  1002. beatCounter = 0;
  1003. player.reset();
  1004. bullet.reset();
  1005. saucer.reset();
  1006. bombs.reset();
  1007. shields.reset();
  1008. invaders.reset(invaderWave);
  1009. if (++invaderWave == 0) {
  1010. invaderWave = 1;
  1011. }
  1012. }
  1013. /*---------------------------------------------
  1014. Reset the game
  1015. ---------------------------------------------*/
  1016. void resetGame()
  1017. {
  1018. numLives = 3;
  1019. playerScore = 0;
  1020. invaderWave = 0;
  1021. startNextWave();
  1022. // GD.fill((64*((screenTop+239)>>3))+(screenLeft>>3),CH_FLOOR,screenWidth>>3);
  1023. }
  1024. uint32_t clock() {
  1025. return micros();
  1026. return GD.rd32(REG_CLOCK);
  1027. }
  1028. void loop()
  1029. {
  1030. ++frameCounter;
  1031. GD.get_inputs();
  1032. int x, y, z;
  1033. GD.get_accel(x, y, z);
  1034. byte tag = GD.rd(REG_TAG);
  1035. // tag = GD.rd(REG_TOUCH_TAG);
  1036. long t0 = clock();
  1037. GD.Clear();
  1038. GD.Begin(BITMAPS);
  1039. GD.ColorMask(1, 1, 1, 0);
  1040. GD.ColorRGB(0x686868);
  1041. GD.Vertex2ii(screenLeft - (248 - screenWidth) / 2, 0, 2, 0);
  1042. GD.ColorMask(0, 0, 0, 1);
  1043. GD.BlendFunc(ONE, ONE);
  1044. GD.AlphaFunc(GREATER, 0);
  1045. // Collision detection first (we have to do it all during vertical blanking!)
  1046. bullet.collide();
  1047. bombs.collide();
  1048. // The rest of the game logic
  1049. // joystick.read();
  1050. player.update();
  1051. bullet.update();
  1052. saucer.update();
  1053. bombs.update();
  1054. shields.update();
  1055. invaders.update();
  1056. if (!remainingInvaders) {
  1057. startNextWave();
  1058. }
  1059. if (player.isDying()) {
  1060. bombs.reset();
  1061. bullet.reset();
  1062. }
  1063. if (player.isDead()) {
  1064. resetGame();
  1065. }
  1066. if (invaders.haveLanded()) {
  1067. numLives = 1;
  1068. player.kill();
  1069. }
  1070. saucer.draw();
  1071. invaders.draw();
  1072. invaders.hit(tag);
  1073. bombs.draw();
  1074. shields.draw();
  1075. GD.TagMask(0);
  1076. player.draw();
  1077. bullet.draw();
  1078. updateScore();
  1079. drawBases();
  1080. GD.ColorRGB(0xc0c0c0);
  1081. GD.ColorMask(1, 1, 1, 0);
  1082. GD.BlendFunc(DST_ALPHA, ONE);
  1083. GD.cmd_loadidentity();
  1084. GD.cmd_scale(F16(8), F16(8));
  1085. GD.cmd_setmatrix();
  1086. GD.Vertex2ii(screenLeft, screenTop, 3, 0);
  1087. if (--beatCounter < 0) {
  1088. // alienBeatSound = true;
  1089. beatCounter = remainingInvaders+4;
  1090. }
  1091. static long tprev;
  1092. GD.RestoreContext();
  1093. // GD.cmd_number(60, 0, 26, OPT_RIGHTX, clock() - t0);
  1094. // GD.cmd_number(60, 0, 26, OPT_RIGHTX, tag);
  1095. tprev = t0;
  1096. GD.swap();
  1097. }
  1098. void setup()
  1099. {
  1100. Serial.begin(UART_SPEED);
  1101. GD.begin();
  1102. GD.cmd_inflate(0);
  1103. GD.copy(invaders_assets, sizeof(invaders_assets));
  1104. GD.BitmapHandle(0);
  1105. GD.BitmapSource(SPR16);
  1106. GD.BitmapSize(NEAREST, BORDER, BORDER, 16, 8);
  1107. GD.BitmapLayout(L1, 2, 8);
  1108. GD.BitmapHandle(1);
  1109. GD.BitmapSource(SAUCER);
  1110. GD.BitmapSize(NEAREST, BORDER, BORDER, 16, SAUCER_HEIGHT);
  1111. GD.BitmapLayout(L1, SAUCER_WIDTH / 8, 8);
  1112. GD.BitmapHandle(2);
  1113. GD.cmd_loadimage(ASSETS_END, 0);
  1114. GD.copy(background_jpg, sizeof(background_jpg));
  1115. GD.BitmapHandle(3);
  1116. GD.BitmapSource(OVERLAY);
  1117. GD.BitmapSize(NEAREST, BORDER, BORDER, 8 * OVERLAY_WIDTH, 8 * OVERLAY_HEIGHT);
  1118. GD.BitmapLayout(RGB332, OVERLAY_WIDTH, OVERLAY_HEIGHT);
  1119. GD.BitmapHandle(4);
  1120. GD.BitmapSource(SHIELDS);
  1121. GD.BitmapSize(NEAREST, BORDER, BORDER, SHIELDS_WIDTH, SHIELDS_HEIGHT);
  1122. GD.BitmapLayout(L1, SHIELDS_WIDTH / 8, SHIELDS_HEIGHT);
  1123. resetGame();
  1124. highScore = 0;
  1125. }