/* Demonstration game. For more information, see "Making Games for the NES". */ #include #include #include #include #include "common.h" //#link "common.c" // for SMS //#link "fonts.s" #define XOFS 12 // sprite horiz. offset #define BGCOL CV_COLOR_BLUE #define CH_BORDER 64 #define CH_FLOOR 65 #define CH_LADDER 66 const byte char_table[8][8] = { /*{w:8,h:8,brev:1,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[2][16*2] = { /*{w:16,h:16,brev:1,remap:[4,0,1,2,3,5,6,7,8,9],count:2}*/ { 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, },{ 0x00, 0x19, 0x1F, 0x0A, 0x05, 0x07, 0x0E, 0x1C, 0x3A, 0x2A, 0x3C, 0x2E, 0x1E, 0x18, 0x0E, 0x07, 0x00, 0x80, 0x00, 0x00, 0x00, 0xC0, 0xF0, 0x38, 0xFC, 0xF4, 0x7C, 0xB4, 0xB8, 0x68, 0xF0, 0xE0, } }; const byte blimp_sprite_table[4][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, } }; /// 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; } 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 = rndint(1,2); lev->objpos = rndint(1,14); lev->ypos = y; y += lev->height; prevlev = lev; } // top level is special levels[MAX_LEVELS-1].height = 15; levels[MAX_LEVELS-1].gap = 0; levels[MAX_LEVELS-1].ladder1 = 0; levels[MAX_LEVELS-1].ladder2 = 0; levels[MAX_LEVELS-1].objtype = 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 setup_graphics() { #ifndef CV_MSX cvu_memtovmemcpy(PATTERN, (void *)(font_bitmap_0 - '0'*8), 0x800); #endif cvu_memtovmemcpy(PATTERN+8*64, char_table, sizeof(char_table)); cvu_memtovmemcpy(PATTERN+8*128, static_sprite_table, sizeof(static_sprite_table)); #ifndef CV_MSX 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 #endif 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)); } void main() { vdp_setup(); setup_graphics(); cv_set_screen_active(true); cv_set_vint_handler(&vint_handler); make_levels(); play_scene(); }