My attempt at a chunky graphics routine for the ZX81 that allows three colors: white, gray and black. This program demonstrates its use.
; ; Chunky GFX - Draw Demo ; Steven Reid (c) 2022 ; v1 11/24/2022 - initial build ; v2 12/04/2022 - imported final routine and cleanup ; ; +++ ; ; Header and startup ; ; start up stuff ORG 16514 ; stored in REM at top (ZX81) jr start ; needed for z80asm ; title and copyright (will show when listed) copy: DB _as,_c_,_h_,_u_,_n_,_k_,_y_,_g_,_f_,_x_ DB _as,_as,_s_,_l_,_r_,_sl,_2_,_0_,_2_,_2_ DB _as,$76,$76 ; *CHUNKYGFX*SLR/2022* start: call slow ; SLOW is required. call cls ; clear screen / exapnd screen ; end header and startup ; ; --- ; +++ ; ; Main Program ; ; chukygfx max width / height max_width: EQU 31 half_width: EQU 15 ; jszeddy doesn't like uneven division max_height: EQU 47 half_height: EQU 23 ; jszeddy doesn't like uneven division ; set pen color ld a,1 ; hash ld (pen_color),a ; draw top left most point ld bc,0*256+1 call cplot ; draw bottom right most point ld bc,max_width*256+max_height call cplot ; draw a circle (well, oval) ld bc,half_width*256+half_height ld a,13 ; radius call draw_circle ; set pen color ld a,2 ; black ld (pen_color),a ; draw top right most point ld bc,max_width*256+1 call cplot ; darw bottom left most point ld bc,0*256+max_height call cplot ; draw a circle (well, oval) ld bc,half_width*256+half_height ld a,5 ; radius call draw_circle ; draw horizontal line over center ld bc,0+(half_width-15)*256+(half_height) ld de,0+(half_width+16)*256+(half_height) call draw_line ; draw vertical line over center ld bc,0+(half_width)*256+(half_height-20) ld de,0+(half_width)*256+(half_height+20) call draw_line ; set pen color ld a,2 ; black ld (pen_color),a ; draw center point ld bc,0+(half_width)*256+(half_height) call cplot ; this loop draws a series of lines as they spin draw_loop: ; rotate pen color ld a,(pen_color) ; grab current value inc a ; add 1 cp 3 ; is it 3? jr nz,savepc ; if no, jump to save it xor a ; otherwise set pen color to 0 savepc: ld (pen_color),a ; line spin ld bc,0+(half_width-12)*256+(half_height) ld de,0+(half_width+12)*256+(half_height) ld (y1_var),bc ld (x_var),de ld b,4 line_loop: push bc ld bc,(y1_var) ld de,(x_var) call draw_line ld h,3 ld a,(x1_var) add a,h ld b,a ld a,(y1_var) add a,h ld c,a ld (y1_var),bc ld h,256-3 ld a,(y_var) add a,h ld d,a ld a,(x_var) add a,h ld e,a ld (x_var),de pop bc djnz line_loop ; not done yet! ld b,4 line_loop2: push bc ld bc,(y1_var) ld de,(x_var) call draw_line ld h,3 ld a,(x1_var) add a,h ld b,a ld a,(y1_var) ld h,256-3 add a,h ld c,a ld (y1_var),bc ld a,(y_var) add a,h ld d,a ld a,(x_var) ld h,3 add a,h ld e,a ld (x_var),de pop bc djnz line_loop2 ; not done yet! ld hl,$0999 call delay jp draw_loop ; End of loop! ; ; +++ ; ; Routines ; ; +++ ; Chunky Plot ; ; This is a 3 color version of plot that uses ; white, black and hash (gray). ; ; set pen_color to 0 (white), 1 (gray), or 2 (black) ; set bc register to x/y coordinates (preserved) ; all other registers are destroied ; ;variables pen_color: DB 0 ; Primary entry point - preserves BC cplot: push bc ; save plot points call chunky_plot ; call plot routine pop bc ; restore plot points ret ; and done! ; Secondary entry point - doesn't preserve BC chunky_plot: ; make sure not out of bounds ld a,b ; ld a with x bit 7,a ret nz ; out of bounds sub 32 ret p ; out of bounds ld a,c ; ld a with y bit 7,a ret nz ; out of bounds sub 48 ret p ; out of bounds ; get character on screen ld hl,(d_file) inc hl ld a,c ; load a wity y sra a ; divide by 2 or a ; cp 0 jr z,cp_findx ; already at y,skip ld de,33 cp_findy: add hl,de dec a jr nz, cp_findy cp_findx: ld d,0 ld e,b ; load e with x add hl,de ld a,(hl) ; grab charater push hl ; save screen location ; find char and convert to 4 bit normlized format ld hl,cp_char_table+10 ; start at end of table ld b,10 ; walk backwards cp_find_new_char_loop: cp (hl) ; did we match? jr z,cp_get_top_or_bottom ; yes, skip ahead! dec hl ; try next character djnz cp_find_new_char_loop ; no? Keep searching cp_get_top_or_bottom: ld a,(pen_color) ld d,a ; grab and set pen color ; d = pen color ; b = normalized char ; c = y coordinate ld a,c ; get y coord rrca ; check if even or odd jp nc,cp_is_even cp_is_odd: ld a,%00001100 ; mask out bottom and b add a,d ; add in pen color jp cp_print_char cp_is_even: ld a,%00000011 ; mask out top and b sla d ; shift pen color to top sla d add a,d ; add in pen color cp_print_char: ; a = new character ld l,a ; extend to 16 bits ld h,0 ld bc,cp_char_table add hl,bc ; get destination ld a,(hl) ; and grab new character pop hl ; restore screen location ld (hl),a ; load new character to screen ret ; and done! ; Here is the conversion table to the characters: cp_char_table: DB %00000000 ; 0 - 00000000 - white/white 00 DB %00001001 ; 1 - 00000001 - white/hash 09 DB %10000011 ; 2 - 00000010 - white/black 83 DB %11111111 ; 3 - 00000011 - not used DB %00001010 ; 4 - 00000100 - hash/white 0A cp_hash_hash_char: ; label so I can change this DB %00001000 ; 5 - 00000101 - hash/hash 08 DB %10001010 ; 6 - 00000110 - hash/black 8A (inverted hash) DB %11111111 ; 7 - 00000111 - not used DB %00000011 ; 8 - 00001000 - black/white 03 DB %10001001 ; 9 - 00001001 - black/hash 89 (inverted hash) DB %10000000 ; 10 - 00001010 - black/black 80 ; end chunky plot ; --- ; +++ ; Other Math Routines ; absA: ; get absolute value of a or a ret p neg ret sgnA: ; get sign of A (-1, 0 or 1) or a ; is a 0? jp z,sgnA_zero jp p,sgnA_plus sgnA_minus: xor a dec a ret sgnA_zero: xor a ret sgnA_plus: xor a inc a ret ; end math routines ; --- ; +++ ; Draw_Line ; ; variables u_var: DB 0 v_var: DB 0 b_var: DB 0 a_var: DB 0 d_var: DB 0 c_var: DB 0 d1x_var: DB 0 d1y_var: DB 0 d2y_var: DB 0 d2x_var: DB 0 m_var: DB 0 n_var: DB 0 s_var: DB 0 draw_line: ; save a, b ld hl,b_var ld (hl),c inc hl ld (hl),b ; save c, d ld hl,d_var ld (hl),e inc hl ld (hl),d ; from page 121 of the Sinclair Basic book. ; this looks rather brute force, but I don't much mind as it is ; simple math. Curious how fast this runs. ;1000 LET U=C-A ld a,(a_var) ld b,a ld a,(c_var) sub b ; a-b = c-a ld (u_var),a ;1005 REM U SHOWS HOW MANY STEPS ALONG WE NEED TO GO ;1010 LET V=D-B ld a,(b_var) ld b,a ld a,(d_var) sub b ; a-b = d-b ld (v_var),a ;1015 REM V SHOWS HOW MANY STEPS UP ;1020 LET D1X=SGN U ld a,(u_var) call sgnA ld (d1x_var),a ;1030 LET D1Y=SGN V ld a,(v_var) call sgnA ld (d1y_var),a ;1035 REM (D1X,D1Y) IS A SINGLE STEP IN A DIAGONAL DIRECTION ;1040 LET D2X=SGN U ld a,(u_var) call sgnA ld (d2x_var),a ;1050 LET D2Y=0 xor a ld (d2y_var),a ;1055 REM (D2X,D2Y) IS A SINGLE STEP LEFT OR RIGHT ;1060 LET M=ABS U ld a,(u_var) call absA ld (m_var),a ld b,a ; b = m ;1070 LET N=ABS V ld a,(v_var) call absA ld (n_var),a ; a = n ;1080 IF M>N THEN GOTO 1130 cp b ; n<m? jr c,line_1130 ;1090 LET D2X=0 xor a ld (d2x_var),a ;1100 LET D2Y=SGN V ld a,(v_var) call sgnA ld (d2y_var),a ;1105 REM NOW (D2X,D2Y) IS A SINGLE STEP UP OR DOWN ;1110 LET M=ABS V ld a,(v_var) call absA ld (m_var),a ;1120 LET N=ABS U ld a,(u_var) call absA ld (n_var),a line_1130: ;1130 REM M IS THE LARGER OF ABS U & ABS V, N IS THE SMALLER ;1140 LET S=INT (M/2) ld a,(m_var) srl a ld (s_var),a ;1145 REM WE WANT TO MOVE FROM (A,B) TO (C,D) IN M STEPS USING N UP- DOWN OR RIGHT-LEFT STEPS D2, & M-N DIAGONAL STEPS D1, DISTRIBUTED AS EVENLY AS POSSIBLE ;1150 FOR I=0 TO M ld a,(m_var) ld b,a i_loop: push bc ;1160 PLOT A,B ld bc,(b_var) ; x1 in h, y1 in l call cplot ; ld hl,300 ; call newpause ;1170 LET S=S+N ld hl,s_var ld a,(n_var) add a,(hl) ld (hl),a ;1180 IF S<M THEN GOTO 1230 ld a,(m_var) ld b,a ld a,(hl) cp b ; compare s to m jr c,line_1230 ;1190 LET S=S-M ld hl,s_var ld a,(m_var) ld b,a ld a,(hl) sub b ld (hl),a ;1200 LET A=A+D1X ld hl,a_var ld a,(d1x_var) add a,(hl) ld (hl),a ;1210 LET B=B+D1Y ld hl,b_var ld a,(d1y_var) add a,(hl) ld (hl),a ;1215 REM A DIAGONAL STEP ;1220 GOTO 1250 jr line_1250 line_1230: ;1230 LET A=A+D2X ld hl,a_var ld a,(d2x_var) add a,(hl) ld (hl),a ;1240 LET B=B+D2Y ld hl,b_var ld a,(d2y_var) add a,(hl) ld (hl),a ;1245 REM AN UP-DOWN OR RIGHT-LEFT STEP ;1250 NEXT I line_1250: pop bc djnz i_loop ;1260 RETURN ret ; end draw line ;--- ; +++ ; Draw Circle ; moving the circle into a routine ; paramters: ; bc = x,y coords (center) ; a = radius ; variables: ; x, y ; x1, y1 ; p ; py ; pxy ;variables x_var: DB 0 y_var: DB 0 p_var: DB 0 y1_var: DB 0 x1_var: DB 0 draw_circle: ; save x, y ld hl,x_var ld (hl),b inc hl ld (hl),c ; set p and y1 to zero ld (x1_var),a ; set x1 to r xor a ld (p_var),a ; set p to 0 ld (y1_var),a ; set y1 to 0 plot_loop: ; Plot the coordinates in each sector ; X+X1,Y+Y1 ld hl,(y1_var) ; x1 in h, y1 in l ld a,(x_var) add a,h ; x+x1 ld b,a ld a,(y_var) add a,l ; y+y1 ld c,a call cplot ; X-X1,Y+Y1 ld hl,(y1_var) ; x1 in h, y1 in l ld a,(x_var) sub h ; x+x1 ld b,a ld a,(y_var) add a,l ; y+y1 ld c,a call cplot ; X+X1,Y-Y1 ld hl,(y1_var) ; x1 in h, y1 in l ld a,(x_var) add a,h ; x+x1 ld b,a ld a,(y_var) sub l ; y-y1 ld c,a call cplot ; X-X1,Y-Y1 ld hl,(y1_var) ; x1 in h, y1 in l ld a,(x_var) sub h ; x-x1 ld b,a ld a,(y_var) sub l ; y-y1 ld c,a call cplot ; X+Y1,Y+X1 ld hl,(y1_var) ; x1 in h, y1 in l ld a,(x_var) add a,l ; x+y1 ld b,a ld a,(y_var) add a,h ; y+x1 ld c,a call cplot ; X-Y1,Y+X1 ld hl,(y1_var) ; x1 in h, y1 in l ld a,(x_var) sub l ; x-y1 ld b,a ld a,(y_var) add a,h ; y+x1 ld c,a call cplot ; X+Y1,Y-X1 ld hl,(y1_var) ; x1 in h, y1 in l ld a,(x_var) add a,l ; x+y1 ld b,a ld a,(y_var) sub h ; y-x1 ld c,a call cplot ; X-Y1,Y-X1 ld hl,(y1_var) ; x1 in h, y1 in l ld a,(x_var) sub l ; x-y1 ld b,a ld a,(y_var) sub h ; y-x1 ld c,a call cplot ; calculate changes ld hl,(y1_var) ; x1 in h, y1 in l ; PY=P+Y1+Y1+1 ld a,(p_var) inc a add a,l add a,l ld b,a ; save py into b ld (p_var),a ; save py for later ; PXY=PY-X1-X1+1 inc a sub h sub h ld c,a ; save pxy into c ld hl,y1_var ; y1=y1+1 inc (hl) ; okay, test if we need to change position ld a,b ; set a to py call absA ld b,a ; b now holds abs py ld a,c ; set a to pxy call absA ; a is now abs pxy cp b ; check if abs pxy >= abs py jp nc,skipahead ; no carry if true ld a,c ; set a to pxy ld (p_var),a ; and store in p! ld hl,x1_var ; x1=x1-1 dec (hl) skipahead: ld hl,(y1_var) ; x1 in h, y1 in l ld a,h ; a is now x1 cp l ; is x1>=y1? ret c ; if no we are all done, return! jp plot_loop ; it is, then we keep going! ; end Draw_Circle ; --- ; +++ ; Break ; ; preserves state, but will exit if SPACE is pushed check_break: exx ; save register states ; did the player press break key (space)? call $0f46 ; was break pressed? (break-1 ROM routine) jr nc,break ; no, exit as normal exx ; restore registers ret ; and return ; yes, exit the program as normal break: rst $0008 ; call ERROR-1 reset DB $ff ; with error code 0 (normal exit) ; end break ; --- ; +++ ; Delay ; ; set bc to speed ; uses check_break to exit delay_count: DW $0000 delay: ld (delay_count),hl ; save delay call check_break ; check if done ld hl,(delay_count) ; grab what to test dec hl ; subtract 1 ld a,h ; check if done or l jr nz,delay ; not zero, keep going! ret ; pause is done! ; end delay ; --- ; +++ ; ; Data and Defines ; ; ZX81 system vars d_file: EQU $400c df_cc: EQU 16398 last_k: EQU 16421 margin: EQU 16424 s_posn: EQU 16441 frames: EQU 16436 ; ZX81 ROM functions kscan: EQU $02bb findchar: EQU $07bd stop: EQU $0cdc slow: EQU $0f2b fast: EQU $02e7 save: EQU $02f9 printat: EQU $08f5 pause: EQU $0f35 cls: EQU $0a2a ; ZX81 Characters (not ASCII) _sp: EQU $00 _qu: EQU $0b _lb: EQU $0c _dl: EQU $0d _cl: EQU $0e _lp: EQU $10 _rp: EQU $11 _gt: EQU $12 _lt: EQU $13 _eq: EQU $14 _pl: EQU $15 _mi: EQU $16 _as: EQU $17 _sl: EQU $18 _sc: EQU $19 _cm: EQU $1a _pr: EQU $1b _0_: EQU $1c _1_: EQU $1d _2_: EQU $1e _3_: EQU $1f _4_: EQU $20 _5_: EQU $21 _6_: EQU $22 _7_: EQU $23 _8_: EQU $24 _9_: EQU $25 _a_: EQU $26 _b_: EQU $27 _c_: EQU $28 _d_: EQU $29 _e_: EQU $2a _f_: EQU $2b _g_: EQU $2c _h_: EQU $2d _i_: EQU $2e _j_: EQU $2f _k_: EQU $30 _l_: EQU $31 _m_: EQU $32 _n_: EQU $33 _o_: EQU $34 _p_: EQU $35 _q_: EQU $36 _r_: EQU $37 _s_: EQU $38 _t_: EQU $39 _u_: EQU $3a _v_: EQU $3b _w_: EQU $3c _x_: EQU $3d _y_: EQU $3e _z_: EQU $3f ; end defines ; ---