// reserve space for the HGR1 screen buffer #define CFGFILE apple2-hgr.cfg #pragma data-name(push,"HGR") #pragma data-name(pop) /* * An Apple ][ port of the Cosmic Impalas game * described in the book * "Making 8-bit Arcade Games in C" */ #include #include #include #include // type aliases for byte/signed byte/unsigned 16-bit typedef unsigned char byte; typedef signed char sbyte; typedef unsigned short word; // peeks, pokes, and strobes #define STROBE(addr) __asm__ ("sta %w", addr) // speaker click #define CLICK STROBE(0xc030) /// HIRES LOOKUP TABLE #define VHEIGHT 192 // number of scanlines #define VBWIDTH 40 // number of bytes per scanline #define PIXWIDTH 280 // 7 pixels per byte #define LUT(x) (byte*)(0x2000|x) // starting address of each scanline static byte* const vidmem[VHEIGHT] = { LUT(0x0000), LUT(0x0400), LUT(0x0800), LUT(0x0c00), LUT(0x1000), LUT(0x1400), LUT(0x1800), LUT(0x1c00), LUT(0x0080), LUT(0x0480), LUT(0x0880), LUT(0x0c80), LUT(0x1080), LUT(0x1480), LUT(0x1880), LUT(0x1c80), LUT(0x0100), LUT(0x0500), LUT(0x0900), LUT(0x0d00), LUT(0x1100), LUT(0x1500), LUT(0x1900), LUT(0x1d00), LUT(0x0180), LUT(0x0580), LUT(0x0980), LUT(0x0d80), LUT(0x1180), LUT(0x1580), LUT(0x1980), LUT(0x1d80), LUT(0x0200), LUT(0x0600), LUT(0x0a00), LUT(0x0e00), LUT(0x1200), LUT(0x1600), LUT(0x1a00), LUT(0x1e00), LUT(0x0280), LUT(0x0680), LUT(0x0a80), LUT(0x0e80), LUT(0x1280), LUT(0x1680), LUT(0x1a80), LUT(0x1e80), LUT(0x0300), LUT(0x0700), LUT(0x0b00), LUT(0x0f00), LUT(0x1300), LUT(0x1700), LUT(0x1b00), LUT(0x1f00), LUT(0x0380), LUT(0x0780), LUT(0x0b80), LUT(0x0f80), LUT(0x1380), LUT(0x1780), LUT(0x1b80), LUT(0x1f80), LUT(0x0028), LUT(0x0428), LUT(0x0828), LUT(0x0c28), LUT(0x1028), LUT(0x1428), LUT(0x1828), LUT(0x1c28), LUT(0x00a8), LUT(0x04a8), LUT(0x08a8), LUT(0x0ca8), LUT(0x10a8), LUT(0x14a8), LUT(0x18a8), LUT(0x1ca8), LUT(0x0128), LUT(0x0528), LUT(0x0928), LUT(0x0d28), LUT(0x1128), LUT(0x1528), LUT(0x1928), LUT(0x1d28), LUT(0x01a8), LUT(0x05a8), LUT(0x09a8), LUT(0x0da8), LUT(0x11a8), LUT(0x15a8), LUT(0x19a8), LUT(0x1da8), LUT(0x0228), LUT(0x0628), LUT(0x0a28), LUT(0x0e28), LUT(0x1228), LUT(0x1628), LUT(0x1a28), LUT(0x1e28), LUT(0x02a8), LUT(0x06a8), LUT(0x0aa8), LUT(0x0ea8), LUT(0x12a8), LUT(0x16a8), LUT(0x1aa8), LUT(0x1ea8), LUT(0x0328), LUT(0x0728), LUT(0x0b28), LUT(0x0f28), LUT(0x1328), LUT(0x1728), LUT(0x1b28), LUT(0x1f28), LUT(0x03a8), LUT(0x07a8), LUT(0x0ba8), LUT(0x0fa8), LUT(0x13a8), LUT(0x17a8), LUT(0x1ba8), LUT(0x1fa8), LUT(0x0050), LUT(0x0450), LUT(0x0850), LUT(0x0c50), LUT(0x1050), LUT(0x1450), LUT(0x1850), LUT(0x1c50), LUT(0x00d0), LUT(0x04d0), LUT(0x08d0), LUT(0x0cd0), LUT(0x10d0), LUT(0x14d0), LUT(0x18d0), LUT(0x1cd0), LUT(0x0150), LUT(0x0550), LUT(0x0950), LUT(0x0d50), LUT(0x1150), LUT(0x1550), LUT(0x1950), LUT(0x1d50), LUT(0x01d0), LUT(0x05d0), LUT(0x09d0), LUT(0x0dd0), LUT(0x11d0), LUT(0x15d0), LUT(0x19d0), LUT(0x1dd0), LUT(0x0250), LUT(0x0650), LUT(0x0a50), LUT(0x0e50), LUT(0x1250), LUT(0x1650), LUT(0x1a50), LUT(0x1e50), LUT(0x02d0), LUT(0x06d0), LUT(0x0ad0), LUT(0x0ed0), LUT(0x12d0), LUT(0x16d0), LUT(0x1ad0), LUT(0x1ed0), LUT(0x0350), LUT(0x0750), LUT(0x0b50), LUT(0x0f50), LUT(0x1350), LUT(0x1750), LUT(0x1b50), LUT(0x1f50), LUT(0x03d0), LUT(0x07d0), LUT(0x0bd0), LUT(0x0fd0), LUT(0x13d0), LUT(0x17d0), LUT(0x1bd0), LUT(0x1fd0) }; // divide-by 7 table const byte DIV7[256] = { 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9,10,10,10,10,10,10,10,11,11,11,11,11,11,11,12,12,12,12,12,12,12,13,13,13,13,13, 13,13,14,14,14,14,14,14,14,15,15,15,15,15,15,15,16,16,16,16,16,16,16,17,17,17,17,17,17,17,18,18, 18,18,18,18,18,19,19,19,19,19,19,19,20,20,20,20,20,20,20,21,21,21,21,21,21,21,22,22,22,22,22,22, 22,23,23,23,23,23,23,23,24,24,24,24,24,24,24,25,25,25,25,25,25,25,26,26,26,26,26,26,26,27,27,27, 27,27,27,27,28,28,28,28,28,28,28,29,29,29,29,29,29,29,30,30,30,30,30,30,30,31,31,31,31,31,31,31, 32,32,32,32,32,32,32,33,33,33,33,33,33,33,34,34,34,34,34,34,34,35,35,35,35,35,35,35,36,36,36,36}; // modulo-by-7 table const byte MOD7[256] = { 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3}; // bitmask table const byte BIT7[7] = { 1, 2, 4, 8, 16, 32, 64 }; /// SOUND FUNCTIONS void tone(byte period, byte dur, sbyte mod) { word i; while (dur--) { for (i=0; i> (7-xs); // save leftover bits } // compute final byte operation switch (op) { case OP_DRAW: *dest = rest; break; case OP_XOR: result |= (*dest ^= rest) & rest; break; case OP_ERASE: *dest &= ~rest; break; } } return result; } void draw_sprite(const byte* src, byte x, byte y) { render_sprite(src, x, y, OP_DRAW); } // XOR returns non-zero if any pixels were overlapped byte xor_sprite(const byte* src, byte x, byte y) { return render_sprite(src, x, y, OP_XOR); } void erase_sprite(const byte* src, byte x, byte y) { render_sprite(src, x, y, OP_ERASE); } // clear just sets all bytes to 0, and is fast void clear_sprite(const byte* src, byte x, byte y) { byte i,j; byte w = *src++; byte h = *src++; byte xb = DIV7[x]; for (j=0; j>= 4; } } // add two 4-digit BCD words word bcd_add(word a, word b) { word result; __asm__ ("sed"); // set decimal (BCD) mode result = a+b; __asm__ ("cld"); // clear BCD mode return result; } // // GAME GRAPHICS // const byte player_bitmap[] = {3,16,/*{w:24,h:16,bpp:1}*/0x00,0x00,0x00,0x00,0x18,0x00,0x00,0x3C,0x00,0x04,0x18,0x20,0x04,0x18,0x20,0x0C,0x3C,0x30,0x3C,0x3C,0x3C,0xCC,0xE7,0x33,0x0C,0x66,0x30,0xC4,0xE7,0x23,0x34,0xE7,0x2C,0x0C,0x7E,0x30,0xCC,0xFF,0x33,0x3C,0x18,0x3C,0x0C,0x18,0x30,0x04,0x18,0x20}; const byte bomb_bitmap[] = {1,5,/*{w:8,h:5,bpp:1}*/0x88,0x55,0x77,0x55,0x88}; const byte bullet_bitmap[] = {1,5,/*{w:8,h:5,bpp:1}*/0x14,0x28,0x14,0x14,0x28}; const byte enemy1_bitmap[] = {2,16,/*{w:16,h:16,bpp: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,16,/*{w:16,h:16,bpp:1}*/0x00,0x00,0x30,0x0C,0x10,0x08,0x3C,0x3D,0x2C,0x35,0x34,0x2D,0x24,0x25,0x50,0x15,0x28,0x0A,0x30,0x0D,0x28,0x15,0x4A,0x50,0x2A,0x55,0x22,0x05,0x0A,0x14,0x0A,0x50}; const byte enemy3_bitmap[] = {2,16,/*{w:16,h:16,bpp: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,16,/*{w:16,h:16,bpp: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 explosion_bitmap[] = {2,16,/*{w:16,h:16,bpp:1}*/0x40,0x00,0x02,0x44,0x08,0x20,0x40,0x02,0x28,0x14,0x54,0x2A,0x2A,0x54,0x55,0xAA,0x2A,0x54,0x55,0xAA,0x2A,0x54,0x54,0x2A,0x28,0x14,0x40,0x22,0x04,0x40,0x42,0x04}; const byte* const enemy_bitmaps[4] = { enemy1_bitmap, enemy2_bitmap, enemy3_bitmap, enemy4_bitmap }; // // GAME CODE // byte attract; word score; byte lives; #define MAXLIVES 5 byte player_x; byte bullet_x; byte bullet_y; byte bomb_x; byte bomb_y; 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; void draw_lives() { byte i; byte n = lives; byte y = 18; byte x = VBWIDTH-1; for (i=0; i j) { CLICK; } } } } void destroy_player() { xor_player_derez(); // xor derez pattern xor_sprite(player_bitmap, player_x, 192-17); // erase ship via xor xor_player_derez(); // xor 2x to erase derez pattern player_x = 0xff; lives--; } void init_enemies() { byte i,x,y,bm; x=0; y=10; bm=0; for (i=0; ix = x; e->y = y; e->shape = enemy_bitmaps[bm]; x += 29; if (x >= 200) { x = 0; y += 20; bm++; } } 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) { byte x = e->x; byte y = e->y; clear_sprite(e->shape, x, y); xor_sprite(explosion_bitmap, x, y); memmove(e, e+1, sizeof(Enemy)*(enemies-e+MAX_ENEMIES-1)); num_enemies--; // update_next_enemy() will check enemy_index tone(1,20,10); xor_sprite(explosion_bitmap, x, y); } void update_next_enemy() { Enemy* e; if (enemy_index >= num_enemies) { enemy_index = 0; memcpy(&this_mode, &next_mode, sizeof(this_mode)); tone(220+num_enemies,3,1); } e = &enemies[enemy_index]; clear_sprite(e->shape, e->x, e->y); if (this_mode.down) { e->y += 4; // if too close to ground, end game if (e->y > 170) { destroy_player(); lives = 0; } next_mode.down = 0; } else { if (this_mode.right) { e->x += 2; if (e->x >= 255-16) { next_mode.down = 1; next_mode.right = 0; } } else { e->x -= 2; if (e->x == 0) { next_mode.down = 1; next_mode.right = 1; } } } draw_sprite(e->shape, e->x, e->y); 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; xor_sprite(bomb_bitmap, bomb_x, bomb_y); } void move_bomb() { byte leftover = xor_sprite(bomb_bitmap, bomb_x, bomb_y); // erase if (bomb_y > 192-12) { bomb_y = 0; } else if (leftover & 0x7f) { // don't count hi bit erase_sprite(bomb_bitmap, bomb_x, bomb_y); // erase bunker if (bomb_y > 192-23) { // player was hit (probably) destroy_player(); } bomb_y = 0; } else { bomb_y += 3; xor_sprite(bomb_bitmap, bomb_x, bomb_y); } } byte frame; signed char player_dir = 0; void move_player() { if (attract) { if (bullet_y == 0) fire_bullet(); player_dir = 0; } else { char key; // handle keyboard if (kbhit()) { key = cgetc(); switch (key) { case 8: // left arrow player_dir = player_dir < 0 ? 0 : -2; break; case 21: // right arrow player_dir = player_dir > 0 ? 0 : 2; break; case ' ': // space if (bullet_y == 0) { fire_bullet(); } break; } } } // move player if (player_dir < 0 && player_x > 0) player_x += player_dir; else if (player_dir > 0 && player_x < 255-28) player_x += player_dir; draw_sprite(player_bitmap, player_x, 192-17); } void play_round() { draw_playfield(); player_x = VHEIGHT/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=11; byte y=10; for (i=0; i<50; i++) { draw_string(" *************** ", x, y+0, 0); draw_string("*** ***", x, y+1, 0); draw_string("** GAME OVER **", x, y+2, 0); draw_string("*** ***", x, y+3, 0); draw_string(" *************** ", x, y+4, 0); } } 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 main() { while (1) { play_game(); } }