core_routines.asm

As was mentioned earlier on the page “C64 Assembly Project Main Setup”, the WaitFrame subroutine below is used to check individual scan lines that the display generates (from an electronical level) to draw a picture on the screen. These scan lines can be accessed with the variable (VIC_RASTER_LINE) from memory location $D012 (53266). The first example just utilizes a delay to control the game timing at line $F8 (248). Recall earlier that the display can read up to 262 scan lines on the American monitors and 312 scan lines on a European monitor. So this line 248 is almost reaching the bottom of the screen, which is waiting nearly for a complete screen refresh rate. It is also repeated here in the routine (WaitStep2) to ensure a more smoother game display.

; Wait for the raster to reach line $f8 - if it's aleady there, wait for  
; the next screen blank. This prevents mistimings if the code runs too fast 

WaitFrame

lda VIC_RASTER_LINE ; fetch the current raster line 
cmp #$F8 ; wait here till l  
beq WaitFrame

@WaitStep2

lda VIC_RASTER_LINE 
cmp #$F8 
bne @WaitStep2 
rts

Now we begin to increase the variable (TIMER) in the subroutine called UpdateTimers. We slowly cycle through 16 bits of information (using our AND mask). After this it transfers to the second routine where the variable (SLOW_TIMER) kicks off yet another timer.

; 2 basic timers - a fast TIMER that is updated every frame, 
; and a SLOW_TIMER updated every 16 frames 
;----------------------------------------------------------------------- 

UpdateTimers

inc TIMER ; increment TIMER by 1 
lda TIMER 
and #$0F ; check if it's equal to 16 
beq @updateSlowTimer ; if so we update SLOW_TIMER  
rts
@updateSlowTimer
inc SLOW_TIMER ; increment slow timer 
rts
; Trying this a different way this time. Rather than hitting the joystick registers then  
; doing something every time - The results will be stored in JOY_X and JOY_Y with values  
; -1 to 1 , with 0 meaning 'no input' - I should be able to just add this to a sprite for a  
; simple move, while still being able to do an easy check for more complicated movement  
; later on  
;------------------------------------------------------------------------------------------- 

At the beginning of our subroutine ReadJoystick we need to clear out the variables (JOY_X) and (JOY_Y) to ensure no joystick movement values remain from a previous read. Then we can check for all individual movement as seen in the variables below:

checkup = Waits until the joystick is pressed up before writing a value there.

checkdown = Waits until the joystick is pressed up before writing a value there.

ReadJoystick

lda #$00 ; Reset JOY X and Y variables 
sta JOY_X 
sta JOY_Y 
sta NE_DIR

@testUp

; Test for Up pressed 
lda checkup ; Mask for bit 0 bit JOY_2 
; test bit 0 for press 
bne @testDown 
lda #$FF ; set JOY_Y to -1 ($FF) 
sta JOY_Y 
jmp @testLeft ; Can't be up AND down

@testDown ; Test for Down
lda checkdown ; Mask for bit 1
bit JOY_2
bne @testLeft
lda #$01 ; set JOY_Y to 1 ($01)
sta JOY_Y
rts

Now we begin testing our other joystick values.

checkleft = Waits until the joystick is pressed up before writing a value there.

checkright = Waits until the joystick is pressed up before writing a value there.

@testLeft ; Test for Left
lda checkleft ; Mask for bit 2
bit JOY_2
bne @testRight
lda #$FF
sta JOY_X
rts 
@testRight ; Test for Right 
lda checkright ; Mask for bit 3 
bit JOY_2 
bne @checkUpLeft 
lda #$01 
sta JOY_X 
rts

Finally we test for a button press (using the binary mask #%0001000). Then we test for the joystick down to the right as seen below.

checkdownright = Waits until the joystick is pressed down to the right before writing a value there in the variable (NE_DIR).

@checkUpLeft
; check zero = button pressed 
; continue other checks 
; no more checks
lda #%00010000
bit JOY_2 ; check zero = button pressed
bne @testDownRight ; continue other checks

@testUpLeft
lda #1
sta NE_DIR
rts

@testDownRight ; Test for Right
lda checkdownright ; Mask for bit 3
bit JOY_2
bne @done
lda #$02
sta NE_DIR
rts

@done
; Nothing pressed
rts

 
; Notifies the state of the fire button on JOYSTICK 2.  
; BUTTON_ACTION is set to one on a single press (that is when the button is released)  
; BUTTON_PRESSED is set to 1 while the button is held down.  
; So either a long press, or a single press can be accounted for.  
; TODO I might put a 'press counter' in here to test how long the button is down for..  
;--------------------------------------------------------------------------------------

In the subroutine below JoyButton we check the variable (BUTTON_ACTION) to see if the fire button was pressed and then set a value in variable (JOY_2). So if the fire button was pressed a value is set in (BUTTON_ACTION).

The next routine below this will check the variable (BUTTON_PRESSED) to see if the fire button was actually pressed. So once the button is pressed we clear the variable (BUTTON_PRESSED) and then set the value in (BUTTON_ACTION).

JoyButton

lda #1 ; checks for a previous button action 
cmp BUTTON_ACTION ; and clears it if set 
bne @buttonTest 
lda #0 
sta BUTTON_ACTION
@buttonTest
lda #$10 ; test bit #4 in JOY_2 Register 
bit JOY_2 
bne @buttonNotPressed 
lda #1 ; if it's pressed - save the result 
sta BUTTON_PRESSED ; and return - we want a single press 
rts 
@buttonNotPressed 
lda BUTTON_PRESSED ; and check to see if it was pressed first 
bne @buttonAction ; if it was we go and set BUTTON_ACTION 
rts
@buttonAction
lda #0 
sta BUTTON_PRESSED 
lda #1 
sta BUTTON_ACTION 
rts
; Copy the custom character set into the VIC Memory Bank (2048 bytes) 
; ZEROPAGE_POINTER_1 = Source 
; ZEROPAGE_POINTER_2 = Dest 
; 
; Returns A,X,Y and PARAM2 intact 
;------------------------------------------------------------------------------------------- 

CopyChars

saveRegs
ldx #$00 ; clear X, Y, A and PARAM2 
ldy #$00 
lda #$00 
sta PARAM2
@NextLine
; CHAR_MEM = ZEROPAGE_POINTER_1 
; LEVEL_1_CHARS = ZEROPAGE_POINTER_2

lda (ZEROPAGE_POINTER_1),Y ; copy from source to target
sta (ZEROPAGE_POINTER_2),Y

The first part of the subroutine CopyChars will copy source data contained in ZEROPAGE_POINTER_1 to ZEROPAGE_POINTER_2). So earlier when we passed information into the zero page pointers it pointed to the information here:

ZEROPAGE_POINTER_1 = CHAR_MEM (This contains the character set data loaded from CharPad)

ZEROPAGE_POINTER_2  = LEVEL_1_CHARS (This contains the map level data loaded from CharPad)

saveRegs
ldx #$00 ; clear X, Y, A and PARAM2 
ldy #$00 
lda #$00 
sta PARAM2
@NextLine
; CHAR_MEM = ZEROPAGE_POINTER_1 
; LEVEL_1_CHARS = ZEROPAGE_POINTER_2

lda (ZEROPAGE_POINTER_1),Y ; copy from source to target
sta (ZEROPAGE_POINTER_2),Y
inx ; increment x / y 
iny  
cpx #$08 ; test for next character block (8 bytes) 
bne @NextLine ; copy next line 
cpy #$00 ; test for edge of page (256 wraps back to 0) 
bne @PageBoundryNotReached 
inc ZEROPAGE_POINTER_1 + 1 ; if reached 256 bytes, increment high byte 
inc ZEROPAGE_POINTER_2 + 1 ; of source and target

Then we begin to copy 8 characters and continue copying until we have reached 256 characters.

@PageBoundryNotReached
inc PARAM2 ; Only copy 254 characters (to keep irq vectors intact) 
lda PARAM2 ; If copying to F000-FFFF block 
cmp #255 
beq @CopyCharactersDone 
ldx #$00 
jmp @NextLine
@CopyCharactersDone
restoreRegs
rts

The first part of the subroutine (WaterAnimation) extracts data values from several tables that contain our animation.

ZEROPAGE_POINTER_1 (low and high bytes) = RIVER_ANIM1_LO/HI bytes.

ZEROPAGE_POINTER_2 (low and high bytes) = RIVER_ANIM2_LO/HI bytes.

ZEROPAGE_POINTER_3 (low and high bytes) = RIVER_ANIM3_LO/HI bytes.

ZEROPAGE_POINTER_4 (low and high bytes) = RIVER_ANIM4_LO/HI bytes.

WaterAnimation

lda RIVER_ANIM1_LO 
sta ZEROPAGE_POINTER_1 
lda RIVER_ANIM1_HI 
sta ZEROPAGE_POINTER_1 + 1 
lda RIVER_ANIM2_LO 
sta ZEROPAGE_POINTER_2 
lda RIVER_ANIM2_HI 
sta ZEROPAGE_POINTER_2 + 1 
lda RIVER_ANIM3_LO 
sta ZEROPAGE_POINTER_3 
lda RIVER_ANIM3_HI 
sta ZEROPAGE_POINTER_3 + 1 
lda RIVER_ANIM4_LO 
sta ZEROPAGE_POINTER_4 
lda RIVER_ANIM4_HI 
sta ZEROPAGE_POINTER_4 + 1
ldy #0 
ldx #2  ; Still looping the animation
 
@shiftPixelsRight 
lda (ZEROPAGE_POINTER_1),y 
lsr a 
bcc @store2 
clc 
adc #128 ; Get all 8 bits (128,64,32,16,8,4,2,1)
@store2
sta (ZEROPAGE_POINTER_1),y
lda (ZEROPAGE_POINTER_2),y 
lsr a 
bcc @store3 
clc 
adc #128
@store3 
sta (ZEROPAGE_POINTER_2),y 
lda (ZEROPAGE_POINTER_3),y 
lsr a 
bcc @store4 
clc 
adc #128
 
@store5 
sta (ZEROPAGE_POINTER_4),y 
iny ; This comparison checks for the tile width + (all 4 tiles) 
; In a 4x4 matrix. Because 4 x 4 = 16 
cpy #28 
bcc @shiftPixelsRight  
rts
UPLEFT byte 0 
UPRIGHT byte 0 
RIVER_ANIM1_LO byte <CHRADR1 
RIVER_ANIM1_HI byte >CHRADR1 
RIVER_ANIM2_LO byte <CHRADR2 
RIVER_ANIM2_HI byte >CHRADR2 
RIVER_ANIM3_LO byte <CHRADR3 
RIVER_ANIM3_HI byte >CHRADR3 
RIVER_ANIM4_LO byte <CHRADR4 
RIVER_ANIM4_HI byte >CHRADR4 
Y_ANIM_COUNT byte 0