What can the guard see from one square? Hold it at the opening position while we build an answer we can inspect. This temporary version says SIGHT TEST at the top; movement and corner scans return in lesson 8.
Widen the candidate view
The guard at (7,5) faces east. Consider five successive columns in front of it. At distance one there is one candidate square; then three, five, seven and nine. This diagram shows candidates before walls remove any. It is an explanation of the rule, not a game capture.
For a target at forward distance depth, its sideways offset runs from 1-depth through depth-1. Check the path towards each candidate one forward step at a time. At step distance, the sideways displacement is floor(offset * distance / depth + 0.5). Here floor means round down to an integer; Sinclair BASIC calls that INT.
Trace this rule; the following is pseudocode, not another program to type:
For each candidate target:
previous = guard position
For distance from 1 through target depth:
side = floor(offset * distance / depth + 0.5)
sample = guard + forward * distance + sideways * side
Reject target if sample is outside the map or in a wall
If this step changes both column and row:
Reject target if either square beside that diagonal is a wall
previous = sample
If no step rejected the target, mark it visible
Forward is (1,0) when facing east; sideways is (0,1). For any forward pair (dx,dy), the sideways pair is (−dy,dx). This rotates the same construction with the guard. Blocking either side of a diagonal prevents sight squeezing between touching wall corners. These are the game’s discrete sight rules, not a claim about physically exact vision.
At the opening, (9,4) and (9,6) are visible: the fan reaches beyond its centre line. (13,5) is six columns ahead, so it is outside the range. A different example distinguishes cover from range: from (7,12) facing north, (9,9) lies inside the candidate fan, but a wall blocks its sampled path.
Store the answer separately
We use a string v$ as a visibility mask: each position holds a space for unseen or ! for visible. Its index is 32*row+column, so (9,5) becomes 169. The stride is 32, matching the display’s columns, even though our map is only thirty columns wide. DIM reserves 608 characters, including unused margin positions; the last map square uses index 606.
The opening mask has eleven cells. Line 8900 begins with position, facing, visible count and change count, then lists those cell indices. For this first view every change is an addition. Decode 137 as row INT (137/32)=4, column 137-32*4=9: square (9,4).
Continue from Look around at corners. Remove its movement section and phase table as specified below. Keep a saved copy of that version if you want to compare it later.
Delete lines 400–440 in steps of ten, 455, 460, 8200–8800 in steps of ten.
Replace lines 100, 120, 140, 370, 1010, 4010.
Add lines 2020, 2070, 3000, 3005, 3010, 3020, 3030, 8900.
Delete a line by entering its number alone and pressing Enter. Then enter the replacements and additions below.
100 DIM v$(608): LET phase=0
120 LET x=3: LET y=16: LET gx=7: LET gy=5: LET gd=1
140 GO SUB 1000: RESTORE 8900: READ gx,gy,gd,bn,n: GO SUB 3000: GO SUB 3300: GO SUB 2500
370 IF v$(y*32+x)="!" OR (x=gx AND y=gy) THEN LET outcome=1: GO TO 4000
1010 PRINT AT 0,1; INK 5; BRIGHT 1;"NIGHT PATROL";AT 0,20;"SIGHT TEST"
2020 IF v$(r*32+c)="!" THEN LET shade=6
2070 IF v$(r*32+c)="!" AND ((r=y AND c=x) OR (r=3 AND c=27) OR (r=16 AND c=3)) THEN LET shade=2
3000 IF n=0 THEN RETURN
3005 FOR j=1 TO n: READ cell: LET v$(cell)="!"
3010 LET r=INT (cell/32): LET c=cell-32*r
3020 PRINT AT r+1,c; PAPER 6; INK 7; BRIGHT 1;" "
3030 NEXT j: LET r=y: LET c=x: GO SUB 2000: RETURN
4010 IF outcome=1 THEN PRINT AT 1,1; INK 2; BRIGHT 1;"SPOTTED! Use the hiding places. "
8900 DATA 7,5,1,11,11,168,137,169,201,138,170,202,139,171,140,172Complete program at this stage
10 GO SUB 7000: GO SUB 7100
100 DIM v$(608): LET phase=0
120 LET x=3: LET y=16: LET gx=7: LET gy=5: LET gd=1
130 LET got=0: LET outcome=0: LET beat=0: LET steps=0
140 GO SUB 1000: RESTORE 8900: READ gx,gy,gd,bn,n: GO SUB 3000: GO SUB 3300: GO SUB 2500
150 LET tick=PEEK 23672
200 LET k$=INKEY$
210 IF k$="q" OR k$="Q" THEN GO TO 9000
220 IF k$="r" OR k$="R" THEN GO SUB 8100: GO TO 100
230 LET elapsed=PEEK 23672-tick: IF elapsed<0 THEN LET elapsed=elapsed+256
240 IF elapsed<8 THEN GO TO 200
250 LET k$=INKEY$
260 LET dx=0: LET dy=0
270 IF k$="i" OR k$="I" THEN LET dy=-1
280 IF k$="k" OR k$="K" THEN LET dy=1
290 IF k$="j" OR k$="J" THEN LET dx=-1
300 IF k$="l" OR k$="L" THEN LET dx=1
310 LET nx=x+dx: LET ny=y+dy
320 IF m$(ny,nx)=CHR$ 145 THEN GO TO 370
330 IF nx=x AND ny=y THEN GO TO 370
340 LET r=y: LET c=x: LET x=nx: LET y=ny: GO SUB 2000
350 LET steps=steps+1: LET r=y: LET c=x: GO SUB 2000
370 IF v$(y*32+x)="!" OR (x=gx AND y=gy) THEN LET outcome=1: GO TO 4000
380 IF x=27 AND y=3 AND got=0 THEN LET got=1: GO SUB 2500
390 IF x=3 AND y=16 AND got=1 THEN LET outcome=2: GO TO 4000
470 LET tick=PEEK 23672: GO TO 200
1000 BORDER 0: PAPER 0: INK 7: BRIGHT 0: CLS
1010 PRINT AT 0,1; INK 5; BRIGHT 1;"NIGHT PATROL";AT 0,20;"SIGHT TEST"
1020 FOR r=1 TO 18: PRINT AT r+1,1; INK 1; BRIGHT 1;m$(r): NEXT r
1022 LET r=3: LET c=27: GO SUB 2000
1024 LET r=y: LET c=x: GO SUB 2000
1030 PRINT AT 20,1;"I up J left K down L right"
1040 PRINT AT 21,1;"R retry Q quit";
1050 RETURN
2000 LET z$=" ": LET colour=7: LET shade=0
2010 IF m$(r,c)=CHR$ 145 THEN LET z$=CHR$ 145: LET colour=1
2020 IF v$(r*32+c)="!" THEN LET shade=6
2030 IF r=3 AND c=27 AND got=0 THEN LET z$=CHR$ 146: LET colour=5
2040 IF r=16 AND c=3 THEN LET z$=CHR$ 147: LET colour=4
2050 IF r=gy AND c=gx THEN LET z$=g$(gd): LET colour=6
2060 IF r=y AND c=x THEN LET z$=CHR$ 144: LET colour=7
2070 IF v$(r*32+c)="!" AND ((r=y AND c=x) OR (r=3 AND c=27) OR (r=16 AND c=3)) THEN LET shade=2
2080 PRINT AT r+1,c; PAPER shade; INK colour; BRIGHT 1;z$: RETURN
2500 PRINT AT 1,1; INK 5;"TAKE THE FILE. RETURN TO EXIT. "
2510 IF got=1 THEN PRINT AT 1,1; INK 4;"FILE TAKEN. GET BACK TO EXIT. "
2520 RETURN
3000 IF n=0 THEN RETURN
3005 FOR j=1 TO n: READ cell: LET v$(cell)="!"
3010 LET r=INT (cell/32): LET c=cell-32*r
3020 PRINT AT r+1,c; PAPER 6; INK 7; BRIGHT 1;" "
3030 NEXT j: LET r=y: LET c=x: GO SUB 2000: RETURN
3300 LET r=gy: LET c=gx: GO SUB 2000: RETURN
4000 LET r=y: LET c=x: GO SUB 2000
4010 IF outcome=1 THEN PRINT AT 1,1; INK 2; BRIGHT 1;"SPOTTED! Use the hiding places. "
4020 IF outcome=2 THEN PRINT AT 1,1; INK 4; BRIGHT 1;"FILE RECOVERED. CLEAN GETAWAY! "
4030 PRINT AT 20,1;"R tries again. Q quits. "
4040 GO SUB 8000
4050 IF k$="r" OR k$="R" THEN GO SUB 8100: GO TO 100
4060 IF k$="q" OR k$="Q" THEN GO TO 9000
4070 GO TO 4040
7000 RESTORE 7500: FOR j=0 TO 31: READ a: POKE USR "a"+j,a: NEXT j
7010 DIM a(4): DIM b(4): LET a(1)=1: LET b(2)=1: LET a(3)=-1: LET b(4)=-1
7020 LET g$=">v<"+CHR$ 94: RETURN
7100 DIM m$(18,30): RESTORE 7200
7110 FOR r=1 TO 18: READ m$(r): FOR c=1 TO 30
7120 IF m$(r,c)="#" THEN LET m$(r,c)=CHR$ 145
7130 NEXT c: NEXT r: RETURN
7200 DATA "##############################"
7210 DATA "##############################"
7220 DATA "########### ############# ###"
7230 DATA "##### ####### # ###"
7240 DATA "##### ###"
7250 DATA "##### ###### # #####"
7260 DATA "###### ######### ##### ######"
7270 DATA "###### ############ ######"
7280 DATA "###### ############ ######"
7290 DATA "###### ############## ######"
7300 DATA "#### ################ ####"
7310 DATA "#### #### ########## ####"
7320 DATA "##### ####### #####"
7330 DATA "##### #####"
7340 DATA "##### ###### # #####"
7350 DATA "## ######### ############"
7360 DATA "##############################"
7370 DATA "##############################"
7500 DATA 24,24,0,60,90,24,36,66
7510 DATA 255,128,128,255,8,8,8,255
7520 DATA 126,66,90,66,90,66,126,0
7530 DATA 126,66,66,74,66,66,126,0
8000 GO SUB 8100
8010 LET k$=INKEY$: IF k$="" THEN GO TO 8010
8020 RETURN
8100 IF INKEY$<>"" THEN GO TO 8100
8110 RETURN
8900 DATA 7,5,1,11,11,168,137,169,201,138,170,202,139,171,140,172
9000 PAPER 0: INK 7: BRIGHT 0: PRINT AT 21,0;"Finished. RUN to try again. ";: STOPDisplay the mask and use it
The routine at 3000 reads the eleven cells, marks them in v$ and prints yellow paper at their screen positions. The per-square drawing routine also consults v$, so restoring a square preserves its lit background. It uses red paper under a visible player, file or entrance to keep the symbol distinct.
Line 370 checks v$(y*32+x)="!" after a player move, alongside contact with the guard. The program reads logical visibility. Changing a screen colour cannot make a hidden square dangerous.
RUN. The guard must stay at (7,5), facing east. Walk into the displayed fan to be spotted. Retry, then use the surrounding walls and unlit passages to fetch the file. A successful return is still possible; this diagnostic stage isolates sight, not the objective.
Is every unlit square protected by a wall?
Show the explanation
No. Some are outside the fan’s direction or range; others are candidates blocked by walls. Both are unseen now, but a turn or step can change which ones are safe.
Sources
Steven Vickers, edited by Robin Bradbeer, ZX Spectrum BASIC Programming, second edition (Sinclair Research, 1983), chapter 8, chapter 12, chapter 16.
Run it here
This unit's program, built for you.
- Program
- night-patrol.tap
- Machine
- ZX Spectrum 48K (PAL)