```;; This example demonstrates how to draw a sprite on a spectrum
;; sized screen.
;;
;; A Spectrum screen is 256x192.
;;
;; We set screen width to 32 chars wide (2 bytes per char):
;;
;; - In mode 1, this makes a screen 256 pixels wide.
;; - in mode 0, this makes a screen 128 pixels wide.
;;
;; We set screen height to 24 chars (8 lines per char) to give 192 pixels tall.
;;
;; Normally we don't hardware scroll the screen. So we have the added advantages:
;; 1. We can use INC L to move to the next byte to the right.
;;
;; 2. We have the following regions used:
;;
;; Screen base set to &c000.
;;
;; ranges used:
;; &c000-&c5ff
;; &c800-&cdff
;; &d000-&d5ff
;; &d800-&ddff
;; &e000-&e5ff
;; &e800-&edff
;; &f000-&f5ff
;; &f800-&fdff
;;
;; 3. We have the following regions free:
;;
;; ranges free:
;; &c000+1536 -> &c600-&c7ff free
;; &c800+1536 -> &ce00-&cfff free
;; &d000+1536 -> &d600-&d7ff free
;; &d800+1536 -> &de00-&dfff free
;; &e000+1536 -> &e600-&e7ff free
;; &e800+1536 -> &ee00-&efff free
;; &f000+1536 -> &f600-&f7ff free
;; &f800+1536 -> &fe00-&ffff free
;;
;; We calculate these as follows:
;; 32 chars in width, 2 bytes per char, 24 lines tall gives 32x24x2 = 1536 bytes per char line. Max is 2048 bytes per line.
;;  Remainder is 2048-1536 = 512 bytes per scanline. So 512*8 per screen.
;;

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

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

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/4			;; sprite width in bytes

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

start:
;; set mode to 1
ld a,1
call scr_set_mode

;; set border colour so we can see the smaller screen.
ld bc,&1a1a
call scr_set_border

;; 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,&c000
ld b,192
ld ix,scr_table
mst1:
ld (ix+0),l
ld (ix+1),h
call scr_next_line
inc ix
inc ix
djnz mst1
ret

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

scr_table:
defs 192*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:

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

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

crtc_vals:
defb &3f					;; R0 - Horizontal Total
defb 32	      	;; R1 - Horizontal Displayed  (32 chars wide)

defb 42						;; R2 - Horizontal Sync Position (centralises screen)
defb &86					;; R3 - Horizontal and Vertical Sync Widths
defb 38					;; R4 - Vertical Total
defb 0						;; R5 - Vertical Adjust
defb 24		;; R6 - Vertical Displayed (24 chars tall)
defb 31						;; R7 - Vertical Sync Position (centralises screen)
defb 0						;; R8 - Interlace
defb 7;; R9 - Max Raster
defb 0						;; R10 - Cursor (not used)
defb 0						;; R11 - Cursor (not used)
defb &30  ;; R12 - Screen start (start at &c000)
defb &00  ;; R13 - Screen start

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

scr_next_line:
;; go down next scan line
ld a,h
ld h,a
ret nc
;; add on amount to go to next char line
ld a,l
ld l,a
ld a,h
ld h,a
ret

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

draw_sprite:
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
inc l           ;; move to next byte on screen
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,192
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,32*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 ```