--- title: "ESCHER" id: 71031 type: "computer_media" slug: "escher" url: "http://localhost/computer_media/escher/" markdown_url: "http://localhost/computer_media/escher.md" published_at: "2026-08-26T01:47:57+00:00" modified_at: "2026-08-26T01:50:05+00:00" author: "David Anderson" featured_image: url: "http://localhost/wp-content/uploads/2022/04/20230809-041332.jpg" excerpt: "Design a 16×16 pixel tile and watch it automatically rotate through four orientations to create seamless Escher-style tessellating patterns, navigable with arrow keys." 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/" indiv: - name: "Hal Renko" slug: "hal-renko" taxonomy: "indiv" url: "http://localhost/indiv/hal-renko/" - name: "Sam Edwards" slug: "sam-edwards" taxonomy: "indiv" url: "http://localhost/indiv/sam-edwards/" genre: - name: "Demo" slug: "demo" taxonomy: "genre" url: "http://localhost/type/demo/" media_type: "Program" programmers: - name: "Hal Renko" slug: "hal-renko" taxonomy: "indiv" url: "http://localhost/indiv/hal-renko/" - name: "Sam Edwards" slug: "sam-edwards" taxonomy: "indiv" url: "http://localhost/indiv/sam-edwards/" download_url: "https://archive.org/download/timex-sinclair-software-archive/ESCHER%20%28198x%29%28Renko%2C%20Hal%3B%20Edwards%2C%20Sam%29%28TS2068%29%28US%29%28Program%29.zip" mediadate: "198x" related_content: - id: 16171 title: "Tantalizing Games for the Timex/Sinclair 2000 Series" type: "article" url: "http://localhost/article/tantalizing-games-for-the-timex-sinclair-2000-series/" media_type_tags: "Demo" --- # ESCHER 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 offset | Pixel columns | Pixel rows | | --- | --- | --- | | `b` (e.g. 144) | 1–8 | 1–8 (top-left) | | `b+1` | 9–16 | 1–8 (top-right) | | `b+2` | 1–8 | 9–16 (bottom-left) | | `b+3` | 9–16 | 9–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. ## 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;"----------------" 100 FOR i=1 TO 16 110 INPUT ("row ";i); TAB 7;a$(i) 120 PRINT TAB 8;a$(i) 130 NEXT i 140 FOR n=0 TO 4:PRINT AT n,0,,:NEXT n:PRINT AT 1,1;"Please wait.... I'm computing!" 150 PRINT AT 4,1;"When I'm ready, use arrow keys" 160 BEEP .3,1 170 REM generate characters 180 FOR b=144 TO 159 STEP 4 190 FOR i=0 TO 7 200 POKE USR CHR$ (b)+i, FN c(a$(i+1, TO 8)) 210 POKE USR CHR$ (b+1)+i, FN c(a$(i+1,9 TO )) 220 POKE USR CHR$ (b+2)+i, FN c(a$(i+9, TO 8)) 230 POKE USR CHR$ (b+3)+i, FN c(a$(i+9,9 TO )) 240 NEXT i 250 REM rotate characters 260 FOR i=1 TO 16 270 FOR j=1 TO 16 280 LET b$(i,j)=a$(j,17-i) 290 NEXT j:NEXT i 300 REM COPY in original array 310 FOR i=1 TO 16 320 LET a$(i)=b$(i):NEXT i 330 NEXT b 340 REM PRINT characters 350 BEEP .5,2:CLS 360 LET ro=10:LET co=15 370 PRINT AT ro,co;"[UDG-A][UDG-B]" 380 PRINT AT ro+1,co;"[UDG-C][UDG-D]" 390 LET t$= INKEY$ 400 LET rj=10*(t$="5")+20*(t$="6")+30*(t$="7")+40*(t$="8") 410 IF rj=0 THEN GO TO 370 420 GO SUB 460+rj 430 PRINT AT ro,co; CHR$ ch; CHR$ (ch+1) 440 PRINT AT ro+1,co; CHR$ (ch+2); CHR$ (ch+3) 450 GO TO 370 460 REM 470 LET ch=148:LET co=co-2*(co>2):RETURN 480 LET ch=152:LET ro=ro+2*(ro<19):RETURN 490 LET ch=144:LET ro=ro-2*(ro>2):RETURN 500 LET ch=156:LET co=co+2*(co<29):RETURN 510 SAVE "ESCHER" LINE 10 520 REM Escher(Escher patterns) 530 REM from "Tantalizing Games for the TS 2000 Series" by H.Renko & S.Edwards (modified) ```