// **************************** // * Demo Game for SixtyPical * // **************************** // Copyright (c) 2014-2024, Chris Pressey, Cat's Eye Technologies. // This file is distributed under a 2-clause BSD license. See LICENSES/ dir. // SPDX-License-Identifier: LicenseRef-BSD-2-Clause-X-SixtyPical include "joystick.60p" // ---------------------------------------------------------------- // 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.) // typedef routine inputs joy2, press_fire_msg, dispatch_game_state, actor_pos, actor_delta, actor_logic, player_died, screen, colormap outputs dispatch_game_state, actor_pos, actor_delta, actor_logic, player_died, screen, colormap 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 player_died to 1 if the player perished. Unchanged otherwise. // typedef routine inputs pos, delta, joy2, screen, player_died outputs pos, delta, new_pos, screen, player_died trashes a, x, y, z, n, v, c, ptr logic_routine // ---------------------------------------------------------------- // System Locations // ---------------------------------------------------------------- byte vic_border @ 53280 byte vic_bg @ 53281 byte table[2048] screen @ 1024 byte table[2048] colormap @ 55296 // ---------------------------------------------------------------- // Global Variables // ---------------------------------------------------------------- pointer ptr @ 254 word table[256] actor_pos word pos word new_pos word table[256] actor_delta byte player_died 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 // ---------------------------------------------------------------- define clear_screen routine outputs screen, colormap trashes a, y, c, n, z { ld y, 0 repeat { ld a, 1 st a, colormap + y st a, colormap + 250 + y st a, colormap + 500 + y st a, colormap + 750 + y ld a, 32 st a, screen + y st a, screen + 250 + y st a, screen + 500 + y st a, screen + 750 + y inc y cmp y, 250 } until z } define calculate_new_position routine 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 } } define init_game routine inputs actor_pos, actor_delta, actor_logic outputs actor_pos, actor_delta, actor_logic, player_died 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 40, actor_pos + y copy word 0, actor_delta + y copy player_logic, actor_logic + y st y, player_died } // ---------------------------------------------------------------- // Actor Logics // ---------------------------------------------------------------- define player_logic logic_routine { call read_stick call calculate_new_position call check_new_position_in_bounds if c { point ptr into screen { reset ptr 0 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 { reset ptr 0 st off, c add ptr, pos copy 32, [ptr] + y copy new_pos, pos reset ptr 0 st off, c add ptr, pos copy 81, [ptr] + y } } } else { ld a, 1 st a, player_died } } define enemy_logic logic_routine static word compare_target : 0 { call calculate_new_position call check_new_position_in_bounds if c { point ptr into screen { reset ptr 0 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 { reset ptr 0 st off, c add ptr, pos copy 32, [ptr] + y copy new_pos, pos reset ptr 0 st off, c add ptr, pos copy 82, [ptr] + 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 } } } // ---------------------------------------------------------------- // 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, screen + 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 { ld x, 0 st x, player_died for x up to 15 { copy actor_pos + x, pos copy actor_delta + x, delta // // Save our loop counter on the stack temporarily. This means that routines // like `dispatch_logic` and `clear_screen` are allowed to do whatever they // want with the `x` register; we will restore it at the end of this block. // save x { copy actor_logic + x, dispatch_logic call dispatch_logic } copy pos, actor_pos + x copy delta, actor_delta + x } ld a, player_died if not z { // Player died! Want no dead! call clear_screen copy game_state_game_over, dispatch_game_state } 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 preserved game_state_routine { goto dispatch_game_state } define main routine inputs cinv outputs cinv, save_cinv, pos, dispatch_game_state, screen, colormap 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 }