Alien Invasion

Products: Alien Invasion
Date: 1981
Type: Cassette
Platform(s): TS 1000
Tags: Arcade, Game

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:

  1. Line 0: A large REM statement containing the entire Z80 machine code game engine (approximately 800+ bytes of hex-encoded opcodes).
  2. Lines 1–5: Title/author REMs, a decorative inverse-video banner line, a GOTO 10 bypass, and a SAVE line.
  3. Lines 10–95: Initialisation — USR call to machine code setup, variable initialisation, difficulty level selection, and POKE-based configuration of machine code parameters.
  4. Lines 100–278: Main game loop — wave management, score display, USR calls into the machine code engine, and result dispatching.
  5. Lines 280–590: End-game handling — base destroyed, planet invaded, score extraction, high-score comparison, and name entry.
  6. Lines 600–799: Sound and utility subroutines, including the tone-generation engine and level-specific configuration POKEs.
  7. Lines 800–828: Machine code parameter patch subroutines (modifying bytes within the REM block at runtime).
  8. Lines 830–990: Port-driven sound player subroutine using string tables.
  9. Lines 991–996: Score extraction from machine code memory.
  10. 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/NEXT delay loop timed by CODE 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 for MAX(X,0): if X>0 is 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 plain USR to 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

VariableRoleEncoding
C$Channel 2 tone / noise dataBlock graphic characters, values read via CODE
F$Frequency register valuesMixed 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 with CODE, 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

Appears On

Related Products

A dark cloud appears on the horizon and inches towards you. The invasion is on and they fire, blowing away...

Related Articles

Related Content

Image Gallery

Source Code

   0 REM 76762AC4011220196203E8A772310FC3E80E15620237710FC23D20F66203E3237710FC11300ED5236A6223C401EB2ED52631E5AF36723772377237723772336841910F01D3E2E323232366722310FC19D20F63D20EE63232323771923771910F8C92AC4011A6019223E4011703EBC4B6D77232310FB19D20F53E5B3240403E803241402110224840C9E521FF7F3672B7E02336F2B56233672B02A3C401FEEFED780100CB47CA1842CB5F288CB67C011FFFF186CB67C811103680197EFE76202ED52223C4036A6FE97C0C3D54321FF7F3662B361F233692B36F233672B36C93A4140CB7F28BCDAF41ED5B4840CDCF41C9CDAF4111210CDCF412148407EEEFE77237EEEFF773A4140CBFF0324140FE81C0E2C92A3E40119448E76D7EFEBC20236801223231310F3672310FDD20E9C92A3E4019223E40119448E76D1AFEBC2021CD92457EFE9BCCA643237E2BFE762872B7E23FE762083A4140CBBF32414036BC23231310D5672310FDD20CB21FF7F3672B74C9ED5B4240CB72C011DFFF197EFE97C8FEBCCA3043369B224240CD6A45C90000000000000000000002A4440CB74C02A3C4011DFFF1013197EFEBC28310F8C960ED527EFE9BCCA643FE802833680C93697224440C90002A4240CB74C811DFFF3680197EFEBCCA3043FE94CA5843FE8ACAA643FEC3CCC143FE802853680C3A643369B224240C9002A4440CB74C8112103680197EFEA6CAD543FE3CAC143FE9BCCA643FE802853680C3C1433697224440C900000002A4640CB74C02AC4011430193A3440CB472051E1F19CBFC3694224640C90000000002A4640CB74C83680CB7C2822B2B237EFE76206260224640C9FE9BCA58433694224640C9000368021FF7F36C2B36A233672B36FE2336D2B369E3CD8A4321404035C0E3C900000368021FF7F3602B3632233662B3602336C2B3619233672B36F62336D2B3632A34402607EE6FC654F2147403602AC4011F01959414A347EFEA6203369CC10F52B79FE020ED43227B402A42407EFE9B202368021002242402A7B403E80C900227B4021002244402A7B403E80C9000001103680C900021FF7FEB21FE7F3E1123603C123603C1236A3C123603C1236F3C3C1236FF3C1236103C3C1236F3C123602101EE511084102EDB0E1EA0EDB0E5217044E8EDB0E1E89E10EDB0E5217844E8EDB0E1E89E18EDB0E5218044E8EDB0E1E89E50EDB0E5218844E8EDB0E1E89EA8EDB0E5219044E8EDB0E1E89E18EDB0C900003C7EA57E3C00243C3C1818000018181818000183C7E7E5A420042247EDBFFA59900000000000000000000000000000000000000002A4A4078FE153075816019224A406079FE0283E6FCC865ACD1E46010FAC3E8440000100214C40347E21C044E521AE420CB47C27C420CB4FC21A41CB575FC48433A404057E670CB3FCB3FCB3FCB3FC624F7AFE5DA654151E03E2CB5320193CCB5B20143CCB6320F3CCB6B20A3CCB732053CCB7B284BACA65417BE6FFCA4A422A3440CBBCCBB47E0BDCAE042C9000000000000C900021FF7F3602B36C8233662B3612233692B36102336C2B36C233672B36EE2336D2B369C90ED537B4011210197EED52ED5B7B40FE3C03A4140CBC7324140C90000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000C900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
   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.

People

No people associated with this content.

Scroll to Top