/* Some of this source file is commented */ /* Keep these headers */ #include #include #include #include /* Standard headers */ #include #include #include #include /* Shared libraries */ #include #include #include #include "gfx/titol_gfx.h" /* Timer defines */ #define ONE_SECOND 32768/1 #define HALF_SECOND 32768/2 #define QUARTER_SECOND 32768/4 /* Tilemap defines */ #define TILE_WIDTH 11 #define TILE_HEIGHT 11 #define TILEMAP_WIDTH 30 #define TILEMAP_HEIGHT 18 #define TILEMAP_X_OFFSET -5 #define TILEMAP_Y_OFFSET -5 /* Block defines */ #define EMPTY 0x00 #define BLOCK 0x01 #define UP_SPIKE 0x02 #define DOWN_SPIKE 0x03 #define LEFT_SPIKE 0x04 #define RIGHT_SPIKE 0x05 #define START_PIPE 0x06 #define END_PIPE 0x07 #define DOOR_1 0x08 #define DOOR_2 0x09 #define BUTTON_1 0x0A #define BUTTON_2 0x0B #define MIMES_TILE 0x0C /* Palette defines */ #define WHITE 0 #define BLOCK_COLOR 1 #define PIPE_COLOR 2 #define BANNER_BLUE 4 #define DOOR_GREEN 5 #define RED 6 #define SPIKE_COLOR 7 #define BLACK 8 #define PURPLE 9 #define ORANGE 10 #define OFF_WHITE 11 #define UNUSED_COLOR 255 /* Other defines */ #define SPAWN_X 50 #define SPAWN_Y 50 #define GRAVITY_VELOCITY 0.024 #define JUMP_VELOCITY -1.34 #define STAGE_5_GRAVITY_VELOCITY 0.015 #define STAGE_5_JUMP_VELOCITY -1.34 #define STAGE_7_JUMP_VELOCITY -2.63 #define STAGE_18_COLLISION_TIMER 58 /* Global variable declarations and whatnot; should be self-explanatory */ extern uint8_t TITOL_map[]; uint8_t TITOL_map_collision[ TILEMAP_HEIGHT * TILEMAP_WIDTH ]; uint16_t blocks_hit[ TILEMAP_WIDTH + 1 ]; uint8_t blocks_hit_length; const gfx_image_t *tile_ptrs[] = { block, block, up_spike, down_spike, left_spike, right_spike, start_pipe, end_pipe, block, block, button, block }; const char *stage_names[] = { "Arrow keys required", "Not always straight forward", "Think before doing", "Alternate control scheme", "Freefloating", "A bit bouncy here", "Dull appearances", "Candy stripes of doom", "Arrow control", "Heavy headwind, here", "No returns, no refunds", "Stay low", "Left Right March", "One Leap of Faith", "Take a break", "Keep hitting it", "Worried about nothing", "Collapse", "Stuttering", "Do you remember?", "Interbetween gravitii", "Mime's folly", "Center keyboarder", "UPPERCASE", "When it feels like it", "Or is it", "Credit page", "Rabbit's power-jump", "Closing shop", "No sweat.", }; const uint8_t stage_13_y[] = { 94, 90, 86, 84, 84, 86, 89, 96, 105, 105, 105, 105, 100, 96, 94, 94, 96, 99, 106, 116, 116, 111, 107, 105, 105, 107, 110, 117, 125, 134, 138, 137, 137, 140, 145, 149, 149, 149, 149, 149, 149, }; typedef struct timer_stuff { uint8_t minutes, seconds, milliseconds; uint32_t timer_counter; } time_t; time_t time; ti_var_t TITOL_appvar; uint8_t temp_w, temp_x, temp_y, temp_z; uint8_t frame_skipper = 0; int darken_value; bool panic_debounce, quit_menu; int player_x; double player_y; double player_y_velocity; bool game_ended, player_has_died, player_face_right; bool button_pressed, button_was_pressed; bool door_open; uint8_t door_y; uint8_t stage_10_timer, stage_13_index, stage_16_counter, stage_19_timer, stage_29_seconds; uint16_t stage_30_colors[sizeof(titol_gfx_pal)/sizeof(uint16_t)]; bool stage_13_state, stage_14_has_leaped, stage_15_trick, stage_27_trick; uint8_t stage; int deaths; gfx_image_t *player_back; uint8_t block_2_color[3]; /* Function prototypes */ void button_door(void); uint8_t collision( uint8_t tile_x, uint8_t tile_y ); void debounce(void); void draw_level(bool new_level); void draw_tile( uint8_t tile_x, uint8_t tile_y ); bool hit_block( uint24_t hit_x, uint8_t hit_y, uint8_t block_hit ); void move_left(bool face_dir); void move_right(bool face_dir); void respawn(void); void stage_30_palette_reset(void); void stage_end(void); void start_new_stage(bool next_level); void tick_timer(void); /* Main function (actual code) */ void main(void) { uint8_t key3, key4, key5, key6, key7; uint8_t menu_option; bool menu_debounce; /* Seed the random number generator */ srand(rtc_Time()); /* Allocate space for the temporary sprite to save the background */ malloc(0); player_back = gfx_AllocSprite( player_left->width, player_left->height, malloc ); /* Initialize graphics */ gfx_Begin(gfx_8bpp); /* Set up the palette */ gfx_SetPalette(titol_gfx_pal, sizeof(titol_gfx_pal), 0); gfx_SetDrawBuffer(); /* Disable everything in the timers so they don't run when we don't want them to */ timer_Control = TIMER1_DISABLE; /* By using the 32768 kHz clock, we can count for exactly 1 second here, or a different interval of time */ timer_1_ReloadValue = ONE_SECOND; /* Program code starts here */ ti_CloseAll(); TITOL_appvar = ti_Open("TITOLCE","r"); if ( TITOL_appvar ) { /* Savefile does exist; read each variable */ ti_Read( &time.minutes, sizeof(uint8_t), sizeof(time.minutes) / sizeof(uint8_t), TITOL_appvar ); ti_Read( &time.seconds, sizeof(uint8_t), sizeof(time.seconds) / sizeof(uint8_t), TITOL_appvar ); ti_Read( &time.milliseconds, sizeof(uint8_t), sizeof(time.milliseconds) / sizeof(uint8_t), TITOL_appvar ); ti_Read( &time.timer_counter, sizeof(uint32_t), sizeof(time.timer_counter) / sizeof(uint32_t), TITOL_appvar ); ti_Read( &stage, sizeof(uint8_t), sizeof(stage) / sizeof(uint8_t), TITOL_appvar ); ti_Read( &deaths, sizeof(int), sizeof(deaths) / sizeof(int), TITOL_appvar ); ti_Read( &stage_15_trick, sizeof(bool), sizeof(stage_15_trick) / sizeof(bool), TITOL_appvar ); ti_Read( &stage_27_trick, sizeof(bool), sizeof(stage_15_trick) / sizeof(bool), TITOL_appvar ); } else { /* Savefile doesn't exist */ time.minutes = 0; time.seconds = 0; time.milliseconds = 0; time.timer_counter = ONE_SECOND; stage = 1; deaths = 0; stage_15_trick = false; stage_27_trick = false; } ti_CloseAll(); MENU: gfx_ZeroScreen(); gfx_SetColor( BANNER_BLUE ); gfx_VertLine_NoClip( 6, 30, 48 ); gfx_VertLine_NoClip( 190, 26, 67 ); gfx_SetColor( BLACK ); gfx_VertLine_NoClip( 159, 144, 70 ); gfx_SetTextBGColor( WHITE ); gfx_SetTextFGColor( BANNER_BLUE ); gfx_SetTextScale( 1, 2 ); gfx_PrintStringXY( "This is the Only Level", 8, 41 ); gfx_SetTextScale( 1, 1 ); gfx_PrintStringXY( "Adventures in singularity", 8, 62 ); gfx_PrintStringXY( "Erase Game", 206, 76 ); gfx_SetTextFGColor( PURPLE ); gfx_PrintStringXY( "Play or Continue", 206, 36 ); gfx_PrintStringXY( "Level One", 216, 46 ); gfx_SetTextFGColor( DOOR_GREEN ); gfx_PrintStringXY( "Armor Games", 206, 56 ); gfx_SetTextFGColor( ORANGE ); gfx_PrintStringXY( "Credits", 206, 66 ); gfx_SetTextFGColor( BLACK ); gfx_PrintStringXY( "inspired by jmtb02", 173, 173 ); gfx_PrintStringXY( "Elephants sometimes do forget...", 52, 110 ); gfx_PrintStringXY( "...the rest of the levels.", 81, 119 ); gfx_Sprite_NoClip( jmtb02, 84, 144 ); gfx_Sprite_NoClip( player_right, 192, 33 ); quit_menu = false; menu_option = 0; menu_debounce = false; debounce(); while ( !((kb_ScanGroup(kb_group_1) & kb_2nd) || (kb_ScanGroup(kb_group_6) & kb_Enter)) && !quit_menu ) { key7 = kb_ScanGroup(kb_group_7); if ( key7 & kb_Up ) { if ( !menu_debounce && menu_option > 0 ) { menu_option--; } menu_debounce = true; } else if ( key7 & kb_Down ) { if ( !menu_debounce && menu_option < 3 ) { menu_option++; } menu_debounce = true; } else { menu_debounce = false; } if ( kb_ScanGroup(kb_group_6) & kb_Clear ) { quit_menu = true; } gfx_Sprite_NoClip( player_right, 192, ((menu_option > 0) + menu_option) * 10 + 33 ); gfx_BlitBuffer(); gfx_SetColor( WHITE ); gfx_FillRectangle_NoClip( 192, ((menu_option > 0) + menu_option) * 10 + 33, 11, 11 ); } if ( quit_menu ) { goto END; } else if ( !menu_option ) { goto START_GAME; } else if ( menu_option == 1 ) { goto ARMOR_GAMES; } else if ( menu_option == 2 ) { goto CREDITS; } else if ( menu_option == 3 ) { goto ERASE_GAME; } ARMOR_GAMES: gfx_ZeroScreen(); gfx_SetColor( BANNER_BLUE ); gfx_VertLine_NoClip( 142, 80, 48 ); gfx_SetColor( BLACK ); gfx_Rectangle_NoClip( 125, 194, 71, 16 ); gfx_SetTextBGColor( WHITE ); gfx_SetTextFGColor( BANNER_BLUE ); gfx_SetTextScale( 1, 2 ); gfx_PrintStringXY( "This is the Only Level", 144, 91 ); gfx_SetTextScale( 1, 1 ); gfx_PrintStringXY( "Adventures in singularity", 144, 112 ); gfx_PrintStringXY( "Play hundreds of", 0, 88 ); gfx_PrintStringXY( "awesome online flash", 0, 97 ); gfx_PrintStringXY( "games like this at", 0, 106 ); gfx_PrintStringXY( "armorgames.com", 0, 115 ); gfx_SetTextFGColor( BLACK ); gfx_PrintStringXY( "Main Menu", 129, 198 ); gfx_Sprite_NoClip( player_right, 113, 196 ); debounce(); while ( !((kb_ScanGroup(kb_group_1) & kb_2nd) || (kb_ScanGroup(kb_group_6) & kb_Enter)) && !(kb_ScanGroup(kb_group_6) & kb_Clear) ) { gfx_BlitBuffer(); } goto MENU; CREDITS: gfx_ZeroScreen(); gfx_SetColor( BANNER_BLUE ); gfx_VertLine_NoClip( 142, 70, 70 ); gfx_SetColor( BLACK ); gfx_Rectangle_NoClip( 31, 194, 257, 16 ); gfx_SetTextBGColor( WHITE ); gfx_SetTextFGColor( BANNER_BLUE ); gfx_SetTextScale( 1, 2 ); gfx_PrintStringXY( "This is the Only Level", 144, 91 ); gfx_SetTextScale( 1, 1 ); gfx_PrintStringXY( "Adventures in singularity", 144, 112 ); gfx_PrintStringXY( "Developed by", 0, 70 ); gfx_PrintStringXY( "Armor Games Inc.", 29, 79 ); gfx_PrintStringXY( "Concept by", 0, 88 ); gfx_PrintStringXY( "jmtb02", 95, 97 ); gfx_PrintStringXY( "Ported by", 0, 106 ); gfx_PrintStringXY( "Josiah Winslow", 44, 115 ); gfx_PrintStringXY( "Testing by", 0, 124 ); gfx_PrintStringXY( "So and So", 80, 133 ); gfx_SetTextFGColor( BLACK ); gfx_PrintStringXY( "Main Menu, and Unlock Stage 27 For Me", 35, 198 ); gfx_Sprite_NoClip( player_right, 19, 196 ); debounce(); while ( !((kb_ScanGroup(kb_group_1) & kb_2nd) || (kb_ScanGroup(kb_group_6) & kb_Enter) || (kb_ScanGroup(kb_group_6) & kb_Clear)) ) { gfx_BlitBuffer(); } stage_27_trick = (stage == 27); goto MENU; ERASE_GAME: gfx_ZeroScreen(); gfx_SetColor( RED ); gfx_HorizLine_NoClip( 0, 110, 320 ); gfx_HorizLine_NoClip( 0, 175, 320 ); gfx_SetColor( BLACK ); gfx_Rectangle_NoClip( 27, 194, 145, 16 ); gfx_Rectangle_NoClip( 200, 194, 93, 16 ); gfx_SetTextBGColor( WHITE ); gfx_SetTextFGColor( BANNER_BLUE ); gfx_SetTextScale( 1, 2 ); gfx_PrintStringXY( "Are you sure you want to erase your game?", 18, 0 ); gfx_SetTextFGColor( RED ); gfx_SetTextScale( 3, 3 ); gfx_PrintStringXY( "Are you sure?", 22, 114 ); gfx_SetTextScale( 1, 1 ); gfx_PrintStringXY( "This is not a clever trick or part of the game,", 7, 144 ); gfx_PrintStringXY( "this will REALLY erase it", 79, 154 ); gfx_PrintStringXY( "and there is no going back.", 71, 164 ); gfx_PrintStringXY( "Yes, Erase It Please", 32, 198 ); gfx_SetTextFGColor( BLACK ); gfx_PrintStringXY( "Back To Menu", 204, 198 ); gfx_Sprite_NoClip( player_right, 188, 196 ); debounce(); quit_menu = false; menu_option = 1; menu_debounce = false; while ( !((kb_ScanGroup(kb_group_1) & kb_2nd) || (kb_ScanGroup(kb_group_6) & kb_Enter)) && !quit_menu ) { key7 = kb_ScanGroup(kb_group_7); if ( key7 & kb_Left ) { if ( !menu_debounce && menu_option ) { menu_option--; } menu_debounce = true; } else if ( key7 & kb_Right ) { if ( !menu_debounce && !menu_option ) { menu_option++; } menu_debounce = true; } else { menu_debounce = false; } if ( kb_ScanGroup(kb_group_6) & kb_Clear ) { quit_menu = true; } gfx_Sprite_NoClip( player_right, menu_option * 173 + 15, 196 ); gfx_BlitBuffer(); gfx_SetColor( WHITE ); gfx_FillRectangle_NoClip( menu_option * 173 + 15, 196, 11, 11 ); } if ( quit_menu ) { goto MENU; } else if ( menu_option == 0 ) { time.minutes = 0; time.seconds = 0; time.milliseconds = 0; time.timer_counter = ONE_SECOND; stage = 1; deaths = 0; stage_15_trick = false; stage_27_trick = false; } goto MENU; START_GAME: timer_1_Counter = time.timer_counter; /* Enable the timer, set it to the 32768 kHz clock, enable an interrupt once it reaches 0, and make it count down */ timer_Control = TIMER1_ENABLE | TIMER1_32K | TIMER1_0INT | TIMER1_DOWN; timer_IntStatus = TIMER1_RELOADED; /* Randomize the color of the blocks and spikes */ gfx_palette[BLOCK_COLOR] = gfx_RGBTo1555( randInt( 0, 200 ), randInt( 0, 200 ), randInt( 0, 200 ) ); gfx_palette[SPIKE_COLOR] = gfx_RGBTo1555( randInt( 0, 200 ), randInt( 0, 200 ), randInt( 0, 200 ) ); /* Set the panic button to un-pressed */ panic_debounce = false; /* Set the game to not finished yet */ game_ended = false; /* Start the stage without a level transition */ start_new_stage(false); while ( !(game_ended || kb_ScanGroup(kb_group_6) & kb_Clear) ) { if ( frame_skipper == 0 ) { gfx_Sprite_NoClip( player_back, player_x, player_y ); } if ( stage == 10 ) { if ( ++stage_10_timer == 3 ) { stage_10_timer = 0; move_left(false); } } else if ( stage == 19 ) { if ( ++stage_19_timer == 50 ) { stage_19_timer = 0; } else { continue; } } key3 = kb_ScanGroup(kb_group_3); key4 = kb_ScanGroup(kb_group_4); key5 = kb_ScanGroup(kb_group_5); key6 = kb_ScanGroup(kb_group_6); key7 = kb_ScanGroup(kb_group_7); if ( (stage != 23 && key7) || (stage == 23 && ( key3 || key4 || key5 )) ) { if ( ( stage != 23 && key7 & kb_Right) || ( stage == 23 && key5 & kb_6 ) ) { if ( stage == 2 ) { /* You move left if you go right in Stage 2 */ move_left(true); } else if ( stage == 9 ) { for( temp_x = 0; temp_x < TILE_WIDTH * 0.5; temp_x++ ) { move_right(true); } } else if ( stage == 13 ) { if ( player_y_velocity == 0.0 ) { player_face_right = stage_13_index > 0; if ( stage_13_state == false ) { stage_13_state = true; stage_13_index++; player_x += 6; player_y = stage_13_y[stage_13_index]; } } } else { move_right(true); } } else if ( ( stage != 23 && key7 & kb_Left) || ( stage == 23 && key3 & kb_4 ) ) { if ( stage == 2 ) { /* You move right if you go left in Stage 2 */ move_right(true); } else if ( stage == 9 ) { for( temp_x = 0; temp_x < TILE_WIDTH * 0.5; temp_x++ ) { move_left(true); } } else if ( stage == 11 ) { /* You can't move left in Stage 11 */ player_face_right = false; } else if ( stage == 13 ) { if ( player_y_velocity == 0.0 ) { player_face_right = stage_13_index > 0; if ( stage_13_state == true ) { stage_13_state = false; stage_13_index++; player_x += 6; player_y = stage_13_y[stage_13_index]; } } } else { move_left(true); } } if ( stage == 4 ) { if ( key7 & kb_Up || key7 & kb_Down || key7 & kb_Left || key7 & kb_Right ) { player_y_velocity = 0.0; if ( key7 & kb_Up ) { player_y--; } if ( key7 & kb_Down ) { player_y++; } } } } /* Gravity functions */ if ( player_y_velocity >= 0.0 && hit_block( player_x, player_y + player_y_velocity, 1 ) ) { /* Player has hit the floor */ /* Correct his position */ player_y += player_y_velocity - TILEMAP_Y_OFFSET; player_y = floor( player_y / TILE_HEIGHT ) * TILE_HEIGHT; player_y += TILEMAP_Y_OFFSET; /* Player bounces with some velocity in Stage 6; this is figured out with a calculation */ if ( stage == 6 ) { player_y_velocity = -sqrt( 2 * GRAVITY_VELOCITY * (player_y-(TILE_HEIGHT-TILEMAP_Y_OFFSET+1)) ); } else { /* If velocity is high enough (slightly higher than velocity after a jump), give player a slight bounce */ if ( player_y_velocity >= 1.48 ) { player_y_velocity = -0.54; } else { player_y_velocity = 0.0; } } /* Add blocks to hit-blocks queue for Stage 18 */ if ( stage == 18 ) { for ( temp_z = 0; temp_z <= TILE_WIDTH - 1; temp_z += TILE_WIDTH - 1 ) { temp_x = (player_x + temp_z - TILEMAP_X_OFFSET) / TILE_WIDTH; temp_y = (player_y + TILE_HEIGHT - TILEMAP_Y_OFFSET) / TILE_HEIGHT; temp_w = TITOL_map[temp_y * TILEMAP_WIDTH + temp_x]; if ( temp_w == BLOCK || temp_w == DOOR_1 || temp_w == DOOR_2 ) { if ( TITOL_map_collision[temp_y * TILEMAP_WIDTH + temp_x] == STAGE_18_COLLISION_TIMER ) { TITOL_map_collision[temp_y * TILEMAP_WIDTH + temp_x]--; blocks_hit[ blocks_hit_length ] = temp_y * TILEMAP_WIDTH + temp_x; /* Re-draw the relevant tile */ draw_tile( temp_x, temp_y ); blocks_hit_length++; } } } } if ( (stage != 23 && stage != 28 && key7 & kb_Up) || (stage == 23 && key4 & kb_8) || (stage == 28 && key6 & kb_Power) ) { /* Player is jumping */ if ( stage == 4 || stage == 6 || stage == 7 || stage == 12 || stage == 13 || (stage == 14 && stage_14_has_leaped == true) ) { /* You are not allowed to jump; do nothing */ } else if ( stage == 5 ) { player_y_velocity = STAGE_5_JUMP_VELOCITY; } else { player_y_velocity = JUMP_VELOCITY; if ( stage == 14 ) { stage_14_has_leaped = true; } } } if ( stage == 8 ) { temp_x = (player_x + (TILE_WIDTH/2) - TILEMAP_X_OFFSET) / TILE_WIDTH; if ( (collision( temp_x, (player_y - TILEMAP_Y_OFFSET) / TILE_HEIGHT + 1) == BLOCK) ) { if ( (temp_x % 3 == 1) ) { player_has_died = true; } } } } else { /* Player is falling */ if ( !((stage == 4) && (key7 & kb_Up || key7 & kb_Down || key7 & kb_Left || key7 & kb_Right)) ) { if ( stage == 5 ) { player_y_velocity += STAGE_5_GRAVITY_VELOCITY; } else if ( stage == 13 && stage_13_index > 0 ) { /* Do nothing */ } else if ( stage == 21 ) { temp_x = (player_x + (TILE_WIDTH/2) - TILEMAP_X_OFFSET) / TILE_WIDTH; if ( (temp_x % 2 == 1) ) { player_y_velocity -= GRAVITY_VELOCITY; } else { player_y_velocity += GRAVITY_VELOCITY; } } else { player_y_velocity += GRAVITY_VELOCITY; } } } /* If player's head is embedded into ceiling, correct this */ if ( player_y_velocity <= 0.0 && hit_block( player_x, player_y + player_y_velocity, 1 ) ) { player_y += player_y_velocity - TILEMAP_Y_OFFSET; player_y = ( floor( player_y / TILE_HEIGHT ) + 1 ) * TILE_HEIGHT; player_y += TILEMAP_Y_OFFSET; if ( stage == 6 ) { player_y_velocity = -player_y_velocity; } else { player_y_velocity = 0.0; } } /* Add velocity to Y-position */ player_y += player_y_velocity; /* Spike collision */ if ( hit_block( player_x, player_y, 2 ) ) { if ( stage == 7 ) { player_y_velocity = STAGE_7_JUMP_VELOCITY; } else { player_has_died = true; } } if ( stage == 18 ) { if ( blocks_hit_length > 0 ) { /* Handle collision timers for Stage 18 */ for ( temp_x = 0; temp_x < blocks_hit_length; temp_x++ ) { if ( TITOL_map_collision[ blocks_hit[ temp_x ] ] < STAGE_18_COLLISION_TIMER ) { if ( TITOL_map_collision[ blocks_hit[ temp_x ] ] ) { /* Timer is active */ /* Decrement timer */ TITOL_map_collision[ blocks_hit[ temp_x ] ]--; } else { /* Timer is stopped */ /* Re-draw the relevant tile */ draw_tile( blocks_hit[ temp_x ] % TILEMAP_WIDTH, blocks_hit[ temp_x ] / TILEMAP_WIDTH ); /* Delete the block from the queue */ for ( temp_y = temp_x; temp_y < blocks_hit_length - 1; temp_y++ ) { blocks_hit[temp_y] = blocks_hit[temp_y + 1]; } blocks_hit_length--; } } } } } else if ( stage == 30 ) { /* Change palettes for Stage 30 */ temp_w = TILE_WIDTH * 9 + SPAWN_X; if ( player_x > temp_w ) { darken_value = 0; } else { darken_value = (255 * (temp_w - player_x)) / temp_w; } for ( temp_x = 0; temp_x < sizeof(stage_30_colors)/sizeof(uint16_t); temp_x++ ) { if ( temp_x != BANNER_BLUE && temp_x != PIPE_COLOR && temp_x != OFF_WHITE ) { gfx_palette[ temp_x ] = gfx_Darken( stage_30_colors[ temp_x ], (uint8_t)(darken_value) ); } } } if ( player_y >= TILE_HEIGHT * (TILEMAP_HEIGHT - 1) - TILEMAP_Y_OFFSET ) { player_has_died = true; } /* Panic button (GRAPH) */ if ( kb_ScanGroup(kb_group_1) & kb_Graph ) { if ( !panic_debounce ) { panic_debounce = true; player_has_died = true; } } else { panic_debounce = false; } /* Has player died? */ if ( player_has_died ) { /* Deaths cap off at 9999 */ /* I don't wanna have to use more than a 16 bit int here */ if ( deaths < 9999 ) { deaths++; } respawn(); } /* Run one iteration of the timer */ tick_timer(); /* Every function of the button and door */ button_door(); /* Check whether or not the stage has ended */ stage_end(); /* Draw the player where he is, and save the temporary background sprite */ if ( ++frame_skipper == 2 ) { frame_skipper = 0; gfx_GetSprite_NoClip( player_back, player_x, player_y ); gfx_TransparentSprite_NoClip( (player_face_right == false ? player_left : player_right), player_x, player_y ); gfx_BlitBuffer(); } } stage_30_palette_reset(); goto MENU; END: /* If you quit out of Stage 15, make sure that's recognized */ stage_15_trick = (stage == 15); /* Save your game */ ti_CloseAll(); TITOL_appvar = ti_Open("TITOLCE","w"); ti_Write( &time.minutes, sizeof(uint8_t), sizeof(time.minutes) / sizeof(uint8_t), TITOL_appvar ); ti_Write( &time.seconds, sizeof(uint8_t), sizeof(time.seconds) / sizeof(uint8_t), TITOL_appvar ); ti_Write( &time.milliseconds, sizeof(uint8_t), sizeof(time.milliseconds) / sizeof(uint8_t), TITOL_appvar ); ti_Write( &time.timer_counter, sizeof(uint32_t), sizeof(time.timer_counter) / sizeof(uint32_t), TITOL_appvar ); ti_Write( &stage, sizeof(uint8_t), sizeof(stage) / sizeof(uint8_t), TITOL_appvar ); ti_Write( &deaths, sizeof(int), sizeof(deaths) / sizeof(int), TITOL_appvar ); ti_Write( &stage_15_trick, sizeof(bool), sizeof(stage_15_trick) / sizeof(bool), TITOL_appvar ); ti_Write( &stage_27_trick, sizeof(bool), sizeof(stage_27_trick) / sizeof(bool), TITOL_appvar ); ti_CloseAll(); /* Clean up and exit */ gfx_End(); prgm_CleanUp(); } /* Functions */ /* Every function of the button and door */ void button_door(void) { /* Depends on stage */ if ( stage == 24 ) { door_open = kb_ScanGroup(kb_group_2) == kb_Alpha; } else if ( stage == 25 ) { door_open = !(time.seconds % 10); } else if ( stage == 29 ) { if ( door_open ) { /* Stage 29 timer goes for 3.5 seconds */ if ( stage_29_seconds + ((~timer_2_Counter & 0x7FFF) / 32768.0) < 4.5 ) { if(timer_IntStatus & TIMER2_RELOADED) { stage_29_seconds++; timer_IntStatus = TIMER2_RELOADED; } } else { /* Disable the timer */ timer_Control ^= TIMER2_ENABLE; door_open = false; } } } /* Check if the button has been pressed */ button_pressed = hit_block( player_x, player_y, 4 ); if ( button_pressed ) { if ( !button_was_pressed ) { /* Button was newly pressed */ if ( stage == 3 ) { door_open = false; } else if ( stage == 4 || stage == 15 || stage == 18 || stage == 24 || stage == 25 || stage > 30 ) { /* Do nothing */ } else if ( stage == 16 ) { if ( stage_16_counter < 5 ) { if ( ++stage_16_counter == 5 ) { door_open = true; } } } else if ( stage == 29 ) { /* Set timer 2 settings, enable the timer, set it to the 32768 kHz clock, enable an interrupt once it reaches 0, and make it count down */ stage_29_seconds = 0; timer_2_ReloadValue = timer_2_Counter = ONE_SECOND; timer_Control |= TIMER2_ENABLE | TIMER2_32K | TIMER2_0INT | TIMER2_DOWN; timer_IntStatus = TIMER2_RELOADED; door_open = true; } else { door_open = true; } /* Draw the button */ if ( stage != 20 ) { draw_tile( 14, 7 ); } } } else { if ( button_was_pressed ) { /* Button just stopped being pressed */ /* Draw the button */ if ( stage != 20 ) { draw_tile( 14, 7 ); } } } /* Store status of button */ button_was_pressed = button_pressed; /* Draw the door where it is supposed to go */ if ( stage != 20 ) { if ( ( stage != 26 && !door_open ) || ( stage == 26 && door_open ) ) { if ( door_y < (TILE_HEIGHT * 2) ) { door_y++; gfx_SetColor( DOOR_GREEN ); gfx_HorizLine_NoClip( 271, 137 + door_y, 4 ); } } else { if ( door_y > 0 ) { gfx_SetColor( WHITE ); gfx_HorizLine_NoClip( 271, 137 + door_y, 4 ); door_y--; } } } } /* Returns number based on tile on tilemap 0 = not collidable 1 = block / closed door 2 = spike 3 = exit pipe 4 = button */ uint8_t collision( uint8_t tile_x, uint8_t tile_y ) { if ( !(stage == 18 && TITOL_map_collision[tile_y * TILEMAP_WIDTH + tile_x] == 0) ) { uint8_t tile = TITOL_map[TILEMAP_WIDTH * tile_y + tile_x]; if ( tile == BLOCK || ((tile == DOOR_1 || tile == DOOR_2) && !door_open ) || (stage == 22 && tile == MIMES_TILE) ) { return 1; } if ( tile >= UP_SPIKE && tile <= RIGHT_SPIKE ) { return 2; } if ( tile == END_PIPE ) { return 3; } if ( tile == BUTTON_1 || tile == BUTTON_2 ) { return 4; } } return 0; } /* Wait until 2nd, Enter, and Clear are released before continuing */ void debounce(void) { while ( (kb_ScanGroup(kb_group_1) & kb_2nd) || (kb_ScanGroup(kb_group_6) & kb_Enter) || (kb_ScanGroup(kb_group_6) & kb_Clear) ) { gfx_BlitBuffer(); } } /* Draw one tile from the tilemap onto the screen */ void draw_tile( uint8_t tile_x, uint8_t tile_y ) { unsigned xpos = TILE_WIDTH * tile_x + TILEMAP_X_OFFSET; unsigned ypos = TILE_HEIGHT * tile_y + TILEMAP_Y_OFFSET; uint8_t tile = TITOL_map[TILEMAP_WIDTH * tile_y + tile_x]; switch( tile ) { case EMPTY: // empty space case MIMES_TILE: // mime's tile (undrawn, only for purposes of collision) case BUTTON_2: // 2nd and 3rd button tiles (undrawn, only for purposes of collision) case DOOR_1: // door (undrawn by sprites, but animated) case DOOR_2: // 2nd door tile (undrawn, only for purposes of collision) break; case BLOCK: // block if ( stage == 8 && tile_x % 3 == 1 ) { gfx_SetColor( gfx_RGBTo1555( block_2_color[0], block_2_color[1], block_2_color[2] ) ); gfx_FillRectangle( xpos, ypos, TILE_WIDTH, TILE_HEIGHT ); } else if ( stage == 18 && TITOL_map_collision[ TILEMAP_WIDTH * tile_y + tile_x ] < STAGE_18_COLLISION_TIMER ) { if ( TITOL_map_collision[ TILEMAP_WIDTH * tile_y + tile_x ] == 0 ) { gfx_SetColor( WHITE ); gfx_FillRectangle( xpos, ypos, TILE_WIDTH, TILE_HEIGHT ); } else { gfx_Sprite( block_collapse, xpos, ypos ); } } else if ( stage == 21 && tile_x % 2 == 1 ) { gfx_SetColor( gfx_RGBTo1555( block_2_color[0], block_2_color[1], block_2_color[2] ) ); gfx_FillRectangle( xpos, ypos, TILE_WIDTH, TILE_HEIGHT ); } else { gfx_Sprite( block, xpos, ypos ); } break; case BUTTON_1: // button if ( button_pressed ) { gfx_Sprite_NoClip( button_down, xpos, ypos ); } else { gfx_Sprite_NoClip( button, xpos, ypos ); } break; default: // anything else; no special rules for drawing everything else gfx_TransparentSprite_NoClip( tile_ptrs[tile], xpos, ypos ); break; } } /* Draw the entire level */ void draw_level(bool new_level) { uint8_t tilemap_x, tilemap_y; /* Randomize the color of the blocks and spikes */ //gfx_palette[BLOCK_COLOR] = randInt( 0x100, 0xF000 ); //gfx_palette[SPIKE_COLOR] = randInt( 0x100, 0xF000 ); if ( new_level ) { gfx_palette[BLOCK_COLOR] = gfx_RGBTo1555( randInt( 0, 200 ), randInt( 0, 200 ), randInt( 0, 200 ) ); gfx_palette[SPIKE_COLOR] = gfx_RGBTo1555( randInt( 0, 200 ), randInt( 0, 200 ), randInt( 0, 200 ) ); } for( temp_x = 0; temp_x < 3; temp_x++ ) { block_2_color[temp_x] = randInt( 0, 200 ); } /* Loop through every tile and draw it */ gfx_ZeroScreen(); if ( stage != 20 ) { for( tilemap_x = 0; tilemap_x < TILEMAP_WIDTH; tilemap_x++ ) { for( tilemap_y = 0; tilemap_y < TILEMAP_HEIGHT; tilemap_y++ ) { draw_tile( tilemap_x, tilemap_y ); } } } /* Draw stats rectangle */ gfx_SetColor( BANNER_BLUE ); gfx_FillRectangle_NoClip( 0, 193, 320, 47 ); /* Text settings for stats */ gfx_SetTextFGColor( OFF_WHITE ); gfx_SetTextBGColor( BANNER_BLUE ); gfx_SetTextTransparentColor( UNUSED_COLOR ); /* Current level and stage */ gfx_SetTextScale( 1, 1 ); gfx_PrintStringXY( "Level 1 Stage ", 2, 196 ); gfx_PrintInt( stage, 1 + (stage >= 10) ); if ( stage >= 1 && stage <= 30 ) { gfx_PrintStringXY( stage_names[stage - 1], 2, 205 ); } else { gfx_PrintStringXY( "undefined", 2, 205 ); } /* Number of deaths */ gfx_SetTextScale( 1, 1 ); gfx_PrintStringXY( "Deaths: ", 212, 214 ); gfx_SetTextXY( 265, 214 ); gfx_PrintInt( deaths, 1 + (deaths >= 10) + (deaths >= 100) + (deaths >= 1000) ); } /* Display the ending! */ void end_game(void) { /* Disable the timer */ timer_Control ^= TIMER1_ENABLE; game_ended = true; for( temp_x = 193; temp_x <= 240; temp_x++ ) { gfx_ShiftDown(1); gfx_HorizLine_NoClip( 0, 0, 320 ); gfx_BlitBuffer(); } gfx_SetTextBGColor( WHITE ); gfx_SetTextFGColor( BANNER_BLUE ); gfx_SetTextScale( 6, 6 ); gfx_PrintStringXY( "YES.", 2, 5 ); gfx_SetTextScale( 2, 2 ); gfx_PrintStringXY( "Here are your stats:", 0, 85 ); gfx_PrintStringXY( "Thanks for Playing!", 0, 209 ); gfx_SetTextFGColor( ORANGE ); gfx_SetTextScale( 3, 3 ); gfx_SetTextXY( 0, 129 ); gfx_PrintInt( time.minutes, 2 ); gfx_PrintString( ":" ); gfx_PrintInt( time.seconds, 2 ); gfx_PrintString( "." ); gfx_PrintInt( time.milliseconds, 2 ); gfx_SetTextFGColor( RED ); gfx_PrintStringXY( "Deaths: ", 0, 160 ); gfx_PrintInt( deaths, 1 + (deaths >= 10) + (deaths >= 100) + (deaths >= 1000) ); gfx_SetTextFGColor( BLACK ); gfx_SetTextScale( 1, 1 ); gfx_PrintStringXY( "Yes, yes it is. You have", 161, 5 ); gfx_PrintStringXY( "beaten the only level", 161, 14 ); gfx_PrintStringXY( "in the entire game. What", 161, 23 ); gfx_PrintStringXY( "was one level surely", 161, 32 ); gfx_PrintStringXY( "was more than that!", 161, 41 ); gfx_PrintStringXY( "We are very proud of you.", 75, 58 ); gfx_PrintStringXY( "We think you are really, truly special.", 34, 67 ); for( temp_x = 1; temp_x < 240; temp_x++ ) { gfx_BlitRectangle( gfx_buffer, 0, 0, 320, temp_x ); } debounce(); quit_menu = false; while ( !((kb_ScanGroup(kb_group_1) & kb_2nd) || (kb_ScanGroup(kb_group_6) & kb_Enter) || (kb_ScanGroup(kb_group_6) & kb_Clear)) && !quit_menu ) { if ( kb_ScanGroup(kb_group_1) & kb_Del ) { quit_menu = true; } gfx_BlitBuffer(); } time.minutes = 0; time.seconds = 0; time.milliseconds = 0; time.timer_counter = ONE_SECOND; if ( quit_menu ) { stage = 31; } else { stage = 1; } deaths = 0; stage_15_trick = false; stage_27_trick = false; } /* Take collision as a function of player X and Y */ bool hit_block( uint24_t hit_x, uint8_t hit_y, uint8_t block_hit ) { /* Some statements have inline conditional functions */ /* This is the only way I know how to make collision more lenient with some blocks */ /* Don't hurt me */ uint8_t x_off1 = (hit_x - TILEMAP_X_OFFSET) / TILE_WIDTH; uint8_t x_off2 = (hit_x + ( ( TILE_WIDTH - ((block_hit == 4) * 3) ) / ((block_hit == 2 || block_hit == 3) + 1) ) - TILEMAP_X_OFFSET - 1) / TILE_WIDTH; uint8_t y_off1 = (hit_y - TILEMAP_Y_OFFSET) / TILE_HEIGHT; uint8_t y_off2 = (hit_y + floor( TILE_HEIGHT / (((block_hit == 2 || block_hit == 3 || block_hit == 4) * 1.5) + 1) ) - TILEMAP_Y_OFFSET - 1) / TILE_HEIGHT; /* Apparently this is less code than looping? */ return (collision( x_off1, y_off1 ) == block_hit) || (collision( x_off1, y_off2 ) == block_hit) || (collision( x_off2, y_off1 ) == block_hit) || (collision( x_off2, y_off2 ) == block_hit) ; } /* Move left */ void move_left(bool face_dir) { /* Move player left and face him the right direction, if possible */ if ( !hit_block( player_x - 1, player_y, 1 ) ) { player_x--; if ( face_dir ) { player_face_right = stage == 2; } } } /* Move right */ void move_right(bool face_dir) { /* Move player right and face him the right direction, if possible */ if ( !hit_block( player_x + 1, player_y, 1 ) ) { player_x++; if ( face_dir ) { player_face_right = stage != 2; } } } /* Respawn the player, for example after dying */ void respawn(void) { /* Set player at spawn point, with 0 velocity */ player_x = SPAWN_X; player_y = SPAWN_Y; player_y_velocity = 0.0; /* Make sure player is alive */ player_has_died = false; /* Face player some direction, depending on stage */ if ( stage == 2 || stage == 10 ) { player_face_right = true; } else { player_face_right = false; } /* Set the button not pressed */ button_pressed = false; button_was_pressed = false; if ( stage != 20 ) { draw_tile( 14, 7 ); } /* Set the door to open or not open, depending on the conditions */ if ( stage == 3 || stage == 4 || stage == 12 || stage == 13 || (stage == 15 && stage_15_trick) || stage == 26 || (stage == 27 && stage_27_trick) ) { door_open = true; } else { door_open = false; } /* Depends on stage */ if ( stage == 10 ) { stage_10_timer = 0; } else if ( stage == 13 ) { stage_13_index = 0; stage_13_state = true; } else if ( stage == 14 ) { stage_14_has_leaped = false; } else if ( stage == 16 ) { stage_16_counter = 0; } else if ( stage == 18 ) { memset_fast( TITOL_map_collision, STAGE_18_COLLISION_TIMER, sizeof(TITOL_map_collision) ); memset_fast( blocks_hit, 0, sizeof(blocks_hit) ); blocks_hit_length = 0; draw_level(false); } else if ( stage == 19 ) { stage_19_timer = 0; } /* Re-print the number of deaths */ gfx_SetTextScale( 1, 1 ); gfx_PrintStringXY( "Deaths: ", 212, 214 ); gfx_SetTextXY( 265, 214 ); gfx_PrintInt( deaths, 1 + (deaths >= 10) + (deaths >= 100) + (deaths >= 1000) ); } /* Reset the palette after Stage 30 inevitably alters it */ void stage_30_palette_reset(void) { if ( stage == 30 ) { for ( temp_x = 0; temp_x < sizeof(stage_30_colors)/sizeof(uint16_t); temp_x++ ) { gfx_palette[ temp_x ] = stage_30_colors[ temp_x ]; } } } /* Check whether or not the stage has ended */ void stage_end(void) { int box_x; uint8_t end_seconds; /* If player has reached end, time for animation! */ if ( hit_block( player_x, player_y, 3 ) ) { /* Stage 30 palette reset */ stage_30_palette_reset(); /* Draw rectangle growing from center */ for( box_x = 0; box_x < 160; box_x++ ) { gfx_SetColor( WHITE ); gfx_VertLine_NoClip( 160 + box_x, 67, 87 ); gfx_VertLine_NoClip( 159 - box_x, 67, 87 ); gfx_SetColor( DOOR_GREEN ); gfx_VertLine_NoClip( 160 + box_x, 72, 77 ); gfx_VertLine_NoClip( 159 - box_x, 72, 77 ); if ( !(box_x % 4) || box_x == 159 ) { tick_timer(); gfx_BlitBuffer(); } } /* Text settings for stage end text */ gfx_SetTextFGColor( OFF_WHITE ); gfx_SetTextBGColor( DOOR_GREEN ); gfx_SetTextTransparentColor( UNUSED_COLOR ); /* Print message */ gfx_SetTextScale( 2, 2 ); gfx_PrintStringXY( "Stage Complete!", 56, 81 ); gfx_SetTextScale( 1, 1 ); gfx_PrintStringXY( "But is the level over...?", 84, 127 ); /* Set timer 2 settings, enable the timer, set it to the 32768 kHz clock, enable an interrupt once it reaches 0, and make it count down */ end_seconds = 0; timer_2_ReloadValue = timer_2_Counter = ONE_SECOND; timer_Control |= TIMER2_ENABLE | TIMER2_32K | TIMER2_0INT | TIMER2_DOWN; timer_IntStatus = TIMER2_RELOADED; /* Show the screen for 3 seconds exactly */ while( end_seconds < 3 ) { tick_timer(); gfx_BlitBuffer(); if(timer_IntStatus & TIMER2_RELOADED) { end_seconds++; timer_IntStatus = TIMER2_RELOADED; } } /* Disable the timer */ timer_Control ^= TIMER2_ENABLE; /* Erase gameplay area from both screen sides */ for( box_x = 159; box_x >= 0; box_x-- ) { gfx_SetColor( WHITE ); gfx_VertLine_NoClip( 160 + box_x, 0, 193 ); gfx_VertLine_NoClip( 159 - box_x, 0, 193); if ( !(box_x % 4) || box_x == 0 ) { tick_timer(); gfx_BlitBuffer(); } } if ( stage == 30 ) { /* It's the ending! YAY! */ end_game(); } else { /* Start the stage with a level transition */ start_new_stage(true); } } } void start_new_stage(bool next_stage) { /* If a level transition is happening, it's gotta be the next stage */ if ( next_stage ) { stage++; } /* Set the door's y position to look open, so it animates closing if it's closed */ door_y = 0; /* Respawn the player */ respawn(); /* Actually draw the level */ draw_level(next_stage); if ( stage == 30 ) { for ( temp_x = 0; temp_x < sizeof(stage_30_colors)/sizeof(uint16_t); temp_x++ ) { stage_30_colors[ temp_x ] = gfx_palette[ temp_x ]; } } /* If a level transition is happening, reveal it from a growing rectangle or something */ if ( next_stage ) { for( temp_x = 1; temp_x < 160; temp_x++ ) { gfx_BlitRectangle( gfx_buffer, 160 - temp_x, 0, temp_x * 2, 240 ); tick_timer(); gfx_BlitRectangle( gfx_buffer, 212, 196, 108, 14 ); } } /* Save the background to a temporary sprite */ gfx_GetSprite( player_back, player_x, player_y ); } /* Run one iteration of the timer */ void tick_timer(void) { /* Save current timer counter to a variable (for purposes of the saved game and stuff) */ time.timer_counter = timer_1_Counter; /* Milliseconds are constantly changing and stuff */ time.milliseconds = (~timer_1_Counter & 0x7FFF)/328; /* If one second has passed, increase the number of seconds and minutes */ /* Never tested if this goes up to 99:59.99 */ /* It probably won't */ /* Can someone show me how to make the milliseconds stop counting by the time 99:59.99 is reached? */ /* I assume I can make a timer_maxed_out bool that gets set when that maximum is reached */ /* And doesn't allow the time to increment at all if it's set */ /* But I'm too lazy to do that */ if(timer_IntStatus & TIMER1_RELOADED) { if(++time.seconds == 60) { time.seconds = 0; if(time.minutes<99) { time.minutes++; } } timer_IntStatus = TIMER1_RELOADED; } /* Text settings for stats */ gfx_SetTextFGColor( OFF_WHITE ); gfx_SetTextBGColor( BANNER_BLUE ); gfx_SetTextTransparentColor( UNUSED_COLOR ); /* Display current timer */ gfx_SetTextScale( 2, 2 ); gfx_SetTextXY( 212, 196 ); gfx_PrintInt( time.minutes, 2 ); gfx_PrintString( ":" ); gfx_PrintInt( time.seconds, 2 ); gfx_PrintString( "." ); gfx_PrintInt( time.milliseconds, 2 ); }