// **************************** // * Demo Game for SixtyPical * // **************************** // ---------------------------------------------------------------- // Type Definitions // ---------------------------------------------------------------- // // Type of routines (and vectors to those routines) which are called on each frame // to implement a certain state of the game (title screen, in play, game over, etc.) // // This type is also used as the type for the interrupt vector, even though // the interrupt routine saves and restores everything before being called and // thus clearly does not actually trash all the registers. It is declared this // way so that the game state routines, which do trash these registers, can be // assigned to it. // // This type is also used as the type for the location the old interrupt vector // is backed up to, because all the game state routines `goto` the old handler // and the end of their own routines, so the type needs to be compatible. // (In a good sense, it is a continuation.) // // Further, // // It's very arguable that screen1/2/3/4 and colormap1/2/3/4 are not REALLY inputs. // They're only there to support the fact that game states sometimes clear the // screen, and sometimes don't. When they don't, they preserve the screen, and // currently the way to say "we preserve the screen" is to have it as both input // and output. There is probably a better way to do this, but it needs thought. // typedef routine inputs joy2, press_fire_msg, dispatch_game_state, actor_pos, actor_delta, actor_logic, screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 outputs dispatch_game_state, actor_pos, actor_delta, actor_logic, screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 trashes a, x, y, c, z, n, v, pos, new_pos, delta, ptr, dispatch_logic game_state_routine // // Routines that are called to get the new state of each actor (player, enemy, etc.) // // Routines that conform to this type also follow this convention: // // Set carry if the player perished. Carry clear otherwise. // typedef routine inputs pos, delta, joy2, screen outputs pos, delta, new_pos, screen, c trashes a, x, y, z, n, v, ptr logic_routine // ---------------------------------------------------------------- // System Locations // ---------------------------------------------------------------- byte vic_border @ 53280 byte vic_bg @ 53281 byte table[256] screen1 @ 1024 byte table[256] screen2 @ 1274 byte table[256] screen3 @ 1524 byte table[256] screen4 @ 1774 byte table[256] colormap1 @ 55296 byte table[256] colormap2 @ 55546 byte table[256] colormap3 @ 55796 byte table[256] colormap4 @ 56046 buffer[2048] screen @ 1024 byte joy2 @ $dc00 // ---------------------------------------------------------------- // Global Variables // ---------------------------------------------------------------- pointer ptr @ 254 word table[256] actor_pos word pos word new_pos word table[256] actor_delta word delta vector logic_routine table[256] actor_logic vector logic_routine dispatch_logic byte table[18] press_fire_msg: "PRESS`FIRE`TO`PLAY" // // Points to the routine that implements the current game state. // vector game_state_routine dispatch_game_state // // Interrupt vector. Has same type as game states (see above.) // vector game_state_routine cinv @ 788 // // Location to which the old interrupt vector is saved before replacement. // vector game_state_routine save_cinv // ---------------------------------------------------------------- // Utility Routines // ---------------------------------------------------------------- routine read_stick inputs joy2 outputs delta trashes a, x, z, n { ld x, joy2 ld a, x and a, 1 // up if z { copy $ffd8, delta // -40 } else { ld a, x and a, 2 // down if z { copy word 40, delta } else { ld a, x and a, 4 // left if z { copy $ffff, delta // -1 } else { ld a, x and a, 8 // right if z { copy word 1, delta } else { copy word 0, delta } } } } } // You can repeatedly (i.e. as part of actor logic or an IRQ handler) // call this routine. // Upon return, if carry is set, the button was pressed then released. define check_button routine inputs joy2 outputs c trashes a, z, n static byte button_down : 0 { ld a, button_down if z { ld a, joy2 and a, $10 if z { ld a, 1 st a, button_down } st off, c } else { ld a, joy2 and a, $10 if not z { ld a, 0 st a, button_down st on, c } else { st off, c } } } routine clear_screen outputs screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 trashes a, y, c, n, z { ld y, 0 repeat { ld a, 1 st a, colormap1 + y st a, colormap2 + y st a, colormap3 + y st a, colormap4 + y ld a, 32 st a, screen1 + y st a, screen2 + y st a, screen3 + y st a, screen4 + y inc y cmp y, 250 } until z } routine calculate_new_position inputs pos, delta outputs new_pos trashes a, c, n, z, v { copy pos, new_pos st off, c add new_pos, delta } define check_new_position_in_bounds routine inputs new_pos outputs c trashes a, z, n, v static word compare_target : 0 { copy 1000, compare_target st on, c sub compare_target, new_pos if not c { copy word 0, compare_target st on, c sub compare_target, new_pos if not c { st off, c } else { st on, c } } else { st on, c } } routine init_game inputs actor_pos, actor_delta, actor_logic outputs actor_pos, actor_delta, actor_logic trashes pos, a, y, z, n, c, v { ld y, 0 copy word 0, pos repeat { copy pos, actor_pos + y copy word 40, actor_delta + y copy enemy_logic, actor_logic + y st off, c add pos, word 7 inc y cmp y, 16 } until z ld y, 0 copy word 0, actor_pos + y copy word 0, actor_delta + y copy player_logic, actor_logic + y } // ---------------------------------------------------------------- // Actor Logics // ---------------------------------------------------------------- define player_logic logic_routine { call read_stick call calculate_new_position call check_new_position_in_bounds if c { copy ^screen, ptr st off, c add ptr, new_pos ld y, 0 // check collision. ld a, [ptr] + y // if "collision" is with your own self, treat it as if it's blank space! cmp a, 81 if z { ld a, 32 } cmp a, 32 if z { copy ^screen, ptr st off, c add ptr, pos copy 32, [ptr] + y copy new_pos, pos copy ^screen, ptr st off, c add ptr, pos copy 81, [ptr] + y st off, c } else { st on, c } // FIXME these trashes, strictly speaking, probably shouldn't be needed, // but currently the compiler cares a little too much about values that are // initialized in one branch of an `if`, but not the other, but are trashed // at the end of the routine anyway. trash ptr trash y trash v } else { st off, c } } define enemy_logic logic_routine static word compare_target : 0 { call calculate_new_position call check_new_position_in_bounds if c { copy ^screen, ptr st off, c add ptr, new_pos ld y, 0 // check collision. ld a, [ptr] + y // if "collision" is with your own self, treat it as if it's blank space! cmp a, 82 if z { ld a, 32 } cmp a, 32 if z { copy ^screen, ptr st off, c add ptr, pos copy 32, [ptr] + y copy new_pos, pos copy ^screen, ptr st off, c add ptr, pos copy 82, [ptr] + y st off, c } else { st on, c } // FIXME these trashes, strictly speaking, probably shouldn't be needed, // but currently the compiler cares too much about values that are // initialized in one branch of an `if`, but not the other, but trashed // at the end of the routine anyway. trash ptr trash y } else { copy delta, compare_target st on, c sub compare_target, word 40 if not z { copy word 40, delta } else { copy $ffd8, delta } } st off, c } // ---------------------------------------------------------------- // Game States // ---------------------------------------------------------------- define game_state_title_screen game_state_routine { ld y, 0 for y up to 17 { ld a, press_fire_msg + y st on, c sub a, 64 // yuck. oh well st a, screen1 + y } st off, c call check_button if c { call clear_screen call init_game copy game_state_play, dispatch_game_state } goto save_cinv } define game_state_play game_state_routine static byte save_x : 0 { ld x, 0 repeat { copy actor_pos + x, pos copy actor_delta + x, delta st x, save_x copy actor_logic + x, dispatch_logic call dispatch_logic if c { // Player died! Want no dead! Break out of the loop (this is a bit awkward.) call clear_screen copy game_state_game_over, dispatch_game_state ld x, 15 } else { ld x, save_x } copy pos, actor_pos + x copy delta, actor_delta + x inc x cmp x, 16 } until z goto save_cinv } define game_state_game_over game_state_routine { st off, c call check_button if c { call clear_screen call init_game copy game_state_title_screen, dispatch_game_state } goto save_cinv } // ************************* // * Main Game Loop Driver * // ************************* define our_cinv game_state_routine { goto dispatch_game_state } routine main inputs cinv outputs cinv, save_cinv, pos, dispatch_game_state, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 trashes a, y, n, c, z, vic_border, vic_bg { ld a, 5 st a, vic_border ld a, 0 st a, vic_bg ld y, 0 call clear_screen copy game_state_title_screen, dispatch_game_state copy word 0, pos with interrupts off { copy cinv, save_cinv copy our_cinv, cinv } repeat { } forever }