ESCHER

Developer(s): Hal Renko, Sam Edwards
Date: 198x
Type: Program
Platform(s): TS 2068
Tags: Demo

ESCHER generates Escher-style tessellating tile patterns on a 16×16 pixel grid defined by the user as rows of ones and zeros. The program stores the design across four UDG characters (pairs of 8×8 tiles covering the full 16×16 area) and uses a 90-degree rotation algorithm—transposing the 16×16 character matrix via `b$(i,j)=a$(j,17-i)`—to compute four orientations of the tile, storing each rotation in a successive group of four UDGs (characters 144–159). The custom function `FN c(l$)` converts an 8-character binary string into a byte value using weighted powers of two, which is then POKEd directly into UDG memory. After generation, the user navigates the screen using the arrow keys (mapped to keys 5–8), repositioning a 2×2 block of UDG characters to tile the display.


Program Structure

The program is organized into five logical phases: user input of a 16×16 binary grid, UDG generation from that grid, 90-degree rotation of the grid repeated across four UDG banks, display of the resulting tiled pattern, and interactive repositioning of the tile block via arrow keys.

  1. Lines 10–80: Setup, title display, and prompt.
  2. Lines 90–130: Input loop collecting 16 rows of 16 binary digits into a$(i).
  3. Lines 140–160: “Please wait” message.
  4. Lines 170–330: Outer loop over four UDG banks (b=144 TO 159 STEP 4), inner loop over 8 pixel rows, POKEing UDG data and then rotating the array.
  5. Lines 340–450: Display and interactive navigation loop.
  6. Lines 460–500: Subroutines for the four movement directions.
  7. Lines 510–530: SAVE and remarks.

Binary String to Byte Conversion: FN c

Line 30 defines the user function FN c(l$), which converts an 8-character string of '0' and '1' characters into a pixel byte by computing:

128*VAL l$(1) + 64*VAL l$(2) + ... + VAL l$(8)

This is a straightforward binary-to-decimal conversion where VAL extracts the numeric value of each character. The result is POKEd into UDG memory at USR CHR$(b)+i for each of the four sub-tiles and each of 8 pixel rows.

UDG Layout and Quadrant Mapping

Each 16×16 pixel design is split into four 8×8 UDG quadrants stored in consecutive characters starting at code 144 (UDG A). The POKE statements map the quadrants as follows:

UDG offsetPixel columnsPixel rows
b (e.g. 144)1–81–8 (top-left)
b+19–161–8 (top-right)
b+21–89–16 (bottom-left)
b+39–169–16 (bottom-right)

The inner loop runs i=0 TO 7, addressing rows of each quadrant. For the bottom half, the row index shifts by 9: a$(i+9, TO 8) and a$(i+9, 9 TO). This correctly tiles all four quadrants across the 16-row array.

Rotation Algorithm

Lines 260–320 perform a 90-degree clockwise rotation of the 16×16 character grid. The transformation used is:

b$(i,j) = a$(j, 17-i)

This transposes along the anti-diagonal, which implements a 90-degree clockwise rotation. After computing b$, the contents are copied back into a$ (lines 310–320), so each pass of the outer b loop operates on the already-rotated version. This produces four successive 90-degree rotations stored in UDG banks at characters 144, 148, 152, and 156.

Navigation and Subroutine Dispatch

Lines 390–450 implement a keypress-driven navigation loop. Keys 5–8 are the standard Spectrum arrow keys. The variable rj is computed as a weighted sum:

rj = 10*(t$="5") + 20*(t$="6") + 30*(t$="7") + 40*(t$="8")

A GO SUB to 460+rj then dispatches to one of four subroutines at lines 470, 480, 490, or 500. Each subroutine sets ch to the appropriate UDG base character for that rotation and adjusts either ro (row offset) or co (column offset) within screen bounds using conditional arithmetic to clamp the position.

Display Mechanics

Lines 370–380 print the initial four UDG characters as a 2×2 block using the [UDG-A] through [UDG-D] placeholders (UDG characters \a–\d in source form), representing the first rotation. Lines 430–440 reprint using CHR$ ch through CHR$ (ch+3), selecting the appropriate rotated set. The display thus always shows a single 2×2 tile that the user can move around the screen.

Notable Techniques and Idioms

  • Clamped movement without IF statements: co=co-2*(co>2) subtracts 2 only when the condition is true (evaluating to 1), keeping the tile within bounds.
  • Computed GO SUB dispatch via arithmetic on line numbers avoids a sequence of IF statements for key handling.
  • The DIM a$(16,16) and DIM b$(16,16) declarations (line 20) allocate 2D string arrays, each element being a single character, used here as a 16×16 character matrix.
  • The INPUT prompt at line 110 uses the TAB 7 argument and immediately echoes the entered row at line 120.

Bugs and Anomalies

  • Line 140 uses PRINT AT n,0, with a trailing comma inside the FOR loop — this is unusual syntax but likely intended to clear lines 0–4 before printing the “Please wait” message.
  • The initial display at lines 370–380 hard-codes ch=144 implicitly (via the UDG literal characters) but the variable ch is not set before the first PRINT. If the user presses no key and the GO TO 370 loop re-enters without a valid rj, lines 430–440 would use an uninitialized ch. However, since line 410 sends control back to 370 (skipping 430–440) when rj=0, the uninitialized ch is never used until after a valid keypress.
  • The outer loop variable b runs from 144 to 159 step 4, giving four iterations. The fourth rotation (after three 90-degree turns) returns the tile to its original orientation — stored redundantly in the last UDG bank but never actually displayed differently from the first.

Content

Appears On

Related Products

Related Articles

Image Gallery

Source Code

 10 BORDER 6:PAPER 6:CLS :INK 1
 20 DIM a$(16,16):DIM b$(16,16)
 30 DEF FN c(l$)=128* VAL l$(1)+64* VAL l$(2)+32* VAL l$(3)+16* VAL l$(4)+8* VAL l$(5)+4* VAL l$(6)+2* VAL l$(7)+ VAL l$(8)
 40 LET cb=144
 50 REM  INPUT character
 60 PRINT "       ESCHER PATTERNS"
 70 PRINT '"      Please enter 16 rows      of 16 ones ('1'=ink) or zeroes  ('0'=no ink)."
 80 BEEP .3,6
 90 PRINT AT 21,8;"----------------"
\n100 FOR i=1 TO 16
\n110 INPUT ("row ";i); TAB 7;a$(i)
\n120 PRINT TAB 8;a$(i)
\n130 NEXT i
\n140 FOR n=0 TO 4:PRINT AT n,0,,:NEXT n:PRINT AT 1,1;"Please wait....  I'm computing!"
\n150 PRINT AT 4,1;"When I'm ready, use arrow keys"
\n160 BEEP .3,1
\n170 REM generate characters
\n180 FOR b=144 TO 159 STEP 4
\n190 FOR i=0 TO 7
\n200 POKE USR CHR$ (b)+i, FN c(a$(i+1, TO 8))
\n210 POKE USR CHR$ (b+1)+i, FN c(a$(i+1,9 TO ))
\n220 POKE USR CHR$ (b+2)+i, FN c(a$(i+9, TO 8))
\n230 POKE USR CHR$ (b+3)+i, FN c(a$(i+9,9 TO ))
\n240 NEXT i
\n250 REM rotate characters
\n260 FOR i=1 TO 16
\n270 FOR j=1 TO 16
\n280 LET b$(i,j)=a$(j,17-i)
\n290 NEXT j:NEXT i
\n300 REM  COPY in original array
\n310 FOR i=1 TO 16
\n320 LET a$(i)=b$(i):NEXT i
\n330 NEXT b
\n340 REM  PRINT characters
\n350 BEEP .5,2:CLS 
\n360 LET ro=10:LET co=15
\n370 PRINT AT ro,co;"[UDG-A][UDG-B]"
\n380 PRINT AT ro+1,co;"[UDG-C][UDG-D]"
\n390 LET t$= INKEY$
\n400 LET rj=10*(t$="5")+20*(t$="6")+30*(t$="7")+40*(t$="8")
\n410 IF rj=0 THEN GO TO 370
\n420 GO SUB 460+rj
\n430 PRINT AT ro,co; CHR$ ch; CHR$ (ch+1)
\n440 PRINT AT ro+1,co; CHR$ (ch+2); CHR$ (ch+3)
\n450 GO TO 370
\n460 REM 
\n470 LET ch=148:LET co=co-2*(co>2):RETURN 
\n480 LET ch=152:LET ro=ro+2*(ro<19):RETURN 
\n490 LET ch=144:LET ro=ro-2*(ro>2):RETURN 
\n500 LET ch=156:LET co=co+2*(co<29):RETURN 
\n510 SAVE "ESCHER" LINE 10
\n520 REM Escher(Escher patterns)
\n530 REM from "Tantalizing Games   for the TS 2000 Series"   by H.Renko & S.Edwards (modified)

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

Scroll to Top