mirror of
https://github.com/sehugg/8bitworkshop.git
synced 2024-11-25 18:33:11 +00:00
778 lines
19 KiB
C
778 lines
19 KiB
C
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#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; i<NMISSILES; i++) {
|
|
// sprite struct: {y, x, name, tag}
|
|
cvu_voutb(missiles[i].ypos, ofs);
|
|
cvu_voutb(missiles[i].xpos, ofs+1);
|
|
ofs += 4;
|
|
}
|
|
}
|
|
|
|
void add_score(word bcd) {
|
|
player_score = bcd_add(player_score, bcd);
|
|
draw_bcd_word(0, 1, player_score);
|
|
putchar(4, 1, CHAR('0'));
|
|
}
|
|
|
|
void clrobjs() {
|
|
byte i;
|
|
word ofs;
|
|
memset(vsprites, 0, sizeof(vsprites));
|
|
for (i=0; i<NSPRITES; i++) {
|
|
vsprites[i].y = YOFFSCREEN;
|
|
}
|
|
ofs = SPRITES + NSPRITES*4;
|
|
for (i=0; i<NMISSILES; i++) {
|
|
// sprite struct: {y, x, name, tag}
|
|
cvu_voutb(i == 7 ? 4 : 8, ofs+2);
|
|
cvu_voutb(CV_COLOR_WHITE, ofs+3);
|
|
ofs += 4;
|
|
missiles[i].ypos = YOFFSCREEN;
|
|
}
|
|
}
|
|
|
|
void setup_formation() {
|
|
byte i;
|
|
memset(formation, 0, sizeof(formation));
|
|
memset(attackers, 0, sizeof(attackers));
|
|
for (i=0; i<MAX_IN_FORMATION; i++) {
|
|
byte flagship = i < ENEMIES_PER_ROW;
|
|
formation[i].shape = flagship ? 0x43 : 0x43;
|
|
}
|
|
enemies_left = MAX_IN_FORMATION;
|
|
}
|
|
|
|
void draw_row(byte row) {
|
|
byte i;
|
|
byte x = formation_offset_x / 8;
|
|
byte xd = (formation_offset_x & 7) * 3;
|
|
byte y = 3 + row * 2;
|
|
//vcolumns[y].attrib = 0x2;
|
|
//vcolumns[y].scroll = formation_offset_x;
|
|
for (i=0; i<x; i++)
|
|
putchar(i, y, BLANK);
|
|
for (i=0; i<ENEMIES_PER_ROW; i++) {
|
|
byte shape = formation[i + row*ENEMIES_PER_ROW].shape;
|
|
if (shape) {
|
|
shape += xd;
|
|
putchar(x, y, shape);
|
|
putchar(x+1, y, shape+1);
|
|
putchar(x+2, y, shape+2);
|
|
} else {
|
|
putchar(x, y, BLANK);
|
|
putchar(x+1, y, BLANK);
|
|
putchar(x+2, y, BLANK);
|
|
}
|
|
x += 3;
|
|
}
|
|
for (; x<COLS; x++)
|
|
putchar(x, y, BLANK);
|
|
}
|
|
|
|
void draw_next_row() {
|
|
draw_row(current_row);
|
|
if (++current_row == ENEMY_ROWS) {
|
|
current_row = 0;
|
|
formation_offset_x += formation_direction;
|
|
if (formation_offset_x == 71) {
|
|
formation_direction = -1;
|
|
}
|
|
else if (formation_offset_x == 0) {
|
|
formation_direction = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
const byte DIR_TO_CODE[32] = {
|
|
16, 16,
|
|
20, 20, 20, 20,
|
|
24, 24, 24, 24,
|
|
28, 28, 28, 28,
|
|
0, 0, 0, 0,
|
|
4, 4, 4, 4,
|
|
8, 8, 8, 8,
|
|
12, 12, 12, 12,
|
|
16, 16,
|
|
};
|
|
|
|
const byte SINTBL[32] = {
|
|
0, 25, 49, 71, 90, 106, 117, 125,
|
|
127, 125, 117, 106, 90, 71, 49, 25,
|
|
0, -25, -49, -71, -90, -106, -117, -125,
|
|
-127, -125, -117, -106, -90, -71, -49, -25,
|
|
};
|
|
|
|
signed char isin(byte dir) {
|
|
return SINTBL[dir & 31];
|
|
}
|
|
|
|
signed char icos(byte dir) {
|
|
return isin(dir+8);
|
|
}
|
|
|
|
#define FORMATION_X0 0
|
|
#define FORMATION_Y0 19
|
|
#define FORMATION_XSPACE 24
|
|
#define FORMATION_YSPACE 16
|
|
|
|
byte get_attacker_x(byte formation_index) {
|
|
byte column = (formation_index % ENEMIES_PER_ROW);
|
|
return FORMATION_XSPACE*column + FORMATION_X0 + formation_offset_x;
|
|
}
|
|
|
|
byte get_attacker_y(byte formation_index) {
|
|
byte row = formation_index / ENEMIES_PER_ROW;
|
|
return FORMATION_YSPACE*row + FORMATION_Y0;
|
|
}
|
|
|
|
void draw_attacker(byte i) {
|
|
AttackingEnemy* a = &attackers[i];
|
|
if (a->findex) {
|
|
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; i<MAX_ATTACKERS; i++) {
|
|
draw_attacker(i);
|
|
}
|
|
}
|
|
|
|
void return_attacker(AttackingEnemy* a) {
|
|
byte fi = a->findex-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; i<MAX_ATTACKERS; i++) {
|
|
AttackingEnemy* a = &attackers[i];
|
|
if (a->findex) {
|
|
if (a->returning)
|
|
return_attacker(a);
|
|
else
|
|
fly_attacker(a);
|
|
}
|
|
}
|
|
}
|
|
|
|
void think_attackers() {
|
|
byte i;
|
|
for (i=0; i<MAX_ATTACKERS; i++) {
|
|
AttackingEnemy* a = &attackers[i];
|
|
if (a->findex) {
|
|
// 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; i<MAX_ATTACKERS; i++) {
|
|
AttackingEnemy* a = &attackers[i];
|
|
if (a->findex == 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; i<MAX_ATTACKERS; i++) {
|
|
AttackingEnemy* a = &attackers[i];
|
|
if (a->findex && 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<MAX_ATTACKERS; i++) {
|
|
if (missiles[i].dy &&
|
|
in_rect(missiles[i].xpos + 8, missiles[i].ypos + 16,
|
|
player_x, player_y, 16, 16)) {
|
|
player_exploding = 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void new_attack_wave() {
|
|
byte i = rand();
|
|
byte j;
|
|
// find a random slot that has an enemy
|
|
for (j=0; j<MAX_IN_FORMATION; j++) {
|
|
i = (i+1) & (MAX_IN_FORMATION-1);
|
|
// anyone there?
|
|
if (formation[i].shape) {
|
|
formation_to_attacker(i);
|
|
formation_to_attacker(i+1);
|
|
formation_to_attacker(i+ENEMIES_PER_ROW);
|
|
formation_to_attacker(i+ENEMIES_PER_ROW+1);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void new_player_ship() {
|
|
player_exploding = 0;
|
|
player_x = 128;
|
|
draw_player();
|
|
}
|
|
|
|
void set_sounds() {
|
|
byte i;
|
|
// missile fire sound
|
|
if (missiles[7].ypos != YOFFSCREEN) {
|
|
cv_set_frequency(CV_SOUNDCHANNEL_0, 2000-missiles[7].ypos*4);
|
|
cv_set_attenuation(CV_SOUNDCHANNEL_0, 18);
|
|
} else {
|
|
cv_set_attenuation(CV_SOUNDCHANNEL_0, 32);
|
|
}
|
|
// enemy explosion sound
|
|
if (enemy_exploding) {
|
|
cv_set_frequency(CV_SOUNDCHANNEL_1, 500+enemy_exploding*64);
|
|
cv_set_attenuation(CV_SOUNDCHANNEL_1, 14);
|
|
} else {
|
|
cv_set_attenuation(CV_SOUNDCHANNEL_1, 32);
|
|
}
|
|
cv_set_attenuation(CV_SOUNDCHANNEL_2, 32);
|
|
// player explosion
|
|
if (player_exploding && player_exploding < 15) {
|
|
cv_set_frequency(CV_SOUNDCHANNEL_2, player_exploding*256);
|
|
cv_set_attenuation(CV_SOUNDCHANNEL_NOISE, 4+player_exploding);
|
|
cv_set_noise(true, 3);
|
|
} else {
|
|
// set diving sounds for spaceships
|
|
cv_set_attenuation(CV_SOUNDCHANNEL_NOISE, 32);
|
|
for (i=0; i<3; i++) {
|
|
byte y = attackers[i].y >> 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();
|
|
}
|