REM. +-------------------------------------------------------------+
REM. | |
REM. | SubZap II |
REM. | |
REM. | Version 1.0b |
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.0b"
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
PROCTitlePage
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
PROCcreateBubble( 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
PROCcheckKeys
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
ENDIF
IF bonus.msgCounter% > 0 THEN
PROCdrawXCenteredText( font32%, "3-pt Bonus!", WinH%DIV2, FNrgb(0, 180+RND(75), 180+RND(75)) )
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
PROCdrawXCenteredText( font32%, "That's a new high score!", 170, &40FF00 )
ship.hiscore% = ship.score%
ELSE
PROCdrawXCenteredText( font32%, "You didn't beat the Hi-Score", 170, &FF4020 )
ENDIF
PROCdrawXCenteredText( 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
PROCcreateBubble( 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
PROCcreateExplosion( 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