REM >.Src.Invaders REM Taito Space Invaders (Circa 1978) Clone REM Keys | Title | In-Game REM ======+===============+============= REM SPACE | Start Game | REM S | Screenshot | Screenshot REM Q | Quit Program | Suicide. REM M | Music On/Off | Music On/Off REM E | | End game Sequence. (Test.) REM Z | | Left. REM X | | Right. REM Enter | | Fire. REM ************************************************************* REM Initialisation Part 1... (Files & Environment.) REM ************************************************************* REM Setup error trapping and announce ourselves... ON ERROR PROCtrap_error:END MODE28:OFF :REM 800*600 (256 Colours) string$="Acorn Invaders. (c)2008 DynaByte Software" PRINT string$:PRINT STRING$(LEN(string$),"-"):PRINT:PRINT PRINT"Loading Game Data...":PRINT"Please Wait..." LIBRARY".Resources.Logger" PROClog_init:PROClog_str("Memory Usage",0) REM Check we have all our essential files... REM Our Hiscore table is a special case as it can be either REM generated if one doesn't exist, or we can carry on regardless REM if we can't create it. REM Locate files... (Mission critical...) bail%=FALSE:app_dir$="." IF FNfs_find(app_dir$+"Resources.Gfx0")<>1 THEN bail%=TRUE IF FNfs_find(app_dir$+"Resources.Gfx1")<>1 THEN bail%=TRUE IF FNfs_find(app_dir$+"Resources.Intro")<>1 THEN bail%=TRUE IF FNfs_find(app_dir$+"Resources.Creds")<>1 THEN bail%=TRUE IF bail% THEN ERROR 255,"File Missing!!" REM Load and start music playing... OSCLI"Playmod "+app_dir$+"Resources.Intro" REM Setup our variables. (These are used all over...) bail%=FALSE :REM TRUE to quit game. dump_num%=0 :REM Screendump counter. music_status%=1 :REM Music on if =1. scores_save%=TRUE :REM TRUE to save HiScores. DIM scores_name$(10),scores_value%(10) :REM HiScore table. DIM scores_rack%(10) DIM cred_str$(10),cred_spc%(10) :REM Credit screen stuff. REM Initialise Graphics Sub-system... REM The variables we're interested in here will be... REM gfx_spriteop% -> The OS Call we'll use to draw graphics. REM gfx_spritepool% -> Block of RAM where our sprites reside. REM gfx_fontpool% -> Block of RAM where our charset resides. REM gfx_area% -> Pointer to our current graphics pool. SYS "OS_SWINumberFromString",, "OS_SpriteOp" TO gfx_spriteop% PROClog_sec("Graphics Areas") gfx_spritepool%=FNgfx_load(app_dir$+"Resources.Gfx0") gfx_fontpool%=FNgfx_load(app_dir$+"Resources.Gfx1") gfx_area%=gfx_spritepool% REM Load Credit screen data... REM This was originally as strings in the listing, but REM implementing this in a loop reading data from an array takes REM less space and tidies up the listing somewhat. hdl%=OPENIN(app_dir$+"Resources.Creds") FOR i%=1 TO 10 INPUT#hdl%,cred_spc%(i%),cred_str$(i%) NEXT i% CLOSE#hdl% REM HiScore Table... REM Here, we look for and load, if it exists, our Hiscores file. REM If we can't find one, we check whether we can write to the REM location before we attempt to create one. If we can't REM do that, then we can still run, but the user's scores will REM not be retained on exit. hifile$=app_dir$+"Resources.HiTable" :REM Location of file. IF FNfs_find(hifile$)<>1 THEN IF NOT FNfs_writeable(0,app_dir$) THEN scores_save%=FALSE PROChitable_create(scores_save%) ELSE IF NOT FNfs_writeable(1,hifile$) THEN scores_save%=FALSE PROChitable_load ENDIF REM ************************************************************* REM Initialisation Part 2... (Object offsets.) REM ************************************************************* REM Objects... Each object (Base, saucer, missile etc.) is REM given a block of memory up to 64 bytes long to hold all REM relevant data for that object. This, apart from being REM quicker, is far easier to code and saves memory (See REM tutorial). These are tokens to address offsets in each REM of those blocks of memory. (See tutorial for how this REM works...) Contents for offsets 32-60 (flags1% - flags8%) will REM be different, depending on the nature of the object. I'll REM define LOCAL variables with more meaningful names and point REM them to these in the object's handler routines. REM Object offsets... xpos%=0:ypos%=4:xsize%=8:ysize%=12 :REM Position & Size. dir%=16:speed%=20 :REM Movement. active%=24:beenhit%=28 :REM Object Status. flags1%=32:flags2%=36:flags3%=40 :REM Misc flags. flags4%=44:flags5%=48:flags6%=52 flags7%=56:flags8%=60 REM Define offsets for screen and rail objects. x_eig%=16:y_eig%=20:setmode%=24 :REM Offsets in screen obj. xfont%=28:yfont%=32 toprail%=16:baserail%=20 :REM Offsets in rail obj. REM ************************************************************* REM Initialisation Part 3... (Global objects and variables.) REM ************************************************************* REM Reserve memory for global object blocks... DIM screen%36,rail%24,box%16 :REM Global objects. PROClog_sec("Global Objects") PROClog_memusage("Screen",screen%,36) PROClog_memusage("Rail",rail%,24) PROClog_memusage("Box",box%,16) REM Get the video display into a VGA 256 colour mode. This is REM also the same MODE as our sprites. Call to PROCget_screeninfo REM calls the OS to get some values and sets up our screen object REM for us. CLS:PROCget_screeninfo :REM Setup screen object. screen%!xfont%=FNconv_units(8,1) screen%!yfont%=FNconv_units(8,2)+7 REM Define property values for other global objects. rail%!xsize%=FNconv_units(6,1) :REM Rail properties. rail%!ysize%=FNconv_units(6,2) rail%!toprail%=screen%!ysize%-50 rail%!baserail%=30 REM ************************************************************* REM Main Loop... (Title>Game>Title.) REM ************************************************************* REPEAT CLS:bail%=FNtitle_loop IF NOT bail% THEN CLS:PROCmain UNTIL bail% REM Enclose our goodbyes in a border... REM Setup our border size and position. Centre this on the screen REM and then drop it down by 3px. (Raising the text by 3px would REM break too many things!) Call PROCdraw_box() to produce our REM border and then display our text. Once we've done all that, we REM start to fade our music and bail back to the desktop. CLS:box%!xsize%=220:box%!ysize%=40 box%=FNcentre_sprite_xy(box%,screen%):box%!ypos%-=3 PROCdraw_box(box%):PROCcentre_text_xy("GOODBYE!!!",10) IF music_status%=1 THEN FOR i%=127 TO 1 STEP-2 OSCLI"PlayVolume "+STR$(i%):PROCwait(4) NEXT i% OSCLI"PlayStop":OSCLI"PlayVolume 127" ENDIF PROClog_close END REM Bail greacefully on error. DEFPROCtrap_error ON ERROR OFF REPORT:PRINT" at line : ";ERL PROClog_close:OSCLI"PlayStop" ENDPROC REM ************************************************************* REM Title Loop... (Display our title sequence.) REM ************************************************************* REM Animate in our initial screen layout and then perform a cycle REM between Score Advance & HiScore tables. Do a keyboard scan REM periodically and check for -17 (Q) and -99 (Space Bar) and REM return from function accordingly... Object hwind% points to REM the area of the screen where either the HiScore or Score REM Advance table gets placed in attract mode. This is the area REM below "Invaders" and above "PRESS SPACE TO PLAY" text. Objects REM inv1% - inv3% are identical copies of each other, except for REM ypos value. Our saucer% object here, is only large enough to REM hold screen co-ordinates. It is made LOCAL by this function so REM we can re-define it for the main game loop. DEFFNtitle_loop LOCAL i%,space%,invt%,hwind%,str%,saucer%,inv1%,inv2% LOCAL inv3%,keyz%,keyx%,keye%,keyq%,keym%,x%,y%,rc%,s$,string$ DIM space%16,invt%16,hwind%16 :REM Title sequence objects. DIM saucer%16,inv1%16,keyz%16 PROClog_sec("Title Sequence Objects") PROClog_memusage("space",space%,16) PROClog_memusage("invt",invt%,16) PROClog_memusage("hwind",hwind%,16) PROClog_memusage("saucer",saucer%,16) PROClog_memusage("inv1",inv1%,16) PROClog_memusage("keyz",keyz%,16) REM Initialise LOCAL (title sequence) variables. space%!xsize%=FNconv_units(100,1) :REM Space text properties. space%!ysize%=FNconv_units(40,2) space%!xpos%=FNcentre_sprite(space%,screen%) space%!ypos%=800 invt%!xsize%=FNconv_units(150,1) :REM Invaders text properties. invt%!ysize%=FNconv_units(40,2) invt%!xpos%=FNcentre_sprite(invt%,screen%) invt%!ypos%=space%!ypos%-invt%!ysize% hwind%!xsize%=640:hwind%!ysize%=500:REM Window properties. hwind%!xpos%=FNcentre_sprite(hwind%,screen%) hwind%!ypos%=200 saucer%!xsize%=FNconv_units(40,1) :REM Saucer properties. saucer%!ysize%=FNconv_units(20,2) saucer%!xpos%=hwind%!xpos%+150 saucer%!ypos%=(hwind%!ypos%+hwind%!ysize%)-160 inv1%!xsize%=FNconv_units(20,1) :REM Common invader props. inv1%!ysize%=FNconv_units(20,2) inv1%!xpos%=hwind%!xpos%+150+FNcentre_sprite(inv1%,saucer%) inv2%=FNcopy_object(inv1%,16) :REM Create two copies. inv3%=FNcopy_object(inv1%,16) PROClog_memusage("inv2",inv2%,16) PROClog_memusage("inv3",inv3%,16) inv1%!ypos%=hwind%!ypos%+100 :REM Unique invader props. inv2%!ypos%=inv1%!ypos%+inv1%!ysize%+40 inv3%!ypos%=inv2%!ypos%+inv2%!ysize%+40 keyz%!xsize%=FNconv_units(20,1) :REM KB Controls "keys". keyz%!ysize%=FNconv_units(20,2) keyz%!xpos%=hwind%!xpos%+150+FNcentre_sprite(keyz%,saucer%) keyx%=FNcopy_object(keyz%,16) :REM Create four copies keye%=FNcopy_object(keyz%,16) :REM for the other keys. keym%=FNcopy_object(keyz%,16) keyq%=FNcopy_object(keyz%,16) PROClog_memusage("keyx",keyx%,16) PROClog_memusage("keye",keye%,16) PROClog_memusage("keym",keym%,16) PROClog_memusage("keyq",keyq%,16) :REM Unique "keys" props. keyz%!ypos%=(hwind%!ypos%+hwind%!ysize%)-160 keyx%!ypos%=keyz%!ypos%-60:keye%!ypos%=keyx%!ypos%-60 keym%!ypos%=keye%!ypos%-100:keyq%!ypos%=keym%!ypos%-60 REM Animate the initial screen title "Space Invaders" & "PRESS REM SPACE TO PLAY" messages. PROCdraw_rail(rail%!toprail%):PROCdraw_rail(rail%!baserail%) FOR i%=0-space%!xsize% TO space%!xpos% STEP 5 WAIT:PROCplot("space",i%,space%!ypos%) PROCwait(1) NEXT i% FOR i%=screen%!xsize% TO invt%!xpos% STEP -5 WAIT:PROCplot("invaders",i%,invt%!ypos%) PROCwait(1) NEXT i% string$="PRESS SPACE TO PLAY" str%=LEN(string$)*screen%!xfont%:y%=170 PROCprint(FNcentre_text(str%,screen%!xsize%),y%,string$,11) string$="OR Q TO QUIT" str%=LEN(string$)*screen%!xfont%:y%-=screen%!yfont% PROCprint(FNcentre_text(str%,screen%!xsize%),y%,string$,11) REM Cycle the remainder of the attract mode. WHILE NOT INKEY(-99) AND NOT INKEY(-17) AND NOT INKEY(-82) REM Display Credits Screen... REM Text strings are held in cred_str$() inencrypted form. REM Line spacing info is held in the matching element of REM cred_spc%(). Negative values have special meanings... REM Value | Line spacing is... REM --------+--------------------- REM -1 | 1 * screen%!yfont% (Next line.) REM -2 | 2 * screen%!yfont% (Leave a blank line.) REM <+n> | Leave a gap of OS Units... y%=hwind%!ypos%+hwind%!ysize% FOR i%=1 TO 10 string$=FNencode_string(cred_str$(i%)) x%=FNtitle_stringcentre(string$) CASE cred_spc%(i%) OF WHEN -1 : y%-=screen%!yfont% WHEN -2 : y%-=(2*screen%!yfont%) OTHERWISE : y%-=cred_spc%(i%) ENDCASE PROCprint(x%,y%,string$,0) NEXT i% REM Wait for 750 counts or until SPACE or Q pressed... rc%=FNtitle_wait IF rc%=1 THEN =TRUE IF rc%=2 THEN =FALSE PROCwipe_sprite(hwind%) REM Display Score Advance table... string$="SCORE ADVANCE" x%=FNtitle_stringcentre(string$) y%=hwind%!ypos%+hwind%!ysize%-20 PROCprint(x%,y%,string$,0) string$="TABLE":y%-=screen%!yfont% x%=FNtitle_stringcentre(string$) PROCprint(x%,y%,string$,0) x%=saucer%!xpos%+saucer%!xsize%+10 PROCplot("saucer_0",saucer%!xpos%,saucer%!ypos%) PROCprint(x%,saucer%!ypos%+10,"= 500 POINTS !",0) PROCplot("inv_3",inv3%!xpos%,inv3%!ypos%) PROCprint(x%,inv3%!ypos%+12, "= 100 POINTS !",0) PROCplot("inv_2",inv2%!xpos%,inv2%!ypos%) PROCprint(x%,inv2%!ypos%+12, "= 75 POINTS !",0) PROCplot("inv_1",inv1%!xpos%,inv1%!ypos%) PROCprint(x%,inv1%!ypos%+12, "= 50 POINTS !",0) REM Wait for 750 counts or until SPACE or Q pressed... rc%=FNtitle_wait IF rc%=1 THEN =TRUE IF rc%=2 THEN =FALSE PROCwipe_sprite(hwind%) REM Display Keyboard controls screen... string$="KEYBOARD CONTROLS" x%=FNtitle_stringcentre(string$) y%=hwind%!ypos%+hwind%!ysize%-20 PROCprint(x%,y%,string$,0) x%=keyz%!xpos%+keyz%!xsize%+10 PROCplot("key_z",keyz%!xpos%,keyz%!ypos%) PROCprint(x%,keyz%!ypos%+12,": MOVE LEFT",0) PROCplot("key_x",keyx%!xpos%,keyx%!ypos%) PROCprint(x%,keyx%!ypos%+12,": MOVE RIGHT",0) PROCplot("key_enter",keye%!xpos%,keye%!ypos%) PROCprint(x%,keye%!ypos%+12,": FIRE SHELL",0) PROCplot("key_m",keym%!xpos%,keym%!ypos%) PROCprint(x%,keym%!ypos%,": MUSIC ON/OFF",0) PROCplot("key_q",keyq%!xpos%,keyq%!ypos%) PROCprint(x%,keyq%!ypos%+12,": END GAME",0) REM Wait for 750 counts or until SPACE or Q pressed... rc%=FNtitle_wait IF rc%=1 THEN =TRUE IF rc%=2 THEN =FALSE PROCwipe_sprite(hwind%) REM Display HiScore table... REM First line... (This is conditional on scores_save%) string$="TODAY'S" IF scores_save% THEN string$="ALL TIME" x%=FNtitle_stringcentre(string$) y%=hwind%!ypos%+hwind%!ysize%-20 PROCprint(x%,y%,string$,0) REM Second line... string$="HI SCORES":y%-=screen%!yfont% x%=FNtitle_stringcentre(string$) PROCprint(x%,y%,string$,0) REM Loop through the HiScore table arrays displaying names and REM scores... y%=(hwind%!ypos%+hwind%!ysize%)-100 string$=" POS."+" PLAYER."+STRING$(12," ")+"SCORE." string$+=STRING$(3," ")+"RACK." x%=FNtitle_stringcentre(string$) PROCprint(x%,y%,string$,0) y%-=2*screen%!yfont% FOR i%=1 TO 10 IF i%<10 THEN string$=" "+STR$(i%)+". " ELSE string$=" "+STR$(i%)+". " ENDIF s$=FNencode_string(scores_name$(i%)) string$+=FNstr_pad(s$,8," ",FALSE):string$+=" .... " string$+=FNstr_rightalign(STR$(scores_value%(i%)),10) string$+=" ... "+FNstr_rightalign(STR$(scores_rack%(i%)),3) x%=FNtitle_stringcentre(string$) PROCprint(x%,y%-(i%*(screen%!yfont%+5)),string$,0) NEXT i% REM Wait for 750 counts or until SPACE or Q pressed... rc%=FNtitle_wait IF rc%=1 THEN =TRUE IF rc%=2 THEN =FALSE PROCwipe_sprite(hwind%) ENDWHILE IF INKEY(-17) THEN =TRUE :REM Get outta here (Q)... =FALSE REM Wait for 750 counts or SPACE or Q to be pressed... REM This has been moved here due to frequency of calling in it's REM current state it returns... REM 0 -> Timeout period expired... (750 counts up.) REM 1 -> "Q" to Quit... REM 2 -> SPACE to Play... DEFFNtitle_wait LOCAL t% t%=TIME+750 REPEAT IF INKEY(-82) THEN PROCevnt_kb_screendump IF INKEY(-102) THEN PROCevnt_kb_musictoggle UNTIL TIME=t% OR INKEY(-17) OR INKEY(-99) IF INKEY(-17) THEN =1 :REM Get outta here (Q)... IF INKEY(-99) THEN =2 :REM Play a game... =0 REM Prepare a string for display... REM Centering a string requires a number of parameters to be setup REM before calling PROCprint(). The process is largely repeatitive REM All we do is change a few values. DEFFNtitle_stringcentre(string$) LOCAL str% str%=LEN(string$)*screen%!xfont% =hwind%!xpos%+FNcentre_text(str%,hwind%!xsize%) REM ************************************************************* REM Game Loop... (Play one instance of the game.) REM ************************************************************* REM Loop through one instance of the game. This basically means... REM Setup in-game stuff. -> Play until out of bases. -> Manage REM hiscore entry. -> Return. DEFPROCmain LOCAL base%,shell%,saucer%,rack%,player% :REM Objects. LOCAL inv% LOCAL invstat%(),invfire%() :REM Invader status. LOCAL lives%,racknum%,suicide%,colgap% :REM Properties. LOCAL xnum%,ynum%,rownum% LOCAL evnt%,str%,string$,x%,y%,i% :REM Misc. REM Reserve memory for object blocks & setup pointer tokens. REM (Local objects.) DIM base%32,shell%36,saucer%48,rack%64,inv%16,player%16 DIM invstat%(10,6),invfire%(7) PROClog_sec("In Game Objects") PROClog_memusage("base",base%,32) PROClog_memusage("shell",shell%,36) PROClog_memusage("saucer",saucer%,48) PROClog_memusage("rack",rack%,64) PROClog_memusage("inv",inv%,16) PROClog_memusage("player",player%,16) lives%=0:score%=4:racknum%=8:suicide%=12 :REM Player tokens. colgap%=flags2%:xnum%=flags4%:ynum%=flags5% :REM Rack tokens. rownum%=flags8% REM Initialise LOCAL (in-game) variables. base%!xsize%=FNconv_units(30,1) :REM Base properties. base%!ysize%=FNconv_units(15,2) base%!xpos%=FNcentre_sprite(base%,screen%) base%!ypos%=50:base%!speed%=6:base%!dir%=base%!speed% inv%!xsize%=FNconv_units(20,1) :REM Invader properties. inv%!ysize%=FNconv_units(20,2) saucer%!xsize%=FNconv_units(40,1) :REM Saucer properties. saucer%!ysize%=FNconv_units(20,2) saucer%!ypos%=rail%!toprail%-(10+saucer%!ysize%) saucer%!speed%=5:saucer%!active%=FALSE saucer%!flags4%=1 shell%!xsize%=FNconv_units(5,1) :REM Shell properties. shell%!ysize%=FNconv_units(20,2) shell%!active%=FALSE:shell%!speed%=10 player%!racknum%=1:player%!score%=0 :REM Player properties. player%!lives%=3:player%!suicide%=FALSE rack%!colgap%=FNconv_units(20,1) :REM Rack properties. rack%!xnum%=10:rack%!ynum%=6 rack%!active%=TRUE FOR i%=1 TO 7 :REM Invader fire. invfire%(i%)=FNalloc_blk(44) PROClog_memusage("invfire%("+STR$(i%)+")",invfire%(i%),44) invfire%(i%)!xsize%=FNconv_units(5,1) invfire%(i%)!ysize%=FNconv_units(20,2) NEXT i% REM Initialise game screen... REM Draw the top and bottom rails, prepare and display the HUD & REM sort out the first rack of invaders. Finally... Display the REM player's base. PROCdraw_rail(rail%!toprail%):PROCdraw_rail(rail%!baserail%) PROChud_init(0,scores_value%(1),player%!lives%,player%!racknum%) PROCrack_gameinit :REM Setup invaders. PROCplot("base",base%!xpos%,base%!ypos%) :REM Display our base. REM Our event generator... REM This is the main polling loop. The program sits tight in this REM loop firing off events until the player runs out of lives. REPEAT REM Keyboard poll... evnt%=FALSE IF INKEY(-74) THEN PROCevnt_kb_basefire(base%!xpos%,base%!ypos%) IF INKEY(-98) THEN PROCevnt_kb_baseleft:evnt%=TRUE IF INKEY(-67) THEN PROCevnt_kb_baseright:evnt%=TRUE IF INKEY(-102)THEN PROCevnt_kb_musictoggle :REM (M)Music On/Off. IF INKEY(-82) THEN PROCevnt_kb_screendump :REM (S)Screenshot... IF INKEY(-17) THEN PROCevnt_kb_suicide :REM (Q)Suicide... IF INKEY(-34) THEN PROCevnt_kb_saucerkill :REM (W)Test... IF INKEY(-35) THEN PROCrack_endgame :REM (E)End Seq... REM Update screen... REM This is our equivalent of an arcade game's VBlank interrupt. REM We've polled the input device (our keyboard) and we're REM idling whilst processing events (evnt%=0). Either way REM something's going on, usually AI/Collision detection REM related. REM Process evnt_kb event updates. (Keyboard.) REM The only two that require action here are left & right as REM we need to display the base at the new position. IF evnt% THEN base%!xpos%+=base%!dir% PROCplot("base",base%!xpos%,base%!ypos%) ENDIF REM Process evnt_fr events. (Frame Refresh.) IF player%!lives%>0 THEN PROCevnt_fr_basefire :REM Animate player's shots. PROCevnt_fr_saucer :REM Saucer handling & AI. PROCevnt_fr_rackmove :REM Move rack & Collision detect. PROCevnt_fr_rackfire :REM Rack missile fire control. ENDIF REM Wait for next VBlank WAIT UNTIL player%!lives%=0 REM Display GAME OVER!!! in a bordered rectangle in the middle REM of the screen. box%!xsize%=240:box%!ysize%=40 box%=FNcentre_sprite_xy(box%,screen%):box%!ypos%-=3 PROCwipe_sprite(box%):PROCdraw_box(box%) PROCcentre_text_xy("GAME OVER!!!",11):PROCwait(750) REM Check & Handle HiTable entry... IF NOT player%!suicide% THEN IF player%!score%>=scores_value%(10) THEN PROChitable_entry(player%) ENDIF ENDIF ENDPROC REM ************************************************************* REM Proceedures (Keyboard events.) REM ************************************************************* REM Move base left. If we're already at left edge then bail... DEFPROCevnt_kb_baseleft REM Collision detection. (Base & Playfield.) IF base%!xpos%<=0 THEN base%!xpos%=0 :REM Don't move. ELSE base%!dir%=0-base%!speed% :REM Change direction. ENDIF ENDPROC REM Move base right. If we're already at the right edge, bail... DEFPROCevnt_kb_baseright REM Collision detection. (Base & Playfield.) IF base%!xpos%>=screen%!xsize%-base%!xsize% THEN base%!xpos%=screen%!xsize%-base%!xsize% :REM Don't move. ELSE base%!dir%=0+base%!speed% :REM Change direction. ENDIF ENDPROC REM Fire a shell. If we're already firing a shell bail. (We can REM only fire one shell at a time or else the animation gets REM confused.) shell%!active% is a property of the shell% object REM that is set boolean TRUE when player fires a shell and is REM cleared when either the shell scrolls off the top of the REM screen or hits something. DEFPROCevnt_kb_basefire(x%,y%) REM Bail if shell already active. IF NOT shell%!active% THEN shell%!active%=TRUE :REM Set active. shell%!xpos%=x%+(base%!xsize%/2) :REM Initial position. shell%!ypos%=y%+base%!ysize% ENDIF ENDPROC REM Debug only... Dump a screen to disc as ".Dump" DEFPROCevnt_kb_screendump REPEAT:UNTIL NOT INKEY(-82) OSCLI"ScreenSave .Dump"+STR$(dump_num%) dump_num%+=1 ENDPROC REM Player has chickened out. (Committed suicide. 'Q' in-game.) REM Set lives to zero and flag it up as a suicide event in the REM player object. (Skip Hiscore entry if suicide set.) DEFPROCevnt_kb_suicide player%!lives%=0:player%!suicide%=TRUE ENDPROC REM Toggle Music on & off... DEFPROCevnt_kb_musictoggle REPEAT:UNTIL NOT INKEY(-102) :REM Clear KB Buffer. music_status%=FNtoggle(music_status%) IF music_status%=1 THEN OSCLI"PlayStart" ELSE OSCLI"PlayPause" ENDPROC REM Test the saucer explosion timer... DEFPROCevnt_kb_saucerkill IF saucer%!active% THEN IF NOT saucer%!beenhit% THEN saucer%!beenhit%=TRUE:saucer%!flags1%=saucer%!flags2% PROCplot("saucer_2",saucer%!xpos%,saucer%!ypos%) ENDIF ENDIF ENDPROC REM ************************************************************* REM Proceedures (VBlank/Frame Events) REM ************************************************************* REM Continue animation of a player's shell... If one hasn't been REM created then we bail back to event loop. DEFPROCevnt_fr_basefire LOCAL out%,sh_cpy%:out%=flags1% IF shell%!active% THEN REM shell%!out% is set TRUE if the current shell is out of play. shell%!out%=FALSE IF shell%!ypos%>=rail%!toprail%-shell%!ysize% THEN shell%!active%=FALSE:shell%!out%=TRUE :REM Hit top. ENDIF REM Decide what to plot. Wipe sprite if we're out of play. IF shell%!out% THEN sh_cpy%=FNcopy_object(shell%,36) :REM Clear shell. sh_cpy%!ypos%-=2:PROCwipe_sprite(sh_cpy%) ELSE PROCplot("shell",shell%!xpos%,shell%!ypos%) :REM Display shell. ENDIF shell%!ypos%+=shell%!speed% :REM Advance frame. ENDIF ENDPROC REM Process saucer animation & generation events. A saucer can be REM randomely activated n% of the time, but not if one is REM currently on the screen. When activated, there is a 50% chance REM of it starting on the left hand side of the screen, likewise REM we have a 50% chance of it starting on the right. DEFPROCevnt_fr_saucer LOCAL hit%,timer%,spr%,toggle%,fr$ hit%=flags1%:timer%=flags2%:spr%=flags3% :REM Object tokens. toggle%=flags4% IF NOT saucer%!active% THEN IF FNpct(4) AND FNpct(5) THEN saucer%!active%=TRUE:saucer%!beenhit%=FALSE:saucer%!spr%=0 saucer%!toggle%=0 REM Decide which side of the screen to start from. IF FNpct(50) THEN saucer%!dir%=0-saucer%!speed% :REM Right edge. (50%) saucer%!xpos%=screen%!xsize% ELSE saucer%!dir%=0+saucer%!speed% :REM Left edge. (50%) saucer%!xpos%=0-saucer%!xsize% ENDIF ENDIF ELSE saucer%!spr%+=1:saucer%!timer%+=1 REM Has saucer gone off screen?? IF saucer%!xpos%<=0-saucer%!xsize%ANDSGN(saucer%!dir%)=-1 THEN saucer%!hit%=FALSE:saucer%!active%=FALSE:saucer%!timer%=0 ENDPROC ENDIF IF saucer%!xpos%>screen%!xsize%ANDSGN(saucer%!dir%)=1 THEN saucer%!hit%=FALSE:saucer%!active%=FALSE:saucer%!timer%=0 ENDPROC ENDIF REM Has saucer been hit by player shot?? IF NOT saucer%!beenhit% THEN IF FNhit(shell%,saucer%) THEN PROCwipe_sprite(shell%) PROCplot("saucer_2",saucer%!xpos%,saucer%!ypos%) saucer%!hit%=saucer%!timer%:saucer%!beenhit%=TRUE shell%!active%=FALSE:PROChud_scorehit(500) ENDIF ELSE IF FNhit(shell%,saucer%) THEN PROCwipe_sprite(shell%):shell%!active%=FALSE ENDIF ENDIF REM Animate saucer... IF NOT saucer%!beenhit% THEN IF saucer%!spr%>=100 THEN saucer%!spr%=0 REM Change image every ten frames. IF saucer%!spr%MOD10=0 THEN saucer%!toggle%=FNtoggle(saucer%!toggle%) ENDIF REM Display one of two images. fr$="saucer_"+STR$(saucer%!toggle%) PROCplot(fr$,saucer%!xpos%,saucer%!ypos%) saucer%!xpos%+=saucer%!dir% ELSE REM Animate/Remove explosion... IF saucer%!timer%>=saucer%!hit%+80 THEN PROCwipe_sprite(saucer%) saucer%!active%=FALSE:saucer%!hit%=FALSE saucer%!timer%=0:saucer%!toggle%=TRUE ELSE IF saucer%!timer%>=saucer%!hit%+60 THEN PROCplot("saucer_5",saucer%!xpos%,saucer%!ypos%) ELSE IF saucer%!timer%>=saucer%!hit%+40 THEN PROCplot("saucer_4",saucer%!xpos%,saucer%!ypos%) ELSE IF saucer%!timer%>=saucer%!hit%+20 THEN PROCplot("saucer_3",saucer%!xpos%,saucer%!ypos%) ENDIF ENDIF ENDIF ENDIF ENDIF ENDIF ENDPROC REM Check out the rack of invaders. Move left, right & down. Deal REM with collision detection between player missiles and invaders. DEFPROCevnt_fr_rackmove LOCAL rack_ext%,z%,col%,row%,eclr%,sclr%,rclr%,offset% LOCAL frame%,timer%,scol%,ecol% REM Setup meaningful pointers to our object flags... REM Vars colgap%, xnum%, ynum% & rownum% have already been tied REM to flags2%, flags4%, flags5% & flags8% respectively. frame%=flags1%:timer%=flags3%:scol%=flags6%:ecol%=flags7% REM Check timer related stuff. Can't let this value get too high REM or it will overwrite other memory locations. Still need to REM find the precise limit for this, but resetting the counter REM every 10,000 calls or so should prevent any over-runs. rack%!timer%+=1 IF rack%!timer% MOD 5=0 THEN rack%!frame%=FNtoggle(rack%!frame%) IF rack%!timer%>10000 THEN rack%!timer%=0 REM Deal with rack movement... REM Move rack left, right & down. Check each time we move down REM to make sure that the rack hasn't invaded the base. If it has REM then end the game. rack_ext%=rack%!xpos%+rack%!xsize% offset%=0-((rack%!scol%-1)*(inv%!xsize%+rack%!colgap%)) IF rack_ext%>=screen%!xsize% THEN rack%!dir%=0-rack%!speed%:PROCrack_advance ENDIF IF rack%!xpos%<=offset% THEN rack%!dir%=0+rack%!speed%:PROCrack_advance ENDIF REM Have we reached the base? offset%=rack%!ypos%+((rack%!rownum%-1)*inv%!ysize%) IF offset%<=base%!ypos%+base%!ysize% THEN PROCrack_endgame:ENDPROC ENDIF REM Deal with collision detection... REM Firstly we check to see if the player's shell lays somewhere REM inside the rack of invaders. If this is not the case then we REM bail and take no further action this time. However, if we REM are inside the rack (FNhit(shell%,rack%)=TRUE) then we need REM to see what, if anything, we've actually hit. IF FNhit(shell%,rack%) THEN z%=inv%!xsize%+rack%!colgap% IF (shell%!xpos%-rack%!xpos%) MOD z% <= inv%!xsize% THEN REM We've hit something... Find out what. col%=((shell%!ypos%-rack%!ypos%) DIV inv%!ysize%)+1 row%=((shell%!xpos%-rack%!xpos%) DIV z%)+1 REM Act on it... REM Vars col% & row% now contain the index of the invader that REM the player hit. We now check the status of that invader REM and act accordingly. A value of zero indicates that it's REM already dead and buried, whilst a negative value is used REM as a countdown timer for the explosion sprite. We are only REM interested in positive non-zero values. Anything else, we REM take no action. IF SGN(invstat%(row%,col%))=1 THEN invstat%(row%,col%)=-5 :REM Set explosion timer. PROCwipe_sprite(shell%) :REM Remove shell. shell%!active%=FALSE :REM De-activate shell. rack%!beenhit%+=1 :REM Another one down. REM Scoring... CASE col% OF WHEN 1,2 : PROChud_scorehit(50) :REM Bottom two rows. WHEN 3,4 : PROChud_scorehit(75) :REM Middle two rows. WHEN 5,6 : PROChud_scorehit(100) :REM Top two rows. ENDCASE ENDIF ENDIF ENDIF REM Have we destroyed the last invader in the current rack? IF rack%!beenhit%>=rack%!xnum%*rack%!ynum% THEN player%!racknum%+=1 :REM Advance rack counter. PROCwipe_sprite(rack%) :REM Clear the last invader. PROCdraw_rail(rail%!baserail%) :REM Re-draw bottom rail. PROChud_lives(player%!lives%) :REM Update HUD. PROChud_rack(player%!racknum%) PROCrack_init :REM Create new rack. ENDIF REM Do we need to resize the rack? The rack is defined as a block REM of a fixed size. We display the invaders in this block. Each REM time we clear the left or rightmost column or the bottom row, REM we need to update the size of this block to keep the display REM accurate. eclr%=TRUE:sclr%=TRUE FOR col%=1 TO rack%!ynum% IF invstat%(rack%!ecol%,col%)<>0 THEN eclr%=FALSE IF invstat%(rack%!scol%,col%)<>0 THEN sclr%=FALSE NEXT col% rclr%=TRUE FOR row%=1 TO rack%!xnum% IF invstat%(row%,rack%!rownum%)<>0 THEN rclr%=FALSE NEXT row% IF sclr% THEN rack%!scol%+=1 IF eclr% THEN rack%!xsize%-=(inv%!xsize%+rack%!colgap%):rack%!ecol%-=1 ENDIF IF rclr% THEN rack%!rownum%+=1 REM Finally... Redraw the rack... rack%!xpos%+=rack%!dir%:PROCrack_redraw ENDPROC REM Invader missile fire logic... REM There are 2 missile types @ 2 frames per type for a total of REM 4 frames. The type is set at random 1 or 2. The value of REM of invfire%(i%)!type% holds our missile type. Type 1 can be REM destroyed by player, type 2 can't. Initial speed (spd%) is set REM at 4. If we're dealing with a type 2 missile then we half REM that. DEFPROCevnt_fr_rackfire LOCAL timer%,frame%,type%,spd%,i%,j%,x%,y%,fpos%,frate%,fr$ timer%=flags1%:frame%=flags2%:type%=flags3% FOR i%=1 TO 7 IF NOT invfire%(i%)!active% THEN fpos%=RND(10):frate%=player%!racknum%+5 IF frate%>100 THEN frate%=100 IF FNpct(frate%) AND FNpct(4) THEN IF invstat%(fpos%,rack%!rownum%)>0 THEN x%=rack%!xpos%+(fpos%-1)*(inv%!xsize%+rack%!colgap%) y%=rack%!ypos%+(rack%!rownum%-1)*inv%!ysize% invfire%(i%)!xpos%=x%+inv%!xsize%/2:invfire%(i%)!ypos%=y% invfire%(i%)!active%=TRUE:invfire%(i%)!beenhit%=FALSE invfire%(i%)!timer%=0:invfire%(i%)!type%=1 invfire%(i%)!frame%=0:spd%=4 IF FNpct(25) THEN invfire%(i%)!type%=2:spd%=spd%/2 invfire%(i%)!speed%=spd% invfire%(i%)!dir%=invfire%(i%)!speed% ENDIF ENDIF ELSE REM Continue animating down the screen... invfire%(i%)!timer%+=1 IF invfire%(i%)!timer% MOD 5=0 THEN invfire%(i%)!frame%=FNtoggle(invfire%(i%)!frame%) ENDIF IF invfire%(i%)!timer%>10000 THEN invfire%(i%)!timer%=0 invfire%(i%)!ypos%-=invfire%(i%)!dir% REMPRINT"Type is : "+STR$(invfire%(i%)!type%) REMPRINT"Frame is : "+STR$(invfire%(i%)!frame%) fr$="miss_"+STR$(invfire%(i%)!type%)+STR$(invfire%(i%)!frame%) REMPRINT"Image is : "+fr$ PROCplot(fr$,invfire%(i%)!xpos%,invfire%(i%)!ypos%) REM Have we reached the bottom?? IF invfire%(i%)!ypos%<=rail%!baserail%+(rail%!ysize%+5) THEN invfire%(i%)!active%=FALSE:PROCwipe_sprite(invfire%(i%)) ENDIF REM Collision Detection... REM One of two events can happen here. (We've already checked REM the third.) Either we hit the player's base, or a shell REM hits us on the way up. REM Have we hit the player's base?? IF FNhit(invfire%(i%),base%) AND invfire%(i%)!active% THEN FOR j%=1 TO 3 PROCplot("base_"+STR$(j%),base%!xpos%,base%!ypos%) PROCwait(20):WAIT NEXT j% PROCwipe_sprite(base%) IF shell%!active% THEN PROCwipe_sprite(shell%):shell%!active%=FALSE ENDIF base%!xpos%=FNcentre_sprite(base%,screen%) player%!lives%-=1:PROChud_lives(player%!lives%) PROCwipe_sprite(invfire%(i%)):invfire%(i%)!active%=FALSE PROCplot("base",base%!xpos%,base%!ypos%) ENDIF REM Has a shell hit us?? IF FNhit(shell%,invfire%(i%)) AND invfire%(i%)!active% THEN IF invfire%(i%)!type%=1 THEN invfire%(i%)!active%=FALSE:PROChud_scorehit(5) PROCwipe_sprite(invfire%(i%)) ENDIF PROCwipe_sprite(shell%):shell%!active%=FALSE ENDIF ENDIF NEXT i% ENDPROC REM ************************************************************* REM Functions & Proceedures. (HiScore Table/HiScore Entry.) REM ************************************************************* REM Create a clean HiScore table. The value passed to us in save% REM determines whether we can save the table to disc or not. If REM we can (save%=TRUE) then we call PROChitable_save to write the REM file back. DEFPROChitable_create(save%) LOCAL i%,file_hdl%,n$ FOR i%=1 TO 10 IF i% MOD 2=0 THEN n$="Software" ELSE n$="DynaByte" scores_name$(i%)=FNencode_string(n$) scores_value%(i%)=(11-i%)*1000:scores_rack%(i%)=0 NEXT i% IF save% THEN PROChitable_save ENDPROC REM Load our HiTable from disc. The names are stored in RAM in REM encoded form and are only decoded for display. DEFPROChitable_load LOCAL i%,file_hdl% file_hdl%=OPENIN".Resources.HiTable" FOR i%=1 TO 10 INPUT#file_hdl%,scores_name$(i%),scores_value%(i%) INPUT#file_hdl%,scores_rack%(i%) NEXT i% CLOSE#file_hdl% ENDPROC REM Save our HiTable back to disc. The names are stored in RAM in REM encoded form and are only decoded for display. DEFPROChitable_save LOCAL i%,file_hdl% file_hdl%=OPENOUT".Resources.HiTable" FOR i%=1 TO 10 PRINT#file_hdl%,scores_name$(i%),scores_value%(i%) PRINT#file_hdl%,scores_rack%(i%) NEXT i% CLOSE#file_hdl% ENDPROC REM HiScore name entry... REM Sort through our hiscore array and find out where the player REM is. (We already know he is worthy.) Allow to enter their name REM and save the HiTable back to disk (Only if scores_save%=TRUE). REM then return... DEFPROChitable_entry(player%) LOCAL i%,pos%,racknum%,score%,space%,invt%,str%,x%,y%,g%,done% LOCAL box%,string$ DIM space%16,invt%16,box%16 :REM Space for objects. score%=4:racknum%=8 :REM Offsets in player% REM Setup graphical objects for later on... space%!xsize%=FNconv_units(100,1) :REM Space text properties. space%!ysize%=FNconv_units(40,2) space%!xpos%=FNcentre_sprite(space%,screen%) space%!ypos%=800 invt%!xsize%=FNconv_units(150,1) :REM Invaders text properties. invt%!ysize%=FNconv_units(40,2) invt%!xpos%=FNcentre_sprite(invt%,screen%) invt%!ypos%=space%!ypos%-invt%!ysize% REM Find out where we are and stick it in pos% pos%=0 FOR i%=10 TO 1 STEP -1 IF player%!score%>=scores_value%(i%) THEN pos%=i% NEXT i% REM Insert a new entry into the table and drop everything below REM us by one place... (We are 5th, so old 5th becomes 6th... 9th REM becomes 10th and 10th gets dropped.) We need to do this to REM all three arrays... FOR i%=9 TO pos% STEP -1 scores_name$(i%+1)=scores_name$(i%) scores_value%(i%+1)=scores_value%(i%) scores_rack%(i%+1)=scores_rack%(i%) NEXT i% REM Insert Player's achieved score & rack into the table... scores_value%(pos%)=player%!score% scores_rack%(pos%)=player%!racknum% REM Now deal with the graphics... REM Produce our basic Space Invaders layout a la title sequence. CLS:PROCdraw_rail(rail%!toprail%):PROCdraw_rail(rail%!baserail%) PROCplot("space",space%!xpos%,space%!ypos%) PROCplot("invaders",invt%!xpos%,invt%!ypos%) REM Build and display the text. string$="Congratulations!!! Your score of "+STR$(player%!score%) string$+=" has earned the rank of "+STR$(pos%)+FNsuffix(pos%) str%=LEN(string$)*screen%!xfont% x%=FNcentre_text(str%,screen%!xsize%):y%=invt%!ypos%-170 PROCprint(x%,y%,string$,0) REM Create and display a box... box%!xsize%=165:box%!ysize%=40 box%=FNcentre_sprite_xy(box%,screen%):PROCdraw_box(box%) REM Handle player input... REM Code 13 is Return/Enter. Code 8 is BackSpace. All other codes REM are filtered with FNvalid() before being added to string$ REM to prevent the program breaking when we can't find a graphic. x%=box%!xpos%+20:y%=box%!ypos%+12:string$="":i%=1:done%=FALSE OSCLI"FX 21,0" :REM Flush Keyboard buffer... REPEAT g%=GET :REM Get a char from Keyboard. REM Validate input... REM Only allow character input if our string length is less REM than 8 characters. Only accept Enter or Backspace for the REM 9th character. CASE g% OF WHEN 13 :done%=TRUE WHEN 8 :i%-=1:string$=LEFT$(string$,i%-1) OTHERWISE :IF i%<9 AND FNvalid(g%) THEN i%+=1:string$+=CHR$(g%) ENDCASE REM Update Display... REM Var i% will be less than 1 if the first character pressed REM was backspace. We need to trap that and make it 1. We also REM clear the input field before we re-display string$... This REM is so that when Backspace is pressed, the character is REM actually removed from the screen as well as from the input. REM (string$) REM We also need to check if we are trying to display an empty REM string, as our PROCprint() will fall over. This occurs if REM the first key typed was BackSpace or a character we can't REM display. (In the case of the latter, it is not added to REM string$, but we still come down here to update the display.) IF i%<1 THEN i%=1 PROCprint(x%,y%,STRING$(8," "),0) IF string$="" THEN PROCprint(x%,y%," ",0) ELSE PROCprint(x%,y%,string$,0) ENDIF UNTIL done% REM Add player's name to entry already prepared earlier and save REM if we can... (scores_save%=TRUE) IF string$="" OR string$=" " THEN string$="Anon ..." scores_name$(pos%)=FNencode_string(string$) IF scores_save% THEN PROChitable_save ENDPROC REM Have we got the ability to display this character? REM Return TRUE if we can or FALSE if we can't DEFFNvalid(chr%) LOCAL out% out%=FALSE IF chr%>=32 AND chr%<=126 THEN out%=TRUE =out% REM ************************************************************* REM Functions & Proceedures (HUD & Scoring) REM ************************************************************* REM Initialise the HUD display... DEFPROChud_init(sc%,hi%,l%,r%) LOCAL x%,string$ string$="SCORE :-"+FNstr_pad(STR$(sc%),10," ",TRUE) PROCprint(0,screen%!ysize%-screen%!yfont%,string$,0) string$="HI SCORE :-" string$+=FNstr_pad(STR$(hi%),10," ",TRUE) x%=screen%!xsize%-LEN(string$)*screen%!xfont% PROCprint(x%,screen%!ysize%-screen%!yfont%,string$,0) PROChud_lives(l%):PROChud_rack(r%) ENDPROC REM Update HUD lives display... DEFPROChud_lives(num%) LOCAL string$ PROCplot("base",0,0):string$=" = "+STR$(num%) PROCprint(base%!xsize%,0,string$,0) ENDPROC REM Update HUD rack display... DEFPROChud_rack(num%) LOCAL x%,string$ string$="RACK :-"+FNstr_pad(STR$(num%),3,"0",TRUE) x%=screen%!xsize%-LEN(string$)*screen%!xfont% PROCprint(x%,0,string$,0) ENDPROC REM Score a hit... REM The first part updates the score display on the top left REM corner of the HUD. If the player's current score is greater REM than that of the top spot, this value is also reflected in the REM Hi Score display in the top right. DEFPROChud_scorehit(value%) LOCAL x%,string$ player%!score%+=value% string$="SCORE :-"+FNstr_pad(STR$(player%!score%),10," ",TRUE) PROCprint(0,screen%!ysize%-screen%!yfont%,string$,0) IF player%!score%>scores_value%(1) THEN string$="HI SCORE :-" string$+=FNstr_pad(STR$(player%!score%),10," ",TRUE) x%=screen%!xsize%-LEN(string$)*screen%!xfont% PROCprint(x%,screen%!ysize%-screen%!yfont%,string$,0) ENDIF ENDPROC REM ************************************************************* REM Functions & Proceedures (Rack & Invaders) REM ************************************************************* REM Cold initialisation... REM Called directly by PROCmain once at the beginning of each REM game to initialise the first wave... DEFPROCrack_gameinit LOCAL frame%,scol%,ecol%,rownum% frame%=flags1%:scol%=flags6%:ecol%=flags7%:rownum%=flags8% PROCrack_init:PROCrack_redraw ENDPROC REM Initialise a rack of invaders... REM This is called at the beginning of the game and whenever REM rack%!beenhit%=rack%!xnum%*rack%!ynum% (All invaders hit.) REM to produce the next wave of the invasion force... We hold the REM status of each invader in the invstat%() array... REM Value Significance REM ======================== REM -n Just been hit. REM 0 Dead and buried. REM >0 Current graphic. DEFPROCrack_init LOCAL col%,row%,s% REM Rack properties... REM These are set here as they have to be reset at the beginning REM of each wave. rack%!xpos%=50:rack%!ypos%=550:rack%!frame%=0:rack%!beenhit%=0 rack%!speed%=3:rack%!dir%=rack%!speed%:rack%!scol%=1 rack%!ecol%=rack%!xnum%:rack%!rownum%=1 REM Use s% as a temporary to avoid problems with line length in REM !Zap edit window. s%=(rack%!xnum%*inv%!xsize%)+((rack%!xnum%-1)*rack%!colgap%) rack%!xsize%=s%:rack%!ysize%=rack%!ynum%*inv%!ysize% REM Initialise invaders status. FOR row%=1 TO rack%!xnum% FOR col%=1 TO rack%!ynum% invstat%(row%,col%)=((col%+1)/2)+(rack%!frame%*10) NEXT col% NEXT row% ENDPROC REM Redraw the whole rack of invaders at once. DEFPROCrack_redraw LOCAL col%,row%,x%,y%,blk%:DIM blk%16 FOR row%=rack%!scol% TO rack%!ecol% x%=rack%!xpos%+((row%-1)*(inv%!xsize%+rack%!colgap%)) FOR col%=rack%!rownum% TO rack%!ynum% y%=rack%!ypos%+((col%-1)*inv%!ysize%) IF invstat%(row%,col%)>0 THEN invstat%(row%,col%)=((col%+1)/2)+(rack%!frame%*10) ENDIF IF invstat%(row%,col%)<>0 THEN CASE SGN(invstat%(row%,col%)) OF WHEN -1 : PROCrack_checkinv(x%,y%,row%,col%) WHEN 1 : PROCplot("inv_"+STR$(invstat%(row%,col%)),x%,y%) ENDCASE ELSE blk%!xpos%=x%:blk%!ypos%=y%:blk%!xsize%=inv%!xsize% blk%!ysize%=inv%!ysize%:PROCwipe_sprite(blk%) ENDIF NEXT col% NEXT row% ENDPROC REM We start our explosion at -5. Every time we redraw an invader REM we add to the count until zero is reached. When this happens REM we wipe the sprite by creating a temporary object and passing REM it to PROCwipe_sprite() in the normal way. REM +----+--------------------------+ REM | x% | Physical X co-ordinate. | REM | y% | Physical Y co-ordinate. | REM | r% | Row index into array. | REM | c% | Column index into array. | REM +----+--------------------------+ DEFPROCrack_checkinv(x%,y%,r%,c%) LOCAL inv_obj% DIM inv_obj%16 invstat%(r%,c%)+=1:PROCplot("inv_4",x%,y%) IF invstat%(r%,c%)=0 THEN inv_obj%!xpos%=x%:inv_obj%!ypos%=y% inv_obj%!xsize%=inv%!xsize%:inv_obj%!ysize%=inv%!ysize% PROCwipe_sprite(inv_obj%) ENDIF ENDPROC REM Advance rack down by one row... REM Object blk% hovers above the top row of invaders to allow me a REM quick use of PROCwipe_sprite() to remove the old top row. We REM drop a row by dropping 1/3 of a row three times to lessen the REM "jump". The original used to do something like this, but I'm REM guessing this was down to the lack of CPU power rather than by REM design. DEFPROCrack_advance LOCAL blk%,i%:DIM blk%16 FOR i%=1 TO 3 rack%!ypos%-=inv%!ysize%/3:PROCrack_redraw NEXT i% blk%!xpos%=rack%!xpos% blk%!ypos%=rack%!ypos%+rack%!ysize% blk%!xsize%=rack%!xsize%:blk%!ysize%=inv%!ysize% PROCwipe_sprite(blk%) ENDPROC REM End game... REM We are called when the rack of invaders has reached the REM player's base. Do a little animation before we return to the REM Hiscore entry etc... DEFPROCrack_endgame LOCAL inv_left%,inv_right%,frame%,alt%,speed%,i%,img$ speed%=1:inv_ext%=24 DIM inv_left%28,inv_right%28 :REM Reserve space... REM Clear any in-game debris from the screen. (Invaders, saucers REM and other junk.) PROCwipe_sprite(rack%):PROCwipe_sprite(base%) IF shell%!active% THEN PROCwipe_sprite(shell%):shell%!active%=FALSE ENDIF IF saucer%!active% THEN PROCwipe_sprite(saucer%):saucer%!active%=FALSE ENDIF FOR i%=1 TO 7 IF invfire%(i%)!active% THEN PROCwipe_sprite(invfire%(i%)):invfire%(i%)!active%=FALSE ENDIF NEXT i% REM Setup initial values. base%!xpos%=FNcentre_sprite(base%,screen%) inv_left%!xpos%=0-inv%!xsize% :REM Left invader properties. inv_left%!ypos%=base%!ypos% inv_left%!dir%=speed% inv_right%!xpos%=screen%!xsize% :REM Right invader properties. inv_right%!ypos%=base%!ypos% inv_right%!dir%=0-speed% REM Zero any remaining lives & update bottom of HUD... player%!lives%=0:PROCdraw_rail(rail%!baserail%) PROChud_lives(player%!lives%):PROChud_rack(player%!racknum%) REM Hit it!! REM Two invaders, one from each side, approach the base and blow REM it up... PROCplot("base",base%!xpos%,base%!ypos%) REM The approach... frame%=0:alt%=FALSE REPEAT inv_left%!xpos%+=inv_left%!dir% inv_left%!inv_ext%=inv_left%!xpos%+inv%!xsize% inv_right%!xpos%+=inv_right%!dir% frame%+=1 IF frame% MOD 20=0 THEN alt%=FNtoggle(alt%) IF alt% THEN img$="inv_11" ELSE img$="inv_1" PROCplot(img$,inv_left%!xpos%,inv_left%!ypos%) PROCplot(img$,inv_right%!xpos%,inv_right%!ypos%) PROCwait(2):WAIT UNTIL inv_left%!inv_ext%>=base%!xpos% REM The cheer... FOR i%=1 TO 10 PROCplot("inv_1",inv_left%!xpos%,inv_left%!ypos%) PROCplot("inv_1",inv_right%!xpos%,inv_right%!ypos%) PROCwait(20) PROCplot("inv_11",inv_left%!xpos%,inv_left%!ypos%) PROCplot("inv_11",inv_right%!xpos%,inv_right%!ypos%) PROCwait(20):WAIT NEXT i% REM The end... FOR i%=1 TO 3 PROCplot("base_"+STR$(i%),base%!xpos%,base%!ypos%) PROCwait(20):WAIT NEXT i% PROCwait(20):PROCwipe_sprite(base%) ENDPROC REM ************************************************************* REM Functions & Proceedures (Core Graphics Routines) REM ************************************************************* REM Load a gfx file... REM Doing it this way allows us to use two different buffers, one REM for the sprites and the other for the charset. DEFFNgfx_load(filespec$) LOCAL area%,size% size%=FNfs_filesize(filespec$)+256 :REM Find filesize. DIM area% size% :REM Reserve space. !area%=size%:area%!4=0:area%!8=16:area%!12=16 :REM Set pointers. REM Load Graphics into our reserved space... SYS gfx_spriteop%, 256+10, area%, filespec$ :REM Load file. SYS gfx_spriteop%, 256+17, area% :REM Verify data. PROClog_memusage("Graphics",area%,size%) =area% REM Display a string character by character with a delay between REM each one. Each character is indexed to the 8*8 character set REM sprites using ASCII codes. DEFPROCprint(x%,y%,string$,char_delay%) LOCAL i%,chsize% gfx_area%=gfx_fontpool% :REM Switch to our charset. chsize%=screen%!xfont% FOR i%=1 TO LEN(string$) PROCplot(STR$(ASC(MID$(string$,i%,1))),x%+((i%-1)*chsize%),y%) IF char_delay%>0 THEN PROCwait(char_delay%) NEXT i% gfx_area%=gfx_spritepool% :REM Switch back... ENDPROC REM Display a given image. Modified to use a local sprite area & REM system calls to speed things up dramatically. (02/01/08) REM sprite$ = Name of image to display. REM x% = X Co-ordinate. REM y% = Y Co-ordinate. DEFPROCplot(sprite$,x%,y%) LOCAL address% SYS gfx_spriteop%, 256+24, gfx_area%, sprite$ TO ,, address% SYS gfx_spriteop%, 512+34, gfx_area%, address%, x%, y%, 0 ENDPROC REM Clear a sprite from the screen. (New method.) REM Before we created an identical bitmap in the sprite file REM and displayed this over our image. This should be faster, and REM result in a smaller sprite file. DEFPROCwipe_sprite(obj%) GCOL0,0 TINT0 RECTANGLE FILLobj%!xpos%,obj%!ypos%,obj%!xsize%,obj%!ysize% ENDPROC REM Centre text horizontally... REM Where string_length% is the length of the string in multiples REM of OS Units, where each multiple is the width of a character. REM Var screen% is the screen width. REM width... DEFFNcentre_text(string_length%,screen%) =(screen%/2)-(string_length%/2) REM Centre text horizontally & vertically. DEFPROCcentre_text_xy(string$,delay%) LOCAL str%,y% str%=LEN(string$)*screen%!xfont% y%=(screen%!ysize%/2)-(screen%!yfont%/2) PROCprint(FNcentre_text(str%,screen%!xsize%),y%,string$,delay%) ENDPROC REM Centre sprite horizontally... REM Takes the two "object" blocks as parameters and extracts the REM required values from them. Then returns the centre position. DEFFNcentre_sprite(object%,relative%) =(relative%!xsize%/2)-(object%!xsize%/2) REM Centre sprite horizontally & vertically. REM Returns the new position at offsets xpos% & ypos% in object%. DEFFNcentre_sprite_xy(object%,relative%) LOCAL x%,y% object%!xpos%=(relative%!xsize%/2)-(object%!xsize%/2) object%!ypos%=(relative%!ysize%/2)-(object%!ysize%/2) =object% REM ************************************************************* REM Functions & Proceedures (Graphical Boxes & Lines.) REM ************************************************************* REM Repeat a 6*6 image across the screen to produce a "rail" like REM effect at position y%. DEFPROCdraw_rail(ypos%) LOCAL i%,reps% reps%=screen%!xsize%/rail%!xsize% FOR i%=1 TO reps%+1 PROCplot("rail",(i%-1)*rail%!xsize%,ypos%) NEXT i% ENDPROC REM Produce a box to surround text in GAME OVER!!! & GOODBYE!!! REM texts. (Among others.) DEFPROCdraw_box(obj%) LOCAL x%,y%,cxsize%,cysize%,rxsize%,rysize%,i% REM Setup... cxsize%=FNconv_units(3,1):cysize%=FNconv_units(3,2) rxsize%=1:rysize%=3 REM The top & bottom... FOR i%=1 TO obj%!xsize% x%=obj%!xpos%+((i%-1)*rxsize%) PROCplot("box_x",x%,obj%!ypos%) PROCplot("box_x",x%,obj%!ypos%+(obj%!ysize%-(2*rysize%))) NEXT i% REM The two sides... x%=obj%!xpos%+(obj%!xsize%-cxsize%) FOR i%=1 TO obj%!ysize%-1 PROCplot("box_y",obj%!xpos%,obj%!ypos%+((i%-1)*rxsize%)) PROCplot("box_y",x%,obj%!ypos%+((i%-1)*rxsize%)) NEXT i% REM The four corners... PROCplot("box_bl",obj%!xpos%,obj%!ypos%) PROCplot("box_tl",obj%!xpos%,(obj%!ypos%+obj%!ysize%)-cysize%) PROCplot("box_br",obj%!xpos%+(obj%!xsize%-cxsize%),obj%!ypos%) x%=(obj%!xpos%+obj%!xsize%)-cxsize% y%=(obj%!ypos%+obj%!ysize%)-cysize% PROCplot("box_tr",x%,y%) ENDPROC REM ************************************************************* REM Functions & Proceedures (Misc. Game Specific.) REM ************************************************************* REM Get various information about screen resolution. This includes REM viewing area and conversion factors. We cannot rely on fixed REM values because they change from mode to mode. So grab the REM current values from the OS and bung 'em into some variables. DEFPROCget_screeninfo LOCAL blk% DIM blk% 20 :REM Reserve space for parameter block. REM Setup parameter block... blk%!0=4 :REM XEigFactor (Convert.) blk%!4=5 :REM YEigFactor (Convert.) blk%!8=11 :REM XWindLimit (Width.) blk%!12=12 :REM YWindLimit (Height.) blk%!16=-1 :REM Termination byte. SYS "OS_ReadVduVariables", blk%, blk%:REM Call OS. REM Setup our screen% object... screen%!x_eig%=blk%!0 :REM X & Y EigFactor. screen%!y_eig%=blk%!4 screen%!xsize%=(blk%!8)+1< OS Units) REM 2 = Height (Pixels > OS Units) REM 3 = Width (OS Units > Pixels) REM 4 = Height (OS Units > Pixels) REM Returns REM conv% = Converted value. DEFFNconv_units(size%,op%) LOCAL conv%,mode% REM Check to see if the screen mode has changed. If so, then REM call PROCget_screeninfo to update the values. This is REM probably overkill and can be omitted, but still good practice REM for debugging. mode%=MODE IF mode%<>screen%!setmode% THEN PROCget_screeninfo REM Do the conversion. CASE op% OF WHEN 1 : conv%=size%<>screen%!x_eig% WHEN 4 : conv%=size%>>screen%!y_eig% OTHERWISE ERROR 255,"Unknown FNconv_units() operation : "+STR$(op%) ENDCASE =conv% REM Collision detection. Takes two objects and returns TRUE if REM one is inside or has hit the other, else returns FALSE. Vars REM tx_ext% & ty_ext% hold the two far edges of the sprite. We REM calculate these by adding the target object's size to it's REM position each time we're called. We then use these values to REM perform a bounds check, one for x (ix% is TRUE if in.) and REM one for y (iy% is TRUE if in.). Finally... We return an REM evaluation of ix% AND iy%. DEFFNhit(obj%,targ%) LOCAL tx_ext%,ty_ext%,ix%,iy% ix%=FALSE:iy%=FALSE IF obj%!active% AND targ%!active% THEN tx_ext%=targ%!xpos%+targ%!xsize% :REM Right hand edge. ty_ext%=targ%!ypos%+targ%!ysize% :REM Top edge. IF obj%!xpos%>=targ%!xpos% AND obj%!xpos%=targ%!ypos% AND obj%!ypos%=t% ENDPROC REM Toggle a flag between 0 & 1. DEFFNtoggle(n%) =n% EOR 1 REM Returns a suffix (st, nd, rd etc...) when given a number. This REM number can be any number of digits long, we just deal with the REM last digit. DEFFNsuffix(n%) LOCAL out$,u% u%=VAL(RIGHT$(STR$(n%),1)) CASE u% OF WHEN 1 : out$="st" WHEN 2 : out$="nd" WHEN 3 : out$="rd" OTHERWISE : out$="th" ENDCASE =out$ REM Allocate a block from an array element... REM We can't DIM an array and have each element point to a block REM of memory all in one hit (DIM var%(7)16 won't work) so we REM DIM our array first and loop call this function to make each REM element a pointer to a block of memory. DEFFNalloc_blk(size%) LOCAL blk% DIM blk% size% =blk% REM ************************************************************ REM Functions & Proceedures (LibSTR) REM ************************************************************ REM I've imported the following two routines from my string utils REM library. Normally I'd just include them using the LIBRARY REM command, but that would kill the cruncher, so I've dumped them REM here... REM Pad a string to len% characters by adding multiple copies of REM char$ to it. If start% is set TRUE then we will add padding to REM the start of the string, else we add it at the end. If longer REM than len% characters, then chop it to len% characters. DEFFNstr_pad(string$,len%,char$,start%) LOCAL diff%,out$ diff%=len%-LEN(string$) :REM How many to add. IF diff%<=0 THEN out$=LEFT$(string$,len%) ELSE IF start% THEN out$=STRING$(len%,char$):RIGHT$(out$,LEN(string$))=string$ ELSE out$=string$+STRING$(diff%,char$) ENDIF ENDIF =out$ REM Fix a string's length to len% characters and return it right REM aligned... DEFFNstr_rightalign(string$,len%) LOCAL out$ out$=STRING$(len%," ") RIGHT$(out$,LEN(string$))=string$ =out$ REM ************************************************************ REM Functions & Proceedures (LibFS) REM ************************************************************ REM I've imported the following two routines from my fs utils REM library. Normally I'd just include them using the LIBRARY REM command, but that would kill the cruncher, so I've dumped them REM here... REM Check for the prescence of a filing system object at a given REM location. I say object, as this can check for the existance of REM directories as well as files. REM Parameter block... REM file$ = Filespec to check for in current directory. REM ...On Exit. REM found% = 0 Object not found. REM = 1 Object is a file. REM = 2 Object is a directory. DEFFNfs_find(file$) LOCAL found% SYS"XOS_File",17,file$ TO found% =found% REM Return the size of a file. (In bytes.) DEFFNfs_filesize(file$) LOCAL size% SYS"XOS_File" ,5,file$ TO ,,,,size% :REM Call OS to return size. =size% REM Can we write to this file? Return TRUE or FALSE. REM rc%=0 -> File doesn't exist. Var file$ contains location. REM rc%=1 -> File exists. Can we write/replace it? DEFFNfs_writeable(op%,file$) LOCAL file_hdl%,flg%,flags%,result% result%=FALSE REM Attempt to open or create a file depending on our opcode. CASE op% OF WHEN 0 : file_hdl%=OPENUP(file$+"chkfile") WHEN 1 : file_hdl%=OPENUP(file$) OTHERWISE : ERROR 255,"Unknown FNfs_writeable op :"+STR$(op%) ENDCASE REM If we get this far and we still haven't got a valid file REM handle then it's fairly safe to say that we're never gonna REM get one, so skip the next bit and return FALSE. IF file_hdl%<>0 THEN SYS "XOS_Args",254,file_hdl% TO flags%;flg% IF (flg% AND 1) THEN CLOSE#file_hdl% ERROR 255,"Unknown FNfs_writeable fault." ENDIF IF ((flags% AND (1<<7))<>0) THEN result%=TRUE CLOSE#file_hdl% ENDIF =result%