;; 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: ;; 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 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: ;; 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 ret nc ;; add on amount to go to next char line ld a,l add a,32*2 ld l,a ld a,h adc a,&c0 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 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 start