--- title: "Spinner" id: 57513 type: "computer_media" slug: "spinner" url: "http://localhost/computer_media/spinner/" markdown_url: "http://localhost/computer_media/spinner.md" published_at: "2024-10-05T21:18:17+00:00" modified_at: "2026-04-03T07:58:25+00:00" author: "David Anderson" featured_image: url: "http://localhost/wp-content/uploads/2024/10/268_Spin.png" excerpt: "A 4×4 inverse-video tile puzzle, where 16 keyboard keys each rotate a different group of four tiles around the grid — can you solve it?" 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 1000" slug: "ts1000" taxonomy: "post_tag" url: "http://localhost/tag/ts1000/" model: - name: "Timex/Sinclair 1000" slug: "ts-1000" taxonomy: "model" url: "http://localhost/model/ts-1000/" genre: - name: "Game" slug: "game" taxonomy: "genre" url: "http://localhost/type/game/" media_contents: - id: 56737 title: "Timex Sinclair Public Domain Library Tape 1006" type: "computer_media" url: "http://localhost/computer_media/timex-sinclair-public-domain-library-tape-1006/" media_type: "Program" mediadate: "198x" images: - url: "http://localhost/wp-content/uploads/2024/10/268_Spin.png" media_type_tags: "Game" --- # Spinner This program implements a 4×4 sliding-tile puzzle called “SPINNER T.M.” where sixteen tiles are arranged in a grid and the player rotates groups of four tiles in a cycle using keyboard keys. The array A$(16,1) stores the current symbol in each of the 16 tile positions, with each cell holding one of four inverse-video characters (X, colon, O, or space) indicating the tile type. The 16 move subroutines (lines 1000–1269) each perform a four-way cyclic rotation among four specific array indices, using a temporary variable B$ for the swap. The display loop redraws the board frame using inverse-video border characters and then plots the current tile symbols into the grid at lines 500–550; PAUSE 60000 inside the loop effectively halts until a keypress is detected in the next iteration. The program auto-saves itself with an auto-run flag via the SAVE command at line 1280. *** ## Program Analysis ### Program Structure The program is divided into four functional regions: 1. **Initialisation (lines 5–60):** Populates a 16-element string array with the starting tile symbols. 2. **Main game loop (lines 70–570):** Scans 16 keys, redraws the board frame and tile grid, then pauses before repeating. 3. **Move subroutines (lines 1000–1269):** One subroutine per key, each performing a four-element cyclic rotation in the tile array. 4. **Save/restart (lines 1270–1290):** Clears memory, saves the program with an auto-run flag, and restarts. ### Data Representation The board is modelled as `A$(16,1)`, a 16-row string array where each row holds exactly one character. The four tile types are initialised in lines 10–60: | Array indices | Character | Meaning | | --- | --- | --- | | 1–4 | %X (inverse X) | Top row tiles | | 5–8 | %: (inverse colon/block) | Second row tiles | | 9–12 | %O (inverse O) | Third row tiles | | 13–16 | % (inverse space) | Bottom row tiles | The solved state therefore has all tiles of the same type in their respective row, providing a clear visual goal. ### Board Layout and Display The frame is drawn unconditionally on every loop iteration (lines 400–430) using inverse-video border characters including `%=`, `%S`, `%P` etc. to form decorative text and box-drawing lines. The tile symbols are then overlaid in lines 500–550 using `PRINT AT 6,(9+2*A)` and similar expressions, placing tiles in every other column to give visual spacing. The `PAUSE 60000` at line 560 acts as a long delay that is effectively interrupted by the INKEY$ polling on the next loop iteration — since PAUSE does not exit early on keypress in this context, the game relies on the outer `FOR T=1 TO 60000` loop cycling rapidly through the INKEY$ checks before reaching the PAUSE. ### Move Subroutines and the Rotation Mechanic Each of the 16 keys (1–4, Q–R, A–F, Z–V) calls a dedicated subroutine that rotates four specific array indices in a cycle. The canonical pattern uses a temporary string `B$`: 1. Save the first element into `B$`. 2. Copy element 2 → position 1, element 3 → position 2, element 4 → position 3. 3. Restore `B$` into position 4. This is a standard four-element left-rotation. The indices chosen for each subroutine define which “spinner” on the board is activated, linking adjacent or diagonal cells in a pattern consistent with the game’s name. ### Key-to-Subroutine Mapping | Key | Subroutine | Indices rotated | | --- | --- | --- | | 1 | 1000 | 2→13→4→5→2 | | 2 | 1120 | 3→14→1→6→3 | | 3 | 1130 | 4→15→2→7→4 | | 4 | 1140 | 1→16→3→8→1 | | Q | 1150 | 6→1→8→9→6 | | W | 1160 | 7→2→5→10→7 | | E | 1170 | 8→3→6→11→8 | | R | 1180 | 5→4→7→12→5 | | A | 1190 | 10→5→12→13→10 | | S | 1200 | 11→6→9→14→11 | | D | 1210 | 12→7→10→15→12 | | F | 1220 | 9→8→11→16→9 | | Z | 1230 | 14→9→16→1→14 | | X | 1240 | 15→10→13→2→15 | | C | 1250 | 16→11→10→3→16 | | V | 1260 | 13→12→15→4→13 | ### Notable Techniques - **Single-character string array:**`DIM A$(16,1)` exploits the ZX81’s fixed-length string array rows to store exactly one character per tile, making indexing straightforward with `A$(N)` notation. - **Inline INKEY$ polling:** Each key check is a separate `IF INKEY$="x" THEN GOSUB` line rather than using a variable, avoiding the overhead of storing the key value but requiring 16 consecutive INKEY$ reads per frame, which can miss fast keypresses. - **Symmetric keyboard layout:** The keys 1–4 occupy a physical row, Q–R the next, A–F the next, and Z–V the bottom — matching the four columns and four rows of the spinner grid in a spatially intuitive way. - **Auto-run save:** Line 1280 uses an inverse-video digit in the filename to set the auto-run flag, so the program restarts automatically when loaded. ### Anomalies and Notes - Line 1250 rotates indices 16→11→10→3→16. The middle step goes 11→10 rather than 11→14 as would be expected for a clean 2×2 spinner — this may be an intentional asymmetric move or a transcription error in the original. - The subroutine numbering is inconsistent: the first subroutine is at 1000 while subsequent ones skip to 1120, leaving a gap of 120 lines that could have accommodated the earlier subroutine’s lines more naturally at 1100. - `PAUSE 60000` inside the main loop means the screen freezes for a very long time if no key is pressed during the INKEY$ checks at the top of each iteration; the effective frame rate depends entirely on how quickly lines 75–260 are scanned. ## Source Code ``` 5 DIM A$(16,1) 10 FOR B=1 TO 4 20 LET A$(B)="%X" 30 LET A$(4+B)="%:" 40 LET A$(8+B)="%O" 50 LET A$(12+B)="% " 60 NEXT B 70 FOR T=1 TO 60000 75 IF INKEY$="1" THEN GOSUB 1000 120 IF INKEY$="2" THEN GOSUB 1120 130 IF INKEY$="3" THEN GOSUB 1130 140 IF INKEY$="4" THEN GOSUB 1140 150 IF INKEY$="Q" THEN GOSUB 1150 160 IF INKEY$="W" THEN GOSUB 1160 170 IF INKEY$="E" THEN GOSUB 1170 180 IF INKEY$="R" THEN GOSUB 1180 190 IF INKEY$="A" THEN GOSUB 1190 200 IF INKEY$="S" THEN GOSUB 1200 210 IF INKEY$="D" THEN GOSUB 1210 220 IF INKEY$="F" THEN GOSUB 1220 230 IF INKEY$="Z" THEN GOSUB 1230 240 IF INKEY$="X" THEN GOSUB 1240 250 IF INKEY$="C" THEN GOSUB 1250 260 IF INKEY$="V" THEN GOSUB 1260 400 PRINT AT 2,7;"%=%=%=%=%=%=%=%=%=%=%=%=%=%=%=%=" 405 PRINT AT 3,7;"% %S%P%I%N%N%E%R % T.M." 410 PRINT AT 4,7;"% %=%=%=%=%=%=%=%=%=%=%= % " 411 PRINT AT 5,7;"% % % % " 412 PRINT AT 6,7;"% %X% % %X% " 413 PRINT AT 7,7;"% % % % " 414 PRINT AT 8,7;"% %:% % %:% " 415 PRINT AT 9,7;"% % % % " 416 PRINT AT 10,7;"% %O% % %O% " 417 PRINT AT 11,7;"% % % % " 418 PRINT AT 12,7;"%=%=%= %=%=%=" 419 PRINT AT 13,7;"% % % % " 423 PRINT AT 14,7;"% % %*%*%*%*%*%*%*%*%*% % " 425 PRINT AT 15,7;"% % " 430 PRINT AT 16,7;"%=%=%=%=%=%=%=%=%=%=%=%=%=%=%=" 500 FOR A=1 TO 4 510 PRINT AT 6,(9+2*A);A$(A);" " 520 PRINT AT 8,(9+2*A);A$(4+A);" " 530 PRINT AT 10,(9+2*A);A$(8+A);" " 540 PRINT AT 12,(9+2*A);A$(12+A);" " 550 NEXT A 560 PAUSE 60000 570 NEXT T 1000 LET B$=A$(2) 1010 LET A$(2)=A$(13) 1020 LET A$(13)=A$(4) 1030 LET A$(4)=A$(5) 1040 LET A$(5)=B$ 1050 RETURN 1120 LET B$=A$(3) 1122 LET A$(3)=A$(14) 1124 LET A$(14)=A$(1) 1126 LET A$(1)=A$(6) 1128 LET A$(6)=B$ 1129 RETURN 1130 LET B$=A$(4) 1132 LET A$(4)=A$(15) 1134 LET A$(15)=A$(2) 1136 LET A$(2)=A$(7) 1138 LET A$(7)=B$ 1139 RETURN 1140 LET B$=A$(1) 1142 LET A$(1)=A$(16) 1144 LET A$(16)=A$(3) 1146 LET A$(3)=A$(8) 1148 LET A$(8)=B$ 1149 RETURN 1150 LET B$=A$(6) 1152 LET A$(6)=A$(1) 1154 LET A$(1)=A$(8) 1156 LET A$(8)=A$(9) 1158 LET A$(9)=B$ 1159 RETURN 1160 LET B$=A$(7) 1162 LET A$(7)=A$(2) 1164 LET A$(2)=A$(5) 1166 LET A$(5)=A$(10) 1168 LET A$(10)=B$ 1169 RETURN 1170 LET B$=A$(8) 1172 LET A$(8)=A$(3) 1174 LET A$(3)=A$(6) 1176 LET A$(6)=A$(11) 1178 LET A$(11)=B$ 1179 RETURN 1180 LET B$=A$(5) 1182 LET A$(5)=A$(4) 1184 LET A$(4)=A$(7) 1186 LET A$(7)=A$(12) 1188 LET A$(12)=B$ 1189 RETURN 1190 LET B$=A$(10) 1192 LET A$(10)=A$(5) 1194 LET A$(5)=A$(12) 1196 LET A$(12)=A$(13) 1198 LET A$(13)=B$ 1199 RETURN 1200 LET B$=A$(11) 1202 LET A$(11)=A$(6) 1204 LET A$(6)=A$(9) 1206 LET A$(9)=A$(14) 1208 LET A$(14)=B$ 1209 RETURN 1210 LET B$=A$(12) 1212 LET A$(12)=A$(7) 1214 LET A$(7)=A$(10) 1216 LET A$(10)=A$(15) 1218 LET A$(15)=B$ 1219 RETURN 1220 LET B$=A$(9) 1222 LET A$(9)=A$(8) 1224 LET A$(8)=A$(11) 1226 LET A$(11)=A$(16) 1228 LET A$(16)=B$ 1229 RETURN 1230 LET B$=A$(14) 1232 LET A$(14)=A$(9) 1234 LET A$(9)=A$(16) 1236 LET A$(16)=A$(1) 1238 LET A$(1)=B$ 1239 RETURN 1240 LET B$=A$(15) 1242 LET A$(15)=A$(10) 1244 LET A$(10)=A$(13) 1246 LET A$(13)=A$(2) 1248 LET A$(2)=B$ 1249 RETURN 1250 LET B$=A$(16) 1252 LET A$(16)=A$(11) 1254 LET A$(11)=A$(10) 1256 LET A$(10)=A$(3) 1258 LET A$(3)=B$ 1259 RETURN 1260 LET B$=A$(13) 1262 LET A$(13)=A$(12) 1264 LET A$(12)=A$(15) 1266 LET A$(15)=A$(4) 1268 LET A$(4)=B$ 1269 RETURN 1270 CLEAR 1280 SAVE "1026%8" 1290 RUN ```