Zebra Joystick Games

Date: 198x
Type: Cassette
Platform(s): TS 1000
Tags: Game

This listing contains three separate ZX81/TS1000 BASIC game compilations, each offering a two-game menu. The first compilation presents “Invaders” and “Road Race”; the second offers “H-Fighter” and “Squish”; the third provides “Painter” and “Cobra.” All three programs share a common set of numeric constant idioms, using expressions like `P/P` for 1 and `I-I` for 0 to avoid storing literal small integers. Each program embeds a short machine code routine in the REM statement at line 1, with the bytes `\DB 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

Zebra Joystick Games

Date: 198x
Type: Cassette
Platform(s): TS 1000
Tags: Game

This listing contains three separate ZX81/TS1000 BASIC game compilations, each offering a two-game menu. The first compilation presents “Invaders” and “Road Race”; the second offers “H-Fighter” and “Squish”; the third provides “Painter” and “Cobra.” All three programs share a common set of numeric constant idioms, using expressions like `P/P` for 1 and `I-I` for 0 to avoid storing literal small integers. Each program embeds a short machine code routine in the REM statement at line 1, with the bytes `\DB\1D\2F\E6\0C\06\00\4F\C9` (or a variant) forming a Z80 routine called via `USR R` (where R=16514, the address just past the REM opcode) to read joystick or keyboard input. The POKE to address 16518 (CDFLAG) switches the display between FAST and SLOW modes, and address 9000 (P) is used for sound or display effects via direct memory writes.


Program Analysis

Overall Structure

The listing comprises three independent two-game compilation programs. Each follows the same architectural pattern: a menu at lines 10–170, game one starting around line 200, and game two starting around line 600. Line 2000 saves the program and line 2010 re-runs it. All three share identical or near-identical constant-encoding idioms and the same machine code joystick/keyboard driver embedded in the REM at line 1.

Machine Code Routine in REM

The REM statement at line 1 contains Z80 machine code bytes. The predominant sequence is \DB\1D\2F\E6\0C\06\00\4F\C9, and the third program uses \DB\1D\2F\E6\1F\06\00\4F\C9. Disassembled:

BytesMnemonicEffect
DB 1DIN A,(0x1D)Read port 0x1D (joystick/keyboard)
2FCPLComplement accumulator
E6 0CAND 0x0CMask bits (programs 1 & 2); E6 1F masks all 5 bits (program 3)
06 00LD B,0Zero high byte of return value
4FLD C,AMove result to C (BC = return value)
C9RETReturn 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

AddressVariablePurpose
9000PPOKE target for sound/display effects
16398QD-FILE system variable (display file pointer)
16514REntry point of machine code in REM
16518CDFLAG — 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

Appears On

Related Products

Connect Atari compatible joystick to the ZX81/TS 1000. Plugs into back of computer; expansion connector for other peripherals. Instructions for...

Related Articles

Related Content

Image Gallery

Zebra Joystick Games

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.

Scroll to Top
F\E6

Zebra Joystick Games

Date: 198x
Type: Cassette
Platform(s): TS 1000
Tags: Game

This listing contains three separate ZX81/TS1000 BASIC game compilations, each offering a two-game menu. The first compilation presents “Invaders” and “Road Race”; the second offers “H-Fighter” and “Squish”; the third provides “Painter” and “Cobra.” All three programs share a common set of numeric constant idioms, using expressions like `P/P` for 1 and `I-I` for 0 to avoid storing literal small integers. Each program embeds a short machine code routine in the REM statement at line 1, with the bytes `\DB\1D\2F\E6\0C\06\00\4F\C9` (or a variant) forming a Z80 routine called via `USR R` (where R=16514, the address just past the REM opcode) to read joystick or keyboard input. The POKE to address 16518 (CDFLAG) switches the display between FAST and SLOW modes, and address 9000 (P) is used for sound or display effects via direct memory writes.


Program Analysis

Overall Structure

The listing comprises three independent two-game compilation programs. Each follows the same architectural pattern: a menu at lines 10–170, game one starting around line 200, and game two starting around line 600. Line 2000 saves the program and line 2010 re-runs it. All three share identical or near-identical constant-encoding idioms and the same machine code joystick/keyboard driver embedded in the REM at line 1.

Machine Code Routine in REM

The REM statement at line 1 contains Z80 machine code bytes. The predominant sequence is \DB\1D\2F\E6\0C\06\00\4F\C9, and the third program uses \DB\1D\2F\E6\1F\06\00\4F\C9. Disassembled:

BytesMnemonicEffect
DB 1DIN A,(0x1D)Read port 0x1D (joystick/keyboard)
2FCPLComplement accumulator
E6 0CAND 0x0CMask bits (programs 1 & 2); E6 1F masks all 5 bits (program 3)
06 00LD B,0Zero high byte of return value
4FLD C,AMove result to C (BC = return value)
C9RETReturn 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

AddressVariablePurpose
9000PPOKE target for sound/display effects
16398QD-FILE system variable (display file pointer)
16514REntry point of machine code in REM
16518CDFLAG — 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

Appears On

Related Products

Connect Atari compatible joystick to the ZX81/TS 1000. Plugs into back of computer; expansion connector for other peripherals. Instructions for...

Related Articles

Related Content

Image Gallery

Zebra Joystick Games

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.

Scroll to Top
C

Zebra Joystick Games

Date: 198x
Type: Cassette
Platform(s): TS 1000
Tags: Game

This listing contains three separate ZX81/TS1000 BASIC game compilations, each offering a two-game menu. The first compilation presents “Invaders” and “Road Race”; the second offers “H-Fighter” and “Squish”; the third provides “Painter” and “Cobra.” All three programs share a common set of numeric constant idioms, using expressions like `P/P` for 1 and `I-I` for 0 to avoid storing literal small integers. Each program embeds a short machine code routine in the REM statement at line 1, with the bytes `\DB\1D\2F\E6\0C\06\00\4F\C9` (or a variant) forming a Z80 routine called via `USR R` (where R=16514, the address just past the REM opcode) to read joystick or keyboard input. The POKE to address 16518 (CDFLAG) switches the display between FAST and SLOW modes, and address 9000 (P) is used for sound or display effects via direct memory writes.


Program Analysis

Overall Structure

The listing comprises three independent two-game compilation programs. Each follows the same architectural pattern: a menu at lines 10–170, game one starting around line 200, and game two starting around line 600. Line 2000 saves the program and line 2010 re-runs it. All three share identical or near-identical constant-encoding idioms and the same machine code joystick/keyboard driver embedded in the REM at line 1.

Machine Code Routine in REM

The REM statement at line 1 contains Z80 machine code bytes. The predominant sequence is \DB\1D\2F\E6\0C\06\00\4F\C9, and the third program uses \DB\1D\2F\E6\1F\06\00\4F\C9. Disassembled:

BytesMnemonicEffect
DB 1DIN A,(0x1D)Read port 0x1D (joystick/keyboard)
2FCPLComplement accumulator
E6 0CAND 0x0CMask bits (programs 1 & 2); E6 1F masks all 5 bits (program 3)
06 00LD B,0Zero high byte of return value
4FLD C,AMove result to C (BC = return value)
C9RETReturn 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

AddressVariablePurpose
9000PPOKE target for sound/display effects
16398QD-FILE system variable (display file pointer)
16514REntry point of machine code in REM
16518CDFLAG — 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

Appears On

Related Products

Connect Atari compatible joystick to the ZX81/TS 1000. Plugs into back of computer; expansion connector for other peripherals. Instructions for...

Related Articles

Related Content

Image Gallery

Zebra Joystick Games

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.

Scroll to Top
F\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 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

Zebra Joystick Games

Date: 198x
Type: Cassette
Platform(s): TS 1000
Tags: Game

This listing contains three separate ZX81/TS1000 BASIC game compilations, each offering a two-game menu. The first compilation presents “Invaders” and “Road Race”; the second offers “H-Fighter” and “Squish”; the third provides “Painter” and “Cobra.” All three programs share a common set of numeric constant idioms, using expressions like `P/P` for 1 and `I-I` for 0 to avoid storing literal small integers. Each program embeds a short machine code routine in the REM statement at line 1, with the bytes `\DB\1D\2F\E6\0C\06\00\4F\C9` (or a variant) forming a Z80 routine called via `USR R` (where R=16514, the address just past the REM opcode) to read joystick or keyboard input. The POKE to address 16518 (CDFLAG) switches the display between FAST and SLOW modes, and address 9000 (P) is used for sound or display effects via direct memory writes.


Program Analysis

Overall Structure

The listing comprises three independent two-game compilation programs. Each follows the same architectural pattern: a menu at lines 10–170, game one starting around line 200, and game two starting around line 600. Line 2000 saves the program and line 2010 re-runs it. All three share identical or near-identical constant-encoding idioms and the same machine code joystick/keyboard driver embedded in the REM at line 1.

Machine Code Routine in REM

The REM statement at line 1 contains Z80 machine code bytes. The predominant sequence is \DB\1D\2F\E6\0C\06\00\4F\C9, and the third program uses \DB\1D\2F\E6\1F\06\00\4F\C9. Disassembled:

BytesMnemonicEffect
DB 1DIN A,(0x1D)Read port 0x1D (joystick/keyboard)
2FCPLComplement accumulator
E6 0CAND 0x0CMask bits (programs 1 & 2); E6 1F masks all 5 bits (program 3)
06 00LD B,0Zero high byte of return value
4FLD C,AMove result to C (BC = return value)
C9RETReturn 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

AddressVariablePurpose
9000PPOKE target for sound/display effects
16398QD-FILE system variable (display file pointer)
16514REntry point of machine code in REM
16518CDFLAG — 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

Appears On

Related Products

Connect Atari compatible joystick to the ZX81/TS 1000. Plugs into back of computer; expansion connector for other peripherals. Instructions for...

Related Articles

Related Content

Image Gallery

Zebra Joystick Games

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.

Scroll to Top
F\E6

Zebra Joystick Games

Date: 198x
Type: Cassette
Platform(s): TS 1000
Tags: Game

This listing contains three separate ZX81/TS1000 BASIC game compilations, each offering a two-game menu. The first compilation presents “Invaders” and “Road Race”; the second offers “H-Fighter” and “Squish”; the third provides “Painter” and “Cobra.” All three programs share a common set of numeric constant idioms, using expressions like `P/P` for 1 and `I-I` for 0 to avoid storing literal small integers. Each program embeds a short machine code routine in the REM statement at line 1, with the bytes `\DB\1D\2F\E6\0C\06\00\4F\C9` (or a variant) forming a Z80 routine called via `USR R` (where R=16514, the address just past the REM opcode) to read joystick or keyboard input. The POKE to address 16518 (CDFLAG) switches the display between FAST and SLOW modes, and address 9000 (P) is used for sound or display effects via direct memory writes.


Program Analysis

Overall Structure

The listing comprises three independent two-game compilation programs. Each follows the same architectural pattern: a menu at lines 10–170, game one starting around line 200, and game two starting around line 600. Line 2000 saves the program and line 2010 re-runs it. All three share identical or near-identical constant-encoding idioms and the same machine code joystick/keyboard driver embedded in the REM at line 1.

Machine Code Routine in REM

The REM statement at line 1 contains Z80 machine code bytes. The predominant sequence is \DB\1D\2F\E6\0C\06\00\4F\C9, and the third program uses \DB\1D\2F\E6\1F\06\00\4F\C9. Disassembled:

BytesMnemonicEffect
DB 1DIN A,(0x1D)Read port 0x1D (joystick/keyboard)
2FCPLComplement accumulator
E6 0CAND 0x0CMask bits (programs 1 & 2); E6 1F masks all 5 bits (program 3)
06 00LD B,0Zero high byte of return value
4FLD C,AMove result to C (BC = return value)
C9RETReturn 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

AddressVariablePurpose
9000PPOKE target for sound/display effects
16398QD-FILE system variable (display file pointer)
16514REntry point of machine code in REM
16518CDFLAG — 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

Appears On

Related Products

Connect Atari compatible joystick to the ZX81/TS 1000. Plugs into back of computer; expansion connector for other peripherals. Instructions for...

Related Articles

Related Content

Image Gallery

Zebra Joystick Games

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.

Scroll to Top
C

Zebra Joystick Games

Date: 198x
Type: Cassette
Platform(s): TS 1000
Tags: Game

This listing contains three separate ZX81/TS1000 BASIC game compilations, each offering a two-game menu. The first compilation presents “Invaders” and “Road Race”; the second offers “H-Fighter” and “Squish”; the third provides “Painter” and “Cobra.” All three programs share a common set of numeric constant idioms, using expressions like `P/P` for 1 and `I-I` for 0 to avoid storing literal small integers. Each program embeds a short machine code routine in the REM statement at line 1, with the bytes `\DB\1D\2F\E6\0C\06\00\4F\C9` (or a variant) forming a Z80 routine called via `USR R` (where R=16514, the address just past the REM opcode) to read joystick or keyboard input. The POKE to address 16518 (CDFLAG) switches the display between FAST and SLOW modes, and address 9000 (P) is used for sound or display effects via direct memory writes.


Program Analysis

Overall Structure

The listing comprises three independent two-game compilation programs. Each follows the same architectural pattern: a menu at lines 10–170, game one starting around line 200, and game two starting around line 600. Line 2000 saves the program and line 2010 re-runs it. All three share identical or near-identical constant-encoding idioms and the same machine code joystick/keyboard driver embedded in the REM at line 1.

Machine Code Routine in REM

The REM statement at line 1 contains Z80 machine code bytes. The predominant sequence is \DB\1D\2F\E6\0C\06\00\4F\C9, and the third program uses \DB\1D\2F\E6\1F\06\00\4F\C9. Disassembled:

BytesMnemonicEffect
DB 1DIN A,(0x1D)Read port 0x1D (joystick/keyboard)
2FCPLComplement accumulator
E6 0CAND 0x0CMask bits (programs 1 & 2); E6 1F masks all 5 bits (program 3)
06 00LD B,0Zero high byte of return value
4FLD C,AMove result to C (BC = return value)
C9RETReturn 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

AddressVariablePurpose
9000PPOKE target for sound/display effects
16398QD-FILE system variable (display file pointer)
16514REntry point of machine code in REM
16518CDFLAG — 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

Appears On

Related Products

Connect Atari compatible joystick to the ZX81/TS 1000. Plugs into back of computer; expansion connector for other peripherals. Instructions for...

Related Articles

Related Content

Image Gallery

Zebra Joystick Games

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.

Scroll to Top
F\C9
, and the third program uses \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

Zebra Joystick Games

Date: 198x
Type: Cassette
Platform(s): TS 1000
Tags: Game

This listing contains three separate ZX81/TS1000 BASIC game compilations, each offering a two-game menu. The first compilation presents “Invaders” and “Road Race”; the second offers “H-Fighter” and “Squish”; the third provides “Painter” and “Cobra.” All three programs share a common set of numeric constant idioms, using expressions like `P/P` for 1 and `I-I` for 0 to avoid storing literal small integers. Each program embeds a short machine code routine in the REM statement at line 1, with the bytes `\DB\1D\2F\E6\0C\06\00\4F\C9` (or a variant) forming a Z80 routine called via `USR R` (where R=16514, the address just past the REM opcode) to read joystick or keyboard input. The POKE to address 16518 (CDFLAG) switches the display between FAST and SLOW modes, and address 9000 (P) is used for sound or display effects via direct memory writes.


Program Analysis

Overall Structure

The listing comprises three independent two-game compilation programs. Each follows the same architectural pattern: a menu at lines 10–170, game one starting around line 200, and game two starting around line 600. Line 2000 saves the program and line 2010 re-runs it. All three share identical or near-identical constant-encoding idioms and the same machine code joystick/keyboard driver embedded in the REM at line 1.

Machine Code Routine in REM

The REM statement at line 1 contains Z80 machine code bytes. The predominant sequence is \DB\1D\2F\E6\0C\06\00\4F\C9, and the third program uses \DB\1D\2F\E6\1F\06\00\4F\C9. Disassembled:

BytesMnemonicEffect
DB 1DIN A,(0x1D)Read port 0x1D (joystick/keyboard)
2FCPLComplement accumulator
E6 0CAND 0x0CMask bits (programs 1 & 2); E6 1F masks all 5 bits (program 3)
06 00LD B,0Zero high byte of return value
4FLD C,AMove result to C (BC = return value)
C9RETReturn 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

AddressVariablePurpose
9000PPOKE target for sound/display effects
16398QD-FILE system variable (display file pointer)
16514REntry point of machine code in REM
16518CDFLAG — 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

Appears On

Related Products

Connect Atari compatible joystick to the ZX81/TS 1000. Plugs into back of computer; expansion connector for other peripherals. Instructions for...

Related Articles

Related Content

Image Gallery

Zebra Joystick Games

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.

Scroll to Top
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

Zebra Joystick Games

Date: 198x
Type: Cassette
Platform(s): TS 1000
Tags: Game

This listing contains three separate ZX81/TS1000 BASIC game compilations, each offering a two-game menu. The first compilation presents “Invaders” and “Road Race”; the second offers “H-Fighter” and “Squish”; the third provides “Painter” and “Cobra.” All three programs share a common set of numeric constant idioms, using expressions like `P/P` for 1 and `I-I` for 0 to avoid storing literal small integers. Each program embeds a short machine code routine in the REM statement at line 1, with the bytes `\DB\1D\2F\E6\0C\06\00\4F\C9` (or a variant) forming a Z80 routine called via `USR R` (where R=16514, the address just past the REM opcode) to read joystick or keyboard input. The POKE to address 16518 (CDFLAG) switches the display between FAST and SLOW modes, and address 9000 (P) is used for sound or display effects via direct memory writes.


Program Analysis

Overall Structure

The listing comprises three independent two-game compilation programs. Each follows the same architectural pattern: a menu at lines 10–170, game one starting around line 200, and game two starting around line 600. Line 2000 saves the program and line 2010 re-runs it. All three share identical or near-identical constant-encoding idioms and the same machine code joystick/keyboard driver embedded in the REM at line 1.

Machine Code Routine in REM

The REM statement at line 1 contains Z80 machine code bytes. The predominant sequence is \DB\1D\2F\E6\0C\06\00\4F\C9, and the third program uses \DB\1D\2F\E6\1F\06\00\4F\C9. Disassembled:

BytesMnemonicEffect
DB 1DIN A,(0x1D)Read port 0x1D (joystick/keyboard)
2FCPLComplement accumulator
E6 0CAND 0x0CMask bits (programs 1 & 2); E6 1F masks all 5 bits (program 3)
06 00LD B,0Zero high byte of return value
4FLD C,AMove result to C (BC = return value)
C9RETReturn 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

AddressVariablePurpose
9000PPOKE target for sound/display effects
16398QD-FILE system variable (display file pointer)
16514REntry point of machine code in REM
16518CDFLAG — 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

Appears On

Related Products

Connect Atari compatible joystick to the ZX81/TS 1000. Plugs into back of computer; expansion connector for other peripherals. Instructions for...

Related Articles

Related Content

Image Gallery

Zebra Joystick Games

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.

Scroll to Top
F\C9
. Disassembled:

BytesMnemonicEffect
DB 1DIN A,(0x1D)Read port 0x1D (joystick/keyboard)
2FCPLComplement accumulator
E6 0CAND 0x0CMask bits (programs 1 & 2); E6 1F masks all 5 bits (program 3)
06 00LD B,0Zero high byte of return value
4FLD C,AMove result to C (BC = return value)
C9RETReturn 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

AddressVariablePurpose
9000PPOKE target for sound/display effects
16398QD-FILE system variable (display file pointer)
16514REntry point of machine code in REM
16518CDFLAG — 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

Appears On

Related Products

Connect Atari compatible joystick to the ZX81/TS 1000. Plugs into back of computer; expansion connector for other peripherals. Instructions for...

Related Articles

Related Content

Image Gallery

Zebra Joystick Games

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



Zebra Joystick Games

Date: 198x
Type: Cassette
Platform(s): TS 1000
Tags: Game

This listing contains three separate ZX81/TS1000 BASIC game compilations, each offering a two-game menu. The first compilation presents “Invaders” and “Road Race”; the second offers “H-Fighter” and “Squish”; the third provides “Painter” and “Cobra.” All three programs share a common set of numeric constant idioms, using expressions like `P/P` for 1 and `I-I` for 0 to avoid storing literal small integers. Each program embeds a short machine code routine in the REM statement at line 1, with the bytes `\DB\1D\2F\E6\0C\06\00\4F\C9` (or a variant) forming a Z80 routine called via `USR R` (where R=16514, the address just past the REM opcode) to read joystick or keyboard input. The POKE to address 16518 (CDFLAG) switches the display between FAST and SLOW modes, and address 9000 (P) is used for sound or display effects via direct memory writes.


Program Analysis

Overall Structure

The listing comprises three independent two-game compilation programs. Each follows the same architectural pattern: a menu at lines 10–170, game one starting around line 200, and game two starting around line 600. Line 2000 saves the program and line 2010 re-runs it. All three share identical or near-identical constant-encoding idioms and the same machine code joystick/keyboard driver embedded in the REM at line 1.

Machine Code Routine in REM

The REM statement at line 1 contains Z80 machine code bytes. The predominant sequence is \DB\1D\2F\E6\0C\06\00\4F\C9, and the third program uses \DB\1D\2F\E6\1F\06\00\4F\C9. Disassembled:

BytesMnemonicEffect
DB 1DIN A,(0x1D)Read port 0x1D (joystick/keyboard)
2FCPLComplement accumulator
E6 0CAND 0x0CMask bits (programs 1 & 2); E6 1F masks all 5 bits (program 3)
06 00LD B,0Zero high byte of return value
4FLD C,AMove result to C (BC = return value)
C9RETReturn 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

AddressVariablePurpose
9000PPOKE target for sound/display effects
16398QD-FILE system variable (display file pointer)
16514REntry point of machine code in REM
16518CDFLAG — 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

Appears On

Related Products

Connect Atari compatible joystick to the ZX81/TS 1000. Plugs into back of computer; expansion connector for other peripherals. Instructions for...

Related Articles

Related Content

Image Gallery

Zebra Joystick Games

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.

Scroll to Top
F\E6

Zebra Joystick Games

Date: 198x
Type: Cassette
Platform(s): TS 1000
Tags: Game

This listing contains three separate ZX81/TS1000 BASIC game compilations, each offering a two-game menu. The first compilation presents “Invaders” and “Road Race”; the second offers “H-Fighter” and “Squish”; the third provides “Painter” and “Cobra.” All three programs share a common set of numeric constant idioms, using expressions like `P/P` for 1 and `I-I` for 0 to avoid storing literal small integers. Each program embeds a short machine code routine in the REM statement at line 1, with the bytes `\DB\1D\2F\E6\0C\06\00\4F\C9` (or a variant) forming a Z80 routine called via `USR R` (where R=16514, the address just past the REM opcode) to read joystick or keyboard input. The POKE to address 16518 (CDFLAG) switches the display between FAST and SLOW modes, and address 9000 (P) is used for sound or display effects via direct memory writes.


Program Analysis

Overall Structure

The listing comprises three independent two-game compilation programs. Each follows the same architectural pattern: a menu at lines 10–170, game one starting around line 200, and game two starting around line 600. Line 2000 saves the program and line 2010 re-runs it. All three share identical or near-identical constant-encoding idioms and the same machine code joystick/keyboard driver embedded in the REM at line 1.

Machine Code Routine in REM

The REM statement at line 1 contains Z80 machine code bytes. The predominant sequence is \DB\1D\2F\E6\0C\06\00\4F\C9, and the third program uses \DB\1D\2F\E6\1F\06\00\4F\C9. Disassembled:

BytesMnemonicEffect
DB 1DIN A,(0x1D)Read port 0x1D (joystick/keyboard)
2FCPLComplement accumulator
E6 0CAND 0x0CMask bits (programs 1 & 2); E6 1F masks all 5 bits (program 3)
06 00LD B,0Zero high byte of return value
4FLD C,AMove result to C (BC = return value)
C9RETReturn 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

AddressVariablePurpose
9000PPOKE target for sound/display effects
16398QD-FILE system variable (display file pointer)
16514REntry point of machine code in REM
16518CDFLAG — 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

Appears On

Related Products

Connect Atari compatible joystick to the ZX81/TS 1000. Plugs into back of computer; expansion connector for other peripherals. Instructions for...

Related Articles

Related Content

Image Gallery

Zebra Joystick Games

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.

Scroll to Top
C

Zebra Joystick Games

Date: 198x
Type: Cassette
Platform(s): TS 1000
Tags: Game

This listing contains three separate ZX81/TS1000 BASIC game compilations, each offering a two-game menu. The first compilation presents “Invaders” and “Road Race”; the second offers “H-Fighter” and “Squish”; the third provides “Painter” and “Cobra.” All three programs share a common set of numeric constant idioms, using expressions like `P/P` for 1 and `I-I` for 0 to avoid storing literal small integers. Each program embeds a short machine code routine in the REM statement at line 1, with the bytes `\DB\1D\2F\E6\0C\06\00\4F\C9` (or a variant) forming a Z80 routine called via `USR R` (where R=16514, the address just past the REM opcode) to read joystick or keyboard input. The POKE to address 16518 (CDFLAG) switches the display between FAST and SLOW modes, and address 9000 (P) is used for sound or display effects via direct memory writes.


Program Analysis

Overall Structure

The listing comprises three independent two-game compilation programs. Each follows the same architectural pattern: a menu at lines 10–170, game one starting around line 200, and game two starting around line 600. Line 2000 saves the program and line 2010 re-runs it. All three share identical or near-identical constant-encoding idioms and the same machine code joystick/keyboard driver embedded in the REM at line 1.

Machine Code Routine in REM

The REM statement at line 1 contains Z80 machine code bytes. The predominant sequence is \DB\1D\2F\E6\0C\06\00\4F\C9, and the third program uses \DB\1D\2F\E6\1F\06\00\4F\C9. Disassembled:

BytesMnemonicEffect
DB 1DIN A,(0x1D)Read port 0x1D (joystick/keyboard)
2FCPLComplement accumulator
E6 0CAND 0x0CMask bits (programs 1 & 2); E6 1F masks all 5 bits (program 3)
06 00LD B,0Zero high byte of return value
4FLD C,AMove result to C (BC = return value)
C9RETReturn 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

AddressVariablePurpose
9000PPOKE target for sound/display effects
16398QD-FILE system variable (display file pointer)
16514REntry point of machine code in REM
16518CDFLAG — 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

Appears On

Related Products

Connect Atari compatible joystick to the ZX81/TS 1000. Plugs into back of computer; expansion connector for other peripherals. Instructions for...

Related Articles

Related Content

Image Gallery

Zebra Joystick Games

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.

Scroll to Top
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

Zebra Joystick Games

Date: 198x
Type: Cassette
Platform(s): TS 1000
Tags: Game

This listing contains three separate ZX81/TS1000 BASIC game compilations, each offering a two-game menu. The first compilation presents “Invaders” and “Road Race”; the second offers “H-Fighter” and “Squish”; the third provides “Painter” and “Cobra.” All three programs share a common set of numeric constant idioms, using expressions like `P/P` for 1 and `I-I` for 0 to avoid storing literal small integers. Each program embeds a short machine code routine in the REM statement at line 1, with the bytes `\DB\1D\2F\E6\0C\06\00\4F\C9` (or a variant) forming a Z80 routine called via `USR R` (where R=16514, the address just past the REM opcode) to read joystick or keyboard input. The POKE to address 16518 (CDFLAG) switches the display between FAST and SLOW modes, and address 9000 (P) is used for sound or display effects via direct memory writes.


Program Analysis

Overall Structure

The listing comprises three independent two-game compilation programs. Each follows the same architectural pattern: a menu at lines 10–170, game one starting around line 200, and game two starting around line 600. Line 2000 saves the program and line 2010 re-runs it. All three share identical or near-identical constant-encoding idioms and the same machine code joystick/keyboard driver embedded in the REM at line 1.

Machine Code Routine in REM

The REM statement at line 1 contains Z80 machine code bytes. The predominant sequence is \DB\1D\2F\E6\0C\06\00\4F\C9, and the third program uses \DB\1D\2F\E6\1F\06\00\4F\C9. Disassembled:

BytesMnemonicEffect
DB 1DIN A,(0x1D)Read port 0x1D (joystick/keyboard)
2FCPLComplement accumulator
E6 0CAND 0x0CMask bits (programs 1 & 2); E6 1F masks all 5 bits (program 3)
06 00LD B,0Zero high byte of return value
4FLD C,AMove result to C (BC = return value)
C9RETReturn 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

AddressVariablePurpose
9000PPOKE target for sound/display effects
16398QD-FILE system variable (display file pointer)
16514REntry point of machine code in REM
16518CDFLAG — 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

Appears On

Related Products

Connect Atari compatible joystick to the ZX81/TS 1000. Plugs into back of computer; expansion connector for other peripherals. Instructions for...

Related Articles

Related Content

Image Gallery

Zebra Joystick Games

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.

Scroll to Top
F\E6

Zebra Joystick Games

Date: 198x
Type: Cassette
Platform(s): TS 1000
Tags: Game

This listing contains three separate ZX81/TS1000 BASIC game compilations, each offering a two-game menu. The first compilation presents “Invaders” and “Road Race”; the second offers “H-Fighter” and “Squish”; the third provides “Painter” and “Cobra.” All three programs share a common set of numeric constant idioms, using expressions like `P/P` for 1 and `I-I` for 0 to avoid storing literal small integers. Each program embeds a short machine code routine in the REM statement at line 1, with the bytes `\DB\1D\2F\E6\0C\06\00\4F\C9` (or a variant) forming a Z80 routine called via `USR R` (where R=16514, the address just past the REM opcode) to read joystick or keyboard input. The POKE to address 16518 (CDFLAG) switches the display between FAST and SLOW modes, and address 9000 (P) is used for sound or display effects via direct memory writes.


Program Analysis

Overall Structure

The listing comprises three independent two-game compilation programs. Each follows the same architectural pattern: a menu at lines 10–170, game one starting around line 200, and game two starting around line 600. Line 2000 saves the program and line 2010 re-runs it. All three share identical or near-identical constant-encoding idioms and the same machine code joystick/keyboard driver embedded in the REM at line 1.

Machine Code Routine in REM

The REM statement at line 1 contains Z80 machine code bytes. The predominant sequence is \DB\1D\2F\E6\0C\06\00\4F\C9, and the third program uses \DB\1D\2F\E6\1F\06\00\4F\C9. Disassembled:

BytesMnemonicEffect
DB 1DIN A,(0x1D)Read port 0x1D (joystick/keyboard)
2FCPLComplement accumulator
E6 0CAND 0x0CMask bits (programs 1 & 2); E6 1F masks all 5 bits (program 3)
06 00LD B,0Zero high byte of return value
4FLD C,AMove result to C (BC = return value)
C9RETReturn 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

AddressVariablePurpose
9000PPOKE target for sound/display effects
16398QD-FILE system variable (display file pointer)
16514REntry point of machine code in REM
16518CDFLAG — 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

Appears On

Related Products

Connect Atari compatible joystick to the ZX81/TS 1000. Plugs into back of computer; expansion connector for other peripherals. Instructions for...

Related Articles

Related Content

Image Gallery

Zebra Joystick Games

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.

Scroll to Top
C

Zebra Joystick Games

Date: 198x
Type: Cassette
Platform(s): TS 1000
Tags: Game

This listing contains three separate ZX81/TS1000 BASIC game compilations, each offering a two-game menu. The first compilation presents “Invaders” and “Road Race”; the second offers “H-Fighter” and “Squish”; the third provides “Painter” and “Cobra.” All three programs share a common set of numeric constant idioms, using expressions like `P/P` for 1 and `I-I` for 0 to avoid storing literal small integers. Each program embeds a short machine code routine in the REM statement at line 1, with the bytes `\DB\1D\2F\E6\0C\06\00\4F\C9` (or a variant) forming a Z80 routine called via `USR R` (where R=16514, the address just past the REM opcode) to read joystick or keyboard input. The POKE to address 16518 (CDFLAG) switches the display between FAST and SLOW modes, and address 9000 (P) is used for sound or display effects via direct memory writes.


Program Analysis

Overall Structure

The listing comprises three independent two-game compilation programs. Each follows the same architectural pattern: a menu at lines 10–170, game one starting around line 200, and game two starting around line 600. Line 2000 saves the program and line 2010 re-runs it. All three share identical or near-identical constant-encoding idioms and the same machine code joystick/keyboard driver embedded in the REM at line 1.

Machine Code Routine in REM

The REM statement at line 1 contains Z80 machine code bytes. The predominant sequence is \DB\1D\2F\E6\0C\06\00\4F\C9, and the third program uses \DB\1D\2F\E6\1F\06\00\4F\C9. Disassembled:

BytesMnemonicEffect
DB 1DIN A,(0x1D)Read port 0x1D (joystick/keyboard)
2FCPLComplement accumulator
E6 0CAND 0x0CMask bits (programs 1 & 2); E6 1F masks all 5 bits (program 3)
06 00LD B,0Zero high byte of return value
4FLD C,AMove result to C (BC = return value)
C9RETReturn 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

AddressVariablePurpose
9000PPOKE target for sound/display effects
16398QD-FILE system variable (display file pointer)
16514REntry point of machine code in REM
16518CDFLAG — 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

Appears On

Related Products

Connect Atari compatible joystick to the ZX81/TS 1000. Plugs into back of computer; expansion connector for other peripherals. Instructions for...

Related Articles

Related Content

Image Gallery

Zebra Joystick Games

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.

Scroll to Top
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

Zebra Joystick Games

Date: 198x
Type: Cassette
Platform(s): TS 1000
Tags: Game

This listing contains three separate ZX81/TS1000 BASIC game compilations, each offering a two-game menu. The first compilation presents “Invaders” and “Road Race”; the second offers “H-Fighter” and “Squish”; the third provides “Painter” and “Cobra.” All three programs share a common set of numeric constant idioms, using expressions like `P/P` for 1 and `I-I` for 0 to avoid storing literal small integers. Each program embeds a short machine code routine in the REM statement at line 1, with the bytes `\DB\1D\2F\E6\0C\06\00\4F\C9` (or a variant) forming a Z80 routine called via `USR R` (where R=16514, the address just past the REM opcode) to read joystick or keyboard input. The POKE to address 16518 (CDFLAG) switches the display between FAST and SLOW modes, and address 9000 (P) is used for sound or display effects via direct memory writes.


Program Analysis

Overall Structure

The listing comprises three independent two-game compilation programs. Each follows the same architectural pattern: a menu at lines 10–170, game one starting around line 200, and game two starting around line 600. Line 2000 saves the program and line 2010 re-runs it. All three share identical or near-identical constant-encoding idioms and the same machine code joystick/keyboard driver embedded in the REM at line 1.

Machine Code Routine in REM

The REM statement at line 1 contains Z80 machine code bytes. The predominant sequence is \DB\1D\2F\E6\0C\06\00\4F\C9, and the third program uses \DB\1D\2F\E6\1F\06\00\4F\C9. Disassembled:

BytesMnemonicEffect
DB 1DIN A,(0x1D)Read port 0x1D (joystick/keyboard)
2FCPLComplement accumulator
E6 0CAND 0x0CMask bits (programs 1 & 2); E6 1F masks all 5 bits (program 3)
06 00LD B,0Zero high byte of return value
4FLD C,AMove result to C (BC = return value)
C9RETReturn 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

AddressVariablePurpose
9000PPOKE target for sound/display effects
16398QD-FILE system variable (display file pointer)
16514REntry point of machine code in REM
16518CDFLAG — 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

Appears On

Related Products

Connect Atari compatible joystick to the ZX81/TS 1000. Plugs into back of computer; expansion connector for other peripherals. Instructions for...

Related Articles

Related Content

Image Gallery

Zebra Joystick Games

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.

Scroll to Top
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

Zebra Joystick Games

Date: 198x
Type: Cassette
Platform(s): TS 1000
Tags: Game

This listing contains three separate ZX81/TS1000 BASIC game compilations, each offering a two-game menu. The first compilation presents “Invaders” and “Road Race”; the second offers “H-Fighter” and “Squish”; the third provides “Painter” and “Cobra.” All three programs share a common set of numeric constant idioms, using expressions like `P/P` for 1 and `I-I` for 0 to avoid storing literal small integers. Each program embeds a short machine code routine in the REM statement at line 1, with the bytes `\DB\1D\2F\E6\0C\06\00\4F\C9` (or a variant) forming a Z80 routine called via `USR R` (where R=16514, the address just past the REM opcode) to read joystick or keyboard input. The POKE to address 16518 (CDFLAG) switches the display between FAST and SLOW modes, and address 9000 (P) is used for sound or display effects via direct memory writes.


Program Analysis

Overall Structure

The listing comprises three independent two-game compilation programs. Each follows the same architectural pattern: a menu at lines 10–170, game one starting around line 200, and game two starting around line 600. Line 2000 saves the program and line 2010 re-runs it. All three share identical or near-identical constant-encoding idioms and the same machine code joystick/keyboard driver embedded in the REM at line 1.

Machine Code Routine in REM

The REM statement at line 1 contains Z80 machine code bytes. The predominant sequence is \DB\1D\2F\E6\0C\06\00\4F\C9, and the third program uses \DB\1D\2F\E6\1F\06\00\4F\C9. Disassembled:

BytesMnemonicEffect
DB 1DIN A,(0x1D)Read port 0x1D (joystick/keyboard)
2FCPLComplement accumulator
E6 0CAND 0x0CMask bits (programs 1 & 2); E6 1F masks all 5 bits (program 3)
06 00LD B,0Zero high byte of return value
4FLD C,AMove result to C (BC = return value)
C9RETReturn 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

AddressVariablePurpose
9000PPOKE target for sound/display effects
16398QD-FILE system variable (display file pointer)
16514REntry point of machine code in REM
16518CDFLAG — 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

Appears On

Related Products

Connect Atari compatible joystick to the ZX81/TS 1000. Plugs into back of computer; expansion connector for other peripherals. Instructions for...

Related Articles

Related Content

Image Gallery

Zebra Joystick Games

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.

Scroll to Top
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.

People

No people associated with this content.

Scroll to Top