GrafProgs

Date: 198x
Type: Program
Platform(s): TS 2068
Tags: Demo, Graphics

GrafProgs is a multi-part demonstration and utility suite for displaying enlarged, pixel-scaled text on screen using a machine code routine loaded at address 32256. The BASIC programs (numbered 1 through 12) showcase various text-scaling effects, including ripple layouts, stacked titles, UDG-based lettered graphics, word-wrapped paragraphs, and full-screen character fills. Program 1 uses direct PEEK of the ROM character set at address 15360 to manually render scaled characters via PLOT and DRAW, while Programs 2–12 delegate rendering to the machine code routine via USR 32256, passing parameters through memory addresses 23306 onward. UDGs are loaded by PEEKing ROM glyph data at fixed addresses such as 16024 and 15896, and the word-wrap routine in Program 12 walks a string backward to find space characters for clean line breaks. Several REMs at the top document a relocation utility for moving the machine code block to a user-specified address, with address fixup DATA for five internal pointer patches.


Program Structure

GrafProgs is organized as a sequence of twelve self-contained demonstration programs sharing a common subroutine framework. Each numbered program (100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 2000, 2050) runs independently and chains to the next via a direct RUN call to the following program’s entry point after a “repeat?” prompt. Lines 10–70 are pure REM commentary containing a relocation utility for the companion machine code file.

Machine Code Interface

The core rendering engine is a 300-byte machine code block loaded at address 32256 (decimal) via LOAD "GrafProgsC" CODE. BASIC communicates with it by POKEing a parameter block starting at address 23306:

OffsetVariablePurpose
+0xxX pixel position (computed to center the string)
+1yyY pixel position
+2xsHorizontal scale factor
+3ysVertical scale factor
+48Fixed value (glyph height in pixels)
+5…character codesString data, terminated by 255

The routine is invoked with LET w= USR 32256, discarding the return value. Addresses 23606–23607 are also manipulated (e.g., POKE 23606,216: POKE 23607,250) in Programs 4 and 10 to control an animation loop offset, stepped through a FOR f loop before calling the routine repeatedly.

Subroutine at Line 260

The central rendering subroutine at line 260 computes horizontal centering with LET xx=(256-8*xs*LEN p$)/2, builds the parameter block via sequential POKEs, appends the string character codes, writes the 255 terminator, and calls USR 32256. It is called by every demonstration program.

Program 1 — Pure BASIC Pixel Renderer

Program 1 (lines 110–190) is the only demo that does not use the machine code routine. It reads character bitmaps directly from ROM using PEEK (15360 + CODE a$(n)*8 + f), then extracts individual bits with successive INT(p/2) divisions, using PLOT and DRAW to render scaled pixels. The inner FOR i=i TO t construct is a deliberate trick: when a bit is set, the loop variable i is advanced to t, effectively skipping the remaining bit-extraction steps for that pixel block. Line 160 handles screen wrapping when the rendered string overflows 255 pixels wide.

UDG Loading from ROM Glyphs

Programs 3, 4, and 10 load UDGs by PEEKing ROM addresses that contain pre-drawn glyph data. Program 4’s DATA at line 430 maps UDG letters to ROM addresses such as 16024, 15896, 16016, 15992, and 15968. Program 10 extends this to eleven UDGs (a–l), using a variable d$ still in scope from the DATA read — a potential hazard if execution order is disrupted, since d$ holds the address string for UDG “i” and is reused for UDG “j” (PEEK (VAL d$+n)).

Word-Wrap Routine (Program 12)

Lines 2060–2160 implement a word-wrapping paragraph renderer for a Shakespeare quotation. The algorithm scans each 32-character slice backward for a space (lines 2100–2110), then feeds the trimmed substring to the machine code renderer, advancing yy by y = ys*8 after each line. Edge cases for short trailing strings are handled at line 2080.

Notable Techniques and Idioms

  • SGN PI is used throughout as a compact literal for 1 (since PI > 0), and NOT PI evaluates to 0 — both save bytes over typing the digits directly.
  • INT PI (≈ 3) is used at line 1020 to set ys, again saving a byte.
  • VAL b$ in line 1010 converts DATA strings to numbers, a common memory-saving technique.
  • Line 290 (POKE 23606,0: POKE 23607,60) resets the system variable pair, likely restoring an attribute or display pointer modified by the animation sequence.
  • The relocation utility in REMs 10–60 provides a five-entry DATA table of byte offsets and patches self-referential address bytes within the machine code block using two helper functions FN a(x) and FN b(x) for high and low bytes respectively.

Bugs and Anomalies

  • Line 170 contains PRINT AT 21,31' — the trailing apostrophe issues an extra newline after the AT position, which may produce an unintended blank line during the overflow scroll in Program 1. The REM on line 70 acknowledges a fix is needed.
  • The double colon :: at line 730 (LET yy=f*5::) is a harmless empty statement artifact.
  • In line 1010, the DATA entry "i",d$ uses the BASIC variable d$ rather than a string literal; this relies on d$ retaining the value "15968" from the immediately preceding READ iteration, which is correct but fragile.
  • Line 2130 advances a$ with a$(n+1 TO) but does not guard against n exceeding LEN a$ when the last segment ends exactly at the string boundary, potentially causing a subscript error on edge-case input.

Save/Load Structure

Line 2180 saves both the BASIC program (SAVE "GrafProgs" LINE 2140) and the machine code block (SAVE "GrafProgsC" CODE 32256,300). Line 2170 provides a bootstrap loader that clears RAM to 32255, loads the code file, and runs the program.

Image Gallery

Source Code

   10 REM         Screen Utilities  ZX COMPUTING  April/May 85   Utility;  use to relocate starting address of m/c in memory,  original=32256,300
   20 REM INPUT "Start address? ";s:CLEAR s-1:PRINT "Load code":LOAD "p2"CODE 
   30 REM DEF FN a(x)=INT ((s+x)/256)
   40 REM DEF FN b(x)=s+x-256*FN a(x)
   50 REM RESTORE :FOR i=1 TO 5:READ x,x:POKE s+x,FN b(x):POKE s+1+x,FN a(x):NEXT i
   60 REM DATA 106,32,127,164,86,3,154,48,251,156
   70 REM this utility needs fix-ing
  100 REM    Program 1
  110 PAPER 7:BORDER 7:INK 9:CLS 
  120 LET w=4:LET t=4:LET a=7*w:LET d=168:LET a$="ABCDE":REM  w max=8, min=32/48; d max=21,  min=1; try d=0
  130 FOR n=1 TO LEN a$:FOR f=0 TO 7:LET p= PEEK (15360+ CODE a$(n)*8+f)
  140 FOR i=0 TO 7:IF p-2* INT (p/2) THEN FOR i=i TO t:PLOT a-i*w,d-f*t-1:DRAW 1-w,0:NEXT i:REM fool around with i & 1 here
  150 LET p= INT (p/2):NEXT i:NEXT f:LET a=a+w*8
  160 IF a>255 AND d-t*8>t*8-1 THEN LET d=d-t*8:LET a=w*8-1
  170 IF a>255 AND d-t*8<t*8 THEN PRINT AT 21,31':FOR f=1 TO t:PRINT :NEXT f:LET a=w*8-1
  180 NEXT n:PRINT AT 21,4;a$:GO SUB 380
  190 IF z$="y" OR z$="Y" THEN RUN 100
  200 REM    Program 2
  210 INPUT "times wider? ";xs'"times taller? ";ys
  220 LET yy=0
  230 LET p$="H"
  240 GO SUB 260:GO SUB 380:IF z$="y" OR z$="Y" THEN RUN 200
  250 RUN 300
  260 LET xx=(256-8*xs* LEN p$)/2
  270 LET i=23306:POKE i,xx:POKE i+1,yy:POKE i+2,xs:POKE i+3,ys:POKE i+4,8:LET i=i+4:LET w= LEN p$:FOR n= SGN PI TO w:POKE i+n, CODE p$(n):NEXT n:POKE i+w+1,255:LET w= USR 32256:RETURN :REM pause any amount; cls screen after each TITLE if you wish
  290 POKE 23606,0:POKE 23607,60:RETURN 
  300 REM    Program 3
  320 FOR f= USR "a" TO USR "u" STEP 8:POKE f,255:POKE f+7,255:NEXT f
  330 LET p$="[UDG-A][UDG-B][UDG-C][UDG-D][UDG-E]":POKE 23606,216:POKE 23607,250
  340 LET yy=50:LET xs=2:LET ys=2:GO SUB 260:GO SUB 290
  350 GO SUB 380:IF z$="y" OR z$="Y" THEN RUN 300
  360 RUN 400
  370 FOR n= SGN PI TO 176:PRINT "PPPP";:NEXT n:RETURN 
  380 INPUT "Repeat This Example (Y/N) ";z$:IF z$="N" OR z$="n" OR z$="y" OR z$="Y" THEN CLS :RETURN 
  390 GO TO 380
  400 REM    Program 4
  420 RESTORE 420:FOR f= SGN PI TO 5:READ a$,a:FOR n= NOT PI TO 7:POKE USR a$+n, PEEK (a+n):POKE USR a$+n+8, NOT PI:NEXT n:NEXT f
  430 DATA "a",16024,"c",15896,"e",16016,"g",15992,"i",15968
  440 LET xs=2:LET ys=3:LET yy=50:LET p$="[UDG-A][UDG-C][UDG-E][UDG-G][UDG-I][UDG-I]":FOR F=0 TO 8:POKE 23606,216+F:POKE 23607,250:REM     P$ in GRAPHICS mode
  450 GO SUB 260:NEXT f:GO SUB 290
  460 GO SUB 380:IF z$="y" OR z$="Y" THEN RUN 400
  500 REM    Program 5
  510 RESTORE 500:LET yy=50
  520 FOR f=2 TO 8 STEP 2
  530 LET xs=f:LET ys=f:READ p$,i:INK i:LET yy=yy-8:GO SUB 260:NEXT f
  540 DATA "THIS",2," IS ",4,"YOUR",6,"LIFE",1
  550 GO SUB 380:IF z$="y" OR z$="Y" THEN RUN 500
  600 REM    Program 6
  620 LET p$="TITLE":FOR f=6 TO SGN PI STEP - SGN PI
  630 INK f- SGN PI:LET yy=100-f*10:LET xs=f:LET ys=f:GO SUB 260:NEXT f
  635 PAUSE 300:CLS :FOR f=6 TO SGN PI STEP - SGN PI:INK f- SGN PI:LET yy=100-f*4:LET xs=f:LET ys=f:GO SUB 260:NEXT f
  640 GO SUB 380:IF z$="y" OR z$="Y" THEN RUN 600
  700 REM     Program 7
  720 LET p$="RIPPLE":FOR f=5 TO 1 STEP -1
  730 INK f-1:LET yy=f*5::LET xs=f:LET ys=f:GO SUB 260:NEXT f
  740 GO SUB 380:IF z$="y" OR z$="Y" THEN RUN 700
  800 REM    Program 8
  830 GO SUB 370:LET yy= NOT PI:LET xs=32:LET ys=22:LET p$=" ":GO SUB 260
  840 GO SUB 380:IF z$="y" OR z$="Y" THEN RUN 800
  900 REM    Program 9
  930 GO SUB 370:LET yy= NOT PI:LET xs= SGN PI:LET ys=22:LET p$="                                ":GO SUB 260
  940 GO SUB 380:IF z$="y" OR z$="Y" THEN RUN 900
 1000 REM    Program 10
 1010 RESTORE 1000:FOR f= SGN PI TO 6:READ a$,b$,c$,d$:FOR n= NOT PI TO 7:POKE USR a$+n, PEEK (VAL b$+n):POKE USR c$+n, PEEK (VAL d$+n):NEXT n:NEXT f:DATA "a","16024","b","16032","c","15896","d","15944","e","16016","f","16032","g","15992","h","15968","i",d$,"j","15912","k",b$,"l","16204"
 1020 LET xs=2:LET ys= INT PI:LET yy=50:LET p$="[UDG-A][UDG-C][UDG-E][UDG-G][UDG-I][UDG-K]":FOR f= NOT PI TO 8:POKE 23606,216+f:POKE 23607,250:REM p$ is graphics mode
 1030 GO SUB 260:NEXT f:GO SUB 290
 1040 GO SUB 380:IF z$="y" OR z$="Y" THEN RUN 1000
 2000 REM     Program 11
 2030 GO SUB 370:LET yy=48:LET xs=16:LET ys=8:LET p$=" ":GO SUB 260
 2040 GO SUB 380:IF z$="y" OR z$="Y" THEN RUN 2000
 2050 REM    Program 12
 2060 LET a$="The quality of mercy is not strained. It droppeth as gentle rain from the heaven up on the place beneath. It is twice blest. It blesseth him that gives, and him that takes."
 2070 CLS :LET ys=1:LET yy=0:LET y=ys*8:LET xs=1
 2080 IF LEN a$<33 THEN LET n= LEN a$+1:LET b$=a$:GO TO 2120
 2090 LET b$=a$( TO 32):IF a$(33)=" " THEN LET n=33:GO TO 2120
 2100 FOR n=32 TO 1 STEP -1:IF b$(n)=" " THEN GO TO 2120
 2110 NEXT n
 2120 LET p$=b$( TO n-1):GO SUB 260:IF a$="" THEN GO TO 2140
 2130 LET yy=yy+y:LET a$=a$(n+1 TO ):GO TO 2080
 2140 INPUT "You have seen all my examples.  Do you wish to go through this  program again? (Y/N) ";z$
 2150 IF z$="y" OR z$="Y" THEN RUN 
 2160 STOP 
 2170 CLEAR 32255:LOAD "GrafProgsC"CODE :RUN 
 2180 SAVE "GrafProgs" LINE 2140:SAVE "GrafProgsC"CODE 32256,300

Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.