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 itemtype='https://schema.org/Blog' itemscope='itemscope' class="wp-singular computer_media-template-default single single-computer_media postid-56754 wp-custom-logo wp-embed-responsive wp-theme-astra wp-child-theme-astra-child ast-desktop ast-separate-container ast-left-sidebar astra-4.12.6 group-blog ast-blog-single-style-1 ast-custom-post-type ast-single-post ast-inherit-site-logo-transparent ast-hfb-header ast-full-width-primary-header ast-box-layout ast-normal-title-enabled astra-addon-4.12.4"D Skip to content
Zebra Joystick Games
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 1LET Z = I-I— evaluates to 0LET 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 UNPLOTted, 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-Iat 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), callingUSR Rtwice 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 likeVAL "16514"is a deliberate token-count optimisation: the tokenised form ofVAL "16514"is shorter than the five-digit literal16514in certain BASIC implementations.
Content
Source Code
1 REM \DB\1D\2F\E6\0C\06\00\4F\C9
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 T<K THEN GOTO 250
480 NEXT L
490 STOP
500 LET F=I
510 LET Y=6
520 LET X=G
530 LET J=J-H
540 RETURN
600 LET X=K
610 LET Y=K
620 LET M=CODE "O"
630 LET N=G/U
640 CLS
650 POKE R+U+U,CODE "£"
660 LET B=G-I
690 GOTO 750
700 LET N=N+INT (U*RND-U*RND)
710 POKE P,B-J*13
720 PRINT "%8"
730 PRINT AT 19,N;"OO OO"
740 PRINT AT X-I,W;" "
750 LET W=Y
760 LET J=USR R
770 LET Y=Y+SGN (J-7)*(J>U)
780 LET J=J<>0
790 POKE P,B-J*12
800 IF N>23 THEN LET N=23
810 IF N<U THEN LET N=U
820 LET SC=SC+U-J
825 POKE P,B-J*13
830 SCROLL
840 PRINT AT X,Y;
850 POKE P,B-J*12
860 IF PEEK (PEEK Q+256*PEEK (Q+I))<>M THEN GOTO 700
870 PRINT AT Z,Z;"SCORE --> ";SC
880 POKE P,Z
890 STOP
2000 SAVE "GAME%S"
2010 RUN
1 REM \DB\1D\2F\E6\0C\06\00\4F\C9
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 Y<T OR Y>T+I OR X<B-2 OR X>B-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 \DB\1D\2F\E6\1F\06\00\4F\C9
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
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.
People
No people associated with this content.
Zebra Joystick Games
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 1LET Z = I-I— evaluates to 0LET 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 UNPLOTted, 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-Iat 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), callingUSR Rtwice 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 likeVAL "16514"is a deliberate token-count optimisation: the tokenised form ofVAL "16514"is shorter than the five-digit literal16514in certain BASIC implementations.
Content
Source Code
1 REM \DB\1D\2F\E6\0C\06\00\4F\C9
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 T<K THEN GOTO 250
480 NEXT L
490 STOP
500 LET F=I
510 LET Y=6
520 LET X=G
530 LET J=J-H
540 RETURN
600 LET X=K
610 LET Y=K
620 LET M=CODE "O"
630 LET N=G/U
640 CLS
650 POKE R+U+U,CODE "£"
660 LET B=G-I
690 GOTO 750
700 LET N=N+INT (U*RND-U*RND)
710 POKE P,B-J*13
720 PRINT "%8"
730 PRINT AT 19,N;"OO OO"
740 PRINT AT X-I,W;" "
750 LET W=Y
760 LET J=USR R
770 LET Y=Y+SGN (J-7)*(J>U)
780 LET J=J<>0
790 POKE P,B-J*12
800 IF N>23 THEN LET N=23
810 IF N<U THEN LET N=U
820 LET SC=SC+U-J
825 POKE P,B-J*13
830 SCROLL
840 PRINT AT X,Y;
850 POKE P,B-J*12
860 IF PEEK (PEEK Q+256*PEEK (Q+I))<>M THEN GOTO 700
870 PRINT AT Z,Z;"SCORE --> ";SC
880 POKE P,Z
890 STOP
2000 SAVE "GAME%S"
2010 RUN
1 REM \DB\1D\2F\E6\0C\06\00\4F\C9
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 Y<T OR Y>T+I OR X<B-2 OR X>B-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 \DB\1D\2F\E6\1F\06\00\4F\C9
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
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.
People
No people associated with this content.
Zebra Joystick Games
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 1LET Z = I-I— evaluates to 0LET 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 UNPLOTted, 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-Iat 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), callingUSR Rtwice 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 likeVAL "16514"is a deliberate token-count optimisation: the tokenised form ofVAL "16514"is shorter than the five-digit literal16514in certain BASIC implementations.
Content
Source Code
1 REM \DB\1D\2F\E6\0C\06\00\4F\C9
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 T<K THEN GOTO 250
480 NEXT L
490 STOP
500 LET F=I
510 LET Y=6
520 LET X=G
530 LET J=J-H
540 RETURN
600 LET X=K
610 LET Y=K
620 LET M=CODE "O"
630 LET N=G/U
640 CLS
650 POKE R+U+U,CODE "£"
660 LET B=G-I
690 GOTO 750
700 LET N=N+INT (U*RND-U*RND)
710 POKE P,B-J*13
720 PRINT "%8"
730 PRINT AT 19,N;"OO OO"
740 PRINT AT X-I,W;" "
750 LET W=Y
760 LET J=USR R
770 LET Y=Y+SGN (J-7)*(J>U)
780 LET J=J<>0
790 POKE P,B-J*12
800 IF N>23 THEN LET N=23
810 IF N<U THEN LET N=U
820 LET SC=SC+U-J
825 POKE P,B-J*13
830 SCROLL
840 PRINT AT X,Y;
850 POKE P,B-J*12
860 IF PEEK (PEEK Q+256*PEEK (Q+I))<>M THEN GOTO 700
870 PRINT AT Z,Z;"SCORE --> ";SC
880 POKE P,Z
890 STOP
2000 SAVE "GAME%S"
2010 RUN
1 REM \DB\1D\2F\E6\0C\06\00\4F\C9
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 Y<T OR Y>T+I OR X<B-2 OR X>B-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 \DB\1D\2F\E6\1F\06\00\4F\C9
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
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.
People
No people associated with this content.
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 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. 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 The This routine is called as All three programs avoid storing small literal integers directly, instead deriving them from a base value. The pattern is consistent across all compilations: 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. All three programs use a single self-modifying This is a well-known ZX81 BASIC idiom that avoids Invaders: Uses a string Road Race: Uses Squish: A ball bounces around a bordered play area. The player controls a paddle at the bottom. Lives are tracked in H-Fighter: A shoot-em-up using Painter: Uses the ZX81’s Cobra: A snake game. The snake’s head position is Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters. No people associated with this content. 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. 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 The This routine is called as All three programs avoid storing small literal integers directly, instead deriving them from a base value. The pattern is consistent across all compilations: 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. All three programs use a single self-modifying This is a well-known ZX81 BASIC idiom that avoids Invaders: Uses a string Road Race: Uses Squish: A ball bounces around a bordered play area. The player controls a paddle at the bottom. Lives are tracked in H-Fighter: A shoot-em-up using Painter: Uses the ZX81’s Cobra: A snake game. The snake’s head position is Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters. No people associated with this content. 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. 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 The This routine is called as All three programs avoid storing small literal integers directly, instead deriving them from a base value. The pattern is consistent across all compilations: 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. All three programs use a single self-modifying This is a well-known ZX81 BASIC idiom that avoids Invaders: Uses a string Road Race: Uses Squish: A ball bounces around a bordered play area. The player controls a paddle at the bottom. Lives are tracked in H-Fighter: A shoot-em-up using Painter: Uses the ZX81’s Cobra: A snake game. The snake’s head position is Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters. No people associated with this content. 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. 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 The This routine is called as All three programs avoid storing small literal integers directly, instead deriving them from a base value. The pattern is consistent across all compilations: 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. All three programs use a single self-modifying This is a well-known ZX81 BASIC idiom that avoids Invaders: Uses a string Road Race: Uses Squish: A ball bounces around a bordered play area. The player controls a paddle at the bottom. Lives are tracked in H-Fighter: A shoot-em-up using Painter: Uses the ZX81’s Cobra: A snake game. The snake’s head position is Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters. No people associated with this content. 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. 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 The This routine is called as All three programs avoid storing small literal integers directly, instead deriving them from a base value. The pattern is consistent across all compilations: 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. All three programs use a single self-modifying This is a well-known ZX81 BASIC idiom that avoids Invaders: Uses a string Road Race: Uses Squish: A ball bounces around a bordered play area. The player controls a paddle at the bottom. Lives are tracked in H-Fighter: A shoot-em-up using Painter: Uses the ZX81’s Cobra: A snake game. The snake’s head position is Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters. No people associated with this content.REM statement at line 1 contains Z80 machine code bytes. The predominant sequence is \DB itemtype='https://schema.org/Blog' itemscope='itemscope' class="wp-singular computer_media-template-default single single-computer_media postid-56754 wp-custom-logo wp-embed-responsive wp-theme-astra wp-child-theme-astra-child ast-desktop ast-separate-container ast-left-sidebar astra-4.12.6 group-blog ast-blog-single-style-1 ast-custom-post-type ast-single-post ast-inherit-site-logo-transparent ast-hfb-header ast-full-width-primary-header ast-box-layout ast-normal-title-enabled astra-addon-4.12.4"D
Skip to content
, and the third program uses Zebra Joystick Games
Program Analysis
Overall Structure
REM at line 1.Machine Code Routine in REM
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 1DIN A,(0x1D) Read port 0x1D (joystick/keyboard) 2FCPL Complement accumulator E6 0CAND 0x0C Mask bits (programs 1 & 2); E6 1F masks all 5 bits (program 3)06 00LD B,0 Zero high byte of return value 4FLD C,A Move result to C (BC = return value) C9RET Return to BASIC with USR result in BC 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
LET P = VAL "9000" — base anchor (also used as a POKE address)LET I = P/P — evaluates to 1LET Z = I-I — evaluates to 0LET U = I+I — evaluates to 2Menu Navigation (GOTO Arithmetic)
GOTO line for menu selection:
GOTO 170+30*(INKEY$="1")+430*(INKEY$="2") — branches to 200 or 600GOTO 200+500*(INKEY$="1")+100*(INKEY$="2") — branches to 700 or 300IF/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)
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.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)
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.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)
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 UNPLOTted, effectively erasing.(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 PPOKE target for sound/display effects 16398 QD-FILE system variable (display file pointer) 16514 REntry point of machine code in REM 16518 — CDFLAG — switches FAST/SLOW display mode Notable Anomalies
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.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.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.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.Content
Source Code
1 REM \DB\1D\2F\E6\0C\06\00\4F\C9
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 T<K THEN GOTO 250
480 NEXT L
490 STOP
500 LET F=I
510 LET Y=6
520 LET X=G
530 LET J=J-H
540 RETURN
600 LET X=K
610 LET Y=K
620 LET M=CODE "O"
630 LET N=G/U
640 CLS
650 POKE R+U+U,CODE "£"
660 LET B=G-I
690 GOTO 750
700 LET N=N+INT (U*RND-U*RND)
710 POKE P,B-J*13
720 PRINT "%8"
730 PRINT AT 19,N;"OO OO"
740 PRINT AT X-I,W;" "
750 LET W=Y
760 LET J=USR R
770 LET Y=Y+SGN (J-7)*(J>U)
780 LET J=J<>0
790 POKE P,B-J*12
800 IF N>23 THEN LET N=23
810 IF N<U THEN LET N=U
820 LET SC=SC+U-J
825 POKE P,B-J*13
830 SCROLL
840 PRINT AT X,Y;
850 POKE P,B-J*12
860 IF PEEK (PEEK Q+256*PEEK (Q+I))<>M THEN GOTO 700
870 PRINT AT Z,Z;"SCORE --> ";SC
880 POKE P,Z
890 STOP
2000 SAVE "GAME%S"
2010 RUN
1 REM \DB\1D\2F\E6\0C\06\00\4F\C9
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 Y<T OR Y>T+I OR X<B-2 OR X>B-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 \DB\1D\2F\E6\1F\06\00\4F\C9
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
People
Zebra Joystick Games
Program Analysis
Overall Structure
REM at line 1.Machine Code Routine in REM
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 1DIN A,(0x1D) Read port 0x1D (joystick/keyboard) 2FCPL Complement accumulator E6 0CAND 0x0C Mask bits (programs 1 & 2); E6 1F masks all 5 bits (program 3)06 00LD B,0 Zero high byte of return value 4FLD C,A Move result to C (BC = return value) C9RET Return to BASIC with USR result in BC 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
LET P = VAL "9000" — base anchor (also used as a POKE address)LET I = P/P — evaluates to 1LET Z = I-I — evaluates to 0LET U = I+I — evaluates to 2Menu Navigation (GOTO Arithmetic)
GOTO line for menu selection:
GOTO 170+30*(INKEY$="1")+430*(INKEY$="2") — branches to 200 or 600GOTO 200+500*(INKEY$="1")+100*(INKEY$="2") — branches to 700 or 300IF/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)
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.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)
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.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)
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 UNPLOTted, effectively erasing.(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 PPOKE target for sound/display effects 16398 QD-FILE system variable (display file pointer) 16514 REntry point of machine code in REM 16518 — CDFLAG — switches FAST/SLOW display mode Notable Anomalies
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.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.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.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.Content
Source Code
1 REM \DB\1D\2F\E6\0C\06\00\4F\C9
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 T<K THEN GOTO 250
480 NEXT L
490 STOP
500 LET F=I
510 LET Y=6
520 LET X=G
530 LET J=J-H
540 RETURN
600 LET X=K
610 LET Y=K
620 LET M=CODE "O"
630 LET N=G/U
640 CLS
650 POKE R+U+U,CODE "£"
660 LET B=G-I
690 GOTO 750
700 LET N=N+INT (U*RND-U*RND)
710 POKE P,B-J*13
720 PRINT "%8"
730 PRINT AT 19,N;"OO OO"
740 PRINT AT X-I,W;" "
750 LET W=Y
760 LET J=USR R
770 LET Y=Y+SGN (J-7)*(J>U)
780 LET J=J<>0
790 POKE P,B-J*12
800 IF N>23 THEN LET N=23
810 IF N<U THEN LET N=U
820 LET SC=SC+U-J
825 POKE P,B-J*13
830 SCROLL
840 PRINT AT X,Y;
850 POKE P,B-J*12
860 IF PEEK (PEEK Q+256*PEEK (Q+I))<>M THEN GOTO 700
870 PRINT AT Z,Z;"SCORE --> ";SC
880 POKE P,Z
890 STOP
2000 SAVE "GAME%S"
2010 RUN
1 REM \DB\1D\2F\E6\0C\06\00\4F\C9
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 Y<T OR Y>T+I OR X<B-2 OR X>B-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 \DB\1D\2F\E6\1F\06\00\4F\C9
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
People
Zebra Joystick Games
Program Analysis
Overall Structure
REM at line 1.Machine Code Routine in REM
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 1DIN A,(0x1D) Read port 0x1D (joystick/keyboard) 2FCPL Complement accumulator E6 0CAND 0x0C Mask bits (programs 1 & 2); E6 1F masks all 5 bits (program 3)06 00LD B,0 Zero high byte of return value 4FLD C,A Move result to C (BC = return value) C9RET Return to BASIC with USR result in BC 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
LET P = VAL "9000" — base anchor (also used as a POKE address)LET I = P/P — evaluates to 1LET Z = I-I — evaluates to 0LET U = I+I — evaluates to 2Menu Navigation (GOTO Arithmetic)
GOTO line for menu selection:
GOTO 170+30*(INKEY$="1")+430*(INKEY$="2") — branches to 200 or 600GOTO 200+500*(INKEY$="1")+100*(INKEY$="2") — branches to 700 or 300IF/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)
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.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)
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.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)
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 UNPLOTted, effectively erasing.(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 PPOKE target for sound/display effects 16398 QD-FILE system variable (display file pointer) 16514 REntry point of machine code in REM 16518 — CDFLAG — switches FAST/SLOW display mode Notable Anomalies
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.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.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.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.Content
Source Code
1 REM \DB\1D\2F\E6\0C\06\00\4F\C9
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 T<K THEN GOTO 250
480 NEXT L
490 STOP
500 LET F=I
510 LET Y=6
520 LET X=G
530 LET J=J-H
540 RETURN
600 LET X=K
610 LET Y=K
620 LET M=CODE "O"
630 LET N=G/U
640 CLS
650 POKE R+U+U,CODE "£"
660 LET B=G-I
690 GOTO 750
700 LET N=N+INT (U*RND-U*RND)
710 POKE P,B-J*13
720 PRINT "%8"
730 PRINT AT 19,N;"OO OO"
740 PRINT AT X-I,W;" "
750 LET W=Y
760 LET J=USR R
770 LET Y=Y+SGN (J-7)*(J>U)
780 LET J=J<>0
790 POKE P,B-J*12
800 IF N>23 THEN LET N=23
810 IF N<U THEN LET N=U
820 LET SC=SC+U-J
825 POKE P,B-J*13
830 SCROLL
840 PRINT AT X,Y;
850 POKE P,B-J*12
860 IF PEEK (PEEK Q+256*PEEK (Q+I))<>M THEN GOTO 700
870 PRINT AT Z,Z;"SCORE --> ";SC
880 POKE P,Z
890 STOP
2000 SAVE "GAME%S"
2010 RUN
1 REM \DB\1D\2F\E6\0C\06\00\4F\C9
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 Y<T OR Y>T+I OR X<B-2 OR X>B-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 \DB\1D\2F\E6\1F\06\00\4F\C9
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
People
\DB itemtype='https://schema.org/Blog' itemscope='itemscope' class="wp-singular computer_media-template-default single single-computer_media postid-56754 wp-custom-logo wp-embed-responsive wp-theme-astra wp-child-theme-astra-child ast-desktop ast-separate-container ast-left-sidebar astra-4.12.6 group-blog ast-blog-single-style-1 ast-custom-post-type ast-single-post ast-inherit-site-logo-transparent ast-hfb-header ast-full-width-primary-header ast-box-layout ast-normal-title-enabled astra-addon-4.12.4"D
Skip to content
. Disassembled:Zebra Joystick Games
Program Analysis
Overall Structure
REM at line 1.Machine Code Routine in REM
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 1DIN A,(0x1D) Read port 0x1D (joystick/keyboard) 2FCPL Complement accumulator E6 0CAND 0x0C Mask bits (programs 1 & 2); E6 1F masks all 5 bits (program 3)06 00LD B,0 Zero high byte of return value 4FLD C,A Move result to C (BC = return value) C9RET Return to BASIC with USR result in BC 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
LET P = VAL "9000" — base anchor (also used as a POKE address)LET I = P/P — evaluates to 1LET Z = I-I — evaluates to 0LET U = I+I — evaluates to 2Menu Navigation (GOTO Arithmetic)
GOTO line for menu selection:
GOTO 170+30*(INKEY$="1")+430*(INKEY$="2") — branches to 200 or 600GOTO 200+500*(INKEY$="1")+100*(INKEY$="2") — branches to 700 or 300IF/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)
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.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)
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.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)
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 UNPLOTted, effectively erasing.(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 PPOKE target for sound/display effects 16398 QD-FILE system variable (display file pointer) 16514 REntry point of machine code in REM 16518 — CDFLAG — switches FAST/SLOW display mode Notable Anomalies
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.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.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.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.Content
Source Code
1 REM \DB\1D\2F\E6\0C\06\00\4F\C9
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 T<K THEN GOTO 250
480 NEXT L
490 STOP
500 LET F=I
510 LET Y=6
520 LET X=G
530 LET J=J-H
540 RETURN
600 LET X=K
610 LET Y=K
620 LET M=CODE "O"
630 LET N=G/U
640 CLS
650 POKE R+U+U,CODE "£"
660 LET B=G-I
690 GOTO 750
700 LET N=N+INT (U*RND-U*RND)
710 POKE P,B-J*13
720 PRINT "%8"
730 PRINT AT 19,N;"OO OO"
740 PRINT AT X-I,W;" "
750 LET W=Y
760 LET J=USR R
770 LET Y=Y+SGN (J-7)*(J>U)
780 LET J=J<>0
790 POKE P,B-J*12
800 IF N>23 THEN LET N=23
810 IF N<U THEN LET N=U
820 LET SC=SC+U-J
825 POKE P,B-J*13
830 SCROLL
840 PRINT AT X,Y;
850 POKE P,B-J*12
860 IF PEEK (PEEK Q+256*PEEK (Q+I))<>M THEN GOTO 700
870 PRINT AT Z,Z;"SCORE --> ";SC
880 POKE P,Z
890 STOP
2000 SAVE "GAME%S"
2010 RUN
1 REM \DB\1D\2F\E6\0C\06\00\4F\C9
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 Y<T OR Y>T+I OR X<B-2 OR X>B-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 \DB\1D\2F\E6\1F\06\00\4F\C9
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
People
Zebra Joystick Games
Program Analysis
Overall Structure
REM at line 1.Machine Code Routine in REM
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 1DIN A,(0x1D) Read port 0x1D (joystick/keyboard) 2FCPL Complement accumulator E6 0CAND 0x0C Mask bits (programs 1 & 2); E6 1F masks all 5 bits (program 3)06 00LD B,0 Zero high byte of return value 4FLD C,A Move result to C (BC = return value) C9RET Return to BASIC with USR result in BC 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
LET P = VAL "9000" — base anchor (also used as a POKE address)LET I = P/P — evaluates to 1LET Z = I-I — evaluates to 0LET U = I+I — evaluates to 2Menu Navigation (GOTO Arithmetic)
GOTO line for menu selection:
GOTO 170+30*(INKEY$="1")+430*(INKEY$="2") — branches to 200 or 600GOTO 200+500*(INKEY$="1")+100*(INKEY$="2") — branches to 700 or 300IF/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)
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.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)
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.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)
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 UNPLOTted, effectively erasing.(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 PPOKE target for sound/display effects 16398 QD-FILE system variable (display file pointer) 16514 REntry point of machine code in REM 16518 — CDFLAG — switches FAST/SLOW display mode Notable Anomalies
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.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.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.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.Content
Source Code
1 REM \DB\1D\2F\E6\0C\06\00\4F\C9
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 T<K THEN GOTO 250
480 NEXT L
490 STOP
500 LET F=I
510 LET Y=6
520 LET X=G
530 LET J=J-H
540 RETURN
600 LET X=K
610 LET Y=K
620 LET M=CODE "O"
630 LET N=G/U
640 CLS
650 POKE R+U+U,CODE "£"
660 LET B=G-I
690 GOTO 750
700 LET N=N+INT (U*RND-U*RND)
710 POKE P,B-J*13
720 PRINT "%8"
730 PRINT AT 19,N;"OO OO"
740 PRINT AT X-I,W;" "
750 LET W=Y
760 LET J=USR R
770 LET Y=Y+SGN (J-7)*(J>U)
780 LET J=J<>0
790 POKE P,B-J*12
800 IF N>23 THEN LET N=23
810 IF N<U THEN LET N=U
820 LET SC=SC+U-J
825 POKE P,B-J*13
830 SCROLL
840 PRINT AT X,Y;
850 POKE P,B-J*12
860 IF PEEK (PEEK Q+256*PEEK (Q+I))<>M THEN GOTO 700
870 PRINT AT Z,Z;"SCORE --> ";SC
880 POKE P,Z
890 STOP
2000 SAVE "GAME%S"
2010 RUN
1 REM \DB\1D\2F\E6\0C\06\00\4F\C9
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 Y<T OR Y>T+I OR X<B-2 OR X>B-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 \DB\1D\2F\E6\1F\06\00\4F\C9
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
People
| 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 1LET Z = I-I— evaluates to 0LET 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 UNPLOTted, 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-Iat 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), callingUSR Rtwice 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 likeVAL "16514"is a deliberate token-count optimisation: the tokenised form ofVAL "16514"is shorter than the five-digit literal16514in certain BASIC implementations.
Content
Source Code
1 REM \DB itemtype='https://schema.org/Blog' itemscope='itemscope' class="wp-singular computer_media-template-default single single-computer_media postid-56754 wp-custom-logo wp-embed-responsive wp-theme-astra wp-child-theme-astra-child ast-desktop ast-separate-container ast-left-sidebar astra-4.12.6 group-blog ast-blog-single-style-1 ast-custom-post-type ast-single-post ast-inherit-site-logo-transparent ast-hfb-header ast-full-width-primary-header ast-box-layout ast-normal-title-enabled astra-addon-4.12.4"D
Skip to content
Zebra Joystick Games
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 1DIN A,(0x1D) Read port 0x1D (joystick/keyboard) 2FCPL Complement accumulator E6 0CAND 0x0C Mask bits (programs 1 & 2); E6 1F masks all 5 bits (program 3) 06 00LD B,0 Zero high byte of return value 4FLD C,A Move result to C (BC = return value) C9RET 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 UNPLOTted, 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 PPOKE target for sound/display effects 16398 QD-FILE system variable (display file pointer) 16514 REntry 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.
Content
Source Code
1 REM \DB\1D\2F\E6\0C\06\00\4F\C9
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 T<K THEN GOTO 250
480 NEXT L
490 STOP
500 LET F=I
510 LET Y=6
520 LET X=G
530 LET J=J-H
540 RETURN
600 LET X=K
610 LET Y=K
620 LET M=CODE "O"
630 LET N=G/U
640 CLS
650 POKE R+U+U,CODE "£"
660 LET B=G-I
690 GOTO 750
700 LET N=N+INT (U*RND-U*RND)
710 POKE P,B-J*13
720 PRINT "%8"
730 PRINT AT 19,N;"OO OO"
740 PRINT AT X-I,W;" "
750 LET W=Y
760 LET J=USR R
770 LET Y=Y+SGN (J-7)*(J>U)
780 LET J=J<>0
790 POKE P,B-J*12
800 IF N>23 THEN LET N=23
810 IF N<U THEN LET N=U
820 LET SC=SC+U-J
825 POKE P,B-J*13
830 SCROLL
840 PRINT AT X,Y;
850 POKE P,B-J*12
860 IF PEEK (PEEK Q+256*PEEK (Q+I))<>M THEN GOTO 700
870 PRINT AT Z,Z;"SCORE --> ";SC
880 POKE P,Z
890 STOP
2000 SAVE "GAME%S"
2010 RUN
1 REM \DB\1D\2F\E6\0C\06\00\4F\C9
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 Y<T OR Y>T+I OR X<B-2 OR X>B-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 \DB\1D\2F\E6\1F\06\00\4F\C9
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
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.
People
No people associated with this content.
F\E6
Skip to content
Zebra Joystick Games
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 1DIN A,(0x1D) Read port 0x1D (joystick/keyboard) 2FCPL Complement accumulator E6 0CAND 0x0C Mask bits (programs 1 & 2); E6 1F masks all 5 bits (program 3) 06 00LD B,0 Zero high byte of return value 4FLD C,A Move result to C (BC = return value) C9RET 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 UNPLOTted, 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 PPOKE target for sound/display effects 16398 QD-FILE system variable (display file pointer) 16514 REntry 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.
Content
Source Code
1 REM \DB\1D\2F\E6\0C\06\00\4F\C9
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 T<K THEN GOTO 250
480 NEXT L
490 STOP
500 LET F=I
510 LET Y=6
520 LET X=G
530 LET J=J-H
540 RETURN
600 LET X=K
610 LET Y=K
620 LET M=CODE "O"
630 LET N=G/U
640 CLS
650 POKE R+U+U,CODE "£"
660 LET B=G-I
690 GOTO 750
700 LET N=N+INT (U*RND-U*RND)
710 POKE P,B-J*13
720 PRINT "%8"
730 PRINT AT 19,N;"OO OO"
740 PRINT AT X-I,W;" "
750 LET W=Y
760 LET J=USR R
770 LET Y=Y+SGN (J-7)*(J>U)
780 LET J=J<>0
790 POKE P,B-J*12
800 IF N>23 THEN LET N=23
810 IF N<U THEN LET N=U
820 LET SC=SC+U-J
825 POKE P,B-J*13
830 SCROLL
840 PRINT AT X,Y;
850 POKE P,B-J*12
860 IF PEEK (PEEK Q+256*PEEK (Q+I))<>M THEN GOTO 700
870 PRINT AT Z,Z;"SCORE --> ";SC
880 POKE P,Z
890 STOP
2000 SAVE "GAME%S"
2010 RUN
1 REM \DB\1D\2F\E6\0C\06\00\4F\C9
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 Y<T OR Y>T+I OR X<B-2 OR X>B-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 \DB\1D\2F\E6\1F\06\00\4F\C9
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
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.
People
No people associated with this content.
C
Skip to content
Zebra Joystick Games
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 1DIN A,(0x1D) Read port 0x1D (joystick/keyboard) 2FCPL Complement accumulator E6 0CAND 0x0C Mask bits (programs 1 & 2); E6 1F masks all 5 bits (program 3) 06 00LD B,0 Zero high byte of return value 4FLD C,A Move result to C (BC = return value) C9RET 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 UNPLOTted, 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 PPOKE target for sound/display effects 16398 QD-FILE system variable (display file pointer) 16514 REntry 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.
Content
Source Code
1 REM \DB\1D\2F\E6\0C\06\00\4F\C9
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 T<K THEN GOTO 250
480 NEXT L
490 STOP
500 LET F=I
510 LET Y=6
520 LET X=G
530 LET J=J-H
540 RETURN
600 LET X=K
610 LET Y=K
620 LET M=CODE "O"
630 LET N=G/U
640 CLS
650 POKE R+U+U,CODE "£"
660 LET B=G-I
690 GOTO 750
700 LET N=N+INT (U*RND-U*RND)
710 POKE P,B-J*13
720 PRINT "%8"
730 PRINT AT 19,N;"OO OO"
740 PRINT AT X-I,W;" "
750 LET W=Y
760 LET J=USR R
770 LET Y=Y+SGN (J-7)*(J>U)
780 LET J=J<>0
790 POKE P,B-J*12
800 IF N>23 THEN LET N=23
810 IF N<U THEN LET N=U
820 LET SC=SC+U-J
825 POKE P,B-J*13
830 SCROLL
840 PRINT AT X,Y;
850 POKE P,B-J*12
860 IF PEEK (PEEK Q+256*PEEK (Q+I))<>M THEN GOTO 700
870 PRINT AT Z,Z;"SCORE --> ";SC
880 POKE P,Z
890 STOP
2000 SAVE "GAME%S"
2010 RUN
1 REM \DB\1D\2F\E6\0C\06\00\4F\C9
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 Y<T OR Y>T+I OR X<B-2 OR X>B-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 \DB\1D\2F\E6\1F\06\00\4F\C9
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
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.
People
No people associated with this content.
F\C9
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 T<K THEN GOTO 250
480 NEXT L
490 STOP
500 LET F=I
510 LET Y=6
520 LET X=G
530 LET J=J-H
540 RETURN
600 LET X=K
610 LET Y=K
620 LET M=CODE "O"
630 LET N=G/U
640 CLS
650 POKE R+U+U,CODE "£"
660 LET B=G-I
690 GOTO 750
700 LET N=N+INT (U*RND-U*RND)
710 POKE P,B-J*13
720 PRINT "%8"
730 PRINT AT 19,N;"OO OO"
740 PRINT AT X-I,W;" "
750 LET W=Y
760 LET J=USR R
770 LET Y=Y+SGN (J-7)*(J>U)
780 LET J=J<>0
790 POKE P,B-J*12
800 IF N>23 THEN LET N=23
810 IF N<U THEN LET N=U
820 LET SC=SC+U-J
825 POKE P,B-J*13
830 SCROLL
840 PRINT AT X,Y;
850 POKE P,B-J*12
860 IF PEEK (PEEK Q+256*PEEK (Q+I))<>M THEN GOTO 700
870 PRINT AT Z,Z;"SCORE --> ";SC
880 POKE P,Z
890 STOP
\n2000 SAVE "GAME%S"
\n2010 RUN
1 REM \DB itemtype='https://schema.org/Blog' itemscope='itemscope' class="wp-singular computer_media-template-default single single-computer_media postid-56754 wp-custom-logo wp-embed-responsive wp-theme-astra wp-child-theme-astra-child ast-desktop ast-separate-container ast-left-sidebar astra-4.12.6 group-blog ast-blog-single-style-1 ast-custom-post-type ast-single-post ast-inherit-site-logo-transparent ast-hfb-header ast-full-width-primary-header ast-box-layout ast-normal-title-enabled astra-addon-4.12.4"D
Skip to content
Zebra Joystick Games
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 1DIN A,(0x1D) Read port 0x1D (joystick/keyboard) 2FCPL Complement accumulator E6 0CAND 0x0C Mask bits (programs 1 & 2); E6 1F masks all 5 bits (program 3) 06 00LD B,0 Zero high byte of return value 4FLD C,A Move result to C (BC = return value) C9RET 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 UNPLOTted, 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 PPOKE target for sound/display effects 16398 QD-FILE system variable (display file pointer) 16514 REntry 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.
Content
Source Code
1 REM \DB\1D\2F\E6\0C\06\00\4F\C9
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 T<K THEN GOTO 250
480 NEXT L
490 STOP
500 LET F=I
510 LET Y=6
520 LET X=G
530 LET J=J-H
540 RETURN
600 LET X=K
610 LET Y=K
620 LET M=CODE "O"
630 LET N=G/U
640 CLS
650 POKE R+U+U,CODE "£"
660 LET B=G-I
690 GOTO 750
700 LET N=N+INT (U*RND-U*RND)
710 POKE P,B-J*13
720 PRINT "%8"
730 PRINT AT 19,N;"OO OO"
740 PRINT AT X-I,W;" "
750 LET W=Y
760 LET J=USR R
770 LET Y=Y+SGN (J-7)*(J>U)
780 LET J=J<>0
790 POKE P,B-J*12
800 IF N>23 THEN LET N=23
810 IF N<U THEN LET N=U
820 LET SC=SC+U-J
825 POKE P,B-J*13
830 SCROLL
840 PRINT AT X,Y;
850 POKE P,B-J*12
860 IF PEEK (PEEK Q+256*PEEK (Q+I))<>M THEN GOTO 700
870 PRINT AT Z,Z;"SCORE --> ";SC
880 POKE P,Z
890 STOP
2000 SAVE "GAME%S"
2010 RUN
1 REM \DB\1D\2F\E6\0C\06\00\4F\C9
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 Y<T OR Y>T+I OR X<B-2 OR X>B-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 \DB\1D\2F\E6\1F\06\00\4F\C9
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
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.
People
No people associated with this content.
F\E6
Skip to content
Zebra Joystick Games
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 1DIN A,(0x1D) Read port 0x1D (joystick/keyboard) 2FCPL Complement accumulator E6 0CAND 0x0C Mask bits (programs 1 & 2); E6 1F masks all 5 bits (program 3) 06 00LD B,0 Zero high byte of return value 4FLD C,A Move result to C (BC = return value) C9RET 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 UNPLOTted, 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 PPOKE target for sound/display effects 16398 QD-FILE system variable (display file pointer) 16514 REntry 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.
Content
Source Code
1 REM \DB\1D\2F\E6\0C\06\00\4F\C9
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 T<K THEN GOTO 250
480 NEXT L
490 STOP
500 LET F=I
510 LET Y=6
520 LET X=G
530 LET J=J-H
540 RETURN
600 LET X=K
610 LET Y=K
620 LET M=CODE "O"
630 LET N=G/U
640 CLS
650 POKE R+U+U,CODE "£"
660 LET B=G-I
690 GOTO 750
700 LET N=N+INT (U*RND-U*RND)
710 POKE P,B-J*13
720 PRINT "%8"
730 PRINT AT 19,N;"OO OO"
740 PRINT AT X-I,W;" "
750 LET W=Y
760 LET J=USR R
770 LET Y=Y+SGN (J-7)*(J>U)
780 LET J=J<>0
790 POKE P,B-J*12
800 IF N>23 THEN LET N=23
810 IF N<U THEN LET N=U
820 LET SC=SC+U-J
825 POKE P,B-J*13
830 SCROLL
840 PRINT AT X,Y;
850 POKE P,B-J*12
860 IF PEEK (PEEK Q+256*PEEK (Q+I))<>M THEN GOTO 700
870 PRINT AT Z,Z;"SCORE --> ";SC
880 POKE P,Z
890 STOP
2000 SAVE "GAME%S"
2010 RUN
1 REM \DB\1D\2F\E6\0C\06\00\4F\C9
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 Y<T OR Y>T+I OR X<B-2 OR X>B-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 \DB\1D\2F\E6\1F\06\00\4F\C9
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
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.
People
No people associated with this content.
C
Skip to content
Zebra Joystick Games
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 1DIN A,(0x1D) Read port 0x1D (joystick/keyboard) 2FCPL Complement accumulator E6 0CAND 0x0C Mask bits (programs 1 & 2); E6 1F masks all 5 bits (program 3) 06 00LD B,0 Zero high byte of return value 4FLD C,A Move result to C (BC = return value) C9RET 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 UNPLOTted, 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 PPOKE target for sound/display effects 16398 QD-FILE system variable (display file pointer) 16514 REntry 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.
Content
Source Code
1 REM \DB\1D\2F\E6\0C\06\00\4F\C9
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 T<K THEN GOTO 250
480 NEXT L
490 STOP
500 LET F=I
510 LET Y=6
520 LET X=G
530 LET J=J-H
540 RETURN
600 LET X=K
610 LET Y=K
620 LET M=CODE "O"
630 LET N=G/U
640 CLS
650 POKE R+U+U,CODE "£"
660 LET B=G-I
690 GOTO 750
700 LET N=N+INT (U*RND-U*RND)
710 POKE P,B-J*13
720 PRINT "%8"
730 PRINT AT 19,N;"OO OO"
740 PRINT AT X-I,W;" "
750 LET W=Y
760 LET J=USR R
770 LET Y=Y+SGN (J-7)*(J>U)
780 LET J=J<>0
790 POKE P,B-J*12
800 IF N>23 THEN LET N=23
810 IF N<U THEN LET N=U
820 LET SC=SC+U-J
825 POKE P,B-J*13
830 SCROLL
840 PRINT AT X,Y;
850 POKE P,B-J*12
860 IF PEEK (PEEK Q+256*PEEK (Q+I))<>M THEN GOTO 700
870 PRINT AT Z,Z;"SCORE --> ";SC
880 POKE P,Z
890 STOP
2000 SAVE "GAME%S"
2010 RUN
1 REM \DB\1D\2F\E6\0C\06\00\4F\C9
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 Y<T OR Y>T+I OR X<B-2 OR X>B-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 \DB\1D\2F\E6\1F\06\00\4F\C9
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
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.
People
No people associated with this content.
F\C9
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 Y<T OR Y>T+I OR X<B-2 OR X>B-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
\n2000 SAVE "GAME%S"
\n2010 RUN
1 REM \DB itemtype='https://schema.org/Blog' itemscope='itemscope' class="wp-singular computer_media-template-default single single-computer_media postid-56754 wp-custom-logo wp-embed-responsive wp-theme-astra wp-child-theme-astra-child ast-desktop ast-separate-container ast-left-sidebar astra-4.12.6 group-blog ast-blog-single-style-1 ast-custom-post-type ast-single-post ast-inherit-site-logo-transparent ast-hfb-header ast-full-width-primary-header ast-box-layout ast-normal-title-enabled astra-addon-4.12.4"D
Skip to content
Zebra Joystick Games
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 1DIN A,(0x1D) Read port 0x1D (joystick/keyboard) 2FCPL Complement accumulator E6 0CAND 0x0C Mask bits (programs 1 & 2); E6 1F masks all 5 bits (program 3) 06 00LD B,0 Zero high byte of return value 4FLD C,A Move result to C (BC = return value) C9RET 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 UNPLOTted, 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 PPOKE target for sound/display effects 16398 QD-FILE system variable (display file pointer) 16514 REntry 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.
Content
Source Code
1 REM \DB\1D\2F\E6\0C\06\00\4F\C9
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 T<K THEN GOTO 250
480 NEXT L
490 STOP
500 LET F=I
510 LET Y=6
520 LET X=G
530 LET J=J-H
540 RETURN
600 LET X=K
610 LET Y=K
620 LET M=CODE "O"
630 LET N=G/U
640 CLS
650 POKE R+U+U,CODE "£"
660 LET B=G-I
690 GOTO 750
700 LET N=N+INT (U*RND-U*RND)
710 POKE P,B-J*13
720 PRINT "%8"
730 PRINT AT 19,N;"OO OO"
740 PRINT AT X-I,W;" "
750 LET W=Y
760 LET J=USR R
770 LET Y=Y+SGN (J-7)*(J>U)
780 LET J=J<>0
790 POKE P,B-J*12
800 IF N>23 THEN LET N=23
810 IF N<U THEN LET N=U
820 LET SC=SC+U-J
825 POKE P,B-J*13
830 SCROLL
840 PRINT AT X,Y;
850 POKE P,B-J*12
860 IF PEEK (PEEK Q+256*PEEK (Q+I))<>M THEN GOTO 700
870 PRINT AT Z,Z;"SCORE --> ";SC
880 POKE P,Z
890 STOP
2000 SAVE "GAME%S"
2010 RUN
1 REM \DB\1D\2F\E6\0C\06\00\4F\C9
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 Y<T OR Y>T+I OR X<B-2 OR X>B-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 \DB\1D\2F\E6\1F\06\00\4F\C9
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
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.
People
No people associated with this content.
F\E6 itemtype='https://schema.org/Blog' itemscope='itemscope' class="wp-singular computer_media-template-default single single-computer_media postid-56754 wp-custom-logo wp-embed-responsive wp-theme-astra wp-child-theme-astra-child ast-desktop ast-separate-container ast-left-sidebar astra-4.12.6 group-blog ast-blog-single-style-1 ast-custom-post-type ast-single-post ast-inherit-site-logo-transparent ast-hfb-header ast-full-width-primary-header ast-box-layout ast-normal-title-enabled astra-addon-4.12.4"F
Skip to content
Zebra Joystick Games
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 1DIN A,(0x1D) Read port 0x1D (joystick/keyboard) 2FCPL Complement accumulator E6 0CAND 0x0C Mask bits (programs 1 & 2); E6 1F masks all 5 bits (program 3) 06 00LD B,0 Zero high byte of return value 4FLD C,A Move result to C (BC = return value) C9RET 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 UNPLOTted, 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 PPOKE target for sound/display effects 16398 QD-FILE system variable (display file pointer) 16514 REntry 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.
Content
Source Code
1 REM \DB\1D\2F\E6\0C\06\00\4F\C9
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 T<K THEN GOTO 250
480 NEXT L
490 STOP
500 LET F=I
510 LET Y=6
520 LET X=G
530 LET J=J-H
540 RETURN
600 LET X=K
610 LET Y=K
620 LET M=CODE "O"
630 LET N=G/U
640 CLS
650 POKE R+U+U,CODE "£"
660 LET B=G-I
690 GOTO 750
700 LET N=N+INT (U*RND-U*RND)
710 POKE P,B-J*13
720 PRINT "%8"
730 PRINT AT 19,N;"OO OO"
740 PRINT AT X-I,W;" "
750 LET W=Y
760 LET J=USR R
770 LET Y=Y+SGN (J-7)*(J>U)
780 LET J=J<>0
790 POKE P,B-J*12
800 IF N>23 THEN LET N=23
810 IF N<U THEN LET N=U
820 LET SC=SC+U-J
825 POKE P,B-J*13
830 SCROLL
840 PRINT AT X,Y;
850 POKE P,B-J*12
860 IF PEEK (PEEK Q+256*PEEK (Q+I))<>M THEN GOTO 700
870 PRINT AT Z,Z;"SCORE --> ";SC
880 POKE P,Z
890 STOP
2000 SAVE "GAME%S"
2010 RUN
1 REM \DB\1D\2F\E6\0C\06\00\4F\C9
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 Y<T OR Y>T+I OR X<B-2 OR X>B-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 \DB\1D\2F\E6\1F\06\00\4F\C9
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
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.
People
No people associated with this content.
F\C9
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
\n1000 PRINT AT Z,Z;SC
\n1010 STOP
\n2000 SAVE "GAME%S"
\n2010 RUN
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.



