--- title: "AutoGrafix" id: 71160 type: "computer_media" slug: "autografix" url: "http://localhost/computer_media/autografix/" markdown_url: "http://localhost/computer_media/autografix.md" published_at: "2026-08-31T07:43:01+00:00" modified_at: "2026-08-31T07:43:02+00:00" author: "David Anderson" featured_image: url: "http://localhost/wp-content/uploads/2026/08/autografix-1.png" alt: "AutoGrafix screen" excerpt: "A full-featured UDG character editor supporting joystick or keyboard control, pixel-by-pixel bit toggling, numeric byte entry, and tape save/load for all 21 user-defined graphics." category: - name: "Archived Media" slug: "archived-media" taxonomy: "category" url: "http://localhost/category/archived-media/" post_tag: - name: "Downloadable" slug: "downloadable" taxonomy: "post_tag" url: "http://localhost/tag/downloadable/" - name: "TS 2068" slug: "ts2068" taxonomy: "post_tag" url: "http://localhost/tag/ts2068/" model: - name: "Timex/Sinclair 2068" slug: "ts-2068" taxonomy: "model" url: "http://localhost/model/ts-2068/" genre: - name: "User Defined Graphics" slug: "user-defined-graphics" taxonomy: "genre" url: "http://localhost/type/user-defined-graphics/" media_type: "Program" download_url: "https://archive.org/download/timex-sinclair-software-archive/AutoGrafix%20%281984%29%28-%29%28TS2068%29%28US%29%28Program%29.zip" mediadate: "1984" images: - url: "http://localhost/wp-content/uploads/2026/08/autografix-1.png" alt: "AutoGrafix screen" - url: "http://localhost/wp-content/uploads/2026/08/autografix-2.png" alt: "AutoGrafix screen" media_type_tags: "User Defined Graphics" --- # AutoGrafix AutoGrafix is a User Defined Graphic (UDG) character editor that lets users design all 21 UDG characters (A–U) either interactively with a joystick/keyboard cursor or by entering raw byte values numerically. The program uses custom character shapes stored at address 64000 to draw a box cursor on screen, manipulating the character definition table pointer at address 23606 (CHARS) to temporarily swap the definition of the block character used for the editing grid. Two 8×8 arrays, `S(8,8)` for the current pixel state and `A(21,8)` for all 21 UDG byte values, track edits in memory; completed designs can be saved and reloaded as a named DATA file via tape using SAVE/LOAD with the array. Joystick input is supported on both ports using STICK, allowing simultaneous keyboard and joystick operation for cursor movement and bit toggling. *** ### Program Structure AutoGrafix is organized as a collection of named subroutines, each preceded by a REM label rendered in inverse video. The main flow is: 1. Line 1080 – INITIALIZE: dimensions arrays, sets constants, POKEs custom box characters, reads current UDG values. 2. Line 850 – TITLES: splash screen with animated UDG pixels, joystick port selection, and tape-load option. 3. Line 570 – MAIN: menu loop dispatching to cursor editor, numeric editor, save, or quit. 4. Line 200 – EXISTING: renders old and new UDG bitmaps side by side; prompts Create or Modify. 5. Line 80 – CURSOR: interactive pixel editor using joystick/keyboard. 6. Line 410 – TURNON: toggles a single pixel bit and updates the byte array. 7. Line 490 – NUMERIC: byte-by-byte numeric entry mode. 8. Line 790 – STORE: writes all edited `A()` values back to UDG RAM via `POKE USR X$+N`. 9. Line 1020 – SAVE: saves array `A()` to tape by name; verifies after writing. ### Custom Cursor Character Technique The program stores two custom 8×8 pixel patterns at address 64000 (a solid box outline and a filled box) using DATA statements at lines 1150–1180. The system variable `CHARS` (address 23606) normally points 256 bytes before the character set. By POKEing `CHARS` with `BOX1` (248) and `CHARS+1` with `BOX2` (247), the program temporarily redirects the character lookup so that printing `CHR$ 8` (backspace character code) renders the custom box instead. After each operation the pointer is restored to 0/60 to re-establish the normal character set. This avoids consuming a UDG slot for the cursor. ### UDG Address Calculation The address of a UDG byte is computed throughout as: `PEEK UDG + 256 * PEEK (UDG+1) + (code-1)*8 + byte_index` where `UDG` = 23675, a two-byte system variable holding the base address of the UDG table. This is the canonical two-byte little-endian peek idiom used instead of the simpler `USR X$` form, possibly for slight speed or flexibility. ### Pixel State Arrays `S(8,8)` holds the current on-screen pixel state for one UDG being edited, using the ASCII codes 65 (‘A’) for a set pixel and 66 (‘B’) for a clear pixel. This allows `PRINT AT x,y; CHR$ S(r,c)` to redraw any cell directly. `A(21,8)` stores the byte values for all 21 UDGs simultaneously, enabling batch store operations and the numeric entry mode. ### Joystick and Keyboard Input Lines 130 and 140 use compound boolean arithmetic — a hallmark of ZX Spectrum BASIC optimization — to compute cursor delta in a single expression. Both joystick ports and the 5/6/7/8/SPACE keys are checked simultaneously. The variable `PORT` is set during initialization based on which fire button the user presses first, selecting between STICK port 1 and port 2 for movement, while fire detection always reads port 2. ### Bit Manipulation in TURNON (line 410) When a pixel is toggled at cursor position `(X,Y)`, the corresponding bit in `A(code, X-XO)` is updated using powers of two: - Turn off (A→B): `A(code,row) = A(code,row) - 2^(8-(Y-YO))` - Turn on (B→A): `A(code,row) = A(code,row) + 2^(8-(Y-YO))` The column offset `8-(Y-YO)` maps screen Y position to bit weight (MSB on the left). This is a straightforward but correct bit-field manipulation without bitwise operators, since Spectrum BASIC lacks them. ### Bitmap Rendering in EXISTING (line 250–300) Bytes are decoded bit-by-bit in the inner loop at line 280 using repeated integer division by 2 to extract each bit LSB-first, then printed in reverse column order (14-C) so that the visual result is MSB-left. When `MOD=1`, the decoded pixel values are also written back into `S()` for subsequent cursor-mode editing. ### Tape I/O `SAVE E$ DATA A()` and `LOAD E$ DATA A()` serialize the entire 21×8 numeric array to/from tape under a user-supplied filename. After loading, `GO SUB 790` with `FILE=1` iterates all 21 UDGs and POKEs each byte into the UDG area, making the loaded designs immediately active. ### Notable Idioms and Details - `F$` is a 32-space string (line 1140) used throughout as a fast screen-clearing print for specific rows, avoiding a full `CLS`. - `TITLE` flag (set to 1 at line 1130) controls whether EXISTING renders the old UDG column at all during the splash sequence versus normal editing. - Line 870 uses a `FLASH` variable (distinct from the keyword) as a flag — a subtle name collision that works because Spectrum BASIC resolves the keyword from context. - The `MOD` variable (line 1110) tracks whether the user chose Modify mode; it is set to 0 after display and to 1 when modifications are applied, controlling the EXISTING subroutine’s dual-purpose behavior. - Line 760 prints UDG preview characters \a through \u (UDGs A–U) in a grid so the user can see current designs at a glance after each editing session. - Line 1240 saves the program itself with `LINE 1` for auto-run. ### Potential Issues - The variable name `VAL` at line 260 shadows the built-in `VAL` function. Spectrum BASIC allows this, but any subsequent use of `VAL "..."` would be ambiguous; however, no such use appears in this program. - Similarly, `FLASH` is used as both a keyword and a variable name (line 860/870). This works because Spectrum BASIC distinguishes them by context, but it is an unusual and potentially confusing pattern. - The numeric entry in line 530 accepts values via `INPUT A(code,C)` without an upper range guard at entry time; the out-of-range check at line 540 aborts the whole session rather than re-prompting for the same byte. ## Source Code ``` 10 REM *** Program AUTOGRAFIX 20 REM *** Updated 4/10/84 AM 30 POKE 23658,8:POKE 23609,10 40 GO SUB 1080 50 GO SUB 850 60 LET TITLE=0 70 GO TO 570 80 REM \{20}\{1} CURSOR \{20}\{0} 90 LET X=XO+1:LET Y=YO+1 100 LET X1=X:LET Y1=Y 110 IF INKEY$=" " OR STICK (2,PORT)=1 THEN GO SUB 410 120 LET X1=X:LET Y1=Y 130 LET X=X+((INKEY$="6" OR STICK (1,PORT)=6 OR STICK (1,PORT)=2 OR STICK (1,PORT)=10) AND X<14)-((INKEY$="7" OR STICK (1,PORT)=5 OR STICK (1,PORT)=1 OR STICK (1,PORT)=9) AND X>7) 140 LET Y=Y+((INKEY$="8" OR STICK (1,PORT)=8 OR STICK (1,PORT)=9 OR STICK (1,PORT)=10) AND Y<26)-((INKEY$="5" OR STICK (1,PORT)=4 OR STICK (1,PORT)=5 OR STICK (1,PORT)=6) AND Y>19) 150 IF X <>X1 OR Y <>Y1 THEN BEEP .01, RND*30:POKE CHARS,BOX1:POKE (CHARS+1),BOX2:PRINT AT X1,Y1; CHR$ S(X1-XO,Y1-YO):POKE CHARS,0:POKE (CHARS+1),60 160 PRINT OVER 1; INK 9; AT X,Y;"*" 170 IF INKEY$= CHR$ 13 THEN PRINT AT 2,0;F$; AT 3,0;F$; AT 17,0;F$; AT 18,0;F$:BEEP .02,15:RETURN 180 IF INKEY$="0" THEN FOR J=1 TO 8:LET A(code,J)= PEEK (PEEK UDG+256* PEEK (UDG+1)+(code-1)*8+J-1):NEXT J:PRINT AT 2,0;F$; AT 3,0;F$; AT 17,0;F$; AT 18,0;F$:GO TO 570 190 GO TO 110 200 REM \{20}\{1} EXISTING \{20}\{0} 210 POKE CHARS,BOX1:POKE (CHARS+1),BOX2 220 PRINT AT 6,0:FOR N=1 TO 8 230 BEEP .01, RND*30:PRINT TAB 19-15*TITLE;"BBBBBBBB":NEXT N:IF TITLE=1 THEN POKE CHARS,0:POKE (CHARS+1),60:RETURN 240 IF TITLE=0 THEN POKE CHARS,0:POKE (CHARS+1),60:FOR N=1 TO 8:PRINT AT (6+N),28;"0":NEXT N:POKE CHARS,BOX1:POKE (CHARS+1),BOX2 250 FOR B=0 TO 7 260 LET VAL= PEEK (PEEK UDG+256* PEEK (UDG+1)+B+8*(code-1)) 270 IF NUMB=1 THEN LET VAL=A(code,B+1) 280 FOR C=1 TO 8:LET XX= INT (VAL/2):LET AORB=VAL-2*XX:LET VAL=XX 290 PRINT AT B+7,13*MOD+14-C; CHR$ (66-AORB):IF MOD=1 THEN LET S(B+1,27-C-YO)=66-AORB 300 NEXT C:NEXT B 310 POKE CHARS,0:POKE (CHARS+1),60 320 IF MOD=1 THEN LET MOD=0:FOR N=1 TO 8:PRINT AT 6+N,28;A(code,N):NEXT N:RETURN 330 IF NUMB=1 THEN RETURN 340 FOR V=1 TO 4:BEEP .015, RND*20:NEXT V:POKE CHARS,0:PRINT AT 17,8;"\{20}\{1}\{20}\{1}C\{20}\{0}REATE OR \{20}\{1}M\{20}\{0}ODIFY?":PAUSE 0 350 IF INKEY$="M" THEN LET MOD=1:POKE CHARS,BOX1:POKE (CHARS+1),BOX2:BEEP .02,10:PRINT AT 17,0;F$:GO TO 250 360 IF INKEY$ <>"C" THEN GO TO 350 370 BEEP .02,10:PRINT AT 17,8;" " 380 FOR N=1 TO 8:FOR M=1 TO 8:LET S(N,M)=66:NEXT M:LET A(code,N)=0:NEXT N 390 POKE CHARS,0:POKE (CHARS+1),60 400 RETURN 410 REM \{20}\{1} TURNON \{20}\{0} 420 BEEP .02, RND*20-20 430 POKE CHARS,BOX1:POKE (CHARS+1),BOX2 440 IF S(X-XO,Y-YO)=65 THEN PRINT AT X,Y; CHR$ 66:LET S(X-XO,Y-YO)=66:LET A(code,X-XO)=A(code,X-XO)-2^(8-(Y-YO)):GO TO 460 450 IF S(X-XO,Y-YO)=66 THEN PRINT AT X,Y; CHR$ 65:LET S(X-XO,Y-YO)=65:LET A(code,X-XO)=A(code,X-XO)+2^(8-(Y-YO)) 460 POKE CHARS,0:POKE (CHARS+1),60 470 PRINT AT X,28;" ":PRINT AT X,28;A(code,X-XO) 480 RETURN 490 REM \{20}\{1} NUMERIC \{20}\{0} 500 FOR C=1 TO 8:PRINT AT 21,(C-1)*4;A(code,C):NEXT C 510 FOR V=1 TO 4:BEEP .015, RND*20:NEXT V 520 FOR C=1 TO 8:FOR B=1 TO 8:PRINT AT 19,(B-1)*4;"#";B:NEXT B 530 PRINT AT 16,0;"ENTER NEW VALUE FOR BYTE:"; AT 17,0;"(OR VALUE >255 TO QUIT)":PRINT FLASH 1; AT 19,(C-1)*4;"#";C:INPUT A(code,C):PRINT AT 21,(C-1)*4;" ":PRINT AT 21,(C-1)*4;A(code,C) 540 BEEP .1, RND*20:PRINT AT 19,28;"#8":IF A(code,C)>255 OR A(code,C)<0 THEN FOR J=1 TO 8:LET A(code,J)= PEEK (PEEK UDG+256* PEEK (UDG+1)+(code-1)*8+J-1):NEXT J:GO TO 570 550 NEXT C:LET MOD=1:POKE CHARS,BOX1:POKE (CHARS+1),BOX2:GO SUB 250 560 RETURN 570 REM \{20}\{1} MAIN \{20}\{0} 580 LET NUMB=0 590 POKE 23658,8 600 CLS :FOR V=1 TO 4:BEEP .015, RND*20:NEXT V:PRINT AT 7,0;"PRESS A-U TO CREATE WITH CURSOR"; AT 9,6;"'X' TO CREATE WITH NUMBERS" 610 PRINT AT 11,6;"'Y' TO SAVE DATA"; AT 13,6;"'Z' TO STOP PROGRAM":PAUSE 0 620 LET X$= INKEY$:IF X$<"A" OR X$>"Z" OR X$="V" OR X$="W" THEN GO TO 620 630 BEEP .1,5:IF X$="Y" THEN GO TO 1020 640 IF X$="Z" THEN CLS :STOP 650 IF X$="X" THEN LET NUMB=1 660 CLS :FOR V=1 TO 4:BEEP .015, RND*20:NEXT V:PRINT AT 0,3;"SPECIFY UDG CHARACTER (A-U)" 670 PAUSE 0:LET X$= INKEY$:IF X$<"A" OR X$>"U" THEN GO TO 670 680 CLS :PRINT AT 0,2;"THIS IS THE UDG CHARACTER '";X$;"'"; AT 5,6;"OLD"; AT 5,19;"NEW" 690 LET code= CODE X$-64 700 GO SUB 200 710 IF NUMB=1 THEN GO SUB 490:PRINT AT 16,0;F$; AT 17,0;F$; AT 19,0;F$; AT 21,0;F$ 720 FOR V=1 TO 4:BEEP .015, RND*20:NEXT V:PRINT AT 2,1;"USE JOYSTICK OR KEYS TO MOVE *"; AT 3,1;"USE \{20}\{1}SPACE\{20}\{0} OR \{20}\{1}FIRE\{20}\{0} TO ALTER BIT"; AT 17,6;"PRESS \{20}\{1}ENTER\{20}\{0} TO STORE"; AT 18,6;"PRESS '0' TO QUIT" 730 GO SUB 80 740 GO SUB 790 750 PRINT AT 16,0;F$; AT 19,0;F$ 760 PRINT AT 17,0;"A B C D E F G H I J K "; AT 18,0;" \a \b \c \d \e \f \g \h \i \j \k"; AT 20,0;" L M N O P Q R S T U "; AT 21,0;" \l \m \n \o \p \q \r \s \t \u " 770 PRINT #1;" Press any key to continue":PAUSE 0 780 GO TO 570 790 REM \{20}\{1} STORE \{20}\{0} 800 IF FILE=1 THEN FOR J=1 TO 21:LET code=J:LET X$= CHR$ (code+64) 810 FOR N=0 TO 7 820 POKE USR X$+N,A(code,N+1) 830 NEXT N:IF FILE=1 THEN NEXT J 840 RETURN 850 REM \{20}\{1} TITLES \{20}\{0} 860 LET FLASH=0 870 PRINT INVERSE 1; FLASH FLASH; AT 7,14;" "; AT 8,14;" AUTOGRAFIX "; AT 9,14;" "; INVERSE 0; FLASH 0; AT 14,14;"\*1984 B. Schaffer":IF FLASH=1 THEN RETURN 880 GO SUB 200:FOR N=1 TO 25:PRINT AT XO+ INT (RND*8+1),YO+ INT (RND*8+1)-15;"\::":BEEP .10, RND*25:NEXT N 890 LET FLASH=1:GO SUB 870:PAUSE 120:FOR C=1 TO 10:BEEP .008,4*C:NEXT C 900 CLS :PRINT AT 8,2;"PRESS \{20}\{1}FIRE BUTTON\{20}\{1} #2\{20}\{0}\{20}\{0} TO USE"; AT 9,2;"JOYSTICK #2 AND/OR KEYBOARD"; AT 12,2;"\{20}\{1}FIRE BUTTON #1\{20}\{0} OR \{20}\{1}SPACE BAR\{20}\{0}"; AT 13,1;"WILL PERMIT USE OF JOYSTICK #1"; AT 14,1;"AND/OR KEYS (5,6,7,8 AND SPACE)" 910 IF INKEY$=" " OR STICK (2,1)=1 THEN LET PORT=1:BEEP .2,15:GO TO 940 920 IF STICK (2,2)=1 THEN LET PORT=2:BEEP .2,15:GO TO 940 930 GO TO 910 940 CLS :PRINT AT 10,0;"\{20}\{1}L\{20}\{0}OAD A UDG FILE FROM TAPE?"; AT 12,0;"\{20}\{1}C\{20}\{0}REATE NEW / ALTER EXISTING UDG?" 950 PAUSE 0:IF INKEY$ <>"L" AND INKEY$ <>"C" THEN GO TO 950 960 BEEP .01,15:IF INKEY$="C" THEN RETURN 970 CLS :INPUT AT 10,0;"NAME OF INPUT FILE? ";E$:BEEP .1,20 980 BEEP .1,20:CLS :PRINT AT 10,7;"Press recorder \{20}\{1}PLAY\{20}\{0}"; AT 12,4;"Press any key to continue":PAUSE 0 990 CLS :BEEP .1,15:LOAD E$ DATA A() 1000 CLS :PRINT AT 10,7;"Press recorder \{20}\{1}STOP\{20}\{0}"; AT 12,4;"Press any key to continue":PAUSE 0 1010 CLS :BEEP .1,10:LET FILE=1:GO SUB 790:LET FILE=0:RETURN 1020 REM \{20}\{1} SAVE \{20}\{0} 1030 CLS :INPUT AT 10,0;"OUTPUT FILE NAME? ";E$:BEEP .1,20:CLS 1040 BEEP .1,15:SAVE E$ DATA A() 1050 CLS :PRINT AT 8,7;"Press recorder \{20}\{1}STOP\{20}\{0}"; AT 10,4;"\{20}\{1}REWIND\{20}\{0} tape to verify save"; AT 12,10;"Press any key"; AT 14,7;"Press recorder \{20}\{0}\{20}\{0}\{20}\{1}PLAY\{20}\{0}":PAUSE 0 1060 CLS :BEEP .1,10:VERIFY E$ DATA :BEEP .1,10:PRINT AT 10,7;"Press recorder \{20}\{1}STOP\{20}\{0}"; AT 12,4;"Press any key to continue" 1070 PAUSE 0:CLS :BEEP .1,10:GO TO 570 1080 REM \{20}\{1} INITIALIZE \{20}\{0} 1090 DIM S(8,8):DIM A(21,8) 1100 BRIGHT 0:FLASH 0:INVERSE 0:PAPER 1:BORDER 1:INK 9:CLS 1110 LET MOD=0 1120 LET FILE=0:LET G=9900:LET ADDRESS=64000:LET CHARS=23606:LET UDG=23675 1130 LET TITLE=1:LET BOX1=248:LET BOX2=247:LET XO=6:LET YO=18 1140 LET F$=" " 1150 DATA 255,255,255,255,255,255,255,255 1160 DATA 255,129,129,129,129,129,129,255 1170 FOR M=0 TO 1:FOR N=0 TO 7 1180 READ USER:POKE ADDRESS+(8*M)+N,USER:NEXT N:NEXT M 1190 FOR N=1 TO 21:FOR M=1 TO 8:LET A(N,M)= PEEK (PEEK UDG+256* PEEK (UDG+1)+(M-1)+8*(N-1)) 1200 NEXT M:NEXT N 1210 RETURN 1220 REM \{20}\{1} CORRECT \{20}\{0} 1230 POKE CHARS,0:POKE (CHARS+1),60 1240 SAVE "AutoGrafix" LINE 1 ```