REM >.!RunImage REM Taito Space Invaders (Circa 1979) Clone REM Keys... REM Z -- Move Left. REM X -- Move Right. REM Enter -- Fire. REM ************************************************************* REM Initialisation... (This has got to happen first.) 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. DIM scores_name$(10),scores_value%(10) :REM HiScore table. 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 Mission critical... file_err%=0 IF FNfile(".Resources.Gfx0")<>1 THEN file_err%+=1 IF file_err% THEN ERROR 255,"File Missing!!" OSCLI"SLoad .Resources.Gfx0" REM HiScore Table... IF FNfile(".Resources.HiTable")<>1 THEN IF NOT FNcreate_hitable THEN scores_save%=FALSE ELSE PROCload_hitable ENDIF REM Object offsets. Each object (Base, saucer, missile etc.) is REM given a block of memory by me to hold all relevant data for REM that object. This, apart from being quicker, takes up far less REM room. These are tokens to address offsets in each of those REM blocks of memory. (See tutorial for how this works...) REM Contents for offsets 24-40 will be different, depending on REM the nature of the object. I'll define LOCAL variables with REM more meaningful names and point them to these values. xpos%=0:ypos%=4:xsize%=8:ysize%=12 :REM Position & Size. dir%=16:speed%=20 :REM Movement. flags1%=24:flags2%=28:flags3%=32 :REM Misc flags. flags4%=36:flags5%=40 x_eig%=flags1%:y_eig%=flags2% :REM Offsets in screen obj. setmode%=flags3% toprail%=flags1%:baserail%=flags2% :REM Offsets in rail obj. REM Reserve memory for global object blocks... DIM screen%40,rail%40 :REM Global objects. REM ************************************************************* REM Initialisation... (Start doing something.) REM ************************************************************* PROCset_mode(28,FALSE) :REM 1280*1024 (256 cols.) REM Global object screen% has already been setup. Let's sort out REM our 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:PRINT"GoodBye!":END REM ************************************************************* REM Title Loop... (Currently does nothing.) REM ************************************************************* DEFFNtitle LOCAL i%,space%,invt%,string$,key$ DIM space%44,invt%44 :REM Title sequence objects. 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%!xsize%,screen%!xsize%) space%!ypos%=800 invt%!xsize%=FNconv_units(150,1) :REM Invaders text properties. invt%!ysize%=FNconv_units(40,2) invt%!xpos%=FNcentre_sprite(invt%!xsize%,screen%!xsize%) invt%!ypos%=space%!ypos%-invt%!ysize% REM Get it all up and going... PROCdraw_rail(rail%!flags1%):PROCdraw_rail(rail%!flags2%) 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" PROCchar_print(string$,FNcentre_text(string$,80),51) string$="OR Q TO QUIT" PROCchar_print(string$,FNcentre_text(string$,80),53) REPEAT:key$=GET$:UNTIL INSTR(" Qq",key$)<>0 IF key$="Q" OR key$="q" THEN =TRUE =FALSE REM ************************************************************* REM Game Loop... REM ************************************************************* DEFPROCmain LOCAL base%,shell%,saucer%,evnt%,lives%,active% REM Reserve memory for object blocks & setup pointer tokens. REM (Local objects.) DIM base%44,shell%44,saucer%44 lives%=flags4% :REM Base tokens. active%=flags1% :REM Shell/Saucer. 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%!xsize%,screen%!xsize%) base%!ypos%=50:base%!speed%=6:base%!lives%=3 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%-(20+saucer%!ysize%) saucer%!speed%=5:saucer%!active%=FALSE PROCinitialise_display :REM Initial layout. REM Our event generator... 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 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 UNTIL base%!lives%=0 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 ENDIF base%!dir%=0+base%!speed% :REM Change direction. 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%) IF shell%!active% THEN ENDPROC :REM Bail if active. shell%!active%=TRUE :REM Set active. shell%!xpos%=x%+(base%!xsize%/2) :REM Initial position. shell%!ypos%=y%+base%!ysize% 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%:out%=flags2% IF NOT shell%!active% THEN ENDPROC :REM Nothing to do. 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. Display "shout" if we're out of play. IF shell%!out% THEN PROCplot("shout",shell%!xpos%,shell%!ypos%-2):REM Clear shell. ELSE PROCplot("shell",shell%!xpos%,shell%!ypos%) :REM Display shell. ENDIF shell%!ypos%+=shell%!speed% :REM Advance frame. 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%,beenhit% hit%=flags2%:timer%=flags3%:spr%=flags4% :REM Object tokens. beenhit%=flags5% IF NOT saucer%!active% THEN PROCevnt_saucer_gen ELSE IF NOT saucer%!hit% THEN PROCevnt_saucer_process ELSE PROCevnt_saucer_hit ENDIF saucer%!timer%+=1 ENDIF ENDPROC REM Generate a saucer. DEFPROCevnt_saucer_gen 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 ENDPROC REM Process saucer animation & collision detection. DEFPROCevnt_saucer_process saucer%!spr%+=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 PROCplot("saucer_2",saucer%!xpos%,saucer%!ypos%) PROCplot("shout",shell%!xpos%,shell%!ypos%) saucer%!hit%=saucer%!timer%:saucer%!beenhit%=TRUE shell%!active%=FALSE ELSE REM Do the animation... 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% ENDIF ENDIF ENDPROC REM We've entered back for the final stage.. DEFPROCevnt_saucer_hit IF saucer%!timer%>=saucer%!hit%+100 THEN PROCplot("saucer_3",saucer%!xpos%,saucer%!ypos%) saucer%!active%=FALSE:saucer%!hit%=FALSE saucer%!timer%=0 ENDPROC ENDIF 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. DEFFNcreate_hitable LOCAL i%,file_hdl%,n$ FOR i%=1 TO 10 IF i% MOD 2=0 THEN n$="DynaByte" ELSE n$="Software" scores_name$(i%)=FNencode_string(n$) scores_value%(i%)=(11-i%)*1000 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%) 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. DEFPROCload_hitable LOCAL i%,file_hdl% file_hdl%=OPENIN".Resources.HiTable" FOR i%=1 TO 10 INPUT#file_hdl%,scores_name$(i%),scores_value%(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. DEFPROCsave_hitable LOCAL i%,file_hdl% file_hdl%=OPENOUT".Resources.HiTable" FOR i%=1 TO 10 PRINT#file_hdl%,scores_name$(i%),scores_value%(i%) NEXT i% CLOSE#file_hdl% ENDPROC REM ************************************************************* REM Misc. Functions & Proceedures (Game Specific) REM ************************************************************* REM Currently this only displays our base. I'll add the initial REM positions of the invaders et al over time. Hence the reason REM for this routine. DEFPROCinitialise_display PROCdraw_rail(rail%!flags1%):PROCdraw_rail(rail%!flags2%) PROCplot("base",base%!xpos%,base%!ypos%) :REM Display our base. ENDPROC 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 Set our screen mode to a given value. This will call REM PROCget_screeninfo to give us some information about what we REM working with. DEFPROCset_mode(mode%,curs%) MODE mode% IF NOT curs% THEN OFF PROCget_screeninfo 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. REM Call OS & extract returned values. SYS "OS_ReadVduVariables", blk%, blk%:REM Call OS. 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. DEFPROCchar_print(string$,x%,y%) LOCAL i% PRINTTAB(x%,y%); FOR i%=1 TO LEN(string$) IF i%=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 ************************************************************* 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 Centre a sprite horizontally... REM Where sprite% is the sprite's width and screen% is the screen REM width... DEFFNcentre_sprite(sprite%,screen%) =(screen%/2)-(sprite%/2) REM Centre a text string horizontally... REM Where string$ is the string to centre and cols% is the number REM of columns of the current screen mode. DEFFNcentre_text(string$,cols%) =(cols%/2)-(LEN(string$)/2) 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