Ambush is a shooting game in which the player must eliminate five hidden snipers before being shot. The player controls a rifle sight horizontally using N and M keys, raises or lowers aim with A and Z, and fires with B; snipers randomly pop up and return fire using pixel-precise PLOT/DRAW routines with OVER 1 for tracer-bullet animation. User-defined graphics (UDGs A through U) represent trees, a wall, crouching and standing soldier poses, and sniper figures, all initialized from DATA statements via POKE USR loops. Hit detection is performed by comparing the MX aiming variable against hardcoded pixel-range thresholds for each sniper position. A best-time record (TB) persists across multiple rounds within the same session.
Program Structure
The program is divided into clearly labeled sections using REM statements. Execution begins at line 50 with a GO TO 600 to the initialization block, which sets up UDGs, loads DATA, prints the intro screen, then falls through to the scene-drawing routine at line 700. The main loop runs through three cooperating sections:
- Lines 100–180: Keyboard input and timing counter
- Lines 200–298: Action dispatcher — bounds-clamping MX, handling firing
- Lines 300–360: Sniper AI — random sniper selection, state machine advancement
Subroutine at lines 400–460 handles sniper firing with tracer animation and player-hit detection. End-game branches lead to lines 830 (player shot) or 850 (all snipers killed), both returning to line 700 to restart the scene.
UDG Initialization
Lines 615–660 perform extensive UDG setup using POKE USR loops reading from DATA blocks. Nine UDGs (chars 144–152, referenced as CHR$ (143+y)) are loaded in a nested loop at line 615. Subsequent loops load UDGs J/K (sniper figures), L (wall tile), R/S (man-down pose), T/U (man-standing pose), and char 156 (a crouching man). The wall string W$ at line 630 is a 23-character run of the wall UDG, printed as a solid barrier at row 14.
Sniper State Machine
The string K$ (10 characters, initialized to "0000000000") encodes all game state. Characters 1–5 represent each sniper’s activity counter; characters 6–10 are kill flags ("1" = dead). Each game loop iteration picks a random sniper K (1–5), skips it if already dead, and increments its counter character using CHR$ (CODE K$(K)+1). Specific counter values trigger actions:
- Value
"4": sniper becomes visible (pops up) - Value
"5"or"7"while player is not aiming (MN=0): sniper fires (calls line 400 subroutine) - Value
>"7": sniper retreats, counter resets to"0"
Tracer Bullet Animation
Both player and sniper fire are drawn using PLOT OVER 1 / DRAW OVER 1 pairs. Drawing the same line twice with OVER 1 (XOR mode) erases it cleanly without disturbing the background — a standard Spectrum animation idiom. The player’s shot origin is fixed at pixel (82,72), and the draw vector is (MX-60, Y) where Y is calculated from the aiming zone of MX (lines 235–248). A short BEEP loop (lines 262 and 430) adds a firing sound effect.
Hit Detection
Player-to-sniper hits are checked at lines 275–285 using hardcoded MX threshold ranges. Each range corresponds to one sniper’s screen column position:
| Line | MX range | Sniper | Kill flag |
|---|---|---|---|
| 275 | 29–35 | 1 | K$(6) |
| 278 | 61–67 | 2 | K$(7) |
| 280 | 101–107 | 3 | K$(8) |
| 282 | 144–155 | 4 | K$(9) — see bug note |
| 285 | 185–195 | 5 | K$(10) |
Sniper-to-player hit detection (line 450) checks whether the sniper’s shot X coordinate lands in the range 78–86, representing the player’s position behind the wall.
Aiming System
The horizontal aim variable MX (initialized to 80) is adjusted by 8 units per keypress of N (left) or M (right). The vertical component Y is derived from the current MX value via a series of threshold comparisons (lines 235–248), effectively mapping horizontal aim zones to the correct vertical angles to reach each sniper’s screen position. MN flags whether the player is aiming up (A key sets MN=1, Z clears it), and the FR flag is set by B to trigger firing on the next action pass.
Bugs and Anomalies
- Line 282 kill flag:
LET K$="1"replaces the entire 10-character string with a single"1"instead of settingK$(9)="1". This effectively corrupts the kill-flag array when sniper 4 is hit, likely causing immediate win detection or unpredictable behavior. - Line 680 duplicate loop variable:
FOR y=1 TO 5:FOR y=1 TO 5— the inner loop reusesy, shadowing the outery. Only 5 values are read per sniper row instead of the intended nested 5×5 = 25; the outer loop variable is overwritten immediately. - Line 440 draw vector:
DRAW OVER 1;NX,-P(K,2)usesP(K,2)(the sniper’s Y pixel coordinate) as the vertical draw distance on the erase pass, rather thanP(K,5)used on the draw pass (line 425). This means the erase stroke does not exactly retrace the fired line. - Line 440 DRAW syntax: Uses semicolon separator rather than comma, consistent with the rest of the program’s DRAW usage — not a bug per se but worth noting for portability.
- Line 282 array bounds: If
K$were not corrupted as above,K$(9)would be valid within the 10-character string; the intent is clear despite the bug.
Scoring and Persistence
TM is a frame-count timer incremented each keyboard-poll cycle (line 120). The best-time record TB (initialized to 5000) is updated at line 850 only if the current time is lower, persisting across replays within the session. On restart (line 870–890), MX, K$, TM, MN, and FR are reset, but TB is preserved.
Content
Source Code
10 REM **SNIPER AMBUSH ©1983 MICHAEL BEWS
20 DIM P(5,5):DIM L(11,2):DIM S(5,2):DIM S$(2,2)
30 LET TB=5000:LET TM=0:LET K$="0000000000":LET MN=0:LET AI=0:LET FR=0:LET MX=80
40 PAPER 4:INK 7:BORDER 1
50 GO TO 600
100 REM **KEYBOARD INPUT
120 LET TM=TM+1:PRINT INK 0; PAPER 6; AT 15,28;TM
125 LET A= PEEK 23556:IF A=255 THEN GO TO 200
130 IF A=65 THEN PRINT 13,10;R$:LET MN=1:GO TO 200
140 IF A=90 THEN PRINT AT 13,10;Q$:LET MN=0:GO TO 200
150 IF (A=66 AND MN=1) THEN LET FR=1:GO TO 200
160 IF A=78 THEN LET MX=MX-8:GO TO 200
170 IF A=77 THEN LET MX=MX+8:GO TO 200
180 GO TO 120
199 REM *ACTION ROUTINE
210 IF MX<0 THEN LET MX=0
215 IF MX>220 THEN LET MX=220
220 IF FR=0 THEN GO TO 300
225 LET FR=0
230 LET Y=23
235 IF MX<42 THEN LET Y=Y+32:GO TO 250
240 IF MX<70 THEN LET y=y+7:GO TO 250
242 IF MX<124 THEN LET Y=Y+8:GO TO 250
244 IF MX<164 THEN GO TO 250
248 LET Y=Y-8
250 REM *TRACERS AND SOUND
255 PLOT OVER 1,82,72
260 DRAW OVER 1;MX-60,Y
262 FOR X=57 TO 53 STEP -1:BEEP .005,X:NEXT X
265 PLOT OVER 1,82,72
270 DRAW OVER 1;MX-60,Y
272 REM *CHECK FOR HIT
275 IF MX>29 AND MX<35 AND K$(1)>"0" THEN PRINT AT 5,4;"[UDG-R][UDG-S]"; INK 0; PAPER 4;"[UDG-G][UDG-H]":LET K$(6)="1":GO TO 290
278 IF MX>61 AND MX<67 AND K$(2)>"0" THEN PRINT AT 8,8;"[UDG-R][UDG-S]"; INK 0; PAPER 4;"[UDG-G][UDG-H]":LET K$(7)="1":GO TO 290
280 IF MX>101 AND MX<107 AND K$(3)>"0" THEN PRINT AT 8,13;"[UDG-R][UDG-S]"; INK 0; PAPER 4;"[UDG-G][UDG-H]":LET K$(8)="1":GO TO 290
282 IF MX>144 AND MX<155 AND K$(4)>"0" THEN PRINT AT 9,19;"[UDG-R][UDG-S]"; INK 0; PAPER 4;"[UDG-G][UDG-H]":LET K$="1":GO TO 290
285 IF MX>185 AND MX<195 AND K$(5)>"0" THEN PRINT AT 10,24;"[UDG-R][UDG-S]"; INK 0; PAPER 4;"[UDG-G][UDG-H]":LET K$(10)="1"
289 REM *CHECK FOR WIN
290 IF K$(6 TO 10)="11111" THEN GO TO 850
299 REM *SNIPER ROUTINE
310 LET K=1+ INT (RND*5)
315 IF K$(K+5)="1" THEN GO TO 120
320 IF K$(K)="4" THEN PRINT INK 0; PAPER 4; AT S(K,1),S(K,2);S$(2);
330 IF K$(K)="5" OR (K$(K)="7" AND MN=0) THEN GO SUB 400
340 LET K$(K)= CHR$ (CODE K$(K)+1)
350 IF K$(K)>"7" THEN PRINT INK 0; PAPER 4; AT S(K,1),S(K,2);S$(1);:LET K$(K)="0"
360 GO TO 120
399 REM *SNIPER FIRING
405 LET N=1+ INT (RND*P(K,4))
410 LET NX=N-P(K,3)
420 PLOT OVER 1;P(K,1),P(K,2)
425 DRAW OVER 1;NX,-P(K,5)
430 FOR X=50 TO 47 STEP -1:BEEP .005,X:NEXT X
435 PLOT OVER 1;P(K,1),P(K,2)
440 DRAW OVER 1;NX,-P(K,2)
450 IF P(K,1)+NX>78 AND P(K,1)+NX<86 THEN PRINT AT 13,10;Q$; AT 15,9;P$;:GO TO 830
460 RETURN
598 STOP
599 REM **INITIALISATION
602 CLS :PRINT INK 1; PAPER 7;"A M B U S H ©1983 MICHAEL BEWS"; AT 4,0;"YOU HAVE BEEN AMBUSHED BY GUNMENIN A FOREST. THE ONLY SAFE PLACE IS LOW WALL BEHIND WHICH YOU TAKE COVER."
603 PRINT AT 9,0; INK 1; PAPER 7;"YOU MUST TRY TO KILL ALL GUNMEN BY AIMING OVER THE TOP OF THE WALL. BUT THE SNIPERS ARE QUITE GOOD SHOTS!"; AT 16,0;"HOW LONG CAN YOU SURVIVE?......."
608 LET f$= CHR$ 21+ CHR$ 1+" "+ CHR$ 21+ CHR$ 0
610 LET G$= CHR$ 21+ CHR$ 1+" "+ CHR$ 21+ CHR$ 0
615 FOR x=0 TO 7:FOR y=1 TO 9:READ a:POKE USR CHR$ (143+y)+x,a:NEXT y:NEXT x
620 FOR x=0 TO 7:READ a,b:POKE USR "[UDG-J]"+x,a:POKE USR "[UDG-K]"+x,b:NEXT x
625 FOR X=1 TO 11:READ L(X,1):READ L(X,2):NEXT X
630 FOR X=0 TO 7:READ A:POKE USR "[UDG-L]"+X,A:NEXT X:LET W$="[UDG-L][UDG-L][UDG-L][UDG-L][UDG-L][UDG-L][UDG-L][UDG-L][UDG-L][UDG-L][UDG-L][UDG-L][UDG-L][UDG-L][UDG-L][UDG-L][UDG-L][UDG-L][UDG-L][UDG-L][UDG-L][UDG-L][UDG-L]"
640 LET t$="[UDG-A][UDG-B][UDG-C]"+f$+"[UDG-D][UDG-E][UDG-F]"+f$+"[UDG-G][UDG-H][UDG-I]"
645 LET S$(1)="[UDG-G][UDG-H]":LET S$(2)="[UDG-J][UDG-K]"
650 FOR X=0 TO 7:READ A:POKE USR CHR$ (156)+X,A:NEXT X
655 FOR X=0 TO 7:READ A,B:POKE USR "[UDG-R]"+X,A:POKE USR "[UDG-S]"+X,B:NEXT X
660 FOR X=0 TO 7:READ A,B:POKE USR "[UDG-T]"+X,A:POKE USR "[UDG-U]"+X,B:NEXT X
665 LET Q$= CHR$ 16+ CHR$ 0+ CHR$ 17+ CHR$ 4+" "+G$+"[UDG-L]"+G$+"[UDG-M]":LET P$="[UDG-R][UDG-S]"
670 LET R$= CHR$ 16+ CHR$ 0+ CHR$ 17+ CHR$ 4+"[UDG-T]"+G$+"[UDG-U]"+G$+" "
675 FOR X=1 TO 5:READ S(X,1):READ S(X,2):NEXT X
680 FOR x=1 TO 5:FOR y=1 TO 5:FOR y=1 TO 5:READ P(X,Y):NEXT Y:NEXT X
690 INPUT INK 7; PAPER 2;"PRESS {ENTER} TO FIND OUT!!!";X$
700 REM **PRINT SCENE**
705 CLS :PRINT PAPER 6; INK 1;"A M B U S H ©1983 MICHAEL BEWS"
707 FOR x=10 TO 26 STEP 4:PRINT INK 0; PAPER 4; AT 1,x;t$:NEXT x
710 FOR x=1 TO 11:PRINT INK 0; AT L(X,1),L(X,2);t$;:NEXT X
715 FOR X=3 TO 11:PRINT AT L(X,1)+2,L(X,2)+2; INK 6;"[UDG-I]";:NEXT X
720 PRINT INK 0; AT 15,0;T$; AT 17,3;T$
730 PRINT INK 0; AT 14,0;W$
800 PRINT AT 13,10;Q$
805 PRINT AT 15,23; INK 0; PAPER 6;"TIME:"
810 PRINT AT 20,0; PAPER 2; INK 7;"SHOOT SNIPERS AS THEY FIRE.KEYS'A&Z'(UP/DN)'N&M'(AIM) 'B'(FIRE)"
820 GO TO 100
829 REM ** PLAYER SHOT!
830 PRINT AT 20,0; PAPER 7; INK 2;" "; AT 20,0; FLASH 1;"YOU HAVE BEEN SHOT! IN TIME:";TM; AT 21,0;"THIS IS THE SNIPER WHO SHOT YOU↑"
832 PRINT INK 0; PAPER 4; FLASH 1; AT S(K,1),S(K,2);S$(2);
835 BEEP .8,1:PAUSE 5:BEEP .5,1:PAUSE 2:BEEP .3,1:BEEP .8,1:BEEP .5,4:PAUSE 2:BEEP .5,1:PAUSE 1:BEEP .8,1:PAUSE 4:BEEP .5,0:BEEP 1,1
840 GO TO 860
849 REM *ALL SNIPERS DEAD
850 IF TB>TM THEN LET TB=TM
855 PRINT AT 20,0; PAPER 7; INK 2;" "; AT 20,0; FLASH 1;"YOU SHOT ALL SNIPERS IN TIME:";TM; AT 21,0;"BEST TIME SO FAR:";TB
857 FOR Y=1 TO 5:FOR X=1 TO 10:BEEP .05,X*2:NEXT X:NEXT Y
860 INPUT INK 0; PAPER 7; FLASH 1;"PRESS {ENTER} FOR NEXT GAME";X$
870 LET MX=80:LET K$="0000000000"
880 LET TM=0:LET MN=0:LET FR=0
890 GO TO 700
998 STOP
999 REM **tree
1000 DATA 3,15,0,127,255,252,0,60,8,3,255,192,63,255,188,0,60,0,15,255,240,55,247,248,0,60,138,15,255,248,31,63,248,0,60,40,126,255,126,31,60,240,0,60,138,127,255,254,24,60,48,0,60,162,255,255,252,0,60,0,0,60,160,127,223,252,0,60,0,0,60,32
1009 REM * sniper
1010 DATA 0,252,63,252,2,124,3,252,0,124,1,252,1,124,3,60
1019 REM **tree PRINT AT FILE
1020 DATA 2,4,1,18,3,6,2,20,3,24,4,11,4,29,6,10,6,15,7,21,8,26
1029 REM **WALL
1030 DATA 51,255,219,255,253,255,183,255
1039 REM **MAN CROUCHING
1040 DATA 249,113,113,33,255,113,123,219
1049 REM *MAN DOWN
1050 DATA 0,0,0,0,0,0,0,0,160,0,247,224,255,241,247,255
1059 REM *MAN STANDING
1060 DATA 248,115,113,255,115,251,118,255,38,253,250,251,254,247,112,255
1069 REM *sniper print at file
1070 DATA 5,6,8,10,8,15,9,21,10,26
1079 REM *sniper firing coords
1080 DATA 47,127,10,50,54,79,110,39,70,37,119,110,60,60,37,167,102,127,80,29,207,94,200,120,21
4000 BORDER 7:PAPER 7:INK 0:CLS
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.

