This is an updated version of “The Picture” written in assembly using a line drawing routine by Simeon Dwyer. This version uses a double buffer to move the boat.
; ; Picture in Motion ; v1.00 Steven Reid (c) 2021 ; This is an assembly version of my old BASIC drawring routine. ; Trying out a double buffer idea and seeing if I can move the ; boat. ; org 16514 ; stored in REM at top jr start ; needed for z80asm ; title and copyright copy: db $00,$27,$3e,$00,$38,$39,$2a,$3b,$2a,$33 db $00,$37,$2a,$2e,$29,$00,$1e,$1c,$1e,$1d db $00,$17,$00,$17,$00,$35,$2e,$28,$39,$3a db $37,$2a,$00,$2e,$33,$00,$32,$34,$39,$2e db $34,$33,$00,$17,$00,$17,$76,$76 ; BY STEVEN REID 2021 ; * * PICTURE IN MOTION * * ; macros kscan: equ $02bb findchar: equ $07bd stop: equ $0cdc slow: equ $0f2b printat: equ $08f5 pause: equ $0f35 ; system vars frames: equ 16436 d_file: equ $400c ; variables offset: dw 0 boatoffset: xoffset: db 0 yoffset: db 0 direction: db 0 ; ; Start of program! ; start: ld hl,0 ; reset offset ld (offset),hl ld (boatoffset),hl ld (yoffset),hl mainloop: call getkeypress ; delay and get key cp $36 ; pressed 'Q'? jp z,done ; yes, we are done! call clearbuffer ; clear the buffer first ld hl,0 ; reset offset ld (offset),hl ld hl,picturedata ; print sun and horizion call printpicture ld hl,(boatoffset) ; reset offset ld (offset),hl ld hl,boatdata ; print boat call printpicture call copy2screen ld a,(direction) ; get direction and a jp nz,down up: ld a,(yoffset) ; get x offset dec a ; increment a ld (yoffset),a ; save it back cp 256-2 ; is a too high? jp nz,mainloop ; yep, done! ; change direction! ld a,1 ld (direction),a jp mainloop down: ld a,(yoffset) ; get x offset inc a ; increment a ld (yoffset),a ; save it back cp 0 ; is a too high? jp nz,mainloop ; yep, done! ; change direction! ld a,0 ld (direction),a jp mainloop done: jp stop ; all done printpicture: ld a,(hl) ; check if we are at the end of the array cp $ff ret z ; old way ; ld de,linecoords ; coordiantes (to) ; ld bc,4 ; 4 to copy! ; ldir ; copy coordinates ; new with offset ld b,(hl) ; grab x0 ld a,(offset) ; grab offset add a,b ; get new x0 ld (linecoords),a ; load into linecoords inc hl ld b,(hl) ; grab y0 ld a,(offset+1) ; grab offset add a,b ; get new y0 ld (linecoords+1),a ; load into linecoords inc hl ld b,(hl) ; grab x1 ld a,(offset) ; grab offset add a,b ; get new x1 ld (linecoords+2),a ; load into linecoords inc hl ld b,(hl) ; grab y1 ld a,(offset+1) ; grab offset add a,b ; get new y1 ld (linecoords+3),a ; load into linecoords inc hl push hl ; save hl call draw_line ; and go draw the line! pop hl ; restore hl jp printpicture ; loop until out of data! ; ; Copy buffer to screen ; copy2screen: ld de,(d_file) ld hl,buffer ld bc,727 ldir ret ; ; Copy screen to buffer ; copy2buffer: ld de,buffer ld hl,(d_file) ld bc,727 ldir ret ; ; Clear buffer ; clearbuffer: ld de,emptyline-33 ld hl,emptyline ld a,22 cbloop: ld bc,33 lddr dec a jp nz,cbloop ret ;waitloop: ; ld bc,1000 ; call pause ; jp start ; jp z,stop ; all done rval: db 10 ; range value storage rnd: ld bc,0 ; get the pointer in ROM ld (rval),a ; save seed into rval ld hl,(frames) ; for randomness get framecounter add hl,bc ; add framecounter dec hl ; when framecounter is unchanged make sure a change is done ld a,h ; H can have any value, but ROM is #0000-#1FFF and $0f ; only #0000-#0f00 is what we use ld h,a ; set pointer back within ROM ld (rnd+1),hl ; save current pointer (selfmodifying code!!!) ld a,(rval) ; grab range value ld b,a ; put in b ld a,(hl) ; get value in ROM rrange: sub b ; we need 0-rval only jr nc,rrange adc a,b ; undo last subtraction, range 1-rval ret ; back to mainprogram getkeypress: call delay ; call delay call kscan ; get key press? ld b,h ld c,l ld d,c inc d ; this is what we'll test ld a,1 ; ld a with 1 ret z ; return if no key press call findchar ; yep, grab character pressed ld a,(hl) ret ; return delay: ; delay loop ld a,90 call rnd dec a ld b,a ld c,0 ; ld bc,9000 ; a timed delay. Altering the lpdel: dec bc ; initial value of BC changes nop ld a,b or c jr nz,lpdel ret ; End of loop! ; ;x can be 0 to 63 ;y can be 0 to 45 picturedata: ; start x1,y1 and end x2,y2 - 4 bytes per line db 0,22,38,22 ; horizon db 53,22,64,22 db 4,4,9,4 ; sun db 8,4,10,5 db 9,5,9,7 db 9,6,7,8 db 8,8,4,8 db 4,8,2,7 db 2,7,2,6 db 2,6,4,5 db $ff ; array done! boatdata: db 25,38,61,38 ; boat bottom db 60,38,55,41 db 55,41,30,41 db 30,41,25,39 db 48,38,48,8 ; mast db 48,8,45,11 ; flag db 45,11,48,11 db 48,12,26,35 ; sails db 26,35,60,35 db 59,35,48,12 db $ff ; array done! ; ; Routines ; ;start code line zxplot: ;alter pixel nominated by plotx and ploty ;plotmodes are flag2 bit 1. 0 = plot, 1 = unplot ;x can be 0 to 63 ;y can be 0 to 45 call calculate_origin nozxplotcolour: ld a,(flag2) bit 1,a jp nz,unplot_pixel call plot_pixel ret ; end zxplot calculate_origin: ; work out what square on the screen the pixel appears at ld a,(plotx) bit 7,a ret nz sub 64 ret p ld a,(ploty) bit 7,a ret nz sub 46 ret p ld a,$00 ld (plotsector),a ld a,(plotx) srl a ld (plotx),a jr nc,pixelleft ld a,$01 ld (plotsector),a ;pixel is in right half pixelleft: ld a,(ploty) srl a ld (ploty),a jr nc,pixelup ld a,(plotsector) set 1,a ld (plotsector),a ;pixel is in lower half pixelup: ;now find the correct screen position ; ld hl,(d_file) ld hl,buffer inc hl ld de,33 ld a,(ploty) cp $0 jr z,findx ld b,a findy: add hl,de djnz findy findx: ld de,$00 ld a,(plotx) ld e,a add hl,de ld (plot_origin), hl ret plot_pixel: ; plot the pixel in the co-ord from plot_origin (square on screen) and ; plotsector (actual pixel) ; 0= top left, 1 = top right, 2 = bottom left, 3 = bottom right ld hl,(plot_origin) ld a,(plotsector) cp $00 jr z,plottl cp $01 jr z,plottr cp $02 jr z,plotbl cp $03 jr z,plotbr ret plottl: ;top left quadrant ld a,(hl) plottlok: bit 7,a jr nz,tlinvert set 0,a ld (hl),a ret tlinvert: res 0,a ld (hl),a ret plottr: ;top right quadrant ld a,(hl) plottrok: bit 7,a jr nz,trinvert set 1,a ld (hl),a ret trinvert: res 1,a ld (hl),a ret plotbl: ;bottom left quadrant ld a,(hl) plotblok: bit 7,a jr nz,blinvert set 2,a ld (hl),a ret blinvert: res 2,a ld (hl),a ret plotbr: ;bottom right quadrant ld a,(hl) bit 7,a ;collision detect ret nz ;plotbrok: set 7,a xor $07 ;invert all other bits ld (hl),a ret ; plot pixel unplot_pixel: ; unplot the pixel in the co-ord from plot_origin (square on screen) and ; plotsector (actual pixel) ; 0= top left, 1 = top right, 2 = bottom left, 3 = bottom right ld hl,(plot_origin) ld a,(plotsector) cp $00 jr z,uplottl cp $01 jr z,uplottr cp $02 jr z,uplotbl cp $03 jr z,uplotbr ret uplottl: ;top left quadrant ld a,(hl) bit 7,a jr nz,utlinvert res 0,a ld (hl),a ret utlinvert: set 0,a ld (hl),a ret uplottr: ;top right quadrant ld a,(hl) bit 7,a jr nz,utrinvert res 1,a ld (hl),a ret utrinvert: set 1,a ld (hl),a ret uplotbl: ;bottom left quadrant ld a,(hl) bit 7,a jr nz,ublinvert res 2,a ld (hl),a ret ublinvert: set 2,a ld (hl),a ret uplotbr: ;bottom right quadrant ld a,(hl) bit 7,a ret z res 7,a xor $07 ;invert all other bits ld (hl),a ret ret ;unplot pixel draw_line_high: ; bresenham's algorithm ; for up down lines ld a,(x0p) ;dx = x1 - x0 ld b,a ld a,(x1p) sub b ld (dx),a ld a,(y0p) ;dy = y1 - y0 ld b,a ld a,(y1p) sub b ld (dy),a ld hl,xi ;xi =1 ld (hl),$1 ld a,(dx) bit 7,a ;if dx<0? jr z,dx_not_negh ;dx is < 0 ld a,$ff ;xi =-1 ld (xi),a ld a,(dx) ; dx = -dx neg ld (dx),a dx_not_negh: ld a,(dy) ;dis = (2 * dx) - dy ld b,a ld a,(dx) sla a ;dx*2 sub b ld (dis),a ;x = x0 ld a,(x0p) ld (x),a ld a,(y0p) ;for y from y0 to y1 ld (y),a ld b,a ld a,(y1p) sub b jr z,zero_line ;just plot the origin if line length is zero ld b,a ;b contains y1-y0 draw_line_looph: push bc ld a,(y) ld (ploty),a inc a ld (y),a ld a,(x) ld (plotx),a call zxplot ;ploy x,y ;call single_step ld a,(dis) ;if dis > 0 bit 7,a jr nz,dis_lt0h ;x = x + xi ld a,(xi) ld b,a ld a,(x) add a,b ld (x),a ;dis = dis + (2 * (dx - dy)) ld a,(dy) ld b,a ld a,(dx) sub b sla a ld b,a ld a,(dis) add a,b ld (dis),a jr end_draw_line_looph dis_lt0h: ;dis = dis + 2*dx ld a,(dx) sla a ld b,a ld a,(dis) add a,b ld (dis),a end_draw_line_looph: pop bc djnz draw_line_looph ld hl,flag2 res 1,(hl) ret ;draw_line_high zero_line: ld a,(x0) ld (plotx),a ld a,(y0) ld (ploty),a call zxplot ret ;zero_line draw_line_low: ; bresenham's algorithm ld a,(x0p) ;dx = x1 - x0 ld b,a ld a,(x1p) sub b ld (dx),a ld a,(y0p) ;dy = y1 - y0 ld b,a ld a,(y1p) sub b ld (dy),a ld hl,yi ;yi =1 ld (hl),1 bit 7,a ;if dy<0? jr z,dy_not_neg ;dy is < 0 ld a,$ff ;yi =-1 ld (yi),a ld a,(dy) ; dy = -dy neg ld (dy),a dy_not_neg: ld a,(dx) ;dis = (2 * dy) - dx ld b,a ld a,(dy) sla a ;dy*2 sub b ld (dis),a ;y = y0 ld a,(y0p) ld (y),a ld a,(x0p) ;for x from x0 to x1 ld (x),a ld b,a ld a,(x1p) sub b jr z,zero_line ;just plot the origin if line length is zero ld b,a ;b contains x1-x0 draw_line_loop: push bc ld a,(x) ld (plotx),a inc a ld (x),a ld a,(y) ld (ploty),a call zxplot ;ploy x,y ld a,(dis) ;if dis > 0 bit 7,a jr nz,dis_lt0 ;call print_star ;y = y + yi ld a,(yi) ld b,a ld a,(y) add a,b ld (y),a ;dis = dis + (2 * (dy - dx)) ld a,(dx) ld b,a ld a,(dy) sub b sla a ld b,a ld a,(dis) add a,b ld (dis),a jr end_draw_line_loop dis_lt0: ;dis = dis + 2*dy ld a,(dy) sla a ld b,a ld a,(dis) add a,b ld (dis),a end_draw_line_loop: pop bc djnz draw_line_loop ld hl,flag2 res 1,(hl) ret ;draw_line_low undraw_line: ld hl,flag2 set 1,(hl) draw_line: ;will abort if both start and end are the same ld a,(x0) ld (x0p),a ld a,(x1) ld (x1p),a ld a,(y0) ld (y0p),a ld a,(y1) ld (y1p),a ;work out what configuration of the algorithm ;is correct to draw the line ;if abs(y1 - y0) < abs(x1 - x0) ld a,(y0) ld b,a ld a,(y1) sub b ;a= y1-y0 jr nc,y1gty0 ;nc = (y1-y0) >0 neg ;change to abs value y1gty0: ld c,a ;c = abs(y1 - y0) ;works so far ld a,(x0) ld b,a ld a,(x1) sub b jr nc,x1gtx0 neg x1gtx0: ;a = abs(x1 - x0) sub c ;c = abs(y1 - y0) jr c,check_drawlinehigh ;abs(y1 - y0) < abs(x1 - x0) so use draw line high ;if x0 > x1 ;draw line low, else swap x0,y0 and x1,y1 ld a,(x1) ld b,a ld a,(x0) sub b jp c,draw_line_low ;x1 > x0 so swap x0,x1 and y0,y1 call swap_x0_x1_y0_y1 jp draw_line_low check_drawlinehigh: ;if y0 > y1 ld a,(y1) ld b,a ld a,(y0) sub b jp c,draw_line_high ;plotlinehigh(x1, y1, x0, y0) ;x1 > x0 so swap x0,x1 and y0,y1 call swap_x0_x1_y0_y1 jp draw_line_high ;draw_line swap_x0_x1_y0_y1: ld a,(y0) ld b,a ld a,(y1) ld (y0p),a ld a,b ld (y1p),a ld a,(x0) ld b,a ld a,(x1) ld (x0p),a ld a,b ld (x1p),a ret get_cosine: ;same as sine plus 90 degrees sub $40 get_sine: ;take value of the a register and return the sine value from table ;first, work out what quadrant we're in cp $40 jr nc,sine_quad2 ;angle is top right quadrant ;count angle sine from bottom of table ld hl,sine_value_bottom ld de,$00 ld e,a add hl,de ld a,(hl) ret sine_quad2: cp $80 jr nc,sine_quad3 ;angle is bottom right quadrant ;count angle sine from top of table sub $40 ld hl,sine_value_top ld de,$00 ld e,a scf ccf sbc hl,de ld a,(hl) ret sine_quad3: cp $c0 jr nc,sine_quad4 ;causing problems at $80 ;angle is bottom left quadrant ;count negative angle sine from bottom of table sub $80 ld hl,sine_value_bottom ld de,$00 ld e,a add hl,de ld a,(hl) set 7,a ret sine_quad4: ;angle is top left quadrant ;count negative angle sine from top of table ;causing problems at $ff sub $c0 ld hl,sine_value_top ld de,$00 ld e,a scf ccf sbc hl,de ld a,(hl) set 7,a ret ;get_sine calculate_scaled_value: ;a register contains sine value ;multiply line_length by the sine and divide by 128 ;to get the displacement ld c,a ;preserve original displacement res 7,a cp $00 jr z,calculate_zero_value ld b,a ;sine value ld a,(line_length) ld hl,$00 ld l,a push hl pop de ;now multiply line length by the sine value mult_sine_loop: add hl,de djnz mult_sine_loop ;divide result in hl register by 128 ld b,$07 divide_by_128_loop: srl l srl h jr nc,nocarry_l_reg set 7,l nocarry_l_reg: djnz divide_by_128_loop bit 7,c ret z ld a,l neg calculate_zero_value: ld l,a ret ;calculate_scaled_value ;variables flag2: db $00 plotmode: db $00 plotx: db $00 ploty: db $00 plotsector: db $00 plot_origin: db $00, $00 line_length: db $00 ;line drawing x and y. variables ending in p are the parameters ;passed to the line draw program linecoords: x0: db $00 y0: db $00 x1: db $00 y1: db $00 x0p: db $00 y0p: db $00 x1p: db $00 y1p: db $00 ;used for bresenham's line algorithm dx: db $00 dy: db $00 xi: db $00 yi: db $00 dis: db $00 x: db $00 y: db $00 sine_value_bottom: ;64 values f0r the sine of a value of 128, so a line of length ;128 would have a x length of 3 pixels at an angle of 1 unit of 63 db $00,$03,$06,$09,$0c,$10,$13,$16,$19,$1c,$1f,$22,$25,$28,$2b db $2e,$31,$33,$36,$39,$3c,$3f,$41,$44,$47,$49,$4c,$4e,$51,$53 db $55,$58,$5a,$5c,$5e,$60,$62,$64,$66,$68,$6a,$6b,$6d,$6f,$70 db $71,$73,$74,$75,$76,$78,$79,$7a,$7a,$7b,$7c,$7d,$7d,$7e,$7e db $7e,$7f,$7f sine_value_top: db $7f buffer: db $76 db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$76 db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$76 db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$76 db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$76 db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$76 db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$76 db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$76 db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$76 db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$76 db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$76 db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$76 db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$76 db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$76 db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$76 db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$76 db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$76 db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$76 db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$76 db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$76 db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$76 db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$76 db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$76 emptyline: db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$76