Space Invaders Clone - TerryRitchie - 11-01-2022
Back in 2011 or so I wrote QB64 Space Invaders which was a sad attempt at Space Invaders. In 2013 I rewrote it to clone the original as much as possible. However, it was buggy and I never released it. I rediscovered the code recently when rewriting the tutorial web site and decided it was time to do this code justice. I completely rewrote the code and I present to you a QB64 Space Invaders game that clones the original 99%. I spent the last 3 weeks working on this (and playing WAY TOO MANY games of Space Invaders working the kinks out. Give it a whirl and let me know what you think.
The ZIP file below contains the source code and all assets (sound files and graphics). The game does create a file upon startup called "si.sav" that is used to save the game options and high score. It only contains two text lines so will never exceed 1KB in size.
You can turn the bezel image on/off, the background image on/off, resize the screen 1x, 2x, 3x, or full screen. Just like the original you can choose the number of shields (3 to 6), the extra ship score (1000 or 1500), and coin required or free play through the use of DIP switches in the options screen (just like found on the motherboard of the original arcade systems). The game is capable of playing 1 or 2 players exactly like the original was as well.
Code: (Select All) '----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
'
' ÛÛÛÛ ÛÛÛÛÛÛÛ ÛÛÛ ÛÛÛÛ ÛÛÛÛÛÛÛ This software has been written for educational purposes only. Under no circumstances is this source
' ÛÛÛÛÛÛÛ ÛÛÛÛÛÛÛÛ ÛÛÛ ÛÛÛÛÛÛ ÛÛÛÛÛÛÛ code or compiled EXE to be sold or otherwise used for a profitable purpose.
' ÛÛÛÛÛÛÛÛ ÛÛÛÛÛÛÛÛÛ ÛÛÛÛÛ ÛÛÛÛÛÛÛ ÛÛÛÛÛÛÛ
' ÛÛÛÛÛÛÛÛ ÛÛÛÛÛÛÛÛÛ ÛÛÛÛÛ ÛÛÛÛÛÛÛÛ ÛÛÛÛÛÛÛ Space Invaders was designed by Tomohiro Nishikado and is Copyright Taito Corporation with a license
' ÛÛÛÛÛÛÛÛ ÛÛÛ ÛÛÛ ÛÛÛÛÛÛÛ ÛÛÛ ÛÛÛ ÛÛÛ to the Midway division of Bally Corporation for production. This software was written to pay homage
' ÛÛ ÛÛÛ ÛÛÛ ÛÛÛ ÛÛÛÛÛÛÛ ÛÛÛ ÛÛÛ ÛÛÛ to Mr. Nishikado's outstanding game.
' ÛÛÛ ÛÛÛ ÛÛÛ ÛÛÛ ÛÛÛÛÛÛÛ ÛÛÛ ÛÛÛ ÛÛÛ
' ÛÛÛ ÛÛÛ ÛÛÛ ÛÛ Û ÛÛ ÛÛÛ ÛÛÛ This source code has been released as open source which means you are free to use this source code
' ÛÛÛÛ ÛÛÛÛÛÛÛÛÛ ÛÛ Û ÛÛ ÛÛÛ ÛÛÛÛÛ to learn from and modify without prior consent from the original author. It is requested, however,
' ÛÛÛÛÛ ÛÛÛÛÛÛÛÛ ÛÛÛÛÛÛÛ ÛÛÛ ÛÛÛÛÛÛ that if you do make modifications to this source code that the author, version, and any
' ÛÛÛÛÛÛ ÛÛÛÛÛÛ ÛÛÛÛÛ ÛÛÛ ÛÛÛÛÛÛ modifications be noted in the area provided below if you plan to redistribute the source code with
' ÛÛÛÛÛ ÛÛÛÛÛÛ ÛÛÛÛÛ ÛÛÛ ÛÛ your modifications.
' ÛÛÛ ÛÛÛ ÛÛ ÛÛ ÛÛÛ ÛÛ
' ÛÛ ÛÛ ÛÛÛ ÛÛÛ ÛÛÛ ÛÛÛ ÛÛÛ ÛÛ Author Version Date Modifications
' ÛÛ ÛÛ ÛÛÛ ÛÛ ÛÛ ÛÛÛ ÛÛÛ ÛÛÛ ---------------- ------- -------- ------------------------------------------------------------------
' ÛÛÛÛÛÛ ÛÛÛ ÛÛÛÛ ÛÛÛÛ ÛÛÛÛÛÛÛ ÛÛÛÛÛÛ Terry Ritchie 1.0 06/24/13 Original version written in QB64 v0.954 SDL never released (buggy)
' ÛÛÛÛÛÛ ÛÛÛ ÛÛÛ ÛÛÛ ÛÛÛÛÛ ÛÛÛÛÛÛ ---------------- ------- -------- ------------------------------------------------------------------
' ÛÛÛÛÛ ÛÛÛ ÛÛ ÛÛ ÛÛÛ ÛÛÛÛÛÛ Terry Ritchie 2.0 10/31/22 Complete rewrite of code to support QB64PE v.3.3.0
' ÛÛÛ ÛÛÛ ÛÛ ÛÛ ÛÛÛ ÛÛÛÛÛÛ This source code should compile with versions of QB64 2.1 and up
' The QB64PE logo was created by Pwillard at the QB64PE forum
' ÛÛ ÛÛ ÛÛ ÛÛ ÛÛ ÛÛÛ ÛÛÛÛ ÛÛÛÛÛ ÛÛÛ ÛÛÛ ---------------- ------- -------- ------------------------------------------------------------------
' ÛÛ ÛÛ ÛÛ ÛÛ ÛÛ ÛÛÛÛÛ ÛÛÛÛÛ ÛÛ ÛÛÛÛÛ ÛÛ ÛÛ
' ÛÛ ÛÛ ÛÛ ÛÛ ÛÛ ÛÛÛÛÛ ÛÛ ÛÛ ÛÛ ÛÛÛÛÛ ÛÛ
' ÛÛ ÛÛÛ ÛÛ ÛÛ ÛÛ ÛÛ ÛÛ ÛÛ ÛÛ ÛÛ ÛÛ ÛÛ ÛÛ
' ÛÛ ÛÛÛÛÛÛ ÛÛ ÛÛ ÛÛ ÛÛ ÛÛ ÛÛ ÛÛ ÛÛ ÛÛ ÛÛ
' ÛÛ ÛÛÛÛÛÛ ÛÛÛÛ ÛÛÛÛÛ ÛÛ ÛÛ ÛÛÛÛÛ ÛÛÛÛ ÛÛÛ
' ÛÛ ÛÛ ÛÛÛ ÛÛÛÛ ÛÛÛÛÛ ÛÛ ÛÛ ÛÛ ÛÛÛÛÛ ÛÛ
' ÛÛ ÛÛ ÛÛ ÛÛÛÛ ÛÛ ÛÛ ÛÛ ÛÛ ÛÛ ÛÛ ÛÛ ÛÛ ÛÛ
' ÛÛ ÛÛ ÛÛ ÛÛ ÛÛ ÛÛ ÛÛÛÛ ÛÛ ÛÛ ÛÛ ÛÛÛ
' ÛÛ ÛÛ ÛÛ ÛÛ ÛÛ ÛÛ ÛÛÛÛ ÛÛÛÛÛ ÛÛ ÛÛ ÛÛÛ
'
' QB64 adaptation
' by
' Terry Ritchie
' (quickbasic64@gmail.com)
' 06/24/13
' Updated 10/31/22
'
'
' I tried to clone the original space invaders game as closely as possible. The deviations from the original are noted below:
' ---------------------------------------------------------------------------------------------------------------------------
' - Centered the high score.
' - UFO intervals are 23 to 27 seconds. Sources I found state every 25 seconds give or take a few seconds but nothing firm.
' - player keyboard instructions on screen for inserting coins, setting options, and exiting the game.
' - An options screen simulating DIP switch settings on the motherboard of arcade machine.
' - Ability to turn the background image on and off (set in options).
' - Ability to display cabinet bezel around the screen (set in options).
' - Ability to change the screen resolution from native 224x248 to 2x, 3x, and full screen (set in options).
' - QB64 and author credit screen added to demo loop.
' - Invader beat sounds are slightly longer in length than the original.
' - Scores are five digits instead of the four found in the original game.
' - Extra ship sound is not exact to the original.
' - The high score is saved between program executions with the ability to reset the high score if desired (reset in options).
' - There is no demo play yet between the different intro screens at startup. Coming in next version.
'
' What I found about the original that was emulated in the game.
' --------------------------------------------------------------
' - The invaders update in a wave pattern like the original due to the slow microprocessor.
' - As the invaders are destroyed the wave pattern increases in speed.
' - The UFO scoring is not random. After 23 shots the UFO will always be 300 points and every 15 shots after that.
' - DIP switch options for setting the number of shields (3 to 6), coin or free play, and extra ship score (1000 or 1500).
' - The player's laser will always be destroyed when hit by a bomb. The bomb will randomly survive the encounter.
' - The demo screen will alternate between spelling PLAY correctly and with an upside down Y. The upside down Y will be carried off and corrected by an invader.
' - The demo screen will alternative between spelling COIN correctly or adding an extra C. The extra C will be destroyed by an invader.
'
' Bug or easter egg?
' ------------------
' There was a "bug" in the original Space Invaders that some considered to be an "Easter Egg". Mr. Nishikado never confirmed this either way. When the invaders are in the row
' directly above the player the player is immune to bombs. I consider this to be a "bug" because the bottom of the bomb was used to detect a collision with the player's ship.
' Since the bomb moves down one pixel before the collision check is done the player's ship becomes immune. Since this was a bug in my view this behavior has not been emulated
' in this version of the game. However, this behavior would be very easy to emulate if desired.
'
' Coding today is easier!
' -----------------------
' Of all the games I've emulated this was surprisingly one of the most difficult to write. It makes me appreciate the complexity of writing software for the late 70's to early
' 80's microprocessors used in arcade games of the time. Those were true programmers crafting excellent games in Assembler on very limited processors with 2K to 4K of RAM and ROM!
'
' Known issues with this version of the game.
' -------------------------------------------
' - When selecting a different screen size or adding/removing the bezel image in options upon exit from the options screen the game window will not recenter on the desktop.
' I've tried every method I can think of to get this to work. Upon game exit and restart the centering will work fine.
'
' Files included with the game:
' -----------------------------
' File created by the game - si.sav - saved options and high score
' Support files - sibeat1.ogg - heart beat 1 sound
' - sibeat2.ogg - heart beat 2 sound
' - sibeat3.ogg - heart beat 3 sound
' - sibeat4.ogg - heart beat 4 soound
' - sicoin.ogg - coin inserted sound
' - siextra.ogg - extra ship sound
' - siidead.ogg - invader explosion sound
' - silaser.ogg - laser sound
' - sipdead.ogg - ship explosion sound
' - siudead.ogg - UFO explosion sound
' - siufofly.ogg - UFO flying sound
' - siicon.bmp - window icon image
' - sisprites.png - game graphics
' - invaders.bas - QB64 source code
' +--------------+
' | METACOMMANDS |
'------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------+
'
OPTION _EXPLICIT ' force declaration of all variables
$VERSIONINFO:CompanyName=RitchCraft Creations
$VERSIONINFO:FileDescription=QB64 Space Invaders
$VERSIONINFO:InternalName=invaders.exe
$VERSIONINFO:ProductName=QB64 Space Invaders
$VERSIONINFO:OriginalFilename=invaders.exe
$VERSIONINFO:LegalCopyright=(c)2022 RitchCraft Creations
$VERSIONINFO:FILEVERSION#=2,0,0,0
$VERSIONINFO:PRODUCTVERSION#=2,0,0,0
' +-----------+
' | CONSTANTS |
'---------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------+
'
CONST FALSE = 0, TRUE = NOT FALSE ' boolean truth detecors
CONST PLAYER1 = 1 ' player 1 value
CONST PLAYER2 = 2 ' player 2 value
CONST BOTHPLAYERS = 3 ' both players value
CONST BLACK = _RGB32(0, 0, 0) ' color constants
CONST WHITE = _RGB32(255, 255, 255)
CONST INGAME = 5 ' mode settings
CONST INOPTIONS = 1
CONST INCOIN = 2
CONST INSELECT = 3
CONST NEWGAME = -1
CONST NEWLEVEL = 0
CONST CLEARVARIABLES = -1
CONST GODMODE = FALSE ' developer option
' +-------------------+
' | TYPE DECLARATIONS |
'-------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------------+
TYPE RECT ' rectangle definition | RECT |
x1 AS INTEGER ' rectagular coordinates for objects and collision detection +------+
y1 AS INTEGER
x2 AS INTEGER ' x1,y1 = upper left x2,y2 = lower right
y2 AS INTEGER
END TYPE
'-------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------+
TYPE PAUSE ' pause conditions | PAUSE |
Level AS INTEGER ' between levels pause +-------+
Die AS INTEGER ' after player death pause
END TYPE
'----------------------------------------------------------------------------------------------------------------------------------------------------------------------+----------+
TYPE LASERHIT ' laser hitting objects properties | LASERHIT |
Count AS INTEGER ' number of invaders player has hit
Invader AS INTEGER ' countdown timer after invader hit by laser +----------+
InvaderX AS INTEGER ' invader hit coordinates for explosion
InvaderY AS INTEGER
UFO AS INTEGER ' countdown timer after UFO hit by laser
UFOX AS INTEGER ' UFO hit coordinate for score text
Shield AS INTEGER ' countdown timer after shield hit by laser
ShieldX AS INTEGER ' shield hit coordinates for explosion (mask)
ShieldY AS INTEGER
Bomb AS INTEGER ' countdown timer after bomb hit by laser
BombX AS INTEGER ' bomb hit coordinates for explosion
BombY AS INTEGER
END TYPE
'-----------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------+
TYPE BOMBHIT ' bomb hitting objects properties | BOMBHIT |
Shield AS INTEGER ' countdown timer after shield hit by bomb
ShieldX AS INTEGER ' shield hit coordinates for explosion (mask)
ShieldY AS INTEGER
END TYPE
'-------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------+
TYPE LASER ' laser properties | LASER |
rect AS RECT ' laser coordinates +-------+
Active AS INTEGER ' laser active (t/f)
Hit AS LASERHIT ' laser hit something
Miss AS INTEGER ' laser hit top of screen
ShotsFired AS INTEGER ' number of laser shots fired (for UFO scoring)
END TYPE
'--------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------+
TYPE SHIP ' player ship properties | SHIP |
rect AS RECT ' player ship coordinates +------+
Remain AS INTEGER ' player ships remain
Dead AS INTEGER ' player ship is dead
DeadX AS INTEGER ' player ship death location
DeadImage AS INTEGER ' player ship exploding images indicator (-1 or 1)
Extra AS INTEGER ' extra ship awarded to player (t/f)
END TYPE
'------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------+
TYPE SOUNDS ' sounds | SOUNDS |
InvaderHit AS LONG ' invader hit sound snd.invaderhit +--------+
PlayerHit AS LONG ' player hit sound snd.playerhit
UFOHit AS LONG ' UFO hit sound snd.ufohit
UFOFlying AS LONG ' UFO slying sound snd.ufoflying
Laser AS LONG ' laser firing sound snd.laser
Coin AS LONG ' coin dropping sound snd.coin
Beat1 AS LONG ' heartbeat sounds snd.beat1
Beat2 AS LONG ' snd.beat2
Beat3 AS LONG ' snd.beat3
Beat4 AS LONG ' snd.beat4
Extra AS LONG ' extra ship sound snd.extra
END TYPE
'------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------+
TYPE IMAGES ' images | IMAGES |
UFO AS LONG ' UFO image img.ufo +--------+
InvaderHit AS LONG ' invader explosion img.invaderhit
BombHit AS LONG ' bomb explosion (bottom of screen and shields) img.bombhit
BombHitMask AS LONG ' destroy shields image img.bombhitmask
LaserHit AS LONG ' laser hit explosion (top of screen and shields) img.laserhit
LaserHitMask AS LONG ' destroy shields image img.laserhitmask
Shield AS LONG ' shield img.shield
QB64PE AS LONG ' QB64 Phoenix Edition logo img.qb64pe
DipSwitch AS LONG ' single DIP switch image img.dipswitch
END TYPE
'-----------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------+
TYPE OPTIONS ' game options | OPTIONS |
ScreenSize AS INTEGER ' screen size (1 to 4) options.screensize +---------+
Shields AS INTEGER ' number of shields (3 to 6) options.shields
ExtraShip AS INTEGER ' extra ship score (1000 or 1500) options.extraship
FreePlay AS INTEGER ' free play (t/f) options.freeplay
Background AS INTEGER ' show background (t/f) options.background
Bezel AS INTEGER ' show bezel (t/f) options.bezel
Switches AS INTEGER ' DIP switch settings options.switches
FullScreen AS INTEGER ' full screen (t/f) options.fullscreen
END TYPE
'-----------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------+
TYPE DISPLAY ' screens | DISPLAY |
WorkScreen AS LONG ' 224x248 game work screen display.workscreen +---------+
Screen AS LONG ' screen to stretch work screen onto display.screen
OptionScreen AS LONG ' options (dip switch) screen display.optionscreen
ColorMask AS LONG ' color mask to lay over display.colormask
WorkMask AS LONG ' work mask screen display.workmask
Bezel AS LONG ' bezel image display.bezel
Background AS LONG ' background image display.background
Bez AS RECT ' display screen coordinates within bezel display.bez
WithY AS LONG ' Images for intro animations display.withy
WithoutY AS LONG ' display.withouty
CorrectY AS LONG ' display.correcty
AddedC AS LONG ' display.addedc
NormalC AS LONG ' display.normalc
END TYPE
'--------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------+
TYPE GAME ' game in progress settings | GAME |
Player AS INTEGER ' current player playing game game.player +------+
Players AS INTEGER ' number of players playing game game.players
Credits AS INTEGER ' credits inserted into machine game.credits
Pause AS PAUSE ' pause needed between levels game.pause.level (count down timer 120 frames or 2 seconds - PlayGame)
' pause needed between player deaths game.pause.die (count down timer 180 frames or 3 seconds - PlayGame)
Frame AS INTEGER ' master frame counter game.frame
HighScore AS LONG ' game high score game.highscore
Landed AS INTEGER ' invaders landed (t/f) game.landed
END TYPE
'--------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------+
TYPE BOMB ' bomb properties | BOMB |
rect AS RECT ' bomb coordinates bomb().rect +------+
Hit AS BOMBHIT ' indicates bomb hit a shield bomb().hit.shield (count down timer 5 frames - DrawShields)
' where the bomb hit the shield bomb().hit.shieldx
' bomb().hit.shieldy
Image AS INTEGER ' bomb image (1 to 3) bomb().image
Cell AS INTEGER ' bomb image animation cell (1 to 4) bomb().cell
Active AS INTEGER ' bomb currently dropping (t/f) bomb().active
Miss AS INTEGER ' bomb hit bottom of screen bomb().miss (count down timer 5 frames - DrawBombs)
END TYPE
'---------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----+
TYPE UFO ' UFO properties | UFO |
rect AS RECT ' UFO coordinates ufo.rect +-----+
Dir AS INTEGER ' UFO direction ufo.dir
Active AS INTEGER ' UFO active ufo.active
Score AS INTEGER ' UFO score ufo.score
Pause AS INTEGER ' Time to wait for next UFO ufo.pause (count down timer 1500 frames +/- 120 - DrawUFO)
END TYPE
'------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------+
TYPE PLAYER ' player properties | PLAYER |
Ship AS SHIP ' player ship coordinates player().ship.rect +--------+
' player ships remaining player().ship.remain
' indicates the player's ship was hit player().ship.dead
' where on screen the ship was hit player().ship.deadx
' player ship exploding images indicator (-1 or 1) player().ship.deadimage
' indicates if player was awarded extra ship (t/f) player().ship.extra
Level AS INTEGER ' player level player().level
Score AS LONG ' player score player().score
GameOver AS INTEGER ' player game over (t/f) player().gameover
Laser AS LASER ' player laser coordinates player().laser.rect
' indicates if laser if currently flying (t/f) player().laser.active
' indicates laser hit top of screen player().laser.miss (count down timer 10 frames - DrawLaser)
' indictaes laser hit an invader player().laser.hit.invader (count down timer 5 frames - DrawInvaders)
' location on screen where invader was hit player().laser.hit.invaderx
' player().laser.hit.invadery
' number of invaders killed (1 to 55) player().laser.hit.count
' indicates if laser hit a UFO player().laser.hit.ufo (count down timer 60 frames or 1 second - DrawUFO)
' location on screen where UFO was hit player().laser.hit.ufox
' indicates that laser hit a shield player().laser.hit.shield (count down timer 5 frames - DrawShields)
' location on shield where laser hit player().laser.hit.shieldx
' player().laser.hit.shieldy
' indicates that laser hit a bomb player().laser.hit.bomb (count down timer 5 frames - DrawLaser)
' location on screen where laser hit bomb player().laser.hit.bombx
' player().laser.hit.bomby
' number of lasers fired by player (for UFO score) player().laser.shotsfired
idir AS INTEGER ' invader direction (-2 or 2) player().idir
MaxBombs AS INTEGER ' maximum number of invader bombs allowed (1 to 3) player().maxbombs
UFO AS UFO ' UFO saved state in multiplayer game player().ufo.rect
' direction of UFO (-1 or 1) player().ufo.dir
' indicates if UFO currently flying (t/f) player().ufo.active
' score of UFO if hit player().ufo.score
' time to wait in between UFO showings (23 to 27 sec) player().ufo.pause
Keydown AS INTEGER ' player is holding a key down (t/f) player().keydown
END TYPE
'-----------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------+
TYPE SHIELDS ' shield properties | SHIELDS |
rect AS RECT ' shield coordinates shield(player,x).rect +---------+
Image AS LONG ' damaged shield image shield(player,x).image
END TYPE
'----------------------------------------------------------------------------------------------------------------------------------------------------------------------+----------+
TYPE INVADERS ' invader properties | INVADERS |
rect AS RECT ' invader coordinates invader(player,column,row).rect +----------+
Active AS INTEGER ' invader active (t/f) invader(player,column,row).active
cell AS INTEGER ' invader image animation cell invader(player,column,row).cell
Image AS INTEGER ' invader image ' invader(player,column,row).image
Width AS INTEGER ' invader width invader(player,column,row).width
Score AS INTEGER ' invader score invader(player,column,row).score
END TYPE
'--------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------+
TYPE DROPCOLUMN ' bomb column properties | DROPCOLUMN |
Pause AS INTEGER ' time to wait before dropping bomb in column dropcolumn().pause (count down timer) +------------+
Row AS INTEGER ' row that contains bottom invader (0 for none) dropcolumn().row
END TYPE
'------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------+
DIM Player(2) AS PLAYER ' player settings | Declared Variables |
DIM Invader(2, 11, 5) AS INVADERS ' 55 invaders (player,column,row) +--------------------+
DIM DropColumn(11) AS DROPCOLUMN ' 11 columns for bomb drops
DIM Bomb(3) AS BOMB ' invader bombs dropping
DIM UFO(2) AS UFO ' UFO settings
DIM IMG AS IMAGES ' images
DIM SND AS SOUNDS ' sounds
DIM Display AS DISPLAY ' display screens
DIM Options AS OPTIONS ' player selectable options
DIM Game AS GAME ' current game settings
DIM Font(255) AS LONG ' game font characters
DIM IMG_Invader(3, 1) AS LONG ' invader images and animation cells (image, cell)
DIM IMG_Bomb(3, 3) AS LONG ' bomb images and animation cells (image, cell)
DIM IMG_Ship(-1 TO 1) AS LONG ' images of player ship (-1,1 explosion, 0 intact)
DIM Shield(2, 6) AS SHIELDS ' player shield images (player, shield)
' +-------------------+
' | MAIN CODE SECTION |
'-------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------------+
LoadAssets ' load the games graphic and sound files | MAIN GAME LOOP |
LoadOptions ' load the saved game options +-------------------+
Initialize ' initialize all game variables
DO ' begin game play loop
IF Options.FreePlay THEN Game.Credits = 99 ' fill the game with coins if set to free play
InsertCoin ' allow the player to insert coins (skipped if free play)
SelectPlayers ' select the number of players and play a game
LOOP ' loop back forever
' +---------------------------+
' | END MAIN GAME LOOP |
'-----------------------------------------------------------------------------------------------------------------------------------------------------+---------------------------+
' | SUBROUTINES AND FUNCTIONS |
' +---------------------------+
'----------------------------------------------------------------------------------------------------------------------------------------------------------------------+----------+
SUB PlayGame (Players AS INTEGER) ' | PlayGame |
'+-----------------------------------------------------------------------------------------------------------------------------------------------------------------+----------+
'| Plays a 1 or 2 player game of Space Invaders |
'| Players - number of players (1 or 2) |
'+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
SHARED Player() AS PLAYER ' need access to shared variables
SHARED Game AS GAME
SHARED UFO() AS UFO
SHARED SND AS SOUNDS
DIM KeyPress AS INTEGER ' keypress in slow text
DIM p AS INTEGER ' current player
StartNewGame ' reset variables to start a new game
Game.Players = Players ' record the number of players passed in
p = Game.Player ' get the current player
IF Players = 1 THEN Player(2).GameOver = TRUE ' kill off player 2 if a one player game
GetReady ' inform player 1 to get ready
DO ' begin main game loop
DO ' begin game level loop
_LIMIT 60 ' game runs at 60 frames per second
ClearDisplay INGAME ' clear the display
IF Game.Pause.Level THEN ' is game paused for a level change?
Game.Pause.Level = Game.Pause.Level - 1 ' yes, decrement pause count down timer
IF Game.Pause.Level = 0 THEN ' has the count down finished?
ResetBombs ' yes, reset the bombs
DrawShields NEWLEVEL ' restore player shields for next level
END IF
END IF
IF Players = 2 THEN ' is this a 2 player game?
DrawScore BOTHPLAYERS, INGAME ' yes, display both scores on screen
ELSE ' no, this is a 1 player game
DrawScore PLAYER1, INGAME ' display just player 1's score
END IF
MoveInvaders p, INGAME ' move the invaders
DrawInvaders ' draw the invaders to the screen
DrawShields INGAME ' draw the player's shields
DrawUFO ' draw the UFO when active
DrawShip ' draw the player's ship
DrawLaser ' draw the player's laser when active
DrawBombs ' draw the invader bombs when active
DrawShipsRemaining ' draw the number of ships the player has remaining
UpdateDisplay INGAME ' update the display with all the changes
IF _KEYDOWN(27) OR _EXIT THEN ExitGame ' exit the game if the player presses ESC
LOOP UNTIL Player(p).Laser.Hit.Count = 55 OR Player(p).Ship.Dead OR Game.Landed ' leave when level finished or player dead
IF Game.Landed THEN ' did the invaders land?
Player(p).Ship.Dead = TRUE ' yes, the player is dead
Player(p).Ship.DeadX = Player(p).Ship.rect.x1 ' the player died at this location
_SNDPLAY SND.PlayerHit ' play the player death sound
Player(p).Ship.Remain = 1 ' take all ships away from player
Game.Landed = FALSE ' reset for 2nd player if needed
END IF
IF Player(p).Laser.Hit.Count = 55 THEN ' did the player shoot all of the invaders?
Game.Pause.Level = 120 ' yes, pause for 2 seconds between levels
StartNewLevel ' reset variables for a new level
ELSEIF Player(p).Ship.Dead THEN ' no, is the player dead?
ResetBombs ' yes, reset the bombs
IF Game.Pause.Level THEN ' did the player die between level changes?
Game.Pause.Level = 0 ' yes, stop the level pause count down
DrawShields NEWLEVEL ' restore the player's shields for the new level
END IF
Player(p).Ship.Remain = Player(p).Ship.Remain - 1 ' take a ship away from player
Game.Pause.Die = 180 ' 3 second pause after player dies
Player(p).Laser.Active = FALSE ' deactivate the player's laser
Player(p).UFO = UFO(p) ' save the player's UFO state
IF UFO(p).Active AND Players = 2 THEN ' is the UFO still active in a 2 player game?
UFO(p).Active = FALSE ' yes, deactivate the UFO
_SNDSTOP SND.UFOFlying ' stop the UFO sound
END IF
DO ' begin player death pause loop
_LIMIT 60 ' sequence will run at 60 frames per second
ClearDisplay INGAME ' clear the display
IF Players = 1 THEN ' is this a 1 player game?
DrawScore PLAYER1, INGAME ' yes, just draw player 1's score
IF Player(1).Ship.Remain > 0 THEN ' does the player have any ships remaining?
DrawUFO ' yes, keep the UFO flying during pause
ELSE ' no, player 1's game is about to end
_SNDSTOP SND.UFOFlying ' stop the UFO sound if it happens to be playing
END IF
ELSE ' no, this is a 2 player game
DrawScore BOTHPLAYERS, INGAME ' draw both player's scores to the screen
END IF
DrawInvaders ' draw the invaders without moving
DrawShields INGAME ' draw the player's current shields
DrawShipsRemaining ' draw the number of ships the player has remaining
IF Game.Pause.Die = 1 THEN ' is this the last frame of the death pause?
IF Player(p).Ship.Remain = 0 THEN ' yes, is the player out of ships?
IF Players = 1 THEN ' yes, is this a 1 player game?
DrawScore PLAYER1, INGAME ' yes, draw player 1's score
SlowText 6, 9, "GAME OVER", INGAME, KeyPress ' slowly tell player 1 that the game is over
ELSE ' no, this is a 2 player game
DrawScore BOTHPLAYERS, INGAME ' draw both player scores to the screen
SlowText 6, 5, "GAME OVER PLAYER<" + _TRIM$(STR$(p)) + ">", INGAME, KeyPress ' slowly tell the current player that the game is over
END IF
Player(p).GameOver = TRUE ' the player's game is over
SLEEP 2 ' pause for 2 seconds to let it sink in
ELSE ' no, the player has at least 1 ship remaining
Player(p).Ship.Dead = FALSE ' bring the player back to life
END IF
END IF
DrawShip ' draw the player's ship blowing up
UpdateDisplay INGAME ' update the display with all the changes made
Game.Pause.Die = Game.Pause.Die - 1 ' decrement the death pause count down timer
IF _KEYDOWN(27) OR _EXIT THEN ExitGame ' leave the game if the player presses ESC
LOOP UNTIL Game.Pause.Die = 0 ' leave the death loop when the count down has completed
IF Players = 2 AND (Player(1).GameOver = FALSE OR Player(2).GameOver = FALSE) THEN ' is this a 2 player game with at least 1 player still active?
DO ' yes, begin next player search loop
p = p + 1 ' increment the player number
IF p > 2 THEN p = 1 ' return back to player 1 if needed
LOOP UNTIL Player(p).Ship.Dead = FALSE ' leave the loop when a live player is found
Game.Player = p ' record the new current player
GetReady ' tell the player to get ready
UFO(p) = Player(p).UFO ' restore the UFO settings
IF UFO(p).Active THEN _SNDLOOP SND.UFOFlying ' restart the UFO sound if the UFO happens to be flying
END IF
END IF
LOOP UNTIL Player(1).GameOver AND Player(2).GameOver ' leave when both players have exhaused their ships
_KEYCLEAR ' clear all keyboard buffers
KeyPress = GetKey(NEWGAME) ' clear getkey buffer
SaveOptions ' save the options in case a new high score was achieved
END SUB
'--------------------------------------------------------------------------------------------------------------------------------+---------------------------------+--+-----------+
SUB DrawBombs () ' | COLLISION: Bomb and Player Ship | | DrawBombs |
'+---------------------------------------------------------------------------------------------------------------------------+---------------------------------+--+-----------+
'| Manages invader bombs and collisions between bombs and the player's ship |
'+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
SHARED Invader() AS INVADERS ' need access to shared variables
SHARED Bomb() AS BOMB
SHARED IMG_Bomb() AS LONG
SHARED DropColumn() AS DROPCOLUMN
SHARED IMG AS IMAGES
SHARED Game AS GAME
SHARED Player() AS PLAYER
SHARED Shield() AS SHIELDS
SHARED SND AS SOUNDS
DIM b AS INTEGER ' bomb counter
DIM c AS INTEGER ' column counter
DIM p AS INTEGER ' current player
p = Game.Player ' get current player
DO ' begin active bomb loop
b = b + 1 ' increment bomb counter
IF Bomb(b).Active THEN ' is this bomb falling?
IF Bomb(b).Hit.Shield THEN ' yes, has the bomb hit a shield?
Bomb(b).Hit.Shield = Bomb(b).Hit.Shield - 1 ' yes, decrement count down timer
_PUTIMAGE (Bomb(b).Hit.ShieldX, Bomb(b).Hit.ShieldY), IMG.BombHit ' show bomb explosion
IF Bomb(b).Hit.Shield = 0 THEN Bomb(b).Active = FALSE
ELSEIF Bomb(b).Miss THEN ' did bomb miss and hit top of screen?
Bomb(b).Miss = Bomb(b).Miss - 1 ' yes, decrement count down timer
_PUTIMAGE (Bomb(b).rect.x1 - 1, Bomb(b).rect.y1), IMG.BombHit ' show bomb explosion image
IF Bomb(b).Miss = 0 THEN Bomb(b).Active = FALSE ' deactivate bomb when count down complete
ELSE ' no, bomb is stil falling
Bomb(b).rect.y1 = Bomb(b).rect.y1 + 1 + Game.Frame MOD 2 ' drop bomb at 90 FPS
Bomb(b).rect.y2 = Bomb(b).rect.y2 + 1 + Game.Frame MOD 2
Bomb(b).Cell = Bomb(b).Cell + 1 ' increment animation cell
IF Bomb(b).Cell = 4 THEN Bomb(b).Cell = 1 ' reset animation cell when needed
_PUTIMAGE (Bomb(b).rect.x1, Bomb(b).rect.y1), IMG_Bomb(Bomb(b).Image, Bomb(b).Cell) ' show bomb on screen
IF Bomb(b).rect.y1 >= 228 THEN ' has bomb hit bottom of screen?
Bomb(b).Miss = 5 ' yes, set count down timer
ELSE ' no, bomb is still on the sceen
'******************************************************
'** Check for collision between bomb and player ship **
'******************************************************
IF NOT GODMODE THEN ' is developer in god mode?
IF Player(p).Ship.Dead = FALSE THEN ' no, is the player ship active?
IF RectCollide(Player(p).Ship.rect, Bomb(b).rect) THEN ' yes, has the bomb hit the player's ship?
Player(p).Ship.Dead = TRUE ' yes, player is dead
Player(p).Ship.DeadX = Player(p).Ship.rect.x1 ' record where on screen ship was hit
_SNDPLAY SND.PlayerHit ' play ship explosion sound
END IF
END IF
END IF
END IF
END IF
END IF
LOOP UNTIL b = Player(p).MaxBombs ' leave when all bombs checked
IF Game.Pause.Level THEN EXIT SUB ' leave subroutine if game is paused between levels
DO ' begin bomb drop column loop
c = c + 1 ' increment column counter
IF DropColumn(c).Pause THEN ' is this column ready to have a bomb dropped?
DropColumn(c).Pause = DropColumn(c).Pause - 1 ' no, decrement pause timer
ELSE ' yes, the pause period has ended
IF DropColumn(c).Row THEN ' is there an invader in this column?
IF INT(RND * 11) = 1 THEN ' yes, should a bomb be randomly dropped?
b = 0 ' yes, reset bomb counter
DO ' begin inactive bomb search
b = b + 1 ' increment bomb counter
LOOP UNTIL Bomb(b).Active = FALSE OR b = Player(p).MaxBombs ' leave when inactive bomb found or no inactive bombs
IF Bomb(b).Active = FALSE THEN ' is this bomb inactive?
Bomb(b).Active = TRUE ' yes, activate the bomb
Bomb(b).Image = INT(RND * 3) + 1 ' set a random bomb image
Bomb(b).Cell = 1 ' reset the animation cell
Bomb(b).rect.x1 = Invader(p, c, DropColumn(c).Row).rect.x1 + Invader(p, c, DropColumn(c).Row).Width / 2 - 1 ' calculate bomb location on screen
Bomb(b).rect.y1 = Invader(p, c, DropColumn(c).Row).rect.y2 - 2
Bomb(b).rect.x2 = Bomb(b).rect.x1 + 2
Bomb(b).rect.y2 = Bomb(b).rect.y1 + 7
DropColumn(c).Pause = INT(RND * 15) + 55 - Player(p).Laser.Hit.Count ' reset the pause timer for this column
END IF
END IF
END IF
END IF
LOOP UNTIL c = 11 ' leave when all columns checked
END SUB
'--------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------+
SUB ResetBombs () ' | ResetBombs |
'+---------------------------------------------------------------------------------------------------------------------------------------------------------------+------------+
'| Resets the bombs and columns status |
'+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
SHARED DropColumn() AS DROPCOLUMN ' need access to shared variables
SHARED Bomb() AS BOMB
DIM c AS INTEGER ' column counter
DO ' begin column loop
c = c + 1 ' increment column counter
DropColumn(c).Pause = 60 + INT(RND * 240) ' reset column pause timer
IF c < 4 THEN Bomb(c) = Bomb(0) ' reset status of bomb
LOOP UNTIL c = 11 ' leave when all columns processed
END SUB
'-------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+
FUNCTION RectCollide (Rect1 AS RECT, Rect2 AS RECT) ' | RectCollide |
'+--------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+
'| Detects a collision between two rectangular objects |
'| Rect1 - the first set of rectangular coordinates |
'| Rect2 - the second set of rectangular coordinates |
'+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
RectCollide = FALSE ' assume no collision
IF Rect1.x2 >= Rect2.x1 THEN
IF Rect1.x1 <= Rect2.x2 THEN
IF Rect1.y2 >= Rect2.y1 THEN
IF Rect1.y1 <= Rect2.y2 THEN
RectCollide = TRUE ' a collision has occurred
END IF
END IF
END IF
END IF
END FUNCTION
'--------------------------------------------------------------------------------------------------------------------------------------+---------------------------+--+-----------+
SUB DrawLaser () ' | COLLISION: Laser and Bomb | | DrawLaser |
'+---------------------------------------------------------------------------------------------------------------------------------+---------------------------+--+-----------+
'| Manages the player's laser and collisions between the laser and bombs |
'+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
SHARED Player() AS PLAYER ' need access to shared variables
SHARED SND AS SOUNDS
SHARED IMG AS IMAGES
SHARED Bomb() AS BOMB
SHARED Game AS GAME
DIM b AS INTEGER ' bomb counter
DIM p AS INTEGER ' current player
p = Game.Player ' get current player
IF NOT _KEYDOWN(32) THEN Player(p).Keydown = FALSE ' remember when player releases space bar
IF Player(p).Laser.Active = FALSE THEN ' is the player's laser flying?
IF _KEYDOWN(32) AND Player(p).Keydown = FALSE THEN ' no, did the player press the space bar after releasing it?
Player(p).Laser.Active = TRUE ' yes, activate the laser
Player(p).Laser.ShotsFired = Player(p).Laser.ShotsFired + 1 ' increment the shots fired counter
Player(p).Keydown = TRUE ' remember that player is holding the space bar down
Player(p).Laser.rect.x1 = Player(p).Ship.rect.x1 + 6 ' calculate the position of the laser
Player(p).Laser.rect.x2 = Player(p).Laser.rect.x1
Player(p).Laser.rect.y1 = 216
Player(p).Laser.rect.y2 = 219
_SNDPLAY SND.Laser ' play the laser fired sound
END IF
ELSE ' yes, the player's laser is active
IF Player(p).Laser.Miss THEN ' did the player's laser hit the top of screen?
Player(p).Laser.Miss = Player(p).Laser.Miss - 1 ' yes, decrement count down timer
_PUTIMAGE (Player(p).Laser.rect.x1 - 4, 24), IMG.LaserHit ' show the laser explosion
IF Player(p).Laser.Miss = 0 THEN Player(p).Laser.Active = FALSE ' deactivate the laser when count down complete
ELSEIF Player(p).Laser.Hit.Invader THEN ' no, did the laser hit an invader?
Player(p).Laser.Hit.Invader = Player(p).Laser.Hit.Invader - 1 ' yes, decrement the count down timer
_PUTIMAGE (Player(p).Laser.Hit.InvaderX, Player(p).Laser.Hit.InvaderY), IMG.InvaderHit ' show invader explosion
IF Player(p).Laser.Hit.Invader = 0 THEN Player(p).Laser.Active = FALSE ' deactivate the laser whn count down complete
ELSEIF Player(p).Laser.Hit.Bomb THEN ' no, did the laser hit a bomb?
Player(p).Laser.Hit.Bomb = Player(p).Laser.Hit.Bomb - 1 ' yes, decrement the count down timer
_PUTIMAGE (Player(p).Laser.Hit.BombX, Player(p).Laser.Hit.BombY), IMG.BombHit ' show bomb explosion
IF Player(p).Laser.Hit.Bomb = 0 THEN ' has the count down timer ended?
Player(p).Laser.Active = FALSE ' yes, deactivate the player's laser
END IF
ELSEIF Player(p).Laser.Hit.Shield THEN ' no, did the laser hit a shield?
Player(p).Laser.Hit.Shield = Player(p).Laser.Hit.Shield - 1 ' yes, decrement the count down timer
_PUTIMAGE (Player(p).Laser.Hit.ShieldX, Player(p).Laser.Hit.ShieldY), IMG.LaserHit ' show laser explosion
IF Player(p).Laser.Hit.Shield = 0 THEN Player(p).Laser.Active = FALSE ' deactivate the laser when count down complete
ELSE ' no, laser is still flying
Player(p).Laser.rect.y1 = Player(p).Laser.rect.y1 - 4 ' move the laser upward
Player(p).Laser.rect.y2 = Player(p).Laser.rect.y2 - 4
LINE (Player(p).Laser.rect.x1, Player(p).Laser.rect.y1)-(Player(p).Laser.rect.x2, Player(p).Laser.rect.y2 + 3), WHITE ' draw the laser
IF Player(p).Laser.rect.y1 = 24 THEN ' did the laser hit the top of screen?
Player(p).Laser.Miss = 10 ' yes, set the count down timer
ELSE ' no, laser still on sreen
'************************************************
'** Check for collision between laser and bomb **
'************************************************
DO ' begin bomb check loop
b = b + 1 ' increment bomb counter
IF Bomb(b).Active AND Player(p).Laser.Active THEN ' is this bomb falling and player laser active?
IF RectCollide(Player(p).Laser.rect, Bomb(b).rect) THEN ' yes, did the bomb hit the laser?
Player(p).Laser.Hit.Bomb = 5 ' yes, set the count down timer
Player(p).Laser.Hit.BombX = Bomb(b).rect.x1 - 2 ' record the location of the collision
Player(p).Laser.Hit.BombY = Bomb(b).rect.y1 + 5
IF INT(RND * 2) = 1 THEN Bomb(b).Active = FALSE ' randomly deactivate the bomb
END IF
END IF
LOOP UNTIL b = Player(p).MaxBombs OR Player(p).Laser.Hit.Bomb ' leave when all bombs checked
END IF
END IF
END IF
END SUB
'-----------------------------------------------------------------------------------------------------------------------+---------------------------------------+--+--------------+
SUB DrawInvaders () ' | COLLISION:Invader and Laser or Shield | | DrawInvaders |
'+------------------------------------------------------------------------------------------------------------------+---------------------------------------+--+--------------+
'| Draws the active invaders to the screen and handles collisions between invaders and the player's laser and invaders and shields |
'+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
SHARED Invader() AS INVADERS ' need access to shared variables
SHARED DropColumn() AS DROPCOLUMN
SHARED IMG_Invader() AS LONG
SHARED Shield() AS SHIELDS
SHARED Display AS DISPLAY
SHARED Options AS OPTIONS
SHARED Player() AS PLAYER
SHARED SND AS SOUNDS
SHARED Game AS GAME
DIM x AS INTEGER ' invader column counter
DIM y AS INTEGER ' invader row counter
DIM s AS INTEGER ' shield counter
DIM shx AS INTEGER ' location of invader and shield collision
DIM shy AS INTEGER
DIM p AS INTEGER ' current player
IF Game.Pause.Level THEN EXIT SUB ' leave subroutine if game paused between levels
p = Game.Player ' get current player
DO ' begin invader column loop
x = x + 1 ' increment column counter
y = 0 ' reset row counter
DropColumn(x).Row = 0 ' assume no invaders in this column
DO ' begin invader row loop
y = y + 1 ' increment row counter
IF Invader(p, x, y).Active THEN ' is this invader active?
_PUTIMAGE (Invader(p, x, y).rect.x1, Invader(p, x, y).rect.y1), IMG_Invader(Invader(p, x, y).Image, Invader(p, x, y).cell) ' yes, draw the invader
IF y > DropColumn(x).Row THEN DropColumn(x).Row = y ' record the lowest invader in the column
'********************************************
'** Check for invader and shield collision **
'********************************************
IF Invader(p, x, y).rect.y1 > 191 AND Invader(p, x, y).rect.y1 < 209 THEN ' is the invader in the shield area?
s = 0 ' yes, reset shield counter
DO ' begin shield loop
s = s + 1 ' increment shield counter
IF RectCollide(Invader(p, x, y).rect, Shield(p, s).rect) THEN ' is the invader colliding with a shield?
shx = Invader(p, x, y).rect.x1 - Shield(p, s).rect.x1 ' yes, record the location of the collision
shy = Invader(p, x, y).rect.y1 - 192
_DEST Shield(p, s).Image ' draw on the shield image
LINE (shx, shy)-(shx + Invader(p, x, y).Width - 1, shy + 7), BLACK, BF ' remove portion of shield where collision occurring
_DEST Display.WorkScreen ' return to drawing on work display
END IF
LOOP UNTIL s = Options.Shields ' leave when all shields have been checked
ELSEIF Invader(p, x, y).rect.y1 = 216 THEN ' has the invader landed at ship location?
'**************************************
'** Invader reached bottom of screen **
'**************************************
Game.Landed = TRUE ' yes, remember that invader has landed
END IF
'*******************************************
'** Check for invader and laser collision **
'*******************************************
IF Player(p).Laser.Active THEN ' is the player's laser flying?
IF RectCollide(Player(p).Laser.rect, Invader(p, x, y).rect) THEN ' yes, has the laser collided with an invader?
Player(p).Score = Player(p).Score + Invader(p, x, y).Score ' yes, add the invader's score to the player's score
Player(p).Laser.Hit.Count = Player(p).Laser.Hit.Count + 1 ' increment the player's invader hit counter
IF Player(p).Laser.Hit.Count = 27 THEN ' have half the invaders been destroyed?
IF Player(p).MaxBombs < 3 THEN ' yes, are the maximum number of bombs dropping?
Player(p).MaxBombs = Player(p).MaxBombs + 1 ' no, increase the amount of bombs invaders allowed to drop
END IF
END IF
Player(p).Laser.Hit.Invader = 5 ' set count down timer
Player(p).Laser.Hit.InvaderX = Invader(p, x, y).rect.x1 - ((14 - Invader(p, x, y).Width) \ 2) ' record where the collision occurred
Player(p).Laser.Hit.InvaderY = Invader(p, x, y).rect.y1
Invader(p, x, y).Active = FALSE ' deactivate this invader
_SNDPLAY SND.InvaderHit ' play the invader explosion sound
END IF
END IF
END IF
LOOP UNTIL y = 5 ' leave when all rows checked
LOOP UNTIL x = 11 ' leave whan all columns checked
END SUB
'----------------------------------------------------------------------------------------------------------------------------------------------------------------------+----------+
SUB DrawShip () ' | DrawShip |
'+-----------------------------------------------------------------------------------------------------------------------------------------------------------------+----------+
'| Manages the player's ship |
'+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
SHARED IMG_Ship() AS LONG ' need access to shared variables
SHARED Player() AS PLAYER
SHARED Game AS GAME
DIM p AS INTEGER ' current player
p = Game.Player ' get current player
IF Game.Pause.Die THEN ' is a death pause happening?
IF Game.Frame MOD 5 = 0 THEN Player(p).Ship.DeadImage = -Player(p).Ship.DeadImage ' yes, toggle the death image every 5 frames
_PUTIMAGE (Player(p).Ship.DeadX, Player(p).Ship.rect.y1), IMG_Ship(Player(p).Ship.DeadImage) ' draw the ship death image
IF Game.Pause.Die = 1 THEN ' is this the last frame of the death pause?
Player(p).Ship.rect.x1 = 15 ' yes, reset player's ship location
Player(p).Ship.rect.x2 = 27
END IF
ELSE ' no, player is still alive
IF _KEYDOWN(19712) THEN ' is player pressing the right arrow key?
Player(p).Ship.rect.x1 = Player(p).Ship.rect.x1 + 1 ' yes, move ship to the right
Player(p).Ship.rect.x2 = Player(p).Ship.rect.x2 + 1
END IF
IF _KEYDOWN(19200) THEN ' is player pressing the left arrow key?
Player(p).Ship.rect.x1 = Player(p).Ship.rect.x1 - 1 ' yes, move ship to the left
Player(p).Ship.rect.x2 = Player(p).Ship.rect.x2 - 1
END IF
IF Player(p).Ship.rect.x1 < 15 THEN ' is the ship moving too far left?
Player(p).Ship.rect.x1 = 15 ' yes, hold ship at left side of screen
Player(p).Ship.rect.x2 = 27
END IF
IF Player(p).Ship.rect.x2 > 207 THEN ' is the ship moving too far right?
Player(p).Ship.rect.x1 = 194 ' yes, hold ship at right side of screen
Player(p).Ship.rect.x2 = 207
END IF
_PUTIMAGE (Player(p).Ship.rect.x1, Player(p).Ship.rect.y1), IMG_Ship(0) ' draw player ship
END IF
END SUB
'-----------------------------------------------------------------------------------------------------------------------------------------+--------------------------+--+---------+
SUB DrawUFO () ' | COLLISION: Laser and UFO | | DrawUFO |
'+------------------------------------------------------------------------------------------------------------------------------------+--------------------------+--+---------+
'| Manages the UFO and collisions between the UFO and player's laser |
'+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
SHARED Player() AS PLAYER ' need access to shared variables
SHARED UFO() AS UFO
SHARED Game AS GAME
SHARED IMG AS IMAGES
SHARED SND AS SOUNDS
DIM p AS INTEGER ' current player
p = Game.Player ' get current player
IF Player(p).Laser.Hit.UFO THEN ' did the player's laser hit the UFO?
Player(p).Laser.Hit.UFO = Player(p).Laser.Hit.UFO - 1 ' yes, decrement count down timer
Text Player(p).Laser.Hit.UFOX, 32, _TRIM$(STR$(UFO(p).Score)) ' display the UFO score where UFO was hit
END IF
IF UFO(p).Active THEN ' is the UFO flying across the screen?
IF Game.Frame MOD 4 THEN ' yes, skip every 4th frame
UFO(p).rect.x1 = UFO(p).rect.x1 + UFO(p).Dir ' move UFO at 45 FPS
UFO(p).rect.x2 = UFO(p).rect.x1 + 15
END IF
IF UFO(p).rect.x1 > 199 OR UFO(p).rect.x1 < 8 THEN ' has the UFO reached the edge of screen?
_SNDSTOP SND.UFOFlying ' yes, stop the UFO sound
UFO(p).Active = FALSE ' deactivate the UFO
ELSE ' no, UFO is still flying
_PUTIMAGE (UFO(p).rect.x1, UFO(p).rect.y1), IMG.UFO ' draw the UFO
'***********************************************
'** Check for collision between laser and UFO **
'***********************************************
IF Player(p).Laser.Active THEN ' is the player's laser active?
IF RectCollide(Player(p).Laser.rect, UFO(p).rect) THEN ' has the player's laser hit the UFO?
SELECT CASE INT(RND * 3) + 1 ' yes, set a random score value
CASE 1
UFO(p).Score = 50 ' 50 points
CASE 2
UFO(p).Score = 100 ' 100 points
CASE 3
UFO(p).Score = 200 ' 200 points
END SELECT
IF Player(p).Laser.ShotsFired = 23 THEN UFO(p).Score = 300 ' 300 points if the player has fired 23 lasers
IF Player(p).Laser.ShotsFired > 23 THEN ' has the player fired more than 23 lasers?
IF (Player(p).Laser.ShotsFired - 23) MOD 15 = 0 THEN ' every 15 laser firings afterwards?
UFO(p).Score = 300 ' yes, UFO score is again 300 points
END IF
END IF
Player(p).Score = Player(p).Score + UFO(p).Score ' add the UFO score to the player's score
Player(p).Laser.Hit.UFO = 60 ' set the count down timer
Player(p).Laser.Hit.UFOX = UFO(p).rect.x1 + 32 ' record where the UFO was hit
UFO(p).Active = FALSE ' deactivate the UFO
Player(p).Laser.Active = FALSE ' deactivate the player's laser
_SNDSTOP SND.UFOFlying ' stop the UFO sound
_SNDPLAY SND.UFOHit ' play the UFO explosion sound
END IF
END IF
END IF
ELSE ' no, UFO is currently inactive
UFO(p).Pause = UFO(p).Pause - 1 ' decrement the UFO pause timer
IF UFO(p).Pause = 0 THEN ' has the timer ended?
_SNDLOOP SND.UFOFlying ' yes, play the UFO flying sound
UFO(p).Active = TRUE ' activate the UFO
UFO(p).Pause = 1500 + INT(RND * 120) - INT(RND * 120) ' reset the UFO pause for 23 to 27 seconds
IF INT(RND * 2) = 1 THEN ' which direction should UFO come from?
UFO(p).Dir = 1 ' UFO will travel left to right
UFO(p).rect.x1 = 8 ' position UFO at left of screen
ELSE
UFO(p).Dir = -1 ' UFO will travel right to left
UFO(p).rect.x1 = 199 ' position UFO at right of screen
END IF
END IF
END IF
END SUB
'------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------+
SUB MoveInvaders (p AS INTEGER, Mode AS INTEGER) STATIC ' | MoveInvaders |
'+-------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------+
'| Manages the wave motion of invaders across the screen |
'| p - current player |
'| Mode - signal to clear variables for a movement reset (CLEARVARIABLES or -1) |
'| NOTE: This subroutine retains values between calls (STATIC) |
'| |
'| Moves the invaders across the screen by simulating the "strobing" effect of an Intel 8080 barely able to keep up with the graphics. Only one invader is updated per frame |
'| of the game to achieve the slow CPU effect. As fewer invaders are alive this subroutine will simulate the speeding up effect of the invaders as if an 8080 CPU has less |
'| work to do. |
'| |
'| From: https://en.wikipedia.org/wiki/Space_Invaders |
'| |
'| "While programming the game, Nishikado discovered that the processor was able to render the alien graphics faster the fewer were on screen. Rather than design the game to |
'| compensate for the speed increase, he decided to keep it as a challenging gameplay mechanic." |
'+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
SHARED Invader() AS INVADERS ' need access to shared variables
SHARED Player() AS PLAYER
SHARED SND AS SOUNDS
SHARED Game AS GAME
DIM c(2) AS INTEGER ' column counter
DIM r(2) AS INTEGER ' row counter
DIM Down(2) AS INTEGER ' invader to move down flag
DIM Edge(2) AS INTEGER ' invader hit edge of screen flag
DIM Beat(2) AS INTEGER ' current heart throb sound to play
IF Mode = CLEARVARIABLES OR Game.Pause.Level THEN ' time to reset the movement variables?
c(p) = 0 ' yes, reset all movement variables
r(p) = 0
Down(p) = FALSE
Edge(p) = FALSE
Beat(p) = 0
EXIT SUB ' leave the subroutine
END IF
DO ' begin active invader search loop
c(p) = c(p) + 1 ' increment column counter
IF c(p) = 12 THEN ' has the last column been reached?
c(p) = 1 ' yes, reset the column counter
r(p) = r(p) - 1 ' decrement the row counter
END IF
IF r(p) = 0 THEN ' has the top row been reached?
r(p) = 5 ' yes, reset the row counter
IF Edge(p) THEN ' has the invader reached the edge of the screen?
Down(p) = TRUE ' yes, flag the invader for a downward movement
Edge(p) = FALSE ' reset the edge detection flag
ELSEIF Down(p) THEN ' no, has the invader been flagged for a downward movement?
Down(p) = FALSE ' yes, reset the downward movement flag
Player(p).idir = -Player(p).idir ' reverse the direction of the invaders
END IF
Beat(p) = Beat(p) + 1 ' increment the heart throb sound counter
IF Beat(p) = 5 THEN Beat(p) = 1 ' reset the counter when needed
SELECT CASE Beat(p) ' which sound to play?
CASE 1
_SNDPLAY SND.Beat1
CASE 2
_SNDPLAY SND.Beat2
CASE 3
_SNDPLAY SND.Beat3
CASE 4
_SNDPLAY SND.Beat4
END SELECT
END IF
LOOP UNTIL Invader(p, c(p), r(p)).Active ' leave when an active invader found
Invader(p, c(p), r(p)).cell = 1 - Invader(p, c(p), r(p)).cell ' toggle the invader animation cell
IF Down(p) THEN ' is this invader flagged for downward movement?
Invader(p, c(p), r(p)).rect.y1 = Invader(p, c(p), r(p)).rect.y1 + 8 ' yes, move the invader down one row
Invader(p, c(p), r(p)).rect.y2 = Invader(p, c(p), r(p)).rect.y2 + 8
ELSE ' no, move the invader right or left
Invader(p, c(p), r(p)).rect.x1 = Invader(p, c(p), r(p)).rect.x1 + Player(p).idir ' move the invader horizontally across screen
Invader(p, c(p), r(p)).rect.x2 = Invader(p, c(p), r(p)).rect.x2 + Player(p).idir
IF Invader(p, c(p), r(p)).rect.x1 <= 8 OR Invader(p, c(p), r(p)).rect.x2 >= 215 THEN Edge(p) = TRUE ' set the edge flag when an invader reaches side of screen
END IF
END SUB
'-----------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------+
SUB ResetInvaders (p AS INTEGER) ' | ResetInvaders |
'+------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------+
'| Resets the invader start positions for the current level of the player |
'| p - player to reset invaders for |
'+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
SHARED Invader() AS INVADERS ' need access to shared variables
SHARED Player() AS PLAYER
DIM r AS INTEGER ' row counter
DIM c AS INTEGER ' column counter
DIM Level AS INTEGER ' current player level
Level = Player(p).Level ' get current player level
IF Level > 5 THEN Level = 5 ' keep the level at 5
DO ' begin invader row loop
r = r + 1 ' increment row counter
c = 0 ' reset column counter
DO ' begin invader column loop
c = c + 1 ' increment column counter
Invader(p, c, r).Active = TRUE ' activate this invader
Invader(p, c, r).rect.x1 = (16 * c) + 8 - Invader(p, c, r).Width \ 2 ' calculate position of invader on screen
Invader(p, c, r).rect.x2 = Invader(p, c, r).rect.x1 + Invader(p, c, r).Width - 1
Invader(p, c, r).rect.y1 = (32 + (r * 16)) + Level * 16
Invader(p, c, r).rect.y2 = Invader(p, c, r).rect.y1 + 7
Invader(p, c, r).cell = 0 ' reset invader animation cell
LOOP UNTIL c = 11 ' leave when all columns checked
LOOP UNTIL r = 5 ' leave when all rows checked
END SUB
'--------------------------------------------------------------------------------------------------------------------------+-------------------------------------+--+-------------+
SUB DrawShields (Mode AS INTEGER) ' | COLLISION: Shield and Laser or Bomb | | DrawShields |
'+---------------------------------------------------------------------------------------------------------------------+-------------------------------------+--+-------------+
'| Draws the player's shields to the screen and handles collisions between the shields and lasers or bombs |
'| Mode - How to handle drawing of the shields |
'| -1 (NEWGAME) - reset the shields for both players |
'| 0 (NEWLEVEL) - reset the shields for the current player |
'| 5 (INGAME) - manage the shields during game play |
'+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
SHARED Options AS OPTIONS ' need access to shared variables
SHARED IMG AS IMAGES
SHARED Shield() AS SHIELDS
SHARED Player() AS PLAYER
SHARED Display AS DISPLAY
SHARED Bomb() AS BOMB
SHARED Game AS GAME
DIM x AS INTEGER ' shield counter
DIM p AS INTEGER ' current player
DIM lx AS INTEGER ' location of laser hitting shield
DIM ly AS INTEGER
DIM scan AS INTEGER ' pixel perfect scanner
DIM rndx AS INTEGER ' random variance added to hit locations
DIM rndy AS INTEGER
DIM b AS INTEGER ' bomb counter
DIM shx AS INTEGER ' location of hit on shield
DIM shy AS INTEGER
p = Game.Player ' get current player
x = 1 ' set shield counter
DO ' begin shield loop
SELECT CASE Mode ' which mode was requested?
CASE NEWGAME ' a new game is beginning
_PUTIMAGE , IMG.Shield, Shield(PLAYER1, x).Image ' reset the shield images with undamaged images
_PUTIMAGE , IMG.Shield, Shield(PLAYER2, x).Image
CASE NEWLEVEL ' a new level is beginning for the current player
_PUTIMAGE , IMG.Shield, Shield(p, x).Image ' reset the shield images with undamaged images
CASE INGAME ' a game level is currently being played
_SETALPHA 0, BLACK, Shield(p, x).Image ' set the transparency color of shields
_PUTIMAGE (Shield(p, x).rect.x1, 192), Shield(p, x).Image ' draw the player's shield to the screen
'******************************************
'** Check for laser and shield collision **
'******************************************
IF Player(p).Laser.Active AND Player(p).Laser.Hit.Shield = 0 THEN ' if the player's laser flying and didn't hit a shield?
IF RectCollide(Player(p).Laser.rect, Shield(p, x).rect) THEN ' yes, did the laser hit the shield?
lx = Player(p).Laser.rect.x1 - Shield(p, x).rect.x1 ' yes, remember location of collision
ly = Player(p).Laser.rect.y1 - 192
scan = 0 ' reset pixel perfect scanner
_SOURCE Shield(p, x).Image ' work with shield image
_DEST Shield(p, x).Image
DO ' begin pixel perfect collision loop
IF POINT(lx, ly + scan) = WHITE THEN ' did this part of laser hit the shiled?
rndx = INT(RND * 2) - INT(RND * 2) ' yes, set some random variance in collision location
rndy = INT(RND * 2)
Player(p).Laser.Hit.ShieldX = Player(p).Laser.rect.x1 - 4 + rndx ' record location of collision on screen
Player(p).Laser.Hit.ShieldY = Player(p).Laser.rect.y1 - 3 + scan + rndy '
_PUTIMAGE (lx - 4 + rndx, ly - 3 + scan + rndy), IMG.LaserHitMask ' damage the shields
Player(p).Laser.Hit.Shield = 5 ' set count down timer
END IF
scan = scan + 1 ' move to next pixel location on laser
LOOP UNTIL scan = 4 OR Player(p).Laser.Hit.Shield ' leave when laser length scanned or a hit on shield occurred
_SOURCE Display.WorkScreen ' return back to the work display
_DEST Display.WorkScreen
END IF
END IF
'*****************************************
'** Check for bomb and shield collision **
'*****************************************
b = 0 ' reset bomb counter
DO ' begin bomb loop
b = b + 1 ' increment bomb counter
IF Bomb(b).Active AND Bomb(b).Hit.Shield = 0 THEN ' is this bomb dropping and not hit a shield?
IF RectCollide(Bomb(b).rect, Shield(p, x).rect) THEN ' yes, has the bomb hit a shield?
shx = Bomb(b).rect.x1 - Shield(p, x).rect.x1 ' yes, record location of collision
shy = Bomb(b).rect.y1 - 192
_SOURCE Shield(p, x).Image ' work with shield image
_DEST Shield(p, x).Image
scan = -1 ' reset pixel perfect scanner
DO ' begin pixel perfect collision loop
IF POINT(shx + scan, shy) = WHITE THEN ' did this part of bomb hit the shield?
rndy = INT(RND * 3) ' yes, set som random variance in collision location
Bomb(b).Hit.ShieldX = Bomb(b).rect.x1 - 2 ' record screen location of collision
Bomb(b).Hit.ShieldY = Bomb(b).rect.y1 - 4 + scan + rndy
_PUTIMAGE (shx - 2, shy - 4 + scan + rndy), IMG.BombHitMask ' damage the shields
Bomb(b).Hit.Shield = 5 ' set countdown timer
END IF
scan = scan + 1 ' move to next pixel location on bomb
LOOP UNTIL scan = 2 OR Bomb(b).Hit.Shield ' leave when bomb scanned or a hit on shield occurred
_SOURCE Display.WorkScreen ' return back to work display
_DEST Display.WorkScreen
END IF
END IF
LOOP UNTIL b = Player(p).MaxBombs ' leave when all bombs checked
END SELECT
x = x + 1 ' increment shield counter
LOOP UNTIL x > Options.Shields ' leave when all shields checked
END SUB
'-----------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------+
SUB StartNewLevel () ' | StartNewLevel |
'+------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------+
'| Resets the variables in preparation for a new player level |
'+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
SHARED Player() AS PLAYER ' need access to shared variables
SHARED DropColumn() AS DROPCOLUMN
SHARED Game AS GAME
DIM p AS INTEGER ' current player
p = Game.Player ' get current player
Player(p).Level = Player(p).Level + 1 ' increment player level
IF Player(p).Level = 11 THEN Player(p).Level = 1 ' reset player level when level 11 reached
Player(p).idir = 2 ' reset invader movement
Player(p).Laser.Hit.Count = 0 ' reset invader hit count
Player(p).MaxBombs = Player(p).Level ' calculate maximum number of invader bombs allowed
IF Player(p).MaxBombs > 3 THEN Player(p).MaxBombs = 3 ' no more than 3 bombs allowed
MoveInvaders p, CLEARVARIABLES ' clear the invader movement variables
ResetInvaders p ' reset invader locations
END SUB
'------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------+
SUB StartNewGame () ' | StartNewGame |
'+-------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------+
'| Resets the variables in preparation for a new game |
'+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
SHARED DropColumn() AS DROPCOLUMN ' need access to shared variables
SHARED Player() AS PLAYER
SHARED UFO() AS UFO
SHARED Game AS GAME
RANDOMIZE TIMER ' seed the RND generator
Game.Player = 1 ' set the current player to player 1
Game.Landed = FALSE ' reset the invader landed flag
Player(1) = Player(0) ' reset player variables
Player(2) = Player(0)
UFO(1) = UFO(0) ' reset UFO variables
UFO(2) = UFO(0)
DrawShields NEWGAME ' restore the shield images
ResetInvaders PLAYER1 ' reset invader locations
ResetInvaders PLAYER2
MoveInvaders PLAYER1, CLEARVARIABLES ' clear the invader movement variables
MoveInvaders PLAYER2, CLEARVARIABLES
ResetBombs
' reset the bombs and drop columns
END SUB
'--------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------+
SUB Initialize () ' | Initialize |
'+---------------------------------------------------------------------------------------------------------------------------------------------------------------+------------+
'+ Initializes all variables upon power up |
'+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
SHARED Player() AS PLAYER ' need access to shared variables
SHARED Invader() AS INVADERS
SHARED Shield() AS SHIELDS
SHARED UFO() AS UFO
SHARED Bomb() AS BOMB
SHARED Options AS OPTIONS
SHARED Game AS GAME
DIM p AS INTEGER ' player counter
DIM c AS INTEGER ' column counter
DIM r AS INTEGER ' row counter
Game.Credits = 0 ' set in game settings
Game.Player = 1
Game.Players = 1
Game.Pause.Level = 0
Game.Pause.Die = 0
Game.Landed = FALSE
Bomb(0).rect.x1 = 0 ' set default bomb settings
Bomb(0).rect.y1 = 0
Bomb(0).rect.x2 = 0
Bomb(0).rect.y2 = 0
Bomb(0).Hit.Shield = 0
Bomb(0).Hit.ShieldX = 0
Bomb(0).Hit.ShieldY = 0
Bomb(0).Image = 1
Bomb(0).Cell = 1
Bomb(0).Active = FALSE
Bomb(0).Miss = 0
Bomb(1) = Bomb(0) ' set bomb settings to default
Bomb(2) = Bomb(0)
Bomb(3) = Bomb(0)
UFO(0).Score = 0 ' set default UFO settings
UFO(0).Pause = 1500
UFO(0).Active = FALSE
UFO(0).Dir = 1
UFO(0).rect.x1 = 8
UFO(0).rect.y1 = 32
UFO(0).rect.y2 = 39
UFO(1) = UFO(0) ' set UFO settings to default
UFO(2) = UFO(0)
Player(0).Ship.rect.y1 = 216 ' set default player settings
Player(0).Ship.rect.y2 = 223
Player(0).Ship.Dead = FALSE
Player(0).Ship.DeadX = 0
Player(0).Ship.DeadImage = 1
Player(0).Ship.Extra = FALSE
Player(0).Score = 0
Player(0).Ship.Remain = 3
Player(0).idir = 2
Player(0).Level = 1
Player(0).GameOver = FALSE
Player(0).MaxBombs = 1
Player(0).Laser.Active = FALSE
Player(0).Laser.ShotsFired = 0
Player(0).Laser.Hit.Count = 0
Player(0).Laser.Miss = 0
Player(0).Laser.Hit.Invader = 0
Player(0).Laser.Hit.InvaderX = 0
Player(0).Laser.Hit.InvaderY = 0
Player(0).Laser.Hit.Shield = 0
Player(0).Laser.Hit.ShieldX = 0
Player(0).Laser.Hit.ShieldY = 0
Player(0).Laser.Hit.Bomb = 0
Player(0).Laser.Hit.BombX = 0
Player(0).Laser.Hit.BombY = 0
Player(0).UFO = UFO(0)
Player(1) = Player(0) ' set player settings to default
Player(2) = Player(0)
RANDOMIZE TIMER ' seed the RND generator
DO ' begin player loop
p = p + 1 ' increment player counter
r = 0 ' reset row counter
DO ' begin invader row loop
r = r + 1 ' increment row counter
c = 0 ' reset column counter
DO ' begin invader column loop
c = c + 1 ' increment column counter
SELECT CASE r ' which row?
CASE 1 ' row 1 (top row)
Invader(p, c, r).Image = 1 ' set invader image
Invader(p, c, r).Width = 8 ' set invader width
Invader(p, c, r).Score = 30 ' set invader score
CASE 2 TO 3 ' rows 2 and 3
Invader(p, c, r).Image = 2
Invader(p, c, r).Width = 11
Invader(p, c, r).Score = 20
CASE 4 TO 5 ' rows 4 and 5 (bottom row)
Invader(p, c, r).Image = 3
Invader(p, c, r).Width = 12
Invader(p, c, r).Score = 10
END SELECT
LOOP UNTIL c = 11 ' leave when all columns processed
LOOP UNTIL r = 5 ' leave when all rows processed
Shield(p, 1).rect.x1 = 32 ' set first shield screen location
Shield(p, 1).rect.x2 = 57
SELECT CASE Options.Shields ' how many shields set in options?
CASE 3 ' 3 shields
Shield(p, 2).rect.x1 = 99 ' 2nd shield location
Shield(p, 2).rect.x2 = 124
Shield(p, 3).rect.x1 = 166 ' 3rd shield location
Shield(p, 3).rect.x2 = 191
CASE 4 ' 4 shields (default)
Shield(p, 2).rect.x1 = 77 ' 2nd shield location
Shield(p, 2).rect.x2 = 102
Shield(p, 3).rect.x1 = 121 ' 3rd shield location
Shield(p, 3).rect.x2 = 146
Shield(p, 4).rect.x1 = 166 ' 4th shield location
Shield(p, 4).rect.x2 = 191
CASE 5 ' 5 shields
Shield(p, 2).rect.x1 = 66 ' 2nd shield location
Shield(p, 2).rect.x2 = 91
Shield(p, 3).rect.x1 = 99 ' 3rd shield location
Shield(p, 3).rect.x2 = 124
Shield(p, 4).rect.x1 = 132 ' 4th shield location
Shield(p, 4).rect.x2 = 157
Shield(p, 5).rect.x1 = 166 ' 5th shield location
Shield(p, 5).rect.x2 = 191
CASE 6 ' 6 shields
Shield(p, 2).rect.x1 = 59 ' 2nd shield location
Shield(p, 2).rect.x2 = 84
Shield(p, 3).rect.x1 = 86 ' 3rd shield location
Shield(p, 3).rect.x2 = 111
Shield(p, 4).rect.x1 = 113 ' 4th shield location
Shield(p, 4).rect.x2 = 138
Shield(p, 5).rect.x1 = 140 ' 5th shield location
Shield(p, 5).rect.x2 = 165
Shield(p, 6).rect.x1 = 167 ' 6th shield location
Shield(p, 6).rect.x2 = 192
END SELECT
Shield(p, 1).rect.y1 = 192 ' set all shield Y locations
Shield(p, 1).rect.y2 = 207
Shield(p, 2).rect.y1 = 192
Shield(p, 2).rect.y2 = 207
Shield(p, 3).rect.y1 = 192
Shield(p, 3).rect.y2 = 207
Shield(p, 4).rect.y1 = 192
Shield(p, 4).rect.y2 = 207
Shield(p, 5).rect.y1 = 192
Shield(p, 5).rect.y2 = 207
Shield(p, 6).rect.y1 = 192
Shield(p, 6).rect.y2 = 207
LOOP UNTIL p = 2 ' leave when both players processed
END SUB
'----------------------------------------------------------------------------------------------------------------------------------------------------------------------+----------+
SUB GetReady () ' | GetReady |
'+-----------------------------------------------------------------------------------------------------------------------------------------------------------------+----------+
'| Informs the player to get ready to play a game or take turns in a 2 player game |
'+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
SHARED Game AS GAME ' need access to shared variables
DIM Blink AS INTEGER ' blink toggle
DIM Pause AS INTEGER ' pause counter
Pause = 180 ' set 3 second pause
DO ' begin pause loop
_LIMIT 60 ' 60 frames per second
ClearDisplay INCOIN ' clear the display
IF Game.Frame MOD 5 = 0 THEN Blink = NOT Blink ' toggle blink flag every 5 frames
Text 13, 7, "PLAY PLAYER<" + _TRIM$(STR$(Game.Player)) + ">" ' print player notice
IF Blink THEN DrawScore Game.Player, INCOIN ' draw the score when flag set
IF Game.Players = 2 THEN ' is this a 2 player game?
IF Game.Player = 1 THEN DrawScore PLAYER2, INCOIN ELSE DrawScore PLAYER1, INCOIN ' yes, draw the other player's score non-blinking
END IF
UpdateDisplay INCOIN ' update the display with changes
Pause = Pause - 1 ' decrement count down timer
LOOP UNTIL Pause = 0 ' leave when timer ended
END SUB
'-----------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------+
SUB SelectPlayers () ' | SelectPlayers |
'+------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------+
'| Allows the player to choose the number of players when a coin has been inserted into the game |
'+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
SHARED Game AS GAME ' need access to shared variables
DIM KeyPress AS INTEGER ' any key pressed
DO ' begin select loop
_LIMIT 30 ' 30 frames per second
ClearDisplay INSELECT ' clear the display
DrawScore BOTHPLAYERS, INSELECT ' draw the score of both players
DrawCredits ' draw number of credits in machine
DrawShipsRemaining ' draw the number of ships remaining for player 1
Text 12, 12, "PUSH" ' display text
IF Game.Credits = 1 THEN ' only 1 coin inserted?
Text 14, 4, "ONLY <1>PLAYER BUTTON" ' yes, display appropriate text
ELSE ' no, more than 1 coin
Text 14, 2, "<1> OR <2>PLAYERS BUTTON" ' display appropriate text
END IF
UpdateDisplay INSELECT ' update the display with changes
KeyPress = GetKey(INSELECT) ' get any key that may have been pressed
IF KeyPress = 49 THEN ' did player press the 1 key?
Game.Players = 1 ' yes, set the number of players
Game.Credits = Game.Credits - 1 ' subtract a credit from the game
PlayGame PLAYER1 ' player a 1 player game
ELSEIF KeyPress = 50 THEN ' no, did player press the 2 key?
IF Game.Credits > 1 THEN ' yes, is there more than 1 credit in game?
Game.Players = 2 ' yes, set number of players
Game.Credits = Game.Credits - 2 ' subtract 2 credits from game
PlayGame PLAYER2 ' play a two player game
END IF
ELSEIF KeyPress = 79 THEN ' did player press the O key?
SetOptions ' yes, go to set options screen
END IF
LOOP UNTIL Game.Credits = 0 ' leave when all credits have been used
END SUB
'----------------------------------------------------------------------------------------------------------------------------------------------------------------------+----------+
SUB SlowText (Row AS INTEGER, Column AS INTEGER, Txt AS STRING, Mode AS INTEGER, KeyPress AS INTEGER) ' | SlowText |
'+-----------------------------------------------------------------------------------------------------------------------------------------------------------------+----------+
'| Displays text on screen slowly at 1/10th second per letter |
'+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
SHARED Font() AS LONG ' need access to shared variables
SHARED Game AS GAME
DIM c AS INTEGER ' column counter
DIM r AS INTEGER ' row counter
DIM p AS INTEGER ' text character position counter
r = Row * 8 ' yes, calculate text row
c = Column * 8 ' calculate text column
p = 0 ' reset character position counter
DO ' begin text loop
_LIMIT 60 ' 60 frames per second
KeyPress = GetKey(Mode) ' get any key pressed
IF Game.Frame MOD 6 = 0 THEN ' have 6 frames gone by? (1/10th second)
p = p + 1 ' yes, increment character position counter
_PUTIMAGE (c, r), Font(ASC(MID$(Txt, p, 1))) ' draw font character onto screen
c = c + 8 ' move to next text column
END IF
UpdateDisplay Mode ' update the display with changes
LOOP UNTIL p = LEN(Txt) OR KeyPress ' leave when text finished or a valid key pressed
END SUB
'--------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------+
SUB InsertCoin () ' | InsertCoin |
'+---------------------------------------------------------------------------------------------------------------------------------------------------------------+------------+
'| Plays the various intro screens while waiting for a coin to be inserted |
'+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
SHARED Font() AS LONG ' need access to shared variables
SHARED IMG AS IMAGES
SHARED IMG_Invader() AS LONG
SHARED IMG_Bomb() AS LONG
SHARED Display AS DISPLAY
SHARED Game AS GAME
DIM FlippedY AS INTEGER ' toggle used to flip between normal Y and flipped Y screens
DIM AddedC AS INTEGER ' toggle used to flip between normal C and added C screens
DIM KeyPress AS INTEGER ' contains any key pressed by player
DIM Bcycle AS INTEGER ' bomb animation cycler
DIM ShowQB64 AS INTEGER ' toogle used to flip showing QB64 screen or not
DIM x AS INTEGER ' generic counter
DIM Flip AS INTEGER ' invader animation cycler
DO ' begin key check loop
FlippedY = TRUE ' set initial state of toggles
AddedC = TRUE
ShowQB64 = FALSE
DO ' begin insert coin animation loop
FlippedY = NOT FlippedY ' toggle flipped Y screen
ClearDisplay INCOIN ' clear the display
DrawScore BOTHPLAYERS, INCOIN ' draw the score of both players
DrawCredits ' draw number of credits in machine
DrawShipsRemaining ' draw the number of ships remaining
IF NOT FlippedY THEN ' show the flipped Y screen?
SlowText 8, 12, "PLAY", INCOIN, KeyPress: IF KeyPress THEN EXIT DO ' no, display text and exit if key pressed
_PUTIMAGE , Display.WorkScreen, Display.CorrectY, (0, 64)-(223, 71) ' get an image of non-flipped Y
ELSE ' yes, show the slipped Y screen
SlowText 8, 12, "PLA ", INCOIN, KeyPress: IF KeyPress THEN EXIT DO ' display text and exit if key pressed
_PUTIMAGE , Display.WorkScreen, Display.WithoutY, (0, 64)-(223, 71) ' get an image of the Y missing
_PUTIMAGE (127, 70)-(120, 63), Font(89) ' draw an upside down Y
_PUTIMAGE , Display.WorkScreen, Display.WithY, (0, 64)-(223, 71) ' get an image of the upside down Y
END IF
SlowText 11, 7, "SPACE INVADERS", INCOIN, KeyPress: IF KeyPress THEN EXIT DO ' display text and exit if key pressed
SLEEP 1 ' pause for 1 second
Text 15, 4, "*SCORE ADVANCE TABLE*" ' display text
_PUTIMAGE (64, 136), IMG.UFO: Text 17, 10, "=" ' display UFO=
_PUTIMAGE (68, 152), IMG_Invader(1, 1): Text 19, 10, "=" ' display invaders and =
_PUTIMAGE (67, 168), IMG_Invader(2, 0): Text 21, 10, "="
_PUTIMAGE (66, 184), IMG_Invader(3, 1): Text 23, 10, "="
SlowText 17, 11, "? MYSTERY", INCOIN, KeyPress: IF KeyPress THEN EXIT DO ' display point values and exit if key pressed
SlowText 19, 11, "30 POINTS", INCOIN, KeyPress: IF KeyPress THEN EXIT DO
SlowText 21, 11, "20 POINTS", INCOIN, KeyPress: IF KeyPress THEN EXIT DO
SlowText 23, 11, "10 POINTS", INCOIN, KeyPress: IF KeyPress THEN EXIT DO
IF FlippedY THEN ' has the Y been flipped?
FOR x = 240 TO 124 STEP -2 ' yes, cycle from right of screen to left
_LIMIT 30 ' loop will run at 30 frames per second
_PUTIMAGE (0, 64), Display.WithY ' show image with flipped Y
_PUTIMAGE (x + 4, 64), IMG_Invader(1, Flip) ' show invader on screen at x coordinate
Flip = 1 - Flip ' flip between invader images (0 to 1)
DrawScore BOTHPLAYERS, INCOIN ' draw the score of both players
DrawCredits ' draw number of credits in machine
DrawShipsRemaining ' draw the number of ships remaining
UpdateDisplay INCOIN ' show results on display screen
KeyPress = GetKey(INCOIN): IF KeyPress THEN EXIT DO ' leave if a key has been pressed
NEXT x
FOR x = 124 TO 240 STEP 2 ' cycle back to the right of screen
_LIMIT 30 ' loop will run at 30 frames per second
_PUTIMAGE (0, 64), Display.WithoutY ' show image without a Y
_PUTIMAGE (x + 4, 64), IMG_Invader(1, Flip) ' place invader at x location
_PUTIMAGE (x + 3, 70)-(x - 4, 63), Font(89) ' place flipped Y behind invader
Flip = 1 - Flip ' flip between invader images (0 to 1)
DrawScore BOTHPLAYERS, INCOIN ' draw the score of both players
DrawCredits ' draw number of credits in machine
DrawShipsRemaining ' draw the number of ships remaining
UpdateDisplay INCOIN ' show results on display screen
KeyPress = GetKey(INCOIN): IF KeyPress THEN EXIT DO ' leave if a key has been pressed
NEXT x
FOR x = 240 TO 124 STEP -2 ' cycle from right of screen to left
_LIMIT 30 ' loop will run at 30 frames per second
_PUTIMAGE (0, 64), Display.WithoutY ' show image without a Y
_PUTIMAGE (x + 4, 64), IMG_Invader(1, Flip) ' place invader at x location
_PUTIMAGE (x - 4, 64), Font(89) ' place regular Y in front of invader
Flip = 1 - Flip ' flip between invader images (0 to 1)
DrawScore BOTHPLAYERS, INCOIN ' draw the score of both players
DrawCredits ' draw number of credits in machine
DrawShipsRemaining ' draw the number of ships remaining
UpdateDisplay INCOIN ' show results on display screen
KeyPress = GetKey(INCOIN): IF KeyPress THEN EXIT DO ' leave if a key has been pressed
NEXT x
_PUTIMAGE (0, 64), Display.CorrectY ' show image with correct Y
SLEEP 1 ' pause for 1 second
UpdateDisplay INCOIN ' show result on display screen
END IF
SLEEP 2 ' pause for 2 seconds
'** PLAY DEMO **
AddedC = NOT AddedC ' toggle added C screen
ClearDisplay INCOIN ' clear the display
DrawScore BOTHPLAYERS, INCOIN ' draw the score of both players
DrawCredits ' draw number of credits in machine
DrawShipsRemaining ' draw the number of ships remaining
Text 14, 8, "INSERT COIN" ' display text
_PUTIMAGE , Display.WorkScreen, Display.NormalC, (0, 32)-(223, 119) ' get an image without an extra C
IF AddedC THEN ' time to add an extra C?
Text 14, 15, "C" ' yes, display another C
_PUTIMAGE , Display.WorkScreen, Display.AddedC, (0, 32)-(223, 119) ' get an image with the extra C
END IF
SlowText 18, 6, "<1 OR 2 PLAYERS>", INCOIN, KeyPress: IF KeyPress THEN EXIT DO ' display text and exit if key pressed
SlowText 21, 6, "*1 PLAYER 1 COIN", INCOIN, KeyPress: IF KeyPress THEN EXIT DO
SlowText 24, 6, "*2 PLAYERS 2 COINS", INCOIN, KeyPress: IF KeyPress THEN EXIT DO
IF AddedC THEN ' has an extra C been added?
Flip = 1 ' yes, reset invader image flip
FOR x = -16 TO 120 STEP 2 ' start from left side of screen to right
_LIMIT 30 ' limit loop to 30 frames per second
_PUTIMAGE (0, 32), Display.AddedC ' show image with the extra C
_PUTIMAGE (x, 32), IMG_Invader(1, Flip) ' place moving invader on screen
Flip = 1 - Flip ' flip between invader images (0 to 1)
UpdateDisplay INCOIN ' show results on display screen
KeyPress = GetKey(INCOIN): IF KeyPress THEN EXIT DO ' leave if a key has been pressed
NEXT x
_DELAY .125 ' pause for 1/8th second
Bcycle = 1 ' reset bomb image cycler
FOR x = 40 TO 108 STEP 2 ' cycle from under invader to letter C
_LIMIT 30 ' limit loop to 30 frames per second
_PUTIMAGE (0, 32), Display.AddedC ' show image with the extra C
_PUTIMAGE (120, 32), IMG_Invader(1, Flip) ' place stationary invader on screen
_PUTIMAGE (123, x), IMG_Bomb(1, Bcycle) ' place moving bomb on screen
Bcycle = Bcycle + 1 ' cycle to next bomb image
IF Bcycle = 4 THEN Bcycle = 0 ' cycle back to 1 if needed
UpdateDisplay INCOIN ' show results on display screen
KeyPress = GetKey(INCOIN): IF KeyPress THEN EXIT DO ' leave if a key has been pressed
NEXT x
_PUTIMAGE (0, 32), Display.NormalC ' show image without the extra C
_PUTIMAGE (122, 112), IMG.BombHit ' place bomb hit explosion over C
_PUTIMAGE (120, 32), IMG_Invader(1, Flip) ' place stationary invader on screen
UpdateDisplay INCOIN ' show results on display screen
_DELAY .0625 ' pause for 1/16th second
_PUTIMAGE (0, 32), Display.NormalC ' show image without the extra C
_PUTIMAGE (120, 32), IMG_Invader(1, Flip) ' place stationary invader on screen
UpdateDisplay INCOIN ' show results on display screen
END IF
SLEEP 2 ' pause for 2 seconds
ShowQB64 = NOT ShowQB64 ' toggle QB64 screen
IF ShowQB64 THEN ' time to show QB64 screen?
ClearDisplay INCOIN ' yes, clear the display
DrawScore BOTHPLAYERS, INCOIN ' draw the score of both players
DrawCredits ' draw number of credits in machine
DrawShipsRemaining ' draw the number of ships remaining
_PUTIMAGE (75, 71), IMG.QB64PE ' show QB64PE image
SlowText 5, 8, "Created With", INCOIN, KeyPress: IF KeyPress THEN EXIT DO ' draw credits and leave if a key pressed
SlowText 7, 11, "QB64PE", INCOIN, KeyPress: IF KeyPress THEN EXIT DO
SlowText 19, 4, "www.qb64phoenix.com", INCOIN, KeyPress: IF KeyPress THEN EXIT DO
SlowText 21, 7, "QB64 remake by", INCOIN, KeyPress: IF KeyPress THEN EXIT DO
SlowText 23, 7, "TERRY RITCHIE", INCOIN, KeyPress: IF KeyPress THEN EXIT DO
SlowText 25, 3, "quickbasic64@gmail.com", INCOIN, KeyPress: IF KeyPress THEN EXIT DO
SLEEP 4 ' pause for 4 seconds
KeyPress = GetKey(INCOIN): IF KeyPress THEN EXIT DO ' leave if a key has been pressed
END IF
LOOP
IF KeyPress = 79 THEN SetOptions ' go to options screen if O key pressed
LOOP UNTIL KeyPress = 67 ' leave when C key pressed
END SUB
'------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------+
SUB SetupDisplay () ' | SetupDisplay |
'+-------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------+
'+ Sets up the display screen according to options selected |
'+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
SHARED Options AS OPTIONS ' need access to shared variables
SHARED Display AS DISPLAY
SHARED IMG AS IMAGES
DIM TmpScreen AS LONG ' temporary screen image if needed
DIM IMG_Icon AS LONG ' window icon image
DIM Xclicked AS INTEGER ' window X close trap
IF _FULLSCREEN THEN _FULLSCREEN _OFF ' leave full screen if currently enabled
IF Display.Screen THEN ' has a window already been created?
TmpScreen = _NEWIMAGE(1, 1, 32) ' yes, create a temporary new window
SCREEN TmpScreen ' change to the new window
_FREEIMAGE Display.Screen ' remove old window image from RAM
END IF
IF Options.Bezel THEN ' is the bezel selected to display?
Display.Screen = _NEWIMAGE(640 * Options.ScreenSize, 360 * Options.ScreenSize, 32) ' yes, make the display screen the size of the bezel
_PUTIMAGE , Display.Bezel, Display.Screen ' place the bezel image onto the display screen
Display.Bez.x1 = 208 * Options.ScreenSize ' calculate the location of the work screen coordinates within the bezel image
Display.Bez.y1 = 74 * Options.ScreenSize
Display.Bez.x2 = Display.Bez.x1 + 224 * Options.ScreenSize
Display.Bez.y2 = Display.Bez.y1 + 248 * Options.ScreenSize
ELSE ' no, no bezel image to be used
Display.Screen = _NEWIMAGE(224 * Options.ScreenSize, 248 * Options.ScreenSize, 32) ' create player display screen based on size of game screen
END IF
SCREEN Display.Screen ' create game window
_DELAY .5
_SCREENMOVE _MIDDLE ' move game screen to center of desktop
IMG_Icon = _LOADIMAGE("siicon.bmp", 32) ' load window icon
_ICON IMG_Icon ' set window icon
_FREEIMAGE IMG_Icon ' icon image no longer needed
_TITLE "QB64 Space Invaders" ' set window title
IF TmpScreen THEN _FREEIMAGE TmpScreen ' remove temporary image if created
IF Options.FullScreen THEN _FULLSCREEN _SQUAREPIXELS , _SMOOTH ' go full screen if option set
_DEST Display.WorkScreen ' game updates take place on this screen
_SOURCE Display.WorkScreen ' game updates take place on this screen
CLS , BLACK ' clear the screen with black background
Xclicked = _EXIT ' trap the window X close button
END SUB
'------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------+
SUB ClearDisplay (Mode AS INTEGER) ' | ClearDisplay |
'+-------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------+
'| Clears the display according to mode requested |
'| Mode - 5 (INGAME), 2 (INCOIN), 3 (INSELECT) - clear with background or colored foil |
'| 1 (INOPTIONS) - clear completely to black |
'+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
SHARED Options AS OPTIONS ' need access to shared variables
SHARED Display AS DISPLAY
SELECT CASE Mode ' which type of screen clear needs to be done?
CASE INGAME, INCOIN, INSELECT ' game screen is currently showing
IF Options.Background THEN ' is the option to show a background image set?
_PUTIMAGE , Display.Background ' yes, clear the working screen with the background image
ELSE ' no, no background image is to be used
CLS , BLACK ' clear the working screen with solid black
LINE (0, 24)-(223, 55), _RGB32(255, 0, 0, 20), BF ' simulate the foil strips that were placed over
LINE (0, 184)-(223, 239), _RGB32(0, 255, 0, 15), BF ' the screen in arcades to give the illusion that
LINE (16, 240)-(135, 247), _RGB32(0, 255, 0, 15), BF ' the game was in color
END IF
CASE INOPTIONS ' options screen is currently showing
CLS , BLACK ' clear the screen in black
_PUTIMAGE , Display.OptionScreen, Display.WorkScreen ' place the option screen onto the work screen
END SELECT
END SUB
'-----------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------+
SUB UpdateDisplay (Mode AS INTEGER) ' | UpdateDisplay |
'+------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------+
'| Updates the display with changes according to mode requested |
'| Mode - 5 (INGAME), 2 (INCOIN), 3 (INSELECT) - display high score, line under player, and apply color strips |
'| 1 (INOPTIONS) - just copy work screen to work mask, no changes |
'+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
SHARED Game AS GAME ' need access to shared variables
SHARED Display AS DISPLAY
SHARED Options AS OPTIONS
Game.Frame = Game.Frame + 1 ' increment master game frame counter
IF Game.Frame = 32761 THEN Game.Frame = 1 ' reset master game frame counter when needed
SELECT CASE Mode ' which type of screen update needs to be done?
CASE INGAME, INCOIN, INSELECT ' game screen is currently showing
Text 0, 1, "SCORE<1> HI-SCORE SCORE<2>" ' draw high score text
Text 2, 11, RIGHT$("00000" + _TRIM$(STR$(Game.HighScore)), 5)
LINE (0, 237)-(223, 237), WHITE ' draw line under player
_PUTIMAGE , Display.ColorMask, Display.WorkMask ' place color strips onto a temporary image
_SETALPHA 0, WHITE, Display.WorkScreen ' make white the transparent color of the working screen
_PUTIMAGE , Display.WorkScreen, Display.WorkMask ' place the working screen onto the color strips
CASE INOPTIONS ' options screen is currently showing
_PUTIMAGE , Display.WorkScreen, Display.WorkMask ' place the working screen onto a temporary image
END SELECT
IF Options.Bezel THEN ' is the bezel selected to be displayed?
_PUTIMAGE (Display.Bez.x1, Display.Bez.y1)-(Display.Bez.x2, Display.Bez.y2), Display.WorkMask, Display.Screen ' yes, place screen inside bezel image
ELSE ' no, no bezel image is to be shown
_PUTIMAGE , Display.WorkMask, Display.Screen ' place the temporary image onto the player's view screen
END IF
_DISPLAY ' update the player's display screen with changes
END SUB
'--------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------+
SUB LoadAssets () ' | LoadAssets |
'+---------------------------------------------------------------------------------------------------------------------------------------------------------------+------------+
'| Load the game's graphics and sound files |
'+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
SHARED IMG AS IMAGES ' need access to shared variables
SHARED SND AS SOUNDS
SHARED Font() AS LONG
SHARED IMG_Invader() AS LONG
SHARED IMG_Bomb() AS LONG
SHARED IMG_Ship() AS LONG
SHARED Shield() AS SHIELDS
SHARED Display AS DISPLAY
DIM IMG_SpriteSheet AS LONG ' image sprite sheet
DIM x AS INTEGER ' generic counter
DIM y AS INTEGER ' generic counter
SND.Beat1 = _SNDOPEN("sibeat1.ogg") ' beat 1 sound
SND.Beat2 = _SNDOPEN("sibeat2.ogg") ' beat 2 sound
SND.Beat3 = _SNDOPEN("sibeat3.ogg") ' beat 3 sound
SND.Beat4 = _SNDOPEN("sibeat4.ogg") ' beat 4 sound
SND.InvaderHit = _SNDOPEN("siidead.ogg") ' invader explosion sound
SND.PlayerHit = _SNDOPEN("sipdead.ogg") ' player explosion sound
SND.UFOHit = _SNDOPEN("siudead.ogg") ' ufo explosion sound
SND.UFOFlying = _SNDOPEN("siufofly.ogg") ' ufo flying sound
SND.Laser = _SNDOPEN("silaser.ogg") ' player shooting sound
SND.Coin = _SNDOPEN("sicoin.ogg") ' coin dropping sound
SND.Extra = _SNDOPEN("siextra.ogg") ' extra ship sound
IMG.QB64PE = _NEWIMAGE(73, 73, 32) ' create image containers
IMG.DipSwitch = _NEWIMAGE(16, 36, 32)
IMG.UFO = _NEWIMAGE(16, 8, 32)
IMG.InvaderHit = _NEWIMAGE(14, 8, 32)
IMG.BombHit = _NEWIMAGE(6, 8, 32)
IMG.BombHitMask = _NEWIMAGE(6, 8, 32)
IMG.LaserHit = _NEWIMAGE(8, 8, 32)
IMG.LaserHitMask = _NEWIMAGE(8, 8, 32)
IMG.Shield = _NEWIMAGE(26, 16, 32)
IMG_Ship(-1) = _NEWIMAGE(16, 8, 32)
IMG_Ship(0) = _NEWIMAGE(13, 8, 32)
IMG_Ship(1) = _NEWIMAGE(16, 8, 32)
IMG_Invader(1, 0) = _NEWIMAGE(8, 8, 32)
IMG_Invader(1, 1) = _NEWIMAGE(8, 8, 32)
IMG_Invader(2, 0) = _NEWIMAGE(11, 8, 32)
IMG_Invader(2, 1) = _NEWIMAGE(11, 8, 32)
IMG_Invader(3, 0) = _NEWIMAGE(12, 8, 32)
IMG_Invader(3, 1) = _NEWIMAGE(12, 8, 32)
Display.Bezel = _NEWIMAGE(1920, 1080, 32)
Display.Screen = _NEWIMAGE(224, 248, 32)
Display.WorkScreen = _NEWIMAGE(224, 248, 32)
Display.WorkMask = _NEWIMAGE(224, 248, 32)
Display.ColorMask = _NEWIMAGE(224, 248, 32)
Display.Background = _NEWIMAGE(896, 992, 32)
Display.OptionScreen = _NEWIMAGE(224, 248, 32)
Display.WithY = _NEWIMAGE(224, 8, 32)
Display.WithoutY = _NEWIMAGE(224, 8, 32)
Display.CorrectY = _NEWIMAGE(224, 8, 32)
Display.AddedC = _NEWIMAGE(224, 88, 32)
Display.NormalC = _NEWIMAGE(224, 88, 32)
FOR x = PLAYER1 TO PLAYER2
FOR y = 1 TO 6
Shield(x, y).Image = _NEWIMAGE(26, 16, 32) ' 6 shield image containers
NEXT y
NEXT x
IMG_Bomb(1, 0) = _NEWIMAGE(3, 8, 32)
IMG_Bomb(1, 1) = _NEWIMAGE(3, 8, 32)
IMG_Bomb(1, 2) = _NEWIMAGE(3, 8, 32)
IMG_Bomb(1, 3) = _NEWIMAGE(3, 8, 32)
IMG_Bomb(2, 0) = _NEWIMAGE(3, 8, 32)
IMG_Bomb(2, 1) = _NEWIMAGE(3, 8, 32)
IMG_Bomb(2, 2) = _NEWIMAGE(3, 8, 32)
IMG_Bomb(2, 3) = _NEWIMAGE(3, 8, 32)
IMG_Bomb(3, 0) = _NEWIMAGE(3, 8, 32)
IMG_SpriteSheet = _LOADIMAGE("sisprites.png", 32) ' load the sprite sheet
_SETALPHA 0, BLACK, IMG_SpriteSheet ' make black transparent
_PUTIMAGE , IMG_SpriteSheet, IMG.QB64PE, (848, 200)-(920, 272) ' get qb64pe logo
_PUTIMAGE , IMG_SpriteSheet, Display.ColorMask, (400, 200)-(623, 447) ' get color mask
_PUTIMAGE , IMG_SpriteSheet, Display.OptionScreen, (624, 200)-(847, 447) ' get dip switch screen
_DEST IMG_SpriteSheet ' draw on sprite sheet
LINE (400, 200)-(920, 447), BLACK, BF ' remove images from inside of bezel
_PUTIMAGE , IMG_SpriteSheet, Display.Bezel, (0, 8)-(1919, 1087) ' get bezel image
_PUTIMAGE , IMG_SpriteSheet, Display.Background, (1920, 8)-(2815, 999) ' get background image
_PUTIMAGE , Display.OptionScreen, IMG.DipSwitch, (16, 32)-(31, 67) ' get single dip switch
_PUTIMAGE , IMG_SpriteSheet, IMG_Invader(1, 0), (0, 0)-(7, 7) ' get invader 1 image cell 1
_PUTIMAGE , IMG_SpriteSheet, IMG_Invader(1, 1), (8, 0)-(15, 7) ' get invader 1 image cell 2
_PUTIMAGE , IMG_SpriteSheet, IMG_Invader(2, 0), (16, 0)-(26, 7) ' get invader 2 image cell 1
_PUTIMAGE , IMG_SpriteSheet, IMG_Invader(2, 1), (27, 0)-(37, 7) ' get invader 2 image cell 2
_PUTIMAGE , IMG_SpriteSheet, IMG_Invader(3, 0), (38, 0)-(49, 7) ' get invader 3 image cell 1
_PUTIMAGE , IMG_SpriteSheet, IMG_Invader(3, 1), (50, 0)-(61, 7) ' get invader 3 image cell 2
_PUTIMAGE , IMG_SpriteSheet, IMG.UFO, (62, 0)-(77, 7) ' get UFO image
_PUTIMAGE , IMG_SpriteSheet, IMG_Ship(-1), (91, 0)-(106, 7) ' get exploding player ship image cell 1
_PUTIMAGE , IMG_SpriteSheet, IMG_Ship(0), (78, 0)-(90, 7) ' get player ship image
_PUTIMAGE , IMG_SpriteSheet, IMG_Ship(1), (107, 0)-(122, 7) ' get exploding player ship image cell 2
_PUTIMAGE , IMG_SpriteSheet, IMG.InvaderHit, (123, 0)-(136, 7) ' get exploding invader image
_PUTIMAGE , IMG_SpriteSheet, IMG.BombHit, (137, 0)-(142, 7) ' get laser/bomb exploding hit image
_SOURCE IMG.BombHit ' image used on display screen
_DEST IMG.BombHitMask ' image used to destroy shields
CLS , BLACK ' clear image in black
FOR x = 0 TO 5 ' cycle through columns
FOR y = 0 TO 7 ' cycle through rows
IF POINT(x, y) <> WHITE THEN ' is pixel at x,y white?
PSET (x, y), WHITE ' no, draw white pixel on mask
END IF ' (this creates a "negative" or mask
NEXT y ' of the IMG.bombhit image)
NEXT x
_SETALPHA 0, WHITE, IMG.BombHitMask ' make white transparent
_PUTIMAGE , IMG_SpriteSheet, IMG.LaserHit, (143, 0)-(150, 7) ' get laser exploding miss image
_SOURCE IMG.LaserHit ' image used on display screen
_DEST IMG.LaserHitMask ' image used to destroy shields
CLS , BLACK ' clear image in black
FOR x = 0 TO 7 ' cycle through columns
FOR y = 0 TO 7 ' cycle through rows
IF POINT(x, y) <> WHITE THEN ' is pixel at x,y white?
PSET (x, y), WHITE ' no, draw white pixel on mask
END IF ' (this creates a "negative" or mask
NEXT y ' of the IMG.laserhit image)
NEXT x
_SETALPHA 0, WHITE, IMG.LaserHitMask ' set white as transparent
_PUTIMAGE (0, 0), IMG_SpriteSheet, IMG.Shield, (154, 0)-(166, 7) ' get upper left shield image
_PUTIMAGE (13, 0), IMG_SpriteSheet, IMG.Shield, (167, 0)-(179, 7) ' get upper right shield image
_PUTIMAGE (0, 8), IMG_SpriteSheet, IMG.Shield, (186, 0)-(198, 7) ' get lower left shield image
_PUTIMAGE (13, 8), IMG_SpriteSheet, IMG.Shield, (199, 0)-(211, 7) ' get lower right shield image
_SETALPHA 0, BLACK, IMG.Shield ' add transparency to shield image
_PUTIMAGE , IMG_SpriteSheet, IMG_Bomb(1, 0), (215, 0)-(217, 7) ' get bomb 1 image cell 1
_PUTIMAGE , IMG_SpriteSheet, IMG_Bomb(1, 1), (218, 0)-(220, 7) ' get bomb 1 image cell 2
_PUTIMAGE , IMG_SpriteSheet, IMG_Bomb(1, 2), (221, 0)-(223, 7) ' get bomb 1 image cell 3
_PUTIMAGE , IMG_SpriteSheet, IMG_Bomb(1, 3), (224, 0)-(226, 7) ' get bomb 1 image cell 4
_PUTIMAGE , IMG_SpriteSheet, IMG_Bomb(2, 0), (227, 0)-(229, 7) ' get bomb 2 image cell 1
_PUTIMAGE , IMG_SpriteSheet, IMG_Bomb(2, 1), (230, 0)-(232, 7) ' get bomb 2 image cell 2
_PUTIMAGE , IMG_SpriteSheet, IMG_Bomb(2, 2), (233, 0)-(235, 7) ' get bomb 2 image cell 3
_PUTIMAGE , IMG_SpriteSheet, IMG_Bomb(2, 3), (236, 0)-(238, 7) ' get bomb 2 image cell 4
_PUTIMAGE , IMG_SpriteSheet, IMG_Bomb(3, 0), (239, 0)-(241, 7) ' get bomb 3 image cell 1
IMG_Bomb(3, 1) = _COPYIMAGE(IMG_Bomb(1, 0)) ' copy bomb 3 image cell 2
IMG_Bomb(3, 2) = _COPYIMAGE(IMG_Bomb(3, 0)) ' copy bomb 3 image cell 3
IMG_Bomb(3, 3) = _COPYIMAGE(IMG_Bomb(1, 2)) ' copy bomb 3 image cell 4
FOR x = 1 TO 255 ' cycle through 255 font images
Font(x) = _NEWIMAGE(8, 8, 32) ' create font image container
_PUTIMAGE (0, 0), IMG_SpriteSheet, Font(x), ((x - 1) * 8 + 242, 0)-(((x - 1) * 8 + 242) + 7, 7)
NEXT x ' get font character fron sprite sheet
_FREEIMAGE IMG_SpriteSheet ' remove spritesheet from RAM
END SUB
'-------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+
SUB LoadOptions () ' | LoadOptions |
'+--------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+
'| Load the game options from the options file. If the file does not exist create one with default settings. |
'+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
SHARED Options AS OPTIONS ' need access to shared variables
SHARED Game AS GAME
IF _FILEEXISTS("si.sav") THEN ' does the options file exist?
OPEN "si.sav" FOR INPUT AS #1 ' yes, open the file for reading
INPUT #1, Options.Switches ' get the saved DIP switch settings
INPUT #1, Game.HighScore ' get the save high score
CLOSE #1 ' close the options file
ELSE ' no, the options file does not exist
Options.Switches = 215 ' set DIP switches to default settings
SaveOptions ' save the options
END IF
IF Options.Switches AND 128 THEN Options.FreePlay = FALSE ELSE Options.FreePlay = TRUE ' set free play according to DIP switch setting
IF Options.Switches AND 64 THEN Options.ExtraShip = 1500 ELSE Options.ExtraShip = 1000 ' set extra ship value according to DIP switch setting
IF (Options.Switches AND 48) = 48 THEN ' are DIP switches 3 and 4 on?
Options.Shields = 6 ' yes, 6 shields will be used
ELSEIF Options.Switches AND 32 THEN ' no, is DIP switch 3 on?
Options.Shields = 5 ' yes, 5 shields will be used
ELSEIF Options.Switches AND 16 THEN ' no, is DIP switch 4 on?
Options.Shields = 4 ' yes, 4 shields will be used
ELSE ' no, neither DIP switch 3 or 4 is on
Options.Shields = 3 ' 3 shields will be used
END IF
Options.FullScreen = FALSE ' assume full screen mode is disabled
IF (Options.Switches AND 12) = 12 THEN ' are DIP switches 5 and 6 on?
Options.FullScreen = TRUE ' yes, full screen mode activated
Options.ScreenSize = 3 ' game screen will be 3X size full screen
ELSEIF Options.Switches AND 8 THEN ' no, is DIP switch 5 on?
Options.ScreenSize = 3 ' yes, game screen will be 3X size windowed
ELSEIF Options.Switches AND 4 THEN ' no, is DIP switch 6 on?
Options.ScreenSize = 2 ' yes, game screen will be 2X size windowed
ELSE ' no, neither DIP switch 5 or 6 is on
Options.ScreenSize = 1 ' game screen will be 1X size windowed
END IF
IF Options.Switches AND 2 THEN Options.Background = TRUE ELSE Options.Background = FALSE ' set background image according to DIP switch setting
IF Options.Switches AND 1 THEN Options.Bezel = TRUE ELSE Options.Bezel = FALSE ' set bezel image according to DIP switch setting
IF (Options.Bezel = FALSE) AND Options.FullScreen THEN Options.ScreenSize = 4 ' use a 4x screen size for full screen without the bezel
SetupDisplay ' set the dsiplay according to chosen options
END SUB
'-------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+
SUB SaveOptions () ' | SaveOptions |
'+--------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+
'| Save the game's options and high score to the options file |
'+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
SHARED Options AS OPTIONS ' need access to shared variables
SHARED Game AS GAME
OPEN "si.sav" FOR OUTPUT AS #1 ' create a file to write to
PRINT #1, Options.Switches ' write the current options to the file
PRINT #1, Game.HighScore ' write the high score to the file
CLOSE #1 ' close the file
END SUB
'--------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------+
SUB SetOptions () ' | SetOptions |
'+---------------------------------------------------------------------------------------------------------------------------------------------------------------+------------+
'| Displays the options screen allowing the player to select options |
'+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
SHARED Options AS OPTIONS ' need access to shared variables
SHARED IMG AS IMAGES
SHARED Game AS GAME
SHARED SND AS SOUNDS
DIM KeyPress AS INTEGER ' a key that was pressed
DO ' begin display loop
_LIMIT 30 ' limit to 30 frames per second
ClearDisplay INOPTIONS ' clear the screen
IF Options.Switches AND 128 THEN ' is switch 1 on?
Text 14, 1, "1: COIN REQUIRED TO PLAY" ' yes, display text
_PUTIMAGE (31, 67)-(16, 32), IMG.DipSwitch ' flip switch 1 to on position
ELSE ' no, switch 1 is off
Text 14, 1, "1: GAME SET TO FREE PLAY" ' display text
END IF
IF Options.Switches AND 64 THEN ' is switch 2 on?
Text 16, 1, "2: EXTRA SHIP AT 1500" ' yes, display text
_PUTIMAGE (57, 67)-(42, 32), IMG.DipSwitch ' flip switch 2 to on position
ELSE ' no, switch 2 is off
Text 16, 1, "2: EXTRA SHIP AT 1000" ' display text
END IF
IF (Options.Switches AND 48) = 48 THEN ' are switches 3 and 4 on?
Text 18, 1, "3: SIX BASES DURING PLAY" ' yes, display text
Text 19, 1, "4: HELP MOMMY - 6 BASES"
ELSEIF Options.Switches AND 32 THEN ' no, is switch 3 on?
Text 18, 1, "3: FIVE BASES DURING PLAY" ' yes, display text
Text 19, 1, "4: EASY - 5 BASES"
ELSEIF Options.Switches AND 16 THEN ' no, is switch 4 on?
Text 18, 1, "3: FOUR BASES DURING PLAY" ' yes, display text
Text 19, 1, "4: DEFAULT - 4 BASES"
ELSE ' no, both switches 3 and 4 are off
Text 18, 1, "3: THREE BASES DURING PLAY" ' display text
Text 19, 1, "4: HARD - 3 BASES"
END IF
IF Options.Switches AND 32 THEN _PUTIMAGE (83, 67)-(68, 32), IMG.DipSwitch ' if switch 3 is on flip it to the on position
IF Options.Switches AND 16 THEN _PUTIMAGE (109, 67)-(94, 32), IMG.DipSwitch ' if switch 4 is on flip it to the on position
IF (Options.Switches AND 12) = 12 THEN ' are switches 5 and 6 on?
Text 21, 1, "5: FULL SCREEN" ' yes, display text
Text 22, 1, "6: (3X ORIGINAL SIZE)"
ELSEIF Options.Switches AND 8 THEN ' no, is switch 5 on?
Text 21, 1, "5: LARGE 672x744 WINDOW" ' yes, display text
Text 22, 1, "6: (3X ORIGINAL SIZE)"
ELSEIF Options.Switches AND 4 THEN ' no, is switch 6 on?
Text 21, 1, "5: MEDIUM 448x496 WINDOW" ' yes, display text
Text 22, 1, "6: (2X ORIGINAL SIZE)"
ELSE ' no, both switches 5 and 6 are off
Text 21, 1, "5: SMALL 224x248 WINDOW" ' display text
Text 22, 1, "6: (ORIGINAL SIZE)"
END IF
IF Options.Switches AND 8 THEN _PUTIMAGE (135, 67)-(120, 32), IMG.DipSwitch ' if switch 5 is on flip it to the on position
IF Options.Switches AND 4 THEN _PUTIMAGE (161, 67)-(146, 32), IMG.DipSwitch ' if switch 6 is on flip it to the on position
IF Options.Switches AND 2 THEN ' is switch 7 on?
Text 24, 1, "7: SHOW BACKGROUND IMAGE" ' yes, display text
_PUTIMAGE (187, 67)-(172, 32), IMG.DipSwitch ' flip switch 7 to on position
ELSE ' no, switch 7 is off
Text 24, 1, "7: NO BACKGROUND IMAGE" ' display text
END IF
IF Options.Switches AND 1 THEN ' is switch 8 on?
Text 26, 1, "8: SHOW BEZEL IMAGE" ' yes, display text
_PUTIMAGE (213, 67)-(198, 32), IMG.DipSwitch ' flip switch 8 to the on position
ELSE ' no, switch 8 is off
Text 26, 1, "8: NO BEZEL IMAGE" ' display text
END IF
Text 28, 2, " <R> RESET HIGH SCORE" ' display input options
Text 29, 2, " <S> SAVE SETTINGS"
Text 30, 2, "<1-8> TOGGLE DIP SWITCH"
UpdateDisplay INOPTIONS ' update display with changes
DO ' begin keyboard input loop
_LIMIT 30 ' limit to 30 frames per second
KeyPress = GetKey(INOPTIONS) ' get a valid keyboard input
LOOP UNTIL KeyPress ' leave when valid keyboard input received
IF KeyPress = 82 THEN ' was the R key pressed?
Game.HighScore = 0 ' yes, reset the high score
_SNDPLAY SND.Extra ' play sound to acknowledge
END IF
IF KeyPress <> 83 THEN ' was the S key pressed?
KeyPress = ABS(KeyPress - 56) ' no, convert keypress vale to 7 through 0
Options.Switches = Options.Switches XOR 2 ^ KeyPress ' flip appropriate switch setting
END IF
LOOP UNTIL KeyPress = 83 ' leave when S keyboard input received
SaveOptions ' save game options
LoadOptions ' load game options to make any changes take effect
END SUB
'--------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------+
SUB Text (Row AS INTEGER, Column AS INTEGER, Txt AS STRING) ' | Text |
'+---------------------------------------------------------------------------------------------------------------------------------------------------------------------+------+
'| Displays text instantly at the requested location |
'| Row - row where text is to printed (if row > 31 then actual screen coordinates are used. This is for the UFO text) |
'| Column - column where text is to be printed |
'| Txt - text string to be printed |
'+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
SHARED Font() AS LONG ' need access to shared variables
DIM c AS INTEGER ' column counter
DIM r AS INTEGER ' row counter
DIM p AS INTEGER ' text character position counter
IF Row < 31 THEN ' text on an 8x8 grid?
r = Row * 8 ' yes, calculate text row
c = Column * 8 ' calculate text column
ELSE ' no, use actual coordinates on screen for UFO text
r = Column ' convert row to X screen coordinate
c = Row - 32 ' convert column to Y screen coordinate
END IF
DO ' begin text print loop
p = p + 1 ' increment character position counter
_PUTIMAGE (c, r), Font(ASC(MID$(Txt, p, 1))) ' draw font character onto screen
c = c + 8 ' move to next text column
LOOP UNTIL p = LEN(Txt) ' leave when all characters printed
END SUB
'------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------+
FUNCTION GetKey (Mode AS INTEGER) ' | GetKey |
'+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------+
'| Get key presses from the player |
'| Mode - 1 (INOPTIONS) S, 1-8, and R keys only are returned |
'| 2 (INCOIN) O key (letter O) is the only one returned |
'| 3 (INSELECT) 1, 2, and O (letter O) keys are only returned |
'| -1 (NEWGMAE) clears the buffer and exits |
'| The C key is always monitored and returned. Additionaly when C is pressed a credit is added to the game. |
'| The ESC key is always monitored and the game exited if pressed. |
'+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
SHARED Game AS GAME ' need access to shared variables
SHARED SND AS SOUNDS
STATIC Buffer AS INTEGER ' buffer to hold last key pressed (retains value between calls)
DIM KeyPress AS INTEGER ' last key that was pressed
IF _EXIT THEN ExitGame ' leave game if player closes window with X button
IF Mode = NEWGAME THEN Buffer = 0: EXIT FUNCTION 'clear the buffer and exit
KeyPress = _KEYHIT ' get a key if pressed
IF KeyPress = 0 THEN EXIT FUNCTION ' leave function if no key pressed
IF KeyPress < 0 THEN Buffer = 0: EXIT FUNCTION ' if a key was released then clear the buffer and leave function
IF Buffer THEN GetKey = 0: EXIT FUNCTION ' if a key is being held down return nothing and leave function
IF KeyPress = 27 THEN ExitGame ' exit the game if the ESC key pressed
Buffer = KeyPress ' put the key into the buffer
IF KeyPress = 67 OR KeyPress = 99 THEN ' was the C key pressed?
Game.Credits = Game.Credits + 1 ' yes, insert a coin
IF Game.Credits > 99 THEN Game.Credits = 99 ' limit amount of coins in game
_SNDPLAY SND.Coin ' play the coin dropping sound
GetKey = 67 ' return the key to the insert coin screen
EXIT FUNCTION ' leave the function
END IF
SELECT CASE Mode ' which mode is the game in?
CASE INOPTIONS ' the options screen is showing
IF KeyPress = 83 OR KeyPress = 115 THEN ' was the S key pressed?
GetKey = 83 ' yes, return the key to the options screen
END IF
IF KeyPress > 48 AND KeyPress < 57 THEN ' was the 1 through 8 key pressed?
GetKey = KeyPress ' yes, return the key to the options screen
END IF
IF KeyPress = 82 OR KeyPress = 114 THEN ' was the R key pressed?
GetKey = 82 ' yes, return the key to the options screen
END IF
CASE INCOIN ' the insert coin screen is showing
IF KeyPress = 79 OR KeyPress = 111 THEN ' was the O key pressed?
GetKey = 79 ' yes, return the key to the insert coin screen
END IF
CASE INSELECT ' the select players screen is showing
IF KeyPress = 49 OR KeyPress = 50 THEN ' was the 1 or 2 key pressed?
GetKey = KeyPress ' yes, return the key to the select players screen
END IF
IF KeyPress = 79 OR KeyPress = 111 THEN ' was the O key pressed?
GetKey = 79 ' yes, return the key to the insert coin screen
END IF
END SELECT
END FUNCTION
'---------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------+
SUB DrawScore (p AS INTEGER, Mode AS INTEGER) ' | DrawScore |
'+----------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------+
'| Draws the given player's score in the mode requested |
'| p - player 1 or 2 - 3 (BOTHPLAYERS) to have both players high scores drawn at same time |
'| Mode - 5 (INGAME) - update the high score if it has been surpassed |
'| any other value is ignored |
'+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
SHARED Player() AS PLAYER ' need access to shared variables
SHARED Game AS GAME
SHARED Options AS OPTIONS
SHARED SND AS SOUNDS
IF p = PLAYER1 OR p = BOTHPLAYERS THEN ' player 1 selected?
Text 2, 2, RIGHT$("00000" + _TRIM$(STR$(Player(1).Score)), 5) ' yes, print palyer 1's score
END IF
IF p = PLAYER2 OR p = BOTHPLAYERS THEN ' player 2 selected?
Text 2, 20, RIGHT$("00000" + _TRIM$(STR$(Player(2).Score)), 5) ' yes, print player 2's score
END IF
IF Player(Game.Player).Ship.Extra = FALSE THEN ' has the current player been awarded an extra ship?
IF Player(Game.Player).Score >= Options.ExtraShip THEN ' no, is the current player's score high enough for an extra ship?
Player(Game.Player).Ship.Remain = Player(Game.Player).Ship.Remain + 1 ' yes, award the player another ship
Player(Game.Player).Ship.Extra = TRUE ' remember that an extra ship was awarded
_SNDPLAY SND.Extra
END IF
END IF
IF Mode = INGAME THEN ' is a game currently in progress?
IF Player(Game.Player).Score > Game.HighScore THEN Game.HighScore = Player(Game.Player).Score ' yes, update the high score is a player exceeds it
END IF
END SUB
'------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------+
SUB DrawShipsRemaining () ' | DrawShipsRemaining |
'+-------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------+
'| Draws the player's remaining ships |
'+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
SHARED Player() AS PLAYER ' need access to shared variables
SHARED IMG_Ship() AS LONG
SHARED Game AS GAME
DIM s AS INTEGER ' ship counter
DIM p AS INTEGER ' curent player
p = Game.Player ' get current player
Text 30, 1, _TRIM$(STR$(Player(p).Ship.Remain)) ' print number of total ships
s = 1 ' reset ship counter
WHILE s <= Player(p).Ship.Remain - 1 ' display a ship?
_PUTIMAGE (8 + (s * 16), 239), IMG_Ship(0) ' yes, draw ship
s = s + 1 ' increment ship counter
WEND ' leave when no more ships to draw
END SUB
'-------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+
SUB DrawCredits () ' | DrawCredits |
'+--------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+
'| Draw the number of credits in the game |
'+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
SHARED Game AS GAME ' ned access to shared variables
Text 28, 1, "<C>OIN <O>PTIONS <ESC>EXIT" ' print the options text
Text 30, 17, "CREDIT-" + RIGHT$("00" + _TRIM$(STR$(Game.Credits)), 2) ' print the number of credits inserted
END SUB
'----------------------------------------------------------------------------------------------------------------------------------------------------------------------+----------+
SUB ExitGame () ' | ExitGame |
'+-----------------------------------------------------------------------------------------------------------------------------------------------------------------+----------+
'| Frees all game assets from RAM and exits the game |
'+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
SHARED SND AS SOUNDS ' need access to shared variables
SHARED IMG AS IMAGES
SHARED IMG_Ship() AS LONG
SHARED IMG_Invader() AS LONG
SHARED IMG_Bomb() AS LONG
SHARED Display AS DISPLAY
SHARED Shield() AS SHIELDS
SHARED Font() AS LONG
DIM x AS INTEGER ' generic counter
DIM y AS INTEGER ' generic counter
IF _FULLSCREEN THEN _FULLSCREEN _OFF ' leave full screen if enabled
SCREEN 0, 0, 0, 0 ' switch to a pure text screen
_SNDCLOSE SND.Beat1 ' remove sounds from RAM
_SNDCLOSE SND.Beat2
_SNDCLOSE SND.Beat3
_SNDCLOSE SND.Beat4
_SNDCLOSE SND.InvaderHit
_SNDCLOSE SND.PlayerHit
_SNDCLOSE SND.UFOHit
_SNDCLOSE SND.UFOFlying
_SNDCLOSE SND.Laser
_SNDCLOSE SND.Coin
_SNDCLOSE SND.Extra
_FREEIMAGE IMG.QB64PE ' remove images from RAM
_FREEIMAGE IMG.DipSwitch
_FREEIMAGE IMG.UFO
_FREEIMAGE IMG.InvaderHit
_FREEIMAGE IMG.BombHit
_FREEIMAGE IMG.BombHitMask
_FREEIMAGE IMG.LaserHit
_FREEIMAGE IMG.LaserHitMask
_FREEIMAGE IMG.Shield
_FREEIMAGE IMG_Ship(-1)
_FREEIMAGE IMG_Ship(0)
_FREEIMAGE IMG_Ship(1)
_FREEIMAGE IMG_Invader(1, 0)
_FREEIMAGE IMG_Invader(1, 1)
_FREEIMAGE IMG_Invader(2, 0)
_FREEIMAGE IMG_Invader(2, 1)
_FREEIMAGE IMG_Invader(3, 0)
_FREEIMAGE IMG_Invader(3, 1)
_FREEIMAGE Display.Bezel
_FREEIMAGE Display.Screen
_FREEIMAGE Display.WorkScreen
_FREEIMAGE Display.WorkMask
_FREEIMAGE Display.ColorMask
_FREEIMAGE Display.Background
_FREEIMAGE Display.OptionScreen
_FREEIMAGE Display.WithY
_FREEIMAGE Display.WithoutY
_FREEIMAGE Display.CorrectY
_FREEIMAGE Display.AddedC
_FREEIMAGE Display.NormalC
_FREEIMAGE IMG_Bomb(1, 0)
_FREEIMAGE IMG_Bomb(1, 1)
_FREEIMAGE IMG_Bomb(1, 2)
_FREEIMAGE IMG_Bomb(1, 3)
_FREEIMAGE IMG_Bomb(2, 0)
_FREEIMAGE IMG_Bomb(2, 1)
_FREEIMAGE IMG_Bomb(2, 2)
_FREEIMAGE IMG_Bomb(2, 3)
_FREEIMAGE IMG_Bomb(3, 0)
_FREEIMAGE IMG_Bomb(3, 1)
_FREEIMAGE IMG_Bomb(3, 2)
_FREEIMAGE IMG_Bomb(3, 3)
FOR x = 1 TO 2
FOR y = 1 TO 6
_FREEIMAGE Shield(x, y).Image
NEXT y
NEXT x
FOR x = 1 TO 255
_FREEIMAGE Font(x)
NEXT x
SYSTEM ' return to the operating system
END SUB
'----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
RE: Space Invaders Clone - bplus - 11-01-2022
Don't think I've ever played original version, love the Halloween one. Thanks!
RE: Space Invaders Clone - TerryRitchie - 11-01-2022
(11-01-2022, 05:56 PM)bplus Wrote: Don't think I've ever played original version, love the Halloween one. Thanks!
You're welcome. I had the original arcade ROMs running in MAME side by side with mine. It's almost exact right to the timing.
RE: Space Invaders Clone - bplus - 11-01-2022
And doesn't cost a quarter!
RE: Space Invaders Clone - TempodiBasic - 11-01-2022
Yeah! I come back to my childhood with this!
Good Job, Master
RE: Space Invaders Clone - james2464 - 11-02-2022
This is amazing. Thank you Terry!
RE: Space Invaders Clone - Dav - 11-02-2022
Perfect clone! It looks and plays great as the original. Really amazing work, Terry!
- Dav
RE: Space Invaders Clone - TerryRitchie - 11-02-2022
Thanks everyone.
I have a few other unreleased games from the same time period (Pac-Man, Missile Command, Frogger, etc..) I'll need to start resurrecting their code too. Heck, maybe I'll make an arcade front end and let player's choose between machines ... hmm..
RE: Space Invaders Clone - madscijr - 11-02-2022
(11-01-2022, 05:44 PM)TerryRitchie Wrote: Back in 2011 or so I wrote QB64 Space Invaders which was a sad attempt at Space Invaders. In 2013 I rewrote it to clone the original as much as possible. However, it was buggy and I never released it. I rediscovered the code recently when rewriting the tutorial web site and decided it was time to do this code justice. I completely rewrote the code and I present to you a QB64 Space Invaders game that clones the original 99%. I spent the last 3 weeks working on this (and playing WAY TOO MANY games of Space Invaders working the kinks out. Give it a whirl and let me know what you think.
The ZIP file below contains the source code and all assets (sound files and graphics). The game does create a file upon startup called "si.sav" that is used to save the game options and high score. It only contains two text lines so will never exceed 1KB in size.
You can turn the bezel image on/off, the background image on/off, resize the screen 1x, 2x, 3x, or full screen. Just like the original you can choose the number of shields (3 to 6), the extra ship score (1000 or 1500), and coin required or free play through the use of DIP switches in the options screen (just like found on the motherboard of the original arcade systems). The game is capable of playing 1 or 2 players exactly like the original was as well.
This is awesome! You have replicated the look and feel so well!
In fact, I was just thinking about Space Invaders,
and how in 2022 with these giant 1080p and 4k screens we have,
it should be updated to take advantage of that real estate (see pic).
Now, how hard would it be to tweak the code for such a thing?
It would be neat to add some simple variables to control things like:
how many invaders (rows/columns), size of invaders, # players,
and options for cooperative & head-to-head play modes, etc.
RE: Space Invaders Clone - TerryRitchie - 11-02-2022
(11-02-2022, 09:31 PM)madscijr Wrote: (11-01-2022, 05:44 PM)TerryRitchie Wrote: Back in 2011 or so I wrote QB64 Space Invaders which was a sad attempt at Space Invaders. In 2013 I rewrote it to clone the original as much as possible. However, it was buggy and I never released it. I rediscovered the code recently when rewriting the tutorial web site and decided it was time to do this code justice. I completely rewrote the code and I present to you a QB64 Space Invaders game that clones the original 99%. I spent the last 3 weeks working on this (and playing WAY TOO MANY games of Space Invaders working the kinks out. Give it a whirl and let me know what you think.
The ZIP file below contains the source code and all assets (sound files and graphics). The game does create a file upon startup called "si.sav" that is used to save the game options and high score. It only contains two text lines so will never exceed 1KB in size.
You can turn the bezel image on/off, the background image on/off, resize the screen 1x, 2x, 3x, or full screen. Just like the original you can choose the number of shields (3 to 6), the extra ship score (1000 or 1500), and coin required or free play through the use of DIP switches in the options screen (just like found on the motherboard of the original arcade systems). The game is capable of playing 1 or 2 players exactly like the original was as well.
This is awesome! You have replicated the look and feel so well!
In fact, I was just thinking about Space Invaders,
and how in 2022 with these giant 1080p and 4k screens we have,
it should be updated to take advantage of that real estate (see pic).
Now, how hard would it be to tweak the code for such a thing?
It would be neat to add some simple variables to control things like:
how many invaders (rows/columns), size of invaders, # players,
and options for cooperative & head-to-head play modes, etc.
That would actually be pretty easy to do. Just increase a few array sizes, change the screen dimensions, and get rid of the hard coded "magic numbers" I used for character position on a 224x248 screen. The way the game works now is that everything is drawn to a 224x248 screen. _PUTIMAGE is then used to resize it onto a master display screen. Simply tweak that behavior and your in business.
It would be the same thing I did with Asteroids and updated it to "Widescreen Asteroids": https://www.qb64tutorial.com/games#h.o55mx5ahta14
But who in their right mind would play such a beast, LOL?
|