Manic miner game for the Gameduino

Dependencies:   Gameduino mbed

Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers main.cpp Source File

main.cpp

00001 #include "mbed.h"
00002 #include "GD.h"
00003 #include "arduino.h"
00004 #include "shield.h"
00005 
00006 GDClass GD(ARD_MOSI, ARD_MISO, ARD_SCK, ARD_D9, USBTX, USBRX) ;
00007 SPI spi(ARD_MOSI, ARD_MISO, ARD_SCK);// mosi, miso, sclk
00008 DigitalOut cs(ARD_D9);
00009 Serial pc(USBTX, USBRX);
00010 
00011 #define CHEAT_INVINCIBLE    0   // means Willy unaffected by nasties
00012 #define START_LEVEL         0   // level to start on, 0-18
00013 #define CHEAT_OPEN_PORTAL   0   // Portal always open
00014 
00015 // Game has three controls: LEFT, RIGHT, JUMP.  You can map them
00016 // to any pins by changing the definitions of PIN_L, PIN_R, PIN_J
00017 // below.
00018 
00019 #if 1 // SPARKFUN_JOYSTICK
00020 #define PIN_L  ARD_A0
00021 #define PIN_R  ARD_A1
00022 #define PIN_J  ARD_D6
00023 #else
00024 #define PIN_L  ARD_A2
00025 #define PIN_R  ARD_A3
00026 #define PIN_J  ARD_A5
00027 #endif
00028 
00029 #define CONTROL_LEFT  1
00030 #define CONTROL_RIGHT 2
00031 #define CONTROL_JUMP  4
00032 
00033 DigitalIn jump(ARD_D4);
00034 DigitalIn jumpa(ARD_D5);
00035 DigitalIn jumpb(ARD_D6);
00036 DigitalIn jumpc(ARD_D7);
00037 DigitalIn jumpd(ARD_A4);
00038 DigitalIn left(ARD_A1);
00039 DigitalIn right(ARD_A0);
00040 
00041 static byte setup_control()
00042 {
00043   jump.mode(PullUp);
00044   jumpa.mode(PullUp);
00045   jumpb.mode(PullUp);
00046   jumpc.mode(PullUp);
00047   jumpd.mode(PullUp);
00048   left.mode(PullUp);
00049   right.mode(PullUp);
00050 }
00051 
00052 static byte control()
00053 {
00054   byte r = 0;
00055   //if (jump)
00056   //  r |= CONTROL_JUMP;
00057   if (!jumpa)
00058     r |= CONTROL_JUMP;
00059   if (!jumpb)
00060     r |= CONTROL_JUMP;
00061   if (!jumpc)
00062     r |= CONTROL_JUMP;
00063   if (!jumpd)
00064     r |= CONTROL_JUMP;
00065   if (!left)
00066     r |= CONTROL_LEFT;
00067   if (!right)
00068     r |= CONTROL_RIGHT;
00069   return r;
00070 }
00071 
00072 #define BLACK   RGB(0,0,0)
00073 #define RED     RGB(255,0,0)
00074 #define YELLOW  RGB(255,255,0)
00075 #define CYAN    RGB(0,255,255)
00076 #define GREEN   RGB(0,255,0)
00077 #define MAGENTA RGB(255,0,255)
00078 #define WHITE   RGB(255,255,255)
00079 
00080 #define CHR_BORDER    31    // hw char used for all of the border
00081 #define CHR_AIR       128   // AIR line
00082 
00083                             // assigned sprite images and sprite numbers
00084 #define IMG_ITEM      0     // items 0-4
00085 #define IMG_PORTAL    5     // room exit
00086 #define IMG_SWITCH1   6     // the switches for the Kong Beast levels
00087 #define IMG_SWITCH2   7     
00088 #define IMG_GUARD     8     // All guardians, 8 max
00089 #define IMG_NASTY1    16    // Nasty blocks
00090 #define IMG_NASTY2    17
00091 #define IMG_WILLYC    56
00092 #define IMG_WILLY     63
00093 
00094 #define ELEM_AIR      0
00095 #define ELEM_FLOOR    1
00096 #define ELEM_CRUMBLE  2
00097 #define ELEM_WALL     3
00098 #define ELEM_CONVEYOR 4
00099 #define ELEM_NASTY1   5
00100 #define ELEM_NASTY2   6
00101 
00102 struct level {
00103   byte name[32];
00104   prog_uchar *background;
00105   byte border;
00106   prog_uchar bgchars[64];
00107   byte bgattr[8];
00108   byte item[8];
00109   struct { byte x, y; } items[5];
00110   byte portal[32];
00111   byte air;
00112   byte conveyordir;
00113   byte portalattr, portalx, portaly;
00114   byte guardian[8 * 32];
00115   struct { byte a, x, y, d, x0, x1; } hguard[8];
00116   byte wx, wy, wd, wf;
00117   byte bidir;
00118 };
00119 #include "manicminer.h"
00120 #include "spectrum.h"
00121 #include "spectrum_data.h"    // title screen
00122 
00123 #define COLOR(i)  (pgm_read_word_near(specpal + (2 * (i))))
00124 #define BRIGHT(x) (((x) & 64) >> 3)
00125 #define PAPER(a)  ((((a) >> 3) & 7) | BRIGHT(a))
00126 #define INK(a)    (((a) & 7) | BRIGHT(a))
00127 
00128 // screen coordinate - Spectrum 256x192 screen is centered in Gameduino screen
00129 static uint16_t atxy(byte x, byte y)
00130 {
00131   return RAM_PIC + 64 * (y + 6) + (x + 9);
00132 }
00133 
00134 // unpack 8 monochrome pixels into Gameduino character RAM
00135 void unpack8(byte b)
00136 {
00137   static byte stretch[16] = {
00138     0x00, 0x03, 0x0c, 0x0f,
00139     0x30, 0x33, 0x3c, 0x3f,
00140     0xc0, 0xc3, 0xcc, 0xcf,
00141     0xf0, 0xf3, 0xfc, 0xff
00142   };
00143   spi.write(stretch[b >> 4]);
00144   spi.write(stretch[b & 15]);
00145 }
00146 
00147 // unpack monochrome bitmap from flash to Gameduino character RAM at dst
00148 void unpack8xn(uint16_t dst, PROGMEM prog_uchar *src, uint16_t size)
00149 {
00150   GD.__wstart(dst);
00151   while (size--)
00152     unpack8(pgm_read_byte_near(src++));
00153   GD.__end();
00154 }
00155 
00156 // Load 8x8 1-bit sprite from src into slot (0-63)
00157 // zero and one are the two colors
00158 
00159 void send8(byte b, byte zero, byte one)
00160 {
00161     for (byte j = 8; j; j--) {
00162       spi.write((b & 0x80) ? one : zero);
00163       b <<= 1;
00164     }
00165 }
00166 
00167 // load an 8x8 into a hw 256-color sprite, padding with transparent (color 255)
00168 void loadspr8(byte slot, PROGMEM prog_uchar *src, byte zero, byte one)
00169 {
00170   GD.__wstart(RAM_SPRIMG + (slot << 8));
00171   for (byte i = 8; i; i--) {
00172     send8(pgm_read_byte_near(src++), zero, one);
00173     send8(0, 255, 255);
00174   }
00175   for (byte i = 128; i; i--)
00176     spi.write(255);
00177   GD.__end();
00178 }
00179 
00180 // load an 16x16 into a hw 256-color sprite
00181 void loadspr16(byte slot, PROGMEM prog_uchar *src, byte zero, byte one)
00182 {
00183   GD.__wstart(RAM_SPRIMG + (slot << 8));
00184   for (byte i = 32; i; i--)
00185     send8(pgm_read_byte_near(src++), zero, one);
00186   GD.__end();
00187 }
00188 
00189 
00190 static void sprite(byte spr, byte x, byte y, byte img, byte rot = 0)
00191 {
00192   GD.sprite(spr, x + 9*8, y + 6*8, img, 0, rot);
00193 }
00194 
00195 static void hide(byte spr)
00196 {
00197   GD.sprite(spr, 400, 400, 0, 0, 0);
00198 }
00199 
00200 struct guardian {
00201   byte a;
00202   byte x, y;
00203   signed char d;
00204   byte x0, x1;
00205   byte f;
00206 } guards[8];
00207 
00208 #define MAXRAY 40
00209 
00210 struct state_t {
00211   byte level;
00212   byte lives;
00213   uint32_t score;
00214   uint32_t hiscore;
00215 
00216   byte bidir;
00217   byte air;
00218   byte conveyordir;
00219   byte portalattr;
00220   byte nitems;
00221   byte wx, wy;    // Willy x,y
00222   byte wd, wf;    // Willy dir and frame
00223   byte lastdx;    // Willy last x movement
00224   byte convey;    // Willy caught on conveyor
00225   byte jumping;
00226   signed char wyv;
00227   byte conveyor[2];
00228   PROGMEM prog_uchar *guardian;
00229   uint16_t prevray[MAXRAY];
00230   byte switch1, switch2;
00231 } state;
00232 
00233 // All this is taken from
00234 // http://jswremakes.emuunlim.com/Mmt/Manic%20Miner%20Room%20Format.htm
00235 
00236 static void plot_air()
00237 {
00238   // Starting at CHR_AIR+4, draw a line of n/8 solid, n%8, then 27-(n/8)
00239   uint16_t addr = RAM_CHR + (16 * (CHR_AIR + 4)); // line 2 of (CHR_AIR+4)
00240   for (byte i = 0; i < 28; i++) {
00241     byte v;
00242     if (i < (state.air >> 3))
00243       v = 8;
00244     else if (i == (state.air >> 3))
00245       v = state.air & 7;
00246     else
00247       v = 0;
00248     unpack8xn(addr + i * 16, airs + (v << 3), 8);
00249   }
00250 }
00251 
00252 static void plot_score()
00253 {
00254   uint32_t n = state.score;
00255 
00256   GD.__wstart(atxy(26, 19));
00257   spi.write('0' + (n / 100000) % 10);
00258   spi.write('0' + (n / 10000) % 10);
00259   spi.write('0' + (n / 1000) % 10);
00260   spi.write('0' + (n / 100) % 10);
00261   spi.write('0' + (n / 10) % 10);
00262   spi.write('0' + n % 10);
00263   GD.__end();
00264 }
00265 
00266 static void bump_score(byte n)
00267 {
00268   if ((state.score < 10000) && (10000 <= (state.score + n )))
00269     state.lives++;
00270   state.score += n;
00271   plot_score();
00272 }
00273 
00274 static void pause(byte n)
00275 {
00276   while (n--)
00277     GD.waitvblank();
00278 }
00279 
00280 static void clear_screen(byte yclear = 16)
00281 {
00282   // blank out top 16 lines
00283   GD.fill(RAM_CHR, 0, 16);
00284   GD.wr16(RAM_PAL, BLACK);
00285   for (byte y = 0; y < yclear; y++)
00286     GD.fill(atxy(0, y), 0, 32);
00287   for (int i = 0; i < 256; i++)
00288     hide(i);
00289   for (int i = 0; i < 64; i++)
00290     GD.voice(i, 0, 0, 0, 0);
00291   pause(6);
00292 }
00293 
00294 static void loadlevel(struct level *l)
00295 {
00296   clear_screen();
00297 
00298   // border
00299   GD.wr16(RAM_PAL + 8 * CHR_BORDER, COLOR(pgm_read_byte_near(&l->border)));
00300 
00301   // background picture: uncompress into scratch then copy to screen
00302   prog_uchar *background = (prog_uchar*)pgm_read_word_near(&l->background);
00303   uint16_t scratch = 4096 - 512;  // offscreen scratch
00304   GD.uncompress(scratch, background);
00305   for (byte y = 0; y < 16; y++)
00306     for (byte x = 0; x < 32; x++)
00307       GD.wr(atxy(x, y), GD.rd(scratch + (y << 5) + x));
00308   for (byte y = 16; y < 24; y++)
00309     GD.fill(atxy(0, y), ' ', 32);
00310 
00311   // bg characters
00312   unpack8xn(RAM_CHR, l->bgchars, sizeof(l->bgchars));
00313   state.conveyor[0] = pgm_read_byte_near(&l->bgchars[8 * 4 + 0]);
00314   state.conveyor[1] = pgm_read_byte_near(&l->bgchars[8 * 4 + 3]);
00315 
00316   // bg character palettes
00317   for (byte c = 0; c < 8; c++) {
00318     byte attr = pgm_read_byte_near(l->bgattr + c);
00319     GD.wr16(RAM_PAL + 8 * c, COLOR(PAPER(attr)));
00320     GD.wr16(RAM_PAL + 8 * c + 6, COLOR(INK(attr)));
00321   }
00322 
00323   // block 2 is the crumbling floor
00324   // put the 7 anims of crumbling floor in chars 8-14
00325   GD.fill(RAM_CHR + 16 * 8, 0, 16 * 7);
00326   prog_uchar *src = l->bgchars + 2 * 8;
00327   for (byte i = 0; i < 7; i++) {
00328     byte ch = 8 + i;
00329     unpack8xn(RAM_CHR + 16 * ch + 2 * (i + 1), src, 7 - i);
00330     byte attr = pgm_read_byte_near(l->bgattr + 2);
00331     GD.wr16(RAM_PAL + 8 * ch, COLOR(PAPER(attr)));
00332     GD.wr16(RAM_PAL + 8 * ch + 6, COLOR(INK(attr)));
00333   }
00334 
00335   // level name and score line
00336   for (byte x = 0; x < 32; x++) {
00337     char scoreline[] = "High Score 000000   Score 000000";
00338     GD.wr(atxy(x, 16), 0x80 + pgm_read_byte_near(l->name + x));
00339     GD.wr(atxy(x, 19), scoreline[x]);
00340   }
00341   plot_score();
00342 
00343   // AIR line characters
00344   for (byte i = 0; i < 32; i++)
00345     GD.wr(atxy(i, 17), CHR_AIR + i);
00346 
00347   // the items are 5 sprites, using colors 16 up
00348   state.nitems = 0;
00349   for (byte i = 0; i < 5; i++) {
00350     byte x = pgm_read_byte_near(&l->items[i].x);
00351     byte y = pgm_read_byte_near(&l->items[i].y);
00352     if (x || y) {
00353       loadspr8(IMG_ITEM + i, l->item, 255, 16 + i);   // items have color 16 up
00354       sprite(IMG_ITEM + i, x, y, IMG_ITEM + i);
00355       state.nitems++;
00356     } else
00357       hide(IMG_ITEM + i);
00358   }
00359 
00360   // the portal is a sprite, using colors 32,33
00361   state.portalattr = pgm_read_byte_near(&l->portalattr);
00362   loadspr16(IMG_PORTAL, l->portal, 32, 33);
00363   byte portalx = pgm_read_byte_near(&l->portalx);
00364   byte portaly = pgm_read_byte_near(&l->portaly);
00365   sprite(IMG_PORTAL, portalx, portaly, IMG_PORTAL);
00366 
00367   // use sprites for blocks NASTY1 and NASTY2
00368   byte nc1 = pgm_read_byte_near(l->bgattr + ELEM_NASTY1);
00369   byte nc2 = pgm_read_byte_near(l->bgattr + ELEM_NASTY2);
00370   loadspr8(IMG_NASTY1, l->bgchars + ELEM_NASTY1 * 8, 255, INK(nc1));
00371   loadspr8(IMG_NASTY2, l->bgchars + ELEM_NASTY2 * 8, 255, INK(nc2));
00372   // now paint sprites over all the NASTY blocks
00373   {
00374     byte spr = IMG_NASTY1;
00375     for (byte y = 0; y < 16; y++)
00376       for (byte x = 0; x < 32; x++) {
00377         byte blk = GD.rd(atxy(x, y));
00378         if (blk == ELEM_NASTY1)
00379           sprite(spr++, x << 3, y << 3, IMG_NASTY1);
00380         if (blk == ELEM_NASTY2)
00381           sprite(spr++, x << 3, y << 3, IMG_NASTY2);
00382       }
00383   }
00384 
00385   // air
00386   state.air = pgm_read_byte_near(&l->air);
00387   plot_air();
00388 
00389   // Conveyor direction
00390   state.conveyordir = pgm_read_byte_near(&l->conveyordir);
00391 
00392   // the hguardians
00393   state.bidir = pgm_read_byte_near(&l->bidir);
00394   state.guardian = l->guardian;
00395   guardian *pg;
00396   for (byte i = 0; i < 8; i++) {
00397     byte a = pgm_read_byte_near(&l->hguard[i].a);
00398     guards[i].a = a;
00399     if (a) {
00400       byte x = pgm_read_byte_near(&l->hguard[i].x);
00401       byte y = pgm_read_byte_near(&l->hguard[i].y);
00402       guards[i].x = x;
00403       guards[i].y = y;
00404       guards[i].d = pgm_read_byte_near(&l->hguard[i].d);
00405       guards[i].x0 = pgm_read_byte_near(&l->hguard[i].x0);
00406       guards[i].x1 = pgm_read_byte_near(&l->hguard[i].x1);
00407       guards[i].f = 0;
00408     } else {
00409       hide(6 + i);
00410     }
00411   }
00412 
00413   pg = &guards[4];  // Use slot 4 for special guardians
00414   switch (state.level) { // Special level handling
00415   case 4: // Eugene's lair
00416     pg->a = 0;  // prevent normal guardian logic
00417     pg->x = 120;
00418     pg->y = 0;
00419     pg->d = -1;
00420     pg->x0 = 0;
00421     pg->x1 = 88;
00422     loadspr16(IMG_GUARD + 4, eugene, 255, 15);
00423     break;
00424   case 7:   // Miner Willy meets the Kong Beast
00425   case 11:  // Return of the Alien Kong Beast
00426     pg->a = 0;
00427     pg->x = 120;
00428     pg->y = 0;
00429     state.switch1 = 0;
00430     loadspr8(IMG_SWITCH1, lightswitch, 0, 6);
00431     sprite(IMG_SWITCH1, 48, 0, IMG_SWITCH1);
00432     state.switch2 = 0;
00433     loadspr8(IMG_SWITCH2, lightswitch, 0, 6);
00434     sprite(IMG_SWITCH2, 144, 0, IMG_SWITCH2);
00435     break;
00436   }
00437 
00438   for (byte i = 0; i < MAXRAY; i++)
00439     state.prevray[i] = 4095;
00440 
00441   // Willy
00442   state.wx = pgm_read_byte_near(&l->wx);
00443   state.wy = pgm_read_byte_near(&l->wy);
00444   state.wf = pgm_read_byte_near(&l->wf);
00445   state.wd = pgm_read_byte_near(&l->wd);
00446   state.jumping = 0;
00447   state.convey = 0;
00448   state.wyv = 0;
00449 }
00450 
00451 static byte rol8(byte b, byte d)  // 8-bit byte rotate left
00452 {
00453   d &= 7;
00454   return ((b << d) | (b >> (8 - d))) & 0xff;
00455 }
00456 
00457 
00458 void setup()
00459 {
00460   GD.begin();
00461   setup_control();
00462 }
00463 
00464 void game_graphics()
00465 {
00466   // Load the Spectrum font: regular in 32-127 reverse in 160-255
00467   unpack8xn(RAM_CHR + 16 * ' ', specfont, sizeof(specfont));
00468   unpack8xn(RAM_CHR + 16 * (128 + ' '), specfont, sizeof(specfont));
00469   for (byte i = 32; i < 128; i++) {
00470     GD.setpal(4 * i,              BLACK);
00471     GD.setpal(4 * i + 3,          YELLOW);
00472     GD.setpal(4 * (128 + i),      COLOR(6));    // dark yellow
00473     GD.setpal(4 * (128 + i) + 3,  BLACK);
00474   }
00475 
00476   // AIR display in 32 chars starting at CHR_AIR
00477   unpack8xn(RAM_CHR + 16 * CHR_AIR, specfont + 8 * ('A' - ' '), 8);
00478   unpack8xn(RAM_CHR + 16 * (CHR_AIR+1), specfont + 8 * ('I' - ' '), 8);
00479   unpack8xn(RAM_CHR + 16 * (CHR_AIR+2), specfont + 8 * ('R' - ' '), 8);
00480   for (byte i = 0; i < 32; i++) {
00481     uint16_t color = (i < 10) ? RED : GREEN;
00482     GD.setpal(4 * (CHR_AIR + i), color);
00483     GD.setpal(4 * (CHR_AIR + i) + 3, WHITE);
00484   }
00485 
00486   // Load the Spectrum's palette into PALETTE256A
00487   GD.copy(RAM_SPRPAL, specpal, sizeof(specpal));
00488   GD.wr16(RAM_SPRPAL + 2 * 255, TRANSPARENT);  // color 255 is transparent
00489 
00490   // fill screen with char CHR_BORDER
00491   GD.fill(RAM_CHR + 16 * CHR_BORDER, 0, 16);
00492   GD.fill(RAM_PIC, CHR_BORDER, 64 * 64);
00493 
00494   // Load Willy in bright cyan and white
00495   for (byte i = 0; i < 4; i++) {
00496     loadspr16(IMG_WILLYC + i, willy + (i << 5), 255, 8 + 5);
00497   }
00498 }
00499 
00500 // midi frequency table
00501 static PROGMEM prog_uint16_t midifreq[128] = {
00502 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
00503 };
00504 #define MIDI(n) pgm_read_word(midifreq + (n))
00505 
00506 static void squarewave(byte voice, uint16_t f0, byte amp)
00507 {
00508   GD.voice(8*voice + 0, 0, f0,     amp,    amp);
00509   GD.voice(8*voice + 1, 0, 3 * f0, amp/3,  amp/3);
00510   GD.voice(8*voice + 2, 0, 5 * f0, amp/5,  amp/5);
00511   GD.voice(8*voice + 3, 0, 7 * f0, amp/7,  amp/7);
00512   GD.voice(8*voice + 4, 0, 9 * f0, amp/9,  amp/9);
00513   GD.voice(8*voice + 5, 0, 11 * f0, amp/11,  amp/11);
00514 }
00515 
00516 static void drive_level(byte t);
00517 
00518 static int title_screen() // returns 1 if player pressed something
00519 {
00520   for (;;) {
00521 display:
00522     clear_screen();
00523     GD.fill(0x4000, 0, 6144);   // clear emulated Spectrum video RAM
00524     GD.uncompress(0x7000, spectrum_tables);
00525 
00526     // fill screen with char 0x80 for border
00527     GD.setpal(4 * 0x80, COLOR(2));
00528     GD.fill(RAM_CHR, 0, 4096);
00529     GD.fill(RAM_PIC, 0x80, 4096);
00530 
00531     // paint the 256x192 window as alternating lines of
00532     // chars 0-31 and 32-63
00533     for (byte y = 0; y < 24; y += 2) {
00534       for (byte x = 0; x < 32; x++) {
00535         GD.wr(atxy(x, y), x);
00536         GD.wr(atxy(x, y + 1), 32 + x);
00537       }
00538     }
00539     GD.microcode(spectrum_code, sizeof(spectrum_code));
00540     GD.uncompress(0x4000, screen_mm1);
00541 
00542     for (prog_uint32_t *tune = blue_danube;
00543          (size_t)tune < ((size_t)blue_danube + sizeof(blue_danube));
00544          tune++) {
00545       uint32_t cmd = pgm_read_dword_near(tune);
00546       for (int t = 60 * (cmd >> 28); t; t--) {
00547         delay(1);
00548         if (control())
00549           goto escape;
00550       }
00551       byte n0 = 127 & (cmd >> 21);
00552       byte a0 = 127 & (cmd >> 14);
00553       byte n1 = 127 & (cmd >> 7);
00554       byte a1 = 127 & cmd;
00555       squarewave(0, MIDI(n0), a0 >> 1);
00556       squarewave(1, MIDI(n1), a1 >> 1);
00557       GD.setpal(4 * 0x80, COLOR((n1 ^ n0) & 7));
00558     }
00559     GD.setpal(4 * 0x80, COLOR(7));
00560 
00561     for (byte i = 0; i < ((sizeof(message) - 1) - 32); i++) {
00562       prog_uchar *src = message + i;
00563       for (byte j = 0; j < 32; j++) {
00564         byte ch = pgm_read_byte_near(src + j);
00565         prog_uchar *glyph = specfont + ((ch - ' ') << 3);
00566         for (byte k = 0; k < 8; k++)
00567           GD.wr(0x5060 + j + (k << 8), pgm_read_byte_near(glyph++));
00568       }
00569       for (byte t = 80; t; t--) {
00570         delay(1);
00571         if (control())
00572           goto escape;
00573       }
00574     }
00575 
00576     GD.wr(J1_RESET, 1); // stop coprocessor
00577     clear_screen(24);
00578     game_graphics();
00579 
00580     for (state.level = 0; state.level < 19; state.level++) {
00581       loadlevel(&levels[state.level]);
00582       for (byte t = 0; t < 128; t++) {
00583         drive_level(t);
00584         GD.waitvblank();
00585         if (control())
00586           goto display;
00587       }
00588     }
00589   }
00590 
00591 escape:
00592   GD.wr(J1_RESET, 1); // stop coprocessor
00593   clear_screen(24);
00594   game_graphics();
00595 }
00596 
00597 static void game_over()
00598 {
00599   clear_screen();
00600   // Willy
00601   loadspr16(0, willy, 255, 8 + 7);
00602   sprite(0, 124, 128 - 32, 0);
00603 
00604   // plinth
00605   loadspr16(2, plinth, 255, 8 + 7);
00606   sprite(2, 120, 128 - 16, 2);
00607 
00608   // Boot
00609   loadspr16(1, boot, 255, 8 + 7);
00610   loadspr16(3, boot, 255, 8 + 7);   // sprite 3 is top 2 lines of boot
00611   // erase the bottom 14 lines of boot
00612   GD.fill(RAM_SPRIMG + 3 * 256 + 2 * 16, 255, 14 * 16);
00613 
00614   for (byte i = 0; i <= 48; i++) {
00615     sprite(1, 120, 2 * i, 1);         // boot in sprite 1
00616     sprite(3 + i, 120, 2 * i, 3);     // leg in sprites 3,4, etc
00617     if (41 <= i) // erase Willy as boot covers him
00618       GD.fill(RAM_SPRIMG + 32 * (i - 41), 255, 32);
00619     if ((i & 1) == 0)
00620       GD.wr16(RAM_PAL, COLOR(random(7)));
00621     squarewave(0, 400 + 4 * i, 150);
00622     pause(2);
00623   }
00624   GD.wr16(RAM_PAL, BLACK);
00625   squarewave(0, 0, 0);
00626 
00627   // "Game Over" text in sprite images 16-23, color 16-23
00628   char msg[] = "GameOver";
00629   for (byte i = 0; i < 8; i++)
00630     loadspr8(16 + i, specfont + (msg[i] - 32) * 8, 255, 16 + i);
00631   for (byte i = 0; i < 4; i++) {
00632     sprite(100 + i, 80 + i * 8, 48, 16 + i);
00633     sprite(104 + i, 140 + i * 8, 48, 20 + i);
00634   }
00635   for (byte t = 120; t; t--) {
00636     for (byte i = 0; i < 8; i++)
00637       GD.wr16(RAM_SPRPAL + (16 + i) * 2, COLOR(9 + random(7)));
00638     pause(2);
00639   }
00640   clear_screen();
00641 }
00642 
00643 // crumble block at s, which is the sequence
00644 // 2->8->9->10->11->12->13->14->0
00645 
00646 static void crumble(uint16_t s)
00647 {
00648   byte r = GD.rd(s);
00649   signed char nexts[] = {
00650     -1, -1,  8, -1, -1, -1, -1, -1,
00651     9,  10, 11, 12, 13, 14, 0 };
00652   if (r < sizeof(nexts) && nexts[r] != -1)
00653     GD.wr(s, nexts[r]);
00654 }
00655 
00656 // is it legal for willy to be at (x,y)
00657 static int canbe(byte x, byte y)
00658 {
00659   uint16_t addr = atxy(x / 8, y / 8);
00660   return
00661     (GD.rd(addr) != ELEM_WALL) &&
00662     (GD.rd(addr+1) != ELEM_WALL) &&
00663     (GD.rd(addr+64) != ELEM_WALL) &&
00664     (GD.rd(addr+65) != ELEM_WALL);
00665 }
00666 
00667 // is Willy standing in a solar ray?
00668 static int inray(byte x, byte y)
00669 {
00670   uint16_t addr = atxy(x / 8, y / 8);
00671   return
00672     (GD.rd(addr) > 0x80 ) ||
00673     (GD.rd(addr+1) > 0x80) ||
00674     (GD.rd(addr+64) > 0x80) ||
00675     (GD.rd(addr+65) > 0x80);
00676 }
00677 
00678 static void drive_level(byte t)
00679 {
00680   uint16_t freq = 133955L / pgm_read_byte_near(music1 + ((t >> 1) & 63));
00681   GD.waitvblank();
00682   squarewave(0, freq, 128);
00683   GD.waitvblank();
00684   squarewave(0, freq, 0);
00685   GD.waitvblank();
00686   GD.waitvblank();
00687 
00688   guardian *pg;
00689   byte lt;  // local time, for slow hguardians
00690   for (byte i = 0; i < 8; i++) {
00691     pg = &guards[i];
00692     if (pg->a) {
00693       byte vertical = (i >= 4);
00694       byte frame;
00695       switch (state.level) {
00696       case 13:  // Skylabs
00697         if (pg->y != pg->x1) {
00698           pg->f = 0;
00699           pg->y += pg->d;
00700         } else {
00701           pg->f++;
00702         }
00703         if (pg->f == 8) {
00704           pg->f = 0;
00705           pg->x += 64;
00706           pg->y = pg->x0;
00707         }
00708         loadspr16(IMG_GUARD + i, state.guardian + (pg->f << 5), 255, INK(pg->a));
00709         sprite(IMG_GUARD + i, pg->x, pg->y, IMG_GUARD + i);
00710         break;
00711       default:
00712         lt = t;
00713         if (!vertical && (pg->a & 0x80)) {
00714           if (t & 1)
00715             lt = t >> 1;
00716           else
00717             break;
00718         }
00719         if (state.bidir)
00720           frame = (lt & 3) ^ (pg->d ? 7 : 0);
00721         else
00722           if (vertical)
00723             frame = lt & 3;
00724           else
00725             frame = 4 + (lt & 3) ^ (pg->d ? 3 : 0);
00726         loadspr16(IMG_GUARD + i, state.guardian + (frame << 5), 255, INK(pg->a));
00727         sprite(IMG_GUARD + i, pg->x, pg->y, IMG_GUARD + i);
00728         if (!vertical) {
00729           if ((lt & 3) == 3) {
00730             if (pg->x == pg->x0 && pg->d)
00731               pg->d = 0;
00732             else if (pg->x == pg->x1 && !pg->d)
00733               pg->d = 1;
00734             else
00735               pg->x += pg->d ? -8 : 8;
00736           }
00737         } else {
00738           if (pg->y <= pg->x0 && pg->d < 0)
00739             pg->d = -pg->d;
00740           else if (pg->y >= pg->x1 && pg->d > 0)
00741             pg->d = -pg->d;
00742           else
00743             pg->y += pg->d;
00744         }
00745       }
00746     }
00747   }
00748   pg = &guards[4];
00749   switch (state.level) { // Special level handling
00750   case 4: // Eugene's lair
00751     sprite(IMG_GUARD + 4, pg->x, pg->y, IMG_GUARD + 4);
00752     if (pg->y == pg->x0 && pg->d < 0)
00753       pg->d = 1;
00754     if (pg->y == pg->x1 && pg->d > 0)
00755       pg->d = -1;
00756     if (state.nitems == 0) {  // all collected -> descend and camp
00757       if (pg->d == -1)
00758         pg->d = 1;
00759       if (pg->y == pg->x1)
00760         pg->d = 0;
00761     }
00762     pg->y += pg->d;
00763     break;
00764   case 7: // Miner Willy meets the Kong Beast
00765   case 11: //  Return of the Alien Kong Beast
00766     byte frame, color;
00767     if (!state.switch2) {
00768       frame = (t >> 3) & 1;
00769       color = 8 + 4;
00770     } else {
00771       frame = 2 + ((t >> 1) & 1);
00772       color = 8 + 6;
00773       if (pg->y < 104) {
00774         pg->y += 4;
00775         bump_score(100);
00776       }
00777     }
00778     if (pg->y != 104) {
00779       loadspr16(IMG_GUARD + 4, state.guardian + (frame << 5), 255, color);
00780       sprite(IMG_GUARD + 4, pg->x, pg->y, IMG_GUARD + 4);
00781     } else {
00782       hide(IMG_GUARD + 4);
00783     }
00784   }
00785 
00786   // Animate conveyors: 
00787   signed char convt = state.conveyordir ? t : -t;
00788   GD.__wstart(RAM_CHR + 4 * 16 + 2 * 0);  // character 4 line 0
00789   unpack8(rol8(state.conveyor[0], convt));
00790   GD.__end();
00791   GD.__wstart(RAM_CHR + 4 * 16 + 2 * 3);  // character 4 line 3
00792   unpack8(rol8(state.conveyor[0], -convt));
00793   GD.__end();
00794 
00795   // Solar rays
00796   if (state.level == 18) {
00797     // Draw new ray, turning 0x00 to 0x80
00798     byte sx = 23;
00799     byte sy = 0;
00800     byte sd = 0; // down
00801     byte ri;     // ray index
00802     for (ri = 0; ri < MAXRAY; ri++) {
00803       uint16_t a = atxy(sx, sy);
00804       if (state.prevray[ri] != a) {
00805         GD.wr(state.prevray[ri], 0);
00806         if (GD.rd(a) != 0)
00807           break;  // absorbed
00808         GD.wr(a, 0x80 + ' ');
00809         state.prevray[ri] = a;
00810       }
00811       for (byte i = 0; i < 8; i++)
00812         if (guards[i].a && (guards[i].x >> 3) == sx && (guards[i].y >> 3) == sy)
00813           sd ^= 1;
00814       if (sd == 0)
00815         sy++;
00816       else
00817         sx--;
00818     }
00819     while (ri < MAXRAY) {
00820       GD.wr(state.prevray[ri], 0);
00821       state.prevray[ri++] = 4095;
00822     }
00823   }
00824 
00825   // animate item colors starting at 16
00826   uint16_t colors[4] = { MAGENTA, YELLOW, CYAN, GREEN };
00827   for (byte i = 0; i < 5; i++)
00828     GD.wr16(RAM_SPRPAL + 2 * (i + 16), colors[(i + t) & 3]);
00829 
00830   // when all items collected,
00831   // flash portal in colors 32,33 - the thing the Spectrum *can* do in hardware
00832   byte flip = (t >> 3) & (CHEAT_OPEN_PORTAL || state.nitems == 0);
00833   GD.wr16(RAM_SPRPAL + 2 * (32 ^ flip), COLOR(PAPER(state.portalattr)));
00834   GD.wr16(RAM_SPRPAL + 2 * (33 ^ flip), COLOR(INK(state.portalattr)));
00835 
00836   // animated lives count, draw up to seven
00837   for (byte i = 0; i < 7; i++)
00838     if (i < (state.lives - 1))
00839       sprite(IMG_WILLYC + i, i << 4, 168, IMG_WILLYC + ((t >> 2) & 3));
00840     else
00841       hide(IMG_WILLYC + i);
00842 
00843   GD.waitvblank();
00844 }
00845 
00846 // play a complete game
00847 void loop()
00848 {
00849   title_screen();
00850   game_graphics();
00851 
00852   state.level = START_LEVEL;
00853   state.score = 0;
00854   for (state.lives = 3; state.lives; state.lives--) {
00855     loadlevel(&levels[state.level]);
00856     // state.wy = 16;
00857     byte alive = 1;
00858     byte t = 0;
00859     while (alive) {
00860       drive_level(t);
00861 
00862       // Willy draw
00863       byte frame = state.wf ^ (state.wd ? 7 : 0);
00864       loadspr16(IMG_WILLY, willy + (frame << 5), 255, 8 + 7);
00865       sprite(IMG_WILLY, state.wx, state.wy, IMG_WILLY);
00866 
00867       // Willy movement
00868       // See http://www.seasip.demon.co.uk/Jsw/manic.mac
00869       // and http://jetsetwilly.jodi.org/poke.html
00870 
00871       byte con = control();
00872       byte ychanged = 0;  // if Y block changes must check for landing later
00873       if (state.jumping) {
00874 #define JUMP_APEX 9   
00875 #define JUMP_FALL 11  //  1   2   3   4   5   6   7   8   9  10 11
00876         signed char moves[] = {  -4, -4, -3, -3, -2, -2, -1, -1, 0, 0, 1, 1, 2, 2, 3, 3, 4 };
00877         byte index = min(sizeof(moves) - 1, state.jumping - 1);
00878         byte newy = state.wy + moves[index];
00879         state.jumping++;
00880         if (canbe(state.wx, newy)) {
00881           ychanged = (state.wy >> 3) != (newy >> 3);
00882           state.wy = newy;
00883         } else {
00884           state.jumping = max(state.jumping, JUMP_FALL);   // halt ascent
00885           ychanged = 1;
00886         }
00887       }
00888       uint16_t feet_addr = atxy(state.wx >> 3, (state.wy + 16) >> 3);
00889       byte elem0 = GD.rd(feet_addr);
00890       byte elem1 = GD.rd(feet_addr + 1);
00891       byte elem = ((1 <= elem0) && (elem0 <= 31)) ? elem0 : elem1;
00892 
00893       byte dx = 0xff;
00894       byte first_jump = (con & CONTROL_JUMP) && state.lastdx == 0xff;
00895       if (state.jumping) {
00896         dx = state.lastdx;
00897       } else if (!first_jump && (elem == ELEM_CONVEYOR) && (state.wd == state.conveyordir)) {
00898         dx = state.conveyordir;
00899       } else {
00900         if (con & CONTROL_RIGHT) {
00901           if (state.wd != 0) {
00902             state.wf ^= 3;
00903             state.wd = 0;
00904           } else {
00905             dx = 0;
00906           }
00907         } else if (con & CONTROL_LEFT) {
00908           if (state.wd == 0) {
00909             state.wf ^= 3;
00910             state.wd = 1;
00911           } else {
00912             dx = 1;
00913           }
00914         }
00915       }
00916       if (dx != 0xff) {
00917         if (state.wf != 3)
00918           state.wf++;
00919         else {
00920           byte newx = state.wx + (dx ? -8 : 8);
00921           if (canbe(newx, state.wy)) {
00922             state.wf = 0;
00923             state.wx = newx;
00924           }
00925         }
00926       }
00927       state.lastdx = dx;
00928 
00929       if ((elem == ELEM_CONVEYOR) && (dx == 0xff) && !state.jumping)
00930         state.wd = state.conveyordir;
00931 
00932       if (!state.jumping && (con & CONTROL_JUMP)) {
00933         if (canbe(state.wx, state.wy - 3)) {
00934           state.jumping = 1;
00935           state.wy -= 0;
00936         }
00937       }
00938 
00939       byte onground = ((1 <= elem) && (elem <= 4)) || ((7 <= elem) && (elem <= 16));
00940       if (state.jumping) {
00941         if ((JUMP_APEX <= state.jumping) && ychanged && onground) {
00942           state.jumping = 0;
00943           state.wy &= ~7;
00944         }
00945         if (state.jumping >= 19)    // stop x movement late in the jump
00946           state.lastdx = 0xff;
00947       } else {
00948         if (!onground) {    // nothing to stand on, start falling
00949           state.jumping = JUMP_FALL;
00950           state.lastdx = 0xff;
00951         }
00952       }
00953       if (!state.jumping) {
00954         crumble(feet_addr);
00955         crumble(feet_addr + 1);
00956       }
00957 
00958       if (((t & 7) == 0) ||
00959          ((state.level == 18) && inray(state.wx, state.wy))) {
00960         state.air--;
00961         plot_air();
00962         if (state.air == 0) {
00963           alive = 0;
00964         }
00965       }
00966       GD.waitvblank();    // let the screen display, then check for collisions
00967       byte coll = GD.rd(COLLISION + IMG_WILLY);
00968       if (coll <= (IMG_ITEM + 4)) {
00969         bump_score(100);
00970         hide(coll - IMG_ITEM);
00971         --state.nitems;
00972       } else if (coll == IMG_PORTAL) {
00973         if (CHEAT_OPEN_PORTAL || state.nitems == 0) {
00974           while (state.air) {
00975             squarewave(0, 800 + 2 * state.air, 100);
00976             state.air--;
00977             plot_air();
00978             bump_score(7);
00979             GD.waitvblank();
00980           }
00981           state.level = (state.level + 1) % 18;
00982           loadlevel(&levels[state.level]);
00983         }
00984       } else if (coll == IMG_SWITCH1) {
00985         if (!state.switch1) {
00986           state.switch1 = 1;
00987           sprite(IMG_SWITCH1, 48 - 8, 0, IMG_SWITCH1, 2);
00988           GD.wr(atxy(17, 11), 0);
00989           GD.wr(atxy(17, 12), 0);
00990         }
00991       } else if (coll == IMG_SWITCH2) {
00992         if (coll == IMG_SWITCH2) {
00993           state.switch2 = 1;
00994           sprite(IMG_SWITCH2, 144 - 8, 0, IMG_SWITCH2, 2);
00995           GD.wr(atxy(15, 2), 0);
00996           GD.wr(atxy(16, 2), 0);
00997         }
00998       } else if (coll != 0xff && !CHEAT_INVINCIBLE) {
00999         alive = 0;
01000       }
01001       t++;
01002     }
01003     // player died
01004     clear_screen();
01005   }
01006   game_over();
01007 }
01008 
01009 
01010 
01011 
01012 
01013 
01014 
01015 int main(){
01016    setup();
01017    while(1){
01018        loop();
01019    }
01020 }