This is a Space Invaders game for the ZX81/TS1000, written by Dave Edwards, that relies heavily on a large Z80 machine code routine embedded in line 0’s REM statement. The machine code (loaded at address 16516 and called via RAND USR) handles the main game engine including alien movement, collision detection, and rendering, while the BASIC layer manages scoring, lives, difficulty levels, and the AY/beeper sound effects generated through direct port manipulation via POKE to addresses 32767 (A) and 32766 (D). Three difficulty levels are offered (Beginner, Advanced, Expert), each affecting alien speed and the number of waves before the game ends. The score display uses inverse-video digit characters constructed by adding 128 to character codes, and the high-score name entry with persistent storage across playthroughs is handled entirely in BASIC. Sound generation uses a noteworthy technique: subroutine at line 850 drives an I/O port in a tight FOR-NEXT timing loop, reading frequency and duration data from the string arrays C$, F$, and L$.
Program Analysis
Program Structure
The program is divided into distinct functional regions:
- Line 0: A large REM statement containing the entire Z80 machine code game engine (approximately 800+ bytes of hex-encoded opcodes).
- Lines 1–5: Title/author REMs, a decorative inverse-video banner line, a
GOTO 10bypass, and aSAVEline. - Lines 10–95: Initialisation — USR call to machine code setup, variable initialisation, difficulty level selection, and POKE-based configuration of machine code parameters.
- Lines 100–278: Main game loop — wave management, score display, USR calls into the machine code engine, and result dispatching.
- Lines 280–590: End-game handling — base destroyed, planet invaded, score extraction, high-score comparison, and name entry.
- Lines 600–799: Sound and utility subroutines, including the tone-generation engine and level-specific configuration POKEs.
- Lines 800–828: Machine code parameter patch subroutines (modifying bytes within the REM block at runtime).
- Lines 830–990: Port-driven sound player subroutine using string tables.
- Lines 991–996: Score extraction from machine code memory.
- Lines 999–1010: REM byte-count label, SAVE, and RUN.
Machine Code Engine
The program stores its entire Z80 machine code payload in the REM statement at line 0. This is the standard ZX81/TS1000 technique for embedding machine code: the REM body begins at a known offset from the start of BASIC, and RAND USR 17374 (line 10) jumps directly into it. The engine handles the core game loop: alien grid movement, player cannon control, missile/bomb physics, collision detection, and display rendering — tasks that would be impossibly slow in interpreted BASIC at arcade speed.
A second entry point at USR 16516 (line 130) initialises the game field, and USR 16622 (line 170) advances alien movement by one step. The return value from USR 17640 (line 200) encodes the game state: 1 = base hit, 2 = invasion, other = still playing.
Runtime Machine Code Patching
Several subroutines (lines 800–828) modify bytes inside the REM block at runtime using POKE to addresses in the 17000–17700 range, effectively patching machine code instructions or data tables to alter game behaviour. This is used to switch between game modes (e.g., toggling between two firing patterns or speed tables). For example, line 820 POKEs address 17677 with 230 (Z80 opcode AND) and address 17678 with 112, overwriting a previously patched instruction. Lines 800–818 patch complementary code paths for different fire rates.
Sound Generation
The sound subroutine at lines 830–990 drives I/O ports directly using two fixed addresses stored in variables A (32767) and D (32766). Frequency data is encoded as character codes in the string F$, timing/duration data in L$, and a secondary channel parameter in C$. For each note step N from P to Q, the subroutine:
- POKEs port A with 7 (select register), port D with 255 (silence all channels)
- POKEs register 4 with
CODE F$(N)(tone frequency) - POKEs register 5 with
CODE C$(N)(second channel data) - POKEs register 7 with 251 (enable output)
- Executes a
FOR/NEXTdelay loop timed byCODE L$(N)-2
This is consistent with driving an AY-3-8910 or compatible sound chip via I/O ports, as found on the TS2068 and certain ZX81 sound expansions.
Difficulty and Wave System
The variable L (1–3) controls difficulty. It is written to address 17223 via POKE for the machine code to use, and also affects the BASIC-side wave counter: the game advances to the next wave when S equals 5-L (so Expert requires only 2 waves per life, Beginner 4). Alien speed is configured at line 90 by POKEing 65+(4-L)*25 to address 17626, giving values 90, 65, or 40 for levels 1–3 respectively. The cannon starting X-position is set at line 110 as 67+33*L.
Score Extraction
The score is not maintained in a BASIC variable during play but is stored inside machine code memory. The subroutine at lines 991–996 extracts it by reading four consecutive bytes relative to a pointer held at addresses 16396–16397 (the BASIC variable area pointer), subtracting 156 from each byte and weighting them as powers of 10. This implies the machine code stores the score as four BCD-like digits packed into display character codes (156 being an offset into the character set).
High Score Display
The high score display string H$ is initialised to five inverse-zero characters ("%0%0%0%0%0"). When a new high score is set, the score is converted via STR$ and each digit character code has 128 added (lines 540), converting it to an inverse-video digit for display in the header line. The player’s name N$ is stored in a BASIC variable and persists for the session.
Key BASIC Idioms
POKE A,x/POKE D,y— using numeric variables for frequently-used port addresses avoids re-parsing literal numbers in the hot sound loop.X AND X>0(line 190) — the classic ZX81 idiom forMAX(X,0): ifX>0is true (=1), result is X; if false (=0), result is 0.2**(S+1)*2**(L+1)(line 180) — uses ZX81’s**(exponentiation) to compute scaling factors for the alien speed threshold.RAND USR— used throughout instead of plainUSRto discard the return value (RAND ignores its argument when it is an integer result of USR).10**(N+1)(line 994) — uses exponentiation for place-value weighting during score extraction.
String Tables for Music/Sound
| Variable | Role | Encoding |
|---|---|---|
C$ | Channel 2 tone / noise data | Block graphic characters, values read via CODE |
F$ | Frequency register values | Mixed printable and token characters |
L$ | Note duration (loop count = CODE-2) | Block graphic / symbol characters |
Notable Anomalies
- Line 14 (
F$) and line 15 (L$) contain BASIC keyword tokens (e.g.,RND,CHR$,IF) embedded as string literals. On the ZX81/TS1000, these are stored as single-byte token codes in the string data and are read back withCODE, yielding values in the 192–255 range — used intentionally as frequency and duration data. - The subroutine at line 790 shares its first two lines (790–791) with the level-configuration POKEs that fall through from line 95 (
GOSUB 790+10*L): for L=1, entry is at 800; for L=2, at 810; for L=3, at 820. Line 790 itself is also the tail of the wave-configuration block (lines 790–799). This means the GOSUB dispatch cleverly reuses code for both purposes. - The delay loop at line 600 (
FOR N=1 TO 30 / NEXT N) is a simple busy-wait pause with no display or sound activity — used as a brief pause after end-game messages.
Content
Source Code
0 REM 76762A C401122 019 6203E8A772310FC3E80 E15 620237710FC23 D20F6 6203E 3237710FC1130 0ED5236A6223C401EB2ED52 6 31E 5AF36 723772377237723772336841910F01D3E 2 E 3232323 6 6722310FC19 D20F63D20EE 6 3232323771923771910F8C92A C4011A6 019223E4011 7 03EBC4B 6 D77232310FB19 D20F53E5B3240403E8032414021 1 0224840C9E521FF7F36 72B7E 02336 F2B562336 72B 02A3C40 1FEEFED78 0 1 0 0CB47CA1842CB5F28 8CB67C011FFFF18 6CB67C811 1 03680197EFE7620 2ED52223C4036A6FE97C0C3D54321FF7F36 62B361F2336 92B36 F2336 72B36C93A4140CB7F28 BCDAF41ED5B4840CDCF41C9CDAF411121 0CDCF412148407EEEFE77237EEEFF773A4140CBFF 0324140FE81C0 E 2C92A3E40119448 E 7 6 D7EFEBC20 236801223231310F3 6 72310FD D20E9C92A3E4019223E40119448 E 7 6 D1AFEBC2021CD92457EFE9BCCA643237E2BFE7628 72B7E23FE7620 83A4140CBBF32414036BC23231310D5 6 72310FD D20CB21FF7F36 72B74C9ED5B4240CB72C011DFFF197EFE97C8FEBCCA3043369B224240CD6A45C9 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 02A4440CB74C02A3C4011DFFF 1 013197EFEBC28 310F8C9 6 0ED527EFE9BCCA643FE8028 33680C93697224440C9 0 0 02A4240CB74C811DFFF3680197EFEBCCA3043FE94CA5843FE8ACAA643FEC3CCC143FE8028 53680C3A643369B224240C9 0 02A4440CB74C81121 03680197EFEA6CAD543FE 3CAC143FE9BCCA643FE8028 53680C3C1433697224440C9 0 0 0 0 0 0 02A4640CB74C02A C401143 0193A3440CB4720 51E1F19CBFC3694224640C9 0 0 0 0 0 0 0 0 02A4640CB74C83680CB7C28 22B2B237EFE7620 626 0224640C9FE9BCA58433694224640C9 0 0 0368021FF7F36 C2B36 A2336 72B36FE2336 D2B36 9 E 3CD8A4321404035C0 E 3C9 0 0 0 0 0368021FF7F36 02B36322336 62B36 02336 C2B36192336 72B36F62336 D2B36 32A344026 07EE6 FC6 54F21474036 02A C4011 F 01959414A347EFEA620 3369C C10F52B79FE 020ED43227B402A42407EFE9B20 2368021 0 02242402A7B403E80C9 0 0227B4021 0 02244402A7B403E80C9 0 0 0 0 0 1 1 03680C9 0 0 021FF7FEB21FE7F3E 11236 03C1236 03C1236 A3C1236 03C1236 F3C3C1236FF3C1236103C3C1236 F3C1236 021 01EE511 084 1 0 2EDB0E1 EA0EDB0E5217044 E 8EDB0E1 E 8 9 E10EDB0E5217844 E 8EDB0E1 E 8 9 E18EDB0E5218044 E 8EDB0E1 E 8 9 E50EDB0E5218844 E 8EDB0E1 E 8 9 EA8EDB0E5219044 E 8EDB0E1 E 8 9 E18EDB0C9 0 0 0 03C7EA57E3C 0 0243C3C1818 0 0 0 018181818 0 0 0183C7E7E5A42 0 042247EDBFFA599 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 02A4A4078FE1530 75816 019224A40 6 079FE 028 3E6FCC8 65ACD1E46 010FAC3E844 0 0 0 0 1 0 0214C40347E21C044E521AE42 0CB47C27C42 0CB4FC21A41CB575FC4 8433A404057E670CB3FCB3FCB3FCB3FC6 24F7AFE 5DA654151 E 03E 2CB5320193CCB5B20143CCB6320 F3CCB6B20 A3CCB7320 53CCB7B28 4BACA65417BE6FFCA4A422A3440CBBCCBB47E 0BDCAE042C9 0 0 0 0 0 0 0 0 0 0 0 0C9 0 0 021FF7F36 02B36C82336 62B36122336 92B36102336 C2B36 C2336 72B36EE2336 D2B36 9C9 0ED537B401121 0197EED52ED5B7B40FE 3C03A4140CBC7324140C9 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0C9 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
1 REM *** SPACE INVADERS ***
2 REM *** BY DAVE EDWARDS ***
3 REM %W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W% %W%W%W%W%W%W%W%W%W%W%W%W% %W%W%W%W%W%W%W%W%W%W%W%W% % %W%W
4 GOTO 10
5 SAVE "SPIN%V"
10 RAND USR 17374
11 LET A=32767
12 LET D=32766
13 LET C$=" ' ' ' ' ' ' ' ' ' ' ' ' ' ' ''' '"
14 LET F$="%?7F%4RNDCHR$ RND7D' 7D68RND%E%ECHR$ IF IF IF IF .:%B%B IF IF B IF "
15 LET L$=":':':':') ': ' ' ' '.'.'£~~##' £##' ##' ##' $"
16 LET H=0
17 LET H$="%0%0%0%0%0"
18 LET N$=""
19 CLS
22 POKE A,14
23 POKE D,0
25 PRINT AT 4,0;"% %W% %W% %W% %W% %W% %W% %W% %W% %W% %W% %W% %W% %W% %W% %W% %W"
26 LET P=1
27 LET Q=5
28 GOSUB 850
30 PRINT AT 8,0;"THERE ARE 3 LEVELS OF PLAY:"
35 PRINT AT 11,2;"%1 BEGINNERS;"
40 PRINT AT 13,2;"%2 ADVANCED;"
45 PRINT AT 15,2;"%3 EXPERT."
50 PRINT AT 20,0;"WHICH LEVEL DO YOU SELECT?"
55 INPUT L
60 LET L=INT L
65 IF L<1 OR L>3 THEN GOTO 55
70 CLS
75 LPRINT
80 POKE 17736,255
85 POKE 17223,L
90 POKE 17626,65+(4-L)*25
91 POKE 17049,151
92 POKE 16666,0
93 POKE 17655,229
94 POKE 17661,225
95 GOSUB 790+10*L
100 LET B=2
105 LET S=1
110 POKE 16626,67+33*L
120 POKE 16627,0
130 RAND USR 16516
140 PRINT AT 0,0;"%A%A% @@% %S%C%O%R%E% %0%0%0%0%0% @@% %H%I%G%H%E%S%T% ";H$
142 LET P=1
144 LET Q=5
146 GOSUB 850
150 LET X=(PEEK 16626)+33*((S+L)<9)
155 IF X>255 THEN POKE 16627,1
160 IF X>255 THEN LET X=X-256
165 POKE 16626,X
170 RAND USR 16622
180 LET X=1+PEEK 17736-2**(S+1)*2**(L+1)
190 POKE 17736,X AND X>0
200 LET X=USR 17640
205 IF X>3 THEN GOTO 200
210 IF X=1 THEN GOTO 280
220 IF X=2 THEN GOTO 390
230 FOR N=1 TO 15
235 NEXT N
240 LET P=6
245 LET Q=14
250 GOSUB 830
260 LET S=S+1
265 IF S<>5-L THEN GOTO 150
270 LET B=B+1
274 PRINT AT 0,B-1;"%A"
278 GOTO 150
280 GOSUB 700
290 LET B=B-1
300 IF B=-1 THEN GOTO 350
310 PRINT AT 0,B;"% "
320 POKE PEEK 16444+256*PEEK 16445,166
330 POKE 16453,0
340 GOTO 200
350 LET P=15
353 LET Q=25
356 GOSUB 830
357 GOSUB 991
358 CLS
360 PRINT AT 3,0;"WE HAVE DESTROYED YOUR LAST BASE"
370 GOSUB 600
380 GOTO 430
390 LET P=15
393 LET Q=25
396 GOSUB 830
397 GOSUB 991
398 CLS
400 PRINT AT 3,2;"WE HAVE INVADED YOUR PLANET"
410 GOSUB 600
430 PRINT AT 10,0;"YOUR SCORE IS ";SC
440 IF SC>H THEN GOTO 490
450 PRINT AT 14,0;"HIGHEST SCORE IS ";H;" BY",N$
460 PRINT AT 20,0;"PRESS ""0"" FOR ANOTHER GO"
470 IF INKEY$="0" THEN GOTO 50
480 GOTO 470
490 PRINT AT 14,0;"YOU HAVE SET A NEW HIGHEST SCORE"
493 LET P=6
496 LET Q=14
498 GOSUB 830
500 GOSUB 600
510 LET S$=STR$ SC
520 LET X=LEN S$
530 FOR N=1 TO X
540 LET H$(5-X+N)=CHR$ (CODE S$(N)+128)
550 NEXT N
560 PRINT AT 14,0;"PLEASE INPUT YOUR NAME "
570 INPUT N$
580 LET H=SC
590 GOTO 440
600 FOR N=1 TO 30
610 NEXT N
620 RETURN
700 POKE A,8
705 POKE D,16
710 POKE A,9
715 POKE D,16
720 POKE A,10
725 POKE D,16
730 POKE A,12
735 POKE D,2
740 POKE A,7
745 POKE D,199
750 POKE A,13
755 POKE D,13
760 POKE A,6
765 POKE D,31
770 POKE D,16
775 POKE D,0
780 POKE A,12
785 POKE D,30
790 POKE A,13
791 POKE D,9
795 POKE A,6
796 FOR N=0 TO 31
797 POKE D,N
798 NEXT N
799 RETURN
800 POKE 17677,203
802 POKE 17678,63
804 POKE 17688,5
806 POKE 17692,0
808 RETURN
810 POKE 17677,203
812 POKE 17678,63
814 POKE 17688,4
816 POKE 17692,0
818 RETURN
820 POKE 17677,230
821 POKE 16666,229
822 POKE 17678,112
823 POKE 17655,0
824 POKE 17688,2
825 POKE 17661,0
826 POKE 17692,5
827 POKE 17049,195
828 RETURN
830 POKE A,10
840 POKE D,15
850 FOR N=P TO Q
860 POKE A,7
870 POKE D,255
880 POKE A,4
890 POKE D,CODE F$(N)
900 POKE A,5
910 POKE D,CODE C$(N)
920 POKE A,7
930 POKE D,251
940 FOR J=1 TO CODE L$(N)-2
950 NEXT J
960 NEXT N
970 POKE A,7
980 POKE D,255
990 RETURN
991 LET SC=0
992 LET HL=15+PEEK 16396+256*PEEK 16397
993 FOR N=0 TO 3
994 LET SC=SC+(PEEK (HL-N)-156)*10**(N+1)
995 NEXT N
996 RETURN
999 REM % %7%K% % %B%Y%T%E%S%
1000 SAVE "IN%V"
1010 RUN
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.



