Give O to the computer. Its first rule will be modest: choose the first empty square in numerical order. A predictable opponent makes a useful starting point because we can check every choice.
Replace lines 20, 320, 330, 340, 1060.
Add lines 600, 610, 620, 630, 640, 650, 660, 670, 4000, 4010, 4020, 4030.
Delete lines 7000, 7010.
To delete a BASIC line, enter its number on its own.
20 DIM b(9): LET moves=0
320 LET b(n)=1: LET moves=moves+1: GO SUB 2000
330 GO SUB 3000
340 IF winner>0 OR moves=9 THEN GO TO 4900
600 PRINT AT 18,2; INK 6;"O is choosing... "
610 GO SUB 4000
620 LET n=mv: LET b(n)=2: LET moves=moves+1: GO SUB 2000
630 PRINT AT 19,2; INK 5;"O chose ";mv;": ";a$;" "
640 GO SUB 3000
650 IF winner>0 OR moves=9 THEN GO TO 4900
660 PRINT AT 18,2; INK 7;"Your move: choose 1-9. "
670 GO TO 250
1060 PRINT AT 18,2; INK 7;"Your move: choose 1-9."
4000 LET mv=0
4010 FOR j=1 TO 9
4020 IF mv=0 AND b(j)=0 THEN LET mv=j
4030 NEXT j: LET a$="first empty": RETURNRUN the changed program.
Complete source at this checkpoint
10 BORDER 0: PAPER 0: INK 7: BRIGHT 1: CLS
20 DIM b(9): LET moves=0
30 GO SUB 1000
40 GO TO 250
250 GO SUB 9000
260 LET k$=INKEY$: IF k$="" THEN GO TO 260
270 IF k$="q" OR k$="Q" THEN GO TO 8000
280 IF k$="r" OR k$="R" THEN RUN
290 IF k$<"1" OR k$>"9" THEN GO TO 250
300 LET n=VAL k$
310 IF b(n)<>0 THEN PRINT AT 18,2; INK 6;"Taken. Choose an empty square.": GO TO 250
320 LET b(n)=1: LET moves=moves+1: GO SUB 2000
330 GO SUB 3000
340 IF winner>0 OR moves=9 THEN GO TO 4900
600 PRINT AT 18,2; INK 6;"O is choosing... "
610 GO SUB 4000
620 LET n=mv: LET b(n)=2: LET moves=moves+1: GO SUB 2000
630 PRINT AT 19,2; INK 5;"O chose ";mv;": ";a$;" "
640 GO SUB 3000
650 IF winner>0 OR moves=9 THEN GO TO 4900
660 PRINT AT 18,2; INK 7;"Your move: choose 1-9. "
670 GO TO 250
1000 CLS
1010 PRINT AT 0,8; INK 5;"THREE IN A ROW"
1030 INK 1: FOR i=0 TO 3: PLOT 56 +i*48,32: DRAW 0,120: NEXT i
1040 FOR i=0 TO 3: PLOT 56,32 +i*40: DRAW 144,0: NEXT i
1050 FOR n=1 TO 9: GO SUB 2000: NEXT n
1060 PRINT AT 18,2; INK 7;"Your move: choose 1-9."
1090 PRINT AT 21,2; INK 7;"1-9: place R: reset Q: quit"
1100 RETURN
2000 LET row=INT ((n-1)/3): LET col=n-1 -row*3
2010 LET x=80 +col*48: LET y=132 -row*40
2020 PRINT AT 5 +row*5,10 +col*6; INK 7;" "
2030 IF b(n)=0 THEN PRINT AT 5 +row*5,10 +col*6; INK 7;n: RETURN
2040 IF b(n)=2 THEN GO TO 2100
2050 INK 5: FOR z=0 TO 1: PLOT x-10 +z,y-10: DRAW 20,20: PLOT x-10 +z,y+10: DRAW 20,-20: NEXT z
2060 RETURN
2100 INK 7: FOR z=0 TO 1: PLOT x-6,y+10 -z: DRAW 12,0: DRAW 4,-4: DRAW 0,-12: DRAW -4,-4: DRAW -12,0: DRAW -4,4: DRAW 0,12: DRAW 4,4: NEXT z
2110 RETURN
3000 LET winner=0: LET wl=0: RESTORE 9500
3010 FOR w=1 TO 8: READ p,q,f
3020 IF b(p)<>0 AND b(p)=b(q) AND b(q)=b(f) THEN LET winner=b(p): LET wl=w
3030 NEXT w: RETURN
4000 LET mv=0
4010 FOR j=1 TO 9
4020 IF mv=0 AND b(j)=0 THEN LET mv=j
4030 NEXT j: LET a$="first empty": RETURN
4900 LET r$="DRAW. No line completed."
4910 IF winner=1 THEN LET r$="X WINS. Three in a row."
4920 IF winner=2 THEN LET r$="O WINS. Three in a row."
4930 IF wl>0 THEN GO SUB 6000
4940 PRINT AT 18,2; INK 6;r$;" "
5000 GO SUB 9000
5010 LET k$=INKEY$: IF k$="" THEN GO TO 5010
5020 IF k$="q" OR k$="Q" THEN GO TO 8000
5030 IF k$="r" OR k$="R" THEN RUN
5040 GO TO 5010
6000 RESTORE 9500: FOR w=1 TO wl: READ p,q,f: NEXT w
6010 LET row=INT ((p-1)/3): LET col=p-1 -row*3
6020 LET sx=80 +col*48: LET sy=132 -row*40
6030 LET row=INT ((f-1)/3): LET col=f-1 -row*3
6040 INK 6: PLOT sx,sy: DRAW 80 +col*48 -sx,132 -row*40 -sy: RETURN
8000 PAPER 0: INK 7: BRIGHT 0: PRINT AT 21,2;"Three in a Row finished. ": STOP
9000 IF INKEY$<>"" THEN GO TO 9000
9010 RETURN
9500 DATA 1,2,3,4,5,6,7,8,9
9510 DATA 1,4,7,2,5,8,3,6,9
9520 DATA 1,5,9,3,5,7Play 1. O should choose 2 and explain first empty. Play 5; O takes 3. Play 9 and X wins the diagonal. There must be no extra O move after that win.
A routine returns a choice
The routine at 4000 starts mv at zero, meaning “no move selected”. It visits squares 1–9. When mv is still zero and b(j) is empty, it stores j in mv. Later empty squares do not replace that first choice. NEXT stays outside the conditional line.
The routine chooses but does not place. On returning, line 620 copies mv to n, stores O in b(n), increases moves and calls the cell drawing routine. Line 630 prints the selected square and a$, the reason supplied by the choosing routine.
The main loop now has two move paths. Human input always stores 1. O’s path always stores 2. Both draw the accepted mark before calling the result scan, and both check whether the round has ended before handing over. We can remove the two-player turn variable and caption routine because these paths establish whose turn it is.
A promise about the board
The choosing routine is called only when there is an empty square and the game has no winner. Under those conditions it must return a legal square. If a full board reached it, mv would remain zero and b(0) would be invalid. The end checks are therefore part of the routine’s calling contract, not optional decoration.
The display says that O is choosing, then keeps its reason on the row below your prompt. A longer message is padded with spaces so a shorter later reason leaves no stale letters behind.
Why not let every empty square replace mv in the loop?
Show the explanation
That would choose the last empty square instead of the first. Either can be a legal policy, but our stated rule and displayed reason must agree with the code. The mv = 0 guard preserves the first choice.
Try an occupied key after O replies, then hold a number through its next turn. Neither should grant another human move without a fresh valid press. R restarts with X; Q quits.
Sources
Steven Vickers, edited by Robin Bradbeer, ZX Spectrum BASIC Programming, second edition (Sinclair Research, 1983), chapter 4, chapter 5, chapter 13.
Run it here
This unit's program, built for you.
- Program
- three.tap
- Machine
- ZX Spectrum 48K (PAL)