manicminer.ino 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961
  1. #include <SPI.h>
  2. #include <GD.h>
  3. #define CHEAT_INVINCIBLE 0 // means Willy unaffected by nasties
  4. #define START_LEVEL 0 // level to start on, 0-18
  5. #define CHEAT_OPEN_PORTAL 0 // Portal always open
  6. // Game has three controls: LEFT, RIGHT, JUMP. You can map them
  7. // to any pins by changing the definitions of PIN_L, PIN_R, PIN_J
  8. // below.
  9. static int ss; // JCB
  10. #if 1 // SPARKFUN_JOYSTICK
  11. #define PIN_L 6
  12. #define PIN_R 3
  13. #define PIN_J 4
  14. #else
  15. #define PIN_L A2
  16. #define PIN_R A3
  17. #define PIN_J A5
  18. #endif
  19. #define CONTROL_LEFT 1
  20. #define CONTROL_RIGHT 2
  21. #define CONTROL_JUMP 4
  22. static byte setup_control()
  23. {
  24. pinMode(PIN_L, INPUT);
  25. digitalWrite(PIN_L, HIGH);
  26. pinMode(PIN_R, INPUT);
  27. digitalWrite(PIN_R, HIGH);
  28. pinMode(PIN_J, INPUT);
  29. digitalWrite(PIN_J, HIGH);
  30. }
  31. static byte control()
  32. {
  33. byte r = 0;
  34. if (!digitalRead(PIN_J))
  35. r |= CONTROL_JUMP;
  36. if (!digitalRead(PIN_L))
  37. r |= CONTROL_LEFT;
  38. if (!digitalRead(PIN_R))
  39. r |= CONTROL_RIGHT;
  40. return r;
  41. }
  42. #define BLACK RGB(0,0,0)
  43. #define RED RGB(255,0,0)
  44. #define YELLOW RGB(255,255,0)
  45. #define CYAN RGB(0,255,255)
  46. #define GREEN RGB(0,255,0)
  47. #define MAGENTA RGB(255,0,255)
  48. #define WHITE RGB(255,255,255)
  49. #define CHR_BORDER 31 // hw char used for all of the border
  50. #define CHR_AIR 128 // AIR line
  51. // assigned sprite images and sprite numbers
  52. #define IMG_ITEM 0 // items 0-4
  53. #define IMG_PORTAL 5 // room exit
  54. #define IMG_SWITCH1 6 // the switches for the Kong Beast levels
  55. #define IMG_SWITCH2 7
  56. #define IMG_GUARD 8 // All guardians, 8 max
  57. #define IMG_NASTY1 16 // Nasty blocks
  58. #define IMG_NASTY2 17
  59. #define IMG_WILLYC 56
  60. #define IMG_WILLY 63
  61. #define ELEM_AIR 0
  62. #define ELEM_FLOOR 1
  63. #define ELEM_CRUMBLE 2
  64. #define ELEM_WALL 3
  65. #define ELEM_CONVEYOR 4
  66. #define ELEM_NASTY1 5
  67. #define ELEM_NASTY2 6
  68. #include "manicminer.h"
  69. #include "spectrum.h"
  70. #include "spectrum_data.h" // title screen
  71. #define COLOR(i) (pgm_read_word_near(specpal + (2 * (i))))
  72. #define BRIGHT(x) (((x) & 64) >> 3)
  73. #define PAPER(a) ((((a) >> 3) & 7) | BRIGHT(a))
  74. #define INK(a) (((a) & 7) | BRIGHT(a))
  75. // screen coordinate - Spectrum 256x192 screen is centered in Gameduino screen
  76. static uint16_t atxy(byte x, byte y)
  77. {
  78. return RAM_PIC + 64 * (y + 6) + (x + 9);
  79. }
  80. // unpack 8 monochrome pixels into Gameduino character RAM
  81. void unpack8(byte b)
  82. {
  83. static byte stretch[16] = {
  84. 0x00, 0x03, 0x0c, 0x0f,
  85. 0x30, 0x33, 0x3c, 0x3f,
  86. 0xc0, 0xc3, 0xcc, 0xcf,
  87. 0xf0, 0xf3, 0xfc, 0xff
  88. };
  89. SPI.transfer(stretch[b >> 4]);
  90. SPI.transfer(stretch[b & 15]);
  91. }
  92. // unpack monochrome bitmap from flash to Gameduino character RAM at dst
  93. void unpack8xn(uint16_t dst, flash_uint8_t *src, uint16_t size)
  94. {
  95. GD.__wstart(dst);
  96. while (size--)
  97. unpack8(pgm_read_byte_near(src++));
  98. GD.__end();
  99. }
  100. // Load 8x8 1-bit sprite from src into slot (0-63)
  101. // zero and one are the two colors
  102. void send8(byte b, byte zero, byte one)
  103. {
  104. for (byte j = 8; j; j--) {
  105. SPI.transfer((b & 0x80) ? one : zero);
  106. b <<= 1;
  107. }
  108. }
  109. // load an 8x8 into a hw 256-color sprite, padding with transparent (color 255)
  110. void loadspr8(byte slot, flash_uint8_t *src, byte zero, byte one)
  111. {
  112. GD.__wstart(RAM_SPRIMG + (slot << 8));
  113. for (byte i = 8; i; i--) {
  114. send8(pgm_read_byte_near(src++), zero, one);
  115. send8(0, 255, 255);
  116. }
  117. for (byte i = 128; i; i--)
  118. SPI.transfer(255);
  119. GD.__end();
  120. }
  121. // load an 16x16 into a hw 256-color sprite
  122. void loadspr16(byte slot, flash_uint8_t *src, byte zero, byte one)
  123. {
  124. GD.__wstart(RAM_SPRIMG + (slot << 8));
  125. for (byte i = 32; i; i--)
  126. send8(pgm_read_byte_near(src++), zero, one);
  127. GD.__end();
  128. }
  129. static void sprite(byte spr, byte x, byte y, byte img, byte rot = 0)
  130. {
  131. GD.sprite(spr, x + 9*8, y + 6*8, img, 0, rot);
  132. }
  133. static void hide(byte spr)
  134. {
  135. GD.sprite(spr, 400, 400, 0, 0, 0);
  136. }
  137. struct guardian {
  138. byte a;
  139. byte x, y;
  140. signed char d;
  141. byte x0, x1;
  142. byte f;
  143. } guards[8];
  144. #define MAXRAY 40
  145. struct state_t {
  146. byte level;
  147. byte lives;
  148. uint32_t score;
  149. uint32_t hiscore;
  150. byte bidir;
  151. byte air;
  152. byte conveyordir;
  153. byte portalattr;
  154. byte nitems;
  155. byte wx, wy; // Willy x,y
  156. byte wd, wf; // Willy dir and frame
  157. byte lastdx; // Willy last x movement
  158. byte convey; // Willy caught on conveyor
  159. byte jumping;
  160. signed char wyv;
  161. byte conveyor[2];
  162. flash_uint8_t *guardian;
  163. uint16_t prevray[MAXRAY];
  164. byte switch1, switch2;
  165. } state;
  166. // All this is taken from
  167. // http://jswremakes.emuunlim.com/Mmt/Manic%20Miner%20Room%20Format.htm
  168. static void plot_air()
  169. {
  170. // Starting at CHR_AIR+4, draw a line of n/8 solid, n%8, then 27-(n/8)
  171. uint16_t addr = RAM_CHR + (16 * (CHR_AIR + 4)); // line 2 of (CHR_AIR+4)
  172. for (byte i = 0; i < 28; i++) {
  173. byte v;
  174. if (i < (state.air >> 3))
  175. v = 8;
  176. else if (i == (state.air >> 3))
  177. v = state.air & 7;
  178. else
  179. v = 0;
  180. unpack8xn(addr + i * 16, airs + (v << 3), 8);
  181. }
  182. }
  183. static void plot_score()
  184. {
  185. uint32_t n = state.score;
  186. GD.__wstart(atxy(26, 19));
  187. SPI.transfer('0' + (n / 100000) % 10);
  188. SPI.transfer('0' + (n / 10000) % 10);
  189. SPI.transfer('0' + (n / 1000) % 10);
  190. SPI.transfer('0' + (n / 100) % 10);
  191. SPI.transfer('0' + (n / 10) % 10);
  192. SPI.transfer('0' + n % 10);
  193. GD.__end();
  194. }
  195. static void bump_score(byte n)
  196. {
  197. if ((state.score < 10000) && (10000 <= (state.score + n )))
  198. state.lives++;
  199. state.score += n;
  200. plot_score();
  201. }
  202. static void pause(byte n)
  203. {
  204. while (n--)
  205. GD.waitvblank();
  206. }
  207. static void clear_screen(byte yclear = 16)
  208. {
  209. // blank out top 16 lines
  210. GD.fill(RAM_CHR, 0, 16);
  211. GD.wr16(RAM_PAL, BLACK);
  212. for (byte y = 0; y < yclear; y++)
  213. GD.fill(atxy(0, y), 0, 32);
  214. for (int i = 0; i < 256; i++)
  215. hide(i);
  216. for (int i = 0; i < 64; i++)
  217. GD.voice(i, 0, 0, 0, 0);
  218. pause(6);
  219. }
  220. static void loadlevel(const PROGMEM struct level *l)
  221. {
  222. clear_screen();
  223. // border
  224. GD.wr16(RAM_PAL + 8 * CHR_BORDER, COLOR(pgm_read_byte_near(&l->border)));
  225. // background picture: uncompress into scratch then copy to screen
  226. flash_uint8_t *background = (flash_uint8_t*)pgm_read_word_near(&l->background);
  227. uint16_t scratch = 4096 - 512; // offscreen scratch
  228. GD.uncompress(scratch, background);
  229. for (byte y = 0; y < 16; y++)
  230. for (byte x = 0; x < 32; x++)
  231. GD.wr(atxy(x, y), GD.rd(scratch + (y << 5) + x));
  232. for (byte y = 16; y < 24; y++)
  233. GD.fill(atxy(0, y), ' ', 32);
  234. // bg characters
  235. unpack8xn(RAM_CHR, l->bgchars, sizeof(l->bgchars));
  236. state.conveyor[0] = pgm_read_byte_near(&l->bgchars[8 * 4 + 0]);
  237. state.conveyor[1] = pgm_read_byte_near(&l->bgchars[8 * 4 + 3]);
  238. // bg character palettes
  239. for (byte c = 0; c < 8; c++) {
  240. byte attr = pgm_read_byte_near(l->bgattr + c);
  241. GD.wr16(RAM_PAL + 8 * c, COLOR(PAPER(attr)));
  242. GD.wr16(RAM_PAL + 8 * c + 6, COLOR(INK(attr)));
  243. }
  244. // block 2 is the crumbling floor
  245. // put the 7 anims of crumbling floor in chars 8-14
  246. GD.fill(RAM_CHR + 16 * 8, 0, 16 * 7);
  247. flash_uint8_t *src = l->bgchars + 2 * 8;
  248. for (byte i = 0; i < 7; i++) {
  249. byte ch = 8 + i;
  250. unpack8xn(RAM_CHR + 16 * ch + 2 * (i + 1), src, 7 - i);
  251. byte attr = pgm_read_byte_near(l->bgattr + 2);
  252. GD.wr16(RAM_PAL + 8 * ch, COLOR(PAPER(attr)));
  253. GD.wr16(RAM_PAL + 8 * ch + 6, COLOR(INK(attr)));
  254. }
  255. // level name and score line
  256. for (byte x = 0; x < 32; x++) {
  257. char scoreline[] = "High Score 000000 Score 000000";
  258. GD.wr(atxy(x, 16), 0x80 + pgm_read_byte_near(l->name + x));
  259. GD.wr(atxy(x, 19), scoreline[x]);
  260. }
  261. plot_score();
  262. // AIR line characters
  263. for (byte i = 0; i < 32; i++)
  264. GD.wr(atxy(i, 17), CHR_AIR + i);
  265. // the items are 5 sprites, using colors 16 up
  266. state.nitems = 0;
  267. for (byte i = 0; i < 5; i++) {
  268. byte x = pgm_read_byte_near(&l->items[i].x);
  269. byte y = pgm_read_byte_near(&l->items[i].y);
  270. if (x || y) {
  271. loadspr8(IMG_ITEM + i, l->item, 255, 16 + i); // items have color 16 up
  272. sprite(IMG_ITEM + i, x, y, IMG_ITEM + i);
  273. state.nitems++;
  274. } else
  275. hide(IMG_ITEM + i);
  276. }
  277. // the portal is a sprite, using colors 32,33
  278. state.portalattr = pgm_read_byte_near(&l->portalattr);
  279. loadspr16(IMG_PORTAL, l->portal, 32, 33);
  280. byte portalx = pgm_read_byte_near(&l->portalx);
  281. byte portaly = pgm_read_byte_near(&l->portaly);
  282. sprite(IMG_PORTAL, portalx, portaly, IMG_PORTAL);
  283. // use sprites for blocks NASTY1 and NASTY2
  284. byte nc1 = pgm_read_byte_near(l->bgattr + ELEM_NASTY1);
  285. byte nc2 = pgm_read_byte_near(l->bgattr + ELEM_NASTY2);
  286. loadspr8(IMG_NASTY1, l->bgchars + ELEM_NASTY1 * 8, 255, INK(nc1));
  287. loadspr8(IMG_NASTY2, l->bgchars + ELEM_NASTY2 * 8, 255, INK(nc2));
  288. // now paint sprites over all the NASTY blocks
  289. {
  290. byte spr = IMG_NASTY1;
  291. for (byte y = 0; y < 16; y++)
  292. for (byte x = 0; x < 32; x++) {
  293. byte blk = GD.rd(atxy(x, y));
  294. if (blk == ELEM_NASTY1)
  295. sprite(spr++, x << 3, y << 3, IMG_NASTY1);
  296. if (blk == ELEM_NASTY2)
  297. sprite(spr++, x << 3, y << 3, IMG_NASTY2);
  298. }
  299. }
  300. // air
  301. state.air = pgm_read_byte_near(&l->air);
  302. plot_air();
  303. // Conveyor direction
  304. state.conveyordir = pgm_read_byte_near(&l->conveyordir);
  305. // the hguardians
  306. state.bidir = pgm_read_byte_near(&l->bidir);
  307. state.guardian = l->guardian;
  308. guardian *pg;
  309. for (byte i = 0; i < 8; i++) {
  310. byte a = pgm_read_byte_near(&l->hguard[i].a);
  311. guards[i].a = a;
  312. if (a) {
  313. byte x = pgm_read_byte_near(&l->hguard[i].x);
  314. byte y = pgm_read_byte_near(&l->hguard[i].y);
  315. guards[i].x = x;
  316. guards[i].y = y;
  317. guards[i].d = pgm_read_byte_near(&l->hguard[i].d);
  318. guards[i].x0 = pgm_read_byte_near(&l->hguard[i].x0);
  319. guards[i].x1 = pgm_read_byte_near(&l->hguard[i].x1);
  320. guards[i].f = 0;
  321. } else {
  322. hide(6 + i);
  323. }
  324. }
  325. pg = &guards[4]; // Use slot 4 for special guardians
  326. switch (state.level) { // Special level handling
  327. case 4: // Eugene's lair
  328. pg->a = 0; // prevent normal guardian logic
  329. pg->x = 120;
  330. pg->y = 0;
  331. pg->d = -1;
  332. pg->x0 = 0;
  333. pg->x1 = 88;
  334. loadspr16(IMG_GUARD + 4, eugene, 255, 15);
  335. break;
  336. case 7: // Miner Willy meets the Kong Beast
  337. case 11: // Return of the Alien Kong Beast
  338. pg->a = 0;
  339. pg->x = 120;
  340. pg->y = 0;
  341. state.switch1 = 0;
  342. loadspr8(IMG_SWITCH1, lightswitch, 0, 6);
  343. sprite(IMG_SWITCH1, 48, 0, IMG_SWITCH1);
  344. state.switch2 = 0;
  345. loadspr8(IMG_SWITCH2, lightswitch, 0, 6);
  346. sprite(IMG_SWITCH2, 144, 0, IMG_SWITCH2);
  347. break;
  348. }
  349. for (byte i = 0; i < MAXRAY; i++)
  350. state.prevray[i] = 4095;
  351. // Willy
  352. state.wx = pgm_read_byte_near(&l->wx);
  353. state.wy = pgm_read_byte_near(&l->wy);
  354. state.wf = pgm_read_byte_near(&l->wf);
  355. state.wd = pgm_read_byte_near(&l->wd);
  356. state.jumping = 0;
  357. state.convey = 0;
  358. state.wyv = 0;
  359. }
  360. static byte rol8(byte b, byte d) // 8-bit byte rotate left
  361. {
  362. d &= 7;
  363. return ((b << d) | (b >> (8 - d))) & 0xff;
  364. }
  365. void setup()
  366. {
  367. GD.begin();
  368. setup_control();
  369. Serial.begin(1000000); // JCB
  370. }
  371. void game_graphics()
  372. {
  373. // Load the Spectrum font: regular in 32-127 reverse in 160-255
  374. unpack8xn(RAM_CHR + 16 * ' ', specfont, sizeof(specfont));
  375. unpack8xn(RAM_CHR + 16 * (128 + ' '), specfont, sizeof(specfont));
  376. for (byte i = 32; i < 128; i++) {
  377. GD.setpal(4 * i, BLACK);
  378. GD.setpal(4 * i + 3, YELLOW);
  379. GD.setpal(4 * (128 + i), COLOR(6)); // dark yellow
  380. GD.setpal(4 * (128 + i) + 3, BLACK);
  381. }
  382. // AIR display in 32 chars starting at CHR_AIR
  383. unpack8xn(RAM_CHR + 16 * CHR_AIR, specfont + 8 * ('A' - ' '), 8);
  384. unpack8xn(RAM_CHR + 16 * (CHR_AIR+1), specfont + 8 * ('I' - ' '), 8);
  385. unpack8xn(RAM_CHR + 16 * (CHR_AIR+2), specfont + 8 * ('R' - ' '), 8);
  386. for (byte i = 0; i < 32; i++) {
  387. uint16_t color = (i < 10) ? RED : GREEN;
  388. GD.setpal(4 * (CHR_AIR + i), color);
  389. GD.setpal(4 * (CHR_AIR + i) + 3, WHITE);
  390. }
  391. // Load the Spectrum's palette into PALETTE256A
  392. GD.copy(RAM_SPRPAL, specpal, sizeof(specpal));
  393. GD.wr16(RAM_SPRPAL + 2 * 255, TRANSPARENT); // color 255 is transparent
  394. // fill screen with char CHR_BORDER
  395. GD.fill(RAM_CHR + 16 * CHR_BORDER, 0, 16);
  396. GD.fill(RAM_PIC, CHR_BORDER, 64 * 64);
  397. // Load Willy in bright cyan and white
  398. for (byte i = 0; i < 4; i++) {
  399. loadspr16(IMG_WILLYC + i, willy + (i << 5), 255, 8 + 5);
  400. }
  401. }
  402. // midi frequency table
  403. static const PROGMEM uint16_t midifreq[128] = {
  404. 32,34,36,38,41,43,46,48,51,55,58,61,65,69,73,77,82,87,92,97,103,110,116,123,130,138,146,155,164,174,184,195,207,220,233,246,261,277,293,311,329,349,369,391,415,440,466,493,523,554,587,622,659,698,739,783,830,880,932,987,1046,1108,1174,1244,1318,1396,1479,1567,1661,1760,1864,1975,2093,2217,2349,2489,2637,2793,2959,3135,3322,3520,3729,3951,4186,4434,4698,4978,5274,5587,5919,6271,6644,7040,7458,7902,8372,8869,9397,9956,10548,11175,11839,12543,13289,14080,14917,15804,16744,17739,18794,19912,21096,22350,23679,25087,26579,28160,29834,31608,33488,35479,37589,39824,42192,44701,47359,50175
  405. };
  406. #define MIDI(n) pgm_read_word(midifreq + (n))
  407. static void squarewave(byte voice, uint16_t f0, byte amp)
  408. {
  409. GD.voice(8*voice + 0, 0, f0, amp, amp);
  410. GD.voice(8*voice + 1, 0, 3 * f0, amp/3, amp/3);
  411. GD.voice(8*voice + 2, 0, 5 * f0, amp/5, amp/5);
  412. GD.voice(8*voice + 3, 0, 7 * f0, amp/7, amp/7);
  413. GD.voice(8*voice + 4, 0, 9 * f0, amp/9, amp/9);
  414. GD.voice(8*voice + 5, 0, 11 * f0, amp/11, amp/11);
  415. }
  416. static void drive_level(byte t);
  417. static int title_screen() // returns 1 if player pressed something
  418. {
  419. for (;;) {
  420. display:
  421. clear_screen();
  422. GD.fill(0x4000, 0, 6144); // clear emulated Spectrum video RAM
  423. GD.uncompress(0x7000, spectrum_tables);
  424. // fill screen with char 0x80 for border
  425. GD.setpal(4 * 0x80, COLOR(2));
  426. GD.fill(RAM_CHR, 0, 4096);
  427. GD.fill(RAM_PIC, 0x80, 4096);
  428. // paint the 256x192 window as alternating lines of
  429. // chars 0-31 and 32-63
  430. for (byte y = 0; y < 24; y += 2) {
  431. for (byte x = 0; x < 32; x++) {
  432. GD.wr(atxy(x, y), x);
  433. GD.wr(atxy(x, y + 1), 32 + x);
  434. }
  435. }
  436. GD.microcode(spectrum_code, sizeof(spectrum_code));
  437. GD.uncompress(0x4000, screen_mm1);
  438. for (const PROGMEM uint32_t *tune = blue_danube;
  439. (size_t)tune < ((size_t)blue_danube + sizeof(blue_danube));
  440. tune++) {
  441. uint32_t cmd = pgm_read_dword_near(tune);
  442. for (int t = 60 * (cmd >> 28); t; t--) {
  443. delay(1);
  444. if (control())
  445. goto escape;
  446. }
  447. byte n0 = 127 & (cmd >> 21);
  448. byte a0 = 127 & (cmd >> 14);
  449. byte n1 = 127 & (cmd >> 7);
  450. byte a1 = 127 & cmd;
  451. squarewave(0, MIDI(n0), a0 >> 1);
  452. squarewave(1, MIDI(n1), a1 >> 1);
  453. GD.setpal(4 * 0x80, COLOR((n1 ^ n0) & 7));
  454. }
  455. GD.setpal(4 * 0x80, COLOR(7));
  456. // GD.screenshot(ss++); // JCB
  457. for (byte i = 0; i < ((sizeof(message) - 1) - 32); i++) {
  458. flash_uint8_t *src = message + i;
  459. for (byte j = 0; j < 32; j++) {
  460. byte ch = pgm_read_byte_near(src + j);
  461. flash_uint8_t *glyph = specfont + ((ch - ' ') << 3);
  462. for (byte k = 0; k < 8; k++)
  463. GD.wr(0x5060 + j + (k << 8), pgm_read_byte_near(glyph++));
  464. }
  465. for (byte t = 80; t; t--) {
  466. delay(1);
  467. if (control())
  468. goto escape;
  469. }
  470. }
  471. GD.wr(J1_RESET, 1); // stop coprocessor
  472. clear_screen(24);
  473. game_graphics();
  474. for (state.level = 0; state.level < 19; state.level++) {
  475. loadlevel(&levels[state.level]);
  476. for (byte t = 0; t < 128; t++) {
  477. drive_level(t);
  478. GD.waitvblank();
  479. if (control())
  480. goto display;
  481. }
  482. }
  483. }
  484. escape:
  485. GD.wr(J1_RESET, 1); // stop coprocessor
  486. clear_screen(24);
  487. game_graphics();
  488. }
  489. static void game_over()
  490. {
  491. clear_screen();
  492. // Willy
  493. loadspr16(0, willy, 255, 8 + 7);
  494. sprite(0, 124, 128 - 32, 0);
  495. // plinth
  496. loadspr16(2, plinth, 255, 8 + 7);
  497. sprite(2, 120, 128 - 16, 2);
  498. // Boot
  499. loadspr16(1, boot, 255, 8 + 7);
  500. loadspr16(3, boot, 255, 8 + 7); // sprite 3 is top 2 lines of boot
  501. // erase the bottom 14 lines of boot
  502. GD.fill(RAM_SPRIMG + 3 * 256 + 2 * 16, 255, 14 * 16);
  503. for (byte i = 0; i <= 48; i++) {
  504. sprite(1, 120, 2 * i, 1); // boot in sprite 1
  505. sprite(3 + i, 120, 2 * i, 3); // leg in sprites 3,4, etc
  506. if (41 <= i) // erase Willy as boot covers him
  507. GD.fill(RAM_SPRIMG + 32 * (i - 41), 255, 32);
  508. if ((i & 1) == 0)
  509. GD.wr16(RAM_PAL, COLOR(random(7)));
  510. squarewave(0, 400 + 4 * i, 150);
  511. pause(2);
  512. }
  513. GD.wr16(RAM_PAL, BLACK);
  514. squarewave(0, 0, 0);
  515. // "Game Over" text in sprite images 16-23, color 16-23
  516. char msg[] = "GameOver";
  517. for (byte i = 0; i < 8; i++)
  518. loadspr8(16 + i, specfont + (msg[i] - 32) * 8, 255, 16 + i);
  519. for (byte i = 0; i < 4; i++) {
  520. sprite(100 + i, 80 + i * 8, 48, 16 + i);
  521. sprite(104 + i, 140 + i * 8, 48, 20 + i);
  522. }
  523. for (byte t = 120; t; t--) {
  524. for (byte i = 0; i < 8; i++)
  525. GD.wr16(RAM_SPRPAL + (16 + i) * 2, COLOR(9 + random(7)));
  526. pause(2);
  527. }
  528. clear_screen();
  529. }
  530. // crumble block at s, which is the sequence
  531. // 2->8->9->10->11->12->13->14->0
  532. static void crumble(uint16_t s)
  533. {
  534. byte r = GD.rd(s);
  535. signed char nexts[] = {
  536. -1, -1, 8, -1, -1, -1, -1, -1,
  537. 9, 10, 11, 12, 13, 14, 0 };
  538. if (r < sizeof(nexts) && nexts[r] != -1)
  539. GD.wr(s, nexts[r]);
  540. }
  541. // is it legal for willy to be at (x,y)
  542. static int canbe(byte x, byte y)
  543. {
  544. uint16_t addr = atxy(x / 8, y / 8);
  545. return
  546. (GD.rd(addr) != ELEM_WALL) &&
  547. (GD.rd(addr+1) != ELEM_WALL) &&
  548. (GD.rd(addr+64) != ELEM_WALL) &&
  549. (GD.rd(addr+65) != ELEM_WALL);
  550. }
  551. // is Willy standing in a solar ray?
  552. static int inray(byte x, byte y)
  553. {
  554. uint16_t addr = atxy(x / 8, y / 8);
  555. return
  556. (GD.rd(addr) > 0x80 ) ||
  557. (GD.rd(addr+1) > 0x80) ||
  558. (GD.rd(addr+64) > 0x80) ||
  559. (GD.rd(addr+65) > 0x80);
  560. }
  561. static void drive_level(byte t)
  562. {
  563. uint16_t freq = 133955L / pgm_read_byte_near(music1 + ((t >> 1) & 63));
  564. GD.waitvblank();
  565. squarewave(0, freq, 128);
  566. GD.waitvblank();
  567. squarewave(0, freq, 0);
  568. GD.waitvblank();
  569. GD.waitvblank();
  570. guardian *pg;
  571. for (byte i = 0; i < 8; i++) {
  572. pg = &guards[i];
  573. if (pg->a) {
  574. byte vertical = (i >= 4);
  575. byte frame;
  576. switch (state.level) {
  577. case 13: // Skylabs
  578. if (pg->y != pg->x1) {
  579. pg->f = 0;
  580. pg->y += pg->d;
  581. } else {
  582. pg->f++;
  583. }
  584. if (pg->f == 8) {
  585. pg->f = 0;
  586. pg->x += 64;
  587. pg->y = pg->x0;
  588. }
  589. loadspr16(IMG_GUARD + i, state.guardian + (pg->f << 5), 255, INK(pg->a));
  590. sprite(IMG_GUARD + i, pg->x, pg->y, IMG_GUARD + i);
  591. break;
  592. default:
  593. if (state.bidir)
  594. frame = (t & 3) ^ (pg->d ? 7 : 0);
  595. else
  596. if (vertical)
  597. frame = t & 3;
  598. else
  599. frame = 4 + (t & 3) ^ (pg->d ? 3 : 0);
  600. loadspr16(IMG_GUARD + i, state.guardian + (frame << 5), 255, INK(pg->a));
  601. sprite(IMG_GUARD + i, pg->x, pg->y, IMG_GUARD + i);
  602. if (!vertical) {
  603. if ((t & 3) == 3) {
  604. if (pg->x == pg->x0 && pg->d)
  605. pg->d = 0;
  606. else if (pg->x == pg->x1 && !pg->d)
  607. pg->d = 1;
  608. else
  609. pg->x += pg->d ? -8 : 8;
  610. }
  611. } else {
  612. if (pg->y <= pg->x0 && pg->d < 0)
  613. pg->d = -pg->d;
  614. else if (pg->y >= pg->x1 && pg->d > 0)
  615. pg->d = -pg->d;
  616. else
  617. pg->y += pg->d;
  618. }
  619. }
  620. }
  621. }
  622. pg = &guards[4];
  623. switch (state.level) { // Special level handling
  624. case 4: // Eugene's lair
  625. sprite(IMG_GUARD + 4, pg->x, pg->y, IMG_GUARD + 4);
  626. if (pg->y == pg->x0 && pg->d < 0)
  627. pg->d = 1;
  628. if (pg->y == pg->x1 && pg->d > 0)
  629. pg->d = -1;
  630. if (state.nitems == 0) { // all collected -> descend and camp
  631. if (pg->d == -1)
  632. pg->d = 1;
  633. if (pg->y == pg->x1)
  634. pg->d = 0;
  635. }
  636. pg->y += pg->d;
  637. break;
  638. case 7: // Miner Willy meets the Kong Beast
  639. case 11: // Return of the Alien Kong Beast
  640. byte frame, color;
  641. if (!state.switch2) {
  642. frame = (t >> 3) & 1;
  643. color = 8 + 4;
  644. } else {
  645. frame = 2 + ((t >> 1) & 1);
  646. color = 8 + 6;
  647. if (pg->y < 104) {
  648. pg->y += 4;
  649. bump_score(100);
  650. }
  651. }
  652. if (pg->y != 104) {
  653. loadspr16(IMG_GUARD + 4, state.guardian + (frame << 5), 255, color);
  654. sprite(IMG_GUARD + 4, pg->x, pg->y, IMG_GUARD + 4);
  655. } else {
  656. hide(IMG_GUARD + 4);
  657. }
  658. }
  659. // Animate conveyors:
  660. signed char convt = state.conveyordir ? t : -t;
  661. GD.__wstart(RAM_CHR + 4 * 16 + 2 * 0); // character 4 line 0
  662. unpack8(rol8(state.conveyor[0], convt));
  663. GD.__end();
  664. GD.__wstart(RAM_CHR + 4 * 16 + 2 * 3); // character 4 line 3
  665. unpack8(rol8(state.conveyor[0], -convt));
  666. GD.__end();
  667. // Solar rays
  668. if (state.level == 18) {
  669. // Draw new ray, turning 0x00 to 0x80
  670. byte sx = 23;
  671. byte sy = 0;
  672. byte sd = 0; // down
  673. byte ri; // ray index
  674. for (ri = 0; ri < MAXRAY; ri++) {
  675. uint16_t a = atxy(sx, sy);
  676. if (state.prevray[ri] != a) {
  677. GD.wr(state.prevray[ri], 0);
  678. if (GD.rd(a) != 0)
  679. break; // absorbed
  680. GD.wr(a, 0x80 + ' ');
  681. state.prevray[ri] = a;
  682. }
  683. for (byte i = 0; i < 8; i++)
  684. if (guards[i].a && (guards[i].x >> 3) == sx && (guards[i].y >> 3) == sy)
  685. sd ^= 1;
  686. if (sd == 0)
  687. sy++;
  688. else
  689. sx--;
  690. }
  691. while (ri < MAXRAY) {
  692. GD.wr(state.prevray[ri], 0);
  693. state.prevray[ri++] = 4095;
  694. }
  695. }
  696. // animate item colors starting at 16
  697. uint16_t colors[4] = { MAGENTA, YELLOW, CYAN, GREEN };
  698. for (byte i = 0; i < 5; i++)
  699. GD.wr16(RAM_SPRPAL + 2 * (i + 16), colors[(i + t) & 3]);
  700. // when all items collected,
  701. // flash portal in colors 32,33 - the thing the Spectrum *can* do in hardware
  702. byte flip = (t >> 3) & (CHEAT_OPEN_PORTAL || state.nitems == 0);
  703. GD.wr16(RAM_SPRPAL + 2 * (32 ^ flip), COLOR(PAPER(state.portalattr)));
  704. GD.wr16(RAM_SPRPAL + 2 * (33 ^ flip), COLOR(INK(state.portalattr)));
  705. // animated lives count, draw up to seven
  706. for (byte i = 0; i < 7; i++)
  707. if (i < (state.lives - 1))
  708. sprite(IMG_WILLYC + i, i << 4, 168, IMG_WILLYC + ((t >> 2) & 3));
  709. else
  710. hide(IMG_WILLYC + i);
  711. GD.waitvblank();
  712. }
  713. // play a complete game
  714. void loop()
  715. {
  716. title_screen();
  717. game_graphics();
  718. state.level = START_LEVEL;
  719. state.score = 0;
  720. for (state.lives = 3; state.lives; state.lives--) {
  721. loadlevel(&levels[state.level]);
  722. // state.wy = 16;
  723. byte alive = 1;
  724. byte t = 0;
  725. while (alive) {
  726. drive_level(t);
  727. // Willy draw
  728. byte frame = state.wf ^ (state.wd ? 7 : 0);
  729. loadspr16(IMG_WILLY, willy + (frame << 5), 255, 8 + 7);
  730. sprite(IMG_WILLY, state.wx, state.wy, IMG_WILLY);
  731. // Willy movement
  732. // See http://www.seasip.demon.co.uk/Jsw/manic.mac
  733. // and http://jetsetwilly.jodi.org/poke.html
  734. byte con = control();
  735. byte ychanged = 0; // if Y block changes must check for landing later
  736. if (state.jumping) {
  737. #define JUMP_APEX 9
  738. #define JUMP_FALL 11 // 1 2 3 4 5 6 7 8 9 10 11
  739. signed char moves[] = { -4, -4, -3, -3, -2, -2, -1, -1, 0, 0, 1, 1, 2, 2, 3, 3, 4 };
  740. byte index = min(sizeof(moves) - 1, state.jumping - 1);
  741. byte newy = state.wy + moves[index];
  742. state.jumping++;
  743. if (canbe(state.wx, newy)) {
  744. ychanged = (state.wy >> 3) != (newy >> 3);
  745. state.wy = newy;
  746. } else {
  747. state.jumping = max(state.jumping, JUMP_FALL); // halt ascent
  748. ychanged = 1;
  749. }
  750. }
  751. uint16_t feet_addr = atxy(state.wx >> 3, (state.wy + 16) >> 3);
  752. byte elem0 = GD.rd(feet_addr);
  753. byte elem1 = GD.rd(feet_addr + 1);
  754. byte elem = ((1 <= elem0) && (elem0 <= 31)) ? elem0 : elem1;
  755. byte dx = 0xff;
  756. byte first_jump = (con & CONTROL_JUMP) && state.lastdx == 0xff;
  757. if (state.jumping) {
  758. dx = state.lastdx;
  759. } else if (!first_jump && (elem == ELEM_CONVEYOR) && (state.wd == state.conveyordir)) {
  760. dx = state.conveyordir;
  761. } else {
  762. if (con & CONTROL_RIGHT) {
  763. if (state.wd != 0) {
  764. state.wf ^= 3;
  765. state.wd = 0;
  766. } else {
  767. dx = 0;
  768. }
  769. } else if (con & CONTROL_LEFT) {
  770. if (state.wd == 0) {
  771. state.wf ^= 3;
  772. state.wd = 1;
  773. } else {
  774. dx = 1;
  775. }
  776. }
  777. }
  778. if (dx != 0xff) {
  779. if (state.wf != 3)
  780. state.wf++;
  781. else {
  782. byte newx = state.wx + (dx ? -8 : 8);
  783. if (canbe(newx, state.wy)) {
  784. state.wf = 0;
  785. state.wx = newx;
  786. }
  787. }
  788. }
  789. state.lastdx = dx;
  790. if ((elem == ELEM_CONVEYOR) && (dx == 0xff) && !state.jumping)
  791. state.wd = state.conveyordir;
  792. if (!state.jumping && (con & CONTROL_JUMP)) {
  793. if (canbe(state.wx, state.wy - 3)) {
  794. state.jumping = 1;
  795. state.wy -= 0;
  796. }
  797. }
  798. byte onground = ((1 <= elem) && (elem <= 4)) || ((7 <= elem) && (elem <= 16));
  799. if (state.jumping) {
  800. if ((JUMP_APEX <= state.jumping) && ychanged && onground) {
  801. state.jumping = 0;
  802. state.wy &= ~7;
  803. }
  804. if (state.jumping >= 16) // stop x movement late in the jump
  805. state.lastdx = 0xff;
  806. } else {
  807. if (!onground) { // nothing to stand on, start falling
  808. state.jumping = JUMP_FALL;
  809. state.lastdx = 0xff;
  810. }
  811. }
  812. if (!state.jumping) {
  813. crumble(feet_addr);
  814. crumble(feet_addr + 1);
  815. }
  816. if (((t & 7) == 0) ||
  817. ((state.level == 18) && inray(state.wx, state.wy))) {
  818. state.air--;
  819. plot_air();
  820. if (state.air == 0) {
  821. alive = 0;
  822. }
  823. }
  824. GD.waitvblank(); // let the screen display, then check for collisions
  825. byte coll = GD.rd(COLLISION + IMG_WILLY);
  826. if (coll <= (IMG_ITEM + 4)) {
  827. bump_score(100);
  828. hide(coll - IMG_ITEM);
  829. --state.nitems;
  830. } else if (coll == IMG_PORTAL) {
  831. if (CHEAT_OPEN_PORTAL || state.nitems == 0) {
  832. while (state.air) {
  833. squarewave(0, 800 + 2 * state.air, 100);
  834. state.air--;
  835. plot_air();
  836. bump_score(7);
  837. GD.waitvblank();
  838. }
  839. state.level = (state.level + 1) % 18;
  840. loadlevel(&levels[state.level]);
  841. }
  842. } else if (coll == IMG_SWITCH1) {
  843. if (!state.switch1) {
  844. state.switch1 = 1;
  845. sprite(IMG_SWITCH1, 48 - 8, 0, IMG_SWITCH1, 2);
  846. GD.wr(atxy(17, 11), 0);
  847. GD.wr(atxy(17, 12), 0);
  848. }
  849. } else if (coll == IMG_SWITCH2) {
  850. if (coll == IMG_SWITCH2) {
  851. state.switch2 = 1;
  852. sprite(IMG_SWITCH2, 144 - 8, 0, IMG_SWITCH2, 2);
  853. GD.wr(atxy(15, 2), 0);
  854. GD.wr(atxy(16, 2), 0);
  855. }
  856. } else if (coll != 0xff && !CHEAT_INVINCIBLE) {
  857. alive = 0;
  858. }
  859. t++;
  860. }
  861. // player died
  862. clear_screen();
  863. }
  864. game_over();
  865. }