Sub-Niner is a single-player number puzzle game where the player must reduce all nine digits of a number string to zero by repeatedly subtracting a randomly generated value. Each turn, a random number R (1–9, weighted toward smaller values via RND*RND) is displayed, and the player selects which digit position (1–9) to subtract it from; selecting position 0 skips the turn. If a digit would go below zero, a penalty subroutine adds 10 to the digit and increases the move count by R, simulating a borrow/wrap mechanic. The score is calculated as 101 minus the total number of moves taken, and a high-score table tracking the best player name and score persists across rounds within the same session.
Program Analysis
Program Structure
The program is divided into clearly separated functional blocks:
- Lines 10–60: Initialisation and high-score display; player name input.
- Lines 100–600: Screen setup — title, playing grid, column headers, player announcement.
- Lines 1000–1140: Main game loop — random number generation, digit selection, subtraction, and penalty handling.
- Lines 1150–1190: End-of-game display, high-score update trigger, and loop-back to the name/high-score screen.
- Lines 2000–2020: Penalty subroutine — applies a borrow (+10) and adds R to the move counter.
- Lines 3000–3020: High-score update subroutine — records winning player name and score.
- Lines 9998–9999: Save and auto-run block.
Gameplay Mechanics
The game string A$ is initialised to "999999999" (nine nines). Each turn, a random digit R is generated and displayed. The player presses a key (1–9) to subtract R from that digit position. Pressing 0 skips the turn without consuming a move counter increment — though M is still incremented at line 1080 before the skip check at line 1110, meaning skipping is not truly free. The game ends when all characters in A$ equal "0" (i.e., CHR$ 28 in internal representation — see below).
Character Code Arithmetic
This is the program’s most technically interesting idiom. Digits are stored as their Sinclair character codes (e.g., "9" = CHR$ 57 on Spectrum, but on ZX81/TS1000, digit ‘0’ = CHR$ 28, ‘1’ = CHR$ 29, …, ‘9’ = CHR$ 37). The variable A is computed as CODE A$(P) - R. The zero-check at line 1070 tests A$="000000000", meaning each character must be CHR$ 28. The INKEY$ handling at line 1090 uses CODE INKEY$ - 28 to extract the numeric value of the pressed digit key directly from its character code, confirming ZX81/TS1000 character encoding is in use.
Penalty / Borrow Subroutine
Lines 2000–2020 handle the case where subtracting R from a digit would take it below CHR$ 28 (i.e., below zero). The fix adds 10 to the character code (CODE A$(P) + 10 - R), wrapping the digit around, and penalises the player by adding R to the move count M. This subroutine is called both before the player’s choice is applied (line 1075, checking if A — still holding the last result — is under 28) and after computing the new value (line 1125).
Weighted Random Number Generation
Line 1050 uses INT (1 + RND*RND*9) rather than the simpler INT (1 + RND*9). Multiplying two independent uniform random values produces a triangular distribution skewed toward lower numbers, meaning small values of R appear more frequently. This subtly adjusts game difficulty.
Key BASIC Idioms
RAND(line 1000) without an argument re-seeds the random number generator from the system clock, ensuring different sequences each game.PAUSE 0is not used here; instead,INKEY$is polled in a tight loop (lines 1090–1100) to wait for a valid keypress.- The high-score check at line 1180 uses
IF HS < 101-M THEN GOSUB 3000— a clean conditional subroutine call pattern. - Screen layout uses multiple
ATclauses within a singlePRINTstatement (line 300) to efficiently draw the board.
Bugs and Anomalies
| Location | Issue |
|---|---|
| Line 1075 | IF A<28 THEN GOSUB 2000 is executed before the player has chosen a position for the current turn. On the very first iteration A=28 (set at line 1030), so this is safe initially, but on subsequent loops A holds the result from the previous turn, potentially triggering an erroneous penalty call. |
| Line 1080 | The move counter M is incremented before checking if the player pressed 0 to skip (line 1110). Pressing 0 costs a move. |
| Line 1030 | A is initialised to 28 to prevent a false penalty trigger on the first pass of line 1075, but this value is a bare character code magic number with no comment. |
Display and Inverse Video
The title “SUB-NINER” at line 100 is rendered entirely in inverse video using the %X escape for each character, producing a solid highlighted title bar. The board background uses a string of inverse spaces (B$ at line 200) to draw filled rectangular cells across five rows, creating a visual grid for the nine digit positions.
Content
Source Code
10 LET C$=""
20 LET HS=0
30 IF LEN C$>0 THEN PRINT AT 5,0;"HIGH SCORE - ";C$;" - ";HS
50 PRINT AT 20,0;"WHAT IS YOUR NAME?"
60 INPUT N$
100 PRINT AT 2,7;"% %S%U%B%-%N%I%N%E%R% "
200 LET B$="% % % % % % % % % % % "
300 PRINT AT 9,7;B$;AT 10,7;B$;AT 11,7;B$;AT 12,7;B$;AT 13,7;B$
400 PRINT AT 8,8;"123456789"
600 PRINT AT 20,0;N$;" IS NOW PLAYING."
\n1000 RAND
\n1020 LET A$="999999999"
\n1030 LET A=28
\n1040 LET M=0
\n1050 LET R=INT (1+RND*RND*9)
\n1060 PRINT AT 10,8;A$;AT 12,12;R
\n1070 IF A$="000000000" THEN GOTO 1150
\n1075 IF A<28 THEN GOSUB 2000
\n1080 LET M=M+1
\n1085 PRINT AT 2,20;"SCORE = ";101-M;" "
\n1090 LET P=CODE INKEY$-28
\n1100 IF P<0 OR P>9 THEN GOTO 1090
\n1110 IF P=0 THEN GOTO 1060
\n1120 LET A=CODE A$(P)-R
\n1125 IF A<28 THEN GOSUB 2000
\n1130 LET A$(P)=CHR$ A
\n1140 GOTO 1050
\n1150 PRINT AT 18,0;"FINAL SCORE = ";101-M;AT 20,0;"PRESS ENTER TO PLAY AGAIN"
\n1160 INPUT Z$
\n1170 CLS
\n1180 IF HS<101-M THEN GOSUB 3000
\n1190 GOTO 30
\n2000 LET A=CODE A$(P)+10-R
\n2010 LET M=M+R
\n2020 RETURN
\n3000 LET C$=N$
\n3010 LET HS=101-M
\n3020 RETURN
\n9998 SAVE "SUB-NINE%R"
\n9999 RUN
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.
