--- title: "Zebra Joystick Games" id: 56754 type: "computer_media" slug: "zebra-joystick-games" url: "http://localhost/computer_media/zebra-joystick-games/" markdown_url: "http://localhost/computer_media/zebra-joystick-games.md" published_at: "2024-09-22T23:31:59+00:00" modified_at: "2026-04-03T07:58:55+00:00" author: "David Anderson" featured_image: url: "http://localhost/wp-content/uploads/2024/09/2.jpeg" excerpt: "Three two-game BASIC compilations share a clever machine code joystick routine hidden inside a REM statement, driving Invaders, Road Race, H-Fighter, Squish, Painter, and Cobra." 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/" - name: "Zebra Systems" slug: "zebra-systems" taxonomy: "post_tag" url: "http://localhost/tag/zebra-systems/" 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_type: "Program" download_url: "https://archive.org/download/timex-sinclair-software-archive/Zebra%20Joystick%20Games%20%28198x%29%28Zebra%20Systems%29%28TS1000%29%28US%29%28Cassette%29.zip" mediadate: "198x" producer_company: - id: 11469 title: "Zebra Systems, Inc." type: "company" url: "http://localhost/company/zebra-systems/" images: - url: "http://localhost/wp-content/uploads/2024/09/2.jpeg" - url: "http://localhost/wp-content/uploads/2024/09/1.jpeg" - url: "http://localhost/wp-content/uploads/2024/09/3.jpeg" - url: "http://localhost/wp-content/uploads/2024/09/ZJG1.jpg" related_products: - id: 12992 title: "Joystick Adaptor" type: "product" url: "http://localhost/product/joystick-adaptor/" media_type_tags: "Game" --- This listing contains three separate ZX81/TS1000 BASIC game compilations, each offering a two-game menu. The first compilation presents “Invaders” and “Road Race”; the second offers “H-Fighter” and “Squish”; the third provides “Painter” and “Cobra.” All three programs share a common set of numeric constant idioms, using expressions like `P/P` for 1 and `I-I` for 0 to avoid storing literal small integers. Each program embeds a short machine code routine in the REM statement at line 1, with the bytes `\DB\1D\2F\E6\0C\06\00\4F\C9` (or a variant) forming a Z80 routine called via `USR R` (where R=16514, the address just past the REM opcode) to read joystick or keyboard input. The POKE to address 16518 (CDFLAG) switches the display between FAST and SLOW modes, and address 9000 (P) is used for sound or display effects via direct memory writes. *** ## Program Analysis ### Overall Structure The listing comprises three independent two-game compilation programs. Each follows the same architectural pattern: a menu at lines 10–170, game one starting around line 200, and game two starting around line 600. Line 2000 saves the program and line 2010 re-runs it. All three share identical or near-identical constant-encoding idioms and the same machine code joystick/keyboard driver embedded in the `REM` at line 1. ### Machine Code Routine in REM The `REM` statement at line 1 contains Z80 machine code bytes. The predominant sequence is `\DB\1D\2F\E6\0C\06\00\4F\C9`, and the third program uses `\DB\1D\2F\E6\1F\06\00\4F\C9`. Disassembled: | Bytes | Mnemonic | Effect | | --- | --- | --- | | `DB 1D` | IN A,(0x1D) | Read port 0x1D (joystick/keyboard) | | `2F` | CPL | Complement accumulator | | `E6 0C` | AND 0x0C | Mask bits (programs 1 & 2); `E6 1F` masks all 5 bits (program 3) | | `06 00` | LD B,0 | Zero high byte of return value | | `4F` | LD C,A | Move result to C (BC = return value) | | `C9` | RET | Return to BASIC with USR result in BC | This routine is called as `USR R` where `R = VAL "16514"`, which is the byte immediately after the `REM` opcode in the program line, landing directly on `\DB`. The result is a small integer encoding joystick direction bits. ### Constant-Encoding Idiom All three programs avoid storing small literal integers directly, instead deriving them from a base value. The pattern is consistent across all compilations: - `LET P = VAL "9000"` — base anchor (also used as a POKE address) - `LET I = P/P` — evaluates to 1 - `LET Z = I-I` — evaluates to 0 - `LET U = I+I` — evaluates to 2 This technique reduces the token count for frequently used small integers at the cost of readability, and was a common space-saving measure on 1 KB machines. ### Menu Navigation (GOTO Arithmetic) All three programs use a single self-modifying `GOTO` line for menu selection: - Program 1 & 3: `GOTO 170+30*(INKEY$="1")+430*(INKEY$="2")` — branches to 200 or 600 - Program 2: `GOTO 200+500*(INKEY$="1")+100*(INKEY$="2")` — branches to 700 or 300 This is a well-known ZX81 BASIC idiom that avoids `IF`/`THEN` branch chains. The line polls `INKEY$` continuously (acting as its own loop) since neither condition fires when no key is pressed, leaving the target as the line’s own number. ### Program 1 — Invaders (lines 200–490) and Road Race (lines 600–890) **Invaders:** Uses a string `D$` of 32 characters to represent a row of invaders. The alien row scrolls by rotating the string: `D$ = D$(U TO 31) + D$(I)`. The player fires upward; the bullet is tracked via coordinates `X`, `Y`. A hit is detected by checking `D$(X+1) <> " "`. Score increments by `K**U` (10²=100) per kill. **Road Race:** Uses `POKE P, B-J*12` and `POKE P, B-J*13` at address 9000 to produce sound effects. The road scrolls with `SCROLL`. Collision detection uses `PEEK(PEEK Q + 256*PEEK(Q+I))` to read the display file indirectly, comparing against `CODE "O"`. Address `Q = VAL "16398"` is the D-FILE system variable. ### Program 2 — H-Fighter (lines 700–910) and Squish (lines 300–600) **Squish:** A ball bounces around a bordered play area. The player controls a paddle at the bottom. Lives are tracked in `D`; the game resets via `RUN` after 5 lives. Ball direction reversal uses `LET B = SGN(INT(RND*(U+I)) - (F<>I) + (F=13))` for randomised wall bounces. **H-Fighter:** A shoot-em-up using `POKE P, N` and `POKE P, T` for sound. The sprite is drawn with block graphics across four `PRINT AT` statements. Joystick movement decomposes `J` into X and Y components: `LET J = J - INT(J/4)*4` extracts the lower two bits for vertical movement. ### Program 3 — Painter (lines 200–340) and Cobra (lines 600–1010) **Painter:** Uses the ZX81’s `PLOT`/`UNPLOT` commands to draw and erase a pixel. The joystick value is decomposed to horizontal (`SGN(J-7)*(J>U)`) and vertical (`(J=I)-(J=U)`) deltas. Pressing fire (bit detected via `J>=G`) sets `SC=1` which causes the next `PLOT` to be immediately `UNPLOT`ted, effectively erasing. **Cobra:** A snake game. The snake’s head position is `(X,Y)` and the “food” or obstacle at `(V,W)`. An indirect display-file read via `VAL E$` (where `E$` holds the string `"PEEK(PEEK 16398+256*PEEK 16399)"`) detects collisions by examining the character under the cursor before printing. The snake body character is `\@@` (a block graphic). Collision with character code 52 triggers game over at line 1000. ### Key Memory Addresses | Address | Variable | Purpose | | --- | --- | --- | | 9000 | `P` | POKE target for sound/display effects | | 16398 | `Q` | D-FILE system variable (display file pointer) | | 16514 | `R` | Entry point of machine code in REM | | 16518 | — | CDFLAG — switches FAST/SLOW display mode | ### Notable Anomalies - In Program 1, `LET SC = SC-I` at line 340 decrements the score each alien-row cycle, meaning score goes negative before kills add back. This appears intentional as a difficulty penalty. - In Program 3 Cobra, line 880 uses `LET J = USR R - 16*(USR R > 16)`, calling `USR R` twice in one expression, which on a ZX81 evaluates both calls sequentially — potentially reading two different joystick states. - In Program 3 Cobra, lines 910 and 920 both assign to `A`; line 910’s assignment (`LET A = J - INT(J/4)*4`) is immediately overwritten by line 920 (`LET A = (J=U)-(J=I)`), making line 910 dead code. - The `VAL "number"` pattern used throughout for constants like `VAL "16514"` is a deliberate token-count optimisation: the tokenised form of `VAL "16514"` is shorter than the five-digit literal `16514` in certain BASIC implementations. ## Source Code ``` 1 REM DB1D2FE6C604FC9 10 SLOW 30 PRINT "1 INVADERS" 40 PRINT "2 ROAD RACE" 50 PRINT "HIT 1 OR 2" 60 LET Q=VAL "16398" 70 LET R=VAL "16514" 80 LET K=VAL "10" 100 LET P=VAL "9000" 110 LET I=P/P 120 LET Z=I-I 130 LET U=I+I 140 LET SC=Z 150 LET G=VAL "16" 160 LET H=G 170 GOTO 170+30*(INKEY$="1")+430*(INKEY$="2") 200 CLS 210 POKE R+U+U,CODE "0" 214 LET V=Z 215 LET W=Z 220 FOR L=1 TO 4 230 LET D$="U U U U U U U U U U " 240 LET T=Z 250 LET F=Z 270 PRINT AT L,Z;D$;AT 7,G-U;" Y " 280 IF F THEN GOTO 360 300 LET J=USR R 310 IF J>=H THEN GOSUB 500 320 LET G=G+J-6*(J<>Z) 330 LET D$=D$(U TO 31)+D$(I) 340 LET SC=SC-I 350 GOTO 270 360 LET Y=Y-I 370 PRINT AT V,W;" " 380 PRINT AT Y,X;"*" 390 LET V=Y 400 LET W=X 410 IF Y>L THEN GOTO 290 420 IF D$(X+1)=" " THEN GOTO 250 430 LET D$(X+I)=" " 440 LET SC=SC+K**U 450 LET T=T+I 460 PRINT AT Z,Z;SC 470 IF TU) 780 LET J=J<>0 790 POKE P,B-J*12 800 IF N>23 THEN LET N=23 810 IF NM THEN GOTO 700 870 PRINT AT Z,Z;"SCORE --> ";SC 880 POKE P,Z 890 STOP 2000 SAVE "GAME%S" 2010 RUN 1 REM DB1D2FE6C604FC9 10 SLOW 11 CLS 20 PRINT AT 5,6;"ZEBRA SYSTEMS INC." 30 PRINT AT 9,8;"1 H-FIGHTER" 40 PRINT AT 11,8;"2 SQUISH" 50 PRINT AT 17,8;"HIT 1 OR 2" 100 LET P=VAL "9000" 110 LET I=P/P 120 LET U=I+I 130 LET B=VAL "15" 140 LET T=B-U-U-I 150 LET Z=I-I 160 LET SC=Z 170 LET D=Z 180 LET N=T-U 190 POKE P,Z 200 GOTO 200+500*(INKEY$="1")+100*(INKEY$="2") 300 POKE 16518,CODE "£" 310 LET C=N-I 320 CLS 330 PRINT "% % % % % % % % % % % % % % % " 340 FOR X=I TO T 350 PRINT "% ";TAB C+C;"% " 360 NEXT X 370 GOTO 560 380 LET J=USR 16514 390 LET C=C+(J=N)-(J=4) 400 IF C>Z AND C<13 THEN PRINT AT T+I,C-I;" '''' " 410 IF E=T AND C<>F AND C+I<>F THEN GOTO 510 420 LET SC=SC+I 430 PRINT AT E,F;" " 440 LET E=E+A 450 IF E=I OR E=T THEN LET A=-A 460 LET F=F+B 470 IF F=I OR F=13 THEN LET B=-B 480 PRINT AT E,F;"O" 490 IF E=I OR E=T THEN LET B=SGN (INT (RND*(U+I))-(F<>I)+(F=13)) 500 GOTO 380 510 LET D=D+I 520 PRINT AT 14,Z;D,SC 540 IF D>5 THEN RUN 550 PRINT AT E,F;" " 560 LET A=I 570 LET E=A 580 LET F=E 590 LET B=A 600 GOTO 380 700 POKE 16518,CODE "3" 710 CLS 720 LET X=INT (RND*(T+B+I)) 730 LET Y=INT (RND*(T+N)) 740 PRINT AT Z,Z;"SCORE ";SC 750 POKE P,Z 760 LET C=D 770 PRINT AT Y-I,X;" ";AT Y,X;" :.: ";AT Y+I,X;" ' ' ";AT Y+U,X;" ";AT T+I,B;"+" 780 IF D>P THEN STOP 790 LET D=D+T 800 IF D-C>T*T*U THEN GOTO 850 810 LET J=USR 16514 815 IF J<=B THEN GOTO 880 820 IF YT+I OR XB-1 THEN GOTO 850 830 POKE P,N 840 LET SC=SC+T+T+T 850 LET SC=SC-T 860 POKE P,T 870 GOTO 710 880 LET X=X-SGN (J-7)*(J>2) 890 LET J=J-INT (J/4)*4 900 LET Y=Y-J*U+3*SGN J 910 GOTO 770 2000 SAVE "GAME%S" 2010 RUN 1 REM DB1D2FE61F604FC9 10 SLOW 30 PRINT "1 PAINTER" 40 PRINT "2 COBRA" 50 PRINT "HIT 1 OR 2" 70 LET R=VAL "16514" 80 LET K=VAL "10" 110 LET I=K/K 120 LET Z=I-I 130 LET U=I+I 140 LET SC=Z 150 LET G=VAL "16" 170 GOTO 170+30*(INKEY$="1")+430*(INKEY$="2") 200 CLS 220 LET X=G+G 230 LET Y=K+K 240 LET J=USR R 250 IF J>=G THEN LET SC=1 260 LET J=J-G*(J>=G) 270 LET X=X+SGN (J-7)*(J>U) 280 LET J=J-INT (J/4)*4 290 LET Y=Y+(J=I)-(J=U) 300 UNPLOT X,Y 310 PLOT X,Y 320 IF SC THEN UNPLOT X,Y 330 LET SC=Z 340 GOTO 240 600 LET E$="PEEK (PEEK 16398+256*PEEK 16399)" 610 CLS 620 LET SC=R 640 LET H=G+K 650 FOR X=Z TO H 660 PRINT AT Z,X;"% ";AT G,X;"% " 670 NEXT X 680 FOR Y=Z TO G 690 PRINT AT Y,Z;"% ";AT Y,H;"% " 700 NEXT Y 710 LET X=G/U 720 LET Y=H-I 730 LET V=X 740 LET W=I 750 LET A=Z 760 LET B=A 770 LET C=SGN (INT (RND*7)-3+A) 780 LET D=SGN (INT (RND*7)-3+B) 800 PRINT AT V+C,W+D; 810 LET E=VAL E$ 820 IF E<>Z THEN GOTO 870 830 PRINT "O";AT V,W;" " 850 LET V=V+C 860 LET W=W+D 870 LET SC=SC-K 880 LET J=USR R-16*(USR R>16) 890 IF J=Z THEN GOTO 750 900 LET B=SGN (J-7)*(J>U) 910 LET A=J-INT (J/4)*4 920 LET A=(J=U)-(J=I) 930 PRINT AT X+A,Y+B; 940 LET E=VAL E$ 960 IF E=128 THEN GOTO 770 970 LET X=X+A 980 LET Y=Y+B 990 PRINT "@@" 995 IF E<>52 THEN GOTO 770 1000 PRINT AT Z,Z;SC 1010 STOP 2000 SAVE "GAME%S" 2010 RUN ```