REM. +-------------------------------------------------------------+
     REM. |                                                             |
     REM. |                       SubZap II                             |
     REM. |                                                             |
     REM. |                     Version  1.0a                           |
     REM. |                                                             |
     REM. |         Created with BBC BASIC for Windows 5.92a            |
     REM. |                                                             |
     REM. +-------------------------------------------------------------+

     REM
     REM. GFXLIB version 2.02 (or later) is required to run this program
     REM.

     REM. Disable Escape key; select 64-bit math mode
     
*ESC OFF
     *FLOAT 64

     REM. Reserve 5MB of RAM for this program
     
M% = 5
     HIMEM = LOMEM + M%*&100000
     HIMEM = (HIMEM + 3) AND -4

     REM. Set up simple error handler
     
ON ERROR PROCerror( REPORT$, TRUE )

     REM. Prevent the user from resizing the program window
     REM. (although they can still minimise it)
     
PROCfixWindowSize

     REM. Set window title text
     
ProgTitle$ = "SubZap II"
     ProgVersion$ = "1.0a"
     SYS "SetWindowText", @hwnd%, ProgTitle$ + " | v" + ProgVersion$

     REM. Set our program window's dimensions (640 x 480)
     
WinW% = 640
     WinH% = 480
     VDU 23, 22, WinW%; WinH%; 8, 16, 16, 0 : OFF

     
REM. Install and initialise the GFXLIB graphics functions library
     
INSTALL @lib$ + "GFXLIB2"
     PROCInitGFXLIB( dispVars{}, 0 )

     PRINT '" Loading, please wait..."

     REM. Install and initialise the required GFXLIB external modules
     
INSTALL @lib$ + "GFXLIB_modules\RectangleSolid.BBC"        : PROCInitModule
     INSTALL @lib$ + "GFXLIB_modules\PlotRotate.BBC"            : PROCInitModule
     INSTALL @lib$ + "GFXLIB_modules\PlotRotateSetAlphaBit.BBC" : PROCInitModule
     INSTALL @lib$ + "GFXLIB_modules\PlotSetAlphaValue.BBC"     : PROCInitModule
     INSTALL @lib$ + "GFXLIB_modules\ReadAlphaValue.BBC"        : PROCInitModule
     INSTALL @lib$ + "GFXLIB_modules\ClrX.BBC"                  : PROCInitModule
     INSTALL @lib$ + "GFXLIB_modules\PlotScaleNC.BBC"           : PROCInitModule
     INSTALL @lib$ + "GFXLIB_modules\BPlotScaleNC.BBC"          : PROCInitModule
     INSTALL @lib$ + "GFXLIB_modules\BPlotScale.BBC"            : PROCInitModule
     INSTALL @lib$ + "GFXLIB_modules\PlotPixelList3.BBC"        : PROCInitModule
     INSTALL @lib$ + "GFXLIB_modules\PlotScaleTintBlend.BBC"    : PROCInitModule
     INSTALL @lib$ + "GFXLIB_modules\TestPixelAlphaBit.BBC"     : PROCInitModule
     INSTALL @lib$ + "GFXLIB_modules\PlotAlphaBlend3.BBC"       : PROCInitModule
     INSTALL @lib$ + "GFXLIB_modules\DrawBmFont3.BBC"           : PROCInitModule
     INSTALL @lib$ + "GFXLIB_modules\PlotShapeBlend.BBC"        : PROCInitModule
     INSTALL @lib$ + "GFXLIB_modules\PlotBMRow.BBC"             : PROCInitModule
     INSTALL @lib$ + "GFXLIB_modules\PlotBMRowList.BBC"         : PROCInitModule
     INSTALL @lib$ + "GFXLIB_modules\PlotScale.BBC"             : PROCInitModule
     INSTALL @lib$ + "GFXLIB_modules\PlotBlend.BBC"             : PROCInitModule
     INSTALL @lib$ + "GFXLIB_modules\PlotTintBlend.BBC"         : PROCInitModule
     INSTALL @lib$ + "GFXLIB_modules\MMXSubtract64.BBC"         : PROCInitModule
     INSTALL @lib$ + "GFXLIB_modules\GetBmFontStrWidth.BBC"     : PROCInitModule
     INSTALL @lib$ + "GFXLIB_modules\PlotColourTransform.BBC"   : PROCInitModule

     REM. PlotRotateScale2 is not part of GFXLIB version 2.02 or earlier
     
INSTALL @dir$ + "resources\lib\PlotRotateScale2.BBC"       : PROCInitModule

     REM. +---------------------------------------------+
     REM. |                                             |
     REM. |    Load resources (graphics, fonts, etc.)   |
     REM. |                                             |
     REM. +---------------------------------------------+

     
bmSubZapBanner600x200% = FNLoadBMP( @dir$ + "resources\graphics\subzapbanner_600x200x24.BMP", 0 )
     seaweedBm% = FNLoadBMP( @dir$ + "resources\graphics\seaweed_680x48x8.BMP", 0 )
     font12% = FNLoadData( @lib$ + "GFXLIB_media\arial12pt.DAT" )
     font16% = FNLoadData( @lib$ + "GFXLIB_media\arial16pt.DAT" )
     font32% = FNLoadData( @lib$ + "GFXLIB_media\arial32pt.DAT" )

     REM. Create a scaled-down version of the 600x200 "SubZap II" banner graphic
     
bmSubZapBanner300x100% = FNmalloc( 4 * 300*100 ) : REM. Create 300x100 32-bpp bitmap
     
SYS GFXLIB_SaveDispVars%, dispVars{}
     SYS GFXLIB_SetDispVars2%, dispVars{}, bmSubZapBanner300x100%, 300, 100
     SYS GFXLIB_PlotScale%, dispVars{}, bmSubZapBanner600x200%, 600, 200, 300, 100, 0, 0
     SYS GFXLIB_RestoreDispVars%, dispVars{}

     DIM soundEffect{ blank%, flex%, thud%, fall%, whoosh%, sonar%, \
     
\ explosion%, release%, bonus%, gameover%, lazer% }

     soundEffect.blank%     = FNLoadData( @dir$ + "resources\soundfx\blank.WAV" )
     soundEffect.flex%      = FNLoadData( @dir$ + "resources\soundfx\flex.WAV" )
     soundEffect.thud%      = FNLoadData( @dir$ + "resources\soundfx\thud.WAV" )
     soundEffect.fall%      = FNLoadData( @dir$ + "resources\soundfx\fall.WAV" )
     soundEffect.whoosh%    = FNLoadData( @dir$ + "resources\soundfx\whoosh.WAV" )
     soundEffect.sonar%     = FNLoadData( @dir$ + "resources\soundfx\sonar.WAV" )
     soundEffect.explosion% = FNLoadData( @dir$ + "resources\soundfx\explosion.WAV" )
     soundEffect.release%   = FNLoadData( @dir$ + "resources\soundfx\release.WAV" )
     soundEffect.bonus%     = FNLoadData( @dir$ + "resources\soundfx\bonus.WAV" )
     soundEffect.gameover%  = FNLoadData( @dir$ + "resources\soundfx\gameover.WAV" )
     soundEffect.lazer%     = FNLoadData( @dir$ + "resources\soundfx\lazer.WAV" )
     SOUND OFF
     SYS
"PlaySound", 0, 0, 0
     SYS "PlaySound", soundEffect.blank%, 0, 5 : REM. There's a reason for doing this!

     REM. +---------------------------------------------+
     REM. |                                             |
     REM. |     Setup global vars, structures, etc.     |
     REM. |                                             |
     REM. +---------------------------------------------+

     REM. Speed up access to some standard Windows API functions
     REM. by putting their addresses into variables
     
GetTickCount% = FNSYS_NameToAddress( "GetTickCount" )
     SetWindowText% = FNSYS_NameToAddress( "SetWindowText" )
     GetForegroundWindow% = FNSYS_NameToAddress( "GetForegroundWindow" )
     Sleep% = FNSYS_NameToAddress( "Sleep" )
     PlaySound% = FNSYS_NameToAddress( "PlaySound" )

     D% = dispVars{}

     MaxNumSubs% = 25
     MaxNumDepthCharges% = 10
     MaxNumExplosions% = 10
     MaxNumBubbles% = 200
     SkyColour% = FNrgba(120, 180, 255, 1)
     SubProductionRate% = 50 : REM. Create a new submarine every 50 frames
     
MaxNumActiveDepthCharges% = 4

     DIM ship{ bm{addr%, width&, height&}, x, xv, max_xv, xacc, y, angle, score%, hiscore%, time%}
     DIM sub{ bm{addr{left%, right%}, width&, height&} }
     DIM sub{( MaxNumSubs%-1 ) active%, bmAddr%, x, dx, y0%, ytheta, y%}
     DIM depthCharge{ bm{addr%, width&, height&} }
     DIM depthCharge{( MaxNumDepthCharges%-1 ) active%, x, y, dy, angle, dt}
     DIM exp{ bm{addr%, width&, height&} }
     DIM exp{( MaxNumExplosions%-1 ) active%, x%, y%, s%}
     DIM bubble{( MaxNumBubbles%-1 ) active%, x#, y#, colour%, xv#, yv#}
     DIM water{ height%, colour% }
     DIM seaweed{( 47 ) row%, x%, y%}
     DIM bonus{ frame{counter%, resetValue%}, partialScore&, msgCounter% }

     REM bonus.frame.resetValue% = 60
     
bonus.frame.resetValue% = FNGetEstimatedScreenRefreshRate

     REM. Set the default hi-score
     
ship.hiscore% = 30

     sub.bm.addr.left%  = FNLoadBMP( @dir$ + "resources\graphics\sub_l_64x26x8.BMP", 0 )
     sub.bm.addr.right% = FNLoadBMP( @dir$ + "resources\graphics\sub_r_64x26x8.BMP", 0 )
     sub.bm.width&      = 64
     sub.bm.height&     = 26

     ship.bm.addr%   = FNLoadBMP( @dir$ + "resources\graphics\ship_96x96x8.BMP", 0 )
     ship.bm.width&  = 96
     ship.bm.height& = 96

     depthCharge.bm.addr%   = FNLoadBMP( @dir$ + "resources\graphics\depthcharge_20x20x8.BMP", 0 )
     depthCharge.bm.width&  = 20
     depthCharge.bm.height& = 20

     exp.bm.addr%   = FNLoadBMP( @dir$ + "resources\graphics\exp1_96x96x24.BMP", 0 )
     exp.bm.width&  = 96
     exp.bm.height& = 96

     water.colour% = FNrgb(80, 75, 255)

     REM. Create a bitmap to contain our wavey ocean surface. The bitmap will be
     REM. one-quarter the width of the program window, and will be stretched to
     REM. full length using the GFXLIB function PlotScaleNC.
     
waveBm% = FNmalloc( 4 * (WinW%DIV4) * 32 )

     REM. Create a 1x64 bitmap to function as a single pixel-wide 'water column'.
     
waveColumnBm% = FNmalloc( 4 * 64 )
     SYS GFXLIB_ClrX%, dispVars{}, waveColumnBm%, 1, 64, water.colour%

     FOR I% = 0 TO 47
       seaweed{( I% )}.row% = I%
       seaweed{( I% )}.x% = I%
       seaweed{( I% )}.y% = I%
     NEXT I%

     PROCIntroScreen

     REPEAT
       PROC
TitlePage
       PROCMain
       PROCGameOver
     UNTIL FALSE
     END
     
:
     :
     :
     :
     DEF PROCMain

     PROCLoadAndPlayMIDITune( @dir$ + "resources\music\1gyp1.MID" )

     PROCinitGameVars

     *REFRESH OFF

     SYS GetTickCount% TO time0%

     REPEAT

       SYS
GFXLIB_RectangleSolid%, D%, 0, 0, WinW%, water.height%, water.colour%
       SYS GFXLIB_RectangleSolid%, D%, 0, 400, WinW%, (WinH% - water.height%), SkyColour%

       PROCdrawWaves

       PROChandleShip

       PROChandleSubs

       PROChandleBubbles

       REM. Draw the hero's depthCharges
       
FOR I% = 0 TO MaxNumDepthCharges%-1
         IF depthCharge{(I%)}.active% THEN
           SYS
GFXLIB_ReadAlphaValue%, dispVars{}, depthCharge{(I%)}.x, depthCharge{(I%)}.y TO testPt1%
           SYS GFXLIB_ReadAlphaValue%, dispVars{}, depthCharge{(I%)}.x-depthCharge.bm.width&/2, depthCharge{(I%)}.y TO testPt2%
           SYS GFXLIB_ReadAlphaValue%, dispVars{}, depthCharge{(I%)}.x+depthCharge.bm.width&/2, depthCharge{(I%)}.y TO testPt3%
           SYS GFXLIB_PlotRotate%, D%, depthCharge.bm.addr%, depthCharge.bm.width&, depthCharge.bm.height&, depthCharge{(I%)}.x, depthCharge{(I%)}.y, 12*SINRAD( depthCharge{(I%)}.dt )

           IF RND(5) = 1 THEN
             PROC
createBubble( bubblePtr%, depthCharge{(I%)}.x, depthCharge{(I%)}.y, FNrndSgn*2*RND(1), 0.5*RND(1) )
           ENDIF

           IF
testPt1% + testPt2% + testPt3% <> 0 THEN
             IF
testPt1% <> 0 AND sub{( testPt1% )}.active% THEN PROCdestroySub( testPt1% )
             IF testPt2% <> 0 AND sub{( testPt2% )}.active% THEN PROCdestroySub( testPt2% )
             IF testPt3% <> 0 AND sub{( testPt3% )}.active% THEN PROCdestroySub( testPt3% )
             depthCharge{( I% )}.active% = FALSE
             
NumActiveDepthCharges% -= 1
           ENDIF
           
depthCharge{( I% )}.dt += 16
           IF depthCharge{( I% )}.dt >= 360 THEN
             
depthCharge{( I% )}.dt -= 360
           ENDIF
           
depthCharge{(I%)}.y -= 3
           IF depthCharge{(I%)}.y < 0 THEN
             
depthCharge{(I%)}.active% = FALSE
             
NumActiveDepthCharges% -= 1
           ENDIF
         ENDIF
       NEXT

       
REM. Draw and update explosions
       REM. FYI: SYS GFXLIB_PlotScaleTintBlend%, dispVars{}, bmAddr, bmW, bmH, newBmW, newBmH, x, y, tintRGB, tintStrength, blend
       REM. 'tintRGB' is &00RrGgBb
       REM. 'tintStrength' is positive integer in the range 0 to 255
       REM. 'blend' is positive integer in the range 0 to 255
       
FOR I% = 0 TO MaxNumExplosions%-1
         IF exp{(I%)}.active% THEN
           SYS
GFXLIB_PlotScaleTintBlend%, dispVars{}, exp.bm.addr%, exp.bm.width&, exp.bm.height&, exp{(I%)}.s%, exp{(I%)}.s%, \
           
\ exp{(I%)}.x% - exp{(I%)}.s%/2, exp{(I%)}.y% - exp{(I%)}.s%/2, \
           
\ FNrgb(100, 100, 255), 80*(1-exp{(I%)}.s%/(2*exp.bm.width&)), 255*(1-exp{(I%)}.s%/(2*exp.bm.width&))^2
           exp{(I%)}.s% += 10
           IF exp{(I%)}.s% >= 2*exp.bm.width& THEN
             
exp{(I%)}.active% = FALSE
           ENDIF
         ENDIF
       NEXT

       
REM. Draw and update the swaying seaweed
       
PROChandleseaweed

       PROCdrawText( font16%, STR$ship.time%, WinW%-38, WinH%-30, &FFFFFF )
       PROCdrawText( font16%, STR$ship.score%, 6, WinH%-30, &FFFF00 )
       PROCdrawText( font16%, "Hi: "+STR$ship.hiscore%, WinW%DIV2-64, WinH%-30, &FFAA40 )

       REM. Is it time to create a new sub?
       
IF SubProductionFrameCounter% > 0 THEN
         
SubProductionFrameCounter% -= 1
       ELSE
         
SubProductionFrameCounter% = SubProductionRate%
         PROCcreateNewSub
       ENDIF

       SYS
GetForegroundWindow% TO H%
       IF H% = @hwnd% THEN
         PROC
checkKeys
       ENDIF

       
REM. Update's ship's X-position
       
ship.x += ship.xv

       REM. Limit speed (X-velocity) of ship
       
IF ABS( ship.xv ) > ship.max_xv THEN
         
ship.xv = SGN(ship.xv) * ship.max_xv
       ENDIF

       
REM. Keep the ship on the screen...
       
IF ship.x < 0 THEN
         
ship.x = 0
         ship.xv = 0
       ENDIF

       IF
ship.x > WinW% THEN
         
ship.x = WinW%
         ship.xv = 0
       ENDIF

       
REM. If left/right arrow keys not pressed, then damp ship's X-velocity
       REM. (in other words, bring the ship to a grinding halt!)
       
IF NOT leftRightKeypress% THEN
         IF ABS
( ship.xv ) > 0 THEN
           
ship.xv += -SGN(ship.xv) * ship.xacc/2
           IF ABS( ship.xv ) <= ship.xacc THEN
             
ship.xv = 0
           ENDIF
         ENDIF
       ENDIF

       IF RND
(800) = 1 THEN SYS PlaySound%, soundEffect.sonar%, 0, 5

       IF bonus.frame.counter% < bonus.frame.resetValue% THEN
         
bonus.frame.counter% += 1
       ELSE
         
bonus.frame.counter% = 0
         bonus.partialScore& = 0
       ENDIF

       IF
bonus.partialScore& >= 3 THEN
         IF
bonus.partialScore& >= 4 THEN
           
ship.time% += 20
         ENDIF
         SYS
PlaySound%, soundEffect.bonus%, 0, 5
         bonus.frame.counter% = 0
         bonus.partialScore& = 0
         ship.score% += 3
         bonus.msgCounter% = 80
         VDU 7
       ENDIF

       IF
bonus.msgCounter% > 0 THEN
         PROC
drawXCenteredText( font32%, "3-pt Bonus!", WinH%DIV2, FNrgb(0, 180+RND(75), 180+RND(75)) )
         REM PROCdrawXCenteredText( font16%, "(3 or more subs destroyed in rapid succession)", WinH%DIV2 - 32, FNrgb(255, 255, 50) )
         
bonus.msgCounter% -= 1
       ENDIF

       
REM. If one second has elapsed, display the frame rate in
       REM. the program window title bar, and decrement the gameplay timer
       
SYS GetTickCount% TO time%
       IF (time% - time0%) >= 1000 THEN
         SYS
SetWindowText%, @hwnd%, ProgTitle$ + " | v" + ProgVersion$ + " | " + STR$NumFrames% + " fps"
         ship.time% -= 1
         NumFrames% = 0
         SYS GetTickCount% TO time0%
       ELSE
         
NumFrames% += 1
       ENDIF

       
REM. Flush the keyboard buffer
       
IF FlushKBCounter% > 0 THEN
         
FlushKBCounter% -= 1
       ELSE
         
FlushKBCounter% = FlushKBCounterStart%
         *FX 21, 0
       ENDIF

       
REM. Update the program window (display all the graphics)
       
PROCdisplay

     UNTIL ship.time% < 0

     SOUND OFF
     SYS
"PlaySound", 0, 0, 0

     ENDPROC
     
:
     :
     :
     :
     DEF PROCTitlePage
     LOCAL fade%, bitmap%, T%, hwnd%, r, g, b, scale, theta, dt

     SYS "SetWindowText", @hwnd%, ProgTitle$ + " | v" + ProgVersion$

     water.height% = 100
     ship.x        = 80
     ship.y        = water.height% + 32
     ship.angle    = 0

     fade% = 255

     PROCLoadAndPlayMIDITune( @dir$ + "resources\music\koto.MID" )

     *REFRESH OFF

     REPEAT

       
REM. Compute time-dependent "sinusoidal" RGB (r, g, b) values for use with PlotColourTransform
       
T% = TIME
       
r = 0.50 + 0.50 * SIN(T%/59) * COS(T%/131)
       g = 0.75 + 0.25 * SIN(T%/112 + 1.0) * COS(T%/125 - 0.3)
       b = 0.75 + 0.25 * SIN(T%/93 + 0.71) * COS(T%/109 - 0.21)

       SYS GFXLIB_RectangleSolid%, D%, 0, 0, WinW%, water.height%, water.colour%
       SYS GFXLIB_RectangleSolid%, D%, 0, water.height%, WinW%, (WinH% - water.height%), SkyColour%

       PROCdrawWaves
       PROChandleShip
       PROChandleseaweed

       SYS GFXLIB_MMXSubtract64%, dispVars{}, dispVars.bmBuffAddr%, 4*(WinW%*WinH% DIV 64), fade%

       PROCdrawXCenteredText( font32%, "How many enemy subs", 330, FNrgb(255,240,0) )
       PROCdrawXCenteredText( font32%, "can you destroy", 280, FNrgb(255,240,0) )
       PROCdrawXCenteredText( font32%, "in 90 seconds?", 230, FNrgb(255,240,0) )
       PROCdrawXCenteredText( font16%, "Hi-Score: "+STR$ship.hiscore%, 180, FNrgb(220,145,200) )
       PROCdrawXCenteredText( font16%, "Controls: Arrow keys to move left or right", 140, FNrgb(120,250,20) )
       PROCdrawXCenteredText( font16%, "Z to drop a depth charge", 114, FNrgb(120,250,20) )
       PROCdrawXCenteredText( font16%, "Press SPACE BAR to begin", 58, FNrgb(40,200,220) )
       PROCdrawXCenteredText( font12%, "Programming & graphics by DW. Music by Michael Miller.", 20, FNrgb(255,200,80) )
       PROCdrawXCenteredText( font12%, "Sound effects by various contributors at freesound.org", 0, FNrgb(255,180,100) )

       SYS GFXLIB_PlotColourTransform%, dispVars{}, bmSubZapBanner300x100%, 300, 100, \
       
\ (WinW% - 300)DIV2, (WinH% - 100), &100000*r, &100000*g, &100000*b

       IF fade% > 128 THEN fade% -= 1

       PROCdisplay

       SYS GetForegroundWindow% TO hwnd%

     UNTIL INKEY-99 AND hwnd% = @hwnd%

     SOUND OFF

     SYS
PlaySound%, soundEffect.lazer%, 0, 5

     REM. =================================================
     REM. Make the Title Screen spin away into the distance
     REM. =================================================

     REM. Create temporary bitmap to which the current window contents is copied
     
SYS "GlobalAlloc", 64, 4*(WinW%*WinH% + 2) TO bitmap%
     bitmap% = (bitmap% + 7) AND -8
     REM. Copy current window contents ("screen memory") to our temporary bitmap
     
SYS GFXLIB_DWORDCopy%, dispVars.bmBuffAddr%, bitmap%, WinW%*WinH%

     REM. Now display rotating and zooming-away Title Screen (our copy of it)
     
theta = 0.0
     dt = 0.0
     FOR scale = 1.0 TO 0.01 STEP -0.01
       SYS GFXLIB_Clr%, dispVars{}, 0
       SYS GFXLIB_PlotRotateScale2%, dispVars{}, bitmap%, WinW%, WinH%, WinW%DIV2, WinH%DIV2, &100000*theta, &100000*scale
       theta += dt
       IF theta >= 360 THEN theta -= 360
       dt += 0.25
       PROCdisplay
     NEXT scale

     REM. Free-up the memory occupied by our temporary bitmap
     
SYS "GlobalFree", bitmap%

     SYS GFXLIB_Clr%, dispVars{}, 0
     PROCdisplay

     *REFRESH ON
     WAIT 100
     ENDPROC
     
:
     :
     :
     :
     DEF PROCGameOver
     LOCAL hwnd%, bm%, width%, height%

     SYS "SetWindowText", @hwnd%, ProgTitle$ + " | v" + ProgVersion$ + " | GameOver!"

     *REFRESH OFF

     SYS GFXLIB_PlotTintBlend%, dispVars{}, dispVars.bmBuffAddr%, WinW%, WinH%, 0, 0, &000000, 200, 180

     PROCdrawXCenteredText( font32%, "GAME OVER", 340, &FFFF20)
     PROCdrawXCenteredText( font32%, "You destroyed " + STR$ship.score% + " subs", 250, &FF8000)

     IF ship.score% > ship.hiscore% THEN
       PROC
drawXCenteredText( font32%, "That's a new high score!", 170, &40FF00 )
       ship.hiscore% = ship.score%
     ELSE
       PROC
drawXCenteredText( font32%, "You didn't beat the Hi-Score", 170, &FF4020 )
     ENDIF

     PROC
drawXCenteredText( font16%, "Press SPACE BAR to continue", 20, &40FF40 )

     PROCdisplay

     *REFRESH ON

     SYS PlaySound%, soundEffect.gameover%, 0, 5

     REPEAT
       SYS
Sleep%, 10
       SYS GetForegroundWindow% TO hwnd%
     UNTIL (hwnd% = @hwnd%) AND INKEY-99

     REPEAT UNTIL INKEY(1)=0 OR NOT INKEY-99

     SYS PlaySound%, soundEffect.lazer%, 0, 5

     REM. Create temporary bitmap to which the current window contents is copied
     
SYS "GlobalAlloc", 64, 4*(WinW%*WinH% + 2) TO bm%
     bm% = (bm% + 7) AND -8
     REM. Copy current window contents ("screen memory") to our temporary bitmap
     
SYS GFXLIB_DWORDCopy%, dispVars.bmBuffAddr%, bm%, WinW%*WinH%

     *REFRESH OFF

     FOR width% = WinW%-1 TO 0 STEP -16
       height% = WinH% * (width% / (WinW%-1))
       SYS GFXLIB_BPlotScale%, dispVars{}, bm%, WinW%, WinH%, width%, height%, (WinW%-width%)DIV2,  (WinH% - height%)DIV2
       SYS GFXLIB_MMXSubtract64%, dispVars{}, dispVars.bmBuffAddr%, 4*(WinW%*WinH% DIV 64), 256*(1 - width%/(WinW%-1))
       PROCdisplay
     NEXT width%

     SYS "GlobalFree", bm%
     ENDPROC
     
:
     :
     :
     :
     DEF PROCIntroScreen
     LOCAL bmW%, bmH%, bmX%, bmY%, newBmH%, D%, I%, O%
     LOCAL flexTriggered%
     LOCAL y, yv, g, theta, delta
     LOCAL rowList{()}

     REM. bmW% and bmH% must be the bitmap's actual width and height
     
bmW% = 600
     bmH% = 200

     DIM rowList{( bmH%-1 ) x, y%, xv, xg}

     D% = dispVars{}

     bmX% = (@vdu%!208 - bmW%) DIV 2
     bmY% = (@vdu%!212 - bmH%) DIV 2

     FOR I% = 0 TO bmH%-1
       rowList{( I% )}.x = bmX%
       rowList{( I% )}.y% = bmY% + I%
       rowList{( I% )}.xv = 0.0
       rowList{( I% )}.xg = 0.5*SIN(2*PI*I% / (bmH%-1))
     NEXT I%

     *REFRESH OFF

     FOR I%=0 TO 120 STEP 2
       SYS GFXLIB_Clr%, dispVars{}, FNrgb( 40*I%/120, 40*I%/120, I% )
       PROCdisplay
     NEXT

     SYS
GFXLIB_Clr%, dispVars{}, FNrgb( 40, 40, 120 )
     PROCdisplay
     *REFRESH ON
     WAIT 200

     SYS PlaySound%, soundEffect.fall%, 0, 5

     y = WinH%
     yv = 0
     g = -0.5
     *REFRESH OFF
     REPEAT
       SYS
GFXLIB_Clr%, dispVars{}, FNrgb( 40, 40, 120 )
       IF ABS(y - bmY%) >= yv THEN
         SYS
GFXLIB_Plot%, dispVars{}, bmSubZapBanner600x200%, 600, 200, bmX%, y
       ELSE
         SYS
GFXLIB_Plot%, dispVars{}, bmSubZapBanner600x200%, 600, 200, bmX%, bmY%
       ENDIF
       
y += yv
       yv += g
       PROCdisplay
     UNTIL y <= bmY%

     SYS PlaySound%, soundEffect.thud%, 0, 5


     theta = 0
     delta = 0
     flexTriggered% = FALSE
     REPEAT
       SYS
GFXLIB_Clr%, dispVars{}, FNrgb( 40, 40, 120 )
       newBmH% = bmH% + 64*COSRAD(delta)*SINRAD(theta + 180)
       PROCDrawFlexedBitmap( bmSubZapBanner600x200%, bmW%, bmH%, newBmH%, bmX%, bmY% )
       PROCdisplay
       theta += 10 + 12*SINRAD(delta)
       delta += 1.5
       IF flexTriggered% = FALSE AND theta >= 180 THEN
         SYS
PlaySound%, soundEffect.flex%, 0, 5
         flexTriggered% = TRUE
       ENDIF
     UNTIL
delta >= 90

     SYS GFXLIB_Clr%, dispVars{}, FNrgb( 40, 40, 120 )
     SYS GFXLIB_Plot%, dispVars{}, bmSubZapBanner600x200%, 600, 200, bmX%, bmY%
     PROCdisplay

     *REFRESH ON
     WAIT 300

     SYS PlaySound%, soundEffect.whoosh%, 0, 5

     *REFRESH OFF
     FOR O% = 256 TO 0 STEP -4
       SYS GFXLIB_Clr%, dispVars{}, FNrgb( 40, 40, 120 )
       FOR I% = 0 TO bmH%-1
         SYS GFXLIB_PlotBlend%, D%, bmSubZapBanner600x200% + 4*I%*bmW%, bmW%, 1, rowList{(I%)}.x, rowList{(I%)}.y%, O%
         rowList{(I%)}.x += rowList{(I%)}.xv
         rowList{(I%)}.xv += rowList{(I%)}.xg
         IF ABSrowList{(I%)}.xv > 16 THEN
           
rowList{(I%)}.xv = 16 * SGNrowList{(I%)}.xv
         ENDIF
       NEXT
I%
       PROCdisplay
     NEXT O%

     FOR I%=120 TO 0 STEP -2
       SYS GFXLIB_Clr%, dispVars{}, FNrgb( 40*I%/120, 40*I%/120, I% )
       PROCdisplay
     NEXT
     ENDPROC
     
:
     :
     :
     :
     DEF PROCcheckKeys
     LOCAL I%
     REM PRIVATE fireKeyPress%

     
leftRightKeypress% = FALSE

     IF INKEY
-26 THEN
       
ship.xv -= ship.xacc
       leftRightKeypress% = TRUE
     ENDIF

     IF INKEY
-122 THEN
       
ship.xv += ship.xacc
       leftRightKeypress% = TRUE
     ENDIF

     IF INKEY
-98 THEN
       IF
NumActiveDepthCharges% < MaxNumActiveDepthCharges% AND fireKeyPress%=FALSE THEN
         
I% = FNgetFreeDepthChargeSlot
         IF I% <> -1 THEN
           
depthCharge{( I% )}.active% = TRUE
           
depthCharge{( I% )}.x = ship.x
           depthCharge{( I% )}.y = ship.y - 26
           fireKeyPress% = TRUE
           
NumActiveDepthCharges% += 1
           SYS PlaySound%, soundEffect.release%, 0, 5
         ENDIF
       ENDIF
     ELSE
       
fireKeyPress% = FALSE
     ENDIF
     ENDPROC
     
:
     :
     :
     :
     DEF PROCinitGameVars
     LOCAL I%

     FOR I% = 0 TO MaxNumBubbles%-1
       bubble{(I%)}.active% = FALSE
       
bubble{(I%)}.x# = -4.0
       bubble{(I%)}.y# = -4.0
       bubble{(I%)}.xv# = 0.0
       bubble{(I%)}.yv# = 0.0
     NEXT I%

     FOR I% = 0 TO MaxNumExplosions%-1 : exp{(I%)}.active% = FALSE    : NEXT I%
     FOR I% = 0 TO MaxNumSubs%-1       : sub{(I%)}.active% = FALSE    : NEXT I%
     FOR I% = 0 TO MaxNumDepthCharges%-1      : depthCharge{(I%)}.active% = FALSE   : NEXT I%

     ship.x        = (WinW% - ship.bm.width&) DIV 2
     ship.y        = 0.90 * WinH%
     ship.angle    = 0
     ship.xv       = 0
     ship.xacc     = 0.25
     ship.max_xv   = 4
     ship.score%   = 0
     ship.time%    = 90
     water.height% = 400

     bonus.frame.counter% = 0
     bonus.partialScore&  = 0
     bonus.msgCounter% = 0

     NumActiveDepthCharges% = 0

     SubProductionFrameCounter% = 0
     NumFrames%                 = 0
     FlushKBCounterStart%       = 250
     FlushKBCounter%            = 0
     bubblePtr%                 = 0
     fireKeyPress%              = FALSE
     ENDPROC
     
:
     :
     :
     :
     DEF PROChandleShip
     LOCAL rgb%

     rgb% = !(!D% + 4*(WinW%*INT(ship.y-16) + INTship.x))
     IF rgb% = water.colour% THEN
       
ship.y += 0.75
     ELSE
       
ship.y -= 0.75
     ENDIF

     SYS
GFXLIB_PlotRotateSetAlphaBit%, D%, ship.bm.addr%, ship.bm.width&, ship.bm.height&, ship.x, ship.y, ship.angle, 0

     PROCrotateShip
     ENDPROC
     
:
     :
     :
     :
     DEF PROCrotateShip
     LOCAL p1x, p1y, p2x, p2y, rgb1%, rgb2%, code%

     p1x = ship.x + 48*SINRAD(ship.angle - 90)
     p1y = (ship.y-16) + 48*COSRAD(ship.angle - 90)
     p2x = ship.x + 48*SINRAD(ship.angle + 90)
     p2y = (ship.y-16) + 48*COSRAD(ship.angle + 90)

     rgb1% = !(!D% + 4*(WinW%*INTp1y + INTp1x))
     rgb2% = !(!D% + 4*(WinW%*INTp2y + INTp2x))

     code% = ABS(rgb1% = water.colour%) + 2*ABS(rgb2% = water.colour%)

     IF code% = 1 THEN ship.angle += 0.25
     IF code% = 2 THEN ship.angle -= 0.25
     ENDPROC
     
:
     :
     :
     :
     DEF PROChandleBubbles
     LOCAL I%, G%, f#, xv#, yv#
     REM. Draw bubbles & update
     REM. Note 190h = 400d

     
f# = 1.0 / (30 * WinH%)
     G% = GFXLIB_TestPixelAlphaBit%

     REM. This loop has to be fast!

     
FOR I% = 0 TO MaxNumBubbles%-1

       IF bubble{(I%)}.active% THEN
         
xv# = bubble{(I%)}.xv#
         IF ABSxv# > 0.05 THEN
           
bubble{(I%)}.x# += xv#
           bubble{(I%)}.xv# += -0.1*SGNxv#
         ENDIF
         
yv# = bubble{(I%)}.yv#
         SYS G%, D%, bubble{(I%)}.x#, bubble{(I%)}.y# + yv#, 0 TO A%
         bubble{(I%)}.y# += yv#
         bubble{(I%)}.yv# += bubble{(I%)}.y# * f#
         IF A% THEN
           
bubble{(I%)}.x# = -4
           bubble{(I%)}.y# = -4
           bubble{(I%)}.active% = FALSE
         ENDIF
       ENDIF

     NEXT

     
REM. Draw the bubbles
     REM. Each pixel plotted four times in positions offset by one pixel in X and/or Y direction;
     REM. this is to 'fatten' the bubble
     
SYS GFXLIB_PlotPixelList3%, dispVars{}, dispVars.bmBuffAddr%, dispVars.bmBuffW%, dispVars.bmBuffH%, ^bubble{(0)}.x#, 5, DIM(bubble{(0)}), MaxNumBubbles%, 0, 0
     SYS GFXLIB_PlotPixelList3%, dispVars{}, dispVars.bmBuffAddr%, dispVars.bmBuffW%, dispVars.bmBuffH%, ^bubble{(0)}.x#, 5, DIM(bubble{(0)}), MaxNumBubbles%, 1, 0
     SYS GFXLIB_PlotPixelList3%, dispVars{}, dispVars.bmBuffAddr%, dispVars.bmBuffW%, dispVars.bmBuffH%, ^bubble{(0)}.x#, 5, DIM(bubble{(0)}), MaxNumBubbles%, 0, 1
     SYS GFXLIB_PlotPixelList3%, dispVars{}, dispVars.bmBuffAddr%, dispVars.bmBuffW%, dispVars.bmBuffH%, ^bubble{(0)}.x#, 5, DIM(bubble{(0)}), MaxNumBubbles%, 1, 1
     ENDPROC
     
:
     :
     :
     :
     DEF PROChandleseaweed
     LOCAL I%, T%, t1, t2
     T% = TIME
     
t1 = T% / 100
     t2 = T% / 400
     REM. Calculate the time-varying X positions of the 48 individual rows of pixels of the seaweed bitmap
     REM. The Y positions and row indices (which were deterdepthCharged earlier) remain constant and unmodified
     
FOR I% = 0 TO 47
       seaweed{( I% )}.x% = I% * SIN(I%*0.05 + t1) * COS(I%*0.03 + t2) - 20
     NEXT
     
REM. Draw the 48 rows of the seaweed bitmap
     
SYS GFXLIB_PlotBMRowList%, dispVars{}, seaweedBm%, 680, 48, ^seaweed{(0)}.row%, 48
     ENDPROC
     
:
     :
     :
     :
     DEF PROChandleSubs
     LOCAL I%, J%, X%

     REM. Draw and update the subs
     
FOR I% = 0 TO MaxNumSubs%-1
       IF sub{( I% )}.active% THEN
         
sub{(I%)}.y% = sub{(I%)}.y0% + 16*SIN( sub{(I%)}.ytheta )
         SYS GFXLIB_PlotShapeBlend%, D%, sub{(I%)}.bmAddr%, sub.bm.width&, sub.bm.height&, sub{(I%)}.x-8, sub{(I%)}.y%-7, 0, 32
         SYS GFXLIB_PlotSetAlphaValue%, D%, sub{(I%)}.bmAddr%, sub.bm.width&, sub.bm.height&, sub{(I%)}.x, sub{(I%)}.y%, I%

         IF RND(20) = 1 THEN
           IF SGN
( sub{(I%)}.dx ) = 1 THEN
             
X% = 34
           ELSE
             
X% = 24
           ENDIF
           PROC
createBubble( bubblePtr%, sub{(I%)}.x + X%, sub{(I%)}.y%+22, sub{(I%)}.dx, 1+RND(1) )
         ENDIF

         IF RND
(10) = 1 THEN
           IF SGN
( sub{(I%)}.dx ) = 1 THEN
             
X% = 0
           ELSE
             
X% = sub.bm.width&
           ENDIF
           FOR
J% = 1 TO RND(5)
             PROCcreateBubble( bubblePtr%, sub{(I%)}.x + X%, sub{(I%)}.y%+4, -(sub{(I%)}.dx + SGN(sub{(I%)}.dx)*2*RND(1)), 1+RND(1) )
           NEXT
         ENDIF

         
sub{(I%)}.ytheta += 0.01
         IF sub{(I%)}.ytheta >= 2.0 * PI THEN
           
sub{(I%)}.ytheta -= 2.0 * PI
         ENDIF
         
sub{(I%)}.x += sub{(I%)}.dx
         IF sub{(I%)}.x < -sub.bm.width& THEN
           
sub{( I% )}.active% = FALSE
         ENDIF
         IF
sub{(I%)}.x > WinW% THEN
           
sub{( I% )}.active% = FALSE
         ENDIF
       ENDIF
     NEXT
     ENDPROC
     
:
     :
     :
     :
     DEF PROCcreateNewSub
     LOCAL I%
     I% = FNgetFreeSubSlot
     IF I% = -1 THEN ENDPROC
     
sub{( I% )}.active% = TRUE
     
sub{( I% )}.dx = FNrndSgn * (2.0 + 2.0 * RND(1))
     IF SGN( sub{( I% )}.dx ) = 1 THEN
       
sub{( I% )}.x = -sub.bm.width&
       sub{( I% )}.bmAddr% = sub.bm.addr.right%
     ELSE
       
sub{( I% )}.x = WinW%
       sub{( I% )}.bmAddr% = sub.bm.addr.left%
     ENDIF
     
sub{( I% )}.y0% = 48 + RND( 0.60 * WinH% )
     sub{( I% )}.ytheta = 2.0 * PI * RND(1)
     ENDPROC
     
:
     :
     :
     :
     DEF PROCdestroySub( S% )
     LOCAL I%, X%, Y%, sub_dx
     X% = sub{(S%)}.x + sub.bm.width&/2
     Y% = sub{(S%)}.y% + sub.bm.height&/2
     sub_dx = sub{(S%)}.dx/2

     IF bonus.frame.counter% < bonus.frame.resetValue% THEN
       
bonus.partialScore& += 1
       bonus.frame.counter% = 0
     ENDIF

     PROC
createExplosion( X%, Y% )

     SYS PlaySound%, soundEffect.explosion%, 0, 5

     FOR I% = 1 TO 40
       PROCcreateBubble( bubblePtr%, X%, Y%, sub_dx + FNrndSgn*6*RND(1), FNrndSgn*3*RND(1) )
     NEXT

     
sub{( S% )}.active% = FALSE
     
ship.score% += 1
     ENDPROC
     
:
     :
     :
     :
     DEF PROCcreateExplosion( X%, Y% )
     LOCAL I%
     I% = FNgetFreeExpSlot
     IF I% <> -1 THEN
       
exp{( I% )}.active% = TRUE
       
exp{( I% )}.x% = X%
       exp{( I% )}.y% = Y%
       exp{( I% )}.s% = 1
     ENDIF
     ENDPROC
     
:
     :
     :
     :
     REM. Note that the use of hexadecimal values here is due to
     REM. the *slightly* faster 'processing' by the BB4W interpreter
     
DEF PROCcreateBubble( P%, X%, Y%, xv#, yv# )
     bubble{(P%)}.active% = TRUE
     
bubble{(P%)}.x# = X%
     bubble{(P%)}.y# = Y%
     bubble{(P%)}.colour% = (&C8+RND(&37))*&10000 + (&C8+RND(&37))*&100 + &FF
     bubble{(P%)}.xv# = xv#
     bubble{(P%)}.yv# = yv#
     bubblePtr% += 1
     IF bubblePtr% >= MaxNumBubbles% THEN
       
bubblePtr% = 0
     ENDIF
     ENDPROC
     
:
     :
     :
     :
     DEF PROCdrawWaves
     REM.
     REM. Sorry if this subroutine looks rather cryptic; the loop is designed to be fast!
     REM.

     
LOCAL G%, T%, X%, Y%, W%, t1, t2, t3, f1, f2, sincos

     SYS GFXLIB_SaveDispVars%, D%
     SYS GFXLIB_SetDispVars2%, D%, waveBm%, WinW%DIV4, 32
     SYS GFXLIB_Clr%, D%, SkyColour%

     T% = TIME
     
t1 = T% / 102
     t2 = T% / 157
     t3 = T% / 1083
     f1 = 1 / 9.1678109
     f2 = 1 / 33.296151
     sincos = &C * (0.5 + SINCOSt3)

     G% = GFXLIB_BPlot%
     W% = waveColumnBm%

     FOR X% = 0 TO WinW%DIV4-1
       Y% = sincos * SIN(X%*f1 + t1) * COS(X%*f2 + t2)
       SYS G%, D%, W%, &1, &40, X%, Y%-&30
     NEXT

     SYS
GFXLIB_RestoreDispVars%, D%
     SYS GFXLIB_BPlotScaleNC%, D%, waveBm%, WinW%DIV4, 32, WinW%, 32, 0, water.height%
     ENDPROC
     
:
     :
     :
     :
     DEF FNgetFreeSubSlot
     LOCAL I%, S%
     S% = -1
     FOR I% = 0 TO MaxNumSubs%-1
       IF NOT sub{( I% )}.active% THEN
         
S% = I%
       ENDIF
     NEXT
     
=S%
     :
     :
     :
     :
     DEF FNgetFreeDepthChargeSlot
     LOCAL I%, S%
     S% = -1
     FOR I% = 0 TO MaxNumDepthCharges%-1
       IF NOT depthCharge{( I% )}.active% THEN
         
S% = I%
       ENDIF
     NEXT
     
=S%
     :
     :
     :
     :
     DEF FNgetFreeExpSlot
     LOCAL I%, S%
     S% = -1
     FOR I% = 0 TO MaxNumExplosions%-1
       IF NOT exp{( I% )}.active% THEN
         
S% = I%
       ENDIF
     NEXT
     
=S%
     :
     :
     :
     :
     DEF PROCdrawText( font%, s$, xPos%, yPos%, colour% )
     SYS GFXLIB_DrawBmFont3%, dispVars{}, font%, s$, xPos%-2, yPos%-2, 0
     SYS GFXLIB_DrawBmFont3%, dispVars{}, font%, s$, xPos%, yPos%, colour%
     ENDPROC
     
:
     :
     :
     :
     DEF PROCdrawXCenteredText( font%, s$, yPos%, colour% )
     LOCAL W%
     SYS GFXLIB_GetBmFontStrWidth%, font%, s$ TO W%
     SYS GFXLIB_DrawBmFont3%, dispVars{}, font%, s$, (@vdu%!208 - W%)DIV2-2, yPos%-2, 0
     SYS GFXLIB_DrawBmFont3%, dispVars{}, font%, s$, (@vdu%!208 - W%)DIV2, yPos%, colour%
     ENDPROC
     
:
     :
     :
     :
     DEF PROCDrawFlexedBitmap( bm%, bmW%, bmH%, newBmH%, bmX%, bmY% )
     LOCAL x1, y1, x2, y2, x3, y3, a, b, c, dx, x2`%, hScale%, X, Y%, dy%
     LOCAL pRow%, rowWidth%, D%

     D% = dispVars{}

     dx = 0.75*(newBmH% - bmH%)

     REM. Compute our quadratic curve coordinates
     REM. (x1,y1),  (x2,y2),  (x3,y3)

     
x1 = bmX%
     y1 = bmY%

     x2 = bmX% + dx
     y2 = bmY% + newBmH%DIV2

     x3 = x1
     y3 = bmY% + newBmH%

     REM. Our quadratic curve is of the form X = aY^2 + bY + c
     REM. We now need to compute a, b and c
     
PROCgetQuadraticCoeffs( y1, x1, y2, x2, y3, x3, a, b, c )

     REM. Some "pre-calculations" (to improve loop efficiency)
     
x2`% = 2*bmX% + bmW%
     hScale% = newBmH% / bmH%
     dy% = y3 - bmY%

     REM. Now draw the scaled rows of pixels from the bitmap
     
FOR Y% = y1 TO y3-1
       X = a*Y%^2 + b*Y% + c
       pRow% = bm% + 4 * bmW% * INT(bmH% * ((Y%-bmY%)/dy%))
       rowWidth% = x2`% - 2*X
       SYS GFXLIB_PlotScale%, D%, pRow%, bmW%, 1, rowWidth%, hScale%+1, X, Y%
     NEXT
     ENDPROC
     
:
     :
     :
     :
     DEF PROCgetQuadraticCoeffs( x1, y1, x2, y2, x3, y3, RETURN a, RETURN b, RETURN c )
     PROCsolve2x2( x1^2-x2^2, x1-x2, y1-y2, \
     
\ x1^2-x3^2, x1-x3, y1-y3, \
     
\ a, b )
     c = y1 - (a*x1^2 + b*x1)
     ENDPROC
     
:
     :
     :
     :
     REM. Solve linear simultaneous equation with two unknowns
     
DEF PROCsolve2x2(A, B, C, D, E, F, RETURN x, RETURN y)
     LOCAL d
     d = 1 / (A*E - B*D)
     x = d * (E*C - B*F)
     y = d * (A*F - D*C)
     ENDPROC
     
:
     :
     :
     :
     REM. FNrndSgn returns a random sign +1 or -1
     
DEF FNrndSgn
     IF RND(2)-2 THEN =1 ELSE =-1
     :
     :
     :
     :
     DEF PROCLoadAndPlayMIDITune( file$ )
     LOCAL F%
     SOUND OFF
     SYS
"PlaySound", 0, 0, 0
     F% = OPENIN( file$ )
     IF F% = 0 THEN CLOSE#F% : PROCerror( "Sorry, but I couldn't load the MIDI tune " + file$, FALSE )
     CLOSE# F%
     OSCLI "PLAY """+file$+""""
     ENDPROC
     
:
     :
     :
     :
     DEF PROCfixWindowSize
     LOCAL GWL_STYLE, WS_THICKFRAME, WS_MAXIMIZEBOX, ws%
     GWL_STYLE = -16
     WS_THICKFRAME = &40000
     WS_MAXIMIZEBOX = &10000
     SYS "GetWindowLong", @hwnd%, GWL_STYLE TO ws%
     SYS "SetWindowLong", @hwnd%, GWL_STYLE, ws% AND NOT (WS_THICKFRAME+WS_MAXIMIZEBOX)
     ENDPROC
     
:
     :
     :
     :
     DEF PROCerror( msg$, L% )
     OSCLI "REFRESH ON" : ON
     COLOUR
1, &FF, &FF, &FF
     COLOUR 1
     PRINT TAB(1,1)msg$;
     IF L% THEN
       PRINT
" at line "; ERL;
     ENDIF
     VDU
7
     REPEAT UNTIL INKEY(1)=0
     ENDPROC