SUB is a submarine combat game in which the player pilots a submarine across the bottom of the screen, firing torpedoes upward to destroy enemy ships passing overhead while dodging depth charges they drop. The game uses 21 custom User Defined Graphics (UDGs), defined via POKEs into USR memory at startup, to draw the submarine, torpedo, depth charge explosion, and five distinct enemy ship types (torpedo boat, destroyer, tanker, cruiser, and frigate). Each ship type carries a different point value (50 to 1000), and the player starts with 20 torpedoes, ending the game when both are exhausted. Ship speed is randomized using a fractional step value computed from RND, and collision detection uses SCREEN$ to test whether a torpedo has struck a ship’s cell. The depth charge system tracks a separate falling object (variables ddc and pdc) that causes instant death if it lands within five columns of the submarine.
Program Structure
The program is organized into several functional blocks accessed via GO TO and GO SUB:
- Lines 8000–9210: UDG initialization subroutine — called first at line 10, pokes 21 custom graphics into UDG memory.
- Lines 10–80: Global variable initialization and title splash screen.
- Lines 120–310: Instructions screen listing ship types, point values, and controls.
- Lines 365–760: Main game loop — draws the sea floor, selects a ship, and repeatedly updates ship position, player movement, torpedo, and depth charge state.
- Lines 900–910: Torpedo-ready subroutine — initializes torpedo position to the sub’s current column.
- Lines 1000–1040: Torpedo-in-flight subroutine — animates the torpedo upward and handles hit detection.
- Lines 2010–2050: Depth charge launch subroutine — records position and sets the drop flag.
- Lines 2500–2550: Depth charge fall subroutine — animates the charge and checks for sub collision.
- Lines 3000–3200 / 4000–4010: Game-over handlers for out-of-torpedoes and depth-charge death, respectively.
- Lines 5000–5040: Replay prompt.
UDG Sprite System
The subroutine at line 8000 loads 21 UDGs (characters 144–164, accessed as \a through \u) by reading 8 bytes each from the DATA statements at lines 9000–9210 and POKEing them into the address returned by USR CHR$ a. This gives the game a full set of custom pixel-art sprites without any machine code loader:
| UDG(s) | Role |
|---|---|
\a\b | Player submarine (2 chars wide) |
\u | Torpedo in flight |
\s | Depth charge in fall |
\t | Explosion graphic |
\k\l | Torpedo boat (1000 pts) |
\c\d\e | Destroyer (250 pts) |
\p\q\r | Tanker (250 pts) |
\m\n\o | Cruiser (500 pts) |
\f\g\h\i\j | Frigate (50 pts) |
Ship Selection and Speed Randomization
At line 510, five ship records are laid out as DATA pairs of (sprite string, point value). Lines 520–540 perform a partial READ loop, iterating a random number of times (1 to 6) to land on a random ship entry — a simple technique to pick from a weighted set without arrays. The ship’s movement step is computed at line 545 as 0.25 + (INT(RND*6)+1)/8, producing fractional pixel advances per frame for speed variation.
Main Game Loop
The main loop (lines 600–760) does not use a FOR/NEXT construct; instead it falls through to GO TO 600 at line 760. Each iteration:
- Redraws the score and torpedo count (line 600).
- Erases the old ship tail pixel with INK 5 (line 700) and prints the sub (line 705).
- Reads
INKEY$to move the sub left/right (line 710), using Boolean arithmetic (ANDreturning 1 or 0) instead ofIFbranches — a classic Spectrum BASIC optimization. - Fires or advances the torpedo via subroutines (lines 725–727).
- Conditionally triggers a depth charge drop when facing the torpedo boat (
val=1000) with probability 0.4 (line 730). - Advances the ship left by
stepand handles boundary conditions (lines 740–760).
Collision Detection via SCREEN$
Hit detection at line 1037 uses SCREEN$(tpy-1, post) to test whether the cell one row above the torpedo tip is non-space. This leverages the display file directly rather than tracking ship coordinates separately, which is efficient but ties correctness to what is actually rendered on screen at that moment.
Depth Charge Mechanics
The depth charge is tracked with variables pdc (column, set to shx at launch), ddc (current row, starting at 10), and sdc (active flag). The subroutine at 2500 animates it downward one row per main-loop iteration. At row 20, if ABS(pdc - sux) < 5, the sub is considered hit and the program branches to the death screen at line 4000; otherwise the charge just splashes and sdc is reset to 0.
Notable Techniques and Idioms
- Boolean movement:
sux = sux + (INKEY$="8" AND sux<30) - (INKEY$="5" AND sux>0)compresses two conditional increments/decrements into one assignment. - PRINT OVER 1: Used to erase moving objects (torpedo, depth charge) by overprinting with a UDG or block, avoiding a full
CLS. - Partial DATA walk for random selection: Reading a random number of records from a fixed
DATAlist to pick a ship, then callingRESTOREto reset the pointer. - Fractional screen coordinates:
tpyandshxare held as floating-point values and stepped by 0.5 or fractional amounts, with the integer part used inPRINT AT. - BEEP for feedback: Short
BEEPcalls with negative pitches simulate low-frequency sounds for torpedo and depth charge audio cues.
Bugs and Anomalies
- At line 530, the loop variable
ais reused as the loop counter for ship selection after having been used as the loop variable in theFOR a=1 TO 5sea-floor drawing loop (line 370). This works because theFORat line 520 reinitializes it, but the naming collision could cause confusion during debugging. - Line 600 prints a trailing space at
AT 0,26;" "only whentorp < 10, to overwrite the tens digit when the count drops to a single digit — a manual field-width fix rather than usingPRINT USING(not available in Spectrum BASIC). - The
GO SUB 2000at line 730 jumps to a non-existent line; the actual depth charge launch code starts at 2010. This is a deliberate no-op first line skip, harmless since BASIC searches forward to 2010. - The torpedo fires from the sub’s position at the moment the spacebar is pressed (captured into
postvia subroutine 900), but if the player moves after firing,suxandpostdiverge — the torpedo correctly stays in the column where it was launched.
Source Code
1 REM \a\b \k\l \c\d\e \p\q\r \m\n\o \f\g\h\i\j \u \s \t
10 GO SUB 8000: INK 0: BORDER 0: PAPER 5: LET score=0: LET sux=15: LET state=0: LET sdc=0: LET pdc=0: LET ddc=0: LET torp=20: LET tpy=0: CLS
70 PRINT AT 10,11;"submarine";AT 12,14;"\a\b"
80 PAUSE 100
120 CLS
130 PRINT TAB 11;"SUBMARINE"
140 PRINT TAB 11;"▀▀▀▀▀▀▀▀▀"
150 PRINT
160 PRINT "The idea of the game is to destroy enemy shipping But AVOID dept charges dropped by torpedo boats \k\l these spell instant";: INK 2: FLASH 1: PRINT "█ DEATH █": FLASH 0: INK 1
165 PRINT "left(5) right(8) "
170 PRINT "fire (space bar)": INK 1
190 PRINT "\k\l","1000 points"
200 PRINT
210 PRINT "\c\d\e","250 points"
220 PRINT
230 PRINT "\p\q\r","250 points"
240 PRINT
250 PRINT "\m\n\o","500 points"
260 PRINT
270 PRINT "\f\g\h\i\j","50 points"
280 INK 0: PRINT
290 PRINT " press any key to start"
300 PAUSE 0
310 CLS
365 INK 1
370 FOR a=10 TO 21
390 PRINT AT a,0;"████████████████████████████████"
410 NEXT a
500 LET shx=26
510 DATA "\f\g\h\i\j",50,"\c\d\e ",250,"\k\l ",1000,"\m\n\o ",500,"\p\q\r ",250
520 FOR a=1 TO (5*RND)+1
530 READ a$,val
540 NEXT a
545 LET step=.25+((INT (RND*6)+1)/8)
550 RESTORE
560 INK 5: PRINT AT 9,0;"███████"
600 PRINT AT 0,0;"SCORE:";score: PRINT AT 0,16;"TORPEDOS:";torp: IF torp<10 THEN PRINT AT 0,26;" "
700 INK 5: PRINT AT 9,shx+5;"█"
705 INK 1: PRINT AT 20,sux;"██"
710 LET sux=sux+(INKEY$="8" AND sux<30)-(INKEY$="5" AND sux>0)
715 PRINT OVER 1;AT 20,sux;"\a\b"
720 PRINT OVER 0;AT 9,shx;a$
725 IF INKEY$=" " AND state=0 THEN LET state=1: LET torp=torp-1
726 IF state=1 THEN GO SUB 1000
727 IF state=0 THEN GO SUB 900
728 IF torp<0 THEN GO TO 3000
729 IF sdc=1 THEN GO SUB 2500
730 IF sdc=0 AND val=1000 AND RND<.4 THEN GO SUB 2000
740 LET shx=shx-step
745 IF (shx<1 AND torp<1) THEN GO TO 3000
750 IF shx<1 THEN GO TO 500
760 GO TO 600
900 LET post=sux
905 LET tpy=18
910 RETURN
1000 INK 1: PRINT AT tpy+1,post;"█"
1030 PRINT OVER 1;AT tpy,post;"\u"
1034 IF tpy>10 THEN GO TO 1040
1035 IF tpy=10 THEN LET state=0
1036 IF tpy=10 THEN PRINT AT tpy,post;"█"
1037 IF tpy=10 AND SCREEN$ (tpy-1,post)<>" " THEN LET score=score+val: PRINT OVER 1;AT tpy-1,post;"\t": PAUSE 50: FOR y=1 TO 8: BEEP .05,10*RND+20: NEXT y: PRINT AT tpy-1,0;" ": GO TO 500
1040 LET tpy=tpy-.5: BEEP .08,-10: RETURN
2010 LET pdc=shx
2020 LET ddc=10
2030 LET sdc=1
2040 BEEP .1,-20
2050 RETURN
2500 INK 1: PRINT AT ddc-1,pdc;"█"
2510 PRINT OVER 1;AT ddc,pdc;"\s"
2520 IF ddc=20 THEN PRINT OVER 1;AT ddc,pdc;"\t": PAUSE 25: BEEP .25,-20: PRINT AT ddc,pdc;"█": LET sdc=0
2530 IF ddc=20 AND ABS (pdc-sux)<5 THEN PRINT AT 20,sux;"\t\t": BEEP .25,-30: PAUSE 25: PRINT AT 20,sux;"██": GO TO 4000
2540 LET ddc=ddc+1
2550 RETURN
3000 CLS
3100 PRINT "GAME OVER YOUR SCORE IS ";SCORE
3200 GO TO 5000
4000 INK 2: CLS : PRINT "DEPTH CHARGE GOT YOU"
4010 INK 0: PRINT "YOUR SCORE WAS:";score
5000 PRINT "DO YOU WANT TO REPLAY (Y or N)"
5010 INPUT a$
5020 IF a$="y" OR a$="Y" THEN RUN
5030 IF a$="n" OR a$="N" THEN STOP
5040 GO TO 5010
8000 RESTORE 9000
8010 FOR a=144 TO 164
8020 FOR b=0 TO 7
8030 READ c
8040 POKE (USR CHR$ a)+b,c
8050 NEXT b
8060 NEXT a
8070 RESTORE
8080 RETURN
9000 DATA 1,3,3,127,255,255,127,0
9010 DATA 0,192,192,248,255,255,248,0
9020 DATA 0,0,3,3,255,127,63,0
9030 DATA 8,60,60,253,255,255,255,0
9040 DATA 0,0,0,192,255,255,254,0
9060 DATA 0,0,0,24,255,127,63,31
9070 DATA 0,0,1,15,255,255,255,255
9080 DATA 1,255,249,255,255,255,255,255
9090 DATA 0,3,243,255,255,255,255,255
9100 DATA 0,0,192,236,255,255,254,252
9110 DATA 0,0,0,3,255,127,63,0
9120 DATA 0,0,192,224,255,255,254,0
9130 DATA 0,0,1,3,255,127,63,0
9140 DATA 4,30,62,255,255,255,255,0
9150 DATA 0,0,192,224,255,255,254,0
9160 DATA 0,0,3,55,255,127,63,0
9170 DATA 4,28,30,254,255,255,255,0
9180 DATA 0,0,192,230,255,255,254,0
9190 DATA 0,0,120,120,120,0,0,0
9200 DATA 137,74,42,0,255,255,125,0
9210 DATA 24,24,24,24,24,24,36,66
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.