8bitworkshop/presets/vcs/vcslib/demo_vcslib.c

436 lines
9.3 KiB
C

//#resource "vcs-ca65.inc"
//#resource "kernel.inc"
//#link "demo_kernels.ca65"
//#link "libvcs.ca65"
//#link "mapper_3e.ca65"
//#link "xdata.ca65"
//#link "frameloop.c"
//#link "scorepf.ca65"
//#link "rand8.ca65"
//#link "bitmap48.ca65"
//#link "tinyfont48.c"
//#link "score6.ca65"
//#link "sound.ca65"
//#link "music.ca65"
//#link "demo_sounds.c"
#include <peekpoke.h>
#include "bcd.h"
#include "vcslib.h"
#define NSPRITES 2
#define NOBJS 5
#pragma bss-name (push,"ZEROPAGE")
/*
Attributes (position, etc) for all objects.
These map directly to player0/player1/missile0/missile1/ball.
(But they don't have to, if you write the code differently.)
*/
byte xpos[NOBJS];
byte ypos[NOBJS];
/*
These are variables used by the display kernels.
Some of them are modified by the kernels, like k_ypos,
so they must be initialized before the kernel starts.
*/
byte k_height[NOBJS];
byte k_ypos[NOBJS];
byte* k_bitmap[NSPRITES];
byte* k_colormap[NSPRITES];
const byte* k_playfield;
/*
BCD-encoded score, used by score display routines.
*/
byte bcd_score[3]; // support 6-digit score (3 bytes)
#pragma bss-name (pop)
/*
We build a number of 30x5 bitmaps with tinyfont48
*/
#define NCAPTIONS 4
// the font doesn't map exactly to ASCII
#pragma charmap (0x20, 0x5b)
#pragma charmap (0x21, 0x29)
#pragma charmap (0x5f, 0x2d)
// use the same ROM bank as tinyfont
#pragma bss-name(push, "XDATA")
// these have to be either in PERM or in XDATA
const char* const CAPTIONS[NCAPTIONS] = {
"HELLO WORLD!",
" WELCOME TO ",
"**[VCSLIB[**",
"C FOR 2600!!",
};
// used by tinyfont48 routine
byte font_bitmap[NCAPTIONS][32]; // at least 30 bytes each
#pragma bss-name(pop)
// music data (demo_sounds.c)
extern const byte music_1[];
extern const byte music_2[];
// kernel function for player sprites + background
extern void fastcall kernel_2pp_4pfa(byte nlines);
#pragma code-name (push, "ROM2")
#pragma rodata-name (push, "ROM2")
// kernel function for banner
extern void fastcall kernel_2pfasync(byte nlines);
// asynchronous playfield bitmap
/*{w:48,h:8,flip:1}*/
const byte k_asyncpf[6*8] = {
0x00,0x00,0x00,0x00,0x00,0x00,
0x80,0x0C,0xCE,0x30,0xEE,0x10,
0x40,0x92,0x50,0x00,0x49,0x28,
0x20,0x50,0x50,0x00,0x49,0x50,
0x20,0x50,0x4E,0x00,0x4E,0x28,
0x20,0x52,0x41,0x00,0x49,0x50,
0x20,0x4C,0x4E,0x00,0xEE,0x20,
0x00,0x00,0x00,0x00,0x00,0x00,
};
// number of lines in the 2pp sprite kernel
#define NLINES (192-44-48)
// Player sprite bitmap
/*{w:8,h:17,flip:1}*/
const byte Frame0[17] = {
0b1100001,
0b100010,
0b100100,
0b101100,
0b111000,
0b10111001,
0b10111010,
0b1111100,
0b11000,
0b111100,
0b1100110,
0b1011010,
0b1111110,
0b1111110,
0b1010110,
0b1111110,
0b10111101,
};
// Player sprite color map
const byte ColorFrame0[17+1] = {
0xF4, // bottom
0xF6,
0x84,
0x86,
0x88,
0xC2,
0xC4,
0xC6,
0xC8,
0x18,
0x28,
0x18,
0x18,
0x18,
0x18,
0x16,
0x5c, // top
0x5c, // (duplicated)
};
// move player with joystick
void move_joy(void) {
if (JOY_UP(0)) {
if (ypos[1] > 0x22) ypos[1]--;
}
if (JOY_DOWN(0)) {
if (ypos[1] < 0x22 + 16 + NLINES/2) ypos[1]++;
}
if (JOY_LEFT(0)) {
if (xpos[1] > 0x3) xpos[1]--;
TIA.refp1 = NO_REFLECT;
}
if (JOY_RIGHT(0)) {
if (xpos[1] < 0x9c) xpos[1]++;
TIA.refp1 = REFLECT;
}
}
// Setup an object for the kernel routines.
void setup_object(byte index) {
k_ypos[index] = ypos[index] >> 1;
set_horiz_pos((index<<8) | xpos[index]);
}
// Setup a player object for the kernel routines.
void setup_player(byte nlines, byte index) {
byte y = ypos[index] >> 1;
byte ofs = nlines - y + 1;
k_bitmap[index] = (char*) Frame0 - ofs;
ofs -= 1;
ofs -= ypos[index] & 1;
k_colormap[index] = (char*) ColorFrame0 - ofs;
k_ypos[index] = y;
set_horiz_pos((index<<8) | xpos[index]);
}
/*
This function runs after VSYNC, and before the display kernel.
*/
void my_preframe(void) {
TIA.vdelp0 = ypos[0];
TIA.vdelp1 = ypos[1];
TIA.nusiz0 = ONE_COPY | MSBL_SIZE4;
TIA.nusiz1 = DOUBLE_SIZE | MSBL_SIZE4;
setup_player(NLINES/2, P0);
setup_player(NLINES/2, P1);
setup_object(M0);
setup_object(M1);
apply_hmove();
// k_playfield = (char*) 0xf000;
}
/*
Versatile playfield data is pretty easy:
FIrst byte contains the register, second byte has the value.
It's in reverse order.
*/
const byte VersatilePlayfield_data_e0_b0[] = {
0x00, 0x3F, 0x00, 0x3F, 0x00, 0x0E, 0xAA, 0x0E,
0x18, 0x08, 0x02, 0x09, 0x00, 0x0F, 0x08, 0x0F,
0x7F, 0x0F, 0x3E, 0x0F, 0x1C, 0x0F, 0x08, 0x0F,
0xC2, 0x08, 0x00, 0x3F, 0x00, 0x3F, 0x00, 0x3F,
0x00, 0x0E, 0x1E, 0x0E, 0x08, 0x08, 0x7F, 0x0E,
0xFE, 0x0E, 0x38, 0x0E, 0x06, 0x08, 0x01, 0x0A,
0xa0, 0x09
};
/*
This function is called to display the frame.
*/
void my_doframe(void) {
byte caption_index; // which message to display? (0..NCAPTIONS-1)
// draw the VCSLIB title using the async playfield kernel
TIA.ctrlpf = 0;
do_wsync();
TIA.colubk = COLOR_CONV(0x68);
TIA.colupf = COLOR_CONV(0xa2);
kernel_2pfasync(42);
// draw the sprites + playfield
TIA.colubk = 0x0;
TIA.ctrlpf = PF_REFLECT;
kernel_2pp_4pfa(NLINES/2); // each line is doubled
// draw the playfield 2-digit score
TIA.ctrlpf = PF_SCORE;
do_wsync();
TIA.colubk = COLOR_CONV(0xa2);
TIA.colup0 = COLOR_CONV(0x2e);
TIA.colup1 = COLOR_CONV(0x8e);
scorepf_kernel();
TIA.wsync = 0;
TIA.colubk = 0;
TIA.ctrlpf = PF_REFLECT;
// draw a 12-letter caption using bitmap48
TIA.colubk = COLOR_CONV(0x82);
// cycle between the messages
// (we are low on memory, so use whatever counter is available :P)
caption_index = ((byte)music_ptr>>3) & (NCAPTIONS-1);
bitmap48_setheight(5); // must call before bitmap48_setaddress()
bitmap48_setaddress(font_bitmap[caption_index]);
bitmap48_setup();
bitmap48_kernel(5); // 5 lines high
// draw the 6-digit score (again using bitmap48)
score6_build();
bitmap48_kernel(8); // 8 lines high
}
/*
This function is called after the frame is displayed,
and before overscan.
*/
void my_postframe(void) {
// move P1
move_joy();
// move P0
if (++xpos[P0] > 150) {
xpos[P0] = 0;
}
if (++ypos[P0] > 100) {
ypos[P0] = 0;
}
// set missile positions
ypos[M0] = 80;
ypos[M1] = 82;
xpos[M0] = 10;
xpos[M1] = 155;
// fire buttons
if (JOY_FIRE(0)) {
sound_play(7);
score6_add(0x0199);
}
if (JOY_FIRE(1)) {
BCD_ADD(bcd_score[0], 1);
}
// update sound
sound_update();
music_update();
// update sound meter
k_height[M0] = sndchan_timer[0]*4;
k_height[M1] = sndchan_timer[1]*4;
// prepare score for next frame
scorepf_build();
// play more music?
if (SW_SELECT()) { music_play(music_2); }
}
/*
kernel_loop() is the main loop routine.
It's wrapped with wrapped-call so that it
switches to the appropriate bank (the one
that contains the function) before running.
kernel_1() etc. do not have to be wrapped,
as long as they are called from a function
that is itself wrapped and in the same bank.
*/
#pragma wrapped-call (push, bankselect, bank)
void kernel_loop() {
while (1) {
kernel_1();
my_preframe();
kernel_2();
my_doframe();
kernel_3();
my_postframe();
kernel_4();
}
}
#pragma wrapped-call (pop)
#pragma rodata-name (pop)
#pragma code-name (pop)
/*
These are just test routines, they can be removed.
*/
#pragma code-name(push, "XDATA");
#pragma data-name(push, "XDATA");
long int var = 0xdeadbeef;
int testfn() {
return 0x1234;
}
#pragma code-name(pop);
#pragma data-name(pop);
#pragma wrapped-call (push, ramselect, 0)
void ramtest(void) {
char x;
POKE(0x17f0, 0xaa);
x = PEEK(0x17f0);
if (x != 0xaa) asm("brk");
x = PEEK(&var); // 0xdeadbeef
if (x != 0xef) asm("brk");
x = PEEK((char*)testfn+4); // rts from testfn()
if (x != 0x60) asm("brk");
// TODO: doesn't work when ram selected
xramset((char*)0x13e0);
xramwrite(0x55);
x = xramread(); // TODO: selects ROM0 here
if (x != 0x55) asm("brk");
}
#pragma wrapped-call (pop)
/* end of test routines */
/*
init() runs first, and runs out of ROM0, which is
selected at power-up.
*/
#pragma wrapped-call (push, bankselect, bank)
#pragma code-name (push, "ROM0")
void init(void) {
byte i;
// set up initial object positions
xpos[P1] = 80;
ypos[P1] = 50;
ypos[M0] = 30;
ypos[M1] = 40;
ypos[BALL] = 60;
// set up kernel variables
k_playfield = VersatilePlayfield_data_e0_b0-1; // kernel expects offset to be -1
k_height[P0] = 16;
k_height[P1] = 16;
k_height[M0] = 0; // multiple of 4
k_height[M1] = 4; // multiple of 4
k_height[BALL] = 10;
// initial BCD scores
bcd_score[0] = 0x12;
bcd_score[1] = 0x34;
// build bitmap for caption messages
for (i=0; i<NCAPTIONS; i++) {
tinyfont48_build(font_bitmap[i], CAPTIONS[i]);
}
// start playing music
music_play(music_1);
}
#pragma code-name (pop)
#pragma wrapped-call (pop)
/*
The main() function is called at startup.
It resides in the shared ROM area (PERM).
*/
void main(void) {
// call functions once for "Analyze CPU Timing" button
// (bank-switching does an indirect jump which isn't detected)
asm("lda #4");
asm("jsr _kernel_2pp_4pfa");
asm("lda #4");
asm("jsr _kernel_2pfasync");
// copy initialized data to XRAM
copyxdata();
// test XRAM (can be removed)
ramtest();
// initialization
init();
// main kernel loop
kernel_loop();
}