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 Z | | Left. REM X | | Right. REM Enter | | Fire. REM ************************************************************* REM Initialisation Part 1... (Files & Environment.) REM ************************************************************* REM Setup error trapping. ON ERROR REPORT:PRINT" at line ";ERL:END REM Setup our variables. (These are used all over...) bail%=FALSE :REM TRUE to quit game. scores_save%=TRUE :REM TRUE to save HiScores. dump_num%=0 :REM Screendump counter. DIM scores_name$(10),scores_value%(10) :REM HiScore table. DIM scores_rack%(10) 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_area% -> Block of RAM where our graphics reside. SYS "OS_SWINumberFromString",, "OS_SpriteOp" TO gfx_spriteop% gfx_size%=FNfs_filesize(".Resources.Gfx0") gfx_size%+=256 :REM Take our filesize & add 256 bytes. DIM gfx_area% gfx_size% :REM Reserve that as a block of RAM. !gfx_area%=gfx_size%:gfx_area%!4=0:gfx_area%!8=16 gfx_area%!12=16 REM Check we have all our files. File HiTable is a special case, REM as we can carry on without it. If it doesn't exist then we try REM to create it with FNcreate_hitable. If that returns FALSE REM (We can't create the file for some reason.) then we change the REM value of a global variable which we check before we try to REM save our Hiscore Table. In the event that we can't create the REM file, scores will be lost when we exit the game. REM Locate files... (Mission critical...) f$=".Resources.Gfx0" IF FNfs_find(f$)<>1 THEN ERROR 255,"File Missing!!" REM Load Graphics into our reserved space... SYS gfx_spriteop%, 256+10, gfx_area%, f$ SYS gfx_spriteop%, 256+17, gfx_area% REM HiScore Table... IF FNfs_find(".Resources.HiTable")<>1 THEN IF NOT FNhitable_create THEN scores_save%=FALSE ELSE 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 52 bytes long to hold all relevant REM data for that object. This, apart from being quicker, is far REM easier to code. 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-48 (flags1% - flags5%) 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 REM Define flags usage for screen and rail objects. x_eig%=16:y_eig%=20:setmode%=24 :REM Offsets in screen obj. xfont%=28:yfont%=32 toprail%=flags1%:baserail%=flags2% :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%52 :REM Global objects. 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. MODE28:OFF :REM 1280*1024 (256 cols.) 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 IF NOT bail% THEN CLS:PROCmain UNTIL bail% CLS:PROCcentre_text_xy("GOODBYE!!!",10):END 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 xpos & ypos values. DEFFNtitle LOCAL i%,space%,invt%,hwind%,str%,string$,x%,saucer%,inv1%,inv2% LOCAL inv3%,t%,y%,s$ DIM space%16,invt%16,hwind%16 :REM Title sequence objects. DIM saucer%16,inv1%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%=630: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) inv1%!ypos%=hwind%!ypos%+100 :REM Unique invader props. inv2%!ypos%=inv1%!ypos%+inv1%!ysize%+40 inv3%!ypos%=inv2%!ypos%+inv2%!ysize%+40 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 PROCplot("space",i%,space%!ypos%) PROCwait(1) NEXT i% FOR i%=screen%!xsize% TO invt%!xpos% STEP -5 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) PROCwipe_sprite(hwind%) REM Display Score Advance table... string$="SCORE ADVANCE" str%=LEN(string$)*screen%!xfont% x%=hwind%!xpos%+FNcentre_text(str%,hwind%!xsize%) y%=hwind%!ypos%+hwind%!ysize%-20 PROCprint(x%,y%,string$,0) string$="TABLE":y%-=screen%!yfont% str%=LEN(string$)*screen%!xfont% x%=hwind%!xpos%+FNcentre_text(str%,hwind%!xsize%) 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%+20, "= 100 POINTS !",0) PROCplot("inv_2",inv2%!xpos%,inv2%!ypos%) PROCprint(x%,inv2%!ypos%+20, "= 75 POINTS !",0) PROCplot("inv_1",inv1%!xpos%,inv1%!ypos%) PROCprint(x%,inv1%!ypos%+20, "= 50 POINTS !",0) REM Wait for 750 counts or until SPACE or Q pressed... t%=TIME+750 REPEAT IF INKEY(-82) THEN PROCevnt_kb_screendump UNTIL TIME=t% OR INKEY(-17) OR INKEY(-99) IF INKEY(-17) THEN =TRUE :REM Get outta here (Q)... IF INKEY(-99) THEN =FALSE :REM Play a game... REM Display HiScore table... REM First line... (This is conditional on scores_save%) PROCwipe_sprite(hwind%) string$="TODAY'S" IF scores_save% THEN string$="ALL TIME" str%=LEN(string$)*screen%!xfont% x%=hwind%!xpos%+FNcentre_text(str%,hwind%!xsize%) y%=hwind%!ypos%+hwind%!ysize%-20 PROCprint(x%,y%,string$,0) REM Second line... string$="HI SCORES":y%-=screen%!yfont% str%=LEN(string$)*screen%!xfont% x%=hwind%!xpos%+FNcentre_text(str%,hwind%!xsize%) 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."+STRING$(21," ")+"SCORE."+STRING$(3," ")+"RACK." str%=LEN(string$)*screen%!xfont% x%=hwind%!xpos%+FNcentre_text(str%,hwind%!xsize%) 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) str%=LEN(string$)*screen%!xfont% x%=hwind%!xpos%+FNcentre_text(str%,hwind%!xsize%) PROCprint(x%,y%-(i%*(screen%!yfont%+5)),string$,0) NEXT i% REM Wait for 750 counts or until SPACE or Q pressed... t%=TIME+750 REPEAT IF INKEY(-82) THEN PROCevnt_kb_screendump UNTIL TIME=t% OR INKEY(-17) OR INKEY(-99) IF INKEY(-17) THEN =TRUE :REM Get outta here (Q)... IF INKEY(-99) THEN =FALSE :REM Play a game... ENDWHILE IF INKEY(-17) THEN =TRUE :REM Get outta here (Q)... =FALSE 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%() :REM Invader status. LOCAL lives%,racknum%,frame%,colgap% :REM Properties. LOCAL evnt%,str%,string$,x%,y% :REM Misc. REM Reserve memory for object blocks & setup pointer tokens. REM (Local objects.) DIM base%52,shell%52,saucer%52,rack%52,inv%16,invstat%(10,6) DIM player%16 lives%=0:score%=4:racknum%=8:suicide%=12 :REM Player tokens. frame%=flags1%:colgap%=flags2% :REM Rack tokens. timer%=flags3%:xnum%=flags4%:ynum%=flags5% 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% shell%!xsize%=FNconv_units(5,1) :REM Shell properties. shell%!ysize%=FNconv_units(20,2) shell%!active%=FALSE:shell%!speed%=10 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%=TRUE inv%!xsize%=FNconv_units(20,1) :REM Invader properties. inv%!ysize%=FNconv_units(20,2) rack%!colgap%=FNconv_units(20,1) :REM Rack properties. rack%!xnum%=10:rack%!ynum%=6 player%!racknum%=1:player%!score%=0 :REM Player properties. player%!lives%=3:player%!suicide%=FALSE 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_init:PROCrack_redraw :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(-82) THEN PROCevnt_kb_screendump :REM Screenshot... IF INKEY(-17) THEN PROCevnt_kb_suicide :REM Suicide... 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.) PROCevnt_fr_basefire PROCevnt_fr_saucer PROCevnt_fr_moverack UNTIL player%!lives%=0 PROCcentre_text_xy("GAME OVER!!!",11):PROCwait(750) 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_fired% is a global variable that is set REM boolean TRUE when player fires a shell and is cleared when REM either the missile scrolls off the top of the screen or REM hits an invader. 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 ************************************************************* 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%,52) :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% 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. IF saucer%!toggle%=0 THEN PROCplot("saucer_0",saucer%!xpos%,saucer%!ypos%) ELSE PROCplot("saucer_1",saucer%!xpos%,saucer%!ypos%) ENDIF saucer%!xpos%+=saucer%!dir% ELSE REM Animate/Remove explosion... IF saucer%!timer%>=saucer%!hit%+125 THEN PROCwipe_sprite(saucer%) saucer%!active%=FALSE:saucer%!hit%=FALSE saucer%!timer%=0:saucer%!toggle%=TRUE ELSE IF saucer%!timer%>=saucer%!hit%+100 THEN PROCplot("saucer_5",saucer%!xpos%,saucer%!ypos%) ELSE IF saucer%!timer%>=saucer%!hit%+75 THEN PROCplot("saucer_4",saucer%!xpos%,saucer%!ypos%) ELSE IF saucer%!timer%>=saucer%!hit%+50 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. REM Deal with AI regarding invaders bombs. DEFPROCevnt_fr_moverack LOCAL timer%,rack_ext%,inv_obj%,col%,row%,x%,y% timer%=flags3% :REM Properties. 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 left & right movement only for the time being. I'll REM add down to this mix later... rack_ext%=rack%!xpos%+rack%!xsize% IF rack_ext%>=screen%!xsize% THEN rack%!dir%=0-rack%!speed% IF rack%!xpos%<=0 THEN rack%!dir%=0+rack%!speed% REM Deal with collision detection. Here we create a temporary REM object containing all the info needed to call FNhit() for REM each of the invaders. We then pass this info and act on the REM result. The actual x & y co-ordinates of each invader are REM worked out on the fly by taking the base co-ordinates of the REM rack and working out using sprite sizes, offsets & the index REM into the invstat%() array. REM ... This code has been removed... Needs a rethink. REM Finally... Redraw the rack... rack%!xpos%+=rack%!dir%:PROCrack_redraw ENDPROC REM ************************************************************* REM Functions & Proceedures. (HiScore Table/HiScore Entry.) REM ************************************************************* REM Create a clean HiScore table. Return TRUE if we can, else REM return FALSE. Either way, we fill our two arrays with default REM values. DEFFNhitable_create 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% file_hdl%=OPENOUT".Resources.HiTable" IF file_hdl%=0 THEN =FALSE 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% =TRUE 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 ************************************************************* 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 Initialise and draw 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 -1 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% 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% FOR row%=1 TO rack%!xnum% x%=rack%!xpos%+((row%-1)*(inv%!xsize%+rack%!colgap%)) FOR col%=1 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 IF invstat%(row%,col%)=-1 THEN PROCplot("inv_4",x%,y%) ELSE PROCplot("inv_"+STR$(invstat%(row%,col%)),x%,y%) ENDIF ENDIF NEXT col% NEXT row% ENDPROC REM Reduce the size of the rack when an end column on either left REM or right hand side has been destroyed. DEFPROCrack_shrinkcolumn REM Placeholder... ENDPROC REM Reduce the size of the rack when the bottom row of invaders REM has been destroyed. DEFPROCrack_shrinkrow REM Placeholder... ENDPROC REM ************************************************************* REM Misc. Functions & Proceedures (Game Specific) 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 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 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% 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% ENDPROC 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%<=tx_ext% THEN ix%=TRUE IF obj%!ypos%>=targ%!ypos% AND obj%!ypos%<=ty_ext% THEN iy%=TRUE ENDIF =ix% AND iy% REM Create a copy of an object and return it's address. Modified REM 02/12/07 to handle objects of multiple sizes more gracefully. REM obj% = Original object block's address. REM size% = Size of original object block. DEFFNcopy_object(obj%,size%) LOCAL offset%,newobj% DIM newobj% size% FOR offset%=0 TO size%-4 STEP 4 newobj%!offset%=obj%!offset% NEXT offset% =newobj% 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 ************************************************************* REM Functions & Proceedures (Generic & Portable Code) REM ************************************************************* 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 Returns TRUE n% percent of the time. DEFFNpct(n%) LOCAL rnd% rnd%=RND(100) =rnd%<=n% REM Encode or decode a string using a simple XOR algo... This REM prevents hackers from peeking at and modifying strings in REM files. Strings are in reverse order in BASIC datafiles, but REM are still in plaintext format. REM string$ = String to mash up. REM Returns REM mashed$ = Mashed up string. DEFFNencode_string(string$) LOCAL i%,mashed$ FOR i%=1 TO LEN(string$) mashed$+=CHR$(ASC(MID$(string$,i%,1)) EOR 131) NEXT i% =mashed$ REM Introduce a delay of n% hundredths of a second. DEFPROCwait(n%) LOCAL t% t%=TIME+n% REPEAT:UNTIL TIME>=t% ENDPROC REM Toggle a flag between 0 & 1. DEFFNtoggle(n%) =n% EOR 1 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 f$ = Filespec to check for in current directory. REM ...On Exit. REM f% = 0 Object not found. REM = 1 Object is a file. REM = 2 Object is a directory. DEFFNfs_find(f$) LOCAL f% SYS"XOS_File",17,f$ TO f% =f% REM Return the size of a file. (In bytes.) DEFFNfs_filesize(f$) LOCAL s% IF FNfs_find(f$)=0 THEN =0 :REM Return zero if not found. SYS"XOS_File" ,5,f$ TO ,,,,s% :REM Call OS to return size. =s%