// **************************** // * 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, button_down, press_fire_msg, dispatch_game_state, save_x, actor_pos, pos, new_pos, actor_delta, delta, screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 outputs button_down, dispatch_game_state, actor_pos, pos, new_pos, actor_delta, delta, screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 trashes a, x, y, c, z, n, v, ptr, save_x, compare_target game_state_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 byte button_down : 0 // effectively static-local to check_button byte table[32] press_fire_msg: "PRESS`FIRE`TO`PLAY" byte save_x word compare_target // // 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. routine check_button inputs joy2, button_down outputs c, button_down trashes a, z, n { 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 } routine check_new_position_in_bounds inputs new_pos outputs c trashes compare_target, a, z, n, v { 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 outputs actor_pos, actor_delta, pos trashes 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 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 } // ---------------------------------------------------------------- // Actor Logics // ---------------------------------------------------------------- // // Sets carry if the player perished. Carry clear otherwise. // routine player_logic inputs pos, delta, joy2, screen outputs pos, delta, new_pos, screen, c trashes a, x, y, z, n, v, ptr, compare_target { 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. copy [ptr] + y, a // 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 trash n trash a trash z } // 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 trash a trash v } else { st off, c } } // // Sets carry if the player perished. Carry clear otherwise. // routine enemy_logic inputs pos, delta, screen outputs pos, delta, new_pos, screen, c trashes a, x, y, z, n, v, ptr, compare_target { 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. copy [ptr] + y, a // 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 trash n trash a trash z } // 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 trash a } else { copy delta, compare_target st on, c sub compare_target, word 40 if not z { copy word 40, delta } else { copy $ffd8, delta } trash compare_target } st off, c } // ---------------------------------------------------------------- // Game States // ---------------------------------------------------------------- // // Because these all `goto save_cinv` at the end, they must have the same signature as that routine. // define game_state_title_screen game_state_routine { ld y, 0 repeat { ld a, press_fire_msg + y st on, c sub a, 64 // yuck. oh well st a, screen1 + y inc y cmp y, 18 } until z st off, c call check_button if c { call clear_screen call init_game copy forward game_state_play, dispatch_game_state // 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 a trash n trash z } else { trash y trash c trash v } goto save_cinv } define game_state_play game_state_routine { ld x, 0 repeat { copy actor_pos + x, pos copy actor_delta + x, delta st x, save_x // FIXME need VECTOR TABLEs to make this happen: // copy actor_logic, x dispatch_logic // call indirect_jsr_logic // For now, just check the actor ID to see what type it is, and go from there. cmp x, 0 if z { call player_logic } else { call enemy_logic } if c { // Player died! Want no dead! Break out of the loop (this is a bit awkward.) call clear_screen copy forward game_state_game_over, dispatch_game_state ld x, 15 st x, save_x trash n trash z trash x } else { trash c } 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 // 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 a trash n trash z } else { trash y trash c trash v } 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 }