123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961 |
- #include <SPI.h>
- #include <GD.h>
- #define CHEAT_INVINCIBLE 0 // means Willy unaffected by nasties
- #define START_LEVEL 0 // level to start on, 0-18
- #define CHEAT_OPEN_PORTAL 0 // Portal always open
- // Game has three controls: LEFT, RIGHT, JUMP. You can map them
- // to any pins by changing the definitions of PIN_L, PIN_R, PIN_J
- // below.
- static int ss; // JCB
- #if 1 // SPARKFUN_JOYSTICK
- #define PIN_L 6
- #define PIN_R 3
- #define PIN_J 4
- #else
- #define PIN_L A2
- #define PIN_R A3
- #define PIN_J A5
- #endif
- #define CONTROL_LEFT 1
- #define CONTROL_RIGHT 2
- #define CONTROL_JUMP 4
- static byte setup_control()
- {
- pinMode(PIN_L, INPUT);
- digitalWrite(PIN_L, HIGH);
- pinMode(PIN_R, INPUT);
- digitalWrite(PIN_R, HIGH);
- pinMode(PIN_J, INPUT);
- digitalWrite(PIN_J, HIGH);
- }
- static byte control()
- {
- byte r = 0;
- if (!digitalRead(PIN_J))
- r |= CONTROL_JUMP;
- if (!digitalRead(PIN_L))
- r |= CONTROL_LEFT;
- if (!digitalRead(PIN_R))
- r |= CONTROL_RIGHT;
- return r;
- }
- #define BLACK RGB(0,0,0)
- #define RED RGB(255,0,0)
- #define YELLOW RGB(255,255,0)
- #define CYAN RGB(0,255,255)
- #define GREEN RGB(0,255,0)
- #define MAGENTA RGB(255,0,255)
- #define WHITE RGB(255,255,255)
- #define CHR_BORDER 31 // hw char used for all of the border
- #define CHR_AIR 128 // AIR line
- // assigned sprite images and sprite numbers
- #define IMG_ITEM 0 // items 0-4
- #define IMG_PORTAL 5 // room exit
- #define IMG_SWITCH1 6 // the switches for the Kong Beast levels
- #define IMG_SWITCH2 7
- #define IMG_GUARD 8 // All guardians, 8 max
- #define IMG_NASTY1 16 // Nasty blocks
- #define IMG_NASTY2 17
- #define IMG_WILLYC 56
- #define IMG_WILLY 63
- #define ELEM_AIR 0
- #define ELEM_FLOOR 1
- #define ELEM_CRUMBLE 2
- #define ELEM_WALL 3
- #define ELEM_CONVEYOR 4
- #define ELEM_NASTY1 5
- #define ELEM_NASTY2 6
- #include "manicminer.h"
- #include "spectrum.h"
- #include "spectrum_data.h" // title screen
- #define COLOR(i) (pgm_read_word_near(specpal + (2 * (i))))
- #define BRIGHT(x) (((x) & 64) >> 3)
- #define PAPER(a) ((((a) >> 3) & 7) | BRIGHT(a))
- #define INK(a) (((a) & 7) | BRIGHT(a))
- // screen coordinate - Spectrum 256x192 screen is centered in Gameduino screen
- static uint16_t atxy(byte x, byte y)
- {
- return RAM_PIC + 64 * (y + 6) + (x + 9);
- }
- // unpack 8 monochrome pixels into Gameduino character RAM
- void unpack8(byte b)
- {
- static byte stretch[16] = {
- 0x00, 0x03, 0x0c, 0x0f,
- 0x30, 0x33, 0x3c, 0x3f,
- 0xc0, 0xc3, 0xcc, 0xcf,
- 0xf0, 0xf3, 0xfc, 0xff
- };
- SPI.transfer(stretch[b >> 4]);
- SPI.transfer(stretch[b & 15]);
- }
- // unpack monochrome bitmap from flash to Gameduino character RAM at dst
- void unpack8xn(uint16_t dst, flash_uint8_t *src, uint16_t size)
- {
- GD.__wstart(dst);
- while (size--)
- unpack8(pgm_read_byte_near(src++));
- GD.__end();
- }
- // Load 8x8 1-bit sprite from src into slot (0-63)
- // zero and one are the two colors
- void send8(byte b, byte zero, byte one)
- {
- for (byte j = 8; j; j--) {
- SPI.transfer((b & 0x80) ? one : zero);
- b <<= 1;
- }
- }
- // load an 8x8 into a hw 256-color sprite, padding with transparent (color 255)
- void loadspr8(byte slot, flash_uint8_t *src, byte zero, byte one)
- {
- GD.__wstart(RAM_SPRIMG + (slot << 8));
- for (byte i = 8; i; i--) {
- send8(pgm_read_byte_near(src++), zero, one);
- send8(0, 255, 255);
- }
- for (byte i = 128; i; i--)
- SPI.transfer(255);
- GD.__end();
- }
- // load an 16x16 into a hw 256-color sprite
- void loadspr16(byte slot, flash_uint8_t *src, byte zero, byte one)
- {
- GD.__wstart(RAM_SPRIMG + (slot << 8));
- for (byte i = 32; i; i--)
- send8(pgm_read_byte_near(src++), zero, one);
- GD.__end();
- }
- static void sprite(byte spr, byte x, byte y, byte img, byte rot = 0)
- {
- GD.sprite(spr, x + 9*8, y + 6*8, img, 0, rot);
- }
- static void hide(byte spr)
- {
- GD.sprite(spr, 400, 400, 0, 0, 0);
- }
- struct guardian {
- byte a;
- byte x, y;
- signed char d;
- byte x0, x1;
- byte f;
- } guards[8];
- #define MAXRAY 40
- struct state_t {
- byte level;
- byte lives;
- uint32_t score;
- uint32_t hiscore;
- byte bidir;
- byte air;
- byte conveyordir;
- byte portalattr;
- byte nitems;
- byte wx, wy; // Willy x,y
- byte wd, wf; // Willy dir and frame
- byte lastdx; // Willy last x movement
- byte convey; // Willy caught on conveyor
- byte jumping;
- signed char wyv;
- byte conveyor[2];
- flash_uint8_t *guardian;
- uint16_t prevray[MAXRAY];
- byte switch1, switch2;
- } state;
- // All this is taken from
- // http://jswremakes.emuunlim.com/Mmt/Manic%20Miner%20Room%20Format.htm
- static void plot_air()
- {
- // Starting at CHR_AIR+4, draw a line of n/8 solid, n%8, then 27-(n/8)
- uint16_t addr = RAM_CHR + (16 * (CHR_AIR + 4)); // line 2 of (CHR_AIR+4)
- for (byte i = 0; i < 28; i++) {
- byte v;
- if (i < (state.air >> 3))
- v = 8;
- else if (i == (state.air >> 3))
- v = state.air & 7;
- else
- v = 0;
- unpack8xn(addr + i * 16, airs + (v << 3), 8);
- }
- }
- static void plot_score()
- {
- uint32_t n = state.score;
- GD.__wstart(atxy(26, 19));
- SPI.transfer('0' + (n / 100000) % 10);
- SPI.transfer('0' + (n / 10000) % 10);
- SPI.transfer('0' + (n / 1000) % 10);
- SPI.transfer('0' + (n / 100) % 10);
- SPI.transfer('0' + (n / 10) % 10);
- SPI.transfer('0' + n % 10);
- GD.__end();
- }
- static void bump_score(byte n)
- {
- if ((state.score < 10000) && (10000 <= (state.score + n )))
- state.lives++;
- state.score += n;
- plot_score();
- }
- static void pause(byte n)
- {
- while (n--)
- GD.waitvblank();
- }
- static void clear_screen(byte yclear = 16)
- {
- // blank out top 16 lines
- GD.fill(RAM_CHR, 0, 16);
- GD.wr16(RAM_PAL, BLACK);
- for (byte y = 0; y < yclear; y++)
- GD.fill(atxy(0, y), 0, 32);
- for (int i = 0; i < 256; i++)
- hide(i);
- for (int i = 0; i < 64; i++)
- GD.voice(i, 0, 0, 0, 0);
- pause(6);
- }
- static void loadlevel(const PROGMEM struct level *l)
- {
- clear_screen();
- // border
- GD.wr16(RAM_PAL + 8 * CHR_BORDER, COLOR(pgm_read_byte_near(&l->border)));
- // background picture: uncompress into scratch then copy to screen
- flash_uint8_t *background = (flash_uint8_t*)pgm_read_word_near(&l->background);
- uint16_t scratch = 4096 - 512; // offscreen scratch
- GD.uncompress(scratch, background);
- for (byte y = 0; y < 16; y++)
- for (byte x = 0; x < 32; x++)
- GD.wr(atxy(x, y), GD.rd(scratch + (y << 5) + x));
- for (byte y = 16; y < 24; y++)
- GD.fill(atxy(0, y), ' ', 32);
- // bg characters
- unpack8xn(RAM_CHR, l->bgchars, sizeof(l->bgchars));
- state.conveyor[0] = pgm_read_byte_near(&l->bgchars[8 * 4 + 0]);
- state.conveyor[1] = pgm_read_byte_near(&l->bgchars[8 * 4 + 3]);
- // bg character palettes
- for (byte c = 0; c < 8; c++) {
- byte attr = pgm_read_byte_near(l->bgattr + c);
- GD.wr16(RAM_PAL + 8 * c, COLOR(PAPER(attr)));
- GD.wr16(RAM_PAL + 8 * c + 6, COLOR(INK(attr)));
- }
- // block 2 is the crumbling floor
- // put the 7 anims of crumbling floor in chars 8-14
- GD.fill(RAM_CHR + 16 * 8, 0, 16 * 7);
- flash_uint8_t *src = l->bgchars + 2 * 8;
- for (byte i = 0; i < 7; i++) {
- byte ch = 8 + i;
- unpack8xn(RAM_CHR + 16 * ch + 2 * (i + 1), src, 7 - i);
- byte attr = pgm_read_byte_near(l->bgattr + 2);
- GD.wr16(RAM_PAL + 8 * ch, COLOR(PAPER(attr)));
- GD.wr16(RAM_PAL + 8 * ch + 6, COLOR(INK(attr)));
- }
- // level name and score line
- for (byte x = 0; x < 32; x++) {
- char scoreline[] = "High Score 000000 Score 000000";
- GD.wr(atxy(x, 16), 0x80 + pgm_read_byte_near(l->name + x));
- GD.wr(atxy(x, 19), scoreline[x]);
- }
- plot_score();
- // AIR line characters
- for (byte i = 0; i < 32; i++)
- GD.wr(atxy(i, 17), CHR_AIR + i);
- // the items are 5 sprites, using colors 16 up
- state.nitems = 0;
- for (byte i = 0; i < 5; i++) {
- byte x = pgm_read_byte_near(&l->items[i].x);
- byte y = pgm_read_byte_near(&l->items[i].y);
- if (x || y) {
- loadspr8(IMG_ITEM + i, l->item, 255, 16 + i); // items have color 16 up
- sprite(IMG_ITEM + i, x, y, IMG_ITEM + i);
- state.nitems++;
- } else
- hide(IMG_ITEM + i);
- }
- // the portal is a sprite, using colors 32,33
- state.portalattr = pgm_read_byte_near(&l->portalattr);
- loadspr16(IMG_PORTAL, l->portal, 32, 33);
- byte portalx = pgm_read_byte_near(&l->portalx);
- byte portaly = pgm_read_byte_near(&l->portaly);
- sprite(IMG_PORTAL, portalx, portaly, IMG_PORTAL);
- // use sprites for blocks NASTY1 and NASTY2
- byte nc1 = pgm_read_byte_near(l->bgattr + ELEM_NASTY1);
- byte nc2 = pgm_read_byte_near(l->bgattr + ELEM_NASTY2);
- loadspr8(IMG_NASTY1, l->bgchars + ELEM_NASTY1 * 8, 255, INK(nc1));
- loadspr8(IMG_NASTY2, l->bgchars + ELEM_NASTY2 * 8, 255, INK(nc2));
- // now paint sprites over all the NASTY blocks
- {
- byte spr = IMG_NASTY1;
- for (byte y = 0; y < 16; y++)
- for (byte x = 0; x < 32; x++) {
- byte blk = GD.rd(atxy(x, y));
- if (blk == ELEM_NASTY1)
- sprite(spr++, x << 3, y << 3, IMG_NASTY1);
- if (blk == ELEM_NASTY2)
- sprite(spr++, x << 3, y << 3, IMG_NASTY2);
- }
- }
- // air
- state.air = pgm_read_byte_near(&l->air);
- plot_air();
- // Conveyor direction
- state.conveyordir = pgm_read_byte_near(&l->conveyordir);
- // the hguardians
- state.bidir = pgm_read_byte_near(&l->bidir);
- state.guardian = l->guardian;
- guardian *pg;
- for (byte i = 0; i < 8; i++) {
- byte a = pgm_read_byte_near(&l->hguard[i].a);
- guards[i].a = a;
- if (a) {
- byte x = pgm_read_byte_near(&l->hguard[i].x);
- byte y = pgm_read_byte_near(&l->hguard[i].y);
- guards[i].x = x;
- guards[i].y = y;
- guards[i].d = pgm_read_byte_near(&l->hguard[i].d);
- guards[i].x0 = pgm_read_byte_near(&l->hguard[i].x0);
- guards[i].x1 = pgm_read_byte_near(&l->hguard[i].x1);
- guards[i].f = 0;
- } else {
- hide(6 + i);
- }
- }
- pg = &guards[4]; // Use slot 4 for special guardians
- switch (state.level) { // Special level handling
- case 4: // Eugene's lair
- pg->a = 0; // prevent normal guardian logic
- pg->x = 120;
- pg->y = 0;
- pg->d = -1;
- pg->x0 = 0;
- pg->x1 = 88;
- loadspr16(IMG_GUARD + 4, eugene, 255, 15);
- break;
- case 7: // Miner Willy meets the Kong Beast
- case 11: // Return of the Alien Kong Beast
- pg->a = 0;
- pg->x = 120;
- pg->y = 0;
- state.switch1 = 0;
- loadspr8(IMG_SWITCH1, lightswitch, 0, 6);
- sprite(IMG_SWITCH1, 48, 0, IMG_SWITCH1);
- state.switch2 = 0;
- loadspr8(IMG_SWITCH2, lightswitch, 0, 6);
- sprite(IMG_SWITCH2, 144, 0, IMG_SWITCH2);
- break;
- }
- for (byte i = 0; i < MAXRAY; i++)
- state.prevray[i] = 4095;
- // Willy
- state.wx = pgm_read_byte_near(&l->wx);
- state.wy = pgm_read_byte_near(&l->wy);
- state.wf = pgm_read_byte_near(&l->wf);
- state.wd = pgm_read_byte_near(&l->wd);
- state.jumping = 0;
- state.convey = 0;
- state.wyv = 0;
- }
- static byte rol8(byte b, byte d) // 8-bit byte rotate left
- {
- d &= 7;
- return ((b << d) | (b >> (8 - d))) & 0xff;
- }
- void setup()
- {
- GD.begin();
- setup_control();
- Serial.begin(1000000); // JCB
- }
- void game_graphics()
- {
- // Load the Spectrum font: regular in 32-127 reverse in 160-255
- unpack8xn(RAM_CHR + 16 * ' ', specfont, sizeof(specfont));
- unpack8xn(RAM_CHR + 16 * (128 + ' '), specfont, sizeof(specfont));
- for (byte i = 32; i < 128; i++) {
- GD.setpal(4 * i, BLACK);
- GD.setpal(4 * i + 3, YELLOW);
- GD.setpal(4 * (128 + i), COLOR(6)); // dark yellow
- GD.setpal(4 * (128 + i) + 3, BLACK);
- }
- // AIR display in 32 chars starting at CHR_AIR
- unpack8xn(RAM_CHR + 16 * CHR_AIR, specfont + 8 * ('A' - ' '), 8);
- unpack8xn(RAM_CHR + 16 * (CHR_AIR+1), specfont + 8 * ('I' - ' '), 8);
- unpack8xn(RAM_CHR + 16 * (CHR_AIR+2), specfont + 8 * ('R' - ' '), 8);
- for (byte i = 0; i < 32; i++) {
- uint16_t color = (i < 10) ? RED : GREEN;
- GD.setpal(4 * (CHR_AIR + i), color);
- GD.setpal(4 * (CHR_AIR + i) + 3, WHITE);
- }
- // Load the Spectrum's palette into PALETTE256A
- GD.copy(RAM_SPRPAL, specpal, sizeof(specpal));
- GD.wr16(RAM_SPRPAL + 2 * 255, TRANSPARENT); // color 255 is transparent
- // fill screen with char CHR_BORDER
- GD.fill(RAM_CHR + 16 * CHR_BORDER, 0, 16);
- GD.fill(RAM_PIC, CHR_BORDER, 64 * 64);
- // Load Willy in bright cyan and white
- for (byte i = 0; i < 4; i++) {
- loadspr16(IMG_WILLYC + i, willy + (i << 5), 255, 8 + 5);
- }
- }
- // midi frequency table
- static const PROGMEM uint16_t midifreq[128] = {
- 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
- };
- #define MIDI(n) pgm_read_word(midifreq + (n))
- static void squarewave(byte voice, uint16_t f0, byte amp)
- {
- GD.voice(8*voice + 0, 0, f0, amp, amp);
- GD.voice(8*voice + 1, 0, 3 * f0, amp/3, amp/3);
- GD.voice(8*voice + 2, 0, 5 * f0, amp/5, amp/5);
- GD.voice(8*voice + 3, 0, 7 * f0, amp/7, amp/7);
- GD.voice(8*voice + 4, 0, 9 * f0, amp/9, amp/9);
- GD.voice(8*voice + 5, 0, 11 * f0, amp/11, amp/11);
- }
- static void drive_level(byte t);
- static int title_screen() // returns 1 if player pressed something
- {
- for (;;) {
- display:
- clear_screen();
- GD.fill(0x4000, 0, 6144); // clear emulated Spectrum video RAM
- GD.uncompress(0x7000, spectrum_tables);
- // fill screen with char 0x80 for border
- GD.setpal(4 * 0x80, COLOR(2));
- GD.fill(RAM_CHR, 0, 4096);
- GD.fill(RAM_PIC, 0x80, 4096);
- // paint the 256x192 window as alternating lines of
- // chars 0-31 and 32-63
- for (byte y = 0; y < 24; y += 2) {
- for (byte x = 0; x < 32; x++) {
- GD.wr(atxy(x, y), x);
- GD.wr(atxy(x, y + 1), 32 + x);
- }
- }
- GD.microcode(spectrum_code, sizeof(spectrum_code));
- GD.uncompress(0x4000, screen_mm1);
- for (const PROGMEM uint32_t *tune = blue_danube;
- (size_t)tune < ((size_t)blue_danube + sizeof(blue_danube));
- tune++) {
- uint32_t cmd = pgm_read_dword_near(tune);
- for (int t = 60 * (cmd >> 28); t; t--) {
- delay(1);
- if (control())
- goto escape;
- }
- byte n0 = 127 & (cmd >> 21);
- byte a0 = 127 & (cmd >> 14);
- byte n1 = 127 & (cmd >> 7);
- byte a1 = 127 & cmd;
- squarewave(0, MIDI(n0), a0 >> 1);
- squarewave(1, MIDI(n1), a1 >> 1);
- GD.setpal(4 * 0x80, COLOR((n1 ^ n0) & 7));
- }
- GD.setpal(4 * 0x80, COLOR(7));
- // GD.screenshot(ss++); // JCB
- for (byte i = 0; i < ((sizeof(message) - 1) - 32); i++) {
- flash_uint8_t *src = message + i;
- for (byte j = 0; j < 32; j++) {
- byte ch = pgm_read_byte_near(src + j);
- flash_uint8_t *glyph = specfont + ((ch - ' ') << 3);
- for (byte k = 0; k < 8; k++)
- GD.wr(0x5060 + j + (k << 8), pgm_read_byte_near(glyph++));
- }
- for (byte t = 80; t; t--) {
- delay(1);
- if (control())
- goto escape;
- }
- }
- GD.wr(J1_RESET, 1); // stop coprocessor
- clear_screen(24);
- game_graphics();
- for (state.level = 0; state.level < 19; state.level++) {
- loadlevel(&levels[state.level]);
- for (byte t = 0; t < 128; t++) {
- drive_level(t);
- GD.waitvblank();
- if (control())
- goto display;
- }
- }
- }
- escape:
- GD.wr(J1_RESET, 1); // stop coprocessor
- clear_screen(24);
- game_graphics();
- }
- static void game_over()
- {
- clear_screen();
- // Willy
- loadspr16(0, willy, 255, 8 + 7);
- sprite(0, 124, 128 - 32, 0);
- // plinth
- loadspr16(2, plinth, 255, 8 + 7);
- sprite(2, 120, 128 - 16, 2);
- // Boot
- loadspr16(1, boot, 255, 8 + 7);
- loadspr16(3, boot, 255, 8 + 7); // sprite 3 is top 2 lines of boot
- // erase the bottom 14 lines of boot
- GD.fill(RAM_SPRIMG + 3 * 256 + 2 * 16, 255, 14 * 16);
- for (byte i = 0; i <= 48; i++) {
- sprite(1, 120, 2 * i, 1); // boot in sprite 1
- sprite(3 + i, 120, 2 * i, 3); // leg in sprites 3,4, etc
- if (41 <= i) // erase Willy as boot covers him
- GD.fill(RAM_SPRIMG + 32 * (i - 41), 255, 32);
- if ((i & 1) == 0)
- GD.wr16(RAM_PAL, COLOR(random(7)));
- squarewave(0, 400 + 4 * i, 150);
- pause(2);
- }
- GD.wr16(RAM_PAL, BLACK);
- squarewave(0, 0, 0);
- // "Game Over" text in sprite images 16-23, color 16-23
- char msg[] = "GameOver";
- for (byte i = 0; i < 8; i++)
- loadspr8(16 + i, specfont + (msg[i] - 32) * 8, 255, 16 + i);
- for (byte i = 0; i < 4; i++) {
- sprite(100 + i, 80 + i * 8, 48, 16 + i);
- sprite(104 + i, 140 + i * 8, 48, 20 + i);
- }
- for (byte t = 120; t; t--) {
- for (byte i = 0; i < 8; i++)
- GD.wr16(RAM_SPRPAL + (16 + i) * 2, COLOR(9 + random(7)));
- pause(2);
- }
- clear_screen();
- }
- // crumble block at s, which is the sequence
- // 2->8->9->10->11->12->13->14->0
- static void crumble(uint16_t s)
- {
- byte r = GD.rd(s);
- signed char nexts[] = {
- -1, -1, 8, -1, -1, -1, -1, -1,
- 9, 10, 11, 12, 13, 14, 0 };
- if (r < sizeof(nexts) && nexts[r] != -1)
- GD.wr(s, nexts[r]);
- }
- // is it legal for willy to be at (x,y)
- static int canbe(byte x, byte y)
- {
- uint16_t addr = atxy(x / 8, y / 8);
- return
- (GD.rd(addr) != ELEM_WALL) &&
- (GD.rd(addr+1) != ELEM_WALL) &&
- (GD.rd(addr+64) != ELEM_WALL) &&
- (GD.rd(addr+65) != ELEM_WALL);
- }
- // is Willy standing in a solar ray?
- static int inray(byte x, byte y)
- {
- uint16_t addr = atxy(x / 8, y / 8);
- return
- (GD.rd(addr) > 0x80 ) ||
- (GD.rd(addr+1) > 0x80) ||
- (GD.rd(addr+64) > 0x80) ||
- (GD.rd(addr+65) > 0x80);
- }
- static void drive_level(byte t)
- {
- uint16_t freq = 133955L / pgm_read_byte_near(music1 + ((t >> 1) & 63));
- GD.waitvblank();
- squarewave(0, freq, 128);
- GD.waitvblank();
- squarewave(0, freq, 0);
- GD.waitvblank();
- GD.waitvblank();
- guardian *pg;
- for (byte i = 0; i < 8; i++) {
- pg = &guards[i];
- if (pg->a) {
- byte vertical = (i >= 4);
- byte frame;
- switch (state.level) {
- case 13: // Skylabs
- if (pg->y != pg->x1) {
- pg->f = 0;
- pg->y += pg->d;
- } else {
- pg->f++;
- }
- if (pg->f == 8) {
- pg->f = 0;
- pg->x += 64;
- pg->y = pg->x0;
- }
- loadspr16(IMG_GUARD + i, state.guardian + (pg->f << 5), 255, INK(pg->a));
- sprite(IMG_GUARD + i, pg->x, pg->y, IMG_GUARD + i);
- break;
- default:
- if (state.bidir)
- frame = (t & 3) ^ (pg->d ? 7 : 0);
- else
- if (vertical)
- frame = t & 3;
- else
- frame = 4 + (t & 3) ^ (pg->d ? 3 : 0);
- loadspr16(IMG_GUARD + i, state.guardian + (frame << 5), 255, INK(pg->a));
- sprite(IMG_GUARD + i, pg->x, pg->y, IMG_GUARD + i);
- if (!vertical) {
- if ((t & 3) == 3) {
- if (pg->x == pg->x0 && pg->d)
- pg->d = 0;
- else if (pg->x == pg->x1 && !pg->d)
- pg->d = 1;
- else
- pg->x += pg->d ? -8 : 8;
- }
- } else {
- if (pg->y <= pg->x0 && pg->d < 0)
- pg->d = -pg->d;
- else if (pg->y >= pg->x1 && pg->d > 0)
- pg->d = -pg->d;
- else
- pg->y += pg->d;
- }
- }
- }
- }
- pg = &guards[4];
- switch (state.level) { // Special level handling
- case 4: // Eugene's lair
- sprite(IMG_GUARD + 4, pg->x, pg->y, IMG_GUARD + 4);
- if (pg->y == pg->x0 && pg->d < 0)
- pg->d = 1;
- if (pg->y == pg->x1 && pg->d > 0)
- pg->d = -1;
- if (state.nitems == 0) { // all collected -> descend and camp
- if (pg->d == -1)
- pg->d = 1;
- if (pg->y == pg->x1)
- pg->d = 0;
- }
- pg->y += pg->d;
- break;
- case 7: // Miner Willy meets the Kong Beast
- case 11: // Return of the Alien Kong Beast
- byte frame, color;
- if (!state.switch2) {
- frame = (t >> 3) & 1;
- color = 8 + 4;
- } else {
- frame = 2 + ((t >> 1) & 1);
- color = 8 + 6;
- if (pg->y < 104) {
- pg->y += 4;
- bump_score(100);
- }
- }
- if (pg->y != 104) {
- loadspr16(IMG_GUARD + 4, state.guardian + (frame << 5), 255, color);
- sprite(IMG_GUARD + 4, pg->x, pg->y, IMG_GUARD + 4);
- } else {
- hide(IMG_GUARD + 4);
- }
- }
- // Animate conveyors:
- signed char convt = state.conveyordir ? t : -t;
- GD.__wstart(RAM_CHR + 4 * 16 + 2 * 0); // character 4 line 0
- unpack8(rol8(state.conveyor[0], convt));
- GD.__end();
- GD.__wstart(RAM_CHR + 4 * 16 + 2 * 3); // character 4 line 3
- unpack8(rol8(state.conveyor[0], -convt));
- GD.__end();
- // Solar rays
- if (state.level == 18) {
- // Draw new ray, turning 0x00 to 0x80
- byte sx = 23;
- byte sy = 0;
- byte sd = 0; // down
- byte ri; // ray index
- for (ri = 0; ri < MAXRAY; ri++) {
- uint16_t a = atxy(sx, sy);
- if (state.prevray[ri] != a) {
- GD.wr(state.prevray[ri], 0);
- if (GD.rd(a) != 0)
- break; // absorbed
- GD.wr(a, 0x80 + ' ');
- state.prevray[ri] = a;
- }
- for (byte i = 0; i < 8; i++)
- if (guards[i].a && (guards[i].x >> 3) == sx && (guards[i].y >> 3) == sy)
- sd ^= 1;
- if (sd == 0)
- sy++;
- else
- sx--;
- }
- while (ri < MAXRAY) {
- GD.wr(state.prevray[ri], 0);
- state.prevray[ri++] = 4095;
- }
- }
- // animate item colors starting at 16
- uint16_t colors[4] = { MAGENTA, YELLOW, CYAN, GREEN };
- for (byte i = 0; i < 5; i++)
- GD.wr16(RAM_SPRPAL + 2 * (i + 16), colors[(i + t) & 3]);
- // when all items collected,
- // flash portal in colors 32,33 - the thing the Spectrum *can* do in hardware
- byte flip = (t >> 3) & (CHEAT_OPEN_PORTAL || state.nitems == 0);
- GD.wr16(RAM_SPRPAL + 2 * (32 ^ flip), COLOR(PAPER(state.portalattr)));
- GD.wr16(RAM_SPRPAL + 2 * (33 ^ flip), COLOR(INK(state.portalattr)));
- // animated lives count, draw up to seven
- for (byte i = 0; i < 7; i++)
- if (i < (state.lives - 1))
- sprite(IMG_WILLYC + i, i << 4, 168, IMG_WILLYC + ((t >> 2) & 3));
- else
- hide(IMG_WILLYC + i);
- GD.waitvblank();
- }
- // play a complete game
- void loop()
- {
- title_screen();
- game_graphics();
- state.level = START_LEVEL;
- state.score = 0;
- for (state.lives = 3; state.lives; state.lives--) {
- loadlevel(&levels[state.level]);
- // state.wy = 16;
- byte alive = 1;
- byte t = 0;
- while (alive) {
- drive_level(t);
- // Willy draw
- byte frame = state.wf ^ (state.wd ? 7 : 0);
- loadspr16(IMG_WILLY, willy + (frame << 5), 255, 8 + 7);
- sprite(IMG_WILLY, state.wx, state.wy, IMG_WILLY);
- // Willy movement
- // See http://www.seasip.demon.co.uk/Jsw/manic.mac
- // and http://jetsetwilly.jodi.org/poke.html
- byte con = control();
- byte ychanged = 0; // if Y block changes must check for landing later
- if (state.jumping) {
- #define JUMP_APEX 9
- #define JUMP_FALL 11 // 1 2 3 4 5 6 7 8 9 10 11
- signed char moves[] = { -4, -4, -3, -3, -2, -2, -1, -1, 0, 0, 1, 1, 2, 2, 3, 3, 4 };
- byte index = min(sizeof(moves) - 1, state.jumping - 1);
- byte newy = state.wy + moves[index];
- state.jumping++;
- if (canbe(state.wx, newy)) {
- ychanged = (state.wy >> 3) != (newy >> 3);
- state.wy = newy;
- } else {
- state.jumping = max(state.jumping, JUMP_FALL); // halt ascent
- ychanged = 1;
- }
- }
- uint16_t feet_addr = atxy(state.wx >> 3, (state.wy + 16) >> 3);
- byte elem0 = GD.rd(feet_addr);
- byte elem1 = GD.rd(feet_addr + 1);
- byte elem = ((1 <= elem0) && (elem0 <= 31)) ? elem0 : elem1;
- byte dx = 0xff;
- byte first_jump = (con & CONTROL_JUMP) && state.lastdx == 0xff;
- if (state.jumping) {
- dx = state.lastdx;
- } else if (!first_jump && (elem == ELEM_CONVEYOR) && (state.wd == state.conveyordir)) {
- dx = state.conveyordir;
- } else {
- if (con & CONTROL_RIGHT) {
- if (state.wd != 0) {
- state.wf ^= 3;
- state.wd = 0;
- } else {
- dx = 0;
- }
- } else if (con & CONTROL_LEFT) {
- if (state.wd == 0) {
- state.wf ^= 3;
- state.wd = 1;
- } else {
- dx = 1;
- }
- }
- }
- if (dx != 0xff) {
- if (state.wf != 3)
- state.wf++;
- else {
- byte newx = state.wx + (dx ? -8 : 8);
- if (canbe(newx, state.wy)) {
- state.wf = 0;
- state.wx = newx;
- }
- }
- }
- state.lastdx = dx;
- if ((elem == ELEM_CONVEYOR) && (dx == 0xff) && !state.jumping)
- state.wd = state.conveyordir;
- if (!state.jumping && (con & CONTROL_JUMP)) {
- if (canbe(state.wx, state.wy - 3)) {
- state.jumping = 1;
- state.wy -= 0;
- }
- }
- byte onground = ((1 <= elem) && (elem <= 4)) || ((7 <= elem) && (elem <= 16));
- if (state.jumping) {
- if ((JUMP_APEX <= state.jumping) && ychanged && onground) {
- state.jumping = 0;
- state.wy &= ~7;
- }
- if (state.jumping >= 16) // stop x movement late in the jump
- state.lastdx = 0xff;
- } else {
- if (!onground) { // nothing to stand on, start falling
- state.jumping = JUMP_FALL;
- state.lastdx = 0xff;
- }
- }
- if (!state.jumping) {
- crumble(feet_addr);
- crumble(feet_addr + 1);
- }
- if (((t & 7) == 0) ||
- ((state.level == 18) && inray(state.wx, state.wy))) {
- state.air--;
- plot_air();
- if (state.air == 0) {
- alive = 0;
- }
- }
- GD.waitvblank(); // let the screen display, then check for collisions
- byte coll = GD.rd(COLLISION + IMG_WILLY);
- if (coll <= (IMG_ITEM + 4)) {
- bump_score(100);
- hide(coll - IMG_ITEM);
- --state.nitems;
- } else if (coll == IMG_PORTAL) {
- if (CHEAT_OPEN_PORTAL || state.nitems == 0) {
- while (state.air) {
- squarewave(0, 800 + 2 * state.air, 100);
- state.air--;
- plot_air();
- bump_score(7);
- GD.waitvblank();
- }
- state.level = (state.level + 1) % 18;
- loadlevel(&levels[state.level]);
- }
- } else if (coll == IMG_SWITCH1) {
- if (!state.switch1) {
- state.switch1 = 1;
- sprite(IMG_SWITCH1, 48 - 8, 0, IMG_SWITCH1, 2);
- GD.wr(atxy(17, 11), 0);
- GD.wr(atxy(17, 12), 0);
- }
- } else if (coll == IMG_SWITCH2) {
- if (coll == IMG_SWITCH2) {
- state.switch2 = 1;
- sprite(IMG_SWITCH2, 144 - 8, 0, IMG_SWITCH2, 2);
- GD.wr(atxy(15, 2), 0);
- GD.wr(atxy(16, 2), 0);
- }
- } else if (coll != 0xff && !CHEAT_INVINCIBLE) {
- alive = 0;
- }
- t++;
- }
- // player died
- clear_screen();
- }
- game_over();
- }
|