SUB

Date: 198x
Type: Program
Platform(s): TS 2068
Tags: Game

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:

  1. Lines 8000–9210: UDG initialization subroutine — called first at line 10, pokes 21 custom graphics into UDG memory.
  2. Lines 10–80: Global variable initialization and title splash screen.
  3. Lines 120–310: Instructions screen listing ship types, point values, and controls.
  4. 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.
  5. Lines 900–910: Torpedo-ready subroutine — initializes torpedo position to the sub’s current column.
  6. Lines 1000–1040: Torpedo-in-flight subroutine — animates the torpedo upward and handles hit detection.
  7. Lines 2010–2050: Depth charge launch subroutine — records position and sets the drop flag.
  8. Lines 2500–2550: Depth charge fall subroutine — animates the charge and checks for sub collision.
  9. Lines 3000–3200 / 4000–4010: Game-over handlers for out-of-torpedoes and depth-charge death, respectively.
  10. 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\bPlayer submarine (2 chars wide)
\uTorpedo in flight
\sDepth charge in fall
\tExplosion graphic
\k\lTorpedo boat (1000 pts)
\c\d\eDestroyer (250 pts)
\p\q\rTanker (250 pts)
\m\n\oCruiser (500 pts)
\f\g\h\i\jFrigate (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 (AND returning 1 or 0) instead of IF branches — 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 step and 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 DATA list to pick a ship, then calling RESTORE to reset the pointer.
  • Fractional screen coordinates: tpy and shx are held as floating-point values and stepped by 0.5 or fractional amounts, with the integer part used in PRINT AT.
  • BEEP for feedback: Short BEEP calls with negative pitches simulate low-frequency sounds for torpedo and depth charge audio cues.

Bugs and Anomalies

  • At line 530, the loop variable a is reused as the loop counter for ship selection after having been used as the loop variable in the FOR a=1 TO 5 sea-floor drawing loop (line 370). This works because the FOR at line 520 reinitializes it, but the naming collision could cause confusion during debugging.
  • Line 600 prints a trailing space at AT 0,26;" " only when torp < 10, to overwrite the tens digit when the count drops to a single digit — a manual field-width fix rather than using PRINT USING (not available in Spectrum BASIC).
  • The GO SUB 2000 at 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 post via subroutine 900), but if the player moves after firing, sux and post diverge — 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.