#include #include #include "cv.h" #include "cvu.h" /* VRAM map 0x0000 - 0x17ff character pattern table 0x1800 - 0x1aff image table 0x2000 - 0x37ff color table 0x3800 - 0x3bff sprite pattern table 0x3c00 - 0x3fff sprite attribute table */ const cv_vmemp PATTERN = 0x0000; const cv_vmemp IMAGE = 0x1800; const cv_vmemp COLOR = 0x2000; const cv_vmemp SPRITE_PATTERNS = 0x3800; const cv_vmemp SPRITES = 0x3c00; #define COLS 32 #define ROWS 24 #define NSPRITES 16 #define NMISSILES 8 #define YOFFSCREEN 239 typedef unsigned char byte; typedef signed char sbyte; typedef unsigned short word; uintptr_t __at(0x6a) font_bitmap_a; uintptr_t __at(0x6c) font_bitmap_0; volatile bool vint; volatile uint_fast8_t vint_counter; void vint_handler(void) { vint = true; vint_counter++; } static byte pattern_table[8*2] = { /*{w:16,h:8,remap:[3,0,1,2]}*/ 0xCC, 0xF2, 0xD0, 0xFC, 0xF3, 0xE8, 0xC4, 0x03, 0x0C, 0x13, 0x02, 0x0F, 0x33, 0x05, 0x08, 0x30, }; static byte sprite_table[][16*2] = { /*{w:16,h:16,remap:[-5,0,1,2,3,5,6,7,8,9],count:15}*/ { 0x01, 0x03, 0x02, 0x01, 0x01, 0x01, 0x01, 0x01, 0x03, 0x86, 0xCD, 0xBE, 0x9F, 0xB1, 0xC0, 0x80, 0x80, 0xC0, 0x40, 0x80, 0x80, 0x80, 0x80, 0x80, 0xC0, 0x61, 0xB3, 0x7D, 0xF9, 0x8D, 0x03, 0x01, },{ 0x00, 0x00, 0x01, 0x03, 0x05, 0x01, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x80, 0xC0, 0xE0, 0xD0, 0xC0, 0xC0, 0x80, 0xA0, 0x80, 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, },{ 0x00, 0x00, 0x02, 0x03, 0x01, 0x01, 0x01, 0x01, 0x03, 0x07, 0x07, 0x03, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 0x40, 0xC0, 0x80, 0x80, 0x80, 0x80, 0xC0, 0xE0, 0xE0, 0xC0, 0xC0, 0x80, 0x00, 0x00, },{ 0x00, 0x00, 0x00, 0x04, 0x08, 0x11, 0x04, 0x05, 0x15, 0x24, 0x02, 0x00, 0x08, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0xC4, 0x22, 0x08, 0xE8, 0xEA, 0xC9, 0x10, 0x00, 0xC4, 0x03, 0x00, 0x00, },{ 0x00, 0x00, 0x08, 0x30, 0x01, 0x00, 0x08, 0x0A, 0x00, 0x09, 0x04, 0x00, 0x10, 0x00, 0x40, 0x00, 0x00, 0x00, 0x04, 0xC3, 0x20, 0x00, 0x04, 0x14, 0x00, 0x24, 0x08, 0x00, 0x04, 0xC0, 0x01, 0x00, },{ 0x04, 0x10, 0x00, 0x22, 0x00, 0x00, 0x44, 0x02, 0x00, 0x40, 0x02, 0x24, 0x00, 0x00, 0x48, 0x01, 0x08, 0x42, 0x00, 0x11, 0x00, 0x80, 0x91, 0x20, 0x00, 0x00, 0x21, 0x90, 0x82, 0x00, 0x08, 0x21, },{ // enemy ship rotations 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x13, 0x02, 0x0F, 0x33, 0x05, 0x08, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xCC, 0xF2, 0xD0, 0xFC, 0xF3, 0xE8, 0xC4, 0x03, 0x00, 0x00, 0x00, },{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x1F, 0x12, 0x13, 0x03, 0x02, 0x04, 0x18, 0x00, 0x00, 0x00, 0x00, 0x70, 0x42, 0x4C, 0xF0, 0xBF, 0xF0, 0xF8, 0xF8, 0xF8, 0xF0, 0x80, 0x80, 0x80, 0x00, },{ 0x00, 0x02, 0x04, 0x04, 0x03, 0x02, 0x07, 0x07, 0x02, 0x03, 0x04, 0x04, 0x02, 0x00, 0x00, 0x00, 0x48, 0x48, 0x90, 0xA0, 0xC0, 0xE0, 0xF0, 0xF0, 0xE0, 0xC0, 0xA0, 0x90, 0x48, 0x48, 0x00, 0x00, },{ 0x02, 0x12, 0x0A, 0x0A, 0x27, 0x27, 0x3D, 0x07, 0x07, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xF0, 0xF0, 0xF0, 0xFE, 0x60, 0xF0, 0x08, 0x04, 0xC4, 0x00, 0x00, 0x00, },{ 0x00, 0x00, 0x00, 0xC0, 0x23, 0x17, 0xCF, 0x3F, 0x0B, 0x4F, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x10, 0xA0, 0xCC, 0xF0, 0x40, 0xC8, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, },{ 0x00, 0x01, 0x01, 0x01, 0x0F, 0x1F, 0x1F, 0x1F, 0x0F, 0xFD, 0x0F, 0x32, 0x42, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x18, 0x20, 0x40, 0xC0, 0xC8, 0x48, 0xF8, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, },{ 0x00, 0x00, 0x12, 0x12, 0x09, 0x05, 0x03, 0x07, 0x0F, 0x0F, 0x07, 0x03, 0x05, 0x09, 0x12, 0x12, 0x00, 0x00, 0x00, 0x40, 0x20, 0x20, 0xC0, 0x40, 0xE0, 0xE0, 0x40, 0xC0, 0x20, 0x20, 0x40, 0x00, },{ 0x00, 0x00, 0x00, 0x23, 0x20, 0x10, 0x0F, 0x06, 0x7F, 0x0F, 0x0F, 0x0F, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0xE0, 0xE0, 0xBC, 0xE4, 0xE4, 0x50, 0x50, 0x48, 0x40, },{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x13, 0x02, 0x0F, 0x33, 0x05, 0x08, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xCC, 0xF2, 0xD0, 0xFC, 0xF3, 0xE8, 0xC4, 0x03, 0x00, 0x00, 0x00, } }; #define SPRI_SHIP (4*0) #define SPRI_MISSILE (4*1) #define SPRI_BOMB (4*2) #define SPRI_EXPLODE (4*3) #define SPRI_ENEMY (4*6) #define COLOR_FORMATION CV_COLOR_LIGHT_GREEN #define COLOR_ATTACKER CV_COLOR_LIGHT_RED #define COLOR_PLAYER CV_COLOR_LIGHT_YELLOW #define COLOR_MISSILE CV_COLOR_WHITE #define COLOR_SCORE CV_COLOR_LIGHT_BLUE #define COLOR_EXPLOSION CV_COLOR_RED void set_shifted_pattern(const byte* src, word dest, byte shift) { byte y; for (y=0; y<8; y++) { byte a = src[y+8]; byte b = src[y]; cvu_voutb(a>>shift, dest); cvu_voutb(b>>shift | a<<(8-shift), dest+8); cvu_voutb(b<<(8-shift), dest+16); dest++; } } /* PATTERN TABLE: 0-95 6x8 font, starting at ' ' 67-82 shifted enemy sprites */ void setup_32_column_font() { byte i; cv_set_image_table(IMAGE); // cvu_vmemset(PATTERN, 0, 8); cvu_memtovmemcpy(PATTERN, (void *)(font_bitmap_0 - 16*8), 96*8); cvu_memtovmemcpy(SPRITE_PATTERNS, sprite_table, sizeof(sprite_table)); cv_set_character_pattern_t(PATTERN); cv_set_screen_mode(CV_SCREENMODE_STANDARD); cv_set_color_table(COLOR); cvu_vmemset(COLOR, COLOR_SCORE<<4, 8); // set color for chars 0-63 cvu_vmemset(COLOR+8, COLOR_FORMATION<<4, 32-8); // set chars 63-255 cv_set_sprite_pattern_table(SPRITE_PATTERNS); cv_set_sprite_attribute_table(SPRITES); cv_set_sprite_big(true); for (i=0; i<8; i++) set_shifted_pattern(pattern_table, PATTERN+67*8+i*3*8, i); } #define LOCHAR 0x20 #define HICHAR 0xff #define CHAR(ch) (ch-LOCHAR) #define BLANK 0 void clrscr() { cvu_vmemset(IMAGE, CHAR(' '), COLS*ROWS); } byte getchar(byte x, byte y) { return cvu_vinb(IMAGE + y*COLS + x); } void putchar(byte x, byte y, byte attr) { cvu_voutb(attr, IMAGE + y*COLS + x); } void putstring(byte x, byte y, const char* string) { while (*string) { putchar(x++, y, CHAR(*string++)); } } void wait_vsync() { vint = false; while (!vint) ; } void delay(byte i) { while (i--) { wait_vsync(); } } void memset_safe(void* _dest, char ch, word size) { byte* dest = _dest; while (size--) { *dest++ = ch; } } char in_rect(byte x, byte y, byte x0, byte y0, byte w, byte h) { return ((byte)(x-x0) < w && (byte)(y-y0) < h); // unsigned } void draw_bcd_word(byte x, byte y, word bcd) { byte j; x += 3; for (j=0; j<4; j++) { putchar(x, y, CHAR('0'+(bcd&0xf))); x--; 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 CODE typedef struct { byte shape; } FormationEnemy; // should be power of 2 length typedef struct { byte findex; byte shape; word x; word y; byte dir; byte returning; } AttackingEnemy; typedef struct { signed char dx; byte xpos; signed char dy; byte ypos; } Missile; #define ENEMIES_PER_ROW 8 #define ENEMY_ROWS 4 #define MAX_IN_FORMATION (ENEMIES_PER_ROW*ENEMY_ROWS) #define MAX_ATTACKERS 6 FormationEnemy formation[MAX_IN_FORMATION]; AttackingEnemy attackers[MAX_ATTACKERS]; Missile missiles[NMISSILES]; struct cvu_sprite vsprites[NSPRITES]; byte formation_offset_x; signed char formation_direction; byte current_row; byte player_x; const byte player_y = 168; byte player_exploding; byte enemy_exploding; byte enemies_left; word player_score; word framecount; void copy_sprites() { byte i; word ofs; cvu_memtovmemcpy(SPRITES, vsprites, sizeof(vsprites)); // copy all "shadow missiles" to video memory ofs = SPRITES + NSPRITES*4; for (i=0; ifindex) { vsprites[i].name = SPRI_ENEMY + DIR_TO_CODE[a->dir & 31]; // TODO: code + a->shape + 14; vsprites[i].x = a->x >> 8; vsprites[i].y = a->y >> 8; vsprites[i].tag = COLOR_ATTACKER; } else { vsprites[i].y = YOFFSCREEN; } } void draw_attackers() { byte i; for (i=0; ifindex-1; byte destx = get_attacker_x(fi); byte desty = get_attacker_y(fi); byte ydist = desty - (a->y >> 8); // are we close to our formation slot? if (ydist == 0) { // convert back to formation enemy formation[fi].shape = a->shape; a->findex = 0; } else { a->dir = (ydist + 16) & 31; a->x = destx << 8; a->y += 128; } } void fly_attacker(AttackingEnemy* a) { a->x += isin(a->dir) * 2; a->y += icos(a->dir) * 2; if ((a->y >> 8) == 0) { a->returning = 1; } } void move_attackers() { byte i; for (i=0; ifindex) { if (a->returning) return_attacker(a); else fly_attacker(a); } } } void think_attackers() { byte i; for (i=0; ifindex) { // rotate? byte x = a->x >> 8; byte y = a->y >> 8; // don't shoot missiles after player exploded if (y < 112 || player_exploding) { if (x < 128) { a->dir++; } else { a->dir--; } } else { // lower half of screen // shoot a missile? if (missiles[i].ypos == YOFFSCREEN) { missiles[i].ypos = y+16; missiles[i].xpos = x; missiles[i].dy = 2; } } } } } void formation_to_attacker(byte formation_index) { byte i; // out of bounds? return if (formation_index >= MAX_IN_FORMATION) return; // nobody in formation? return if (!formation[formation_index].shape) return; // find an empty attacker slot for (i=0; ifindex == 0) { a->x = get_attacker_x(formation_index) << 8; a->y = get_attacker_y(formation_index) << 8; a->shape = formation[formation_index].shape; a->findex = formation_index+1; a->dir = 0; a->returning = 0; formation[formation_index].shape = 0; break; } } } void draw_player() { vsprites[7].x = player_x; vsprites[7].y = player_y; vsprites[7].name = SPRI_SHIP; vsprites[7].tag = COLOR_PLAYER; } void move_player() { struct cv_controller_state state; cv_get_controller_state(&state, 0); // move left/right? if ((state.joystick & CV_LEFT) && player_x > 16) player_x--; if ((state.joystick & CV_RIGHT) && player_x < 224) player_x++; // shoot missile? if ((state.joystick & CV_FIRE_0) && missiles[7].ypos == YOFFSCREEN) { missiles[7].ypos = player_y-8; // must be multiple of missile speed missiles[7].xpos = player_x; // player X position missiles[7].dy = -4; // player missile speed } vsprites[7].x = player_x; } void move_missiles() { byte i; for (i=0; i<8; i++) { if (missiles[i].ypos != YOFFSCREEN) { // hit the bottom or top? if ((byte)(missiles[i].ypos += missiles[i].dy) > YOFFSCREEN) { missiles[i].ypos = YOFFSCREEN; } } } } void blowup_at(byte x, byte y) { vsprites[6].tag = COLOR_EXPLOSION; vsprites[6].name = SPRI_EXPLODE; // TODO vsprites[6].x = x; vsprites[6].y = y; enemy_exploding = 1; } void animate_enemy_explosion() { if (enemy_exploding) { // animate next frame if (enemy_exploding >= 8) { enemy_exploding = 0; // hide explosion after 4 frames vsprites[6].y = YOFFSCREEN; } else { vsprites[6].name = SPRI_EXPLODE + (enemy_exploding += 4); // TODO } } } void animate_player_explosion() { byte z = player_exploding; if (z <= 3) { if (z == 3) { vsprites[7].y = YOFFSCREEN; } else { vsprites[7].name = SPRI_EXPLODE + z*4; } } } void hide_player_missile() { missiles[7].ypos = YOFFSCREEN; } void does_player_shoot_formation() { byte mx = missiles[7].xpos + 8; byte my = missiles[7].ypos; signed char row = (my - FORMATION_Y0) / FORMATION_YSPACE; if (row >= 0 && row < ENEMY_ROWS) { // ok if unsigned (in fact, must be due to range) byte xoffset = mx - FORMATION_X0 - formation_offset_x; byte column = xoffset / FORMATION_XSPACE; byte localx = xoffset - column * FORMATION_XSPACE; if (column < ENEMIES_PER_ROW && localx < 16) { char index = column + row * ENEMIES_PER_ROW; if (formation[index].shape) { formation[index].shape = 0; enemies_left--; blowup_at(get_attacker_x(index), get_attacker_y(index)); hide_player_missile(); add_score(2); } } } } void does_player_shoot_attacker() { byte mx = missiles[7].xpos + 8; byte my = missiles[7].ypos; byte i; for (i=0; ifindex && in_rect(mx, my, a->x >> 8, a->y >> 8, 16, 16)) { blowup_at(a->x >> 8, a->y >> 8); a->findex = 0; enemies_left--; hide_player_missile(); add_score(5); break; } } } void does_missile_hit_player() { byte i; if (player_exploding) return; for (i=0; i> 8; if (y >= 0x80) { cv_set_frequency(CV_SOUNDCHANNEL_2, 4000+y*8); cv_set_attenuation(CV_SOUNDCHANNEL_2, 28); break; } } } } void wait_for_frame() { while (((vint_counter ^ framecount) & 3) == 0); } void play_round() { byte end_timer = 255; player_score = 0; add_score(0); putstring(0, 0, "PLAYER 1"); setup_formation(); clrobjs(); formation_direction = 1; vint_counter = 0; framecount = 0; new_player_ship(); while (end_timer) { if (player_exploding) { if ((framecount & 7) == 1) { animate_player_explosion(); if (++player_exploding > 32 && enemies_left) { new_player_ship(); } } } else { if ((framecount & 0x7f) == 0 || enemies_left < 8) { new_attack_wave(); } move_player(); does_missile_hit_player(); } if ((framecount & 3) == 0) animate_enemy_explosion(); move_attackers(); move_missiles(); does_player_shoot_formation(); does_player_shoot_attacker(); draw_next_row(); draw_attackers(); if ((framecount & 0xf) == 0) think_attackers(); set_sounds(); framecount++; if (!enemies_left) end_timer--; wait_for_frame(); copy_sprites(); } } void main() { cv_set_screen_active(false); setup_32_column_font(); clrscr(); cv_set_vint_handler(&vint_handler); cv_set_screen_active(true); play_round(); main(); }