8bitworkshop/presets/coleco/platform.c

695 lines
18 KiB
C

#include <stdlib.h>
#include <string.h>
#include "cv.h"
#include "cvu.h"
#define PATTERN ((const cv_vmemp)0x1000)
#define IMAGE ((const cv_vmemp)0x1800)
#define COLOR ((const cv_vmemp)0x2000)
#define SPRITE_PATTERNS ((const cv_vmemp)0x3800)
#define SPRITES ((const cv_vmemp)0x3c00)
#define COLS 32
#define ROWS 24
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++;
}
#define XOFS 12 // sprite horiz. offset
#define CH_BORDER 64
#define CH_FLOOR 65
#define CH_LADDER 66
const byte char_table[][8] = {
/*{w:8,h:8,count:8}*/
{0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF},
{0xFF,0xBF,0xBF,0x00,0xFF,0xFB,0xFB,0x00},
{0x81,0xFF,0x81,0x81,0x81,0xFF,0x81,0x81},
{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},
};
const byte static_sprite_table[][16*2] = {
/*{w:16,h:16,remap:[-5,0,1,2,3,5,6,7,8,9],count:1}*/
{
0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x02, 0x3F,
0x35, 0x2A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3F,
0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x40, 0xFC,
0x54, 0xAC, 0x04, 0x04, 0x04, 0x04, 0x04, 0xFC,
}
};
const byte blimp_sprite_table[][16*2] = {
/*{w:16,h:16,remap:[-5,0,1,2,3,5,6,7,8,9],count:4}*/
{
0x00, 0x01, 0x03, 0xE7, 0xFF, 0xFE, 0xFE, 0xFE,
0xFE, 0xFF, 0xE7, 0x03, 0x01, 0x00, 0x00, 0x00,
0xF0, 0xE0, 0xC0, 0x80, 0xC0, 0x20, 0xFF, 0xFF,
0x20, 0xC0, 0x80, 0xC0, 0xE0, 0xF0, 0x00, 0x00,
},{
0xFC, 0xFF, 0xFF, 0xF6, 0xF5, 0xF5, 0xF6, 0xF5,
0xF5, 0x86, 0xFF, 0xFF, 0xFF, 0xFC, 0x40, 0x80,
0x00, 0xE0, 0xFE, 0x3F, 0xBF, 0xBF, 0x3F, 0xBF,
0xBF, 0x3F, 0xFF, 0xFE, 0xE0, 0x11, 0x0E, 0x01,
},{
0x00, 0x1F, 0xFF, 0xF8, 0xF6, 0xF6, 0xF8, 0xFE,
0xFE, 0xFE, 0xFF, 0xFF, 0x1F, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xBA, 0x92, 0xAA, 0xAA, 0xBA,
0xBA, 0xBA, 0xFF, 0xFF, 0xFF, 0xFF, 0x08, 0x07,
},{
0x00, 0x00, 0x00, 0x00, 0x03, 0x07, 0x07, 0x7F,
0x07, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x07, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0x7F, 0x07, 0x00, 0x00, 0x00, 0x00,
}
};
#define NUM_SPRITE_PATTERNS 5
#define NUM_SPRITE_STATES 4
const byte sprite_table[NUM_SPRITE_PATTERNS*2][16*2] = {
/*{w:16,h:16,remap:[-5,0,1,2,3,5,6,7,8,9],count:5,np:2}*/
{ // first plane
0x03, 0x07, 0x07, 0x07, 0x03, 0x00, 0x0F, 0x0F,
0x0F, 0x07, 0x07, 0x07, 0x0F, 0x0F, 0x02, 0x00,
0xC0, 0xA0, 0xA0, 0xA0, 0xC0, 0x00, 0x80, 0x80,
0xF0, 0xE0, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00,
},{
0x03, 0x07, 0x07, 0x07, 0x02, 0x04, 0x0C, 0x1C,
0x1C, 0x14, 0x07, 0x06, 0x0E, 0x0E, 0x00, 0x00,
0xC0, 0xE0, 0xEC, 0xEC, 0x4C, 0x38, 0x30, 0x20,
0x20, 0x20, 0xF0, 0x70, 0x00, 0x00, 0x00, 0x00,
},{
0x03, 0x07, 0x07, 0x07, 0x03, 0x00, 0x0F, 0x0F,
0x0F, 0x07, 0x07, 0x0F, 0x0F, 0x0C, 0x0C, 0x00,
0xC0, 0xA0, 0xA0, 0xA0, 0xC0, 0x00, 0x98, 0xF8,
0xF0, 0x80, 0x80, 0xE0, 0xF0, 0x00, 0x00, 0x00,
},{
0x03, 0x04, 0x06, 0x06, 0x43, 0x60, 0x3F, 0x1C,
0x05, 0x04, 0x07, 0x0F, 0x1C, 0x00, 0x00, 0x00,
0xC0, 0x20, 0x60, 0x60, 0xC0, 0x02, 0xE6, 0x3C,
0xB8, 0x20, 0xE0, 0xE0, 0x70, 0x00, 0x00, 0x00,
},{
0x03, 0x07, 0x07, 0x07, 0x03, 0x00, 0x0F, 0x0F,
0x0F, 0x47, 0x47, 0x0F, 0x1F, 0x19, 0x00, 0x00,
0xC0, 0xA0, 0xA0, 0xA0, 0xC0, 0x00, 0x98, 0xB0,
0xE0, 0x80, 0x80, 0x80, 0x80, 0xC0, 0x00, 0x00,
},{ // second plane
0x00, 0x00, 0x00, 0x00, 0x00, 0x37, 0x30, 0x30,
0x30, 0x30, 0x00, 0x00, 0x00, 0x00, 0x0D, 0x0D,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xC0,
},{
0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 0x03, 0x03,
0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x1C,
0x00, 0x00, 0x00, 0x00, 0x80, 0xC0, 0xC0, 0xC0,
0xC0, 0xC0, 0x00, 0x00, 0x30, 0x38, 0x00, 0x00,
},{
0x00, 0x00, 0x00, 0x00, 0x00, 0x37, 0x30, 0x30,
0x30, 0x30, 0x00, 0x00, 0x00, 0x30, 0x30, 0x20,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x38, 0x00,
},{
0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x03,
0x02, 0x03, 0x00, 0x00, 0x00, 0x18, 0x38, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x00, 0xC0,
0x40, 0xC0, 0x00, 0x00, 0x00, 0x30, 0x38, 0x00,
},{
0x00, 0x00, 0x00, 0x00, 0x00, 0x37, 0x30, 0x30,
0x30, 0x30, 0x00, 0x00, 0x00, 0x00, 0x19, 0x0C,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xE0,
}
};
///
const unsigned char reverse_lookup[16] = {
0x0, 0x8, 0x4, 0xc, 0x2, 0xa, 0x6, 0xe, 0x1, 0x9, 0x5, 0xd, 0x3, 0xb, 0x7, 0xf, };
byte reverse_bits(byte n) {
return (reverse_lookup[n&0b1111] << 4) | reverse_lookup[n>>4];
}
void flip_sprite_patterns(word dest, const byte* patterns, word len) {
word i;
for (i=0; i<len; i++) {
cvu_voutb(reverse_bits(*patterns++), dest++ ^ 16); // swap left/right chars
}
}
#define BGCOL CV_COLOR_BLUE
void setup_32_column_font() {
cvu_vmemset(0, 0, 0x4000); // clear video memory
cv_set_screen_mode(CV_SCREENMODE_STANDARD);
cv_set_color_table(COLOR);
cv_set_image_table(IMAGE);
cv_set_character_pattern_t(PATTERN);
cvu_memtovmemcpy(PATTERN, (void *)(font_bitmap_0 - '0'*8), 0x800);
cvu_memtovmemcpy(PATTERN+8*64, char_table, sizeof(char_table));
cvu_memtovmemcpy(PATTERN+8*128, static_sprite_table, sizeof(static_sprite_table));
cvu_vmemset(COLOR, 0x30|BGCOL, 8); // set color for chars 0-63
cvu_vmemset(COLOR+8, 0x0|BGCOL, 32-8); // set chars 63-255
cvu_vmemset(COLOR+16, 0xb0|BGCOL, 1); // set chars 128-128+8
cvu_memtovmemcpy(SPRITE_PATTERNS, sprite_table, sizeof(sprite_table));
flip_sprite_patterns(SPRITE_PATTERNS + 512, (const byte*)sprite_table, sizeof(sprite_table));
flip_sprite_patterns(SPRITE_PATTERNS + 384, (const byte*)blimp_sprite_table, sizeof(blimp_sprite_table));
cv_set_sprite_pattern_table(SPRITE_PATTERNS);
cv_set_sprite_attribute_table(SPRITES);
cv_set_sprite_big(true);
}
char cursor_x;
char cursor_y;
void clrscr() {
cvu_vmemset(IMAGE, ' ', COLS*ROWS);
}
#define LOCHAR 0x0
#define HICHAR 0xff
#define CHAR(ch) (ch-LOCHAR)
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();
}
}
///
typedef struct Level {
byte ypos;
byte height; // TODO: why does bitmask not work?
byte gap:4;
byte ladder1:4;
byte ladder2:4;
byte objtype:4;
byte objpos:4;
} Level;
#define MAX_LEVELS 32
#define GAPSIZE 3
Level levels[MAX_LEVELS];
bool is_in_gap(byte x, byte gap) {
if (gap) {
byte x1 = gap*16 + 26 - XOFS;
return (x > x1 && x < x1+GAPSIZE*8-4);
} else {
return false;
}
}
bool ladder_in_gap(byte x, byte gap) {
return gap && x >= gap && x < gap+GAPSIZE*2;
}
byte rndint(byte a, byte b) {
return ((byte)rand() % (b-a+1)) + a;
}
void make_levels() {
byte i;
byte y=0;
Level* prevlev = &levels[0];
for (i=0; i<MAX_LEVELS; i++) {
Level* lev = &levels[i];
lev->height = rndint(4,7);
lev->ladder1 = rndint(1,14);
lev->ladder2 = rndint(1,14);
do {
lev->gap = i>0 ? rndint(0,13) : 0;
} while (ladder_in_gap(prevlev->ladder1, lev->gap) ||
ladder_in_gap(prevlev->ladder2, lev->gap));
lev->objtype = 1;
lev->objpos = rndint(1,14);
lev->ypos = y;
y += lev->height;
prevlev = lev;
}
levels[MAX_LEVELS-1].height = 15;
levels[MAX_LEVELS-1].gap = 0;
levels[MAX_LEVELS-1].ladder1 = 0;
levels[MAX_LEVELS-1].ladder2 = 0;
}
static byte scroll_y = 0;
void create_actors_on_level(byte i);
void draw_level_line(byte screen_y) {
char buf[COLS];
byte i;
byte y = screen_y + scroll_y;
for (i=0; i<MAX_LEVELS; i++) {
Level* lev = &levels[i];
byte dy = y - lev->ypos;
// is this level visible on-screen?
if (dy < lev->height) {
if (dy == 0) {
// draw floor
memset(buf, CH_FLOOR, COLS);
// draw the gap
if (lev->gap)
memset(buf+lev->gap*2, 0, GAPSIZE);
} else {
// draw empty space
memset(buf, 0, sizeof(buf));
// draw walls
if (i < MAX_LEVELS-1) {
buf[0] = CH_FLOOR;
buf[COLS-1] = CH_FLOOR;
}
// draw ladders
if (lev->ladder1)
buf[lev->ladder1*2] = CH_LADDER;
if (lev->ladder2)
buf[lev->ladder2*2] = CH_LADDER;
}
//buf[0] = i+'a';buf[1] = y;buf[2] = dy+'0';buf[3] = lev->ypos;buf[4] = lev->height+'0';
// draw object, if it exists
if (lev->objtype) {
byte ch = lev->objtype*4+128-4;
if (dy == 1) {
buf[lev->objpos*2] = ch+1;
buf[lev->objpos*2+1] = ch+3;
}
else if (dy == 2) {
buf[lev->objpos*2] = ch;
buf[lev->objpos*2+1] = ch+2;
}
}
// copy line to screen buffer
cvu_memtovmemcpy(IMAGE + COLS*(ROWS-1) - COLS*screen_y, buf, COLS);
// create actors on this level, if needed
// (only when drawing top and bottom of screen)
if (screen_y == 0 || screen_y == ROWS-1)
create_actors_on_level(i);
break;
}
}
}
void draw_screen() {
byte y;
for (y=0; y<ROWS; y++) {
draw_level_line(y);
}
}
word get_floor_yy(byte level) {
return levels[level].ypos * 8 + 8;
}
word get_ceiling_yy(byte level) {
return (levels[level].ypos + levels[level].height) * 8 + 8;
}
#define MAX_ACTORS 5
typedef enum ActorState {
INACTIVE, WALKING, CLIMBING, JUMPING, FALLING
};
typedef struct Actor {
word yy;
byte x;
byte name;
byte color1:4;
byte color2:4;
byte level;
byte state:4;
byte dir:1;
byte onscreen:1;
union {
struct {
sbyte yvel;
sbyte xvel;
} jumping;
} u;
} Actor;
Actor actors[MAX_ACTORS];
void create_actors_on_level(byte level_index) {
byte actor_index = (level_index % (MAX_ACTORS-1)) + 1;
struct Actor* a = &actors[actor_index];
if (!a->onscreen) {
Level *level = &levels[level_index];
a->state = WALKING;
a->color1 = level->ladder1;
a->color2 = level->ladder2;
a->name = 0;
a->x = level->ladder1 ^ (level->ladder2<<3) ^ (level->gap<<6);
a->yy = get_floor_yy(level_index);
a->level = level_index;
}
}
void draw_actor(byte i) {
struct Actor* a = &actors[i];
struct cvu_sprite sprite;
int screen_y = 175 - a->yy + scroll_y*8;
if (screen_y > 192+8 || screen_y < -18) {
a->onscreen = 0;
return; // offscreen vertically
}
sprite.name = a->name + (a->state - WALKING)*4;
switch (a->state) {
case INACTIVE:
a->onscreen = 0;
return; // inactive, offscreen
case WALKING:
sprite.name += (a->x & 4) ? NUM_SPRITE_STATES*4 : 0;
case JUMPING:
case FALLING:
sprite.name += a->dir ? 16*4 : 0;
break;
case CLIMBING:
sprite.name += (a->yy & 4) ? 16*4 : 0;
break;
}
sprite.x = a->x;
sprite.y = screen_y;
sprite.tag = a->color1 | 0x80;
if (sprite.x >= 255-XOFS) {
sprite.tag &= ~0x80;
sprite.x -= 32-XOFS;
} else {
sprite.x += XOFS;
}
cvu_set_sprite(SPRITES, i*2, &sprite);
sprite.name += NUM_SPRITE_PATTERNS*4;
sprite.tag ^= a->color1 ^ a->color2;
cvu_set_sprite(SPRITES, i*2+1, &sprite);
a->onscreen = 1;
}
void refresh_actors() {
byte i;
for (i=0; i<MAX_ACTORS; i++)
draw_actor(i);
}
void refresh_screen() {
wait_vsync();
draw_screen();
refresh_actors();
}
byte is_ladder_close(byte actor_x, byte ladder_pos) {
byte ladder_x;
if (ladder_pos == 0)
return 0;
ladder_x = ladder_pos * 16 + 21 - XOFS;
return ((byte)(actor_x - ladder_x) < 16) ? ladder_x : 0;
}
byte get_closest_ladder(byte player_x, byte level_index) {
Level* level = &levels[level_index];
byte x;
if (level_index >= MAX_LEVELS) return 0;
x = is_ladder_close(player_x, level->ladder1);
if (x) return x;
x = is_ladder_close(player_x, level->ladder2);
if (x) return x;
return 0;
}
byte mount_ladder(Actor* player, signed char level_adjust) {
byte x = get_closest_ladder(player->x, player->level + level_adjust);
if (x) {
player->x = x + 8;
player->state = CLIMBING;
player->level += level_adjust;
return 1;
} else
return 0;
}
void check_scroll_up() {
byte player_screen_y = cvu_vinb(SPRITES + 0); // sprite Y pos
if (player_screen_y < 192/2-4) {
scroll_y++;
refresh_screen();
check_scroll_up();
}
}
void check_scroll_down() {
byte player_screen_y = cvu_vinb(SPRITES + 0); // sprite Y pos
if (player_screen_y > 192/2+4 && scroll_y > 0) {
scroll_y--;
refresh_screen();
check_scroll_down();
}
}
void fall_down(struct Actor* actor) {
actor->level--;
actor->state = FALLING;
actor->u.jumping.xvel = 0;
actor->u.jumping.yvel = 0;
}
void move_actor(struct Actor* actor, byte joystick, bool scroll) {
switch (actor->state) {
case WALKING:
// left/right has priority over climbing
if (joystick & CV_FIRE_0) {
actor->state = JUMPING;
actor->u.jumping.xvel = 0;
actor->u.jumping.yvel = 15;
if (joystick & CV_LEFT) actor->u.jumping.xvel = -1;
if (joystick & CV_RIGHT) actor->u.jumping.xvel = 1;
} else if (joystick & CV_LEFT) {
actor->x--;
actor->dir = 1;
} else if (joystick & CV_RIGHT) {
actor->x++;
actor->dir = 0;
} else if (joystick & CV_UP) {
mount_ladder(actor, 0); // state -> CLIMBING
if (scroll) check_scroll_up();
} else if (joystick & CV_DOWN) {
mount_ladder(actor, -1); // state -> CLIMBING, level -= 1
if (scroll) check_scroll_down();
}
break;
case CLIMBING:
if (joystick & CV_UP) {
if (actor->yy >= get_ceiling_yy(actor->level)) {
actor->level++;
actor->state = WALKING;
if (scroll) check_scroll_up();
} else {
actor->yy++;
}
} else if (joystick & CV_DOWN) {
if (actor->yy <= get_floor_yy(actor->level)) {
actor->state = WALKING;
if (scroll) check_scroll_down();
} else {
actor->yy--;
}
}
break;
case JUMPING:
case FALLING:
actor->x += actor->u.jumping.xvel;
actor->yy += actor->u.jumping.yvel/4;
actor->u.jumping.yvel -= 1;
if (actor->yy <= get_floor_yy(actor->level)) {
actor->yy = get_floor_yy(actor->level);
actor->state = WALKING;
if (scroll) check_scroll_down();
}
break;
}
// don't allow player to travel past left/right edges of screen
if (actor->x == 0) actor->x = 255; // we wrapped around right edge
if (actor->x < 24) actor->x = 24;
// if player lands in a gap, they fall (switch to JUMPING state)
if (actor->state == WALKING &&
is_in_gap(actor->x, levels[actor->level].gap)) {
fall_down(actor);
}
}
void pickup_object(Actor* actor) {
Level* level = &levels[actor->level];
byte objtype = level->objtype;
if (objtype && actor->state == WALKING) {
byte objx = level->objpos * 16 + 24 - XOFS;
if (actor->x >= objx && actor->x < objx+16) {
level->objtype = 0;
refresh_screen();
}
}
}
void move_player() {
struct cv_controller_state ctrl;
cv_get_controller_state(&ctrl, 0);
move_actor(&actors[0], ctrl.joystick, true);
pickup_object(&actors[0]);
}
inline byte iabs(int x) {
return x >= 0 ? x : -x;
}
bool check_collision(Actor* a) {
byte i;
for (i=1; i<MAX_ACTORS; i++) {
Actor* b = &actors[i];
// actors must be on same level
// no need to apply XOFS because both sprites are offset
if (a->level == b->level &&
iabs(a->yy - b->yy) < 8 &&
iabs(a->x - b->x) < 8) {
return true;
}
}
return false;
}
///
void preview_stage() {
scroll_y = levels[MAX_LEVELS-1].ypos;
while (scroll_y > 0) {
wait_vsync();
refresh_screen();
refresh_actors();
scroll_y--;
}
}
void draw_blimp(struct cvu_sprite* sprite) {
sprite->name = 48;
wait_vsync();
cvu_set_sprite(SPRITES, 28, sprite);
sprite->name += 4;
sprite->x += 16;
cvu_set_sprite(SPRITES, 29, sprite);
sprite->name += 4;
sprite->x += 16;
cvu_set_sprite(SPRITES, 30, sprite);
sprite->name += 4;
sprite->x += 16;
cvu_set_sprite(SPRITES, 31, sprite);
refresh_actors();
}
void blimp_pickup_scene() {
struct cvu_sprite sprite;
byte player_screen_y = cvu_vinb(SPRITES + 0); // sprite Y pos
sprite.x = actors[0].x-14;
sprite.y = 240;
sprite.tag = 0x8f;
while (sprite.y != player_screen_y-16) {
draw_blimp(&sprite);
sprite.x -= 48;
sprite.y++;
}
while (sprite.y != 240) {
draw_blimp(&sprite);
sprite.x -= 48;
sprite.y--;
actors[0].yy++;
}
}
void play_scene() {
byte i;
memset(actors, 0, sizeof(actors));
actors[0].state = WALKING;
actors[0].color1 = 0xf;
actors[0].color2 = 0xb;
actors[0].name = 0;
actors[0].x = 64;
actors[0].yy = 8;
actors[0].level = 0;
create_actors_on_level(2);
refresh_screen();
while (actors[0].level != MAX_LEVELS-1) {
wait_vsync();
refresh_actors();
move_player();
// move all the actors
for (i=1; i<MAX_ACTORS; i++) {
move_actor(&actors[i], rand(), false);
}
// see if the player hit another actor
if (cv_get_sprite_collission()) {
if (actors[0].level > 0 && check_collision(&actors[0])) {
fall_down(&actors[0]);
}
}
}
blimp_pickup_scene();
}
void main() {
setup_32_column_font();
cv_set_screen_active(true);
cv_set_vint_handler(&vint_handler);
make_levels();
play_scene();
}