Nim

Developer(s): Fred Nachbaur
Date: 1993
Type: Cassette
Platform(s): TS 1000
Tags: Game

Nim is a two-player strategy game in which players take turns removing matchsticks arranged in seven rows of seven. Each turn, a player selects a non-empty row and removes between one and seven sticks from it; the player left with the last stick loses. The program uses PEEK on system variables at addresses 16396–16397 to calculate the display file address, then POKEs directly into video memory to animate stick removal using a cycling sequence of block-graphic characters stored in B$. Player names are stored in a two-element string array N$(2,7), and pre-computed VAL literals for numeric constants reduce memory usage throughout.


Program Analysis

Program Structure

The program is organized into several logical blocks:

  1. Lines 10–110: Initialization — SLOW mode, constant definitions, player name input, and title screen.
  2. Lines 120–270: Subroutines — a delay loop (250–270), a display-file address calculator (200–210), and a validated 1–7 input routine (220–240).
  3. Lines 300–390: Game board setup and initial rendering — draws seven rows of seven “O” characters.
  4. Lines 500–810: Main game loop — prompts for row and count, validates input, POKEs sticks away, checks win/loss conditions.
  5. Lines 820–940: End-game handling — announces winner or loser, offers replay, and lets the winner choose who starts next.
  6. Lines 1000–1010: SAVE and auto-RUN.

Constant Pre-computation with VAL

Lines 20–70 define a set of numeric constants using VAL "n" assignments: N0=0, N1=1, N2=2, N7=7, O7=17, P1=21. Storing these in variables avoids repeatedly parsing numeric literals in the hot loop, and using VAL "n" rather than bare literals is a classic memory-saving idiom — the string representation can be shorter in tokenized form than the full floating-point constant record.

Direct Display-File Manipulation

The subroutine at line 200 computes the address of a specific character cell directly from the display-file base address obtained via PEEK 16396 + 256*PEEK 16397 (line 80, stored in DF). The formula DF + 38*H + 2*I + 1 (using the constant variables) converts row H and column I into a byte address, and line 670 POKEs successive character codes from B$ into that address. This directly writes to the video RAM rather than using PRINT, giving a simple animation effect as each stick is replaced by the characters ▞▚▞*. in sequence, ending with a space (erasing the stick).

Game Logic

The board state is tracked on two levels:

  • X$ — a 7-character string where each character is "1" (row has sticks) or "0" (row empty), updated at line 730.
  • N — the total number of sticks remaining, initialized to 49 (7×7) and decremented by the count taken each turn.

The inner loop at lines 600–720 scans from column 7 down to 1 to find the rightmost occupied stick in the chosen row, then iterates backward by the chosen count, POKEing each position. If the requested count exceeds the sticks available in the row, the “TOO MANY” message is shown and the player must re-enter. The game ends when N=0 (the taker of the last stick loses — line 820 calls them a fool) or N=1 before the current move (line 840 declares a win for the current player).

Turn Management and Replay

The current player is tracked by T, which alternates between 0 and 1 via NOT T (line 770). Player names are indexed as N$(T+1). After a game ends, lines 860–880 poll INKEY$ in a tight loop waiting for Y or N. If restarting, lines 890–930 ask whether player 2 should start, setting T directly from the boolean result of INKEY$="Y" — a neat one-liner that avoids an explicit IF branch.

Delay Subroutine

The subroutine at lines 250–270 runs a FOR loop from 0 to N7*O7 = 119, but exits early via RETURN if any key is currently pressed (INKEY$<>""). This provides a brief pause that can be skipped by the player, used after “ROW EMPTY” and “TOO MANY” error messages.

Potential Anomalies

  • Line 170 follows line 150 with no line 160, suggesting a deleted or renumbered line — this is harmless.
  • The stick-removal loop at line 640 reuses variable I (already used as the column scanner in line 600) as its own loop variable with FOR I=I TO I-S+1 STEP -1. This works correctly because the outer FOR I loop at line 600 has been exited by the time line 640 is reached (via the fall-through of line 620’s conditional NEXT).
  • Line 900 checks INKEY$<>"" and loops back to itself, acting as a “wait for key release” before reading the actual Y/N answer in line 910. However, the loop target uses GOTO VAL "900" rather than plain GOTO 900, consistent with the program’s memory-optimization style.
  • The win condition at line 760 checks N=1 after subtracting the sticks taken (line 740), meaning exactly one stick is left for the opponent — who will be forced to take it and lose. This correctly implements the “last stick loses” rule.

Content

Appears On

Related Products

Related Articles

Related Content

Image Gallery

Source Code

  10 SLOW
  20 LET N0=VAL "0"
  30 LET N1=VAL "1"
  40 LET N2=VAL "2"
  50 LET N7=VAL "7"
  60 LET O7=VAL "17"
  70 LET P1=VAL "21"
  80 LET DF=PEEK 16396+256*PEEK 16397
  90 LET T=N0
 100 DIM N$(N2,N7)
 110 PRINT AT N7,N7;"▌▌▌[*] [N][I][M] [*]▐▐▐",,,,"BY F.NACHBAUR",,,
 120 GOSUB VAL "250"
 130 FOR A=N1 TO N2
 140 PRINT "NAME-PLAYER ";A;"?"
 150 INPUT N$(A)
 170 NEXT A
 180 LET B$="▞▚▞*. "
 190 GOTO VAL "300"
 200 LET L=DF+38*H+N2*I+N1
 210 RETURN
 220 INPUT A
 230 IF A<N1 OR A>N7 THEN GOTO VAL "220"
 240 RETURN
 250 FOR A=N0 TO N7*O7
 255 IF INKEY$ <>"" THEN RETURN
 260 NEXT A
 270 RETURN
 300 CLS
 340 LET X$="1111111"
 350 LET N=N7*N7
 360 PRINT "[R][O][W]",
 370 FOR A=N1 TO N7
 380 PRINT ,,," ";A;"  ";"[O] [O] [O] [O] [O] [O] [O] ";
 390 NEXT A
 500 PRINT AT O7,N0;"YOUR TURN,";N$(T+N1);AT P1,N0;"WHICH ROW? "
 510 GOSUB 220
 520 IF X$(A)="1" THEN GOTO 560
 530 PRINT AT P1,N0;"█[R][O][W]█[E][M][P][T][Y][.]"
 540 GOSUB VAL "250"
 550 GOTO VAL "500"
 560 LET H=A
 570 PRINT AT P1,N0;"HOW MANY?  "
 580 GOSUB 220
 590 LET S=A
 600 FOR I=N7 TO N1 STEP -N1
 610 GOSUB 200
 620 IF PEEK L=N0 THEN NEXT I
 630 IF S>I THEN GOTO 790
 640 FOR I=I TO I-S+N1 STEP -N1
 650 GOSUB 200
 660 FOR A=N1 TO LEN B$
 670 POKE L,CODE B$(A)
 680 NEXT A
 720 NEXT I
 730 IF I=N0 THEN LET X$(H)="0"
 740 LET N=N-S
 750 IF N=N0 THEN GOTO 820
 760 IF N=N1 THEN GOTO 840
 770 LET T=NOT T
 780 GOTO 500
 790 PRINT AT P1,N0;"[T][O][O]█[M][A][N][Y][.]  "
 800 GOSUB VAL "250"
 810 GOTO 500
 820 PRINT AT O7,N0;"YOU""RE A FOOL,";N$(T+N1)
 830 GOTO VAL "850"
 840 PRINT AT O7,N0;"YOU WIN, ";N$(N1+T)
 850 PRINT AT P1,N0;"AGAIN? Y/N"
 860 IF INKEY$ ="Y" THEN GOTO 890
 870 IF INKEY$ ="N" THEN STOP
 880 GOTO 860
 890 PRINT AT P1,N0;N$(N2);" STARTS?"
 900 IF INKEY$ <>"" THEN GOTO VAL "900"
 910 LET T=(INKEY$ ="Y")
 930 IF INKEY$ <>"Y" AND INKEY$ <>"N" THEN GOTO 910
 940 GOTO VAL "300"
 1000 SAVE "NI[M]"
 1010 RUN 

Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.

Scroll to Top