Ocean Defense is a submarine warfare game in which the player maneuvers a ship across the top of the screen, dropping depth charges to destroy enemy subs while intercepting missiles they launch at the home base. The program uses 17 custom UDG (User Defined Graphics) characters, stored from USR “A” onward, which are saved and loaded separately as a CODE block. Scrolling sea-lane rows are implemented by rotating string arrays — A$(1) and A$(3) shift left by one character per frame, while A$(2) shifts right — creating a parallax effect across three rows. Difficulty selection sets the variable SK, which modifies missile evasion probability in the subroutine at line 1615 and affects scoring. The end-of-mission statistics screen at line 3000 renders icons for destroyed subs, missed missiles, and depth charge counts in a tabular layout, and computes a percentage rating using a formula combining raw score, accuracy, and mission number.
Program Structure
The program is organized into a set of functional blocks separated by line number ranges:
- Lines 10–200: Initialization, setup, and main intro/title screen.
- Lines 690–790: Main game loop — scrolls sea rows, draws sprites, checks input, dispatches to subroutines.
- Lines 800–830 (GO SUB 800): Draws the player’s ship moving across the screen.
- Lines 900–930 (GO SUB 900): Status bar update; checks home base destruction condition.
- Lines 1000–1040 (GO SUB 1000): Fires a depth charge (if charges remain).
- Lines 1100–1160 (GO SUB 1100): Moves the depth charge downward; detects collisions via
SCREEN$andATTR. - Lines 1400–1470: Submarine destruction sequence with animated explosion using INK cycling.
- Lines 1500–1570: Handles depth charge hitting sea-row graphics (near-miss/sea damage logic).
- Lines 1600–1650 (GO SUB 1600): Randomly generates a missile launch from a submarine row.
- Lines 1700–1840 (GO SUB 1700): Moves missile upward toward the home base ship.
- Lines 1900–1920: Missile misses — increments
MM(missiles missed). - Lines 2000–2030: Wave completion — flash effect via
RANDOMIZE USR 23769, advance to next stage. - Lines 2100–2140: Missile hits ship; ship loss logic, game-over if ships depleted.
- Lines 3040–3300: End-of-mission debrief screen with statistics, rating calculation, and graphical tallying.
- Lines 4010–4150 (GO SUB 4000): Title/intro screen with difficulty selection loop.
- Lines 7005–7070: UDG definition utility (not called during normal gameplay; used to create the custom character set).
- Lines 8010–8040: SAVE/LOAD bootstrap for game code and UDG CODE block.
Scrolling Sea Rows
Three rows of sea graphics are stored as 32-character strings in the array A$. Each game loop iteration, the strings are rotated in-place:
A$(1)andA$(3)scroll left:LET A$(1)=A$(1,2 TO )+A$(1,1)A$(2)scrolls right:LET A$(2)=A$(2,32)+A$(2, TO 31)
This gives a simple but effective parallax impression across the three submarine lanes, printed at rows 8, 13, and L (a randomly chosen row between 15 and 19). The three lanes are printed in different INK colors based on the stage number (ST), so later waves appear in different colors.
Custom UDG Characters
The game relies on 17 custom UDGs (characters A through Q, codes 144–160), loaded at startup from a separately saved CODE block starting at USR "A". The utility at lines 7005–7070 was used to define each glyph by POKEing 8 bytes per character. In-game, these UDGs represent ship parts, sub graphics, missile sprites, explosion tiles, and sea-wave block graphics. The ship display at line 180 uses a 32-character string of UDG references to render the home base across the bottom of the play area.
Collision Detection
The game uses two Spectrum BASIC collision methods:
SCREEN$ (X,Y)<>" "— detects whether the depth charge has hit any visible character at its current position (line 1130).ATTR (X,Y)=78— checks the attribute byte at the collision cell; value 78 corresponds to BRIGHT 0, PAPER 1, INK 6, which identifies submarine cells specifically, routing to the destroy sequence at line 1400 rather than the near-miss handler (line 1510).
Machine Code Usage
Two ROM addresses are called via RANDOMIZE USR:
RANDOMIZE USR 23769— calls the Spectrum’s TV interrupt handler / frame flash routine, used to create rapid screen flash effects during wave completion (line 2010) and missile hits (line 2120, 2140).RANDOMIZE USR 23792— called in the home-base destruction sequence (line 2140), producing a different visual effect.
POKE 23779,N writes to the BORDCR system variable, changing the border and lower screen paper color directly, used for flash effects at line 2010.
POKE 23658,8 at line 10 sets FLAGS2 to disable the lower-case shift lock, ensuring consistent keyboard input.
Difficulty System
The variable SK (1 = Admiral, 2 = Sea Cadet) is set at line 4120 via INKEY$ key detection using the Boolean arithmetic idiom. SK affects gameplay in several places:
- Line 40:
DD=44+(11 AND SK=2)— Sea Cadets start with 55 depth charges; Admirals get 44. - Line 1615:
IF R<0+(.66 AND SK=1)+(.83 AND SK=2) THEN RETURN— At Admiral difficulty, missiles fire 34% of the time; at Cadet, only 17%. - Line 920: Home base is destroyed after
(SK+1)*3missiles reach it — 6 for Admiral, 9 for Cadet. - Lines 3050, 3290: Difficulty label displayed on debrief screen.
Boolean Arithmetic Idiom
The program makes extensive use of the Sinclair BASIC convention that logical expressions evaluate to 1 (true) or 0 (false), enabling compact conditional assignment without IF statements. Examples:
LET DD=44+(11 AND SK=2)— adds 11 depth charges for Cadet mode.LET S=S+(1 AND X=8)+(2 AND X=13)+(3 AND X=L)— awards different scores depending on which sub row was hit.LET XX=1+(1 AND X=13)+(2 AND X=L)— identifies which sea row array to damage.(" \q" AND DD<>0)— prints a depth charge icon only if charges remain.
Rating Calculation
The end-of-mission percentage rating at lines 3240–3250 uses a multi-factor formula:
K1 = S * 0.267— score contribution.O = (DC-(SU+MD))/(SU+MD)^0.5— accuracy penalty based on depth charges used versus kills.K2 = 20 - (O*3)— accuracy component.K3 = 20 - MM— penalty for missiles that reached home base.K4 = ST*6— bonus for completing more waves.
The final rating K = INT(K1+K2+K3+K4) is displayed as a percentage. Note that negative values of O or MM exceeding 20 can push K2 or K3 negative, naturally penalizing poor play.
Bugs and Anomalies
- Line 2020 contains the typo
"MISSION COMPLEATED"(should be “COMPLETED”). This appears in the win condition message. - Lines 50 and 130 are referenced by
GO TO 50(line 2030) andGO TO 130(line 2130) but are not present in the listing. Line 30 and line 140 are the closest existing lines; this likely causes a “Line does not exist” error on wave advance or ship loss, unless these lines exist in the actual loaded program but were omitted from this listing. - Line 1110 contains
IF P=1 THEN LET X=X+1, butPis also used as the loop variable in theFOR P=0 TO 20at line 2010, which will overwrite the depth charge direction variable. This could cause incorrect depth charge behavior if lines 2010 and 1110 interact. - The missile movement at line 1705 uses
LET X1=X1-.5and line 1710 checksIF X1=3. Since X1 decrements by 0.5, it will pass through 3.0 correctly if it started at an odd integer plus 0.5, but may skip the value 3 exactly depending on initial conditions — a potential off-by-one in missile arrival detection. - Line 3050 has a trailing
'character after the closing quote, which in Spectrum BASIC is a newline token within a PRINT statement. This is likely a spurious character but is harmless.
Save/Load Architecture
The program is designed to be saved as two separate blocks: the BASIC program saved with LINE 8040 to auto-run, and the UDG data saved as a 136-byte CODE block (17*8 bytes from USR "A"). Line 8040 loads the CODE block before running, ensuring the custom characters are in place before the title screen displays.
Source Code
1 REM Px RETURN IF <>&> COPY OPEN # RETURN STEP FORMAT \\> OPEN # RETURN STEP FORMAT \\%STICK RETURN GO TO <>> OPEN # RETURN = CLS <>
10 POKE 23658,8: GO SUB 4000
30 LET ST=0: LET SU=0: LET MM=0: LET DC=0: LET MD=0
40 LET DD=44+(11 AND SK=2): LET SS=3: LET S1=0: LET S=0
60 DIM A$(3,32): LET ST=ST+1: LET PP=0: IF RND>.5 THEN LET PP=5
70 LET A$(1)=" \h\i\j \h▄\i\j \h\b\i\j "
80 LET a$(2)=" \k▄\i▄\m \k▄\i▄\m "
90 LET a$(3)=" \h▄\i▄\j \h▄\b\i\j "
140 BRIGHT 1: PAPER 1: INK 7: BORDER 0: CLS
150 FOR n=0 TO 3: PRINT AT N,0; PAPER PP;" ": NEXT N
160 IF PP=0 THEN FOR N=0 TO 40: LET Y=INT (RND*24)+151: LET X=INT (RND*255): PLOT X,Y: NEXT N
170 LET X=5: LET Y=0: LET M=0: LET P=-1: LET D=0: LET B=1
180 PRINT AT 21,0; INK 6;"\a\e\d\d\e\q\e\e\p\e\e\a\d\d\d\e\e\q\e\d\d\d\e\d\a\e\e\a\e\d\d\e"
190 GO SUB 900: LET L=INT (RND*5)+15
200 PRINT #1;AT 1,0; PAPER 2;D$
690 LET A$(1)=A$(1,2 TO )+A$(1,1)
700 LET A$(2)=A$(2,32)+A$(2, TO 31)
710 LET A$(3)=A$(3,2 TO )+A$(3,1)
720 PRINT AT 8,0; INK 7;A$(1);AT 13,0; INK (ST+1);A$(2);AT L,0; INK (ST+2);A$(3)
730 GO SUB 800
740 IF INKEY$="1" THEN GO SUB 1000: REM CHANGE 255 TO 191 IF YOU HAVE AN ISSUE 3 SPECTRUM
750 IF M=0 THEN GO SUB 1600
760 IF M=1 THEN GO SUB 1700
770 IF D=1 THEN GO SUB 1100
780 IF INKEY$="0" THEN GO SUB 800
790 GO TO 300
810 PRINT AT 3,B; PAPER PP; INK 3;(" \q" AND DD<>0);(" " AND DD=0); INK 0+(7 AND PP=0);"\a\b\c\d\e";AT 4,B; INK 2; PAPER 1;" \f▀▀▀▀\g": LET B=B+1
820 IF B=26 THEN PRINT AT 3,B; PAPER PP;" ";AT 4,B; PAPER 1;" ": LET B=0
830 RETURN
910 PRINT #1;AT 0,0; INK 6; PAPER 0;"LOST:";MM;" D/C:";DD;" SHIPS :";SS;" SCORE:";S;" "
920 IF MM=(SK+1)*3 THEN LET M$=" HOME BASE DESTROYED ": GO TO 2140
930 RETURN
1010 IF DD=0 THEN RETURN
1020 LET D=1: LET DD=DD-1: LET DC=DC+1: BEEP .01,20: IF D=1 THEN PRINT AT X,Y;" "
1030 PRINT AT 3,B; PAPER PP;" ";AT 2,B-1; INK 3;"\q": BEEP .01,25: PRINT AT 2,B-1; PAPER PP;" ";AT 3,B-2; INK 3;"\p";AT 3,B-2;" "
1040 GO SUB 900: LET X=4: LET Y=B-1: RETURN
1110 PRINT AT X,Y;" ": BEEP .009,-X: IF P=1 THEN LET X=X+1
1120 IF X=20 THEN BEEP .1,-40: LET D=0: RETURN
1130 IF SCREEN$ (X,Y)<>" " THEN GO TO 1500
1150 PRINT AT X,Y; INK 3;("\q" AND p=-1);("\p" AND P=1)
1160 LET p=-P: RETURN
1420 LET MD=MD+1: LET S=S+3
1450 FOR N=7 TO 1 STEP -1: PRINT AT X1,Y1; PAPER 1; INK N;"\l";AT X1+1,Y1;"\l";AT X1+2,Y1;"\l": BEEP .02,0: NEXT N
1460 PRINT AT X1,Y1;" ";AT X1+1,Y1;" ";AT X1+2,Y1;" ": LET D=0: LET M=0
1470 GO SUB 900: RETURN
1500 IF ATTR (X,Y)=78 THEN GO TO 1400
1510 LET OV=0: LET D=0: LET SU=SU+1: BEEP .1,40:
1520 LET H=Y-5: LET J=Y+5: IF H<1 THEN LET OV=1: LET H=1
1530 LET XX=1+(1 AND X=13)+(2 AND X=L): LET A$(XX,H TO J)=" ": IF OV=1 THEN LET A$(XX,30 TO 32)=" "
1540 PRINT AT X,Y-1; BRIGHT 1; FLASH 1; INK 2; PAPER 6;"\l\l\l"
1550 LET S=S+(1 AND X=8)+(2 AND X=13)+(3 AND X=L)
1560 LET S1=S1+1: IF S1=ST*7 THEN GO TO 2000
1570 GO SUB 900: RETURN
1610 LET T=INT (RND*2): LET X1=0+(13 AND T=0)+(L AND T=1): LET Y1=INT (RND*24)+1
1615 LET R=RND: IF R<0+(.66 AND SK=1)+(.83 AND SK=2) THEN RETURN
1620 IF SCREEN$ (X1,Y1)=" " OR X1=0 THEN RETURN
1630 LET C=1: LET X1=X1-2
1640 PRINT AT X1+1,Y1; INK 6;"\n": BEEP .08,-20: PRINT AT X1,Y1; INK 6;"\n";AT X1+1,Y1;"\o"
1650 LET M=1: LET X1=X1-1
1705 PRINT AT X1+2,Y1; PAPER C;" ": LET X1=X1-.5
1710 IF X1=3 THEN GO TO 1800
1720 IF X1<=-1 THEN GO TO 1900
1730 PRINT AT X1,Y1; INK 6; PAPER C;"\n";AT X1+1,Y1;"\o";AT X1+2,Y1; INK 2;"\l"
1740 RETURN
1810 IF Y1>=B-1 AND Y1<B+6 THEN GO TO 2100
1820 PRINT AT X1,Y1; PAPER PP; INK 6;"\n";AT X1+1,Y1; INK 6; PAPER 1;"\o";AT X1+2,Y1; INK 2;"\l"
1830 LET X1=X1-1: PRINT AT X1+3,Y1;" ";AT X1+2,Y1;" ";AT X1,Y1; INK 6; PAPER PP;"\n";AT X1+1,Y1;"\o"
1840 LET X1=X1-1: LET C=PP: RETURN
1910 LET MM=MM+1: GO SUB 900
1920 PRINT AT X1+1,Y1; PAPER PP;" ": LET M=0: RETURN
2010 FOR N=6 TO 1 STEP -1: FOR P=0 TO 20: RANDOMIZE USR 23769: POKE 23779,N: NEXT P: NEXT N
2020 IF ST=3 THEN LET M$="MISSION COMPLEATED": LET S=S+50: GO TO 3000
2030 BORDER 0: CLS : GO TO 50
2110 LET MD=MD+1: LET S=S+3
2120 POKE 23779,1: FOR N=1 TO 60: RANDOMIZE USR 23769: NEXT N: BORDER 0: LET SS=SS-1: IF SS=0 THEN GO TO 3000
2130 GO TO 130
2140 PRINT AT 1,5; FLASH 1;M$: FOR N=0 TO 250: RANDOMIZE USR 23792: NEXT N
3040 BRIGHT 0: PAPER 1: INK 0: BORDER 0: CLS : PRINT AT 0,0; INK 6; PAPER 2;D$
3050 PRINT AT 3,3; PAPER 6; INK 0;" MISSION OVER -";(" CADET " AND SK=2);(" ADMIRAL " AND SK=1) '
3060 PRINT AT 5,1; PAPER 7; INK 0;" SUBS DEST LOST D/C "
3065 PAPER 7: FOR N=7 TO 17: PRINT AT N,1; PAPER 7;" ": NEXT N
3070 LET X=0: LET Y=1
3080 FOR N=7 TO 17: IF X=SU THEN GO TO 3110
3090 PRINT AT N,Y; INK 2;"\k▄\i\m": LET X=X+1
3100 NEXT N: LET Y=Y+5: GO TO 3080
3110 LET X=0: LET Y=12
3120 FOR N=7 TO 15 STEP 2: IF X=MD THEN GO TO 3160
3130 PRINT AT N,Y; INK 2;"\n";AT N+1,Y;"\o": LET X=X+1
3140 NEXT N: LET Y=Y+1: GO TO 3120
3150 LET X=X+2: GO TO 3120
3160 LET X=0: LET Y=19
3170 FOR N=7 TO 15 STEP 2: IF X=MM THEN GO TO 3200
3180 PRINT AT N,Y; INK 4;"\n";AT N+1,Y;"\o": LET X=X+1
3190 NEXT N: LET Y=Y+1: GO TO 3170
3200 LET X=0: LET Y=26
3210 FOR N=7 TO 17: IF X=DC THEN GO TO 3240
3220 PRINT AT N,Y; INK 3;"\q": LET X=X+1
3230 NEXT N: LET Y=Y+1: GO TO 3210
3240 LET K1=S*0.267: LET O=(DC-(SU+MD))/(SU+MD)^.5: LET K2=(20-(O*3)): LET K3=(20-MM): LET K4=(ST*6)
3250 LET K=INT (K1+K2+K3+K4)
3260 PRINT AT 19,0; PAPER 6;" SCORE : RATING : % "
3270 PRINT AT 19,11; FLASH 1;S;AT 19,25;K
3290 PRINT #1;" MISSION :"; PAPER 2; FLASH 1;("UN" AND SU<=20);"SUCCESSFUL"
3300 PAUSE 0: RUN
4010 BRIGHT 0: PAPER 0: BORDER 1: INK 5: CLS
4020 LET D$=" OCEAN DEFENCE "
4030 PRINT AT 0,0; PAPER 0; INK 7; FLASH 1;D$
4040 PRINT AT 3,3;"Destroy the marauding subs with Depth Charges, while defending your Home Base from the Missiles they launch...!"
4050 PRINT AT 10,2;"D/Charges.......Top Row/Left"
4060 PRINT AT 12,2;"Extra Knots....Top Row/Right"
4070 PRINT AT 21,0; PAPER 7; INK 0;" Typed in by Ken Duda "
4080 PRINT AT 16,3;" Select Difficulty... "
4090 PRINT : PRINT " 1) Admiral of the Fleet"
4100 PRINT " 2) Sea Cadet "
4110 BEEP .1,35: PAUSE 55
4120 LET SK=0+(1 AND INKEY$="1")+(2 AND INKEY$="2")
4130 IF SK<>0 THEN RETURN
4140 FOR N=35 TO 34.45 STEP -.02: BEEP .02,N: NEXT N
4150 PAUSE 35: GO TO 4110
7005 LET B=65
7010 LET N=(B*8)+64848
7020 PRINT INVERSE 1;"GRAPHIC ";CHR$ B: PRINT
7030 FOR M=0 TO 7
7040 INPUT A: PRINT A
7050 POKE N+M,A
7060 NEXT M: BEEP .1,10: PRINT : LET B=B+1
7070 LET N=N+8: GO TO 7020
8010 SAVE "ocean def" LINE 8040
8020 SAVE "graphics"CODE USR "A",17*8
8030 VERIFY "": VERIFY ""CODE
8035 STOP
8040 LOAD ""CODE : RUN
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.
