Chris Dick
/
Gameduino_Manic_Miner_game
Manic miner game for the Gameduino
Embed:
(wiki syntax)
Show/hide line numbers
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 }
Generated on Tue Jul 12 2022 21:57:01 by 1.7.2