A joystick-driven pixel graphics editor for the TS2068, with additions including a Bezier arc routine adapted from Paul Bingham’s curve algorithm (LIST, February 1985). The joystick moves a pixel cursor via machine code running at address 65000; drawing tools include line, circle, flood fill, and smooth curves through up to 20 control points. Additional features cover screen save/load to tape, character stamping with attribute editing, a reference grid overlay, and adjustable cursor speed.
Machine code (lines 30–60): 123 bytes POKEd to addresses 65000–65122. The code reads joystick input via port 0xF6, tests individual direction bits, and moves a pixel cursor accordingly, returning the cursor coordinates packed as y*256+x in the BC register. Several entry points exist: 65000 (read joystick + move cursor in OVER 1 mode), 65084 (restore screen from buffer), 65089 (show cursor), 65097 (save screen to buffer), 65102 (hide cursor). The cursor speed is tunable — q prompts for a 0–9 value that’s POKEd into the delay loop at 65116.
Main loop (lines 100–320): Single-keypress command dispatcher. Full command set:
j/J— joystick pixel cursor (XOR overlay / line-draw mode)x— set anchor point at current cursor positiond— draw line between last two anchor pointsc— draw circle (center and radius from last two anchor points)A— draw Bezier/arc curve through typed coordinatesF— flood fill bounded area from cursor position (scanline-based, expands left/right then up/down)g— toggle reference grid (█ at every even row/column cell)k/r— save/restore screen to internal buffer via machine codes/U— save/load screen to tape as SCREEN$w— write a typed character at character-cell cursor positiony/z— copy character+attribute into buffer / stamp from buffera— directly edit the attribute byte at the character-cell cursorb/i/p— set border/ink/paper colorv— enter pixel cursor position numericallye— erase/draw with joystick using INVERSE 1N— clear screenl— LPRINT (COPY)E— exith— help screen
Line 1300: Visual flash effect — POKEs eight pixel memory addresses at the screen edges with diagonal stripe bytes (00001000, 01001010, 10101100, etc.), pauses 20 frames, then zeros them out. Used as a “blink” acknowledgment after operations like saving or setting an anchor point.
Lines 1400–1420: Converts character-cell coordinates (cx, cy) to Spectrum/TS2068 pixel memory address, correctly handling the non-linear display layout (screen divided into three 2KB thirds).
Bezier curve (lines 2000–2370): Bernstein polynomial interpolation through up to 20 control points at 60 steps. Optionally draws the control polygon alongside the curve. Vertices are marked OVER 1 after drawing to toggle off any artifacts at the control points.
One incomplete section: line 90 notes ON ERR GO TO 100 was temporarily removed during development, and line 1040 contains what appears to be a STICK fire-button wait loop that some of the joystick subroutines jump into rather than their own RETURN paths (lines 1830, 1930 both GO TO 1040 instead of falling through). The subroutine at 1900 appears unreachable from the main loop — likely a leftover development variant.
Content
Source Code
5 REM use joystick-hit cap H forhelp-early WES!
10 CLEAR 51175: DIM c(9)
20 LET px=0: LET py=0: LET lx=0: LET ly=0: LET x=0: LET y=0
30 FOR a=65000 TO 65122
40 READ d: POKE a,d
50 NEXT a
60 DATA 205,090,254,000,000,237,075,125,092,062,014,211,245,062,001,219,246,087,203,079,032,007,120,254,000,040,001,005,122,203,071,032,007,120,254,175,040,001,004,122,203,087,032,007,121,254,000,040,001,013,122,203,095,032,007,121,254,255,040,001,012,122,245,197,205,062,038,205,090,254,000,000,193,197,205,062,038,193,241,203,127,200,024,172,033,232,199,024,003,033,232,226,017,000,064,024,011,017,232,199,024,003,017,232,226,033,000,064,001,000,027,237,176,201,001,255,001,011,120,177,032,251,201
80 LET cx=0: LET cy=0
90 REM on err goto 100 removed temp while under construction
95 REM with apologizes to Wes, I'm trying to improve the capabilities of this program
100 IF INKEY$="E" THEN ON ERR RESET : STOP
105 IF INKEY$="A" THEN GO SUB 2000
110 IF INKEY$="N" THEN CLS
120 IF INKEY$="l" THEN COPY
130 IF INKEY$="k" THEN RANDOMIZE USR 65097: GO SUB 1300
140 IF INKEY$="r" THEN RANDOMIZE USR 65084
150 IF INKEY$<>"g" THEN GO TO 160
152 RANDOMIZE USR 65102: GO SUB 1100
153 IF INKEY$="g" THEN GO TO 153
155 RANDOMIZE USR 65089
160 IF INKEY$="d" THEN PLOT lx,ly: DRAW (px-lx),(py-ly)
170 IF INKEY$="x" THEN LET lx=px: LET px=x: LET ly=py: LET py=y: GO SUB 1300
175 IF INKEY$="x" THEN GO TO 175
180 IF INKEY$="c" THEN CIRCLE lx,ly,INT (.5+SQR ((lx-px)*(lx-px)+(ly-py)*(ly-py)))
190 IF INKEY$<>"b" THEN GO TO 200
194 INPUT AT 0,0;"Border Color? ";a
196 IF a>=0 AND a<=7 THEN BORDER INT a
200 IF INKEY$<>"i" THEN GO TO 210
204 INPUT AT 0,0;"Ink Color? ";a
206 IF a>=0 AND a<=7 THEN INK INT a
210 IF INKEY$<>"p" THEN GO TO 220
214 INPUT AT 0,0;"Paper Color? ";a
216 IF a>=0 AND a<=7 THEN PAPER INT a
220 IF INKEY$="s" THEN INPUT AT 0,0;"Screen Name? ";N$: IF N$<>"" THEN SAVE N$SCREEN$
225 IF INKEY$="e" THEN GO SUB 7250
230 IF INKEY$="j" THEN GO SUB 1300: GO SUB 1010
235 IF INKEY$="J" THEN GO SUB 1300: GO SUB 1800
240 IF INKEY$="q" THEN INPUT AT 0,0;"Cursor Speed? 0-9 (0=fast)";a: IF a>=0 AND a<=9 THEN POKE 65116,INT a
250 IF INKEY$<>"F" THEN GO TO 260
251 LET xmin=x: LET xmax=x: PLOT x,y
252 IF POINT (xmin-1,y)=0 THEN PLOT xmin-1,y: LET xmin=xmin-1: GO TO 252
253 IF POINT (xmax+1,y)=0 THEN PLOT xmax+1,y: LET xmax=xmax+1: GO TO 253
254 FOR j=xmin TO xmax: LET ymax=y+1
255 IF POINT (j,ymax)=0 THEN PLOT j,ymax: LET ymax=ymax+1: GO TO 255
256 NEXT j
257 FOR j=xmin TO xmax: LET ymax=y-1
258 IF POINT (j,ymax)=0 THEN PLOT j,ymax: LET ymax=ymax-1: GO TO 258
259 NEXT j
260 IF INKEY$="w" THEN GO SUB 1200: INPUT AT 0,0;"Character? ";n$: PRINT AT cy,cx;n$(1)
270 IF INKEY$="U" THEN INPUT AT 0,0;"Screen Name? ";n$: LOAD n$SCREEN$
280 IF INKEY$="a" THEN GO SUB 1200: INPUT AT 0,0;"Attribute Byte? ";a: POKE (22528+cx+32*cy),a
290 IF INKEY$<>"v" THEN GO TO 300
292 INPUT AT 0,0;"Cursor x Position? ";a: IF a>=0 AND a<=255 THEN LET x=INT a
294 INPUT AT 0,0;"Cursor y Position?";a: IF a>=0 AND a<=175 THEN LET y=INT a
300 IF INKEY$="y" THEN GO SUB 1200: GO SUB 1400: FOR j=0 TO 7: LET c(j+1)=PEEK (start+256*j): NEXT j: GO SUB 1300: LET c(9)=PEEK (22528+cx+32*cy)
310 IF INKEY$="z" THEN GO SUB 1200: GO SUB 1400: FOR j=0 TO 7: POKE (start+256*j),c(j+1): NEXT j: POKE (22528+cx+32*cy),c(9)
320 IF INKEY$="h" THEN GO SUB 1500
1000 GO TO 100
1010 OVER 1: LET p=USR 65000: OVER 0
1020 LET y=INT (p/256): LET x=p-y*256: RANDOMIZE USR 65102
1030 PRINT #1;x;" ",y;" "
1040 IF |(2,1)=1 THEN GO TO 1040
1050 RANDOMIZE USR 65089
1060 RETURN
1100 FOR j=0 TO 30 STEP 2
1110 FOR k=0 TO 20 STEP 2
1120 PRINT AT k,j;"\::": NEXT k: NEXT j
1160 RETURN
1200 RANDOMIZE USR 65102
1210 RANDOMIZE USR 65089: PRINT AT cy,cx;"\::"
1220 IF |(1,1)=2 AND cy<21 THEN LET cy=cy+1
1230 IF |(1,1)=1 AND cy>0 THEN LET cy=cy-1
1240 IF |(1,1)=4 AND cx>0 THEN LET cx=cx-1
1245 PRINT AT cy,cx;" "
1250 IF |(1,1)=8 AND cx<31 THEN LET cx=cx+1
1260 IF |(2,1)=0 THEN GO TO 1210
1270 RANDOMIZE USR 65089: RETURN
1300 POKE 20735,8: POKE 20991,8: POKE 21247,8: POKE 21503,9: POKE 21759,74: POKE 22015,172: POKE 22271,170: POKE 22527,73: PAUSE 20: POKE 20735,0: POKE 20991,0: POKE 21247,0: POKE 21503,0: POKE 21759,0: POKE 22015,0: POKE 22271,0: POKE 22527,0: RETURN
1400 LET tempy=cy: LET start=16384
1410 IF tempy>7 THEN LET tempy=tempy-8: LET start=start+2048: GO TO 1410
1420 LET start=start+cx+32*tempy: RETURN
1500 RANDOMIZE USR 65102: CLS
1520 PRINT "v=Key In Pixel Cursor Position"
1530 PRINT "x=Set x-Point At Last Pixel"
1540 PRINT "d=Draw Line From Last 2 x-Points"
1550 PRINT "c=Circle From Last 2 x-Points"
1560 PRINT "g=Put Up The Reference Grid"
1570 PRINT "F=Fill In The Bounded Area"
1580 PRINT "q=""Quickness"" Of Pixel Cursor"
1600 PRINT "a=Set Attribute For Character"
1610 PRINT "y=Put Character In Buffer"
1620 PRINT "z=Write Character Now In Buffer"
1630 PRINT "k=Keep Screen In Screen Buffer"
1640 PRINT "r=restore Screen \:: A= Draw arc"
1642 PRINT "w=Write Character\::e=erase line"
1643 PRINT "j=Joystick/Pixel\::J=Joystick line"
1650 PRINT "b=Border Color \::i=Ink Color"
1660 PRINT "p=Paper Color \::l=LPRINT Screen"
1670 PRINT "U=LOAD Screen \::s=SAVE Screen"
1680 PRINT "N=New Screen \::E=Exit Program"
1690 PRINT "\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::"
1700 PRINT "\::Joystick Must Be In Left Port \::"
1710 PRINT "\::\::Characters Must Be Upper Or \::\::"
1720 PRINT "\::\::\::Lower Case As Shown Above \::\::\::"
1730 IF INKEY$="h" THEN GO TO 1730
1740 RANDOMIZE USR 65089: RETURN
1800 OVER 0: LET p=USR 65000: OVER 1
1810 LET y=INT (p/256): LET x=p-y*256: RANDOMIZE USR 65102
1820 PRINT AT 0,0;x;" ",y;" "
1830 IF |(2,1)=1 THEN GO TO 1040
1840 RANDOMIZE USR 65089
1850 RETURN
1900 OVER 1: LET p=USR 65000: OVER 0
1910 LET y=INT (p/256): LET x=p-y*256: RANDOMIZE USR 65102
1920 PRINT #1;x;" ",y;" "
1930 IF |(2,1)=1 THEN GO TO 1040
1940 RANDOMIZE USR 65089
1950 RETURN
2000 REM draw arc
2001 REM "curve"by Paul Bingham, LIST, 2/85 adapted by Paul Hill
2010 LET q=60: DIM s(20): DIM u(20): DIM n(20): DIM f(120): DIM r(120)
2020 INPUT "total number of corners; ";g
2030 LET w=g-1
2040 INPUT "1st coordinates: x=";s;"y=";u
2050 FOR i=1 TO w+1
2060 LET s(i)=s: LET u(i)=u
2070 PLOT s,u: IF i=w+1 THEN GO TO 2090
2080 INPUT "next coordinates:x=";s;"y=";u
2090 NEXT i
2100 GO SUB 2120
2110 PAUSE 0: RETURN
2120 LET f(1)=s(1): LET r(1)=u(1)
2130 FOR e=2 TO q-1
2140 LET j=((e-1))/(q-1): LET n(1)=(1-j)^w
2150 FOR i=1 TO w
2160 LET n(i+1)=(g-i)/i*j/(1-j)*n(i)
2170 NEXT i
2180 LET f(e)=0: LET r(e)=0
2190 FOR i=1 TO w+1
2200 LET f(e)=f(e)+n(i)*s(i)
2210 LET r(e)=r(e)+n(i)*u(i)
2220 NEXT i
2230 NEXT e
2240 LET f(q)=s(g): LET r(q)=u(g)
2250 INPUT "Curve only-c;Frame & Curve-f";z$
2260 IF z$="c" THEN GO TO 2310
2270 FOR i=1 TO w
2280 PLOT s(i),u(i)
2290 DRAW s(i+1)-s(i),u(i+1)-u(i)
2300 NEXT i
2310 FOR e=2 TO q-1
2320 PLOT f(e),r(e)
2330 NEXT e
2340 FOR i=1 TO g
2350 PLOT OVER 1;s(i),u(i)
2360 NEXT i
2370 RETURN
7250 LET xe=PEEK 23677
7255 LET ye=PEEK 23678
7260 IF |(1,1)=2 AND ye<175 THEN LET ye=ye-1
7262 IF |(1,1)=1 AND ye>0 THEN LET ye=ye+1
7264 IF |(1,1)=4 AND xe>0 THEN LET xe=xe-1
7265 PLOT INVERSE 1;xe,ye
7266 IF |(1,1)=8 AND xe<2550 THEN LET xe=xe+1
7268 IF |(2,1)=0 THEN GO TO 7260
7305 RETURN
9999 SAVE "arcSCRATCH"
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.
