#include #include #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 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; iheight = 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; iypos; // 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; yonscreen) { 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_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; ilevel == 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 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(); }