;; This example demonstrates how to draw a sprite on an overscan
;; screen.
;;
;; The overscan screen used here is made using the "32K" overscan
;; method.

;; this has been organised so problem is on left side!
;; avoiding the need for a special scr_next_byte

;;
;; Our screen is located between &0000-&7fff, with a scroll adjustment.
;; This scroll adjustment allows:
;; 1. we can keep lower firmware jumpblock at &0000-&0040
;; 2. the "problem" address is located on left side (this is where "scr next byte" translates &07ff->&4000
;;   which means we don't need special case for this and we can draw sprites faster.

org &8000
;; turn off listing; remove for pasmo assembler
nolist

scr_set_mode equ &bc0e
txt_output equ  &bb5a
mc_wait_flyback equ &bd19
km_test_key equ &bb1e

scr_height_chars equ 35							;; scr height in chars (best value to fill screen)
char_height_lines equ 8
scr_width_chars equ 48							;; width of screen in chars (best value for crtc type 2)
scr_offset equ 208								;; scr offset - setup so that the "bad" address
												;; is located on the left side of the screen
												;; this simplifies "scr next byte" and means sprites
												;; can be drawn a bit quicker
sprite_height equ 16							;; sprite height in lines
sprite_width_pixels equ 16						;; sprite width in mode 0 pixels
sprite_width_bytes equ sprite_width_pixels/2			;; sprite width in bytes

;;-----------------------------------------------------------------------------------------------------

start:
;; set mode to 0
xor a
call scr_set_mode

;; setup crtc
ld hl,crtc_vals
call set_crtc

;; make table of screen addresses for the start of each scanline
call make_scr_table

;; draw sprite using XOR to the screen
call draw_sprite_cur_coords

;;---------------------------------------------------
main_loop:
;; wait for VSYNC
call mc_wait_flyback

;; get current coords and store them in prev coords
;; we compare new coords to previous coords to determine
;; if we should erase and then redraw the sprite.
;; this avoids continuous flicker if the sprite is not moving
;; however, the sprite can still disapear when moving around
;; the screen because there is a time when the sprite has been
;; deleted, the monitor has drawn it when it has been deleted
;; and we then draw the new version too late.
ld hl,(x_coord)
ld (prev_x_coord),hl
ld hl,(y_coord)
ld (prev_y_coord),hl

;; check keys
call do_keys

;; wait for next interrupt
;; this ensures our code takes longer than the vsync time
;; so we can always wait just for the start of the vsync
halt

;; draw the sprite if it has moved
call redraw_sprite

jp main_loop

;;-----------------------------------------------------------------------------------------------------

redraw_sprite:
;; x coord changed?
ld hl,(prev_x_coord)
ld bc,(x_coord)
or a
sbc hl,bc
ld a,h
or l
jr nz,rs1

;; y coord changed?
ld hl,(prev_y_coord)
ld bc,(y_coord)
or a
sbc hl,bc
ld a,h
or l
jr nz,rs1

;; no change in x or y, so no update
ret
;;-----------------------------------------------------------------------------------------------------

rs1:
;; erase in old pos
ld hl,(prev_x_coord)
ld de,(prev_y_coord)
call draw_sprite

draw_sprite_cur_coords:
;; draw in new pos
ld hl,(x_coord)
ld de,(y_coord)
call draw_sprite
ret

;;-----------------------------------------------------------------------------------------------------
;; initialise CRTC
;; HL = address of values
;; R0,R1,R2,R3... R13
set_crtc:
ld bc,&bc00
set_crtc_vals:
out (c),c
inc b
ld a,(hl)
out (c),a
dec b
inc hl
inc c
ld a,c
cp 14
jr nz,set_crtc_vals
ret

;;-----------------------------------------------------------------------------------------------------
;; initialise a table 

make_scr_table:
ld hl,208*2
ld bc,scr_height_chars*char_height_lines
ld ix,scr_table
mst1:
ld (ix+0),l
ld (ix+1),h
call scr_next_line
inc ix
inc ix
dec bc
ld a,b
or c
jr nz,mst1
ret

;;-----------------------------------------------------------------------------------------------------

scr_table:
defs scr_height_chars*char_height_lines*2

;;-----------------------------------------------------------------------------------------------------

;; input conditions:
;; HL = x byte coordinate (0-((scr_width_chars*2)-1))
;; DE = y coordinate (0-((scr_height_chars*char_height_lines)-1))
;; output conditions:
;; HL = screen address

get_scr_addr:
push bc
ex de,hl
add hl,hl
ld bc,scr_table
add hl,bc
ld a,(hl)
inc hl
ld h,(hl)
ld l,a
add hl,de
pop bc
ret

;;-----------------------------------------------------------------------------------------------------
;; crtc values

crtc_vals:
defb &3f					;; R0 - Horizontal Total
defb scr_width_chars		;; R1 - Horizontal Displayed
defb 48						;; R2 - Horizontal Sync Position
defb &86					;; R3 - Horizontal and Vertical Sync Widths
defb &26					;; R4 - Vertical Total
defb 0						;; R5 - Vertical Adjust
defb scr_height_chars		;; R6 - Vertical Displayed
defb 35						;; R7 - Vertical Sync Position
defb 0						;; R8 - Interlace
defb char_height_lines-1	;; R9 - Max Raster 
defb 0						;; R10 - Cursor (not used)
defb 0						;; R11 - Cursor (not used)
defb &0c					;; R12 - Screen start (also includes bits for enabling 32K)
defb scr_offset				;; R13 - Screen start

;;-----------------------------------------------------------------------------------------------------
;; input conditions:
;; HL = screen address
;; output conditions:
;; HL = screen address (next byte to right)
scr_next_byte:
inc hl
ret

;;-----------------------------------------------------------------------------------------------------
;; input conditions:
;; HL = screen address
;; output conditions:
;; HL = screen address (next scanline down)
;;

scr_next_line:
;; go down next scan line
ld a,h
add a,8
ld h,a
;; check if we should go to next char line
and &38
ret nz

;; remove effect of last add
ld a,h
sub &8
ld h,a

;; add on amount to go to next char line
ld a,l
add a,scr_width_chars*2
ld l,a
ld a,h
adc a,&00
ld h,a

;; if we overflowed to next 16k the result will be 0
and &38
ret z

;; we didn't overflow adjust to go back into 1st 16k
ld a,h
sub &38
ld h,a
ret

;;-----------------------------------------------------------------------------------------------------

draw_sprite:
call get_scr_addr
ld de,sprite_pixels

ld b,sprite_height
ld c,sprite_width_bytes
dsl:
push bc
push hl
dsw:
ld a,(de)
xor (hl)						;; XOR to screen, XOR to remove
ld (hl),a
inc de
call scr_next_byte
dec c
jr nz,dsw

pop hl
call scr_next_line
pop bc
djnz dsl
ret

;;-----------------------------------------------------------------------------------------------------

;; sprite width_bytes*sprite_height filled with one value
sprite_pixels:
defs sprite_width_bytes*sprite_height,&ff

;;-----------------------------------------------------------------------------------------------------
;; check keyboard
;;
;; cursor keys

do_keys:
ld a,0*8+0
call km_test_key
jr nz,move_up
ld a,0*8+1
call km_test_key
jr nz,move_right
ld a,0*8+2
call km_test_key
jr nz,move_down
ld a,1*8+0
call km_test_key
jr nz,move_left
ret
;;-----------------------------------------------------------------------------------------------------

move_up:
ld hl,(y_coord)
ld a,h
or l
ret z
dec hl
ld (y_coord),hl
ret
;;-----------------------------------------------------------------------------------------------------

move_down:
;; avoid going off bottom by checking for bottommost y position
ld hl,scr_height_chars*char_height_lines			
ld bc,sprite_height
or a
sbc hl,bc
ld c,l
ld b,h

ld hl,(y_coord)
or a
sbc hl,bc
ret p


ld hl,(y_coord)
inc hl
ld (y_coord),hl
ret
;;-----------------------------------------------------------------------------------------------------

move_left:
;; avoiding going off left side
ld hl,(x_coord)
ld a,h
or l
ret z
dec hl
ld (x_coord),hl
ret
;;-----------------------------------------------------------------------------------------------------

move_right:
;; avoid going off right side by checking for rightmost x coordinate
ld hl,scr_width_chars*2			
ld bc,sprite_width_bytes
or a
sbc hl,bc
ld c,l
ld b,h

ld hl,(x_coord)
or a
sbc hl,bc
ret p

ld hl,(x_coord)
inc hl
ld (x_coord),hl
ret
;;-----------------------------------------------------------------------------------------------------

prev_x_coord:
defw 0
prev_y_coord:
defw 0

x_coord:
defw 0
y_coord:
defw 0

;; make this executable for pasmo, uncomment for pasmo assembler
;;end start