git @ Cat's Eye Technologies SixtyPical / master eg / c64 / demo-game / demo-game.60p
master

Tree @master (Download .tar.gz)

demo-game.60p @masterraw · history · blame

// ****************************
// * 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
}