REM >.!RunImage 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. 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 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...) file_err%=0 IF FNfile(".Resources.Gfx0")<>1 THEN file_err%+=1 IF FNfile(".Resources.Gfx1")<>1 THEN file_err%+=1 IF file_err% THEN ERROR 255,"File Missing!!" REM Load files... (Mission critical...) OSCLI"SLoad .Resources.Gfx0" :REM Sprites. OSCLI"SMerge .Resources.Gfx1" :REM Charset. REM HiScore Table... IF FNfile(".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%) NEXT i% FOR i%=screen%!xsize% TO invt%!xpos% STEP -5 PROCplot("invaders",i%,invt%!ypos%) NEXT i% string$="PRESS SPACE TO PLAY" str%=LEN(string$)*screen%!xfont%:y%=170 PROCprint(FNcentre_text(str%,screen%!xsize%),y%,string$,10) string$="OR Q TO QUIT" str%=LEN(string$)*screen%!xfont%:y%-=screen%!yfont% PROCprint(FNcentre_text(str%,screen%!xsize%),y%,string$,10) 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: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... IF INKEY(-82) THEN PROCevnt_kb_screendump 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: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... IF INKEY(-82) THEN PROCevnt_kb_screendump 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 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%12 lives%=0:score%=4:racknum%=8 :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 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 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%=0 IF INKEY(-74) THEN PROCevnt_kb_basefire(base%!xpos%,base%!ypos%) IF INKEY(-98) THEN PROCevnt_kb_baseleft:evnt%=1 IF INKEY(-67) THEN PROCevnt_kb_baseright:evnt%=2 IF INKEY(-82) THEN PROCevnt_kb_screendump IF INKEY(-17) THEN player%!lives%=0 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.) IF evnt%>0 THEN REM Update base position. (Only left & right.) IF evnt%<=2 THEN base%!xpos%+=base%!dir% PROCplot("base",base%!xpos%,base%!ypos%) ENDIF 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!!!",10):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:ENDPROC base%!dir%=0-base%!speed% :REM Change direction. 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%:ENDPROC 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 ************************************************************* 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% hit%=flags1%:timer%=flags2%:spr%=flags3% :REM Object tokens. IF NOT saucer%!active% THEN IF FNpct(3) AND FNpct(1) THEN saucer%!active%=TRUE:saucer%!beenhit%=FALSE:saucer%!spr%=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 IF saucer%!spr%<50 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%+200 THEN PROCwipe_sprite(saucer%) saucer%!active%=FALSE:saucer%!hit%=FALSE saucer%!timer%=0 ELSE IF saucer%!timer%>=saucer%!hit%+175 THEN PROCplot("saucer_5",saucer%!xpos%,saucer%!ypos%) ELSE IF saucer%!timer%>=saucer%!hit%+150 THEN PROCplot("saucer_4",saucer%!xpos%,saucer%!ypos%) ELSE IF saucer%!timer%>=saucer%!hit%+100 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% 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 PROCrack_toggleframe 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% 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 this 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 PROCplot("inv_"+STR$(invstat%(row%,col%)),x%,y%) NEXT col% NEXT row% ENDPROC REM Toggle value of frame counter. This is used to display the two REM alternating frames for each invader. DEFPROCrack_toggleframe IF rack%!frame%=0 THEN rack%!frame%=1 ELSE rack%!frame%=0 ENDIF 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. REM sprite$ = Name of image to display. REM x% = X Co-ordinate. REM y% = Y Co-ordinate. DEFPROCplot(sprite$,x%,y%) OSCLI"SChoose "+sprite$ PLOT &ED,x%,y% ENDPROC REM Returns TRUE n% percent of the time. DEFFNpct(n%) LOCAL rnd% rnd%=RND(100) =rnd%<=n% 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. DEFFNfile(f$) LOCAL f% SYS"XOS_File",17,f$ TO f% =f% 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 ************************************************************ 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$