--- title: "UDG" id: 55075 type: "computer_media" slug: "udg" url: "http://localhost/computer_media/udg/" markdown_url: "http://localhost/computer_media/udg.md" published_at: "2024-06-14T01:24:05+00:00" modified_at: "2026-03-31T22:52:09+00:00" author: "David Anderson" featured_image: url: "http://localhost/wp-content/uploads/2019/02/20230809-041332.jpg" excerpt: "Design your own custom graphic characters pixel by pixel, then save the raw binary data straight to tape with this interactive UDG editor." 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/UDG%20%28198x%29%28Bell%2C%20John%29%28TS2068%29%28US%29%28Program%29.zip" mediadate: "198x" media_type_tags: "User Defined Graphics" --- This program is a UDG (User Defined Graphics) editor that lets users design up to 21 custom characters by painting individual pixels on an 8×8 grid. An on-screen grid is drawn using PLOT and DRAW commands, and the user enters three-character codes specifying a row letter (A–H), a column number (1–8), and either B (black) or W (white) to set each pixel. Pixel state is tracked in a two-dimensional array and converted from a column-weighted binary representation to decimal byte values using a halving-power technique before being POKEd into UDG memory starting at address 65368. When all UDGs are designed, the program prompts the user to save the raw UDG data block (168 bytes) to tape using the SAVE…CODE command. *** ## Program Analysis ### Program Structure The program is organized into clearly labeled subroutines dispatched from a short main loop at lines 60–100: 1. **Lines 200–270** — Draw the 8×8 pixel grid using PLOT/DRAW. 2. **Lines 400–550** — Print column numbers, row letters, and on-screen instructions. 3. **Lines 600–680** — Accept user input and update the grid display and backing array. 4. **Lines 800–920** — Convert the array to bytes and POKE them into UDG memory; then restart for the next UDG. 5. **Lines 1000–1100** — Save the completed UDG block to tape and optionally repeat. ### Grid Drawing (Lines 200–270) The grid is drawn in INK 5 (cyan) by iterating `A` from 95 to 159 in steps of 8, plotting one horizontal line and one vertical line per iteration. This efficiently draws a 9×9 grid of lines enclosing an 8×8 cell matrix using a single loop with two PLOT/DRAW pairs, rather than separate loops for horizontal and vertical lines. ### User Input Convention The user types a three-character string such as `A1B` or `C4W`. The program decodes it as follows: - `B$(1)` — Row letter (A–H); `CODE B$(1)-63` maps A→2…H→9 for the PRINT AT row, and `CODE B$(1)-64` maps A→1…H→8 for the array index. - `B$(2)` — Column digit (1–8); `CODE B$(2)-37` maps “1”→12…”8″→19 for the PRINT AT column, and `VAL B$(2)` provides the array column index. - `B$(3)` — Either `B` (black, CHR$ 143 = solid block UDG graphic) or `W` (white, CHR$ 128 = blank block graphic). Special single-character inputs `S` (stop) and `N` (next UDG) are checked first before the three-character parsing is attempted. If `B$` is shorter than three characters when neither S nor N was entered, accessing `B$(3)` will cause a BASIC error — a minor robustness issue. ### Pixel State Representation The array `A(8,8)` stores 143 for a set (black) pixel and 128 for a clear (white) pixel, mirroring the CHR$ codes used for display. Zero is the initial value and is also assigned during the clear-down pass in line 880, which is equivalent to white in this context. ### Byte Encoding (Lines 800–920) Each row of eight pixels is converted to a byte using a classic binary-weighting technique. The variable `BINARY` starts at 128 and is halved each iteration, so column 1 is the most-significant bit and column 8 is the least-significant bit. If a cell holds 143 (black), its weight is added to `DECIMAL`. The resulting byte is POKEd to `LOCATION`, which starts at 65367 and is incremented before each POKE, so the first byte lands at 65368 — the standard ZX Spectrum UDG base address (the address of UDG ‘A’). ### UDG Memory Layout | Variable | Value / Role | | --- | --- | | `LOCATION` | Initialized to 65367; incremented to 65368 before first POKE | | SAVE range start | 65368 (first byte of UDG ‘A’) | | SAVE length | 168 bytes (21 UDGs × 8 bytes each) | | `POKE 23658,8` | Caps lock on — forces uppercase INPUT without CAPS SHIFT | ### Notable Techniques - **POKE 23658,8** at line 50 sets the FLAGS2 system variable bit to lock the keyboard into uppercase mode, ensuring the letter inputs (A–H, B, W, S, N) are received correctly without relying on the user’s CAPS LOCK state. - After each UDG is defined, the subroutine at line 800 zeroes out the array (line 880) and the program jumps back to `GO TO 60` (CLS and reinitialize) rather than returning, allowing multiple sequential UDGs to be designed in one session. - The SAVE at line 1040 saves only the raw UDG bitmap data as a CODE block, not the BASIC program itself, so the saved file can be loaded independently to install custom graphics. ### Bugs and Anomalies - The outer `FOR A=1 TO 8` loop at line 420 is redundant — its body only uses `B`, so the PRINT AT header rows and column labels are drawn eight times unnecessarily. The `NEXT A` at line 470 should likely be removed along with the outer loop. - If the user enters `N` or `S`, the program branches before checking `B$(3)`, which is safe. However, any other input shorter than three characters will trigger a “Subscript out of range” error when line 640 evaluates `B$(3)`. - `LOCATION` is only reset to 65367 at line 40 during program initialization, not between UDGs. Each call to the line-800 subroutine advances it by 8, so successive UDGs are correctly placed in sequential 8-byte slots — this is intentional rather than a bug. ## Source Code ``` 10 REM UDG 20 REM by John Bell 30 DIM A(8,8) 40 LET LOCATION=65367 50 POKE 23658,8 60 CLS 70 GO SUB 200 80 GO SUB 400 90 GO SUB 600 100 STOP 200 REM DRAW GRID 210 INK 5 220 FOR A=95 TO 159 STEP 8 230 PLOT 95,A: DRAW 64,0 240 PLOT A,159: DRAW 0,-64 250 NEXT A 260 INK 0 270 RETURN 400 REM PRINT UDG 410 INK 4 420 FOR A=1 TO 8 430 FOR B=1 TO 8 440 PRINT AT 1,B+11;B 450 PRINT AT B+1,11;CHR$ (B+64) 460 NEXT B 470 NEXT A 480 INK 0 490 PRINT AT 15,0;"input row letter" 500 PRINT AT 16,6;"COLUMN NUMBER" 510 PRINT AT 17,6;"B for black orW for white" 520 PRINT AT 18,9;"OR" 530 PRINT AT 19,6;"N to define next UDG" 540 PRINT AT 20,6;"S to stop" 550 RETURN 600 REM INPUT BITS 610 INPUT B$ 620 IF B$="S" THEN GO TO 1000 630 IF B$="N" THEN GO TO 800 640 IF B$(3)="B" THEN LET COLOR=143 650 IF B$(3)="W" THEN LET COLOR=128 660 PRINT AT CODE B$(1)-63,CODE B$(2)-37;CHR$ COLOR 670 LET A(CODE B$(1)-64,VAL B$(2))=COLOR 680 GO TO 600 800 REM POKE DATA 810 FOR A=1 TO 8 820 LET LOCATION=LOCATION+1 830 LET DECIMAL=0 840 LET BINARY=128 850 FOR B=1 TO 8 860 IF A(A,B)=143 THEN LET DECIMAL=DECIMAL+BINARY 870 LET BINARY=BINARY/2 880 LET A(A,B)=0 890 NEXT B 900 POKE LOCATION,DECIMAL 910 NEXT A 920 GO TO 60 1000 REM END PROGRAM 1010 CLS 1020 FLASH 1 1030 PRINT "START RECORDER" 1040 SAVE "UDG"CODE 65368,168 1050 FLASH 0 1060 CLS 1070 INPUT "SAVE AGAIN? Y/N ";C$ 1080 IF C$="Y" THEN GO TO 1030 1090 CLS 1100 PRINT "PROGRAM COMPLETE" ```