/* A shoot-em-up game. Uses CHR RAM to draw attacker tiles at 8 different pixels, then animates them in the nametable. Sprites are used for the player, missiles, attackers, and stars. */ #include #include #include #include "neslib.h" #define NES_MAPPER 2 // mapper 2 (UxROM mapper) #define NES_CHR_BANKS 0 // CHR RAM // APU (sound) support #include "apu.h" //#link "apu.c" // BCD arithmetic support #include "bcd.h" //#link "bcd.c" // VRAM update buffer #include "vrambuf.h" //#link "vrambuf.c" #define COLS 32 #define ROWS 28 //#define DEBUG_FRAMERATE /*{pal:"nes",layout:"nes"}*/ const char PALETTE[32] = { 0x0F, 0x11,0x24,0x3C, 0x00, 0x01,0x15,0x25, 0x00, 0x01,0x10,0x20, 0x00, 0x06,0x16,0x26, 0x00, 0x11,0x24,0x3C, 0x00, 0x01,0x15,0x25, 0x00, 0x31,0x35,0x3C, 0x00, 0x25,0x1C,0x3D }; #define COLOR_PLAYER 3 #define COLOR_FORMATION 1 #define COLOR_ATTACKER 1 #define COLOR_MISSILE 3 #define COLOR_BOMB 2 #define COLOR_SCORE 2 #define COLOR_EXPLOSION 3 /*{w:8,h:8,bpp:1,count:128,brev:1,np:2,pofs:8,remap:[0,1,2,4,5,6,7,8,9,10,11,12]}*/ const char TILESET[128*8*2] = { // font (0..63) 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x38,0x7C,0x7C,0x7C,0x38,0x00,0x38,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x6C,0x6C,0x48,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x6C,0xFE,0x6C,0xFE,0x6C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x10,0xFE,0xD0,0xFE,0x16,0xFE,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xCE,0xDC,0x38,0x76,0xE6,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x38,0x6C,0x7C,0xEC,0xEE,0x7E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x38,0x38,0x30,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x38,0x70,0x70,0x70,0x70,0x70,0x38,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x70,0x38,0x38,0x38,0x38,0x38,0x30,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x6C,0x38,0x6C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x38,0x38,0xFE,0x38,0x38,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,0x30,0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x60,0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0E,0x1E,0x3C,0x78,0xF0,0xE0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x7C,0xEE,0xEE,0xEE,0xEE,0x7C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x38,0x78,0x38,0x38,0x38,0x7C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7C,0x0E,0x7C,0xE0,0xEE,0xFE,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFC,0x0E,0x3C,0x0E,0x0E,0xFC,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3E,0x7E,0xEE,0xEE,0xFE,0x0E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFC,0xE0,0xFC,0x0E,0xEE,0x7C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7C,0xE0,0xFC,0xEE,0xEE,0x7C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFE,0xEE,0x1C,0x1C,0x38,0x38,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7C,0xEE,0x7C,0xEE,0xEE,0x7C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7C,0xEE,0xEE,0x7E,0x0E,0x3C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x60,0x60,0x00,0x00,0x60,0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x60,0x60,0x00,0x00,0x60,0x60,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1C,0x38,0x70,0x70,0x38,0x1C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7C,0x00,0x00,0x7C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x70,0x38,0x1C,0x1C,0x38,0x70,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7C,0xEE,0x1C,0x38,0x00,0x38,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x7C,0xEE,0xEE,0xEE,0xE0,0x7C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7C,0xEE,0xEE,0xEE,0xFE,0xEE,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFC,0xEE,0xFC,0xEE,0xEE,0xFC,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7C,0xEE,0xE0,0xE0,0xEE,0x7C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xF8,0xEC,0xEE,0xEE,0xEE,0xFC,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFE,0xE0,0xF0,0xE0,0xE0,0xFE,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFE,0xE0,0xF8,0xE0,0xE0,0xE0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7C,0xE0,0xEE,0xEE,0xEE,0x7E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xEE,0xEE,0xFE,0xEE,0xEE,0xEE,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7C,0x38,0x38,0x38,0x38,0x7C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0E,0x0E,0x0E,0x0E,0xEE,0x7C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xEE,0xFC,0xF8,0xEC,0xEE,0xEE,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0xE0,0xE0,0xE0,0xEE,0xFE,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xC6,0xEE,0xFE,0xFE,0xEE,0xEE,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xCE,0xEE,0xFE,0xFE,0xEE,0xE6,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7C,0xEE,0xEE,0xEE,0xEE,0x7C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0xFC,0xEE,0xEE,0xEE,0xFC,0xE0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7C,0xEE,0xEE,0xEE,0xEC,0x7E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFC,0xEE,0xEE,0xEE,0xFC,0xEE,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7C,0xE0,0x7C,0x0E,0xEE,0x7C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFE,0x38,0x38,0x38,0x38,0x38,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xEE,0xEE,0xEE,0xEE,0xEE,0x7C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xEE,0xEE,0xEE,0x6C,0x38,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xEE,0xEE,0xFE,0xFE,0xEE,0xC6,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xEE,0x7C,0x38,0x7C,0xEE,0xEE,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xEE,0xEE,0xEE,0x7C,0x38,0x38,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFE,0x1C,0x38,0x70,0xE0,0xFE,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /*{w:16,h:16,bpp:1,count:16,brev:1,np:2,pofs:8,remap:[5,0,1,2,4,6,7,8,9,10,11,12]}*/ // formation enemy (64,66) 0x0F,0x13,0x03,0x03,0x01,0x01,0x00,0x00,0x0C,0x11,0x04,0x08,0x12,0x04,0x08,0x08, 0x0C,0x13,0x03,0x03,0x03,0x01,0x01,0x00,0x0C,0x10,0x01,0x0C,0x30,0x02,0x04,0x18, 0xF8,0xE4,0xE0,0xE0,0xC0,0xC0,0x80,0x00,0x18,0x44,0x10,0x08,0x24,0x10,0x08,0x08, 0x18,0x64,0xE0,0xE0,0xE0,0xC0,0xC0,0x80,0x18,0x04,0x40,0x18,0x06,0x20,0x10,0x0C, // attackers (68) 0x00,0x00,0x04,0x08,0x04,0x03,0x03,0x0F,0x00,0x00,0x04,0x08,0x04,0x00,0x01,0x00, 0x03,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x30,0x02,0x04,0x18,0x00,0x00,0x00,0x00, 0x00,0x00,0x10,0x08,0x10,0x60,0xE0,0xE0,0x00,0x00,0x10,0x08,0x10,0x00,0x40,0x18, 0xE0,0xC0,0xC0,0x80,0x00,0x00,0x00,0x00,0x06,0x20,0x10,0x0C,0x00,0x00,0x00,0x00, 0x00,0x00,0x02,0x04,0x04,0x03,0x03,0x03,0x00,0x00,0x02,0x04,0x04,0x20,0x11,0x0C, 0x03,0x07,0x03,0x03,0x02,0x00,0x00,0x00,0x00,0x20,0x1C,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x08,0x08,0x10,0xE0,0xE0,0x00,0x00,0x00,0x08,0x08,0x10,0x00,0x40, 0xE0,0xC0,0xC0,0x00,0x00,0x00,0x00,0x00,0x10,0x0C,0x02,0x20,0x10,0x0C,0x00,0x00, 0x00,0x00,0x02,0x04,0x04,0x03,0x03,0x03,0x00,0x00,0x02,0x04,0x04,0x20,0x11,0x0C, 0x03,0x07,0x03,0x03,0x02,0x00,0x00,0x00,0x00,0x20,0x1C,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x08,0x08,0x10,0xE0,0xE0,0x00,0x00,0x00,0x08,0x08,0x10,0x00,0x40, 0xE0,0xC0,0xC0,0x00,0x00,0x00,0x00,0x00,0x10,0x0C,0x02,0x20,0x10,0x0C,0x00,0x00, 0x00,0x00,0x01,0x01,0x01,0x01,0x03,0x03,0x00,0x00,0x01,0x11,0x08,0x04,0x01,0x20, 0x07,0x07,0x03,0x02,0x00,0x00,0x00,0x00,0x10,0x08,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x80,0x00,0x04,0xCC,0xF0,0xE0,0x00,0x00,0x80,0x00,0x04,0x0C,0x00,0x40, 0xC0,0xC0,0x80,0x00,0x00,0x00,0x00,0x00,0x20,0x10,0x08,0x80,0x40,0x20,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x04,0x02,0x11,0x08,0x04, 0x03,0x03,0x07,0x01,0x00,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x30,0x20,0x60,0xF0,0xFC,0x00,0x00,0x00,0x30,0x20,0x00,0x40,0x04, 0xFC,0xE0,0xC0,0x80,0x00,0x00,0x00,0x00,0x2C,0x00,0x20,0x18,0x80,0x40,0x60,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x07,0x00,0x00,0x00,0x10,0x10,0x08,0x04,0x00, 0x07,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x02,0x02,0x02,0x00,0x00, 0x00,0x00,0x00,0x08,0x34,0xE0,0xE0,0xF0,0x00,0x80,0x80,0x88,0x94,0x00,0x40,0x00, 0xFA,0xE4,0x00,0x00,0x00,0x00,0x00,0x00,0x2A,0x04,0x80,0x40,0x20,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x07,0x00,0x00,0x01,0x09,0x08,0x04,0x02,0x00, 0x0F,0x07,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x04,0x08,0x09,0x01,0x00, 0x00,0x00,0x00,0x00,0x08,0x14,0xE0,0xE0,0x00,0x00,0x00,0x00,0x88,0x94,0x00,0x40, 0xC0,0xE0,0xE0,0x14,0x08,0x00,0x00,0x00,0x00,0x40,0x00,0x94,0x88,0x00,0x00,0x00, 0x01,0x01,0x01,0x00,0x00,0x01,0x11,0x11,0x01,0x01,0x02,0x07,0x01,0x21,0x21,0x2B, 0x11,0x16,0x1E,0x15,0x10,0x10,0x10,0x10,0x3F,0x3F,0x3F,0x3D,0x3B,0x3C,0x28,0x28, 0x40,0x40,0xC0,0x00,0x00,0x40,0x44,0x44,0xC0,0xC0,0x20,0xF0,0xC0,0xC2,0xC2,0xEA, 0x44,0x34,0x3C,0x54,0x04,0x04,0x04,0x04,0xFE,0xFE,0xFE,0xDE,0xEE,0x1E,0x0A,0x0A, // player ship (96) 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x08,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // bullet (100) 0x00,0x00,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // stars (102) 0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // explosions (112) 0x00,0x00,0x00,0x08,0x04,0x03,0x06,0x05,0x00,0x00,0x00,0x0C,0x06,0x04,0x00,0x18, 0x05,0x06,0x07,0x00,0x00,0x00,0x00,0x00,0x18,0x00,0x08,0x09,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x08,0x90,0xE0,0x90,0xD0,0x00,0x00,0x00,0x18,0x30,0x10,0x00,0x0C, 0xF0,0x20,0xE0,0x80,0x00,0x00,0x00,0x00,0x0C,0x10,0x18,0xC8,0x00,0x00,0x00,0x00, 0x00,0x04,0x12,0x0C,0x42,0x30,0x00,0x19,0x00,0x00,0x00,0x0A,0x00,0x06,0x08,0x00, 0x11,0x04,0x0C,0x00,0x21,0x46,0x00,0x00,0x04,0x10,0x02,0x0C,0x00,0x00,0x00,0x00, 0x00,0x40,0x08,0x91,0xA2,0x04,0x80,0xCC,0x00,0x00,0x00,0x20,0x00,0x30,0x08,0x00, 0xC4,0x10,0x1A,0x80,0x20,0x10,0x00,0x00,0x10,0x04,0xA0,0x98,0x80,0x00,0x00,0x00, 0x61,0x20,0x00,0x04,0x00,0xC4,0x08,0x10,0x00,0x00,0x04,0x06,0x09,0x02,0x10,0x14, 0x10,0x00,0x42,0xC8,0x80,0x00,0x10,0x30,0x34,0x10,0x11,0x02,0x00,0x00,0x00,0x00, 0x86,0xC4,0x00,0x10,0x01,0x10,0x08,0x04,0x00,0x00,0x10,0x30,0x48,0x20,0x04,0x14, 0x04,0x00,0x20,0x89,0x00,0x84,0xC6,0x43,0x16,0x04,0x44,0xA0,0x00,0x00,0x00,0x00, 0x00,0x00,0x02,0x08,0x00,0x06,0x02,0x00,0x00,0x40,0x08,0x01,0x11,0x15,0x00,0x38, 0x30,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x00,0x10,0x00,0x04,0x40,0x00,0x00, 0x00,0x00,0x20,0x08,0x00,0x30,0x20,0x00,0x02,0x00,0x08,0x40,0x44,0x54,0x00,0x0E, 0x06,0x20,0x80,0x80,0x00,0x00,0x00,0x00,0x0C,0x80,0x04,0x80,0x00,0x11,0x00,0x00, }; #define CHAR(x) ((x)-' ') #define BLANK 0 void clrscr() { vrambuf_clear(); ppu_off(); vram_adr(NAMETABLE_A); vram_fill(BLANK, 32*28); vram_adr(0x0); ppu_on_all(); } ///// 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 col, byte row, word bcd) { byte j; static char buf[5]; buf[4] = CHAR('0'); for (j=3; j<0x80; j--) { buf[j] = CHAR('0'+(bcd&0xf)); bcd >>= 4; } vrambuf_put(NTADR_A(col, row), buf, 5); } // GAME CODE #define NSPRITES 8 // max number of sprites #define NMISSILES 8 // max number of missiles #define YOFFSCREEN 240 // offscreen y position (hidden) // sprite indexes #define PLYRMISSILE 7 // player missile #define PLYRSPRITE 7 // player sprite #define BOOMSPRITE 6 // explosion sprite // nametable entries #define NAME_SHIP 96 #define NAME_MISSILE 100 #define NAME_BOMB 104 #define NAME_EXPLODE 112 #define NAME_ENEMY 68 #define SSRC_FORM1 64 // name for formation source sprites #define SDST_FORM1 128 // start of shifted formation table 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 { byte xpos; byte ypos; signed char dx; signed char dy; } Missile; typedef struct { byte x; byte y; byte name; byte tag; } Sprite; #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]; Sprite vsprites[NSPRITES]; byte formation_offset_x; signed char formation_direction; byte current_row; byte player_x; const byte player_y = 190; byte player_exploding; byte enemy_exploding; byte enemies_left; word player_score; void copy_sprites() { byte i; byte oamid = 128; // so we don't clear stars... for (i=0; iy != YOFFSCREEN) { byte y = spr->y; byte x = spr->x; byte chr = spr->name; byte attr = spr->tag; if (attr & 0x40) chr += 2; // horiz flip, swap tiles oamid = oam_spr(x, y, chr, attr, oamid); oamid = oam_spr(x+8, y, chr^2, attr, oamid); } } // copy all "shadow missiles" to video memory for (i=0; iypos != YOFFSCREEN) { oamid = oam_spr(mis->xpos, mis->ypos, NAME_MISSILE, (i==7)?COLOR_MISSILE:COLOR_BOMB, oamid); } } oam_hide_rest(oamid); } void add_score(word bcd) { player_score = bcd_add(player_score, bcd); draw_bcd_word(1, 1, player_score); } void clrobjs() { byte i; memset(vsprites, 0, sizeof(vsprites)); for (i=0; ifindex) { byte code = DIR_TO_CODE[a->dir & 31]; vsprites[i].name = NAME_ENEMY + (code & 7)*4; // tile vsprites[i].tag = code & FLIPXY; // flip h/v vsprites[i].x = a->x >> 8; vsprites[i].y = a->y >> 8; } 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(register AttackingEnemy* a) { a->x += ISIN(a->dir); a->y += ICOS(a->dir); 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[PLYRSPRITE].x = player_x; vsprites[PLYRSPRITE].y = player_y; vsprites[PLYRSPRITE].name = NAME_SHIP; vsprites[PLYRSPRITE].tag = COLOR_PLAYER; } void move_player() { byte joy = pad_poll(0); // move left/right? if ((joy & PAD_LEFT) && player_x > 16) player_x--; if ((joy & PAD_RIGHT) && player_x < 224) player_x++; // shoot missile? if ((joy & PAD_A) && missiles[PLYRMISSILE].ypos == YOFFSCREEN) { missiles[PLYRMISSILE].ypos = player_y-8; // must be multiple of missile speed missiles[PLYRMISSILE].xpos = player_x+4; // player X position missiles[PLYRMISSILE].dy = -4; // player missile speed } vsprites[PLYRMISSILE].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[BOOMSPRITE].tag = COLOR_EXPLOSION; vsprites[BOOMSPRITE].name = NAME_EXPLODE; // TODO vsprites[BOOMSPRITE].x = x; vsprites[BOOMSPRITE].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[BOOMSPRITE].y = YOFFSCREEN; } else { vsprites[BOOMSPRITE].name = NAME_EXPLODE + (enemy_exploding += 4); // TODO } } } void animate_player_explosion() { byte z = player_exploding; if (z <= 3) { if (z == 3) { vsprites[PLYRSPRITE].y = YOFFSCREEN; } else { vsprites[PLYRSPRITE].name = NAME_EXPLODE + z*4; } } } void hide_player_missile() { missiles[PLYRMISSILE].ypos = YOFFSCREEN; } void does_player_shoot_formation() { byte mx = missiles[PLYRMISSILE].xpos + 4; byte my = missiles[PLYRMISSILE].ypos; signed char row = (my - FORMATION_Y0) / FORMATION_YSPACE; if (missiles[PLYRMISSILE].ypos == YOFFSCREEN) return; 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[PLYRMISSILE].xpos + 4; byte my = missiles[PLYRMISSILE].ypos; byte i; if (missiles[PLYRMISSILE].ypos == YOFFSCREEN) return; 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; ifindex && !a->returning) { byte y = a->y >> 8; APU_TRIANGLE_SUSTAIN(0x100 | y); enable |= ENABLE_TRIANGLE; break; } } APU_ENABLE(enable); } void init_stars() { byte oamid = 0; // 32 slots = 128 bytes byte i; for (i=0; i<32; i++) { oamid = oam_spr(rand(), i*8, 103+(i&3), 0|OAM_BEHIND, oamid); } } /* void draw_stars_c() { byte i; for (i=0; i<32; i++) { ++OAMBUF[i].y; } } */ void draw_stars() { asm("ldy #0"); // start with Y = 0 asm("clc"); // clear carry for addition asm("@1: lda $200,y");// read from OAM buffer asm("adc #1"); // increment asm("sta $200,y"); // write to OAM buffer asm("iny"); // increment Y by 4 asm("iny"); asm("iny"); asm("iny"); asm("bpl @1"); // branch while < 128 } void play_round() { register byte framecount; register byte t0; byte end_timer = 255; add_score(0); //putbytes(NTADR_A(0, 1), "PLAYER 1", 8); setup_formation(); clrobjs(); formation_direction = 1; 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 == 0 || enemies_left < 8) { new_attack_wave(); } move_player(); } move_attackers(); move_missiles(); if (framecount & 1) does_player_shoot_formation(); else does_player_shoot_attacker(); draw_attackers(); if ((framecount & 0xf) == 0) { think_attackers(); } else { switch (framecount & 3) { case 1: animate_enemy_explosion(); // continue... case 2: does_missile_hit_player(); break; case 3: draw_stars(); break; } set_sounds(); if (!enemies_left) end_timer--; draw_next_row(); } vrambuf_flush(); copy_sprites(); #ifdef DEBUG_FRAMERATE putchar(t0 & 31, 27, CHAR(' ')); putchar(framecount & 31, 27, CHAR(' ')); #endif framecount++; t0 = nesclock(); #ifdef DEBUG_FRAMERATE putchar(t0 & 31, 27, CHAR('C')); putchar(framecount & 31, 27, CHAR('F')); #endif } } // turn off aggressive inlining to save a few bytes // functions after this point aren't called often #pragma codesize(100) void set_shifted_pattern(const byte* src, word dest, byte shift) { static byte buf[16*3]; byte y; for (y=0; y<16; y++) { byte a = src[y]; byte b = src[y+32]; buf[y] = a>>shift; buf[y+16] = b>>shift | a<<(8-shift); buf[y+32] = b<<(8-shift); } vram_adr(dest); vram_write(buf, sizeof(buf)); } void setup_graphics() { byte i; word src; word dest; // copy background vram_adr(0x0); vram_write(TILESET, sizeof(TILESET)); // copy sprites vram_adr(0x1000); vram_write(TILESET, sizeof(TILESET)); // write shifted versions of formation ships src = SSRC_FORM1*16; dest = SDST_FORM1*16; for (i=0; i<8; i++) { if (i==4) src += 16; set_shifted_pattern(&TILESET[src], dest, i); dest += 3*16; } // activate vram buffer vrambuf_clear(); set_vram_update(updbuf); } void main() { setup_graphics(); apu_init(); player_score = 0; while (1) { pal_all(PALETTE); oam_clear(); oam_size(1); // 8x16 sprites clrscr(); init_stars(); play_round(); } }