--- title: "SUB" id: 70834 type: "computer_media" slug: "sub" url: "http://localhost/computer_media/sub/" markdown_url: "http://localhost/computer_media/sub.md" published_at: "2026-08-23T15:07:59+00:00" modified_at: "2026-08-23T23:37:06+00:00" author: "David Anderson" featured_image: url: "http://localhost/wp-content/uploads/2022/04/20230809-041332.jpg" excerpt: "Pilot a submarine, fire torpedoes at enemy ships, and dodge their depth charges in this action game featuring custom-drawn UDG sprites and randomized enemy speeds." category: - name: "Archived Media" slug: "archived-media" taxonomy: "category" url: "http://localhost/category/archived-media/" post_tag: - name: "Downloadable" slug: "downloadable" taxonomy: "post_tag" url: "http://localhost/tag/downloadable/" - name: "TS 2068" slug: "ts2068" taxonomy: "post_tag" url: "http://localhost/tag/ts2068/" model: - name: "Timex/Sinclair 2068" slug: "ts-2068" taxonomy: "model" url: "http://localhost/model/ts-2068/" genre: - name: "Game" slug: "game" taxonomy: "genre" url: "http://localhost/type/game/" media_type: "Program" download_url: "https://archive.org/download/timex-sinclair-software-archive/SUB%20%28198x%29%28-%29%28TS2068%29%28US%29%28Program%29.zip" tsrun_member: "SUB (198x)(-)(TS2068)(US)(Program).tap" mediadate: "198x" media_type_tags: "Game" --- # SUB 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\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 (`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 ```