/* * An Astrocade port of the Cosmic Impalas game * described in the book * "Making 8-bit Arcade Games in C" */ //#link "acheader.s" #include #define EXIT_CLIPY(y) if (((unsigned char)y)>=VHEIGHT) return #define EXIT_CLIPDEST(addr) if ((((word)addr)&0xfff) >= 0xe10) return typedef unsigned char byte; typedef signed char sbyte; typedef unsigned short word; /// HARDWARE __sfr __at(0x00) hw_col0r; // palette 0 __sfr __at(0x01) hw_col1r; __sfr __at(0x02) hw_col2r; __sfr __at(0x03) hw_col3r; __sfr __at(0x04) hw_col0l; __sfr __at(0x05) hw_col1l; __sfr __at(0x06) hw_col2l; __sfr __at(0x07) hw_col3l; // palette 7 __sfr __at(0x09) hw_horcb; // horiz color boundary __sfr __at(0x0a) hw_verbl; // vertical blanking line * 2 __sfr __at(0x0c) hw_magic; // magic register __sfr __at(0x19) hw_xpand; // expander register __sfr __at(0x08) hw_intst; // intercept test feedback __sfr __at(0x10) hw_p1ctrl; // player controls __sfr __at(0x11) hw_p2ctrl; // player controls __sfr __at(0x12) hw_p3ctrl; // player controls __sfr __at(0x13) hw_p4ctrl; // player controls #define M_SHIFT0 0x00 #define M_SHIFT1 0x01 #define M_SHIFT2 0x02 #define M_SHIFT3 0x03 #define M_XPAND 0x08 #define M_MOVE 0x00 #define M_OR 0x10 #define M_XOR 0x20 #define M_FLOP 0x40 #define M_SHIFT(x) ((x)&3) #define XPAND_COLORS(off,on) (((off)&3) | (((on)&3)<<2)) /// GRAPHICS FUNCTIONS #define VHEIGHT 89 // number of scanlines #define VBWIDTH 40 // number of bytes per scanline #define PIXWIDTH 160 // 4 pixels per byte byte __at (0x0000) vmagic[VHEIGHT][VBWIDTH]; byte __at (0x4000) vidmem[VHEIGHT][VBWIDTH]; // clear screen and set graphics mode void clrscr() { memset(vidmem, 0, VHEIGHT*VBWIDTH); // clear page 1 } // draw vertical line void vline(byte x, byte y1, byte y2, byte col, byte op) { byte xb = x>>2; // divide x by 4 byte* dest = &vmagic[y1][xb]; // destination address byte y; hw_magic = M_SHIFT(x) | op; // set magic register col <<= 6; // put color in high pixel for (y=y1; y<=y2; y++) { EXIT_CLIPDEST(dest); *dest = col; // shift + xor color dest += VBWIDTH; // dest address to next scanline } } // draw a pixel void pixel(byte x, byte y, byte col, byte op) { vline(x, y, y, col, op); // draw line with 1-pixel height } // render a sprite with the given graphics operation void render_sprite(const byte* src, byte x, byte y, byte op) { byte i,j; byte w = *src++; // get width from 1st byte of sprite byte h = *src++; // get height from 2nd byte of sprite byte xb = x>>2; // divide x by 4 byte* dest = &vmagic[y][xb]; // destination address hw_magic = M_SHIFT(x) | op; // set magic register for (j=0; j>2; // divide x by 4 byte* dest = &vidmem[y][xb]; // destination address for (j=0; j>2; // divide x by 4 byte* dest = &vmagic[y][xb]; // destination address hw_magic = M_SHIFT(x) | M_XPAND | op; for (byte i=0; i<8; i++) { char b = *src++; EXIT_CLIPDEST(dest); *dest++ = b; // expand lower nibble -> 1st byte *dest++ = b; // expand upper nibble -> 2nd byte *dest++ = 0; // leftover -> 3rd byte *dest = 0; // reset upper/lower flag dest += VBWIDTH-3; // we incremented 3 bytes for this line } } void draw_string(const char* str, byte x, byte y) { do { byte ch = *str++; if (!ch) break; draw_char(ch, x, y, M_MOVE); x += 8; } while (1); } void draw_bcd_word(word bcd, byte x, byte y, byte op) { byte j; x += 3*8; for (j=0; j<4; j++) { draw_char('0'+(bcd&0xf), x, y, op); x -= 8; bcd >>= 4; } } // add two 16-bit BCD values word bcd_add(word a, word b) { a; b; // to avoid warning __asm ld hl,#4 add hl,sp ld iy,#2 add iy,sp ld a,0 (iy) add a, (hl) daa ld c,a ld a,1 (iy) inc hl adc a, (hl) daa ld b,a ld l, c ld h, b __endasm; } ///// // // GAME GRAPHICS // const byte player_bitmap[] = {3,14,/*{w:12,h:16,bpp:2,brev:1}*/0x00,0x3C,0x00,0x00,0x18,0x00,0x00,0x3C,0x00,0x00,0x18,0x00,0x04,0x18,0x20,0x0C,0x3C,0x30,0x3C,0x3C,0x3C,0x1F,0xE7,0xF4,0x1F,0x66,0xF4,0x17,0xE7,0xE4,0x17,0xE7,0xE4,0x1C,0x7E,0x34,0x1C,0xFF,0x34,0x3C,0x18,0x3C,0x0C,0x18,0x30,0x04,0x18,0x20}; const byte bomb_bitmap[] = {1,5,/*{w:8,h:5,bpp:2,brev:1}*/0x88,0x55,0x77,0x55,0x88}; const byte bullet_bitmap[] = {1,5,/*{w:8,h:5,bpp:2,brev:1}*/0x14,0x28,0x14,0x14,0x28}; const byte enemy1_bitmap[] = {2,8,/*{w:16,h:8,bpp:2,brev:1}*/0x00,0x00,0x70,0x38,0xF8,0x7C,0xFC,0xFC,0xFE,0xFC,0xFE,0xFF,0xFC,0xFF,0xF8,0x7F,0xF0,0x3F,0x88,0x47,0xF0,0x3F,0xF0,0x3F,0xD0,0x2F,0x8C,0xC7,0x48,0x48,0x80,0x04}; const byte enemy2_bitmap[] = {2,8,/*{w:16,h:8,bpp:2,brev:1}*/0x00,0x00,0x30,0x0C,0x14,0x28,0x2E,0x74,0x08,0x10,0x20,0x04,0xE0,0x07,0xD0,0x0B,0xB0,0x0D,0xB2,0x4D,0x19,0x98,0x8E,0x71,0x82,0x41,0xB1,0x8D,0x59,0x9A,0x4A,0x52}; const byte enemy3_bitmap[] = {2,8,/*{w:16,h:8,bpp:2,brev:1}*/0x00,0x00,0x00,0x00,0x04,0x20,0x05,0xA0,0x05,0xA0,0x25,0xA4,0xA7,0xE5,0xF7,0xEF,0xF7,0xEF,0xFE,0x7F,0xFC,0x3F,0xBC,0x3D,0xE4,0x27,0x20,0x00,0x00,0x00,0x00,0x00}; const byte enemy4_bitmap[] = {2,8,/*{w:16,h:8,bpp:2,brev:1}*/0x00,0x00,0x00,0x00,0xF0,0x0F,0xF8,0x1F,0xD8,0x1B,0xF8,0x1F,0xF8,0x1F,0xF8,0x1F,0xF0,0x0F,0xA8,0x15,0xCC,0x33,0xE8,0x17,0x66,0x66,0x33,0xCC,0x61,0x86,0x40,0x02}; const byte* const enemy_bitmaps[4] = { enemy1_bitmap, enemy2_bitmap, enemy3_bitmap, enemy4_bitmap }; #define COLOR_BUNKER 1 #define COLOR_GROUND 2 #define COLOR_SCORE 3 // // GAME CODE // #define MAXLIVES 5 #define PLYRHEIGHT 14 #define ENEMY_SPACING_X 17 #define ENEMY_SPACING_Y 11 #define ENEMY_MARCH_X 1 #define ENEMY_MARCH_Y 2 typedef struct { byte x,y; const byte* shape; // need const here } Enemy; #define MAX_ENEMIES 28 Enemy enemies[MAX_ENEMIES]; byte enemy_index; byte num_enemies; typedef struct { int right:1; int down:1; } MarchMode; MarchMode this_mode, next_mode; byte attract; word score; byte lives; const byte player_y = VHEIGHT-PLYRHEIGHT-1; byte player_x; byte bullet_x; byte bullet_y; byte bomb_x; byte bomb_y; void draw_lives() { byte i; byte n = lives; byte y = 0; byte x = PIXWIDTH-4*5; hw_xpand = XPAND_COLORS(0, COLOR_SCORE); for (i=0; ix = x; e->y = y; e->shape = enemy_bitmaps[bm]; x += ENEMY_SPACING_X; if (x >= PIXWIDTH-ENEMY_SPACING_X*2) { x = 0; y += ENEMY_SPACING_Y; bm++; // TODO: can overflow } } enemy_index = 0; num_enemies = MAX_ENEMIES; this_mode.right = 1; this_mode.down = 0; next_mode.right = 1; next_mode.down = 0; } void delete_enemy(Enemy* e) { erase_sprite(e->shape, e->x, e->y); memmove(e, e+1, sizeof(Enemy)*(enemies-e+MAX_ENEMIES-1)); num_enemies--; // update_next_enemy() will check enemy_index } void update_next_enemy() { Enemy* e; if (enemy_index >= num_enemies) { enemy_index = 0; memcpy(&this_mode, &next_mode, sizeof(this_mode)); } e = &enemies[enemy_index]; erase_sprite(e->shape, e->x, e->y); if (this_mode.down) { e->y += ENEMY_MARCH_Y; // if too close to ground, end game if (e->y > VHEIGHT-ENEMY_SPACING_Y) { destroy_player(); lives = 0; } next_mode.down = 0; } else { if (this_mode.right) { e->x += ENEMY_MARCH_X; if (e->x >= PIXWIDTH-ENEMY_SPACING_X) { next_mode.down = 1; next_mode.right = 0; } } else { e->x -= ENEMY_MARCH_X; if (e->x == 0) { next_mode.down = 1; next_mode.right = 1; } } } render_sprite(e->shape, e->x, e->y, M_XOR); enemy_index++; } char in_rect(Enemy* e, byte x, byte y, byte w, byte h) { byte ew = e->shape[0]*8; byte eh = e->shape[1]; return (x >= e->x-w && x <= e->x+ew && y >= e->y-h && y <= e->y+eh); } Enemy* find_enemy_at(byte x, byte y) { byte i; for (i=0; ix + 7; bomb_y = e->y + 16; render_sprite(bomb_bitmap, bomb_x, bomb_y, M_XOR); } void move_bomb() { hw_intst; // reset intercept counters render_sprite(bomb_bitmap, bomb_x, bomb_y, M_XOR); // erase if (bomb_y > VHEIGHT-12) { bomb_y = 0; } else if (hw_intst & 0xf0) { // any pixels leftover? erase_sprite(bomb_bitmap, bomb_x, bomb_y); // erase bunker if (bomb_y > VHEIGHT-23) { // player was hit (probably) destroy_player(); } bomb_y = 0; } else { bomb_y += 3; render_sprite(bomb_bitmap, bomb_x, bomb_y, M_XOR); } } byte frame; signed char player_dir = 0; void move_player() { if (attract) { if (bullet_y == 0) fire_bullet(); player_dir = 0; } else { byte mask = hw_p1ctrl; if (mask & 0x4) { if (player_x > 0) player_x--; } if (mask & 0x8) { if (player_x < PIXWIDTH-16) player_x++; } if (mask & 0x10) { if (bullet_y == 0) { fire_bullet(); } } } // move player render_sprite(player_bitmap, player_x, player_y, M_MOVE); } void play_round() { draw_playfield(); player_x = PIXWIDTH/2-8; bullet_y = 0; bomb_y = 0; frame = 0; while (player_x != 0xff && num_enemies) { move_player(); if (bullet_y) { move_bullet(); } update_next_enemy(); if (frame & 1) { if (bomb_y == 0) { drop_bomb(); } else { move_bomb(); } } frame++; } } void init_game() { score = 0; lives = 5; } void game_over_msg() { byte i; byte x=16; byte y=10; hw_xpand = XPAND_COLORS(0, COLOR_SCORE); for (i=0; i<50; i++) { draw_string(" *************** ", x, y+0*8); draw_string("*** ***", x, y+1*8); draw_string("** GAME OVER **", x, y+2*8); draw_string("*** ***", x, y+3*8); draw_string(" *************** ", x, y+4*8); } } void play_game() { attract = 0; init_game(); init_enemies(); while (lives) { play_round(); if (num_enemies == 0) { init_enemies(); } } game_over_msg(); } void attract_mode() { attract = 1; while (1) { init_enemies(); play_round(); } } void setup_registers() { hw_col0r = 0x00; hw_col1r = 0x20; hw_col2r = 0xe0; hw_col3r = 0xa0; hw_col0l = 0x10; hw_col1l = 0x30; hw_col2l = 0xc0; hw_col3l = 0xf0; hw_horcb = 0; hw_verbl = VHEIGHT*2; } void main() { setup_registers(); // NOTE: initializers don't get run, so we init here while (1) { //attract_mode(); play_game(); } }