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:
- Lines 200–270 — Draw the 8×8 pixel grid using PLOT/DRAW.
- Lines 400–550 — Print column numbers, row letters, and on-screen instructions.
- Lines 600–680 — Accept user input and update the grid display and backing array.
- Lines 800–920 — Convert the array to bytes and POKE them into UDG memory; then restart for the next UDG.
- 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)-63maps A→2…H→9 for the PRINT AT row, andCODE B$(1)-64maps A→1…H→8 for the array index.B$(2)— Column digit (1–8);CODE B$(2)-37maps “1”→12…”8″→19 for the PRINT AT column, andVAL B$(2)provides the array column index.B$(3)— EitherB(black, CHR$ 143 = solid block UDG graphic) orW(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 8loop at line 420 is redundant — its body only usesB, so the PRINT AT header rows and column labels are drawn eight times unnecessarily. TheNEXT Aat line 470 should likely be removed along with the outer loop. - If the user enters
NorS, the program branches before checkingB$(3), which is safe. However, any other input shorter than three characters will trigger a “Subscript out of range” error when line 640 evaluatesB$(3). LOCATIONis 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.
Content
Image Gallery
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"
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.