mirror of
https://github.com/sehugg/8bitworkshop.git
synced 2024-12-23 18:31:08 +00:00
793 lines
27 KiB
C
793 lines
27 KiB
C
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <nes.h>
|
|
|
|
#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; i<NSPRITES; i++) {
|
|
Sprite* spr = &vsprites[i];
|
|
if (spr->y != 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; i<NMISSILES; i++) {
|
|
Missile* mis = &missiles[i];
|
|
if (mis->ypos != 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; i<NSPRITES; i++) {
|
|
vsprites[i].y = YOFFSCREEN;
|
|
}
|
|
for (i=0; i<NMISSILES; i++) {
|
|
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 ? SDST_FORM1 : SDST_FORM1;
|
|
}
|
|
enemies_left = MAX_IN_FORMATION;
|
|
formation_offset_x = 8;
|
|
}
|
|
|
|
void draw_row(byte row) {
|
|
static char buf[32];
|
|
register byte i;
|
|
register byte x = formation_offset_x / 8;
|
|
byte xd = (formation_offset_x & 7) * 3;
|
|
byte y = 3 + row * 2;
|
|
memset(buf, BLANK, sizeof(buf));
|
|
for (i=0; i<ENEMIES_PER_ROW; i++) {
|
|
byte shape = formation[i + row*ENEMIES_PER_ROW].shape;
|
|
if (shape) {
|
|
shape += xd;
|
|
buf[x] = shape;
|
|
buf[x+1] = shape+1;
|
|
buf[x+2] = shape+2;
|
|
}
|
|
x += 3;
|
|
}
|
|
vrambuf_put(NTADR_A(0, y), buf, sizeof(buf));
|
|
}
|
|
|
|
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 == 63) {
|
|
formation_direction = -1;
|
|
}
|
|
else if (formation_offset_x == 8) {
|
|
formation_direction = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
#define FLIPX 0x40
|
|
#define FLIPY 0x80
|
|
#define FLIPXY 0xc0
|
|
|
|
const byte DIR_TO_CODE[32] = {
|
|
0|FLIPXY, 1|FLIPXY, 2|FLIPXY, 3|FLIPXY, 4|FLIPXY, 5|FLIPXY, 6|FLIPXY, 6|FLIPXY,
|
|
6|FLIPX, 6|FLIPX, 5|FLIPX, 4|FLIPX, 3|FLIPX, 2|FLIPX, 1|FLIPX, 0|FLIPX,
|
|
0, 1, 2, 3, 4, 5, 6, 6,
|
|
6|FLIPY, 6|FLIPY, 5|FLIPY, 4|FLIPY, 3|FLIPY, 2|FLIPY, 1|FLIPY, 0|FLIPY,
|
|
};
|
|
|
|
// sine table, pre-multiplied by 2
|
|
const int SINTBL2[32] = {
|
|
0, 25*2, 49*2, 71*2, 90*2, 106*2, 117*2, 125*2,
|
|
127*2, 125*2, 117*2, 106*2, 90*2, 71*2, 49*2, 25*2,
|
|
0*2, -25*2, -49*2, -71*2, -90*2, -106*2, -117*2, -125*2,
|
|
-127*2, -125*2, -117*2, -106*2, -90*2, -71*2, -49*2, -25*2,
|
|
};
|
|
|
|
#define ISIN(x) (SINTBL2[(x) & 31])
|
|
#define ICOS(x) ISIN(x+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) {
|
|
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; i<MAX_ATTACKERS; i++) {
|
|
draw_attacker(i);
|
|
}
|
|
}
|
|
|
|
void return_attacker(register 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(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; 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[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; 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].ypos != YOFFSCREEN &&
|
|
in_rect(missiles[i].xpos, 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 = 120;
|
|
draw_player();
|
|
}
|
|
|
|
void set_sounds() {
|
|
byte i;
|
|
// these channels decay, so ok to always enable
|
|
byte enable = ENABLE_PULSE0|ENABLE_PULSE1|ENABLE_NOISE;
|
|
// missile fire sound
|
|
if (missiles[PLYRMISSILE].ypos != YOFFSCREEN) {
|
|
APU_PULSE_SUSTAIN(0, missiles[PLYRMISSILE].ypos ^ 0xff, DUTY_50, 6);
|
|
} else {
|
|
APU_PULSE_CONTROL(0, DUTY_50, 1);
|
|
}
|
|
// enemy explosion sound
|
|
if (player_exploding && player_exploding < 8) {
|
|
APU_NOISE_DECAY(8 + player_exploding, 5, 15);
|
|
} else if (enemy_exploding) {
|
|
APU_NOISE_DECAY(8 + enemy_exploding, 2, 8);
|
|
}
|
|
// set diving sounds for spaceships
|
|
for (i=0; i<2; i++) {
|
|
register AttackingEnemy* a = i ? &attackers[4] : &attackers[0];
|
|
if (a->findex && !a->returning) {
|
|
byte y = a->y >> 8;
|
|
APU_TRIANGLE_SUSTAIN(0x100 | y);
|
|
enable |= ENABLE_TRIANGLE;
|
|
}
|
|
}
|
|
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((unsigned char*)TILESET, sizeof(TILESET));
|
|
// copy sprites
|
|
vram_adr(0x1000);
|
|
vram_write((unsigned char*)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();
|
|
}
|
|
}
|
|
|