This program implements a two-player strategic board game called “Fox at Dusk” on an 8×8 checkerboard. The human controls a set of hounds (3–8, user-selectable) while the computer controls a fox; pieces move diagonally and the fox can leap over hounds. A difficulty toggle hides the fox except when it leaps or collides with a hound. The board is drawn using ZX81 block graphics characters, with each square rendered as a 3×3 character cell. The fox’s AI scores candidate moves using a weighted random formula that accounts for board depth, proximity to row 1, and whether a leap over a hound is possible.
Program Analysis
Program Structure
The program is organised into a main flow followed by a set of subroutines accessed via GOSUB. The top-level sections are:
- Lines 10–90: Initialisation, title screen, difficulty prompt.
- Lines 100–140: Hound-count input and validation.
- Lines 160–480: Board array setup and hound placement.
- Lines 490–560: Fox placement and first display.
- Lines 560–1160: Main game loop — human hound move, then fox AI move.
- Lines 1170–1240: Single-digit input subroutine (returns value in
Z). - Lines 1250–1360: Board rendering subroutines for hound, fox, and blank square.
- Lines 1370–1420: Fox-leap logic.
- Lines 1430–1490: Win/loss messages.
- Lines 1510–1570: Fox flash animation subroutine.
- Lines 1580–1600: SAVE and RUN for auto-restart.
Board Representation
The game state is held in A$(8,8), an 8×8 string array. Each cell contains one character: "B" for a black square (off-limits), "H" for a hound, "F" for the fox, or " " (space) for an empty playable square. The checkerboard pattern is set at lines 320–360 by testing whether (A+B) is odd. The fox’s current column and row are tracked separately in scalar variables E and F.
Screen Layout and Block Graphics
Each board square occupies a 3×3 character cell on screen. The column-to-screen mapping is 3*(X-1) and the row mapping inverts the board with 3*(8-Y), placing row 8 at the top and row 1 at the bottom. ZX81 block graphics characters are used to draw checkerboard shading (lines 220–290) and to render the hound (GOSUB 1250), fox (GOSUB 1300), and erased square (GOSUB 1340) as distinct 3-row sprites.
The title area in the right margin (columns 25–31) uses inverse-video characters for labels and move prompts, rendered with PRINT AT statements that leave the board area untouched.
Input Subroutine
The subroutine at line 1170 implements a robust single-keystroke digit reader. Line 1180 first flushes any held key with a busy-wait loop, lines 1190–1200 wait for a new keypress, and lines 1210–1230 reject characters outside "1"–"8" (looping back to flush again). The accepted character is echoed with PRINT C$;, converted to a column/row integer via CODE C$-28 (since CODE "1" = 29 on the ZX81), and returned in Z. The human enters the column then the row on two separate calls (lines 580–600 and 630–650).
Move Validation
A compact string expression is stored at line 170:
LET B$="A>8 OR A<1 OR B>8 OR B<1"
This string is later evaluated with VAL B$ at line 880 and line 1400 to perform bounds-checking on candidate positions — a classic ZX81 memory-saving idiom that avoids repeating the condition in code. Human hound moves are validated at line 710, which checks that the source cell contains "H", the destination is empty " ", and both column and row distances are exactly 1.
Fox AI
The fox AI (lines 820–1010) iterates over the four diagonal neighbours of the fox’s current position (E,F) using nested FOR loops over W=F-1 TO F+1 STEP 2 and V=E-1 TO E+1 STEP 2. For each candidate square it computes a score in T:
3*RND: random noise (0–3) to vary play.(B<F): +1 if the move advances toward row 1 (the fox’s goal).(B=1): +1 bonus for landing on the winning row immediately.
The best-scoring move is stored in (X,Y) and M tracks the maximum score. If a candidate square is occupied by a hound, the leap subroutine at line 1370 is called, which projects the fox two squares in that direction and retries with a reduced random weight (T=RND). If M remains 0 after all candidates are examined, the fox is trapped and the player wins (line 1030).
Visibility (“Dusk”) Mechanic
Variable I is set to 1 (easy mode) or 0 (hard mode) at line 80 based on the player’s "Y"/"N" answer. The fox sprite is drawn at line 1090 whenever G is true (a leap occurred), and at line 1150 when G OR I — meaning in easy mode it is shown every move, while in hard mode it is only shown after a leap. The flash animation subroutine at line 1510 toggles the fox sprite on and off four times to draw attention to leaps.
Notable Techniques and Idioms
POKE 16418,2at line 30 sets the system variable controlling the display file, andPOKE 16418,0at line 200 restores it — used here to manipulate the display during setup underFASTmode.LET H=CODE H$-28(line 130) converts the ASCII digit character to a numeric count using the ZX81 character code offset, consistent with the same technique in the input subroutine.- The hound-placement loop (lines 380–480) fills the bottom two rows of playable squares left-to-right until
Hhounds have been placed, decrementingHeach time and stopping whenNOT His true. SAVE "1025"at line 1590 (whereis an inverse digit) saves the program with the auto-run flag set, so reloading the tape starts the game immediately.
Bugs and Anomalies
- Line 1260 stores a value into
Z(LET Z=3*(X-1)) but this is the hound-draw subroutine entered at line 1250 — line 1250 itself does not exist in the listing; the subroutine effectively starts at 1260. TheGOSUB 1250calls target this gap, which on the ZX81 causes execution to continue from the next higher line (1260), so it functions correctly. - Similarly,
GOSUB 1290(called for the win/loss display at lines 1430 and 1470) targets a non-existent line; execution falls through to line 1300, which draws the fox. This means the fox is always revealed on game-end regardless of the visibility mode — likely intentional. - The instruction text at line 50 contains the typo “COLUME” (for “COLUMN”) and the run-together word “THENTHE”.
- Lines 20 and 550 are referenced by
GOTO(lines 1460 and 1160 respectively) but do not appear in the listing.GOTO 20restarts near the beginning (falling through to line 30), andGOTO 550re-enters the human move prompt section (falling through to line 560) — both are deliberate restart targets.
Content
Source Code
10 RAND
30 POKE 16418,2
40 CLS
50 PRINT TAB 10;"""FOX AT DUSK""",,,"TRAP THE FOX SO IT CANNOT MOVE. FOX AND HOUNDS MOVE ONE SPACE DIAGONALLY UP OR DOWN, BUT THE FOX MAY LEAP OVER A HOUND."," ENTER MOVES COLUME FIRST THENTHE ROW..E.G. 11 IS BOTTOM LEFT CORNER.",,
60 PRINT " AT DUSK THE FOX CAN ONLY BE SEEN AT THE GAME START, WHEN IT LEAPS OVER A HOUND, OR WHEN A HOUND TRIES TO MOVE INTO THE SQUARE THAT THE FOX IS IN.",,,,,,,"WOULD YOU PREFER THE EASIER GAMEWHERE YOU SEE THE FOX WHENEVER IT MOVES?"
70 INPUT B$
80 LET I=B$(1)="Y"
90 CLS
100 PRINT AT 4,0;" HOW MANY HOUNDS WOULD YOU LIKETO USE? (FROM THREE TO EIGHT) "
110 INPUT H$
120 IF H$>"8" OR H$<"3" THEN GOTO 110
130 LET H=CODE H$-28
140 FAST
160 DIM A$(8,8)
170 LET B$="A>8 OR A<1 OR B>8 OR B<1"
180 CLS
200 POKE 16418,0
220 FOR A=0 TO 7
230 FOR B=0 TO 2
240 LET D=3*(A-2*INT (A/2))
250 FOR C=0 TO 3
260 PRINT TAB (D+C*6);"@@@@@@";
270 NEXT C
280 NEXT B
290 NEXT A
300 PRINT AT 2,25;"FOX AT";TAB 25;"''''''''''''";TAB 26;"DUSK";TAB 26;"''''''''"
320 FOR A=1 TO 8
330 FOR B=1 TO 8
340 IF (A+B)/2<>INT ((A+B)/2) THEN LET A$(A,B)="B"
350 NEXT B
360 NEXT A
380 FOR A=1 TO 2
390 FOR B=1 TO 8
400 IF NOT H THEN GOTO 490
410 IF A$(B,A)="B" THEN GOTO 470
420 LET A$(B,A)="H"
430 LET H=H-1
440 LET X=B
450 LET Y=A
460 GOSUB 1250
470 NEXT B
480 NEXT A
490 SLOW
510 LET F=8
520 LET E=2*INT (1+RND*4)
530 LET A$(E,F)="F"
540 GOSUB 1500
560 PRINT AT 7,26;"%M%O%V%E";TAB 26;"%F%R%O%M";AT 10,27;
570 GOSUB 1170
580 LET A=Z
590 GOSUB 1170
600 LET B=Z
610 PRINT AT 12,27;"%T%O";AT 14,27;
620 GOSUB 1170
630 LET C=Z
640 GOSUB 1170
650 LET D=Z
670 IF A$(C,D)="F" THEN GOSUB 1500
690 PRINT AT 7,26;" ";TAB 26;" ";AT 10,27;" ";AT 12,27;" ";AT 14,27;" "
710 IF ABS (A-C)>1 OR ABS (B-D)>1 OR A$(A,B)<>"H" OR A$(C,D)<>" " THEN GOTO 550
730 LET X=A
740 LET Y=B
750 GOSUB 1330
760 LET A$(A,B)=" "
770 LET X=C
780 LET Y=D
790 GOSUB 1250
800 LET A$(C,D)="H"
820 LET M=0
830 FOR W=F-1 TO F+1 STEP 2
840 FOR V=E-1 TO E+1 STEP 2
850 LET A=V
860 LET B=W
880 IF VAL B$ THEN GOTO 1000
890 LET T=0
910 IF A$(A,B)="H" THEN GOTO 1370
930 IF A$(A,B)<>" " THEN GOTO 1000
950 LET T=T+3*RND+(B<F)+(B=1)
960 IF T<M THEN GOTO 1000
970 LET Y=B
980 LET X=A
990 LET M=T
1000 NEXT V
1010 NEXT W
1030 IF NOT M THEN GOTO 1430
1050 LET A$(E,F)=" "
1060 LET A$(X,Y)="F"
1080 LET G=ABS (E-X)>1
1090 IF G THEN GOSUB 1500
1100 LET E=X
1110 LET F=Y
1130 IF Y=1 THEN GOTO 1470
1150 IF G OR I THEN GOSUB 1500
1160 GOTO 550
1180 IF INKEY$<>"" THEN GOTO 1180
1190 LET C$=INKEY$
1200 IF C$="" THEN GOTO 1190
1210 IF C$>"8" OR C$<"1" THEN GOTO 1180
1220 PRINT C$;
1230 LET Z=CODE C$-28
1240 RETURN
1260 LET Z=3*(X-1)
1270 PRINT AT 3*(8-Y),Z;"/ :.";TAB Z;"% % : ";TAB Z;": :"
1280 RETURN
1300 LET Z=3*(E-1)
1310 PRINT AT 3*(8-F),Z;":. .:";TAB Z;":'% ':";TAB Z;"':% :'"
1320 RETURN
1340 LET Z=3*(X-1)
1350 PRINT AT 3*(8-Y),Z;" ";TAB Z;" ";TAB Z;" "
1360 RETURN
1380 LET A=V+(V-E)
1390 LET B=W+(W-F)
1400 IF VAL B$ THEN GOTO 1000
1410 LET T=RND
1420 GOTO 920
1430 GOSUB 1290
1440 PRINT AT 8,25;"%Y%O%U %W%O%N"
1450 PAUSE 40000
1460 GOTO 20
1470 GOSUB 1290
1480 PRINT AT 8,25;"%I %W%I%N"
1490 GOTO 1450
1510 FOR A=1 TO 4
1520 GOSUB 1290
1530 FOR B=1 TO 4
1540 NEXT B
1550 PRINT AT 3*(8-F),Z;" ";TAB Z;" ";TAB Z;" "
1560 NEXT A
1570 RETURN
1580 CLEAR
1590 SAVE "1025%9"
1600 RUN
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.
