This action might not be possible to undo. Are you sure you want to continue?
Beginners Guide to DarkBasic Pro
R E V I S 0I O N 1
Table of Contents
Introduction .......................................................................................................................................... 2 TDK_Man's Dark Basic Programming for Beginners .............................................................................. 3
Part 1 - Variables ............................................................................................................................................ 3 Part 2 - Layout, Structure And Style .............................................................................................................. 14 Part 3 - Elementary Commands 26 Part 4 - File Access ........................................................................................................................................ 46 Choosing The Correct Variables .................................................................................................................... 57 Dark Basic Functions .................................................................................................................................... 60 Everything you wanted to know about strings .............................................................................................. 63 Timer Tutorial............................................................................................................................................... 73 Dark Basic Matrix Primer .............................................................................................................................. 77 Dark Basic 3D Collision (DBC & DBP) ............................................................................................................. 85 2D Shooting - A Basic Introduction (DBC & DBP) ........................................................................................... 94 BitMap Font Tutorial .................................................................................................................................. 100
Space Invader Tutorial ...................................................................................................................... 103
Building a Framework ................................................................................................................................. 103 Design, Design, Design! .............................................................................................................................. 103 Structure .................................................................................................................................................... 104 Game Data ................................................................................................................................................. 105 Standing on 2 Feet ..................................................................................................................................... 105 The Story So Far ......................................................................................................................................... 105 The Plan Ahead .......................................................................................................................................... 106 Building Blocks ........................................................................................................................................... 106 Rapid Development .................................................................................................................................... 106 Create and Destroy .................................................................................................................................... 107 Adding a state machine .............................................................................................................................. 107 The Plan Ahead .......................................................................................................................................... 108 Big Changes, Fast! ...................................................................................................................................... 108 Conclusion .................................................................................................................................................. 110 Zork Tutorial - Part One .............................................................................................................................. 111 Zork Tutorial - Part II - OBJECTS AND INVENTORY ....................................................................................... 128
My very first DPBro program... yes, the planet spins!
Having just bought DarkBasic Professional, it does seem a bit overwhelming. I have programmed BASIC in the past but that was a very long time ago so I’m going to have to relearn it all over again. So, in order to help me I’m compiling the tutorials I’ve found into one document which can be printed out and read away from the computer (it also stops you having to switch back and forth between online tutorial and DBPro).
When you play a computer game. a dot will appear on your monitor screen. so you've downloaded the DB demo or bought it. A computer program is simply a list of instructions the computer has to follow to do it. back when I first started programming. For example.. you need to learn the ABC's and start simple.. It will cover the elementary groundings and anyone with any previous programming experience will find little of interest in this tutorial. Smaller sections of code which can be typed into DB will appear in Courier font to make the text easier to follow: SET DISPLAY MODE 800. You've no programming experience and can't make any sense out of the help files and/or manuals.Variables This is the first of an intended series of tutorials aimed at teaching the newcomer to programming the basics of the BASIC programming language. Every image. To write a game. a language called 'Assembler' was developed. you need to create the machine code which tells the computer's CPU where to move the data from and where to put it. The colour of the dot depends on the value of the number.16 CLS PRINT "Hello World" Where Do I Start? OK. text character and coloured dot you see on the computer screen is nothing more than a number in your computer's memory. but the examples and theory should apply with little or no modification to any dialect of BASIC . In part 1 you will find a very brief introduction to programming languages and an explanation of variables. call_oz(GN_Nln) push iy pop hl ld de. as binary (all 0's and 1's) is not the easiest way to go about it. This turned the programming process into something more manageable and easier to learn.TDK_Man's Dark Basic Programming for Beginners Part 1 . computers manipulate data by transferring numbers around in memory. when a number is placed in a certain part of memory called the video memory.including DB Pro. The examples will be in Dark Basic Classic. all you are seeing is the results of all these numbers being manipulated to create what you see on the screen. 6 3 . loaded it up and don't know what to do next.600. Well. However. so that's what we will do. Programs: In a nutshell. 2 ld b.
A numeric variable can be used in a formula or calculation in exactly the same way as a number can. because each individual dot on the monitor had to be manually set to black in a loop .blindingly fast! And.UNTIL. So. Instead. I found it easiest when learning to program by thinking of it as follows… 4 .l Even so. PUSH and POP and replaced them with higher level commands like PRINT and REPEAT. a variable is in effect a substitute for something which can change . that remains true to this day .a program written in assembler will be a lot faster than the same program written in a higher-level language.none of which for the main part were interchangeable. Besides that. And then along came BASIC or Beginners All-purpose Symbolic Instruction Code which as the name suggests was aimed firmly at beginners. Variables: All versions of BASIC are built using the same building blocks. C and COBOL . but a thousand times bigger and more difficult to write. something like clearing the screen . When these programs were run. short assembler routines are written for specific purposes where speed is essential. Also. Without them you would have an impossible task writing anything but the smallest program.for example.which we now take for granted with CLS in DB . Clearing the screen was reduced to CLS. This might be Z80. Examples of these languages were Pascal. outputting text to the screen used PRINT and so on. The BIG advantage that programming the hardware like this was FAST . 10 call_oz(GN_M16) ld b.took many lines of code. the commands were still obscure and looking at it you had very little idea exactly what it did. Forth.call_oz(GN_Gdn) ld d. though things start to alter when you get to the commands which make use of the hardware differences between machines . 6502. One of the building blocks in any higher level language is variables. Over time. other programming languages appeared which removed the low-level commands like LD... 68000 or one of many others . though later on.either a numeric value or an alphanumeric string of characters.each having their own areas of expertise. compilers appeared which sped things up a bit. It was easy to learn but slow as it was interpreted (not compiled into machine code).and all you had at your disposal were simple instructions like loading values into registers. as you can see from the above snippet of Z80 assembler. what is a variable? Well.c ld hl. This was known as a 'Low-Level Language' as you had to get down to talking directly to the hardware. you had to learn the version of ASM for the CPU in your machine. versions of BASIC running on a PC and a Mac will both have unique commands.h ld c. Most importantly it's 'nearly English' syntax meant you could read the majority of BASIC code and know more or less what it was doing straight away. A program which uses these building blocks can be run on practically any machine. Take the simple example: A=100 Here we are creating a variable called A and giving it the value 100.b ld e. the high-level instructions were compiled into low-level machine code that the computer could use.
so it may NOT equal 100! For example think about these two (admittedly pointless) lines in a DB program: A=10 A=100 On the second line. It then puts 100 into the box. the computer goes through all of its boxes until it finds the one with 'A' on the label and gets the value out to use.The computer creates a cardboard box in memory.where the answer to a question is always either yes (1) or no (0). in BASIC you can say: IF A=100 …which will return a 0 (false) or a 1 (true) depending on whether or not A does equal 100 or not. This may sound very confusing at first. so A=100 is a false statement as A actually doesn't equal 100 . sticks a label on the front of the box and writes 'A' on the label. In fact it would be more precise to read 'A=100' as A becomes equal to 100 rather than A equals 100 as in computer terms 'A=100' may not be true. Computers never say 'I don't know' or 'maybe' in these cases! 5 . A already equals 10 (as defined on the first line). B=50 OK. The variable 'A' can change. This is different to the way you do it in conventional maths.it equals 10! If the first line also said A=100. You should now see the reason for reading it as 'A becomes equal to' rather than just 'A equals'. You will notice that BASIC uses the syntax of 'the bit on the left side of the equals sign ends up containing the results of everything on the right side'. the computer gets the 100 out of box A and the 50 out of box B and adds them together. From this moment on. whenever your program refers to 'A'. but later on it will make more sense when you will find out about BASIC's IF statement which is used to do logical comparisons using boolean logic . then the second line would be true as A does indeed equal 100 at that point. Easy eh? C=A+B What does C equal? If you thought AB then you are thinking in algebra terms and it's not the same! Like before. For example. so be aware of this. putting the resulting 150 into a new box it creates and labels 'C'. another box only with B on the label and 50 stored inside it.
Loop etc). Eg: Time Left is a no no.never a number. so you need to tell DB what type of value it is you are storing. One sets the variable's contents to 100 and the other compares the contents of A with 100 to see if they are the same.09.33 You also have to be careful not to accidentally mix the two types of variable as you can end up with hard to track errors. This effectively tells DB to make a bigger cardboard box to hold the decimal point and numbers after the decimal point.45 or 1000.. Setting the variable A to equal 100 in Pascal is done with A:=100 with the colon meaning 'becomes'. the following examples are correct: Age=21 Apples=8 Height#=137. not quite. A line which says: Score=Score+100 …makes more sense than X=X+100 as the variable name tells you what you are adding 100 to! X could mean anything. As an aside.373.Z character . Integer and real variables need different amounts of memory to store values. if you are storing a value which represents the players score then use a variable called 'Score'. 3. these two tasks have a different syntax to avoid this confusion. you don't have to declare variables prior to using them. Variable Names: The only rules you should apply are: 1. So. 328. For example. IF A=100 in Pascal is exactly the same as BASIC (without the colon). Even a variable name like ABC123DEF is acceptable! Don't use spaces or any special symbols in variable names other than the _ (underscore) character. So. Do. in the programming language Pascal. In DB there are two types of number . so make use of the ability and use a name which makes sense. and if you don't specify that they contain anything. OK. This can be used to separate words to make them more readable.z or A. or whole numbers like 10. You can use any combination of characters and numbers in a variable name as long as you follow rule 2 above. Don't use reserved words as a variable names (words used as BASIC commands like Print. A number can be placed on the end or in the middle of the variable name if you wish though.Integers. As such it does actually read A becomes equal to 100. 2. Always start a variable name with an a. it is important to have firmly in your mind the difference between the two. numeric variables automatically contain 0 (zero). You do this with the variable name. Time_Left is OK.As the A=100 is the same as what we have just covered. unlike some programming languages. For example: 6 . though you can use variable names of which only a part is a reserved word. Sprite is a reserved word and not recommended as a variable name. that's numeric variables… or is it? Well. 123 or 1000 and Reals (or floats floating point fractional numbers). The first time you refer to them in your program they exist.. but MySprite would be OK. Variables which need to store real numbers are identified by putting the # symbol on the end of the variable name. like 1. Also.45 ObjAngleX#=120.
each with an index number with which to access them. you can also have string variables which are pretty much the same.not a numeric variable." Name$="Fred Bloggs" Age$="21" FaveHobby$="5-A-Side Football" …are all legitimate string declarations.NOT 42 as it's a string variable . Arrays: Arrays are very useful. Name3$ all the way up to Name100$ . Arrays can be single or multi-dimensioned so let's take a look at single dimensioned arrays first… An example: Say you were writing a simple game where each person typed in their name and the program stored it.23 B#=10. note that in the third example Age$ may equal 21. I think you can see where we are going with this… If you had 100 people playing this game.37 you would be wrong.and the same for the variable Score. A# and B# are added together to get the correct result of 110.. being alphanumeric strings can also be numbers so beware as numbers in strings are still strings and cannot be treated as if they were number variables! String variables are defined by putting a $ sign on the end of a variable name and their contents have to be enclosed in double quote symbols to clearly define where the string starts and ends. This can be a single character. But. For example: A$="The cat sat on the mat..37 bit is lost. so the . Arrays are a nightmare to new programmers if you are not! Arrays are nothing more than groups of normal variables. The main difference is that arrays have to be dimensioned before you use them as DB has to make sure that it builds enough cardboard boxes (to continue the analogy). The variable Name$ could hold their name and the variable Score could hold their best score. (The result is comparable to the DB function INT() in DB).Z 0. Use the same variables for player number two and player number one's name and score are lost! So. so beware! Strings: As well as numeric variables. that's only good enough for just one single player. a word or a complete sentence up to 255 characters long. but hold alphanumeric characters (a.14 C=A#+B# What is C equal to this time? If you said 110.9) rather than numbers. This effect is known as variable casting in some programming languages. Name2$. But. Arrays are easy if you are shown how they work in the right way. but when learning. we would need Name1$. 7 . but the result is placed into C which is an integer variable. we use Name1$ and Name2$ for the names along with Score1 and Score2 for the scores. If you said 110 then you would be correct.37.z A. This is done with the DIM command. but adding the variable to itself would equal 2121 .. And. the chances are that you do not.A#=100. along with their best score. Sometimes you may want to do this.
if you wanted an array of say 10 across and 5 down you would be better off using DIM ArrayName(10.. The Score array would look something like this: Each of the 100 variables is accessed by using its index number. though you also have number 0) and the second. the line in your program which increments the score would have to be repeated 100 times . Using: DIM Name$(100) DIM Score(100) .Also. What about Multi-Dimensioned arrays? Well. Memory is allocated in a continuous block large enough to store the requested number of variables stated in the DIM statement.the number of boxes across and the number down. Arrays rid you of this hassle. they are basically the same.. Dimensioning an array like this just needs you to use two values in the DIM statement .4) However.once for each player. the first allowing you to store 100 strings of up to 255 characters (1 to 100. you would use: DIM Location(4. while learning.will create two arrays. but instead of the array being one single line from 0 to 100. Doing this. 100 numbers. They are best thought of as being like the old-fashioned pigeon holes you find in schools (or a wall covered with lockers) where there are a number of boxes running across and down. This is a 1 dimensional array. When you realise that the actual number can be replaced by a numeric variable it makes it possible to say things like Name$(CurrentPlayer) and Score(CurrentPlayer) in your programs. For example. Score(1)=2000 Score(2)=2500 Score(3)=2100 …would be the respective scores.5) and completely ignoring the 0 element until you get the hang of things. Bearing in mind that array indexes start at 0. This way you can have: Name$(1)="John" Name$(2)="Dave" Name$(99)="Pete" And so on. to get a numeric array grid of 25 variables (5 across and 5 down) called Location. So that's single dimensioned arrays. for our 5x5 integer array we would therefore use: 8 . they are created in a 2 dimensional grid format. Score(12) would belong to player Name$(12) and so on.
the three values correspond to the X. 9 .it's just a waste of memory if you don't. you can take the dimensioning one step further by creating 3 dimensional arrays. In the above diagram. this isn't really a problem.YLoc). However. Remember you don't have to use the 0 part of an array .using the indices 0 to 3. then the array is stacked four deep . you access the contents using Location(1. This would be akin to having a block of variables in a 3D Rubiks cube structure and storing a value in each of the small boxes that make one up. Y and Z axis of the array and this will create a block of variables as shown in the image on the right.3) etc.DIM Location(5.1) or Location(1. As you can see. With small arrays.2)=10. each one 4 across and 4 down and to set the variable highlighted in red to the value 10.0 based arrays or 1 based arrays . you would use CubeArray(1. 1 along the X axis.3.2)=200. 3 along the Y axis and 2 along the Z axis. This would be done with: Dim CubeArray(3. the 'top slice' is just like the 2 dimensional array discussed in the previous section.just use the one you understand and are happiest with. the box with the 200 in it would be set with Location(3.3) In this example. the more memory is wasted. as the DIM statement has a further Z value. though the larger the array. In other words. Finally to complicate things even further. you can replace the numbers with variables and use something like Location(XLoc. Once again. The first element in brackets is the index across the array (the 'X' value) and the second element is the index down the array (the 'Y' value). This gives you 4 variable grids.5) It doesn't matter which method you use .3. When created. in your programs.
For example: Print "Please Enter Your Name: ". In essence. subtract. F#. you use variables to store things that can vary in your programs or when you want to store information that isn't available when you write the program. In a program. Therefore.which just happens to be one of the topics in the next tutorial! Operators: Variables aren't much use if you can't do anything with them. the PRINT command will print 'Hello' followed by the contents of Name$." This example asks the user to enter a number for a temperature in Centigrade (Celsius) and stores the value in a variable called C. When your program reaches this line. Input Name$ CLS Print "Hello ". * and / respectively." degrees Fahrenheit. Some of these options can be applied to numeric variables. Another example: Print "Please Enter Temperature In Centigrade: ". some to strings and some to both. However. Whatever they type will be stored in the string variable Name$ when they press the Enter key. I doubt if you'll need to use an array like this for the foreseeable future… What You Can Do With Variables: It's difficult to start giving programming examples this early in a series of tutorials as you will probably not be familiar with the commands being used so I'll try to keep the examples as simple as possible. these programs run just once and then end.8*C+32 Print C. CLS will clear the screen and on the last line. Maths Operators: Numeric variables wouldn't be very useful if you couldn't use them with basic maths in your programs so the basic operators add. Notice that this is a real number variable as the formula includes the value 1. (whatever name they typed in). The last line prints out the result in a formatted sentence. that would equate to: 10 . it will stop for the user to type their name in. The next line uses the variable C in a formula to convert the temperature to Fahrenheit and stores the result in the variable F#. On the next line. so a number of options are available to you for this task. adding together two numeric variables is just as easy as adding two numbers.' onto the screen. You have to re-run them every time you want to use them again. Input C F# = 1." degrees centigrade equals ". -. The next line uses BASIC's INPUT command. What we want is for the programs to keep working until we tell them to stop. That however is down to program layout and structure . For example to add 30 and 50 normally we would put 30+50 and the answer would be 80.8 which means that the result may be a real number too.two very simple programs which demonstrate the use of variables.Name$ This simple program will print the message 'Please Enter Your Name. There you have it . multiply and divide are represented by +.But.
THEN that is possible.. 11 . programs would be impossible to write as there would be no way to make decisions. You can force DB to do this by use of the '(' and ')' symbols (parentheses).that sort of thing.. < (less than)..A=30 B=50 C=A+B Print C ... >= (greater than or equals) and <> (does not equal) symbols.. However this brings into play a very important aspect of maths when programming . > (greater than)... With relational operators and IF. This is best explained with the following example so type it in and run it.and when C is printed to the screen you see the number 80 printed. let's have a quick rundown on IF.. The reason is that Dark BASIC (like all programming languages) carries out maths in a specific order. In a nutshell.THEN Without IF.. So.you may want it to be done in the order that results in 27.. Anything enclosed in parentheses is given a higher order of precedence and will be calculated before anything else. So why does it print 13?. followed by addition and subtraction (+ and -). <= (less than or equals). Now this may not be what you want ..will force A to be added to B first before the result is multiplied by C. we need in our programs a method to make decisions. A=7 B=2 C=3 D=A+B*C Print D What is printed? It should be 27 right? 7+2 equals 9 then the 9 is multiplied by 3 to make 27. Multiply and divide (* and /) calculations are done first.THEN first: IF.. Relational Operators: These are used to compare data items (numeric and strings) and use the = (equals).. So..Order Of Precedence. our little calculation above is carried out by multiplying B and C together first to get 6 and then adding A which makes a total of 13.THEN statements. Each one returns true (1) or false (0) and we touched on the subject earlier in the tutorial when we looked at IF A=100. More complicated formulas can also be built up with a combination of the four basic operators such as A=M*V/X+Z. Do something only if another thing has already been done or act on a users input . So: D=(A+B)*C .. The basic form is as follows: IF condition Do stuff here only if the condition is met ENDIF or.
Returns True (1) as 10 does indeed equal 10. 9 <= 9 . Eg: 2 <= 10 .Returns True (1) as 2 is less than 10. the condition is deemed to have been met if the condition is tested True (returns a 1) and the relevant code is executed. 0 if it is not. 9 < 9 ..Returns False (0) as 2 is not greater than 10. Greater Than ( > ) Checks to see if the first item is greater than the second. 10 = 11 . Greater Than or Equals (>=) 12 . 1 is returned if they are. 9 > 9 . on with the description of relational operators.. The second example is used if you want to do one thing if a single condition is met and something else if it is not. 0 if it is not. The condition might be as simple as checking to see if a variable equals a certain value or the user has clicked a mouse button. 0 if it is not.IF condition Do stuff here only if the condition is met ELSE Do this stuff only if the condition is NOT met ENDIF In both above examples. 7 < 5 .Returns True (1) as 9 is less than or equals 9.Returns False (0) as 7 is not less than or equals 5. Eg: 2 > 10 .THEN statements so instead. Less Than or Equals (<=) Checks to see if the first item is less than or equals the second.Returns True (1) as 7 is greater than 5. Less Than ( < ) Checks to see if the first item is less than the second.THEN you don't actually get to see the 0 or the 1.Returns False (0) as 10 does not equal 11.Returns False (0) as 7 is not less than 5. 1 is returned if it is. It is taken for granted that the following examples are used with variables as conditions in IF.Returns False (0) as 9 is not less than 9. If the variable contained ANY other value the code between the ELSE and the ENDIF is executed. 0 if they are not. Eg: 10 = 10 .Returns False (0) as 9 is not greater than 9. I'll show examples with proper numbers along with what they would return. 1 is returned if it is. but that's what DB returns and acts on! The first example is used if you want to carry out the enclosed code ONLY if a single condition is met . If the condition was that a certain variable equalled say 10 then if it did equal 10 the block of code between IF and ELSE would be executed. 7 > 5 . Eg: 2 < 10 . OK. Equals (=) Checks to see if two items are equal.Returns True (1) as 2 is less than or equals 10. 7 <= 5 . Using IF.otherwise nothing is done. 1 is returned if it is. that out of the way..
. 1 is returned if it is not. Try again.Returns True (1) as 2 does not equal 10.Returns False (0) as 9 does equal 9.Until loop repeats until Y or N key is pressed I$=Upper$(Inkey$()): Rem Read the keyboard for keypresses Until I$="Y" or I$="N" If I$="Y": Rem If Y was pressed Goto Start: Rem Jump to Start label at beginning of program Else CLS: Rem Clear the screen Print "Goodbye. 1 is returned if it is. try changing it so that: 13 .Guess Print If Guess < MyNumber: Rem If chosen number is lower than computer's number Print "Your guess was too low. If you like..Returns False (0) as 2 is not greater than or equals 10. 9 >= 9 .Checks to see if the first item is greater than or equals the second. Does Not Equal ( <> ) Checks to see if the first item is not equal to the second. See how quickly you can guess it!" Print Input "What is your guess? ". I've commented the program and they will be covered eventually. So. 9 <> 9 . Randomize Timer(): Rem Initialise the random number generator Start: MyNumber=Rnd(99)+1: Rem Select a random number between 1 and 100 Do: Rem Main Program Loop CLS: Rem Clear the screen Print "I have thought of a number between 1 and 100.Returns True (1) as 7 is greater than or equals 5. a VERY basic little number game as an example of using what we have learnt in this tutorial. 0 if it is not. If you like. to finish. Well done!!" Print Print "Do you want to play again (Y/N)?" Repeat: Rem Repeat.Returns True (1) as 9 is greater than or equals 9. 0 if it is. look those up in the DB help files by pressing F1 when in the DB editor.": Rem Print Goodbye message End: Rem End the program Endif Endif Loop Further Practice: Feel free to alter the above program to see what it does.." Sleep 2000 Endif If Guess > MyNumber: Rem If chosen number is higher than computer's number Print "Your guess was too high.. Don't worry if there are any commands that haven't been covered in the tutorial." Sleep 2000 Endif If Guess = MyNumber: Rem If chosen number is equal to computer's number Print "Your guess was correct. 7 >= 5 . Eg: 2 <> 10 . Try again. Eg: 2 >= 10 .
Guess Inc GuessCount Print If Guess < MyNumber: Rem If chosen number is lower than computer's number Print "Your guess was too low ". A working amended version of the program will be found in Part 2 of this series of tutorials so if you want to try to do it yourself. then well done. then no problem.Name$. how did you get on with the programming task I set at the end of Part 1? If you got a working program. so if you did it differently. If you alter the program to do these three things and it works. Try again.Name$." When the user guesses correctly the program tells them how many guesses they took to do it. Well done ". 2. Structure And Style Before I start with the tutorial. Who knows. See how quickly you can guess it!" Print Input "What is your guess? ".guess-wise. --ooOOoo— Part 2 . 3.it's just the way I decided to do it. then consider yourself having passed the test. Try again.Name$. In a program this minor.". here's my solution to the task… Randomize Timer(): Rem Initialise the random number generator Input "Please Enter Your Name: ". When the player exits the game the program tells them how many games they have played along with their quickest and slowest games ." Sleep 2000 Endif If Guess > MyNumber: Rem If chosen number is higher than computer's number Print "Your guess was too high ". The program asks for the players name at the start and uses the name in the in-game comments.Layout." Sleep 2000 Endif If Guess = MyNumber: Rem If chosen number is equal to computer's number If GuessCount < Best Then Best = GuessCount If GuessCount > Worst Then Worst = GuessCount Print "Your guess was correct.1. Below is how I would have done it. Eg: "Your guess was too high Peter. The method I have used is not meant to be the best or definitive way to do it .Name$ GamesPlayed=1: Best=1000: Worst=0: Rem Start variables Start: MyNumber=Rnd(99)+1: Rem Select a random number between 1 and 100 GuessCount=0: Rem Set guesses at zero at the start of each game Do: Rem Main Program Loop CLS: Rem Clear the screen Print "I have thought of a number between 1 and 100.".some ways are just better than others. there is little difference between any of the methods you use. Try again. there may be reasons why your method is better than mine! Anyway."!!" 14 . There is no right or wrong way to do these three tasks . don't look until you have attempted it.
" degrees centigrade equals ". so now on with Part 2 of the tutorial series… If you remember at the end of the last tutorial I gave you two small examples of Dark BASIC code to demonstrate the use of variables. notice that the variables Best and Worst are initialised at 1000 and 0 respectively which seems odd." Print Print "Goodbye.. Whenever you are recording the highest and lowest of something you should always start the variables off at the opposite end of the scale. In subsequent games. It's like reading a book.. but before we do it's important to know why it ends so abruptly. At any time during the execution of your program. then Best is replaced with that number. Best is replaced by the number of guesses ONLY if it is less that the number already stored in the variable Best.. F. the 0 in the variable Worst is replaced by the number of guesses made and subsequent games will only be replaced again if more guesses are taken. That way.Best. It then carries on with the next line in sequence continuing until there are no more lines to execute or it reaches the command END.GamesPlayed.Until loop repeats until Y or N key is pressed I$=Upper$(Inkey$()): Rem Read the keyboard for keypresses Until I$="Y" or I$="N" If I$="Y": Rem If Y was pressed Inc GamesPlayed: Rem keep count of the number of games played Goto Start: Rem Jump to Start label at beginning of program Else CLS: Rem Clear the screen Print "This session. First of all. Input C F = 1. When all DB programs are run. Let's remind ourselves of the last example program from tutorial 1: Print "Please Enter Temperature In Centigrade: ". the computer executes each line of instructions starting with the very first line." guesses and your worst was "." guesses. at the same time.Print "That time you took ".": Rem Print Goodbye message End: Rem End the program Endif Endif Loop Only a couple of things worth mentioning." games. the program ends. Here. You start with the first line on page one and when you have read the last line on the last page the book is finished. it drops down to the next one and continues." Print Print "Do you want to play again (Y/N)?" Repeat: Rem Repeat. lower number guesses are best.. something called the Program Counter keeps track of the current line and unless the instruction on it tells the program counter to jump to a different line." Print "Your best attempt was ". the least number of guesses will be stored in Best and the most in Worst..GuessCount. By the end of the game.Worst.8*C+32 Print C. pointing out that they weren't much use as they had to be re-run each time you wanted to use them." guesses. you played ". OK.. This is particularly relevant with the second example as it would be better if we could keep re-entering different temperatures and only end the program when we are finished with it. Worst works in the same way only in reverse. When there are no more lines. so we start the variable Best off at 1000. The first game played. when the player has finished the first game and has taken less than 1000 guesses (pretty likely)." 15 . This is actually quite simple to do." degrees Fahrenheit.
' Obviously. to make life easier. making it very difficult to follow when your program grows in size.all of which could be used to print our lines. So. As you can imagine. the next line calculates the result and on the final line.one for each time you want the sentence to appear: Print "This is printed three times. There are however commands which you can use to control the order in which the lines of instructions are carried out. skipping all lines in between. In theory you might never finish the book . Loops Sometimes. the computer stops and waits for input on the next line.Here. The 'This will never be printed!' message never appears because on the second line. Here's an example: Print "Program started…" GOTO Label1 Print "This will never be printed!" Label1: Print "This line appears!" If you enter this program in and run it. When the user enters a value and presses the Enter key. you want your code to do something a certain number of times and repeating the lines isn't a viable solution." Print "This is printed three times. the result is printed to the screen.and that's what keeps a computer program from ending. As there are no more lines. For N=1 To 20 Print "This is printed twenty times. you will see that the 'Program started' message is printed to the screen followed by the 'This line appears!' message. There are many types of loop ." Print "This is printed three times. but for this example the best one to use is the For…Next loop because we know how many times we want to print the sentence. this can be done with three lines . GOTO One such command is GOTO which tells the program counter to jump to a specific location in the program. A label is a single word ending with a colon. imagine if at the bottom of page 1 it said 'continues on page 96'. This is the crudest method of controlling program execution and should be avoided whenever possible as it is possible to create what is known as 'spaghetti code' where control leaps around your program. You turn to page 96 and continue reading only to find at the bottom of page 96 that it says 'continues on page 12' and so on. the program counter is told to jump to line four." But. you may want to print three times." Next N 16 . This can be a point anywhere in the program but has to be a defined label. The rules for naming labels are the same as for variables. this would involve a lot of typing. what about printing something 20 or even 100 times. skipping line three. Our example above doesn't use any of them so the program just zips through all the lines in order and then ends. Continuing with our book analogy. As a silly example. the message asking you to enter a value is printed to the screen. at this point the program has nothing more to do so it ends. this is where control loops come in. the sentence 'This is printed three times.
The next time it will be 21 and so on up to 91. 20 and so on). This is simply done by making the start number higher than the end number and using Step -1: For N=20 to 1 step -1 will count from 20 down to 1. This continues until the value of the loop counting variable N matches the end value. there is the ability to count backwards. When the Next N line is reached. 17 . Note: The Step -1 is required for counting backwards . This makes it safe to use the counting variable for other things inside the loop. but the next time around the loop. the above loop will print the text message to the screen 20 times. If it is less than the end value then the program counter jumps back up to the line number recorded earlier and the loop is repeated. the variable N is set to the first value (1) and the line number is recorded. For…Next loops can be used to count from any number to any other number . 10. Finally. Obviously you can have as many lines between the For and the Next lines . The first time the For N= line is encountered. In the process. 20. The counter variable always equals the whole range of values from the declared start and end range inclusive (unless Step is used). so when it can't add any more the loop is ended. 10 will be added making N equal 11. at which point the loop is ended and program control drops down to the line following the Next N line. On the next line.This loop uses a variable to count from the first supplied number to the last . This is because adding another 10 to 91 would take it over 100 the stated end value. For example: For N=1 to 100 Step 10 will start with N equalling 1. the value of N is incremented and compared with the end value (20).you don't have to start at 1. Remember this as it might save you a few headaches in the future! Hint: If you wanted it to count 10. You don't even have to count in increments of 1 either as there is an optional STEP parameter that can be used on the end. This provides the increment for the counting loop (which defaults to 1 when Step is not used). Notice that the highest value it reaches is 91. 30 etc up to 100 you would use For N=10 to 100 step 10.even if you are counting down 1 number at a time.they will all be carried out the stated number of times.in this case from 1 to 20 using the counter variable N. You can use any variable name for the counter variable as long as the name used on the Next line and the For line are the same. The default when not using Step is positive 1 regardless of the start and end values! Usage Notes: This type of loop is ALWAYS carried out the stated number of times (unless a run-time error occurs inside the loop). the text is printed. (Starting at 0 instead of 10 would give you 0.
Unlike the For…Next loop. Usage Notes: As the counter starts at 1 and the counting variable is incremented at the end of the loop (after the Print line). Other Loops: As mentioned previously. so let's take a look at them: Do…Loop The Do…Loop is the most basic of the loops in DB and has no conditions so isn't normally exited from . Program control will go around one of these loops forever so it is primarily used to create an enclosed main program loop in your program.after which the loop is immediately exited and the Print line not executed a 21st time. N2. That's why the exit condition variable has to be equal to 21 . Let's see an example based on the above For…Next loop example: Counter=1 Repeat Print "This is printed twenty times.regardless of whether the condition is true or false before entering the loop. This is covered later on. For now. Repeat…Until The Repeat…Until loop is exited from only when the condition on the Until line is met. A condition is basically a test which will return 0 (false) or 1 (true). the variable will be incremented to 21 after the line has been printed 20 times. do not use names which have been used elsewhere in your program. The program counter will go around in a loop forever if Counter never equals 21 so that's why we set the variable to 1 before entering the loop and increment the variable each time we go around the loop with Inc Counter. N1. As a tip. 18 .you would usually exit the program with the END command without leaving the loop. the lines in this loop will always be carried out a minimum of once . somewhere in this loop variables must be altered in order for the condition to be met. For this reason. As this loop is repeated until a specified condition is met and the condition is tested for at the end. In this case the condition for exiting the loop is that the variable Counter must equal 21.In the process of a For…Next loop. it's enough to know that we would not use this type of loop to do the above example task. the value of the variable used as a counter will change. there are other types of loop and printing a message a number of times could be done with any of them. (Inc is short for increment and adds 1 to the named variable). N3 etc ONLY as For…Next counting variables and do not use them anywhere else in my programs. you can see the condition on the end." Inc Counter Until Counter=21 In this example. I use N.
its main use is to stop the program from ending until we are ready.. but before we cover any more BASIC commands and get too bogged down with them. while you are a newcomer.. As the condition is tested at the beginning of the loop. OK. The final program might look like this: Do Print "Please Enter Temperature In Centigrade: ".unlike Repeat…Until which has to carry out the code once before the condition is tested for at the end. already having Repeat…Until..Loop is never actually exited from . there is one subtle but significant difference. 19 . All we need to do is add a main Do…Loop enclosing the existing code and add a condition to allow the user to exit. as such. It's important to realise that this loop is not entered at all if the specified condition is not met at the start of the loop. So. that's loops covered. it's best to get into the habit of writing your programs neatly now." degrees Fahrenheit. Counter=1 While Counter<21 Print "This is printed twenty times.While…EndWhile This loop is essentially the Repeat…Until loop with the condition at the start of the loop instead of the end. Input C F = 1.. has Goto's everywhere leaping all over the place and has no structure." Print Print "Convert Another Temperature (Y/N)?" Repeat I$=Upper$(Inkey$()) Until I$="Y" Or I$="N" If I$="N" Then End CLS Loop Notice that as mentioned previously. the lines inside this loop may never be carried out at all. Usage Notes: The code in the loop is only executed if the counter variable is less than 21. This is done with the End command when the user says no to converting another temperature.stopping our little temperature program ending until we want it to. the main Do. F.8*C+32 Print C. back to the original theme ." Inc Counter EndWhile While even having this type of loop may not seem worth the bother. It doesn't help if it's your own code either! So. let's take a look at how best to put them into a program that will work with optimum performance and still be legible." degrees centigrade equals ". Program Layout The worst thing you can have to do is try and fix a fault in a program which has been poorly laid out. if the condition fails then none of the code is executed .
these can be difficult to find . Too many isn't usually a problem.. One of the more common errors in programs by new programmers is failing to close loops.. but your friend can still read it and understand what you mean. Your program may have 20 Repeat lines but only 19 Until lines and once you have a decent sized program. You may have noticed that all of the examples in the tutorials so far all use indentation . a computer even these days is nowhere near as powerful as the human brain and has no way to decipher what you meant when you put ForA = 1 To 10 or put Repet instead of Repeat.Writing programs in DB or any other language is finicky . This means that you have to type everything EXACTLY as it is supposed to be .right down to every full stop.if you don't use indentation.even though it sometimes can't tell you exactly what is wrong with it! Indentation: Few people indent their code. all your speeling mystaykes make your text look bad.the code inside loops is offset from the rest of the code. Indenting makes your code a LOT easier to follow and thus bugs are easier to find . I always increase the indentation of the code inside every loop by two spaces and drop back to the left at the end of the loop. Spaces too are also important. DB will stop you running a faulty program and highlight the iffy line . but I find that with a few nested loops (loops within loops) all but the shortest lines of code are off the right side of the screen! I have always indented Open File lines too as they have to be closed and are similar to loops in that respect. As a rule of thumb. but leave one out when it should be there and your program line will not be recognised. This is because of something called syntax. Some people use three spaces. comma or semi-colon being in the right place.especially when you have more than a couple of hundred lines. An indentation example (not real code): Line 1 Line 2 Do This is indented by 2 spaces So is this For A=1 To 10 Indented y 2 more Ditto Next A: Rem This drops back 2 as the For Next is closed More lines Repeat For B=1 To 5 Indented 2 more So is this Next B Until Z=5 Nearly finished Loop: Rem Back to the left edge End 20 .as no doubt you have already discovered. It takes time and doesn't make your programs run any faster so why bother? And what is indentation anyway? Indentation is the offsetting of lines of code in the editor. Luckily. Unfortunately. When you write a paragraph of text in a word processor for a friend to read.
Having now covered subroutines. indentation makes all the loops stand out so missing Next. Finally. this isn't too far from the truth. Layout: As well as indentation. Subroutines start with a label and end with the line Return. So what does this involve? First of all.As you can see. 21 . On return from a subroutine. six months to get it working properly and forever to get rid of all the bugs". but program development time and more importantly error tracking time can be reduced dramatically. you now know enough to continue with our list above (if you were wondering why I didn't start with item 1 . you would put the ten lines into a subroutine and use a single line of code to call the subroutine whenever you needed the ten lines printed. your programs should have the following format: * Initialisation * Game Menu * Main Program Do…Loop * End Of Program * Subroutines * Functions What Are Subroutines (sometimes called Procedures): These are vital to keeping your programs running smoothly. the Return bit at the end knows where to jump back to after the code in the subroutine has been executed. rather than have thirty lines of code. The code in a subroutine is something that can be called many times in your program . Until and EndWhile lines are easier to pick up. For this reason. all variables in your main Do…Loop are available in a subroutine and any alterations made to them in the subroutine are seen by your main program.Initialisation). you should never exit out of a subroutine by using GOTO. Alternatively a subroutine can simply be code that you may only need once. The command GOSUB is used to call the subroutine and as the calling line is stored. Once again. I have always said to people who have asked me how long it would take to write an application for them. it is still classed as part of your main program. though calling another subroutine from a subroutine is OK as it too will return automatically. Although a joke. a proper layout for your programs will make them smaller and more efficient. control is passed to the line immediately after the GOSUB line. You can write programs without them. If you needed to print ten lines of text in three different places in your program. but just want to keep in a separate location making your program tidier. it doesn't always make them run any quicker (though it can). you wouldn't dream of going back to being without them! Basically. but once you have used them. As such. Although a subroutine is a separate block of code to the code in the main program Do…Loop. so anything you can do to help along the last one is a bonus. "two months to write it.and hence prevents you having to retype the code each time you need it. you should also adopt a suitable layout for your programs. you can think of a subroutine as a little stand-alone program which you can call on at any time. Useless or repeated code can be avoided and modifications are made a lot easier.
.Until which is only exited if the user clicks on one of the Start Game buttons. More will be said about Sync later in the tutorials. maybe create a matrix. Placed on the line following the LOOP line. However. set the screen mode..they only exist in the editor! 22 . I put all of these into a subroutine called Setup. Inside the procedure you should have a conditional loop like Repeat.Initialisation At the start of your program. So. 2 Player Game. Remarks have no overhead on your programs as they are completely ignored when your program is compiled . Your program then only needs a single line which says GOSUB SETUP at the start to set everything up rather than having all that code cluttering up the start of your program. Each Sync takes time. Note: Rem is short for REMark and is purely a comment. Start Game and Exit . Some coders prefer to use Repeat. 1 Player Game. then the last line inside the loop should be a Sync. you can simply call this procedure again with Gosub to display the menu screen again. subroutines can be called and they will always return back to continue round the loop. Use them anywhere in your program to leave yourself 'notes' on what a section of code does or as a reminder to return later and alter something. you may want your program to display a menu screen with buttons for things like Options. Main Menu Next. putting it in doesn't do any harm . turn off the mouse if necessary and all those sort of tasks. you will want to control the screen output yourself and issuing a Sync updates all the 3D information on the screen. if program control ever reached this point.rather than just starting the game. I tend to put a Rem line immediately before each subroutine with a brief description of what it does..I do it out of habit… Subroutines This is the place in your programs where you put all your subroutines. for optimum speed. so one placed at the end of the main loop refreshes the screen with optimal performance. As you need to call this menu at the end of each game. At the end of any game. then it would crash through any code which follows something you don't want as it would cause an error if all your subroutines follow.it's a matter of personal preference. but for now all you need to know is that in most 3D programs. you need to initialise all the required variables. If you are writing a 3D program.Until for the main loop. load images and objects. it should never be executed as there is no way to get out of the Do…Loop to even reach it. When the time comes for it to end. one after another. it doesn't end until you want it to. From within the main program loop. Either will do . you should place it in a procedure. you can use the END statement inside a condition along the lines of 'if the user presses the X key then end the program'. Main Program Do…Loop Your main program should be enclosed in a Do…Loop so that when it runs. End Of Program This is simply the word END which stops your program from running.
an example of this function being called might be: NumVar=Pointless(10. Note however that only a single item of data can be returned from a function.So. the variable names would be irrelevant. The same goes for the return variable type too.A$) L=Len(A$) C=L*A+B EndFunction C OK. But. B and A$ respectively. Variable names inside functions can be the same as those outside the function but still be completely separate entities which can co-exist together. 23 . The calling line uses the function name and must include a list of parameters identical to the function header (if any parameters are used). That's one numeric variable or one string .just like subroutines. In the function. If we did."Hello"). This sounds quite complicated so let's see an example: Function Pointless(A. the function ends and passes the value stored in C back to the calling line. It calculates the length of the supplied string using the Len() function and stores it in the variable L.5. we have to use a variable to 'catch' what is returned . storing the result in the variable C. it multiplies the value of L with the value passed to it in A and adds the value in B. this aptly named function receives integer values which it places into the numeric variables A and B.even if you did write it yourself. So. Next comes the function name and the parameter list in parenthesis. this bit could be omitted making the calling line Pointless(10. Functions are for all intents and purposes the same as subroutines but with a couple of differences. but the variable types must match. but we could have used variables instead. Lots of comments will remind you of what does what and you'll be glad you added them.5. Functions end with the word ENDFUNCTION and an optional variable which is used to pass data back to the calling line. Next. The result (55) is returned by the EndFunction and is caught be the variable NumVar.no multiple variable lists like the entry parameters."Hello") As the function returns a value. a program with tons of comments in the editor will have exactly the same size compiled exe as the same program with none so USE THEM! Load a program written months ago and you'd be surprised how little you will recognise . Finally. Another important difference with functions is that they use local variables (see subject Scope below).B. The only restriction is that you cannot pass arrays to a function. L becomes 5 (the length of the word Hello). functions start not with a label but with the word FUNCTION followed by the function name and an optional parameter list in parenthesis which combined is called the function header. When the three items get to the function they are placed in A. We are passing proper numbers and a literal string here. Local variables are destroyed when exiting the function. Functions (In brief): I'm not going to cover functions in great depth here as there are more detailed tutorials on the subject available. and a single string which it puts into the string variable A$.hence the NumVar= but at the start. which is multiplied by A (10) and has B (5) added to it. They are blocks of code like subroutines and after being used return to the line after the one they were called from . If the function returned no value.
scope is where in your programs your variables can be seen. You don't have to 'Undim' as you would in the main program. earlier I said that in DB. when you enter a function. Global variables can be seen anywhere in your program. alter their contents inside the function and the alterations are intact in your main program when you exit the function! This gives us a very nifty way around the fact that you can only pass a single variable back from a function.including procedures but excluding functions. For the same reason you have the variable to use with EndFunction so you can pass the result from the function back to your main program.they cannot be seen outside of the function that they are used in. For example. Arrays declared inside a function however are still local and are destroyed on exit. It turns out that arrays declared using DIM in your main program are sort of global in so much as you can see them inside functions. Inside a function. Also. Now you don't need to as the array can be seen and modified inside the function anyway! And that's program layout covered.global variables. global variables don't officially exist.not 100! Likewise. They cannot be seen on return to the main program. so I left it until now to cover the subject. you can DIM an array at the start of a function and call it many times without getting an 'Array Already Dimensioned' error.layout example only): 24 . you can also have A=100 but you have to remember that this A is local and is an entirely separate A to the one outside the function . This sounds a bit obvious until you realise that Local variables are local only to functions . With arrays we have no need to pass more than one as we can use arrays. if in your main program you say A=10 then A can be seen throughout your program . Local Variables: DB Classic is slightly different to most programming languages as global variables don't officially exist. When you exit from the function. B does not exist. Our skeleton program layout should therefore look something like this (not real code . but there is a work-around you can use if necessary. Where you declare them affects where you can see them. This also conveniently gets around the limitation mentioned earlier that you cannot have an array in the parameter list you pass to a function. I didn't cover them in the variables section in Part 1 of this series as they are only of any relevance when you know what functions are. Well they don't. I think it would be best to spend a few moments covering local variables and the other type .Before we go any further. In many programming languages you have both Local and Global variables which are declared before you use them. the function's A variable is destroyed and you will find that the original A still contains 10 . Global Variables: OK. so you can't use it! That's why you have the parameter list in the function header so you can pass the variables you do need to the function. if in your main program you had B=100. This is what local variables are all about.in your main program. as they are destroyed when exiting the function. Scope In non-technical terms.
Z) Loop End Rem *********************** Rem List of subroutines start here Rem *********************** Rem This routine does something ThisRoutine: Do Whatever Return Rem This routine does something else ThatRoutine: Do Whatever Return Rem This routine displays the main menu Main_Menu: MenuExit=0 Repeat Display screen and buttons here If 1 Player Game button clicked Do whatever to set up single player game MenuExit=1 Endif If 2 Player Game button clicked Do whatever to set up 2 player game MenuExit=1 Endif Rem If Exit button clicked then End Until MenuExit=1 Return Rem This routine sets up the program Setup: Do Whatever Return Rem ********************* Rem List of functions start here Rem ********************* Function ReadKeyboard(X.Y.Gosub Setup Gosub Main_Menu Do Rem Lines of main program here Gosub ThisRoutine Gosub ThatRoutine If game ends then Gosub Main_Menu K=ReadKeyboard(X.especially if it doesn't work when you hit that F5 key… When you add another feature to your program. you simply put it in another subroutine in the subroutine 25 .Z) Do something with the keyboard here EndFunction F If you use this sort of layout. you should find programming a lot more fun than if you just bang out the code all over the place .Y.
A number of commands are available for both text and graphics . so we need to keep things simple. --ooOOoo— Part 3 . If you look at a toddler’s first reading books. That's it for Part 2 of the Programming For Beginners tutorial series.some of which do the same thing.just Janet and John stuff. we are just learning how to program. you won't find any Chaucer or Shakespeare . This means that X runs from 0 to 639. but with subtle differences.section and add the calling line into the main program loop. it's worth mentioning that the order in which the commands are covered may seem strange. When you write any computer program. we will be looking at some of the elementary commands in Dark Basic . The maximum value for X depends on the current screen size. The Y value starts at 0 at the top of the screen and increases as you move down the screen. Screen Output A screen consists of a grid of dots . DB programs run in a screen size (resolution) of 640x480. The maximum value for Y also depends on the current screen size and in DB's default resolution of 640x480 Y will run from 0 to 479. CLS is a good example as you can use the RGB command as an additional parameter. This is because I have tried whenever possible to cover commands in such a way as to prevent the need to jump ahead in the text to look up something not already covered. The main thing is that a grasp of the simple commands in this tutorial will allow you to create complex but simple complete programs in Dark Basic. By default. you need a method of putting information onto the screen. unless you specifically change the screen size. For now. The X value starts at 0 on the left hand side of the screen and increases as you move right. The commands in this part of the series will all be 2D.initially all set to black. but in no particular order. RGB is covered first when really CLS (being one of the more basic commands) would have normally come before it. but 3D will be mentioned occasionally only if there is anything important to say. then you have no chance of understanding the Shakespeare stuff (3D) when we get to it! I'll try to group the commands in categories of similar types. DB's more advanced 2D and 3D commands will be covered later in the series. Each dot (or pixel) on the screen has a co-ordinate comprising of an X and Y value. So. You choose which to use depending on the circumstances. 26 . Finally.Elementary Commands In this part of the series.many of which will work in any version of Basic and are therefore 2D only. It's important to remember that if you don't get to grips with the Janet and John stuff in DB (Text & 2D). Doing this keeps the code in the main loop as short as possible and easier to follow.
Here are some other common colours as RGB commands: Red . The intensity of each of the colour components defines the colour of the dot produced. you get a black dot.The X and Y maximum values are always 1 less than the actual screen resolution and specifying a location on the screen is simply a case of providing the X and Y positions. The colour created can be applied to any screen output and the command must be issued again to switch to another colour.0) Blue . any desired colour can be reproduced. if you wanted full intensity green. Don't forget however that there may be many.255. whereas RGB(0. By varying the values for all three components. These days. The highest value for each component is 255 and with red.BitDepth.RGB(150. The colour is always selected before the command which draws to the screen . Green.not after. green blue. SET DISPLAY MODE This command is placed at the start of your program and allows you to specify the screen mode you want your program to run in and uses the syntax Set Display Mode Xrez.255.0. So.255) Yellow .16 [Edit] This tutorial was written a few years ago when most DB users worked in this resolution. One advantage of doing this is that the removal of a calculation makes your program run quicker albeit only by a miniscule amount.255) Magenta . green and blue all set to 255.50) or a number like 32742? 27 ..0. 1024x768 and so on and the BitDepth parameter is usually 16 or 32 to select the maximum number of colours available. these intensity values are converted to a colour number which you can also use rather than RGB if you want.Gval. The lowest intensity value for the three components is 0 (zero) and with red.255) Grey . What's easier to use for a colour: RGB(100. Personally.RGB(255.150.0) would produce a darker shade of green. However few people do use colour numbers as three RGB values are infinitely more user-friendly to use.RGB(0. many RGB calculations in your programs and all these tiny amounts soon add up.255.RGB(255.0. Blue and is the method DB uses to specify a colour. you would use RGB(0. green and blue all set to 0. The three intensity values are supplied in parenthesis in the order red.RGB(0. systems are a little better and 1024x768x32 (or even higher) is more common. All colours on a standard CRT monitor are created by red.0).255. 16 is the norm as some graphics cards will not run at 32 bit in some screen resolutions.RGB(255.0) Cyan . Anything already on the screen will remain the designated colour when RGB is used to select another colour. Most of the usual X/Y Windows modes are allowed.Yrez.. I tend to do everything with: Set Display Mode 800.Bval) RGB stands for Red.150) Internally.600.100. you get a white dot. green and blue guns which light up dots on the screen. such as 800x600. RGB(Rval.
(assuming you have not changed the current ink background colour). colour values or a combination of both.it's one less calculation for DB to carry out so it's faster.255).0) . colour values can be used with the Ink command. CLS clears the screen to black. The only exception is the colour black which is 0.RGB(255...0 will produce the same colour.255.0 . green and blue colour values? Well. RGBG() and RGBB() functions where you put the colour value into parenthesis: Red=RGBR(32742) Green=RGBG(32742) Blue=RGBB(32742) This will pull out the red.0. It has no effect on 3D screens. after which: Ink 32742. you now need to apply it. though you can also use an optional RGB 28 . If you have a screen with both 2D and 3D areas.255).BackgroundCol The two parameters can both be RGB values.0) Once again. but are seldom used. and Ink RGB(Red.Blue). If you have a red screen and want to print white text onto it. so setting either the foreground or background to black can be done with a 0 rather than RGB(0. then you would set the foreground text colour to white and the background colour to red with: Ink RGB(255. then CLS will only clear the 2D portion of the screen. INK Having the ability to define a colour. how do you turn it into red. This is done with the Ink command which uses the syntax: Ink ForegroundCol.0.Green. green and blue respectively. white text on a black screen would be done with: Ink RGB(255.255. If you imagine the capital letter A is produced by dots on a grid. Used on its own. RGBG() and RGBB() If however you do have a colour number like 32742.RGBR(). green and blue intensities from our example colour value of 32742 placing the values into the variables red. then the foreground colour is the colour of the dots which form the letter A and the background colour is the colour of the dots which form the unused part of the grid. So.0 CLS The CLS command clears the 2D screen. it's easy with the RGBR().
ready for the next print statement.0) for example will clear the screen to red. Following a Print statement with a .not the names of the variables used (A$ and NumVar). You can prevent this from happening (so you can print more on the end of the same line) by placing a . CLS RGB(255. is in the top left corner of the screen). Print "Rainbow" will result in: 29 . or variables.. You can use this to print a simple text message on the screen like "Press Any Key To Start". Text Commands: PRINT Print is the easiest way to get text onto the screen and can print literal strings enclosed in quotes. Print "The". on the end. Print "Somewhere" Print "Over" Print "The" Print "Rainbow" will make the following appear on the screen: Somewhere Over The Rainbow Whereas. The second and third examples both print the contents of variables .. the next Print statement will appear on the end of the line just printed.command (or colour number) to clear the screen to any other desired colour. (semi-colon) on the end of the Print line. After printing to the screen. Print "Over". Print "Somewhere". Output appears on the screen at the current cursor position. Some examples: Print "The cat sat on the mat" Print A$ Print NumVar The first example is a literal string. You can however position it anywhere you want. The screen output appears at the current cursor position (which after a CLS.0. the cursor position drops down to the start of the next line on the screen .
Print will print your text at the current cursor position. when combined with variables allows you to created formatted strings. Notice that everything is all joined together? That's because we didn't include a space after each word inside the quotes. SET CURSOR As mentioned in the Print command section. As you can see.you would just use: PRINT "Somewhere Over The Rainbow" Another useful variation of using PRINT is: Print "Somewhere ". Also worth noting is that you can also include variable formulas in Print statements too." wives. There are other ways to control what appears on screen when using strings.. Such as: A$="Henry VIII" WifeCount=6 Print "If ". but a character position.appearing." had married again he would have had ".Y command where X is the position across the screen and Y is the position down the screen." had ".10 is here!" Note: The X and Y referred to here is NOT a pixel position." This will print: If Henry VIII had married again he would have had 7 wives. so the above message does not print at 10 pixels across and 10 pixels down.10 Print "Cursor position 10."Rainbow" Which at first glance doesn't appear to be of much use. Set Cursor 10..SomewhereOverTheRainbow ." This will print onto the screen: Henry VIII had 6 wives. However."The ". This is done with the SET CURSOR X.A$.WifeCount. Don't worry too much about that as it's very unlikely you would ever use the Print statement like that anyway ." wives. the screen mode. Take for example: A$="Henry VIII" WifeCount=6 Print A$.WifeCount+1. The values you can use depend on how many characters will fit across and down the screen . you need to be able to position the cursor where you need it.e. 30 . but they are covered later in the 'More Strings' section. If however you want to print something somewhere else.i. this allows you to present personalised messages in your games."Over ".
any of the many string functions can be applied to it which couldn't when it was a numeric variable.where the parameters are the same as the normal Text command apart from X which is the screen X position at which the text is to be centred. A$+" had "+Str$(WifeCount)+" wives.Y." CENTER TEXT A variation on the Text command is the Center Text command which will . unlike the Print command. Our above Print example using the Text command to place the message 100 pixels across the screen and 100 pixels down the screen would look like this: A$="Henry VIII" WifeCount=6 Text 100. This is most commonly used for the Text command .print a message centred around a given X position on the screen. X is the position on screen at which the centre of your string will be positioned. as a string.100.why it is introduced here. The command syntax is: Center Text X. The output part is similar to when using the Print command so you can use literal strings or variable strings.. then for your message to be in the centre of the screen along the X axis you would use: Center Text 400. symbol.. Another difference is that when using Text to create formatted strings.. Also.you need to convert them to strings first using the Str$() function.Y.STR$() Str$() is a function which will convert a number or numeric variable into a string. but it's important to realise that it is the string 42 . In other words. the + symbol is used instead of the .not the number 42! Also.300. This is a valuable feature which will become more apparent later on."This is in the centre of the screen" The command does all the string length calculations for you and all the usual Text command formatting rules apply. TEXT This is a much improved version of the Print command and uses the format: TEXT X.Print just uses the default system font. The main difference is that you cannot use numeric variables with the Text command as you can with Print .Output$ .where X and Y are the desired pixel positions (not character positions) on screen and Output$ is the required information you want to appear.believe it or not . anything placed on the screen with Text is printed in the current font face and size ..Output$ . You must figure out the Y position yourself! 31 . An example: A=42 MeaningOfLife$=Str$(A) Print MeaningOfLife$ This will print '42' on the screen. If your screen is 800x600.
In effect.CentreY.and you simply supply the X and Y screen co-ordinates of the start and end of the line. ELLIPSE This command draws an ellipse in the current ink colour.EndY .CentreY.. The syntax is: CIRCLE CentreX.they just aren't called for often enough.Y will appear in the current Ink colour.the pixel at co-ordinate X.Radius . The syntax is ELLIPSE CentreX. As with all 2D graphics commands.and you simply supply the X and Y screen co-ordinates of the circle's centre along with the circle's radius in pixels.. Unlike some BASICs.Graphics Commands: As well as text output using the Print and Text commands..Bottom. Using the syntax Point(X. X and Y are the required pixel's X and Y co-ordinates..Top. G and B values to be of any real use.. so you do not have a substantial subset of such commands . The value returned is the colour value which needs to be converted to R.Radius1. The syntax is: LINE StartX.Radius2 and you simply supply the X and Y screen co-ordinates of the ellipse's centre along with the ellipse's X radius and Y radius in pixels. BOX This command creates a filled box and uses the syntax BOX Left. in DB you do NOT define the top left corner position then the box's width and height. the Box command uses the current Ink colour. Left and Top define the X and Y co-ordinates of the box's top left corner while Right and Bottom define the X and Y coordinates of the box's bottom right corner. you also have the ability to output basic pixel graphics. DB is aimed at the 3D games programmer.Y). Once again.. DOT This command lets you 'turn on' a single pixel on the screen and is basically a 'Plot' function.Right. 32 .Y . Using the syntax: DOT X.EndX.StartY. CIRCLE This command will draw a circle in the current ink colour. POINT() This function will return the colour number of a pixel on the screen. LINE This command will draw a line in the current ink colour.
then the text background colour is not displayed and the background screen colour shows through. there are a lot of useful string formatting functions which can be used .100. The message created with the Print command appears on the next line to the last printed message . The messages are printed again and this time the Text command does not show the red background colour.Non-Output Commands: The following commands do not send anything to the screen ."Text produced with the the Print command" Text command (Opaque)" the Print command" Text command (Transparent)" In this example.120. it appears on the screen as 0003250? The score is always 7 digits long even though the actual score is only 4 digits! The score is stored in a numeric variable and you can't add on the leading zeros. This is text produced with Text 100. Many can be used to present numeric information on the screen in a more tidy fashion.not Print. For example.they just alter the way things are sent. If this colour is not the same as the current screen colour then the text will appear in a coloured rectangle in the chosen Ink background colour. you convert the score from a number to a string. The text is then set to transparent and the colour scheme left unaltered. ever noticed in games where the player's score is say 3250. The text is set to opaque and a message is then printed to the screen using both Print and Text commands.RGB(150.0. You will also notice that the Text command has no effect on the screen cursor position.0) Set Text Opaque Print "1. Just one of the many things you can do with strings. When transparency is off then the current text background colour is shown. SET TEXT OPAQUE This command switches text transparency off and applies only to the Text command . So let's look at what we need in order to do the score thing.. This is text produced with Text 100. So.0.50) Ink RGB(255.. When transparency is on. SET TEXT TRANSPARENT This command switches text transparency on and applies only to the Text command.even though a Text command has placed a message in the middle of the screen since the Print command. Notice that only the message produced with the Text command has the visible background red colour whereas the Print command text is unaffected..255. More Strings: As mentioned earlier.255). use the string functions to add those zeros and print the resulting string onto the screen rather than the contents of the numeric variable.. the screen is cleared to a dark purple colour and the ink set to white foreground and red background. CLS RGB(30."Text produced with the Set Text Transparent Print "2.some of which we will cover now. Enter and run this example. 33 .
We can use this number to find out how many 0's to add to the front of the string... All well and good .unlike the Repeat. so therefore faster at doing the job. When you get to the Until ScoreLen=7 condition for continuing the loop. then we can't use a string function on it so we have to convert it to a string first with Str$() which we covered earlier.apart from one major problem.... it will not be 7 so the loop will continue adding 0's trying to reach 7 . what we have hit on is a prime example of when you have to choose your loops carefully. so we create a small program loop which will repeat the Len() function until the string is the required length: ScoreStr$=Str$(Score) Repeat ScoreStr$="0"+ScoreStr$ ScoreLen=Len(ScoreStr$) Until ScoreLen=7 What this does is add 0 onto the front of our string containing 200 and then test the length of the string. If it is less than 7 then the loop is repeated. the length of score as a string is already 7 so the loop will add another 0 to the front making it 8 long.Until version. We want our score to always be printed onto the screen with 7 digits. This is because the condition for carrying out the code inside the loop is at the beginning of the loop not the end! 34 .EndWhile loop will not be entered at all if the current score is already 7 characters long (or in fact longer) .not another string. We said in an earlier tutorial that there were different ways to create a loop and they seemed to do the same thing so why have so many? Let's see the above code written using a different type of loop and you should see the difference more easily: ScoreStr$=Str$(Score) While Len(ScoreStr$)<7 ScoreStr$="0"+ScoreStr$ EndWhile It's shorter.. When the last 0 is added to make ScoreLen equal to 7 then the loop is exited.and of course it never will! So. Len() will return the value 3 as the score 200 is three characters long. the Len() function will tell you the length of a string. In this case. This is done with: ScoreStr$=Str$(Score) Now we have a string variable called ScoreStr$ which contains the players score value (200) as a string.LEN() Len is short for Length and as the name suggests. but more importantly the While. If our player's score (say it's 200 for example) is stored in the numeric variable Score. can you spot it? What if the current score is say 1253843? In this case. This is so we can use the number in calculations. We can now use Len() to tell us how long the string is with: ScoreLen=Len(ScoreStr$) You will notice that Len() returns a number .
2D and 3D as well as text programs. This however is only important if you intend other people to use the programs you write. My first PC had a 33MHz processor and 4MB of memory! So.. you can use literal strings where the data is entered directly into the command. LEFT$() This function will pull out a number of characters (a substring) from the beginning (left end) of any string. you will quite often hear people talking about doing something 'this way' instead of 'that way' as it's faster. As with all BASIC's. although using one method may only be a tiny fraction quicker. assuming that A$="ABCDEFGHIJKLMNOPQRSTUVWXYZ": 35 . So. When your program has grown quite large (as they always do) and there is a lot going on. As with all DB commands. you will probably think that speed is no longer an issue. so what runs smoothly on your machine may not do so on everyone else's machines. If the value used for NumChars is greater than the length of Main$ then just the available characters are returned.NumChars) . DB provides a number of commands to do this. When you are learning to program in DB you start off with text only programs then move onto 2D graphics then finally 3D graphics. but the point is that they do all have their differences . Not everyone has a super-fast computer.. you would probably be surprised how vital the string commands can be for all types of program ... 2. However.Knowing which type of loop to use and where..5) . all these fractions can add up and overall make a significant difference in the speed that your program runs. comes when you have a little more experience. Most computers these days have fast processors . shuffling a pack of cards and saving files to disk all involve working on strings.where Main$ is the string you want to extract the substring from and NumChars is the length of the required substring.. Dark Basic Classic is an interpreted language. it will slow down even on the fastest of machines. Those who are completely new to programming and jump straight into 3D don't usually last very long.measured in GigaHertz rather than MegaHertz. On the subject of speed. If you only write stuff for yourself then you don't need to worry about this aspect. will print 'Child' So. or variables. Chopping And Changing Sometimes. there are a couple of things you have to consider: 1. Examples: Print Left$("Children". The syntax is: Left$(Main$.and uses. Formatting information printed on the screen. There are still a lot of older machines out there. you need to manipulate strings. Actually. DBPro is a compiled languages and is therefore a lot faster and less of a problem in this respect.
3) .. will print 'ABCDE' B$=Left$(A$.Print Left$(A$. Print Right$("Children". Example time: Print Mid$("Children".2) RIGHT$() This is exactly the same as Left$() but the substring is taken from the end of the main string rather than the start. Just copy it into DB and run it! A$="Darth Vader was the baddy in Star Wars. will print 'ldren' And again assuming that A$="ABCDEFGHIJKLMNOPQRSTUVWXYZ": Print Right$(A$. will print 'i' Once again. To be honest though.." Print Left$(A$... This means that you can only grab one character at a time.. saying 'substring' is a little misleading as the syntax is Mid$(Main$." B$="k is the 11th lower case letter of the alphabet.NumChars). will print 'VWXYZ' B$=Right$(A$.StartPos) .Right$(Main$.5) ." D$="icicles are made of frozen water....and as you can see there is no 'length' parameter like many versions of BASIC provide . The syntax is also the same ..3) .3)..just a start position. will take 'ABC' from A$ and place it in B$ A trivial example which demonstrates the sort of thing possible with Left$()..Left$(C$. will take 'XYZ' from A$ and place it in B$ MID$() The third in the collection is MID$() which allows you to pull out a substring from the middle of the main string.." C$="Basil is a herb.Left$(D$.3).Left$(B$. assuming that A$="ABCDEFGHIJKLMNOPQRSTUVWXYZ": 36 ...5) .3) . If you need to extract more than one character to build a substring you can do so with a loop.2).5) .
If the string starts with a number but also contains other non-numeric characters then VAL will return the value of the numeric characters UP TO (but not including) the first non-numeric character. 0 (zero) is returned...5) . In effect it is the reverse of Str$() so if you have Score=1000.7.. Asc("F") will return exactly the same ASCII code as Asc("Fred") as only the F is decoded. Asc("7") will return exactly the same ASCII code as 37 . but only the first character will be checked. Things You Should Know About VAL() If the string you are getting the value of starts with anything other than a number.N) Next N .Print Mid$(A$. so Asc("7") is legitimate and will return the ASCII code of the character '7'. Also. The character 'A' for example has the ASCII code 65. Score=Val(A$) will then turn the '1000' string back into a numeric variable called Score ... Asc() can accept a string.replacing what was there already. will print 'E' B$=Mid$(A$.. 'B' is 66 and so on. Likewise. VAL() This function will return the numeric value of a string containing a number.. As you can see.. Examples: Rem Numeric variable Age will contain the value 32 Age=Val("32") Rem Numeric variable Age will contain the value 24 ('Years' ignored) Age=Val("24 Years") Rem Numeric variable NumVal will contain the value 0 NumVal=Val("HSGH32433") ASC() ASC is short for ASCII and this function will return the ASCII code of a single character. A$=Str$(Score) will create a string called A$ containing '1000'. using Left$().4) . Right$(). you can do some really clever stuff. after which Sub$ would equal 'GHIJK'. There are however some other goodies. For those of you with prior knowledge of other BASIC's. remember that numbers also have ASCII codes. Mid$() and adding strings together. Each alpha-numeric character capable of being printed to the screen has an ASCII code. will take 'D' from A$ and place it in B$ To grab a substring from say position 7 to 11 in A$ you would use: A$="ABCDEFGHIJKLMNOPQRSTUVWXYZ" Sub$="" For N=7 To 11 Sub$=Sub$+Mid$(A$.5). this is the same as Mid$(A$. It's up to you to check that the string you are using with Val() is in the correct format.
CHR$() This is the reverse of ASC() and given an ASCII code will produce the equivalent string character.and they would never get to Neptune! You would have to have an If line for every possibility . What you do get is usually sufficient though. Say for example you were writing a program where the user has to enter a string and what your program does next depends on what is typed..you are just creating a temporary upper case version to compare with.Planet$ If Upper$(Planet$)="NEPTUNE" Then Gosub Dest_Neptune Now. Asc() can also be useful for sorting lists of strings into alphabetical order.Planet$ If Planet$="Neptune" Then Gosub Dest_Neptune But. UPPER$() and LOWER$() These two functions are used on strings to convert the string's contents to upper and lower case respectively. In a space game for example. 38 . A good use for Upper$() is when you need to compare two strings in your programs. This is because DB is primarily a game writing language and generally speaking there isn't a great need for them in games. you would use: Planet$=Upper$(Planet$) .or any other combination of upper and lower case characters? Well.and that's only for a single destination! Or. Printing CHR$(65) to the screen will result in a capital 'A'. Yet More String Functions! If you are reading these tutorials as you are new to DB but not programming. Your code may say: Input "HAL: What is your new destination? ".. what if the user typed NEPTUNE or neptune . when the user originally enters the information. The original contents of Planet$ in this example are unchanged . it doesn't matter how Neptune is typed. then you may notice that DB isn't bursting with string functions. you could just use Upper$()! Input "HAL: What is your new destination? ". the subroutine would never get called . your 'on-board computer' may ask for a destination planet which the user types in. as long as it's spelled correctly.Asc("73621"). as the upper case version will always be 'NEPTUNE' and the subroutine will always be called. If you want to do a permanent conversion.
you have a function RND() in DB which will return a random number from a given range. Still not random! So. the same 5 numbers are produced. 10 or any number between..where Seed is the numeric seed value. if the seed is a constant number then the next set of randomly generated numbers will be different. This can be replaced with the following: Inc A: Rem Increments the value currently in numeric variable A by 1 Inc A. If we wanted to add an amount to the variable 'A' we would normally use A=A+1 (or + any other value). RANDOMIZE This command will re-seed the random number generator and uses the syntax: Randomize Seed . Run the following program a few times: For N=1 to 5 Print Rnd(10) Next N You will see that each time you run it. If we wanted to subtract an amount from the variable 'A' we would normally use A=A-1 (or .3: Rem Increments the value currently in numeric variable A by 3 DEC Inc is short for Decrement and is a quick way to do subtraction with variables.. As such. it returns a value between 0 and the supplied value.Other Useful Commands For Beginners INC Inc is short for Increment and is a quick way to do addition with variables. The number returned is inclusive. The seed for the random number generator is based on a register in your PC's hardware when it is turned on and doesn't change until you restart your PC. Using this as a seed will give us a different set of random numbers each time the program is run. However. A card game wouldn't be any fun if the cards always came out in the same order or the aliens came down the screen in the same place every time. To generate a different sequence of numbers each time it is run. Simply use: 39 .3: Rem Decrements the value currently in numeric variable A by 3 RND() All games rely on an amount of randomness.any other value). So.hardly random. This can be replaced with the following: Dec A: Rem Decrements the value currently in numeric variable A by 1 Dec A. we need to re-seed the random number generator with RANDOMIZE. what we need to do is use a different number for the seed each time the program is run and the PC's built in clock is ideal for this purpose. Using the syntax Rnd(Value). the random numbers are the same every time you run the program . but only the first time they are generated . Timer() is a DB function which will return the number of milliseconds which have elapsed since the PC was turned on.from then on they will be repeated. so Rnd(10) can return 0.
Randomize Timer() at the start of your program. To test that this works, add this line to the above example to make: Randomize Timer() For N=1 to 5 Print Rnd(10) Next N Run this a few times and you should see a different set of numbers each time. Random Numbers In A Specific Range OK, so Rnd(10) will give us random numbers from 0 to 10, but what if we want the numbers to be between say 100 and 150? Well this isn't difficult. All we have to do is calculate the actual range of numbers - if we want a number between say 100 and 150 then we use 150-100 giving us an actual range of 50. As the smallest number we actually want to be returned is 100, that's what we add to our random number. A=Rnd(50)+100 ... will do the trick! Getting Information Into Your Programs: Your programs would be pretty useless if there was no user-interaction - especially games! The user may need to enter their name, select buttons on the screen, guide a spaceship, steer a vehicle or simply answer questions. This is done by the mouse and keyboard or a joystick. A number of commands are available to capture this information and in keeping with the beginners tutorial theme I'll just be covering the basic essentials for the novice programmer. The Mouse... As you move the mouse around the screen, it generates an X and Y value and if your mouse has a wheel, it will create a Z value when moved. The buttons also create a value, so let's see what functions we have to use to get these values...
MouseX(), MouseY() and MouseZ()
These three functions return the respective mouse values as integers, though it's unlikely you will need MouseZ() at this stage of learning. The syntax is: NumVar=MouseX() NumVar=MouseY() NumVar=MouseZ() where NumVar is the integer variable you want to store the relevant mouse position:
This function returns an integer value representing the current mouse button status. When no buttons are being pressed this function returns 0. The left button is given the value 1, the right button the value 2 and the middle button (if present) the value 4. Pressing more than one button returns the total of the pressed button's values so pressing the left and right buttons simultaneously returns 3 (1+2). Pressing the centre button with the right button returns 6 (2+4). At first, you will only be interested in whether or not the left or right button has been pressed so you will only need to check if the value returned is simply 1 or 2 and act accordingly. In your programs just place the following line at the beginning of your main program loop: Mx=MouseX(): My=MouseY(): Mc=MouseClick() With this line, at any point in your program, Mx will equal the mouse's X position, My it's Y position and Mc the button status.
Clickable Screen Buttons With the combination of knowing the X and Y position of the mouse at any time, along with the button status you can create areas of the screen designated as 'clickable' in a button fashion. All you need to do is check to see if the mouse is within these areas and if it is, when the mouse button is pressed. An example for you to try: Ink RGB(255,0,0),0 Box 100,100,200,120 Ink 0,0 Box 101,101,199,119 Ink RGB(255,255,255),0 Text 135,102,"Exit" Set Text Opaque Do Mx=MouseX(): My=MouseY(): Mc=MouseClick() If Mx>100 and My>100 and Mx<200 and My<120 If Mc=1 End Else Text 0,0,"Mouse Now Over Button. " Endif Else Text 0,0,"Mouse Not Over Button. " Endif Loop This creates a red button on the screen with its top left corner at X=100 Y=100 and its bottom right corner at X=200 Y=120. The word Exit is then printed in the middle of the button and the text set to opaque so that messages on the screen overwrite each other without messing up the screen. In the main Do...Loop the mouse functions are called to get the X, Y and button values and we do a test to see if the current mouse X and Y position is within the defined button area. If it is not, then the Else part of the code is executed and the message "Mouse Not Over Button. " is displayed.
If the mouse is within the button area another test is made on the current value of Mc - to see if the left mouse button is pressed (Mc=1). If it is not (Mc=0), then once again, only the Else part of the code is executed and the message "Mouse Now Over Button. " is displayed. If the mouse button is pressed then the condition is met and the program ends - just like the label on the button says! You would repeat this process so you had as many sections of code as you had buttons - each checking for different X/Y co-ordinates and doing different tasks for each button. Also, the buttons here are drawn with code and look very basic. There's nothing stopping you from loading in images for your buttons and placing them on the screen. As long as you know the X/Y co-ordinate of each button's top left corner and the width and height of each button, the process to detect which one has been clicked on is identical to that outlined above.
This command simply removes the pointer from the screen when you don't want it visible. While it is hidden, it will still return all the normal values allowing you to replace the cursor with an image of your own if you want to.
This turns the mouse pointer back on.
The Keyboard... Getting information via the keyboard has more options. The first one is INPUT, but it has its limitations so you also have Inkey$() and Scancode(). Each are used for a specific task.
This is the main keyboard input function you will use at first, though it does have its bad points. It's slow and clunky and only uses the default system font and colours. It uses the syntax: Input StringVar$ or Input Message$,StringVar$ ...and your program literally stops running until the user types something in and presses the Enter key. This makes it OK for entering the players name on the hiscore table or at the start of the game - but little else.
This will halt the program and wait for the user to type in their name and press the Enter key at which point what they typed in will be placed into the string variable Name$. The program will then carry on its merry way.
This will do exactly the same, but this time a numeric value is expected which when correctly entered will be stored in the numeric variable 'Age'. If a non-numeric entry is attempted, it will be ignored.
Control and Function keys. I know I said using Goto is bad. The next time the Inkey$() function is called the previous value is lost so if you don't 'grab' it while it's there. In your proper programs just try to avoid Goto OK? The example prints the message and drops into a closed loop which cannot be exited from unless the user presses the Y or N key. Press the p key and the letter p is stored and so on. the condition will be met and the program drops out of the loop and 43 . Here's a useful Yes/No example: Start: CLS Print "Do you want to end this program (Y/N)?" Repeat I$=Upper$(Inkey$()) Until I$="Y" or I$="N" If I$="Y" End Else Goto Start Endif OK. As nothing appears on the screen. it's immediately replaced by something else. So a good way to use this function is to create a loop which is 'locked' until the correct key is pressed. When a key is pressed. Inside the loop. the variable used contains a NULL string (""). Inkey$() grabs the current state of the keyboard and places the upper case version of it in the string variable I$. At some later stage. but it wouldn't have demonstrated Inkey$() any differently. As soon as the user presses either Y or N. how on earth is the user expected to know exactly what they are supposed to type in? That's what the alternative Input method is for: Input "Please Enter Your Name: ".This raises an important point. With a bit more knowledge you will probably decide to write your own function to do it. it is stored in a string variable for use. but this is only an example to demonstrate using Inkey$() and I wanted to keep it short. you will decide that you don't like the way that the standard Input command works. Inkey$() This function is called a 'polling' function as it polls the keyboard for keypresses without stopping like Input does. I could have written a mere half a dozen lines to avoid it. You can of course replace the literal string with a string variable if you wish. Press the b key and the letter b is stored. When no key is pressed.such as the Shift. Some keys do not return any value at all .Age This version of Input will print a message on the screen before waiting for the user's input so at least they know what the program is expecting them to type. Inkey$() only works with characters which have ASCII codes.Name$ Input "Please Enter Your Age: ".
"5."1..into the If. In your program. Menu Item 5" Center Text 320. then the program is not exited and is allowed to continue. we only exit the loop if the key pressed has an ASCII value greater than 48 and less than 55 .160. Menu Item 2" Center Text 320. pressing the A key on its own will return 97 .. so all we need to do is stay in our loop until anything but "" is returned: Print "Press Any Key To Continue" Repeat I$=Inkey$() Until I$<>"" We don't need the Upper$ bit as it doesn't matter what key is pressed. we use ASC() to test the ASCII value of the key pressed. We know that when no key is being pressed."2. How about "Press Any Key To Continue" messages? This is just as easy using Inkey$().the ASCII code for lower case 'a'. This time. This code value is totally unconnected with ASCII codes.in other words only the number keys between 1 and 6.180.. Center Text 320. So. The loop will keep going until I$ doesn't equal "" and that can only happen if a key is pressed. Menu Item 6" Repeat I$=Inkey$() Until Asc(I$)>48 and Asc(I$)<55 CLS If I$="1" Then Print "You selected menu If I$="2" Then Print "You selected menu If I$="3" Then Print "You selected menu If I$="4" Then Print "You selected menu If I$="5" Then Print "You selected menu If I$="6" Then Print "You selected menu option option option option option option 1" 2" 3" 4" 5" 6" The first six lines simply print the menu entries 1 to 6.140. but the method is still the same. you would Gosub a routine rather than print a message."6..200.Else.100. Menu Item 1" Center Text 320. the screen is cleared and the value of I$ is checked and the appropriate message printed depending on what I$ equals.Scancode: Scancode This function will return a code representing the physical key on the keyboard as opposed to what is actually printed on the key."4. If it is anything else (and it can only be N if it isn't Y as any other keypress would not have allowed an exit from the loop). That will do for the Inkey$() function so let's take a look at the last keyboard input method . Menu Item 4" Center Text 320. If it is Y then the program ends.. with Asc(). then Inkey$() will return a NULL string. Pressing the A key with the shift key will 44 . For example.Then test where I$ is tested to see if it equals "Y" or "N"."3.120. The loop is like the previous examples with the exception of the exit loop condition. On exiting the loop. "What can you use for menus"? Inkey$() again! Take a look at the following simple example and then we'll go through it. Menu Item 3" Center Text 320. clever clogs" I hear you ask. The number 1 key has an ASCII value of 49 and 6 has the value 54. "OK.
Keystate As each and every key on the keyboard generates its own Scancode. On your instruction screen you say 'Press Q to go faster'. Scancode uses the syntax NumVar=Scancode() and returns 30 when you press the A key. but the commands covered . 45 . I use a Spanish keyboard and it has a different layout too. so we'll do that in the next part of the tutorial. The only thing left to cover which we haven't done already is File Access. His Q key is in a different place and the Scancode of his Q key is different. then the Q key would have worked on all keyboards! You may need to use Scancode. To get around this. There is no shifted version as the shift key has its own Scancode. then it's impossible to detect more than one key at a time using it as the last key you press replaces the code generated by the previous key pressed even if it's still being held down. but AZERTY. Pierre in France however presses Q and nothing happens! That's because he uses a French keyboard on which the top row of letters are not QWERTY.not what's on it. Keystate returns 0 and a 1 when it is..where Keycode is the Scancode for the key you are polling. at this point we have only scratched the surface of commands in the DB library. The cursor up key has the Scancode 200 and Space Bar is 57. you can use Keystate() which uses the syntax: Keystate(Keycode) . Remember. For example. but bear this in mind. If you wanted to use the cursor up key to move forward and the space bar to fire.few as they may be. If you had used Inkey$() instead of Scancode.. With Keystate it is possible to do both at the same time using something like: If Scancode()=200 Rem Move Forward If Keystate(57)=1 Rem Fire Endif Endif To be honest. your program uses the Q button to accelerate your vehicle and it uses the Scancode 113 to detect when it's pressed. the 30 represents the key itself . then Scancode on its own would not work as you would stop moving every time you fired. When the corresponding key is not being pressed.the ASCII code for upper case 'A'. It's also worthwhile remembering that not all keyboards across the world are the same. are quite sufficient to write some quite sophisticated programs.return 65 .
you can open channel 1 to read and channel 2 to write simultaneously. open the correct valves (READ or WRITE) before using the pipe and remember to close the valves when you are finished.dat'. sounds and models."Mydata. So. The process will fail if the directory DATA does not exist though. so using just a filename like 'Mydata. The channel number is used because you can open more than one channel at a time. Filename$ can be a specific filename including the full path like: C:\Program Files\MyprogData\Mydata.despite any advantages they may have.dat'. Assuming that we need to write a file before we are able to read it back in. It's like connecting a pipe from DB to the file on disk. The first thing you have to do is open a file. read in (or write out) the data then close the file. Filename$ is the filename you want to use. you can use as many pipes as you need. saving hiscore tables or creating new file formats for your new world editor.Filename$ Channel is an integer number and is like a 'stream' number.dat" 46 . this is done with OPEN TO WRITE. The basic process is to open a file for reading or writing. this part of the tutorial series covers saving your program's data to disk and reading it back in again. the file will be opened in the current project directory (where your DB program is located). This process is required for reading INI files. so long as you read the information back in the same order. to open a file called 'Mydata. Although strictly speaking this also encompasses DB's commands for loading media files for your games such as images. What you write is up to you. DB knows which file to access. OPEN TO WRITE This command will create a new file on your hard disk and uses the syntax: OPEN TO WRITE Channel. By including the channel number in all of the commands.File Access All but the most basic programs use file access.dat' in the current directory we would use: Open To Write 1. For example. If you have a directory called 'DATA' in the current directory and wanted to save your data to a new file in there. you would set the filename to 'DATA\Mydata. The main benefit is that you can open up your files after they have been created to see if they actually contain what you thought you had written. allowing you to read from one file and write selected parts of it to a second file at the same time. As long as you number the pipe(s). Some of DB's save data commands create encrypted files which can't be opened for examination .Part 4 .dat It can also be relative. but I have decided that it's far simpler to write data as ASCII text files when you are learning to program. Now I know many people will argue with me. The channel number tells DB which pipe to send the data down when writing and which pipe to take the data from when reading.
47 . So. there are a number of commands to write different types of data. MatrixHeight. you have the FILE EXIST() function. in our example. I am also reliably told that the formats DB uses cannot be loaded into other programming languages like VB either. we now have to write our data to disk. These include WRITE BYTE. So. FILE EXIST() So. TilesX and TilesZ etc. I put 'Do Something About It' in the above example because you have two options at this point. as you only have a single action to carry out . Normally.dat" If the file does exist. before creating a new file. If it does. To avoid this. All we need to do is write all these relevant variables to disk. WRITE FLOAT. When I say encrypted I simply mean that you can't read the data with anything other than DB's respective READ command. So. deleted it if it was found and opened a new file for writing. the code between the If and Endif lines will only be carried out if the file does exist. we'll assume that it can just be deleted.not multiple lines of code. then it is 'implied' .in other words. what if the file is there and contains data which you don't want to lose? Well we'll cover that later.dat" and connects our 'pipe' which is labelled '1'. Once the file has been opened. it HAS to be deleted so you can re-create a new one. you should always check to see if it exists already with: If File Exist(Filename$)=1 Rem Do Something About It Endif Here. the File Exist() function must be given the exact filename string as is used in the Open To Write command or you may not be checking for the existence of the file in the same location. It therefore makes sense to use a variable for the filename . As you cannot open a file to save if it already exists. DB assumes you are testing for true (=1).you can't open it with say Windows Notepad and examine the contents. But. but for now. having checked for the existence of the file.each of which writes data in an encrypted format. then the File Exist() function will return 1 (true) and if it doesn't exist will return 0 (false). WRITE FILE and WRITE LONG .rather than entering the filename literally: Filename$="Mydata. this data would be variables. then you will get an error. Also. it is very important that the named filename DOES NOT ALREADY EXIST. if you don't say =1. But. we use Delete File: If File Exist(Filename$)=1 Delete File Filename$ Endif which can be shortened to: If File Exist(Filename$) Then Delete File Filename$ Here.This creates an empty file called "Mydata. you can add the keyword THEN and include the action on the end of the IF line. Use WRITE FLOAT and you can only access the data with DB's READ FLOAT . If you were writing say a matrix editor then all of the matrix data the user has created or altered would be in variables like MatrixWidth.
the process also works with float (real) numbers too. As mentioned earlier.Str$(MatrixWidth) 1."This is a sample text string!" A$="This is a sample text string!" Write String 1. we need to close the file.the same data is still stored and you are still learning how to save data to disk.Str$(MatrixHeight) 1. But.A$ OK. So. the use of Str$() converts the numeric variables to strings before writing them. For example.There is a way around this though. For example: MatrixWidth=20000 MatrixHeight=20000 TilesX=70 TilesZ=70 FloatVar#=44. let's see some WRITE STRING examples: Write String 1.Str$(FloatVar#) As you can see.Str$(TilesX) 1. The fact that all your output is strings is irrelevant . they would see what the file was for. when you are learning DB. then I think it's important that you are able to write some data to a file then open it in Notepad and see if it contains what you actually thought you were writing.82 Having written our data out.Str$(TilesZ) 1. You could use this method for the very first line of your file to write a header description of the file so if anyone opened the file to look at it.82 Write Write Write Write Write Write String String String String String String 1. If you opened the above resulting file with Notepad you would see: This is the header 20000 20000 70 70 44. This is done very simply with: 48 .not string? That's not a problem. The first example writes the literal string enclosed in the quotes to disk. MatEdit's MA0 matrix files all have the following first line: MatEdit . so you can create as big a header as you like. As you can see. by using WRITE STRING for everything. The original variables are not altered in any way by this process. we just convert them to strings when we write them out."This is the header" 1. (but not the actual quotes). these both do the same thing.MA0 File Lines can be ignored by your loading routine. to identify them. The second example is what you use to write string variables. what if your variables are numeric .
Filename$ Read String 1.. first of all. that's written an example file. As we know that all the data in the file is of type string.Str$(FloatVar#) Close File 1 OK..T$: MatrixWidth=Val(T$) Read String 1.T$: TilesZ=Val(T$) Read String 1. If the file does exist then we use OPEN TO READ along with READ STRING to get the data. but what about reading the information back in? OPEN TO READ This process is very similar to writing files but using Read instead of Write. That's why all the reading code is enclosed inside the If File Exist(Filename$) loop. As this is unwanted information. but as you are reading the same data that you wrote out. we check for the existence of the file we are trying to load. The complete routine for our example would therefore be: Filename$="Mydata. If it isn't then we don't attempt to open it.Str$(TilesZ) Write String 1. though it MUST be loaded as it's part of the file.dat" If File Exist(Filename$) Open To Read 1. It's probably easier to show you the complete routine for reading the file generated by the above example code then discussing it afterwards: Filename$="Mydata.Filename$ Write String 1. You just have to make sure that you load data strictly in the same order that you wrote it out or nothing will work! The first of our data items is a text header.82 Open To Write 1.if it isn't actually a string.Str$(MatrixHeight) Write String 1. To avoid errors we only open the file if it's there.Str$(TilesX) Write String 1.Close File Channel .dat" If File Exist(Filename$) Then Delete File Filename$ MatrixWidth=20000 MatrixHeight=20000 TilesX=70 TilesZ=70 FloatVar#=44.T$: FloatVar#=Val(T$) Close File 1 Endif OK. we can ignore it once it is loaded.Str$(MatrixWidth) Write String 1. Data files are sequential so in order to read say the third item 49 .T$: TilesX=Val(T$) Read String 1.T$: MatrixHeight=Val(T$) Read String 1. There's no way to detect automatically what type of data is in a file.where Channel is the channel number used when opening the file.T$: Rem Ignore This Info Read String 1. you already know what each string you read in has to be converted to ."This is the header" Write String 1. we can use the same string variable (T$) to read each data item in and then convert it where necessary.
let's assume that our game has a hiscore table which holds the top 10 hiscores and the names of the players who scored them. ANY score will get onto the table so they enter their name and the data is stored in the two arrays. Using the method we will discuss next allows you to save all the arrays from your program that you want . These are created with: Dim Hiscore(10) Dim PlayerName$(10) For these tutorials. So the rule is load EVERYTHING and ignore what you don't want! The next item of our example is MatrixWidth which is numeric. However if you miss off the # symbol then FloatVar=Val(T$) will result in FloatVar equalling 44 because without the # it is an integer variable and you will lose the . the arrays written to disk are the same as in memory. After it is read in. the arrays are modified. once again I am purposely ignoring the fact that element 0 exists in an array as it makes life easier . but if it doesn't write the data to disk.. Hiscore() is an integer array as the hiscores will be numeric and PlayerName$() is naturally a string array. then the file will not exist so it must be created and the arrays written out to disk. the first two must be loaded first.82 which is what we want. Val() doesn't mind. Arrays are no more than simple variables in blocks. If it's the very first time it has been run.DAT. Here's a useful example.. T$ will equal "20000" so MatrixWidth=Val(T$) will convert T$ to the number 20000 and place that value into the numeric variable MatrixWidth. but you cannot save more than one array in the same file as the command has to be supplied with the filename.82" to the numeric value 44. For this we need two very simple arrays . At this point. The process is repeated re-using T$ for the remaining numeric variables in the file. This is essential if you want to create your own file format. all the hiscores are lost. when your game runs it checks to see if the file HISCORE.82 as long as you use a float type variable to receive it. so once the string version of the value has been loaded into the variable T$ we need to convert it to a numeric value with VAL().all in the same file. The last data item is a float.DAT exists. Each variable in the array can be accessed by using the array's index number and if you can access a variable. Saving Arrays: There is a command in DB for saving arrays. So. you can save it out to disk.82 off the end! Finally the file is closed. it will still convert the string "44.we can refer to players/hiscores 1 to 10 rather than 0 to 9. Hiscore Tables Creating a hiscore table in your program is easy enough. Obviously the first time the game is played. the next time the program is run.in the file. At this time they will obviously all be empty or contain 0 (zero). So. 50 . FloatVar#=Val(T$) will result in FloatVar# containing 44. The player plays the game and if their score gets on the hiscore table.Hiscore() and PlayerName$(). The file on disk will be called HISCORE.
For Next loops are ideal for this.T$: Hiscore(N)=Val(T$) Next N When writing string arrays.we just alter the loop accordingly.When the game is exited.Str$(MultiArray(Nx. the existing file HISCORE. this nested loop will use Nx to write the 10 Nx array values for every Ny value in the Ny loop.regardless of whether or not it has changed since last time. 51 . our two arrays can be modified when a new hiscore is attained and on exit the hiscore table is just written out again . followed by Nx from 1 to 10 with Ny=2 and so on until Ny=5.Ny)) Next Nx Next Ny Here. So. The next time the game is run and it checks to see if the file HISCORE. the contents of MultiArray() will be written using Nx from 1 to 10 with Ny=1.DAT is deleted (we already have a later version in memory) and the new contents of the two arrays written out to the file HISCORE. To save a numeric integer array which was created with DIM MultiArray(10.Str$(Hiscore(N)) Next N As you can see. it will be there. So. there is no need to convert the data. All we have to do is write the data in a loop which matches the size of the array. the old hiscore table is read in. Once in memory.5) we would use: For Ny=1 To 5 For Nx=1 To 10 Write String 1. Writing arrays are very simple. we would use: For N=1 To 10 Write String 1. to write our array Hiscore() to disk with 10 elements. Reading the array back in is also just as simple: For N=1 To 10 Read String 1. then the process is identical . so instead of creating a new one.DAT.T$: PlayerName$(N)=T$ Next N Saving Multi-Dimensioned Arrays: If the array you want to save is a multi-dimensioned array.PlayerName$(N) Next N Reading the string array back in is done with: For N=1 To 10 Read String 1.DAT exists. Str$() is used as before to convert the numeric array data to string when writing it out to disk. so we skip the Str$() section and just use: For N=1 To 10 Write String 1.
T$: PlayerName$(N)=T$ Read String 1. creates it if it doesn't and reads it in if it does: Dim Hiscore(10) Dim PlayerName$(10) Filename$="HISCORE. but using exactly the same nested loop. that's how data in arrays is saved to disk and read back in again. I will stress that it's very. So back to our hiscore example.DAT" If File Exist(Filename$) Open To Read 1. Delete File FileName$ Open To Write 1. Failure to do this can cause problems . we know that the file definitely exists so we just delete it and create a new file containing the contents of the hiscore arrays currently in memory .Str$(Hiscore(N)) Write String 1.PlayerName$(N) Next N Close File 1 52 .pushing the bottom entry off the list. What we have to do now is place a small routine at the beginning which checks for the hiscore data file.Filename$ For N=1 To 10 Write String 1.ready for being read in the next time the program is run.Str$(Hiscore(N)) Write String 1. ask for the players name.T$: MultiArray(Nx.Filename$ For N=1 To 10 Read String 1.. very important that you read in the information in EXACTLY the same order that it was written out.they just won't work properly and the problem could be very difficult to trace. For Ny=1 To 5 For Nx=1 To 10 Read String 1.. you write the code which checks the players score at the end of each game and if it's higher than the lowest score in the hiscore table. inserts the name and score into the two arrays .Filename$ For N=1 To 10 Write String 1.especially when you realise that it is possible for the data you are reading in to be fed into the wrong variables.Ny)=Val(T$) Next Nx Next Ny OK. On exiting the program.T$: Hiscore(N)=Val(T$) Next N Close File 1 Else Open To Write 1.PlayerName$(N) Next N Close File 1 Endif In your game.Reading back in is the same as with single dimensioned arrays. Once again. Your program will often not error during the load process in cases like this as the routine will load any data into any variables so long as the variable types match .
One of the easiest files to read in are plain ASCII text files created with a text editor as each line is going to be a string.you still have to load all the useless information even though you are immediately going to discard it. This will normally be a function (or collection of functions) which users can #Include in their programs so they can call the functions when required. Reading Other Files Open To Read isn't just restricted to reading files you created yourself with Open To Write. you should have a description of the file type at the start saying what the file is used with. then they are not going to want to use your program. As long as you know the file format. so when there is a lot of data to be written it's worth planning what order to write the data. so they can add routines to their programs giving them the ability to load files created by your programs. just write the data out sensibly and logically. You decide where the data goes in your own file format. There's a bitmap file format. what variable types they are and so on.. Publishing the file format simply describes to others what these number are. The numeric and string variables should come next and finally all the array data.. you can read data in from graphics and text files.File Formats As you have seen. another question is 'how much data do we read in'? As we didn't create the file. you can write many different types of variables while a file is open for writing. We'll ignore this point for the moment though and return to it later. However there is a limit of 255 characters with DB's strings so if the text file you are reading in has a line greater than 255 then the reading will end abruptly with an error. It can also be used to read information in from other files too. one is reserved for the colour palette and another part of the file will be the data which makes up the picture.lots of them. Loading Routines If you write a program which creates a data file usable by other people you will also need to create a loading routine in DB which is supplied with your program. If you were to look at an MDF file you would just see numbers . a Microsoft Word file format and so on. MatEdit for example creates a . The structure of your data file is called a 'File Format' and all files created with Windows applications have one. If you don't provide them with a simple way to do this. Try not to have too much unwanted information like comments scattered about the file as it complicates the load routine . If you write a matrix editor or world editor then you want people to be able to use the creations made with your program in their own DB programs. As a rule of thumb. There are no fixed rules for designing a file format. For example in a graphics file format one part of the file is the header. Also. we have no idea how long the file is! 53 .MDF file with the Build option. The file format defines for other users the layout of your file and what information can be found where.
. The Read String line reads each piece of data into T$ and it is then placed into the string array using the numeric counting variable LineCount.Filename$ LineCount=0 Repeat Inc LineCount Read String 1. Instead we use a Repeat....Next loop any longer as we don't know how many lines there are in the file . Feel free to use whichever method you like ..we have to increment LineCount manually each time around the loop. The data from a string-type file like this is usually done with a string array. You just need to dimension the array with a large enough number of subscripts before reading in the file or an error will occur while reading. If anyone is wondering why I use: Read String 1... Let's see an example: Dim TextLines$(5000) Filename$ ="DOCUMENT.TXT and we use our usual method of placing the loading code inside an If. Since using a normal string variable to read the data and then transferring the contents to an array I haven't encountered those errors.TXT" If File Exist(Filename$) Open To Read 1.TextLines$(LineCount) it's because I have encountered problems in the past when reading array values directly.. we can read all of the data in the file without having to know how much is there first.the end result should be the same. Using this function in a loop.where Channel is the same as the channel used with Open To Read.FILE END() Luckily.Next counting variable .T$: TextLines$(LineCount)=T$ Until File End(1)=1 Close File 1 Endif This example creates a string array with 5000 elements and is thus able to read up to 5000 lines from a text file. DB gives us a function called FILE END() which uses the syntax: File End(Channel) .T$: TextLines$(LineCount)=T$ rather than Read String 1.Until loop which uses File End() to check if the end of the text file has been reached.. As the variable we use for counting in the loop would normally be the For.. The filename is set to DOCUMENT. This is done with Inc LineCount and the line LineCount=0 is used before entering the loop . This loop continues reading lines of text from the text file until there is no more lines to read and then drops 54 .which obviously is not available here .Endif which checks to see if the named file exists.to ensure that the counting loop starts at 0 (in case the routine is used more than once)..and we therefore don't have any start and end values for this kind of loop.. This will return true (1) if the end of the file has been reached or false (0) if there is still more data to be read in. The important part of this example is that we are not using a For.
Here's the program: 55 ." Print For N=1 To LineCount Print TextLines$(N) Next N And that's all there is to reading a text file. but as it's the only way around the problem it's better than nothing. If ContainsWords is set to 1 and EndLineTrigger is set to 80 then the line is cut off at the end of whatever word is at position 80. Line Too Long? Going back to earlier in this tutorial.most of which were a lot larger than 255 characters in length! Below is the small program I wrote to solve the problem. For MatEdit Pro's in-built help files.. What it does is read data in from the file a byte (character) at a time in a loop until it is a given length. they are written out to another file. This is quite a bit slower than reading in a line. If ContainsWords is set to 0 and EndLineTrigger is set to 80 then each line is cut off at the 80th character. When all the lines have been read in and shortened. what do you do if this happens? Well basically. Knowing this.. you switch to reading the line in a character at a time rather than a line at a time. The problem was that the existing docs contained quite large paragraphs and when exported as a text file. When ContainsWords is set to 1 then the file being read in is deemed to be a document containing words and when set to 0. The two variables EndLineTrigger and ContainsWords are worth mentioning. I needed a text file of the MatEdit documentation." lines read in.the two values which record the end of a line in all text files). EndLineTrigger is the length of the required lines after reading in the data and what it does depends on what ContainsWords is set to. just data. I briefly mentioned that DB will error if you try to read in a string which is longer than 255 characters. At this point. but with each line short enough to fit on the screen. we can add a For.out of the loop. So.Next loop to the end of the program which will print the lines read in to the screen: Print LineCount. LineCount is equal to the number of lines read in from the text file. at which point a new line is started. each paragraph became one single line . (or it reads in the two bytes 13 and 10 .
600." lines!" End Use this program on any text file which you can't read with the Read String method.. OK." Open To Read 1..OutputFile$ For N=1 To LineNum Write String 1. that's it for the File Access tutorial. The two filenames at the beginning of the program allow you to set the input and output filenames.. If you think there's some aspect of File Access you think I've missed and would like to see covered then let me know.LineNum.ChNum If ChNum>=32 or ChNum=9 Lines$(LineNum)=Lines$(LineNum)+Chr$(ChNum) Inc CharCount If CharCount=EndLineTrigger: Rem Point at which to seek EOL If ContainsWords=0 Rem Reading text file containing data which can be split anywhere CharCount=0 Inc LineNum Else Rem Reading text file containing words which should not be split Repeat READ BYTE 1.txt": Rem Name of text file with lines > 255 characters OutputFile$="cutoff.txt": Rem Name of resulting file after lines have been shortened LineNum=1: EndLineTrigger=100: ContainsWords=1 Print "PLease Wait . This will convert the file and give you a new file which can be loaded with the Read String method.InputFile$ Repeat READ BYTE 1.Set Display Mode 800..Dummybyte: Rem Read in the unwanted following Chr$(10) CharCount=0 Inc LineNum Endif Until FILE END(1)=1 Close File 1 CLS Print "Writing Out New File.Reading File And Truncating Lines.16 Dim Lines$(10000) InputFile$="test.Lines$(N) Next N Close File 1 CLS Print "Written new text file containing ".ChNum If ChNum>32 Lines$(LineNum)=Lines$(LineNum)+Chr$(ChNum) Endif Until ChNum<=32 or FILE END(1)=1: Rem Until we hit a space or EOL (Chr$ 13/10) CharCount=0 Inc LineNum Endif Endif Endif If ChNum=13: Rem EOL Reached READ BYTE 1." Rem Write out converted file Open To Write 1. 56 .
These days. the result becomes an integer when it's placed into an integer variable. using the wrong variable type can lead to errors in calculations and difficult to trace problems in your programs later on. but as computers are so fast.5' off the end. you lose the '. computers were nowhere near as fast as they are now and we had to use every trick in the book to squeeze as much speed out of our programs as possible. (DBPro has a number of other useful variable types. This is because A and B are integer variables and regardless of the calculation.5 but the program prints 2. this is still true. Variable Types: The three basic variable types common to both versions of DB are String. Choosing The Type Of Variable To Use: What variable you use depends on what sort of information you need to store. 57 . it's clear that many of you are not sure about the difference between float and integer variables . it's still pointless using floating point variables with numbers which can only ever be integers! More to the point.Choosing The Correct Variables Looking at the code posted by many users on the forums. This included using integer variables rather than floating point at every opportunity. String variables have the $ symbol (dollar) on the end of their names . If your variable is only ever going to contain whole numbers then you should use integer variables.like MyString$.or when you use them. If you are going to need the ability to calculate and store fractions then you should use floats. Float variables have a # symbol (hash) on the end of their names . Integer variables have nothing on the end of their names . Choosing the wrong one can cause problems. For example. Back when I started programming. but are not included in this text so the information given here applies to both versions of DB). Numeric Integer and Numeric Float (also called Reals). So. it's not as critical any more. Even so. (prior to CPU's having Maths Co-Processors if anyone else remembers them).like Score. This was because it took a lot longer for a computer to do a floating point calculation than an integer calculation. run the following example in DB: A=5 B=A/2 Print B Wait Key The answer is of course 2.like FireAngle#.
It can NEVER be a floating point number like 100.100. Even though the variable B# is a float.0 Print B# Wait Key Notice that as we have changed the divisor from 2 (integer) to 2.77 because you can't position anything on the screen between two pixels.we just need to know when to use the correct type. when you run it. 58 . Working in 2D with the screen is a good example of where you should only use integers. Placing something on the screen requires the X and Y position values as in: Paste Image 1.5! This is because in the line B#=A/2 both A and 2 are both integers so the result is an integer. So.0 (float). you still get the answer 2 instead of 2. The important thing to realise here is that a pixel co-ordinate is a whole number like 100.5! When To Use What: As mentioned previously.100 which places the image 100 pixels across and 100 pixels down the screen. The screen is usually something like 800x600 or 1024x768 and the values correspond to the number of pixels (screen dots) running across and down the screen. any variables in any way related to 2D screen positions or moving screen objects around should be integer variables. you have to use a floating point variable . This is called variable typecasting. run the following snippet: A=5 B#=A/2. integers are faster than floats so are preferable to use when we can .but there are pitfalls to be aware of even then. In 800x600 mode the 800 pixels running across the screen start at number 0 on the left and end at 799 on the right. the answer is now correct . you simply make sure that the parameters in the formula you use are not all integers. Take the above example amended so that B is now a floating point variable: A=5 B#=A/2 Print B# Wait Key Note that when you run it.5 or 250. So what do you do if you have an integer and need to turn it into a float? Well.To store a floating point number.2. For example. 250 or 399. the result is still turned into an integer when it's stored. Running down the screen 0 is at the top and 599 is at the bottom.
In 3D however.2. 59 .and you'll avoid many problems when your programs grow bigger. but 'units' . 100. For example. it can look the same size to the camera as the larger one . in summary.Another area where you frequently see float variables misused is with the mouse. but 3D units are relative.one of them 100000x100000 units and the other 100x100 units in size.7 and moved 0.3. Objects can be placed at locations like 100. As such. think of two matrices .because they are not a fixed size like pixels.it depends on the size of the objects in the scene to convey 'size'. A tree 20 units high would look enormous on the smaller matrix but miniscule on the larger one. only use float variables when you have to and your programs will be faster .you can have fractional parts of a unit. a metre. It's not easy to get your head around the concept at first. an inch or a mile .not floats. so store the results in integers . If all the objects are scaled down on the smaller matrix. you would use float variables to store these values.1 units in any direction. What's more. they are not whole units .despite the hugely differing unit sizes of the matrices. There's nothing stopping you from using those integer variables later in calculations which result in float values if you need to just remember the bit above about how to force calculations involving integers to return float values. The mouse can only ever return whole numbers. So. 1 unit doesn't equal a centimetre. we aren't talking about pixels any more. the size of your objects defines how big a 3D unit appears and it has nothing to do with the actual number of 3D units. So. 10.
when you exit the function what will A equal? If you said 20 then you got it wrong! The answer is 10 because inside the function. so beware. This 'feature' can be very useful if you know it's there. local variables are 'destructive'. if you set the variable A to equal 10 in your main program with A=10. proper global variables don't really exist officially as they cannot be declared. Another thing to remember about local variables is that they can co-exist at the same time as other variables with the same name. in Dark Basic local variables in functions are 'non-destructive' which means that when you return to a function. For example. In Dark Basic Classic however. you revert back to the original variable A. So. functions are a somewhat different beast. if you write a DB program with just one line which says PRINT MYVAR then DB will quite happily initialise the variable containing the value 0 and print 0 on the screen. DBPro does have global variables. which means that when you exit a function. any local variables are destroyed. functions in Dark Basic have three main differences: 1. In other languages. A normal variable in your main program can be seen inside a procedure. then call a function in which you say A=20. which is of course is still equal to 10. The next time you visit the function. You don't have to tell DB beforehand that you are going to use the variable MYVAR in your program and that it is going to be an integer variable. (often called a 'procedure'). This is very important to remember as not knowing this can lead to some very hard to find bugs in your programs. The visibility of variables is called the variable's 'scope'. Local Variables All variables in a function are local as opposed to global. This feature can be very useful. the variable A would be a newly created local variable called A . For example. but an annoying source of difficult to trace bugs if you don't! 60 . starts with a label and ends with a RETURN and is called using GOSUB. When you exit from the function.they exist from the moment you refer to them. whereas local means that they are only visible inside a function they are used in. but if you are used to programming in other languages you can be lured into a trap.Dark Basic Functions Whereas a subroutine. Although similar to look at code-wise as subroutines.entirely separate from the previously created variable A outside the function. However. although I'll refer to them as global variables. (unless you are using an IDE which supports them). the variables still exist and contain whatever values they did when you were last there. Variables in BASIC do not need to be declared at the start of your program like in Delphi or C . the variables are created again as entirely new entities. In programming terms global means that they are visible to all of your program. but not inside a function. DBC users should think of them as being 'semi-global'.
So. As described above."Elephant") 61 . empty parenthesis can be used when calling the function and in the function header . we use the following call: ValueBack=MyFunction(MinLen. so we must take that into account when calling the function.Var3.0. Think of it as passing the contents of each variable to the function . it depends on whether you need to pass parameters or not and whether your function returns anything.Var2. The first example above neither requires or returns anything so it is called with: StartScreen() The second function needs three integer variables and a string so they are passed in the parameter list. how are functions called? Well.2. Entry & Exit Parameters Functions can be used to do a specific task without any external information and then exit without returning any information.StringVar$) SLen=Len(StringVar$) RetVal=Var1*Var2*Var3+SLen EndFunction RetVal It's fairly obvious here that the function called MyFunction() is passed a parameter list of four variables . you would have functions something like these: No Entry Or Exit Parameters: Function StartScreen CLS RGB(100. The actual calculation is of course nonsense. The function itself must have the exact same parameter list to accept the same variables.100) Ink RGB(255.Score.the first three being integer numbers and the fourth a string.or they can be omitted entirely.255). A 1 returned would denote that the function's task was completed successfully whereas a 0 could denote that something went wrong. so you need a method to pass information from your main program to the function and this is done by calling the function with the required variables in a 'parameter list' which is enclosed in parenthesis '()'. all variables in a function are local. One example of a return value would be a success value. RetVal is then returned back to the calling function. This is done by using a variable of the same type in the call.0 Print "Screen Now Cleared And Ready For Use!" EndFunction Using Entry And Exit Parameters: Function MyFunction(Var1. The function also returns the contents of the variable RetVal. functions can do a task calculated on the information supplied and then return information.not the variables themselves. As our example function MyFunction() returns an integer. If no entry parameters are required. The actual variable names used in the call need NOT be the same as used in the parameter list of the function header as the variable's contents are placed into local variables when they get there.MaxLen. but demonstrates how things work.255. Alternatively. DB will accept either format. These variable names are used inside the function as local variables to calculate the value of RetVal. So.
a function can return multiple values . There are many more tutorials and example code snippets on TGPF . a real number or a string. A big time saver . are all done with functions. Sadly. so the main reason for using them would be for the ability to use local variables or the future grouping together of them in a #include file.especially if you have functions to cover all possible control input methods. It was discovered a long time ago that in DB Classic. there is little or no difference between using a procedure and a function.After calling the function. using arrays is only a work-around and there are limitations. though as it happens. all the functions in the #Include file become available .something widely misunderstood by the newcomer to programming with Dark Basic. all you have to do is put the Control. So using arrays.without you having to recode them again. let's say Control. You'd never have to write a piece of input code again! You must remember however. Some users will swear by using functions for everything. the variable ValueBack will contain the returned contents of RetVal from the function. but the subtle point is that in Dark Basic. but my advice is to go with whichever you are happiest with. The answer in many programming languages is the declaration of true global variables which can also be accessed within functions. arrays behave like proper global variables and an array declared at the start of a program can be seen. the work-around is on a similar theme . If all the actions for moving and firing etc. then they can all be saved in a single file called. you don't have the ability to do this in Dark Basic Classic.or the equivalent anyway.dba for example.dba file into the same folder as your new program and use the following line as the first line of your program: #Include "Control. 3. A good example is the keyboard/mouse control code which you use for controlling the game character or ship in your games. Remember though. the REM statement is the only exception allowed. the idea is that they prevent you from having to write certain sections of often used code over and over again each time you write a new program.dba" When the program is run. a procedure is better due to the lack of the local variable problem.my game programming forums. Why Use Functions? Well speed-wise. functions can only return a single value which can be a bit limiting. In fact in some cases. that Include files can ONLY contain lists of function and no code can reside outside of each function header and its associated EndFunction. (In #Include files you can ONLY have functions . The next time you write a program which uses the same control method. Click on the link below . #Include Files Functions can be used in Include files . In a nutshell.nothing else). To the best of my knowledge. The value returned can be an integer number.it's free to join and everyone's welcome! 62 . Arrays declared inside a function remain local though and not accessible outside it. You can't pass arrays to functions or return them. used and modified inside a function and is still intact upon exiting the function.using arrays.
all strings are enclosed in double quotes ("") and are stored in string variables which are defined by having a dollar sign ($) on the end of them . This. let you talk to other characters or let you type your name into a highscore table. The first letter is a capital M. A character can be any alphanumeric symbol your computer can produce . DB will take everything inside the quotes and place them inside the string variable A$. The section in quotes is called a 'string literal' as opposed to A$ which is a string variable. jumbled up and have sections in them replaced with something else. B is 66.either printable or not. then it will print an M on the screen. Armed with a list of ASCII codes you could build up a string with CHR$(): 63 . You can add strings together: A$ = "ABC" B$ = "DEF" C$ = A$+B$ Print C$ This will print "ABCDEF" to the screen. As it happens. Take our above example 'My Name Is Fred'..as in the variable PlayerName$. searched.. Strings can be cut up. like the rest of the alphabet is an ASCII character and as such has an ASCII code.Everything you wanted to know about strings You wouldn't think so at first glance. what good is a game which doesn't put messages onto the screen. But first. After all.. In this tutorial we'll be taking a look how to do all of these things. There are a number of string-based commands in DB. joined together (concatenation). the code for M is 77 and if you tell DB to Print CHR$(77).. The actual quotes are not stored . which we'll cover later.they are just markers so DB knows where the string starts and ends. however there may be times that you want to create a string of non-printable characters that aren't available on your keyboard. C is 67 and so on. Add one more character on the end and you'll get a String Overflow error and DB will die. One of them is CHR$() which we'll look at now. In DB. The ASCII code for A is 65. They all use strings! In DB. strings have a maximum length of 255 characters. a bit of background info. A very simple example would be: A$="My Name Is Fred" When you put this in your program. but strings are quite important in every DB game you will write. You can type the sentence 'My Name Is Fred' as it's an English sentence.
you can use the ASC() function: ASC(String) or ASC(String Variable) will return the ASCII code of a character so: Print ASC("A") . In a program. Character2Name$ and Character3Name$ to store them. you might want a string built up of characters that you can't type in. The simple code below will sort strings.. In reception on the wall there's a row of mailboxes numbered 1 to 10 for each of the apartments. Print ASC("Alan") Print ASC("Andy") Print ASC("Arthur") will all print the number 65. as mentioned a moment ago.based on the first character of each string. If all 10 mailboxes are collectively called 'Mailbox$' and the letters inside the boxes is our string data. what if you had 3 characters with names? You would need Character1Name$. but you can actually use it with strings of more than one character . 200 or even more characters? The answer is that normal strings would be impossible you would have to use string arrays... The postman puts the mail for apartment 4 in box 4 and when the guy from apartment 4 comes down. but it can't be done with normal string variables . Imagine an apartment block with 10 apartments. In which case you could use this method: A$=Chr$(240)+Chr$(241)+Chr$(242)+Chr$(243)+Chr$(244) Print A$ Each character in a string takes up one byte of memory and as a byte can store 256 numbers (0-255). You would also need a separate line of code referring to them for every occasion in your program.they have to be in a list using string arrays.. String Arrays A string like Character1Name$ is a single entity. However.though in these cases only the first character is tested. in DB terms is doing: 64 . ASC is meant to be used only on single characters as shown above. a standard ASCII character set only has room for 256 characters. To interrogate a character and find out what it's ASCII code is. he simply opens mailbox 4 for his mail. What if you had 100. Don't panic though . Eg. this can still be useful for a rudimentary string sort . then in effect we are describing a string array.will print 65 on the screen.A$ = CHR$(77)+CHR$(121)+CHR$(32)+CHR$(78)+CHR$(97)+CHR$(109)+CHR$(101) All these CHR$()'s place 'My Name' into A$ and in this instance is a pointless exercise. However.they are really easy when you get to know how they work. The postie putting letters into mailbox 4.
This is called DIMensioning an array and with our mailbox example it would be: DIM Mailbox$(9) Wait a minute!. Just pretend that noone lives in that apartment! So what's the point of all these boxes?.. You can however use DIM Mailbox$(10) and ignore box 0 altogether if you find it easier. you can refer to Mailbox$(X). B.. C and so on to F. you need to count across to 5. Imagine printing the contents of 5 'mailboxes' the 'old' way: Print Print Print Print Print Mailbox1$ Mailbox2$ Mailbox3$ Mailbox4$ Mailbox5$ Now again with 100 mailboxes! No thanks. If your stuff is in locker 5D. The lockers running across might be numbered 1 to 10 and the rows running down labelled A. In turn.that's correct I did.just one row of boxes and one index number. But. However you can also have 'multi-dimensioned arrays' which are best thought of as being like a wall of lockers in a changing room. So for 10 actual boxes.. Well.. unlike our apartment block. Try this instead: For X = 1 To 100 Print Mailbox$(X) Next X Now you see the power of arrays! Actually... This means that instead of using Mailbox$(2) or Mailbox$(9). you can replace a number with a variable. the number in parenthesis () is called an index and as always when programming.Mailbox$(4) = Letters$ And when the guy from apartment 4 gets his mail: Owner$ = Mailbox$(4) In DB.10 in total. Yes ..sixty in total.. DB has an apartment number 0. Say there is a stack of lockers ten wide and six high . what we've been talking about so far is a 'single-dimension array' . we only need to DIM 9 which gives us boxes 0-9 . then count 4 down to D to get to it.. Nine? You said 10 mailboxes!. 65 . that means that you can use them in loops.. we just have to say how many mailboxes we require before we can start using them.
Locker 5D would be. All we have to do is look at the ASCII codes of the first characters of strings 1 and 2. Now for the code (which is added to the end of the above snippet): True=1: False=0 Repeat SwappedString = False For N=1 To 9 Str1 = ASC(Names$(N)) Str2 = ASC(Names$(N+1)) If Str1 > Str2 Temp$ = Names$(N) Names$(N) = Names$(N+1) Names$(N+1) = Temp$ SwappedString = True Endif Next N Until SwappedString = False For N=1 To 10 Print Names$(N) Next N Wait Key As you can see..sorting. Locker$(5. We reset the SwappedString flag (to False) at the start of each run through this loop and check to see if it's been set at the end.In DB. Having our strings in an array means that we have an orderly way to present the new list after we've sorted it in a loop using a variable for the array index number. OK. We now repeat the process with strings 2 and 3. If the first string's ASCII code is greater than the second then it's higher in the alphabet and we need to swap their positions..Y) where X is counting across and Y is counting down. 66 . this translates to an array like Locker$(X. We use a 'DidWeSwap' flag and set it each time we have to do a swap when we run through the loop.Until loop. If we run through the loop and a swap wasn't done.. a complete run though the array is within the Repeat. they aren't sorted at the moment. Let’s set up our example array: Dim Names$(10) Names$(1) = "Geoff" Names$(2) = "Tim" Names$(3) = "Alison" Names$(4) = "Pete" Names$(5) = "Chris" Names$(6) = "Barrie" Names$(7) = "Nigel" Names$(8) = "Rosie" Names$(9) = "Simon" Names$(10) = "Kevin" (For simplicity. then 3 and 4 and so on. back to the issue in hand . I've chosen to ignore element 0 of the array). the flag isn't set and we know the strings are now all in the correct order.4) Anyway.
5) .will print 'ABCDE' on the screen .CharPos) will extract the character at position Charpos in StringVar$..the character at position 6. This simple sort routine is called a bubble sort because strings in the list that are not in their correct positions 'bubble' to the top. Right$() and Len() Left$(StringVar$. This is used during the swap process because we don't want to lose string 1 when we copy string 2 into it.. but least efficient. The last For.Next loop prints out the 10 strings in the array and if you run the program you will see that all the names are in sorted in alphabetical order... How about writing word puzzle games like anagrams? Using the above commands we can take a string and jumble all the letters in it: GameWord$ = "elephant" ShowWord$ = "" Randomize Timer() 67 .If it has been set (to True) then we've not finished so repeat the loop again. If it's still False then no swaps were made during that pass and the list is sorted . So A$="ABCDEFGHIJK" Print Len(A$) .the leftmost 5 characters. this one is probably the easiest to program..the length of A$.NumChars) will extract NumChars characters from StringVar$ starting at the left. Left$().so drop out of the loop... The only other thing of note is the use of the string variable Temp$.. So: A$="ABCDEFGHIJK" Print Left$(A$.5) .. string 2 into string 1 and then Temp$ into string 2.will print 'F' on the screen . Len(StringVar$) will return the length of StringVar$. Right$(StringVar$. So A$="ABCDEFGHIJK" Print Mid$(A$. Mid$(StringVar$. We copy string 1 into Temp$.the rightmost 5 characters.will print 'GHIJK' on the screen .will print '11' on the screen .6) .. At this point let's introduce a few more DB string commands before we need them. So: A$="ABCDEFGHIJK" Print Right$(A$. Of all of the sorting methods.. Mid$().NumChars) will extract NumChars characters from StringVar$ starting at the right.
The main For. the character at that position in GameWord$ is added to ShowWord$. How about highscore tables then.but a jumbled version of the word. The loop is repeated so. words could be selected at random from a pre-loaded text file. we set GameWord$ as the word to scramble. The RandChar line picks a random number which is always between 1 and the number of letters in GameWord$. Finally. All we need to do is shuffle all the entries below it down one in both arrays . we simply loop through the score array starting at the bottom comparing the player's score with the score in the array.usually two of them: one for the name and one for the score.RandChar-1)+Right$(GameWord$.Len(GameWord$)RandChar) Next N Print ShowWord$ Wait Key For this example..8 with the word elephant.. It's done like this because in the following lines. GameWord$ has been reduced from 8 characters to an empty string and ShowWord$ has gone from an empty string to being 8 characters long . If however the array entry is greater than the player's score . OK. the quicker it's done. If the array entry is smaller than the player's score then we continue round the loop.awarding more points. All that is required then is to feed our new player information into the array at the calculated position. or read from Data lines). (though in a real situation.then we have reached the required 'slot'. The only tricky part of creating a hiscore list is deciding where to insert an entry at the end of a game. After selecting the random number. After randomizing the random number generator..RandChar) GameWord$ = Left$(GameWord$. You could then display the word and time how long it takes the user to enter the correct word ..or if the hiscore table is empty and we reach the top of the array list . 68 .. by the end of the loop. we start with the name and score arrays both empty. So let's cover the theory before tackling the code. we get the length of the word and store it in WordLen. the length of GameWord$ will change.Next loop counts from 1 to the length of the word . the chosen letter is removed from GameWord$ so it won't be chosen again.WordLen = Len(GameWord$) For N=1 To WordLen RandChar = Rnd(Len(GameWord$)-1)+1 ShowWord$ = ShowWord$ + Mid$(GameWord$. A highscore table also uses string array lists .with the last entries on both lists 'dropping off the end'. When the game has ended and we have a final score.
If we want to use slot 4 for example. So entry 10 gets replaced with entry 9..Next loop will be moving the contents of N+1 into N+2 . So. we need to move all the elements of the array down one . DIM HiScoreName$(10) DIM HiScoreValue$(10) PlayerScore = 3500 PlayerName$ = "TDK_Man" Rem Fill Array With Existing Scores For N=1 To 10 HiScoreName$(N) = "PlayerName" HiScoreValue$(N) = Str$((11-N)*1000) Next N Rem ******************************************* N=11 Repeat Dec N ArrayScore = VAL(HiScoreValue$(N)) Until ArrayScore > PlayerScore or N=0 Rem N is now the slot above the one we actually want (which is N+1) Rem so we shuffle all it and all below it down to make room For I=10 To N+2 Step -1 HiScoreName$(I) = HiScoreName$(I-1) HiScoreValue$(I) = HiScoreValue$(I-1) Next I Rem Slot N+1 is now free so fill it HiScoreName$(N+1) = PlayerName$ HiScoreValue$(N+1) = Str$(PlayerScore) Rem ******************************************* Rem Now print out the new list of 10 names For N=1 To 10 Print Str$(N)+". If a higher number is found then the loop is exited with N containing the number of the array element of the slot directly above the one we need to put our score info into.Once a highscore table is created it's saved to disk and loaded when required. Finally we place the players name and score into the respective arrays at position N+1 .leaving slot N+1 (4) free.and in your game.Until loop starts at the bottom of the hiscore list reading the score values in the array until the score found is greater than the score just made by the player ." + HiScoreValue$(N) Next N Wait Key The important code is inside the Rem lines of stars. "+HiScoreName$(N) + " . entry 9 gets replaced with entry 8 and so on until we reach the slot we want..or the counting loop variable N = 0 (at which point all the array has been checked). The Repeat. So how is this done in DB?. 69 . that slot will be N+1 so the last move in the For..including the one we need to fill..
Armed with the commands we've seen in this tutorial. All we have to do is get the current ASCII value of the third character in our health string.one for each of the enemies.HitPoints EnemyHealth$ = Before$ + Chr$(EnemyHealthVal) + After$ Print EnemyHealth$ Wait Key I've kept the coding as simple as possible so you can follow it and see exactly what it's doing. The fourth line look more complicated than it actually is. When we have it. All you have to do is look at strings in a slightly different way. EnemyHealth$ = "dddddddddd" EnemyNumber = 3 HitPoints = 3 EnemyHealthVal = ASC(Mid$(EnemyHealth$.. here's a novel way to tackle a task in many games with enemies or characters which have health points that can vary as you play. reduce it by 3. The first three lines simply set up the variables.each having the value of 100. say you have 10 enemies in your game and their health values vary from 100 (full health) to 0 (dead). So.. All it does is use Mid$() to get the character in EnemyHealth$ which corresponds to our enemy (number 3). As a string is simply a long joined list of numbers .you just need to remember that being a byte.EnemyNumber)) Before$ = Left$(EnemyHealth$. To end this tutorial. d just happens to have the value of 100 in the ASCII table and in the above line there are 10 d's . the maximum size for the numbers is 255. The first 'd' belongs to enemy 1. the third to enemy 3 and so on. we can also do some pretty unusual stuff with strings. you can treat them as such . You can alter the value on the PlayerScore = 3500 line before running the program to confirm that the correct slot is always selected.which you can adapt to other things. The following example may not be the best way to do what it demonstrates. the second to enemy 2. you use: EnemyHealth$="dddddddddd" What!!?? I hear you say.Len(EnemyHealth$)-EnemyNumber) Dec EnemyHealthVal. we convert it to a number using ASC() ..EnemyNumber-1) After$ = Right$(EnemyHealth$. but it serves nicely as an example of the process using strings .just like a numeric array. At the start of your program. turn it back into a character and put it back into the string. 70 ..write them to a file on disk. So what do we do with it? Well. our string currently contains 10 d's .this gives us 100 and store it in EnemyHealthVal. Well. Say you give enemy 3 a smack in the eye and his health is reduced by 3 points.
Multiplying that random number by 2 and adding 1 gives us the position in Pack$ of the two characters for that card.the 9 of Hearts. 71 . the new health of enemy 3 which we convert back to a string with Chr$(EnemyHealthVal) and After$ (the remaining seven characters). If EnemyNumber is 3 then using EnemyNumber -1 with Left$ will grab the first 2 characters. J for Jack. It does this by using RIght$(). I'm not suggesting that this is either the best or fastest method to use for this particular task . the string would be: Hearts$ = "1H2H3H4H5H6H7H8H9HTHJHQHKH" Each card is two characters . I use the same method to deal from a shuffled pack of cards in card games by creating four string variables one for each suit (H. Eg: With a new pack = (104/2)-1 = 51 Get random number between 0 and 51 . Next. The next line needs to grab all of the characters after the third one and store them in After$. When we deal from the pack. Remember. The other three suits are stored as: Clubs$ = "1C2C3C4C5C6C7C8C9CTCJCQCKC" Diamonds$ = "1D2D3D4D5D6D7D8D9DTDJDQDKD" Spades$ = "1S2S3S4S5S6S7S8S9STSJSQSKS" Next we join them together to form a pack: Pack$ = Hearts$+Clubs$+Diamonds$+Spades$ The length of Pack$ is 104 characters long (52 cards of 2 characters each). For hearts. The last important line builds up the new EnemyHealth$ by adding together: Before$ (the first two characters). In this case. (8*2)+1=17 The 17th character in Pack$ is 9 and the 18th character is H so the card dealt is 9H . Len(EnemyHealth$) returns 10 (the length of the whole string) and deducting the number of our enemy (3) gives us the number of characters to grab from the right using Right$(). This equates to: "dd"+"a"+"ddddddd" which you will see if you run the above snippet.I'm just using it as an example of this particular way of using strings. C. D and S).The fifth line grabs the first two characters of EnemyHealth$ into Before$. Q for Queen and finally K for king. we are only interested in the third character (enemy).the second being the suit and the first being the numbers 1 to 9. we pick a random number based on the length of Pack$. we deduct HitPoints (3) from EnemyHealthVal (100) which gives us 97. 10-3 gives us 7 characters which we store in After$. T for ten.let's say the number 8 comes up (the ninth card).
the length of Pack$ has been reduced by 2 so it's now: (102/2)-1 = 50 . When all the pack has been dealt.. I hope it's covered all the topics you wanted it to. 72 . The next time a random number is required.which gets a random number between 0 and 50. Well that's it for this tutorial on strings. restore Pack$ by adding the four suit strings together again and off you go..Once dealt. we use the method used above to remove the 17th and 18th characters from the pack so the card can't be dealt again.
Set Text Opaque Do Text 0. we simply have to divide them by 1000.Str$(Timer()) Loop The value you see is the contents of the timer register and it is continually changing.. so if for example. (maybe it goes dark after playing for an hour. You can have as many independent timers as you like in your programs by using different variables. though timers have many other uses. and fast! . If you need finer timings than one second intervals. This tutorial will show the seasoned DB user nothing new.. 10 or not divide by anything at all to return 10th.Str$(Timer()/1000) Loop 73 .0. then you can divide by 100. All PC's have built-in timers which place values into registers for programmers to access.then the current value of the PC's timer is stored in the variable RetVal. Copy and paste the following code into DB and run it: Set Text Opaque Do Text 0.0. DB has a function called Timer() which accesses the computers timer register and returns values in one thousandths of a second increments. so modify the code as shown below. 100th and 1000th of a second increments respectively. You are also not restricted to a single timer either. Feel free to copy any of the code in this tutorial into DB and run it. For example: T1=Timer() T2=Timer() T3=Timer() Will give you three timers which can be used for timing three separate events. but is instead aimed at the new programmer and looks at the way timers work and how they can be used in your programs.even when your program is not running! This value is not a lot of use. To convert these values to seconds.Timer Tutorial One subject you often see questions about on forums is that of timers. This value can then be used for a multitude of tasks including calling procedures after a set amount of time (as a way to make your programs run the same speed on all spec machines). you use: RetVal=Timer() . on-screen clocks & timers or any other timed events in your programs. The value can obviously be stored in a variable. or a plane flies overhead every 15 minutes). These may be for showing an on-screen display of either time left or time elapsed.
effectively counting the number of minutes elapsed. this program counts the number of elapsed seconds in the variable 'Elapsed' and then checks to see if that value equals 60 (1 minute).255). we need to grab this value into a variable and deduct it from the value of every subsequent use of Timer(). read updated values of Timer() into a 'current time' variable 3. Your program could just as easily call a function or subroutine when Elapsed reaches a given value.Str$(Elapsed)+" " Until Elapsed=30: Rem change this value to alter the length of the timer In your own programs.0. Subtract the 'start time' value from the 'current time' value 4.Str$((Timer()-T)/1000) Loop ELAPSED TIME To create an 'elapsed time' display. In a loop. If it does.0 T=Timer() Repeat Elapsed=(Timer()-T)/1000 Text 0. the variable MinutesPassed is incremented . (or whatever value you set). When the timer hits 60 seconds. the timer goes back to zero and continues indefinitely. In DB. 74 . the basic programming procedure is as follows: 1. In the above example.Now the value changes. Grab the current value of Timer() into a 'start time' variable 2. It's still not totally useful as it will display a random value on every machine. 'T=Timer()' is placed just before entering your main program loop and the line 'Elapsed=(Timer()-T)/1000' is placed somewhere inside your main loop with an If Elapsed= clause immediately following it: Rem Continuous Seconds Counter T=Timer() Do: Rem Main Program Loop Elapsed=(Timer()-T)/1000 If Elapsed=60 Inc MinutesPassed: Rem Or do whatever you need to do in your program Elapsed=0 T=Timer() Endif Rem The rest of your main loop program here Loop Basically. but ticks over at a more useful once per second. Divide the result by 1000 to give the number of seconds elapsed. The result is a second counter that starts at 0 (zero): Set Text Opaque T=Timer() Do Text 0. then what your program does is up to you. the code for a 30 second timer would look something like this: Rem Simple 30 Second Clock Set Text Opaque Ink RGB(255. To fix this.0. The program could then be set do do something specific when MinutesPassed equals a specific number of minutes.255.
COUNTING TIME DOWN Counting down is essentially the same as counting up.Str$(TimeLeft)+" " Until TimeLeft=0 The only differences in this example are the use of a variable called Seconds which contains the number of seconds to count down and the line 'TimeLeft=Seconds-Elapsed' which subtracts the elapsed time from the number of seconds in the level. in our example above. For example. or 20 seconds. The next part of the program is the bit that most new programmers trip up with: Having reset the variable Elapsed to zero. we set the variable Elapsed to equal zero. you need to reset the timer. So. So. Rem Digital Clock Example Set Text Opaque T=Timer() Do: Rem Main Program Loop Seconds=(Timer()-T)/1000 If Seconds>=60 Inc Minutes If Minutes>=60 Inc Hours If Hours>=24 Hours=0 Endif Minutes=0 Endif Seconds=0 75 . then it really is quite simple and only needs a single timer.ie from zero again.Once the target value has been reached and the required task completed. then a slightly modified version of the first example is all that is required: Rem Simple 30 Second Countdown Timer Set Text Opaque Ink RGB(255.255. A PROPER CLOCK DISPLAY If you want a digital clock display. so if say you want to give the user of your program a set amount of time to complete a task.0 Seconds=30: Rem change this value to alter the length of the timer T=Timer() Repeat Elapsed=(Timer()-T)/1000 TimeLeft=Seconds-Elapsed Text 0.0.255). When Timeleft gets to zero then the example program ends. the start value variable needs updating with a new start time. We do this by putting another T=Timer() line inside the If 'Elapsed=' block of code. the formula 'Elapsed=(Timer()-T)/1000' is still using the original start value stored in 'T' and will therefore continue calculating the elapsed time from when the program was first run. The value of Elapsed will then calculate the number of seconds elapsed from this point instead of the old one . placing the result in the variable 'TimeLeft'. when 10 seconds have elapsed. TimeLeft equals 30-10.
The rest of the program converts the numeric variables to strings with STR$() and formats the strings with a leading "0" if less than 10. When the value of the variable 'seconds' hits 60. If you have been able to follow the examples in this tutorial. 76 . it too is reset to zero and 'hours' is incremented. The time is then printed to the screen. In the same way. when 'minutes' hits 60.0. you should have a good working knowledge of how timers work and can go away and implement your own ideas using Timer().Hrs$+":"+Min$+":"+Sec$+" Loop " In this example. 'Hours' is reset to zero when it hits 24 (not 60) as there are 24 hours in the day.T=Timer() Endif Hrs$=Str$(Hours) If Hours<10 Then Hrs$="0"+Hrs$ Min$=Str$(Minutes) If Minutes<10 Then Min$="0"+Min$ Sec$=Str$(Seconds) If Seconds<10 Then Sec$="0"+Sec$ Text 0. the timer is used simply to get the seconds elapsed. it is reset to zero and the variable 'minutes' is incremented.
they are painfully long-winded to create manually. a couple of aspects are quite difficult for the novice to get to grips with. you need to calculate the pixel width in relation to the height. In order to keep this tutorial compatible with both versions. are produced square . though if you have too many on screen at the same time. If you want bigger landscapes than 70x70 tiles. as well as the physical width and depth that you want in pixels. Plus.Dark Basic Matrix Primer Note: Dark Basic Pro has additional matrix options that DB Classic does not have. Dark Basic gives you the ability to create your 'chessboard' with any number of squares (tiles) across and down that you like. simple maths tells us that a matrix cannot have more than 5. Each square can be painted with a texture and each of its four corners raised or lowered to create hills and valleys.000 tiles.000 tiles wide.900 tiles or 9. so with each square tile consisting of two polygons.you get the idea.mainly because the maths is easier. There are clever ways around this which we'll look at later. This means you can create a matrix 1 tile deep by 5.800 polygons . when you do know what you are doing. this would add up to 10. not matrixes). With a square tile-sized matrix.hence the popularity of matrix editors.082 polygons and not be allowed . So.not square. textures are also designed to look best on a square matrix tile. or 10 deep and 500 wide. a matrix in DB is the floor or terrain in your programs and although fairly simple to master.000 tiles maximum .000 limit. DB will start to slow down on lower spec machines. To correct this. no calculations are necessary as the pixel width and height is always the same. If the matrix was one tile bigger .71x71.000 polygons (triangles). you can create more than one matrix in your program and join them together.just in case you were wondering why DB has the strange number 70x70 as a maximum! Most matrices (the plural of matrix. the biggest square matrix you can have in DB is 70x70 tiles which equal 4. What Is a Matrix? Not to be confused with a mathematical matrix. or any combination up to 5. 77 . Very few people type in all the Dark Basic commands to create a matrix . or 100 deep and 50 wide. (as you will see later) .within our 10. these are not covered in this tutorial. Apart from that. and a matrix 10 tiles wide and 5 tiles deep but having the same pixel width and height would have rectangular tiles .it would take days! The actual matrix is a simple grid which you can think of as being similar to a chessboard. A matrix in DB can have up to 10.
In your DB code. your matrix tile width and height would be stored in variables such as TileWidth and TileHeight. Z. The larger white numbers are the X and Z values you use when referring to tiles when texturing. the numbers along the X and Z axis run from 0 to 3.5000. you will see that there are two sets of numbers . the loop will still texture all tiles. You can of course move it. So. for a 4x4 tile matrix. height co-ordinates run from 0 to 4 and the corresponding DB loop would be something like FOR N=0 TO TileWidth (dropping the -1).NOT the Y axis.0 in 3D space.The matrix is created with the command: MAKE MATRIX Mn. You cannot raise any other part of the matrix . we need four commands . For this 4x4 tile matrix. X.0. Altering the height of one of the tiles means altering one or more of the four associated tile corner points.one for each of the four corner points: 78 . Let There Be Height Raising the height of any part of a matrix means altering the value of one of the tile intersect points.as shown in figure 1. Hence the variable Tz instead of Ty in the example above.Tz where Mn is the matrix number. Pw & Pd is the pixel width and depth and Tx & Tz are the tiles across and down.the middle of a tile for example. Setting all four points of a single tile to the same value will raise or lower the tile but keep it flat.4. The matrix is actually 3 dimensional so the X axis runs from left to right and it's actually the Z axis that runs from bottom to top . When a matrix is first created each of these height values is set to zero. That way.Pw. you would use a loop like FOR N=0 TO TileWidth-1. there has to be an extra co-ordinate to handle this. X and Z are the intersection co-ords (the smaller purple numbers in fig 1) and H is the required height of that point.4 This will create the matrix in figure 1 and the bottom left corner is always placed at 0. H where Mn is the matrix number. The smaller purple numbers are used when altering the matrix height and as there are two points along both axis for every matrix tile. (corners). Positive values raise the point and negative values lower it.5000.Tx. so when texturing.Pd. MAKE MATRIX 1. but it isn't advised until you are more experienced. When you create a matrix you tend to think of it most as being viewed from above in 2 dimensions . As such. should the tiles across variable change. Looking at figure 1 again. If we wanted to raise the tile highlighted with a red circle in fig 1.white and purple. The DB command you use is SET MATRIX HEIGHT Mn.
2. 2. so you need to remember to put the . 2. TextureNum and isn't helped by the confusing help files that come with DB that say "SET MATRIX TILE Matrix Number.0: Rem Top Right SET MATRIX HEIGHT 1. Failure to do this after altering anything on your matrix will result in no change on your screen! A Splash Of Colour All the work done so far will have been done on a wireframe matrix.. 20. 3. The height used is a random value between 0 and whatever you enter for the value MaxHeight. you need to texture the tiles you have created.0: Rem Top Left The height value should be a real (floating point) number. Across. Z.where ImageVal is the texture's image number and Across/Down is the texture grid size. but you might as well get into the habit of doing it correctly now so that if at a later date a version of DB enforces these rules. So all that typing and all we have done is raised a single tile up a bit! Imagine the work involved in a 70x70 tile matrix! How would you decide what height values to use? By now you should be starting to realise the value of a matrix editor. 3. you won't have to go back and alter all of your code. it is important to remember to tell DB to refresh the matrix on the screen. To be able to use this command. MaxHeight which will set the height values of ALL intersection points of a matrix with a single command. X. so the command for raising that corner to a height of 20. To make the matrix a little more colourful and realistic. 2. This is done with UPDATE MATRIX Mn where Mn as usual is the matrix number. IMPORTANT! After using any matrix command. So let's split all of that down into more easily followed sections: 79 . 20. 1. This is done with SET MATRIX TILE Mn. X. 1. In practice integers do work.You will see that the 'red blob' tile's bottom left corner is 2 across (X Axis) and 1 up (Z Axis).0 on the end. Z. Another useful matrix height command to know about is RANDOMIZE MATRIX Mn. 20. Down . it is very important that you have told DB to prepare your texture image ready for use with PREPARE MATRIX TEXTURE Mn.0 would be: SET MATRIX HEIGHT 1. 20.0: Rem Bottom Right SET MATRIX HEIGHT 1..0: Rem Bottom Left Tile Corner The remaining three lines for the other three corner points would be: SET MATRIX HEIGHT 1. Tile Number". ImageVal.
though I have also used others. but 16 textures of that size on a 4x4 grid in a texture image would be 512x512 pixels. the worse the quality. each matrix can only have one single texture image associated with it. The most commonly used size is 128x128 as the smaller the texture. Go up to textures 64x64 in size and you can get 16 textures on a 4x4 grid with double the quality. though that image can have lots of individual textures in it. What!!?? Only one texture? No .. (see image below).4x4 and only use the first 5 slots. It doesn't matter if some slots on the grid aren't used. but the smaller they are (and if they are square). On this 4x4 grid image. Use what you like. A 2x2 grid will only hold 4 textures so you would use the next one up .. Older Voodoo cards don't like anything over 256x256 or they throw a wobbler. For example. so what can you do? Easy . high quality textures can prevent an awful lot of users from using your programs! OK. big. First of all. if anyone remembers Equilibrium. 80 . 128x128 or 256x256. 128x128 for individual textures is a happy medium between size and quality. Most new graphics cards would have the speed and memory to handle this size of texture image. This is done by placing them in a grid just like the old chessboard pattern again.ie NOT 3x4 or 4x5. but bear this in mind if you have texture glitches or problems. (sizes in pixels).including a small collection on this web site. 512x512 can be used. Now for the rules which cause the problems. let's say you have 5 textures. like 3x3 and 6x6 and had no problems whatsoever. as too does the grid of textures in it.just reduce the size of your textures. 8x8 or 16x16. Note: These are recommended grid sizes. I think that the important thing is that they are square grids .A texture like the one above is a graphic image and can in theory be any size. Example sizes can be 32x32. then the faster DB will run. 64x64. but remember a little earlier I said that the Voodoo graphics cards are OK unless the texture image is greater than 256x256? You just have to bear in mind that nice. 4x4. You need to know how many textures you need to fit into the image and make the grid big enough to fit them in but keeping the recommended grid sizes of 2x2. This is when the texture size starts to get a little more important. The same size texture image could be used for up to 16 textures before having to move up to the next size.making your texture. OK so far. Your texture needs to be square.only one texture IMAGE. each texture could be 64x64 and still fit in a 256x256 pixel image. You can always go for the sod 'em attitude and not even worry about whether others can use your programs or not! Next problem . but some graphics cards will start to struggle. Having said that. that had an excellent quality matrix and that only used 32x32 textures! DB comes with a good selection of textures and there are thousands available on the web . A 256x256 texture image can contain 64 textures of 32x32 on an 8x8 grid and you'd be surprised how good they can look.
or via code in Dark Basic. you load a texture image which contains more than one texture. you have to edit the image . you need to decide on your texture size. TileNum which will also set all the tiles to a given height at the same time with a single command. you have to create the image all using the correct sizes . it does all the calculations and creates the texture image to the correct grid dimensions and texture sizes to fit . If we want to use the 2x2 example texture image above (and it was called texture. ImageNum. if your texture image was an 8x8 grid of textures. in summary.bmp) we would use: LOAD IMAGE "texture.after possibly recalculating all the sizes! All in all. For example. so we need to use LOAD IMAGE "ImageName". Obviously. but this time from a texturing perspective. we can now use DB's matrix commands to 'paint' the matrix. then FILL MATRIX is automatically called by DB and the whole matrix will be textured without you asking for it! If like our example.bmp into.So. you would use the value 8 instead of 2. you can use FILL MATRIX Mn.which in turn depends on how many textures you use. The textures are usually stored in a BMP file and we need to get them into an IMAGE. Here we have 4 images on a 2x2 grid. TileNum will be a number from 1 to x where x is the number of available textures in the texture image you loaded.bmp". 1. 1 Note the image number is 1 .but only includes the textures from the palette that you actually used and not those you didn't! OK. Let's refer back to fig 1 for a moment below. Height. the process is very similar to the previously used method to alter the matrix heights . Note: Many new users don't realise that if you use PREPARE MATRIX TEXTURE with an image containing just one single texture. this command tells DB to prepare the image for the matrix by cutting it up into two images horizontally and two images vertically. If you add more textures as they are required. 2. Basically. so the values are 2 and 2.either by hand in a paint program like Paintshop Pro. The four resulting textures are numbered 1 to 4 and stored in memory ready for when you need them.it's needed in the next stage. it's not a straight forward process and once again it adds to the argument that you can't beat a matrix editor. the second is the image number we loaded texture.you just need to remember that there is one less co-ordinate to deal with. the texturing process is reasonably straight forward from here on. If you want the whole matrix textured with the same texture. Now we have the texture in an image we can use: PREPARE MATRIX TEXTURE 1. Then. 2 The first 1 is the matrix number. What next? Well. so let's say you now have your texture image with 4 seperate textures in a 2x2 grid. The last two numbers are the grid size X and Y for the textures in the image. PREPARE MATRIX TEXTURE does nothing to the actual matrix. If you want to texture individual tiles on the matrix. Now the image has been prepared. MatEdit allows you to have up to 100 textures in the texture palette and when you use the Build option. 81 . which depends on the size you want your image to be and what grid size to use .
4. TextureNum 82 .6.1. so with this in mind. Nx.remember.5.. Nz.2.2. there are ways to speed up the texturing process without a matrix editor. Z=2 and looking at the texture image.1. Let's assume a 5x5 matrix which has 6 used textures.each one numbered. 4 Once again. Feeding the texture numbers into the matrix with a loop like FOR. that particular tile is at X=2. 2. One method is to create your matrix grid on paper and list your textures . so this gives us: SET MATRIX TILE 1. Z.. Your sketch would look something like the one on the here. our data statement (placed at the end of the program) would look like this: DATA 2.2. the bottom left corner tile is 0.6 and the program code would look something like this: TileWidth=5: Rem Matrix Tiles Width TileHeight=5: Rem Matrix Tiles Height FOR Nz=0 To TileHeight-1 FOR Nx=0 To TileWidth-1 Read TextureNum SET MATRIX TILE 1.NEXT usually counts from 0.6.5. 2. X.3. If you 'colour in' your matrix by putting the number of the texture you want in each grid square.3.2. Looking at fig 1. We will do this using SET MATRIX TILE Mn. However.184.108.40.206. we need to build our data statement up reading from left to right working our way up the matrix starting at the bottom . you can transfer all the numbers to DATA statements then read all the texture values in a loop.This time we are going to texture the tile with the blue blob using the water texture from our 2x2 texture image. TextureNum.220.127.116.11.2. water is texture number 4. we can see that using the larger white numbers.3.3. So. you can see that creating a good looking terrain using this method would be a slow laborious process. We know we are using matrix number 1.
the matrix does! In fact there is a command called SHIFT MATRIX which lets you scroll the matrix rows and/or columns of tiles up. If this is done carefully. you never reach the edge of the matrix whichever direction you travel.0 in space and reposition the matrix so that the character looks like it's standing in the centre of it.without actually moving the matrix itself.5. you can do what many people do . but there's no easy way to know what values to use for each height like you can with colouring textures with pen and paper. Rows and columns which are scrolled 'off' the matrix re-appear automatically at the opposite edge of the matrix.6. there's a small demo where you can wander around a landscape which is 300x300 tiles square! I haven't tried it myself. What's more.3.1. In the downloads section of my web site.but it certainly isn't a huge matrix . The result is a matrix which goes on forever .4. shifting the matrix by such huge amounts looks incredibly jerky and totally crap. but I'm informed by someone who has.2. MatEdit is available from the downloads page on my web site and if you want to read about some of the other things it makes so easy for you.3. that you can wander in the same direction for half an hour and still not hit the edge! MatEdit's Monster Matrix option was restricted to 600x600 so that the whole terrain area would be visible onscreen in 800x600 screen mode. check out the MatEdit page on there. you control your character and he moves across the matrix landscape. If you are clever though.6.2. you may have forgotten that a little earlier I mentioned that there were clever ways to get around the 70x70 tile matrix limit.3. Unfortunately.2.the biggest it can be is 70x70 tiles and with the same landmarks encountered 83 . left or right . you can create an algorithm with SIN/COS to calculate the heights to create nice hills etc. but it doesn't . When the character is moved. slightly more difficult method is to place the character at 0. The Theory Normally. as the matrix is continuously scrolling with the SHIFT MATRIX command.18.104.22.168 Gigabyte of memory and in theory I could create a terrain of such a size that it could take weeks . it may animate and look like it is moving. Another.write your own simple matrix editor if there isn't one out there that suits your purposes. So.. if it has been designed correctly..6 You could use the same loop method to make matrix height setting easier as well. at which point you put the matrix back at its starting position and use the SHIFT MATRIX command. So let's cover the theory first so you can go away and try to write something yourself. As if you didn't know already. down. The fact is that the real maximum size of your terrain using the method I designed is limited only by the amount of memory you have.1.to walk from one side to the other! So how is this done? Well. it's quite a simple idea really and a number of other DB users have improved on the idea since I first did it.2.3. My main PC has 1. it's been such a long tutorial.not hours .1.3.4. it looks like the character is smoothly walking over the matrix.2. Alternatively. what you do is physically move the matrix a small amount a number of times until it reaches the point that the next row or column of tiles is reached. Clever Stuff OK.0. the camera in tow.Next Nx Next Nz Update Matrix 1 Rem Data Statements At End Of Program DATA 2.3.3.
the relevant tile row or column is completely replaced from the arrays. (as it's only a matrix editor. The same method of character control is used as described above by moving the matrix and then using SHIFT MATRIX. not a world editor).000x100. though any size could be chosen. I didn't bother for MatEdit. My idea was to create a 600x600 array with all the height data in it and another one with all the texture data in it.at regular intervals. the player will soon notice what is happening. but the big difference being that just before the SHIFT MATRIX is used. The routines keep track of the character's direction so it knows which rows/columns to update and the terrain doesn't repeat. but it would be a fairly simple process to create another array containing object position data. On screen you have a single small matrix which actually needs to be no bigger than 10x10 tiles if fog is used effectively. so there's no additional slowdown or lag when you are playing. just an amazingly large playing area! 84 .000 arrays and you still only have a 10x10 tile matrix on screen. Do the same thing with 100. There is an example of a never-ending matrix in the downloads section.
20 Create Bitmap 1. implementing a third party solution is made that much easier.990 Color Object 1.RGB(255. Before we start though.128.50. However. Copy and paste the following code in the DB classic editor and run it.3. The two basic collision commands in DB are: OBJECT HIT(ObjNumA. You need a way to stop your character from walking through walls/other characters or dropping through the floor he/she is walking on.1 Make Object Cube 1.Dark Basic 3D Collision (DBC & DBP) Very few games can be written without some form of collision. I've not had cause yet to stretch them that far. ObjNumB) The first command alerts you if object A bumps into object B. This is what the collision commands are for. ObjNumB) OBJECT COLLISION(ObjNumA.1.1000.980 Do If UpKey()=1 Then Move Object 1. having said that.0) Make Object Cube 2.sprites in 2D also have collision. The two parameters ObjNumA and ObjNumB are your main control character's object number (A) and the object you are testing for collision with (B) . the DB collision commands simply alert you when certain objects bump into each other.0.both are integers.0.128 Set Current Bitmap 0 Delete Bitmap 1 Prepare Matrix Texture 1. What's more.0.1 85 . Sync On: CLS 0 Sync Rate 60 AutoCam Off Hide Mouse Make Matrix 1.128.1. In a nutshell. Use the cursor keys in this top-down view to run the red cube into the white one.0.2000.3: Rem Our Cube Position Object 1.20. so I cannot confirm this from personal experience but have to believe that this is probably true.3.1000. This is in 3D by the way . Try them. because it's widely reported that DB's in-built collision commands are neither all that accurate nor particularly fast when put to the test.0) Get Image 1.3 Position Object 2.2000. Once you understand how they work.128 CLS RGB(0.3. I think it's important to get to know how the built-in commands are used.1000 Position Camera 1000. whereas the second lets you know if object A is overlapping object B.0.980 Point Camera 1000. but sprites are the topic of another tutorial. you may even find that the DB collision commands are actually more than enough for your needs without you having to go away and try and learn a new set of commands.128. I have to explain that you will find a number of third party aids to help with collision like Sparky's external dll.
As for the colour flashing.If DownKey()=1 Then Move Object 22.214.171.124.WrapValue(Object Angle Y(1)-2.0) ObjPosX#=Object Position X(1) ObjPosY#=Object Position Y(1) ObjPosZ#=Object Position Z(1) Position Camera ObjPosX#.1000.990 Color Object 1.0.WrapValue(Object Angle Y(1)+2... Try running the following program: Sync On: CLS 0 Sync Rate 60 AutoCam Off Hide Mouse Make Matrix 1.0 Sleep 20 Repeat Until OBJECT HIT(1.3.-0.20 Create Bitmap 1. you can see that the colour of the stationary cube is set when OBJECT HIT(1. at which point the stationary cube is returned back to white. This is similar to OBJECT HIT.0. but returns a 1 if two objects are overlapping.980 86 .2000.50. it flashes black.0.0) If RightKey()=1 Then YRotate Object 1.1 Make Object Cube 1.2)=1 Color Object 2.0) Get Image 1.you need to move your cube completely away from the other to end the collision and then bump back into it again to trigger another hit.3.RGB(255. So how do we know if a collision is still occurring after the initial hit? Enter the OBJECT COLLISION command.128 CLS RGB(0.1.255) Endif Sync Loop End You will have noticed that when you bump into the white cube. However when the program is running and you hit the other cube.0. It isn't triggered again while the two objects are colliding .0.2)=0 Color Object 2.30.1 If LeftKey()=1 Then YRotate Object 1.ObjPosZ# If OBJECT HIT(1. (it's not automatic). In the code.1000.RGB(255.128.1000 Position Camera 1000.2000.3 Position Object 2.3: Rem Our Cube Position Object 1. Passing through the other cube is what should happen because we haven't yet written any code to handle what happens when a collision does occur.it detects the first contact between two objects.0) Make Object Cube 2.2) returns a 0. it turns black then immediately returns to white and you continue passing through the other cube..128.Until loop immediately after this executes continuously until OBJECT HIT(1. The Repeat. that's because OBJECT HIT does just that .2) returns a 1.128 Set Current Bitmap 0 Delete Bitmap 1 Prepare Matrix Texture 1.128..
WrapValue(Object Angle Y(1)-2. we test with OBJECT COLLISION. the command no longer returns a 0 or a 1.255): FirstHit=0 Endif Sync Loop End It's basically the same as the first example apart from inside the main Do. making it HIT(1.2). but instead returns zero or the number of the object that object 1 (our cube) is colliding with! Now that's a bit more useful don't you think?. it returns the other cube back to white and turns the flag back off so the If FirstHit block isn't executed again unnecessarily.Point Camera 1000.2)=1 Color Object 2..2000.255..Loop. With this option. so it was ignored.ObjPosZ# If OBJECT HIT(1.1 If DownKey()=1 Then Move Object 1.980 Do If UpKey()=1 Then Move Object 1. have you noticed the downside? Here we are just testing for collisions between objects 1 (our cube) and 2 using OBJECT HIT(1. But what if we have another object .. we set a flag (change the value of a variable) to denote that a collision has occurred. FirstHit was set to 0 (zero).30. the code in the If FirstHit=1 block is carried out each time around the loop. (only when the objects are not colliding). when you bump into the stationary cube.0) and COLLISION(1.3. The next line checks to see if it returned 0 and when it does. In the If FirstHit=1 block.20.0. Sync On: CLS 0 Sync Rate 60 AutoCam Off Hide Mouse Randomize Timer() Make Matrix 1. we set the variable FirstHit to 1.0) If RightKey()=1 Then YRotate Object 1. Before the initial collision.2) If C=0 Then Color Object 2. While objects 1 and 2 are overlapping.2) and OBJECT COLLISION(1. So now.0) ObjPosX#=Object Position X(1) ObjPosY#=Object Position Y(1) ObjPosZ#=Object Position Z(1) Position Camera ObjPosX#. we don't. it stays black until you move off it.RGB(255.0 FirstHit=1 Endif If FirstHit=1 C=OBJECT COLLISION(1.1 If LeftKey()=1 Then YRotate Object 1. This time.2000.-0.WrapValue(Object Angle Y(1)+2.0.or dozens more objects? Won't we need a line of code for each of them? Well.. But. Remember the ObjNumA and ObjNumB from earlier? A useful little option is to use a 0 (zero) for ObjNumB. When this is set to 1. this will always return a 1. This is because once the initial 'hit' is recorded.0). So let's see how to detect collision with many objects when you don't know what object numbers they are.20 87 . In this case. we know how to detect a collision between two objects. no actually..
128.0.3. then use the Move command to move it the required amount in the direction it is facing. So we now know how to detect when we hit another object .255.3 Position Object 1.WrapValue(Object Angle Y(1)-2. The second is to use variables for the X.-0. then it's possible to still be colliding with one object when you hit another.980 Point Camera 1000.1000+(Rnd(60)-30). It doesn't matter that the object you hit is different to the one you are currently colliding with. if two objects are close to each other. Next we have to deal with it because as you have seen.WrapValue(Object Angle Y(1)+2.0.4 If LeftKey()=1 Then YRotate Object 126.96.36.1990 Color Object 1. This is normal and won't be a problem when we actually come to handling the collision later.1. This is because if OBJECT COLLISION() is returning any value other than 0 then OBJECT HIT() is not tested for.1000+(Rnd(60)-30) Next N Position Camera 1000.128.60. There are two main ways to move a 3D object around: The first is to turn it in the required direction.NowColliding) Rem And if not.3.60.0) ObjPosX#=Object Position X(1) ObjPosY#=Object Position Y(1) ObjPosZ#=Object Position Z(1) Position Camera ObjPosX#.1.0.1000.0) If RightKey()=1 Then YRotate Object 1.4 If DownKey()=1 Then Move Object 1.Create Bitmap 1. What method we use depends on the method used to move the object we are controlling.RGB(255.128 CLS RGB(0.1 Make Object Cube 1.0) For N=2 To 11 Make Object Cube N.128 Set Current Bitmap 0 Delete Bitmap 1 Prepare Matrix Texture 1. recolour cube to white and turn off flag If C=0 Then Color Object NowColliding.980 Do If UpKey()=1 Then Move Object 1.0) If H>0: Rem collision detected Color Object H. 88 . Y and Z position and use Position Object along with the Rotate commands to move the object about.3 Position Object N.128. the hit on the second object is not detected. detecting the other object doesn't stop you moving through it.255): NowColliding=0 Endif Sync Loop End Important Note: In this example.0 NowColliding=H: Rem Set flag to number of object we are colliding with Endif If NowColliding>0 Rem Test to see if we are still colliding C=OBJECT COLLISION(1.ObjPosZ# H=OBJECT HIT(1. In such a case.3.0) Get Image 1.0.RGB(255.regardless of object number.
1.3.1 Make Object Cube 1.20 Create Bitmap 1...0. Sync On: CLS 0 Sync Rate 60 AutoCam Off Hide Mouse Make Matrix 1.Speed# If DownKey()=1 Then Speed#=0-.Our examples so far use the first method.0. When a game character hits a wall at an angle. However.1000. the actual collision effect used here when the cube hits the wall isn't the best for a game.WrapValue(Object Angle Y(1)-2. then you would use OBJECT HIT() to detect the number of the object you just collided with.3.0.0) If RightKey()=1 Then YRotate Object 1.1000 Ghost Object On 2 Position Camera 1000.80.0.128. if some of your objects are not solid.3.0.3: Rem Create a wall Position Object 2.4: Move Object 1.3.4: Move Object 1.WrapValue(Object Angle Y(1)+2. If this is the case .0)=0 Endif Sync Loop End You will notice first of all that this example does not use OBJECT HIT(). before deciding how to deal with the collision. Here's the next example.128.990 Color Object 1.20.RGB(255.0) Make Object Box 2.Speed# If LeftKey()=1 Then YRotate Object 1.1000. You don't have to if you know that all the objects in your program are simply objects that you cannot pass through. the game's continuity and smooth running is interrupted if the character comes to a dead halt.you just stop your object from passing through any them.ObjPosZ#-40 Point Camera ObjPosX#.Object Position Z(1) Until OBJECT COLLISION(1.50.950 Point Camera 1000.0) Get Image 1. This makes it easy to handle basic object collision because we can simply use the Move command to move back to the very last position before by using a minus value.20.3 Position Object 1.0) ObjPosX#=Object Position X(1) ObjPosZ#=Object Position Z(1) Position Camera ObjPosX#.0.1. Also.0.3.0.0. The alternative is 'Sliding Collision' which is no more realistic when you think about it when was the last time you ran into a brick wall and 'slid' along it .Object Position Z(1)-40 Point Camera Object Position X(1).128 CLS RGB(0.like this program .990 Speed#=0 Do If UpKey()=1 Then Speed#=0.ObjPosZ# If OBJECT COLLISION(188.8.131.520.2000.but it does look and feel much better in a game.50. 89 .0-Speed# Position Camera Object Position X(1).0)>0: Rem collision detected Repeat Move Object 1.128 Set Current Bitmap 0 Delete Bitmap 1 Prepare Matrix Texture 1.
Sliding collision isn't that much different to what we've just been doing. then the collision box moves with it. We also create a new variable called Speed# with which to move our cube around. The basic difference is that we have to use collision boxes. So. but only if we are moving forwards! The cursor down key lets us back away from the wall. These wrappers can give you feedback on collision and you simply size them to fit the object you are applying them to. we use a Repeat. in the loop we test to see if a collision has been triggered with: If OBJECT COLLISION(1.. First of all. at which point the loop is exited. moving it 0-Speed# ALWAYS moves it in the opposite direction by the same amount. but it's better to use the X. we have to move the cube backwards. to prevent this from happening.. away from the wall.x1. We also place the camera a little further 'South' of our cube so we can see the wall at a better angle. we use a variable to store the current direction. we use other DB commands to tell us where to go back to. in the example. the backing out routine kicks in and you are instantly 'transported' to the other end of the wall! For this reason. As this is in a repeat loop. When we press cursor down. The new command we are going to use is MAKE OBJECT COLLISION BOX Object Number.. We don't just move back to the last known good position. there's a little more work involved. 90 . Let's go through them. we create a wall as object number 2.flag and looks rather complicated with all those parameters. But believe me it's not really..z1.z2. In the event of a collision therefore. it's repeated until the cube is no longer in collision with the wall.0-Speed#. If you move the object.The changes to this program from the last one are a little more substantial too. So.y2. you can clip the wall with the corner of the cube when rotating it .. Collision boxes are invisible 'wrappers' that you apply to objects. This method isn't perfect because at certain angles. Y and Z variables for moving objects around if you need collision rather than Move. The reason for this is that when a collision is detected. Speed# is set to a positive number so the cube moves forwards.Until loop moving the cube back with Move Object 1. which as mentioned previously moves the cube back.y1.0)>0 If our cube hits anything.in which case. when the collision is detected we would move the cube backwards further into the wall. When we press cursor up. but what if we bump into the wall while moving backwards? Well.x2.. the code in this block will be executed. The advantage of this is that it doesn't matter what direction the cube is heading. So let's now take a look at Sliding Collision. Speed# is set to a negative number so the cube moves backwards.
the values run negative to the left and positive to the right. X1. an example. our object (the wall) is green. In the same fashion.Object Number Obviously this is the number of the object that you are creating the collision box for. 91 . Here's a small image to show you what exactly is happening: In the image.0. So. If the X axis is 0 at the centre of the object.Y.10 and the CENTRE of this box is positioned at 0. As an object can be placed anywhere in your world with the Position Object X.Z OFFSETS which assume that the object is at point 0. then the collision box moves with it? That's because the collision box is attached to the object number.0 in space. the collision box would still be at the old location. when you define a collision box. X2. We make the object with Make Object Box 1. we can't use 'real' X. Eg: Width (X) = 100/2 = 50 which makes -50. Notice that the values are the negative equivalent of the boxes X. Y and Z dimensions divided by 2. We repeat the process for the Y and Z axis. 50 tall (Y) and 10 Deep (Z). this is exactly in the centre of our wall.0 in space.Z command.0. With a box 50 tall.Y.Y1. instead. is position 0.100.Z1 OK.Z world location co-ords to define the collision box because if we moved the object.0. so this is used for the X2 parameter.50.Z2 If we follow the X axis from the centre 'til we reach the right edge of the box we find that it is at 50.Y. remember that I just said that when you move an object. As you can see. you use X. The dimensions of the wall are shown in blue. the X offset to reach the left edge of the box along the X axis is -50 and this is used for the value X1. This is a neat method as we can use negative and positive values which remain true wherever the object is placed. Y and Z world axis are the black lines and where they all intersect. Therefore.0. Imagine a box or wall 100 wide (X). Y2 is 25 and Z2 is 5. OK.Y2. Notice this time that if X1 = -50 then X2 is 50 and that if Y1 is -25 then Y2 will be 25 and so on. The X. Y1 is -25 and with a depth of 10 then Z1 is -5.
we check for the cursor keys and move the cube in the required direction or rotate it and then grab the object's new position into the variables XPos#.100 Make Object Collision Box 2.-2.Y.184.108.40.206.50. once these offsets are defined.2000. The value returned is the number of the object we are banging our head against.-2 If Leftkey()=1 Then Yrotate Object 1.128 CLS RGB(0.Z location.0.1.0) and if the result is anything but a zero then we've hit something.5.5.0.-25.0.1 Position Matrix 1.0 Do If Upkey()=1 Then Move Object 1.0) Make Object Collision Box 1.Get Object Collision Z() Endif Position Object 1.0 : Camsmooth#=3.ZPos#.0. The important thing to realise using this method is that as mentioned earlier.Get Object Collision X() Dec YPos#.ZPos# Angle#=Object Angle y(1) CamDist#=40.2.5. whereas real X.5.Y.75.5 Color Object 1.5. We next use 92 .150.-1000 Make Object Cube 1. even if the wall was moved to another X. Easy eh? Let's see the code: Set Display Mode 800.2. YPos# and ZPos#. We then test for a collision with Object Collision(1.16 Autocam Off Sync On: CLS 0 Sync Rate 60 Hide mouse Make Matrix 1.25.Wrapvalue(Object Angle Y(1)+4) XPos#=Object Position X(1) YPos#=Object Position Y(1) ZPos#=Object Position Z(1) Rem The sliding collision bit If Object Collision(1.2 If Downkey()=1 Then Move Object 1.128.CamSmooth#.-5.25.Wrapvalue(Object Angle Y(1)-4) If Rightkey()=1 Then Yrotate Object 1.Get Object Collision Y() Dec ZPos#.0 Make Object Box 2.0 XPos#=0: YPos#=2.RGB(255. The origin for the offsets is based on the X.-2.YPos#.600.5: ZPos#=0 Position Object 1.0. in the main loop.Y.0 Sync Loop When the program is running.20.1.Z world position of the Object whose number you supply.5. the offsets would still define the collision box correctly.-75.-1000.The red distances are those used for the offset parameters.5.0.Angle#.10 Position Object 2.-2.CamHeight#.128 Set Current Bitmap 0 Delete Bitmap 1 Prepare Matrix Texture 1.5 Set Camera To Follow XPos#.0)>0 Dec XPos#.0) Get Image 1.Z locations would not.0 : CamHeight#=YPos#+10.CamDist#.20 Create Bitmap 1.XPos#.YPos#.2000.2.
If you cast your mind back to when I was describing how you use the Make Object Collision Box command. that's the end of this tutorial on Simple DB Collision and armed with this new knowledge. Remember? Well. Get Object Collision Y() and Get Object Collision Z(). we decrement them from our object's X. 93 . Y and Z directions. Y and Z positions and use Position Object to place the object in its new position. armed with the amount to back up in the X. Well. I mentioned that the last parameter was called flag and that if you set it to 1 then the collision box rotated with the object. you can use the Get Object Collision functions. if you set that flag to zero.three functions we've not seen before: Get Object Collision X(). you can investigate the many other collision commands DB supplies you with. Remember though that they ONLY return data if that little flag is set to 0! So. These return the sliding collision values which we use to back up our object. The rest of the code simply handles the camera to follow the cube.
5."M" Rem Main Program Loop Do If Leftkey()=1 Then Dec BasePosX.not to demonstrate good programming practices.580.600. we will use the Text X. 3. 2. we'll cover the less realistic environment of the 2D shooting problem. that's the problem. I have to admit that when I started with DB I spent many hours trying to make a bullet move fast enough to look right. Whatever the type of bullet. an image. 6. and then plotting it's course between the two points.Y command and ASCII characters . where it is being aimed. Once you understand how these programs work. In 3D games. Set Display Mode 800.remember the method is basically the same with images and sprites. When you fire a bullet from a gun or a sniper rifle. repeat from number 2 End firing routine The actual X and Y positions of the bullet on the screen are held in variables so we can alter them and move the bullet.A Basic Introduction (DBC & DBP) Many games in both 2D and 3D require the ability to shoot .something no more difficult than locating where a projectile is being fired from.the fatal one of not thinking about what happens in real life. 4. which I'll cover in a different tutorial.. the process is as follows: 1.. the bullet moves that fast you don't see it.2D Shooting . To keep it simple. most beginners make the same mistake as I did . But.4 If BasePosX<0 Then BasePosX=0 If Rightkey()=1 Then Inc BasePosX. 2D Shooting In a 2D program the bullet can be a sprite. 7. or even an ASCII character. Let's try a very simple example of the type that most beginners usually make. Note: The example programs have been written to demonstrate the process as clearly as possible . Place the bullet in front of the gun barrel Calculate the next position of the bullet Replace the background for the old bullet position Place the bullet in the new position Check to see if hit the enemy or left the screen If 'no' to both questions at number 5. So why waste time in your programs trying to show what you shouldn't be able to see anyway? First though. you can optimize and improve on them.16 Sync On: CLS 0 Sync Rate 60 Hide Mouse Set Text Opaque BasePosX = 400 Text BasePosX.4 If BasePosX>788 Then BasePosX=788 CLS 94 .
we decrement or increment this variable. (Some languages have Boolean variables which can be set to True or False. the base freezes while the bullet is on the screen.. Next N Return Essentially. the keen-minded of you may have a question: "If we do that. we have a loop which calculates the bullet start position based on the current base position then goes around a loop decrementing the bullet's Y position."|" Sleep 1 Text BulletPosX. To solve this problem. printing and erasing the bullet on the screen. when the space bar is pressed. Usually this would be by setting the variable to 0 (zero) if it hasn't and 1 if it has. you'll see there's a major problem: When you fire. in this tutorial we'll just use a normal integer variable set to 0 or 1 for the same result). A flag simply conveys a true/false message and in programming terms is a variable which we use to store whether something has been done or not.at which point the flag is set back to 0 (zero). Easy eh? 95 . we gosub the MoveBullet procedure only if this flag is set to 1. In that." " Rem Check to see if we hit anything here and if we did Rem handle it. But because we learn from our mistakes. we use something called a 'flag'. seeing the wrong way to do something makes the right way easier to understand."M" If Spacekey()=1 Then Gosub MoveBullet Sync Loop MoveBullet: BulletPosX = BasePosX + 5 BulletPosY = 580-16 For N=BulletPosY To 0 Step -5 Text BulletPosX.Next loop. In our main loop.N.. So. we really need to get rid of the For N=BulletPosY To 0 Step -5 loop which 'traps' our program while the bullet is moved. When the space bar is pressed. But. won't the bullet be firing all the time"? Well the answer initially is yes. we set a flag to 1 to say a bullet has been fired and it remains 1 until the bullet no longer exists . we gosub the MoveBullet procedure. clear the screen and print our base (the letter M) in the new position. To do this we have to keep track of it's current position manually as we no longer have the variable N from the For. So. BasePosX is integer variable used to store the horizontal position on the screen and when the left and right cursor keys are pressed. That's because this little program isn't the correct way to do this.N. if you run the above code.580. but as DB Classic does not.Text BasePosX. At this point. This is done by calling the MoveBullet procedure EVERY time we go through the main program loop and move the bullet a bit each time.
a delay is used in the procedure (Sleep 1) so the bullet can be seen.4 If BasePosX>788 Then BasePosX=788 CLS Text BasePosX..580.BulletPosY. each time we do. Set Display Mode 800.4 If BasePosX<0 Then BasePosX=0 If Rightkey()=1 Then Inc BasePosX. Next. but keeps the example nice and simple for the tutorial. Reset flag if bullet is done with If BulletPosY<=0 Then FiredBullet=0 Return In this example.BulletPosY. the bullet currently moving up the screen starts again from the beginning! Stopping this is easy.But another problem then creeps in. we calculate the bullet start position and turn on the 'fired bullet' flag. You'll also notice that there is also an 'Else' section.580. All we do is tell the program to ignore the space bar if a bullet is in flight. if the flag has been set.."M" If Spacekey()=1 and FiredBullet=0 FiredBullet=1 BulletPosX = BasePosX + 5 BulletPosY = 580-16 Endif If FiredBullet=1 Gosub MoveBullet Else Sleep 1 Endif Sync Loop MoveBullet: Dec BulletPosY. This isn't good practice. so long as there isn't a bullet already in flight. This is because when you fire a bullet. If we keep pressing the space key."M" Rem Main Program Loop Do If Leftkey()=1 Then Dec BasePosX. As we already have a flag which equals 1 when a bullet is fired.16 Sync On: CLS 0 Sync Rate 60 Hide Mouse Set Text Opaque BasePosX = 400 Text BasePosX. we just tell the program to ignore the space bar unless the bullet flag equals zero! So let's modify our above example." " Rem Check to see if we hit anything here and if we did Rem handle it. we gosub the MoveBullet procedure which moves the bullet a bit then immediately returns to allow the user to move the base. 96 ."|" Sleep 1 Text BulletPosX. when the space bar is pressed.5 Text BulletPosX.600.
this If. In the MoveBullet procedure.16 Sync On: CLS 0 Sync Rate 60 Hide Mouse Set Text Opaque BasePosX = 400 Text BasePosX. Also.8 If BasePosX<0 Then BasePosX=0 If Rightkey()=1 Then Inc BasePosX. when we don't know how many bullets there are if any! This is done with arrays. then come back when you know how they work. OK. we go back to using a loop and use the flag array to decide if the respective bullet should be displayed. our flag from the last example can no longer be a single flag ."M" MaxBullets = 20 Dim FiredBullet(MaxBullets) Dim BulletPosX(MaxBullets) Dim BulletPosY(MaxBullets) Rem Main Program Loop Do If Leftkey()=1 Then Dec BasePosX.. So we create a variable for the maximum number of bullets and use this value to DIMension arrays to hold each bullets X and Y position and represent each bullet's 'fired or not' flag. Let's see the code: Set Display Mode 800.if there isn't then do use a delay'.600.. We also add another procedure for the creation of a new bullet. but "wait!" I hear you cry. but you need separate variables to store the X and Y screen positions of EVERY bullet.one when no bullet is visible and slower when one is.580.8 If BasePosX>788 Then BasePosX=788 97 . This task is exactly what arrays were designed for so if you don't fully understand arrays. "you can only fire a single bullet and I want more!" Well. Multiple Bullets In the last example it gets a little more complicated as we need to keep essentially the same method. go and read the tutorial which covers them now. The result is that the base moves at two speeds . In this next section I have to assume that arrays are not a mystery to you as I make no attempt at explaining them. but more on that after the example code.Else.This also means that when there is no bullet being fired the program runs quicker. Before we start. for multiple firing the process is just the same. In a nutshell it means that the base moves at roughly the same speed whether firing or not. that covers simple 2D firing.there has to be one for each possible bullet. we also need to decide on a maximum number of bullets to display at any one time.Endif section basically says 'if there is a bullet on screen then don't use a delay .. So. but handle more than one bullet in the MoveBullet procedure.. Arrays are covered in my Beginners Guide To Programming Part 1 tutorial.
when bullet 1 is in use. the Exit. turns the FiredBullet() flag on (sets it to 1). if it was the first bullet and there were a maximum of 10 bullets. you can't fire any more until at least one has hit something or gone off the top of the screen. When we press space."Bullets On Screen: "+Str$(BulletCount)+" " Sync Sleep 1 Loop MoveBullet: For N = 1 To MaxBullets If FiredBullet(N)=1 BulletPosY(N)=BulletPosY(N)-20 Text BulletPosX(N)."M" If Spacekey()=1 Then Gosub AddBullet If BulletCount > 0 Then Gosub MoveBullet Text 0. this routine finds the first empty slot in the array. for this explanation. The next line. then when you have fired 20 bullets. think of the array as one of those ammo belts. it equals 0. If you remember. stores that bullet's X and Y start position and increments the variable which holds how many bullets are currently in use.BulletPosY(N).CLS: Text BasePosX. when the space key is pressed. There are also a couple of changes in the Main Loop worth mentioning. For example.0. Each bullet is in a slot and once a bullet has been fired. then FiredBullet(1) equals 1 and when it is not."|" Rem Check For Bullet Going Off Top Of Screen Here And Deal With It If BulletPosY(N) <= 0 FiredBullet(N) = 0 Dec BulletCount Endif Rem Check For Hit Enemy Here And Deal With It Endif Next N Return AddBullet: For N = 1 To MaxBullets If FiredBullet(N)=0 FiredBullet(N)=1 BulletPosX(N) = BasePosX + 5 BulletPosY(N) = 580-16 Inc BulletCount Exit Endif Next N Return You can alter the maximum number of bullets by changing the value of MaxBullets. it's slot is empty and a new bullet can be clipped into the belt at that position. the first slot (1) 98 .580. but as a continuous loop containing 'MaxBullets' bullets..Next loop at that point without unnecessarily running through the whole loop. Note: Remember the machine guns in war movies where the ammunition is on a 'belt' and is fed into the weapon from a box on the floor next to it? Well. basically exits the For. First of all. If this is set to 20.. we now Gosub AddBullet where we loop through every element of the FiredBullet() array looking for an empty slot..
Next loop which counts from 1 to the maximum number of bullets you can have (MaxBullets). placing an Exit inside the block when an empty slot is found saves our program a lot of wasted testing. at this point. This is another similar loop but this time. you can never create more than the required number of bullets. (or as we are using arrays. If the bullet goes off the top of the screen or hits something (that's the bit you can write yourself). then as we are using arrays.is used and continuing the loop testing the other 9 would be a waste of time. ever get the dreaded 'index out of bounds' error)! 99 . the array flag is used to say whether the bullet is drawn or not. If the space is pressed again while another bullet is on screen.. Gosub MoveBullet is called. then we set the array flag back to 0 to show that the slot is available for use again and decrement the BulletCount variable. So. another one is created and as this is done in a For. BulletCount equals 1 so in the main loop. OK.
So. of course. fire and neon lights. those four steps: 1) 2) 3) 4) Load the font file into DBP Cut the fonts into single character images Trim the wasted space to the left and right of each character. Using Sprites Using Textured 3D plains This tutorial will deal with the first method but it is easy to take the code and change it if you would rather have 3D plains. There are two ways we could display bitmap fonts. Anyway. Text Commands. then skip ahead to the end of this tutorial where I will tell you the basics of how to use them.BitMap Font Tutorial by Craig Chipperfield Bitmap fonts. the ‘prettiness'. before we look at bitmap fonts it is important to look at ‘normal'. The advantage of using them is. what are Bitmap fonts? A bitmap font is a collection of images that represent text characters.. 1. you see. Other than that. 100 . it is a little more complicated than a simple TEXT or PRINT command to use true-type fonts but I think you will agree.. or True-Type fonts. and because they are images they can be made to look as graphically pretty as we like. Now that we have got our bitmap fonts and we have established that we are going to display them using sprites there are four steps to getting them on screen. it is not always necessary to go through step 3. single coloured text then we need to use bitmap fonts. this tutorial (and the accompanying code) will make things much easier for you.. Get them on screen Depending on which bitmap fonts you are using. wait for it . If you don't want to know the technical details of how the bitmap fonts are loaded and displayed but instead you just want to get them into your project and working. Using these commands we can load in any font that is in the windows/fonts directory and set its point size. DarkBASIC Professional has a number of text commands which can be found in the help file under the section . underline or italic and we can change its colour.. that look like they are made of metal. which is why all my fonts use the trimming method. So. So. let's look at those four steps in a little more detail. there is little else we can do to change the appearance of the fonts. what are they and what can you do with them? Well. The disadvantage is that they are a little trickier to set up and use but don't worry. or indeed any appearance other than plain. If we want to make fonts like in the title of this tutorial. You still here? In that case. the end results are worth it. 2. you obviously want to know how this all works. Many bitmap font files have each character the same width but I think that it looks ugly to have a ‘W' the same width as an ‘I'. set it to bold.
Repeat those steps until all the characters have been made. Firstly transfer the font image into a memblock. brilliant! Step 3: Trimming the wasted space from each side of the characters. Using a method like that means that we can load in as many bitmap fonts as we like and easily display the one we want simply by referring to it by name. then copy an area of the memblock into a second one and make an image of that. Again we will use memblocks for this and look at each character in turn. The font file is a simple image file. hide etc. we simply store a variable to represent the left edge of the character and another variable for the width of the character. We can use these values later in the display code to correctly position each character. In the code provided. To display the fonts. all we have to do is use the LOAD IMAGE command. fontStyle = LoadBMfont( filename ) This function loads a Bitmap font. which is very helpful if you load in more than one Bitmap font. steps 1-3 are all carried out with a single command: fontStyle = LoadBMfont( filename ) fontStyle is a variable used to represent each style of bitmap font. We continue with that until we do hit a non-transparent pixel and then repeat from the right. cuts the individual images and trims the waste space from the edges. Step 4: Displaying the fonts. create a sprite for each character. stringID = DrawBMfont( X.. Step 2: Cutting up the fonts into single images. the entire string in one go without having to 101 . fontStyle.Step 1: Loading the font file. Kern ) Used to display the Bitmap font on screen. This is all taken care of in the provided function and is best explained by reading the code. How to use the supplied functions (This is the bit to jump to if you have just skipped the techno waffle) InitialiseBMfonts() You will need to call this at the start of your project but after any display setting commands. we need to find the next available sprite number. You can use this reference to show. At the end of this tutorial I will provide very simple steps to get bitmap fonts into your projects without having to understand any of it . You can see how this is done if you download the code but don't worry if you don't understand memblocks. stringID is a reference to the Bitmap string. store the sprite number for the first character and store the length of the string. to load it. string. The best method of doing this is to use memblocks so that we can retain any transparency in the image. Y. It sets up variables and arrays ready for use in the other Bitmap font functions.. Instead of actually trimming the image file down. So. we can safely say that we have found our first bit of wasted space. We can look at each column of pixels from the left side and if there are no non-transparent pixels. fontStyle is a variable that you can use to reference a Bitmap style.
BMstring is the ID of a previously created bitmap string. Kern ) This function will change any or all aspects of your bitmap font string. X & Y are the screen co-ordinates of the top left of the string. S is the string to display. sizeY ) The above functions will use the associated sprite commands on all of your bitmap string simultaneously.reference each sprite image individually. sizeX. X. It doesn't have to be the same as it was though and you can use this function to change the style of font used. S as string. Y ) This will position a previously created bitmap string at the X and Y screen co-ordinates specified. Y. hideBMfont( BMstring ) showBMfont( BMstring ) scaleBMfont( BMstring. String is the string to display on screen. scale ) sizeBMfont( BMstring. you can use this function to change the contents. It doesn't have to be the same as it was. 102 . This function can be used to alter it. X. X is the screen X co-ordinate to display the string. positionBMfont( BMstring. fontStyle. alterBMFont(BMstring. Kern establishes how much space to leave between each character. Kern is used to establish the x distance between each character. Kern Again. fontStyle As in the DrawBMfont function this variable is the style of Bitmap font to use and MUST already be loaded. Y is the screen Y co-ordinate to display the string. Ideal if you bitmap string is constantly changing (like an ingame score for example). fontStyle is the reference to the previously loaded Bitmap font.
It's also important to visualise the look and feel of the game too. but in a way that will aid us as we progress. Icons help me identify the different aspects. and the rest of the journey is far easier. This map contains concepts.Space Invaders. It's a lot more complex than Pong. game components. but it will still help to ascertain positioning and style. or whatever the current buzzword happens to be. Design! I cannot over-emphasise how important designing your game is before you start. "mindmap". as we know what to expect. Here is a map of all the components we need. but right now you'll have to go with mine. including asset management. so let’s get started! Design. Picking a game that is so familiar will help to illustrate the design process. With Space Invaders it's not difficult. and resources. Step 1 is to "brainstorm". methods.Space Invader Tutorial Building a Framework This month we're going start a small series of tutorials that will see us create a complete game . but all of the ideas are thrown onto the page for reference. 103 . Design. Get it right now. There's no room for chitchat. This is far from structured. timing and various other topics. It also covers a lot of the concepts we've studied over the past few months. Everyone finds their own comfortable way of fleshing out the game idea. in no particular order of importance. but not too involved to cloud the tutorial with intricate mathematical formulas and highly advanced techniques.
104 . Let's start by thinking about the broader picture: Initialise() Menu() PlayGame() Exit() We have our 4 broadest areas. Download the tutorial file. being able to isolate individual functions to debug will dramatically improve your ability to locate. wherever there is a complex or repetitive routine. As we will see later. even the most finely tuned of minds need relieving of the mundane task of remembering. but functions have one major advantage . and build them up into fullyfledged game pieces later. Note that the number of comments at this stage outweigh the lines of actual code.they are self-contained. fix and move on swiftly. At this point in the process. and easy to maintain. it isn't necessary to create the complete structure. when things don't go to plan (an inevitable fact of programming). It is our framework for the game. and breaking it down into its smaller components. It is essential that everything is well documented for your own benefit. it can be added as another unit of code in the form of a function. it matches the way the human mind processes information and it produces a very neat and easy-to-follow process. and break it down a little further: InitialiseGame() PlayLevel() EndGame() Again. PlayGame(). Functions are the perfect partner when writing making it easy to read. Later on down the line. install and view example 1. Let's take the first step in writing the code. Rather than delve into the details of good programming practise. let's break down InitialiseGame() to a third level: LoadObjects() PositionObjects() LoadSounds() InitialiseData() And so we can proceed. and once again it is important to get this right at the start of the programming process. We will add much more as we progress. As we progress. not part way through. This top-down approach makes designing on-the-fly very easy too. The flow of the program can be defined in functions. Subroutines also have their place. it is also a fantastic way to test ideas on a basic level. to allow the creative process to evolve unhindered. It's very logical.Structure Maintainability is a very important notion. now let's take one of these. we'll dive straight in and see it in action. the design allows us to improvise as we code. taking each section.
Our delayed gratification will be rewarded with a fast transition from very little to a fully fledged. all of this effort will be worthwhile in the long run. What we need to do now is start the application breathing. such as player and opponent variables. it is the data that drives and feeds the application design. The Story So Far The amount of effort to date. Scalability of the data is the key to allowing a game to be easily modified. and a great deal more. Once again. Likewise. Now is the time to put the data structures into place. We will take advantage of User Defined Types to collate associated information. the task is well under way. we could discuss the theories and concepts around data storage and manipulation ad infinitum. it is important that we maintain game information in an orderly and efficient manner. The logic can also be tested to a certain extent. This has been discussed in a previous article. Standing on 2 Feet We still have a long way to go before there's anything visible to show for our efforts. When building business applications.Game Data Behind the action of any game is a mass of data. But in terms of the total effort required to complete our Space Invaders tribute. and also a static backdrop which will be a permanent feature of the end product. which now includes the data types and arrays that we initially require to build our game. the Init() function is in our framework and that is the focus of our attention now. But believe me. player. the initialisation isn't necessarily finished right now. We've accommodated the general operation. positions. screen size etc). We already know where to start. At this stage. object information. Putting the data in the initialisation routine makes it extremely easy to locate later when the game needs adjusting and fine-tuning. lives. Remember. Let's recap on the steps taken so far: 105 . but only time will reveal what else we require. easy to maintain game in a very short space of time. Let's set up the environment (Sync rate. in relation to the observable rewards are poor to say the least. invaders. by running the compiled program. which may be worth reviewing at this point. Stage 3 includes all of these components. The result should be a simple backdrop. ready to complement the game to come. but instead take a look at the Stage 2 example. bases and everything else we see the need to monitor and control. the program can be compiled to prove the code is syntactically correct. scores. and put some default data in there for the player and invaders. We will use arrays to make a scalable solution.
building your game from the anchor-point of a menu is good practise. Notice especially that our game is already working. bringing to life each part of the game one by one. well polished models. including gameplay area. and will create the code in a way that allows us to replace simple building blocks with the finished article at a later stage. I know we already have a menu() function ready and waiting. bear in mind that this is the ultimate goal.The supporting data components are not only in place. Stage 4 shows our menu in place. Rapid Development As discussed in the competition programming article. In fact. We'll be using a method that provides the flexibility to test each component in turn. We will. enemies. Visuals . player. our presentation to the player. the complete. bases and interface. it's already working. In the meantime. compiled version of the program is included in the download to give you a taste of what we will achieve in the forthcoming instalments. it simply doesn't do anything visible or useful just yet. Study this small piece of the game carefully. Programmatically. Our top-down approach facilitates the calling of different parts of the overall application. ensuring the development is smooth and precise at every step. We will use a simple graphic and add a couple of keyboard-driven options. In our wellstructured functionalised program. 106 . The Plan Ahead What is next in the creation of our game? Visually. scoring system. logical progression. we will not be adding fantastic. working logic is the prime aim at this point. and adding the menu is so effortless. supporting structures and data have been mapped out in visual form. albeit in draft form. proving that our concept is workable and codable. it's time to put the logic in place that will allow a simple progression. and bases. Our previous hard work is already paying off. and simply continuing to run as normal on the return.The game flow has been mapped out into the initial layers of a well-structured program.The game idea.There is a rough draft of the way the game will be presented. and you will understand how the rest of the game will be built up part by part. however. Data Structure . enemies. components. with navigation and expansion of the code simply and effectively implemented. Solid. and we are coding in self-contained functions that are unaffected by anything else. Design . The options are very easily added by directing the program to the relevant function. we will now start to build the game itself. but populated with the raw information needed to start the engine rolling slowly forwards. Coding the game will follow a natural. astounding sounds and out-of-this world effects. Building Blocks Having put our framework into place. we need to build our player. Program Structure . To this end.
The menu is simply but effectively excluded from the screen. We are using the data set up earlier in the invader arrays and player variables to quickly generate and position the objects. It's time to create our game objects and place them on the screen. with no additional effort. At the same time. if necessary. We simply have to add the relevant code. our aim at this stage is to prove everything fits into place. we can very simply implement a structure where every action is called at the right time. and included again afterwards. or switch to another one. similarly calls the deinitialisation function and exits in the simplest way possible. Remember. the world has been populated with the game objects. At the end of the state (or cycle). the code to destroy the objects will also be written. Adding a state machine The actual game itself is a collection of actions and reactions. Depending on the current "state". when the player is dead. Create and Destroy Now we are really moving. Doing these two tasks in parallel ensures the final game is efficient and requires far less debugging! The functions GamePlayInit() and GamePlayDeinit() are already in position and are already being called in the appropriate places in the GamePlay() function. return now to the Initialisation function and make adjustments to the positioning of the various entities quickly and with very little effort. the movement action can happen. By creating what is known as a Finite State Machine. noticing that everything that is created is also destroyed. playing the game. we have only basic boxes representing the objects. in fact there is a programmatic method for implementing this method . Each object has also been introduced into the collision system. For example. while others may come and go in an instant. We could. Option 2. Some states may last for some time. This system isn't as difficult to implement as it may seem. 8 states have been identified: Game Start 107 . does nothing but redirect to the currently empty GamePlay() function and return again. Again. as set out in the original framework. it can't. The most obvious observation is that at the moment. the decision is made to stay in the same state. ending the application. the program is simply ready to compile and run.the SELECT/CASE clauses. A small delay has been added for effect at this stage. when the player is alive. In just a few minutes.Option 1. triggered by the events that have taken place. Stage 5 does exactly this. Open the provided example and compare the 2 functions. each action will or will not happen.
You can follow the state from the game starting and initialising a few variables. The naming of the functions also make the flow easy to follow. and to build them rapidly. initialising more data and waiting a few seconds to start the gameplay. this skeleton engine can now be easily populated with more actions. when the player is alive we must checkKeys() for player input. MoveInvaders() is unique to this state however. Each stage is much more densely populated with actions. the moveInvaders() function is triggered and the game starts to move! Right now. is the speed at which it comes together in the final stages. simply because the process is now so simple! Hopefully the pattern we are following is becoming clearer. the "Alive" state is reached. The moveInvaders() function is actually populated. to give you a feel for this process. we have the simple beginnings of the game. which you can open and review. In the next instalment. and big changes will happen fast now. are being triggered. Work through the following stages. to the level starting. Actions. although very sparse. It can inspire new features and functionality. we will be able to compile and immediately see the results. the actions react to one moment in time. Level Start Player Alive Player Dead Life End Life Begin Level End Game End In each state. we can quickly build up a picture of what needs to happen and when. and investigate the changes at each step. As you can see. has this arrangement added to the GamePlay() function. and especially how each function is designed to act solely on one cycle of the program. Fast! One of the distinct advantages of putting in the effort to build a framework into which our game can be dropped piece by piece. but study how the state engine has been built up using functions. a further level of functions have been added to our Top-Down design. but bringing the game to life will once again be simple and methodical. In order to make this complete. The engine does the cycling. from movement. the function calls to the necessary actions are placed. For example. Big Changes. Stage 6. The functions aren't all populated with code just yet. Note also that the results of actions change the state of the engine. even though the full game is not completed. including acting on keypresses and firing at the invaders. which will also happen when the player is dead and also when the level is starting. invader weaponry and sound effects will be literally dropped into place. At each stage. Don't worry about the intricacies of each action. We must also animateInvaders(). all the various components. Most importantly. to collision detection. The Plan Ahead Everything is now in place to build our game features. The engine is moving from state to state. and checkInvaderBullets() for a hit. Then. 108 .
Stage 7 - The player movement keys are added in checkKeys(). The movement is just the small step the player takes in one cycle. It is also immediately obvious that the base speed is too slow. It is quickly located in the Init() function, where we set up our data, and remedied. The function also checks if the spacebar is pressed to fire, and sets the bullet position and state accordingly. We now have moving invaders, and a moving player. The bullet is initiated, but no more. Stage 8 - Here, we have implemented a new function, checkBullets(). It checks the state of both player and invader bullets, implements the collision with other valid objects, and triggers new invader bullets at random intervals. It does not yet handle the destruction of entities. Again, this highlighted the need to increase the invader bullet value in the Init() routine. It also displayed an unwanted feature, and that is the bullets passing through lower invaders. This was quickly fixed by disabling the Z-depth of the bullets, effectively forcing them to the front without having to make big changes in the positioning of objects in our game. Again, it is easily located in the GameInit() function, where we create our 3D objects. Stage 9 - The Mothership has been actioned by using a new function, checkMothership(). We create a random factor in causing the mothership to appear, apply the movement (on a single cycle basis), and check when it disappears. There's not much else to do, as the checkBullets() function actually determines the destruction of the ship by the player. Again, the original estimate of speed was too low, and this has been adjusted in the Init() routine. We will continue to fine-tune the characteristics of the game as we progress. Given that the entire code to bring the ship to life is a mere 15 lines, this stage also implements the objectsReposition() code. Although the primary role of this function is to put all the objects in their starting positions, it also sets the states of the objects too, and their visibility. The animation of the invaders has also been very simply added in the invadersAnimate() function. Stage 10 - Now we have added the destruction of invaders, and the Mothership. This has necessitated an additional variable to count the number of hits, and ensure we know when the full array of invaders have been eliminated. This in turn will force a new state, to end the Level, and ultimately to start a new level. Destruction of invaders occurs in the checkBullet() function, in the code we have already established. Now we have additional information on the number of destroyed invaders, another traditional feature of Space Invaders can be implemented in the moveInvaders() function. The speed can be increased as the number of successful hits increases, and this has been done. Stage 11 - Now is the time to add destruction of the player, again in the checkBullets() function. We have already detected collision with the player, it just doesn't do anything yet. Once added, the progression of states will expand to include the Dead state, and of course the game can also end. While we are adding this essential operation, and it is necessary to add a variable to the Player type to register the number of lives left,
we'll also add a variable to record the score. We can register the score at each invader and Mothership hit. To prove it is working, a simple text output to the screen is implemented for now. A further modification that has come to light only now is that the 2 states Dead and End of Life are in fact a duplication, and only complicate what is otherwise a much simpler process. So the End of Life state has been removed. This is a prime example of the rapid development process, where the original design is fluid and susceptible to change. It also accepts change quite willingly. Stage 12 - We are getting close to a completed game. Models can now replace the placeholder primitive objects, and unwanted items such as the collision boxes can be hidden. This is the point at which the game visually changes into something worth presenting. In addition, the collision of invaders with the bases is added, and this action ends the game by setting the player state to Dead, and reduces the lives to zero. This action takes place in the invadersMove() function, the most appropriate place to test for the objects descending onto the bases. To add interest to the player object, it now rotates as it moves. This is naturally located in the checkKeys() function, where we test for and move the player in response to the user input. Stage 13 - Now we will add the sound effects. This involves several areas: Constants - to define the sound numbers Loading and unloading the sounds in the GamePlayInit() and GamePlayDeinit() functions. Triggering the sounds at the different points - firing a new bullet (checkKeys()), hitting a target and being hit (checkBullets()), and the appearance of the Mothership (checkMothership())
Stage 14 - This final step simply adds feedback in the form of the score and the number of remaining lives. As this is a permanent feature of the in-game display, it is not placed in the SELECT clause, but just prior to the screen update. Also added is the final score, before the game returns to the menu. Simple High Score functionality retains the best attempt to date, and is loaded and saved as necessary, in the init() function and the Game End state
Believe it or not, we have just completed our game! It may not be the most sophisticated version of Space Invaders ever created, but there's nothing to stop it being updated, improved and finely polished. It's written in a way that makes the maintenance simple, even as far as adding new in-game features by adding selfcontained functions. There's potential for explosions, particle effects and cut-scene sequences. You could add power-ups and special features without too much difficulty, and by following the same process as we have used so far.
Zork Tutorial - Part One
Creating the Map and Moving Around
Zork is perhaps the most famous of all text adventures. It has been the gold standard of the genre and highly regarded. I spent countless hours playing this game and many more trying to figure out how to program it. In this tutorial I am going to show you how to make your own zork game! We are going to recreate a section of the original Zork, which will include the house and the areas just outside of the house. You will learn how to handle objects, inventory, containers, and parse the commands. So, let's get started! The first step is to make the map that is the locations in the game. In the original Zork the player started out at 'West of House', so this is where we will start. Go to the link below and you will see a map of zork. This is the reference we will use to create our map. http://infocom.elsewhere.org/gallery/zork1_invisiclues/map-2-3.jpg We are going to make seven of those locations. If you look at the zork map you can see where those locations are at and get an idea of how they are situated. -Our first line of code will make an array with space for seven location names: DIM LOCATION$(7) -Next we read in those names into an array: FOR I = 1 TO 7 READ LOCATION$(I) NEXT I DATA DATA DATA DATA DATA DATA DATA "West of House" "North of House" "Behind House" "South of House" "Kitchen" "Living Room" "Attic"
The array above has assigned a number for each location. 1 = "west of house", 2 = "North of House" etc. We know the player will start out at the "west of house" location which is location number 1. -So we need to make a variable that represents the player's location and assign it the number 1. PLR_LOC = 1: REM 1 Represents 'WEST OF HOUSE' Now, if we were to print LOCATION$(PLR_LOC), we would see 'west of house'. To verify this just run the code we have written so far:
For instance. In many cases the player can go north but cannot go south to get back! It is strange. if you take five steps due north. but that's the land of Zork. The six directions are. This is not so in Zork. We will now define 6 directions of travel for each location on the map. south. you tell it! One thing to remember about Zork. In the real world. you can take five steps due south to get back to where you started from. Let us start with north. west. This north number tells us if the player is allowed to move north from that location. the original designers were not consistent with their directions. we have our locations but how to get the player movement? First we have to map out which way the player is allowed to move. north. We create a north array that will have a north number for each of the 7 map locations. How does the program know this? Simple. up and down. the player cannot go east if he is at the 'west of house' location. REM LOCATION NUMBER 1 = WEST OF HOUSE PRINT LOCATION$(PLR_LOC) REM MAIN GAME LOOP DO SYNC LOOP REM END MAIN LOOP Change the PLR_LOC variable to any number between 1 and 7 and rerun the program and you will see the location has changed accordingly. Ok. 112 . east.REM Project: Zork Tutorial REM Created: 5/19/2008 7:37:05 PM REM REM ***** Main Source File ***** REM REM LOCATIONS DIM LOCATION$(7) FOR I = 1 TO 7 READ LOCATION$(I) NEXT I DATA DATA DATA DATA DATA DATA DATA "West of House" "North of House" "Behind House" "South of House" "Kitchen" "Living Room" "Attic" REM PLAYER STARTING LOCATION PLR_LOC = 1.
1. which happens to be 'North of House' NORTH(1) can equal any of the 7 map locations. then that means the player is allowed to move north and the player's new location is 2.0.DIM NORTH(7) FOR I = 1 TO 7 READ NORTH(I) NEXT I DATA 2.0. So.5. Here is the code that does that: PLR_LOC = NORTH(PLR_LOC) Now that we know how to define the map movement.0. the PLR_LOC variable is equal to 1.0. Now we look at the NORTH array to see if he is allowed.0. NORTH(PLR_LOC) is the same as NORTH(1).1. It's just a number that tells us what the player's new location is that he just moved to.2.0.0 REM EAST PATHS FOR EACH LOCATION DIM EAST(7) FOR I = 1 TO 7 READ EAST(I) NEXT I DATA 0.6.3. If NORTH(1) = 2.3.0. we can look up NORTH(1). if the player is at location 1.0 113 . REM SOUTH PATHS FOR EACH LOCATION DIM SOUTH(7) FOR I = 1 TO 7 READ SOUTH(I) NEXT I DATA 4. If we assign a ZERO to NORTH(1) then the zero tells us that the player is not allowed to go north while located at 'west of house'.5.3. NORTH(1) is equal to 2. and that is the player's location. He decides to go north.0 So.0. So.0. So let's see how this would work. That means the player is allowed to move north and we assign the player's new location as 2. The player is at location 1 which is 'west of house'.0 REM WEST PATHS FOR EACH LOCATION DIM WEST(7) FOR I = 1 TO 7 READ WEST(I) NEXT I DATA 0. NORTH(1) is equal to what? What number did we assign NORTH(1) in our array? We assigned it the number 2. What is NORTH(PLR_LOC) equal to? Well. let us add the rest of the arrays for the other directions.0.4.
'u' .0.0.down. 'e' . the commands will be kept simple.east. more complicated command sentences in a later part of the tutorial. 'd' .west.0. Here is the PARSE subroutine.0. 114 . the commands we will accept are 'n' .north. Since PLR_MOVE = 0 we will return a message telling the player he cannot go that direction.look. These two steps are the core of the game engine.7.5 The next step is to put this movement into our main loop. If the player enters the letter n.up.0 REM DOWN PATHS FOR EACH LOCATION DIM DOWN(7) FOR I = 1 TO 7 READ DOWN(I) NEXT I DATA 0. REM MAIN GAME LOOP DO REM GET PLAYER COMMAND INPUT "> ". and do whatever the instructions tell us.0. Before we enter the main game loop we should display the player's position on the screen: PRINT LOCATION$(PLR_LOC) Now. 'w' . 's' . Since we are working with map and movement in this part of the tutorial.0. If the player cannot move north then the PLR_MOVE variable does not get changed and remains equal to 0. which is 'n'. and 'l' .REM UP PATHS FOR EACH LOCATION DIM UP(7) FOR I = 1 TO 7 READ UP(I) NEXT I DATA 0. Look at the first condition.0. let's code the main game loop.0. I will cover the parsing. CMD$ REM PROCESS COMMAND GOSUB PARSE SYNC LOOP REM END MAIN LOOP The first step inside of the main loop is an INPUT statement that gets the player's instructions. If it does NOT equal 0 then the player is allowed to move north and we assign the new location number to the PLR_LOC variable and we set the PLR_MOVE variable to 1 which tells us that the player has moved. the IF statement checks the NORTH(PLR_LOC) variable.south.0. The next step calls a subroutine to parse those instructions. For right now.
I have included the full code for this part of the tutorial. now you know how to code the map locations. This is not needed but helps keep the screen from getting too cluttered.REM PARSE PLAYER COMMAND PARSE: PLR_MOVE = 0 SELECT CMD$ CASE "n" IF NORTH(PLR_LOC) <> 0 PLR_LOC = NORTH(PLR_LOC) PLR_MOVE = 1 ENDIF ENDCASE CASE "s" IF SOUTH(PLR_LOC) <> 0 PLR_LOC = SOUTH(PLR_LOC) PLR_MOVE = 1 ENDIF ENDCASE CASE "e" IF EAST(PLR_LOC) <> 0 PLR_LOC = EAST(PLR_LOC) PLR_MOVE = 1 ENDIF ENDCASE CASE "w" IF WEST(PLR_LOC) <> 0 PLR_LOC = WEST(PLR_LOC) PLR_MOVE = 1 ENDIF ENDCASE CASE "u" IF UP(PLR_LOC) <> 0 PLR_LOC = UP(PLR_LOC) PLR_MOVE = 1 ENDIF ENDCASE CASE "d" IF DOWN(PLR_LOC) <> 0 PLR_LOC = DOWN(PLR_LOC) PLR_MOVE = 1 ENDIF ENDCASE CASE "l" PLR_MOVE = 1 ENDCASE ENDSELECT IF PLR_MOVE = 1 REM DISPLAY NEW LOCATION GOSUB CRNT_LOC ELSE REM NOT ALLOWED TO GO THAT DIRECTION PRINT "You cannot go that way. In this code you will notice that I have added location descriptions and a screen reset counter inside of the main loop which clears the screen and brings the cursor back to the top of the screen after a few command entries. 115 . and the player movement." ENDIF RETURN REM END SUB So.
2. this code follows the same movement as the original Zork I game.0.3.0 REM SOUTH PATHS FOR EACH LOCATION DIM SOUTH(7) FOR I = 1 TO 7 READ SOUTH(I) NEXT I DATA 4.3.Remember. the best way to learn is to play with this code.0.4.0. Full Code for Part I: REM Project: Zork Tutorial Part I REM Created: 5/19/2008 7:37:05 PM REM REM ***** Main Source File ***** REM REM LOCATIONS DIM LOCATION$(7) FOR I = 1 TO 7 READ LOCATION$(I) NEXT I DATA DATA DATA DATA DATA DATA DATA "West of House" "North of House" "Behind House" "South of House" "Kitchen" "Living Room" "Attic" REM PLAYER STARTING LOCATION PLR_LOC = 220.127.116.11. Currently.0. Change some of the north numbers or other directional numbers.0.3.0 REM EAST PATHS FOR EACH LOCATION DIM EAST(7) FOR I = 1 TO 7 READ EAST(I) NEXT I DATA 0.0.0. You can make your own paths for the player if you choose to. REM LOCATION NUMBER 1 = WEST OF HOUSE REM REM DIM FOR DIRECTIONS NORTH PATHS FOR EACH LOCATION NORTH(7) I = 1 TO 7 READ NORTH(I) NEXT I DATA 2. In the next tutorial I will show you how to create objects and player inventory.0 REM WEST PATHS FOR EACH LOCATION DIM WEST(7) FOR I = 1 TO 7 READ WEST(I) 116 .
0.0.0.0.0.7.0.5.0 REM UP PATHS FOR EACH LOCATION DIM UP(7) FOR I = 1 TO 7 READ UP(I) NEXT I DATA 0.0.0.6. CMD$ REM RESET TO TOP OF SCREEN AFTER FOUR COMMANDS CLS_CNT = CLS_CNT + 1 IF CLS_CNT > 3 CLS_CNT = 0 CLS ENDIF REM PARSE COMMAND GOSUB PARSE REM LINE SPACING PRINT SYNC LOOP REM END MAIN LOOP REM DISPLAY CURRENT LOCATION CRNT_LOC: PRINT LOCATION$(PLR_LOC) RETURN REM END GOSUB REM DISPLAY CURRENT LOCATION 117 .0 REM DOWN PATHS FOR EACH LOCATION DIM DOWN(7) FOR I = 1 TO 7 READ DOWN(I) NEXT I DATA 0.5 REM DISPLAY CURRENT LOCATION GOSUB CRNT_LOC REM DISPLAY CURRENT LOCATION DESCRIPTION GOSUB CRNT_LOC_DESC REM MAIN GAME LOOP DO REM LINE SPACING PRINT REM GET PLAYER COMMAND INPUT "> ".0.1.NEXT I DATA 0.0.1.
A path leads into the forest to the east. and a large oriental rug in the center of the room." ENDCASE CASE 4 PRINT "You are facing the south side of a white house." ENDCASE CASE 5 PRINT "You are in the kitchen of the white house. To the north a narrow path winds through the trees. A table seems to have been used" PRINT "recently for the preparation of food. A dark chimney leads down and to the east" PRINT "is a small window which is open. There is a doorway to the east." ENDCASE ENDSELECT RETURN REM END GOSUB REM PARSE PLAYER COMMAND PARSE: PLR_MOVE = 0 SELECT CMD$ CASE "n" IF NORTH(PLR_LOC) <> 0 PLR_LOC = NORTH(PLR_LOC) PLR_MOVE = 1 ENDIF ENDCASE CASE "s" IF SOUTH(PLR_LOC) <> 0 PLR_LOC = SOUTH(PLR_LOC) PLR_MOVE = 1 118 . A passage leads to the west and a dark" PRINT "staircase can be seen leading upward. and all" PRINT "the windows are boarded up. and all" PRINT "the windows are boarded." ENDCASE CASE 7 PRINT "This is the attic." ENDCASE CASE 6 PRINT "You are in the living room. with a boarded front door. There is no door here." ENDCASE CASE 2 PRINT "You are facing the north side of a white house." ENDCASE CASE 3 PRINT "You are behind the white house. a wooden door with" PRINT "strange gothic lettering to the west. The only exit is a stairway leading down. There is no door here. a trophy" PRINT "case. In" PRINT "one corner of the house there is a small window which is slightly ajar. which appears to be nailed shut.CRNT_LOC_DESC: SELECT PLR_LOC CASE 1 PRINT "You are standing in an open field west of a white house.
" ENDIF RETURN REM END SUB UPDATE I There is a more convenient method of programming the map locations and directions. it makes for less arrays and changing or adding locations is more convenient. As IanM mentioned. what you will do is create your own type. but as it turns out there is a nice way to do this in DBPro. You use typed arrays. This is not easily done in older BASIC languages. So here is what our type definition might look like: 119 . It would be nice if we could group all the location data together. The good thing about this is that your type definition can be made up of several variables. Basically.ENDIF ENDCASE CASE "e" IF EAST(PLR_LOC) <> 0 PLR_LOC = EAST(PLR_LOC) PLR_MOVE = 1 ENDIF ENDCASE CASE "w" IF WEST(PLR_LOC) <> 0 PLR_LOC = WEST(PLR_LOC) PLR_MOVE = 1 ENDIF ENDCASE CASE "u" IF UP(PLR_LOC) <> 0 PLR_LOC = UP(PLR_LOC) PLR_MOVE = 1 ENDIF ENDCASE CASE "d" IF DOWN(PLR_LOC) <> 0 PLR_LOC = DOWN(PLR_LOC) PLR_MOVE = 1 ENDIF ENDCASE CASE "l" PLR_MOVE = 1 ENDCASE ENDSELECT IF PLR_MOVE = 1 REM DISPLAY NEW LOCATION GOSUB CRNT_LOC REM DISPLAY NEW LOCATION DESCRIPTION GOSUB CRNT_LOC_DESC ELSE REM NOT ALLOWED TO GO THAT DIRECTION PRINT "You cannot go that way.
north read loc_array(i).18.104.22.168. 2.0.0. 0. Again.type Loc loc_name as string north as integer south as integer east as integer west as integer up as integer down as integer endtype The type we created is named Loc.0.0. up.loc_name read loc_array(i). 0.0.0 "North of House". 2.0 "East of House".0.0. 2.0. 2.0.up read loc_array(i). let's look at the data statement: data "West of House".5 Let's look at the first location of the data: data "West of House". west. 0.down next i data data data data data data data "West of House".0 This is location #1 so if we were to print the location name: PRINT loc_array(1).west read loc_array(i).0.0.0.3. 0.0.3.east read loc_array(i).22.214.171.124. south.north This would display the #2.0 Those numbers following 'West of House' represent north.0 "Trophy Room".0.0.0.7. just like you would make an array of integers or strings.0.0. If we were to print the north number we would code: PRINT loc_array(i). east.loc_name It would print 'West of House'.0.0 "Kitchen".2.0. Thus: 120 .south read loc_array(i). dim loc_array(7) as Loc Next we read in all the grouped data: for i = 1 to 7 read loc_array(i). and we can make an array of Locs.2.0.0 "South of House".0 "Attic". 0.0.0. and down.
at the 'west of house' location if the player goes north he will move to location #2.0.up loc_array(i).north loc_array(i).126.96.36.199. south=188.8.131.52.0.0 "East of House".0 "South of House". 0.loc_name loc_array(i). all our location data is in one place so we don't have to scroll through too many arrays to make any changes. 0.down "West of House".0.2.0 "Trophy Room".184.108.40.206.0.1.0.0.4. down=0 So.0.0.2. 0.0.7.0.5 print loc_array(1). 0.0. 2.south loc_array(i).east do sync loop 121 . Here is a small code snippet that you can run and it prints the location from the typed array we just made.0 "Trophy Room". 2. 0. 0.5 Now. I will change the program code by replacing the simple arrays we used in the beginning with this typed array.0.0 "East of House". west=0.0.loc_name print loc_array(1).220.127.116.11. 0.2.2.0.7. data data data data data data data "West of House".3.0. Each location has the location name and the directional numbers. 0.0.0. type Loc loc_name as string north as float south as float east as float west as float up as float down as float endtype dim loc_array(7) as Loc for i = read read read read read read read next i data data data data data data data 1 to 7 loc_array(i).0.0.0 "Attic".0.0.0. 2. up or down because those directions are equal to 0.0.0. The player cannot go east.0.0 "North of House".0 "North of House". east=0.0 is north=18.104.22.168. up=0.0.0. 0.0.0.0 "South of House". If player goes south he will move to location #3.0 "Kitchen".west loc_array(i).east loc_array(i). 2.0. 0.0.2. Let's look at that data one more time.0. west.0 "Kitchen".0.0 "Attic".
' -2 = 'The gate is locked' etc.' So.Of course. We know it is 7 but what if we add another location? Then we have to find every array and change the number.4 or a typed array could be: data "North of House". create a constant variable such as MAX_LOC = 7." 122 .' In Zork you will find that the game is sprinkled with such messages. Zero is still the generic message of 'You can't go that way.' -1 = 'The door is nailed shut. anytime you want to add a location you only have to change the MAX_LOC constant one time. 0. I will have more updates to this part I soon.. Instead of doing that. a new location." DATA "The door is boarded and you can't remove the boards. FINAL UPDATE TO PART I You may have noticed while moving around in the game that when you try to go a direction that is off limits our program displays the message 'You can't go that way.3. For instance: 0 = 'You can't go that way.2. Then we will move on to part II. we used an array to look up the directional number. so we will include those messages in our program. I will add some more shine to it.0. and change array(7) to array(MAX_LOC). Another thing to mention is the number of locations. Now.1." This is fine but you can make it more interesting if you tell the player why he can't go that way. If you have trouble with this then I suggest you stick with the simple array in the original tutorial until you get more comfortable with arrays in general. We can expand on this idea and use negative numbers to represent different messages telling the player he can't go that way. If it was equal to 0 then that told us that direction was off limits.1. 'The bridge is out.. there are even more variations of this as well and I'm not going to go into everyone of them here. our array data may look something like this: data 0.-2. One message might be.3. If you'll recall. If it was a positive number then the player's location would change to that number. First let us declare an array for our messages: REM PLAYER CAN'T GO THAT WAY MESSAGES MAX_NOGO = 3 DIM NOGO$(MAX_NOGO) FOR I = 0 TO MAX_NOGO READ NOGO$(I) NEXT I DATA "You can't go that way. Any positive number means the player can move that direction and his position (PLR_LOC) changes to that positive number. Now that you know the idea behind this.0.-2. let us put it into action.0 Any number 0 or less(negative numbers) means the player is not allowed to go that direction and the number tells us what message to use.
Now that we have a positive number we can use it on our message array to print the matching message." The messages are the same as Zork I. -3 We have to change these to positive numbers after we get them so that we can match them up to the NOGO$ messages." door is nailed shut. We use the number 0 and negative numbers to identify them. For instance: Change -3 to 3 so that we can put it in the NOGO$ array like this: PRINT NOGO$(3) This would display: 'The door is nailed shut. we can use it as our index for the message array NOGO$ PRINT NOGO$( MESSAGE_INDEX ) 123 . -1.DATA "The windows are all boarded. First we use the ABS function: So." door is boarded and you can't remove the boards. around the house area. So." These are our 4 messages.' What we can do is use the ABS function which gives us the absolute value of the number and then match that up with our message array." DATA "The door is nailed shut." windows are all boarded. ABS(-3) is the same as 3. if NORTH(1) = -3 MESSAGE_INDEX = ABS( NORTH(1) ) Same as: MESSAGE_INDEX = 3 Now. 0. -2. Notice the messages will be represented in numeric order like this: NOGO$(0) NOGO$(1) NOGO$(2) NOGO$(3) "You "The "The "The can't go that way.
" NOGO$(2) . I have included lots of comments to help make it clear."The door is nailed shut. so you know most of it already and the rest of it is pretty simple.4.WEST read loc_array(i). REM Project: Zork Tutorial REM Created: 5/19/2008 7:37:05 PM REM REM ***** Main Source File ***** REM REM MAXIMUM NUMBER OF LOCATIONS MAX_LOC = 7 REM CREATE A TYPE DEFINITION FOR THE LOCATION VARIABLES type LOC LOCATION as string NORTH as integer SOUTH as integer EAST as integer WEST as integer UP as integer DOWN as integer endtype REM CREATE AN ARRAY OF LOCATIONS dim LOC_ARRAY(MAX_LOC) as LOC REM POPULATE THE ARRAY WITH MAP LOCATION DATA for i = 1 to MAX_LOC read loc_array(i). Of course you may think of other ways to make them yourself if you choose. I have added the final program.0.NORTH SOUTH EAST WEST UP DOWN data "West of House".LOCATION read loc_array(i).SOUTH read loc_array(i).DOWN next i REM LOCATION N S E W U D ."You can't go that way.0. as I will not make any new updates for this first part. It includes the message codes and I have replaced the simple location arrays with typed arrays.NORTH read loc_array(i)."The windows are all boarded. 2.-1.UP read loc_array(i). complete for PART I of the tutorial. you can always just use the standard message in the original program."The door is boarded and you can't remove the boards." NOGO$(1) ." This concludes this part of the tutorial. If you are having trouble with this part of it don't worry.EAST read loc_array(i)." NOGO$(3) .0 124 .Is the same as: PRINT NOGO$( 3 ) Remember what message NOGO$(3) is? Let's list them: NOGO$(0) . You will notice that the code has not changed that much.
0 "South of House".5 REM PLAYER STARTING LOCATION PLR_LOC = 1.0. 0. 22.214.171.124.data data data data data data "North of House".-3.0 "Living Room". 0.0.0." DATA "The windows are all boarded.0.0 "Kitchen". CMD$ REM RESET TO TOP OF SCREEN AFTER FOUR COMMANDS CLS_CNT = CLS_CNT + 1 IF CLS_CNT > 3 CLS_CNT = 0 CLS ENDIF REM PROCESS COMMAND GOSUB PARSE REM LINE SPACING PRINT SYNC LOOP REM END MAIN LOOP REM DISPLAY CURRENT LOCATION CRNT_LOC: PRINT LOC_ARRAY(PLR_LOC).3 DATA "You can't go that way.6.0." DATA "The door is nailed shut. REM LOCATION NUMBER 1 = WEST OF HOUSE REM PLAYER CAN'T GO THAT WAY MESSAGES MAX_NOGO = 3 DIM NOGO$(MAX_NOGO) FOR I = 0 TO MAX_NOGO READ NOGO$(I) NEXT I REM MESSAGES 0 .0.0 "Attic".0.LOCATION RETURN REM END GOSUB 125 . 0.0.5." DATA "The door is boarded and you can't remove the boards.0. -2.0 "East of House".0." REM DISPLAY CURRENT LOCATION GOSUB CRNT_LOC REM DISPLAY CURRENT LOCATION DESCRIPTION GOSUB CRNT_LOC_DESC REM MAIN GAME LOOP DO REM LINE SPACING PRINT REM GET PLAYER COMMAND INPUT "> ".3.7.4.-126.96.36.199.0. 0.
REM DISPLAY CURRENT LOCATION CRNT_LOC_DESC: SELECT PLR_LOC CASE 1 PRINT "You are standing in an open field west of a white house, with a boarded front door." ENDCASE CASE 2 PRINT "You are facing the north side of a white house. There is no door here, and all" PRINT "the windows are boarded up. To the north a narrow path winds through the trees." ENDCASE CASE 3 PRINT "You are behind the white house. A path leads into the forest to the east. In" PRINT "one corner of the house there is a small window which is slightly ajar." ENDCASE CASE 4 PRINT "You are facing the south side of a white house. There is no door here, and all" PRINT "the windows are boarded." ENDCASE CASE 5 PRINT "You are in the kitchen of the white house. A table seems to have been used" PRINT "recently for the preparation of food. A passage leads to the west and a dark" PRINT "staircase can be seen leading upward. A dark chimney leads down and to the east" PRINT "is a small window which is open." ENDCASE CASE 6 PRINT "You are in the living room. There is a doorway to the east, a wooden door with" PRINT "strange gothic lettering to the west, which appears to be nailed shut, a trophy" PRINT "case, and a large oriental rug in the center of the room." ENDCASE CASE 7 PRINT "This is the attic. The only exit is a stairway leading down." ENDCASE ENDSELECT RETURN REM END GOSUB REM PARSE PLAYER COMMAND PARSE: MOV_MSG = 0 PLR_MOVE = 0 SELECT CMD$ CASE "n" IF LOC_ARRAY(PLR_LOC).NORTH > 0 PLR_LOC = LOC_ARRAY(PLR_LOC).NORTH PLR_MOVE = 1 ELSE MOV_MSG = ABS(LOC_ARRAY(PLR_LOC).NORTH) ENDIF ENDCASE
CASE "s" IF LOC_ARRAY(PLR_LOC).SOUTH > 0 PLR_LOC = LOC_ARRAY(PLR_LOC).SOUTH PLR_MOVE = 1 ELSE MOV_MSG = ABS(LOC_ARRAY(PLR_LOC).SOUTH) ENDIF ENDCASE CASE "e" IF LOC_ARRAY(PLR_LOC).EAST > 0 PLR_LOC = LOC_ARRAY(PLR_LOC).EAST PLR_MOVE = 1 ELSE MOV_MSG = ABS(LOC_ARRAY(PLR_LOC).EAST) ENDIF ENDCASE CASE "w" IF LOC_ARRAY(PLR_LOC).WEST > 0 PLR_LOC = LOC_ARRAY(PLR_LOC).WEST PLR_MOVE = 1 ELSE MOV_MSG = ABS(LOC_ARRAY(PLR_LOC).WEST) ENDIF ENDCASE CASE "u" IF LOC_ARRAY(PLR_LOC).UP > 0 PLR_LOC = LOC_ARRAY(PLR_LOC).UP PLR_MOVE = 1 ELSE MOV_MSG = ABS(LOC_ARRAY(PLR_LOC).UP) ENDIF ENDCASE CASE "d" IF LOC_ARRAY(PLR_LOC).DOWN > 0 PLR_LOC = LOC_ARRAY(PLR_LOC).DOWN PLR_MOVE = 1 ELSE MOV_MSG = ABS(LOC_ARRAY(PLR_LOC).DOWN) ENDIF ENDCASE CASE "l" PLR_MOVE = 1 ENDCASE ENDSELECT IF PLR_MOVE = 1 REM DISPLAY NEW LOCATION GOSUB CRNT_LOC REM DISPLAY NEW LOCATION DESCRIPTION GOSUB CRNT_LOC_DESC ELSE REM DISPLAY APPROPRIATE MESSAGE THAT PLAYER CANNOT GO THAT DIRECTION PRINT NOGO$(MOV_MSG) ENDIF RETURN REM END SUB
Zork Tutorial - Part II - OBJECTS AND INVENTORY
In the first tutorial we created the game world with its locations, movement and a simple command parser. Now we will learn how to create objects and place them in the game. We will also make the player's inventory. For now, we will only make 2 objects, the mailbox and the leaflet located at the 'west of house' location. We will add the rest at a later time. Objects, like a mailbox have attributes, information about them. For instance, the location of the object and the name of the object are all attributes. There are many attributes that an object can have. Some objects can be opened like the mailbox while others cannot. Some objects can be taken by the player, while others cannot. We need to give each object certain attributes that we will include in the game. For now, we will start with just 2 attributes and add others later. These two attributes are the object's name and its location. First we tell the program how many objects there are in the game. let's make a variable called OBJ_MAX. This is equal to the number of objects in the game, which we will set to 2 for now. OBJ_MAX = 2 Next, we will make an array type for the objects. First we create the array definition: REM CREATE A TYPE DEFINITION FOR OBJECTS type OBJ NAME as string LOCATION as integer endtype So, we created a new type and called it OBJ. It has two variables, NAME and LOCATION. Those are the object attributes. Next we create an array that will contain each object and it's attributes. DIM OBJ_ARRAY(MAX_OBJ) as OBJ Now, let's read in the attribute information into the array: REM POPULATE THE ARRAY WITH OBJECT DATA FOR I = 1 to MAX_OBJ READ OBJ_ARRAY(I).NAME READ OBJ_ARRAY(I).LOCATION NEXT I DATA "small mailbox", 1 DATA "leaflet", 2 As you can see, it is fairly simple. There is the name of the object followed by its location. So, object #1 is the 'small mailbox' and it is located at location #1 which is the starting position 'west of house'
Go ahead and run this to see it in action: REM DECLARE MAXIMUM NUMBER OF OBJECTS IN GAME MAX_OBJ = 2 REM CREATE A TYPE DEFINITION FOR OBJECTS type OBJ NAME as string LOCATION as integer endtype REM CREATE AN ARRAY OF OBJECTS dim OBJ_ARRAY(MAX_OBJ) as OBJ REM POPULATE THE ARRAY WITH OBJECT DATA for i = 1 to MAX_OBJ read OBJ_ARRAY(i). We will display the name of the object right after the location and the description.LOCATION Let's try it. For instance.LOCATION next i REM NAME LOCATION data "small mailbox". #2 which is 'north of house' To display each object we would code a print statement with the array name. the 'small mailbox' we would code this: PRINT OBJ_ARRAY(1). Here is the code to print object information (attributes). 2 : rem not on the map yet (hidden inside mailbox PRINT OBJ_ARRAY(1). with a boarded front door. to display the name of the first object.LOCATION do sync loop Now that we have the objects defined we know how to display them we will go ahead and put that code in our game. To display the objects at the player's location we need to search through our object list to see if the object's location number is the same as the player's location number.NAME PRINT OBJ_ARRAY(2).NAME read OBJ_ARRAY(i). So it will look something like this: West of House You are standing in an open field west of a white house. There is a small mailbox here.NAME To display the location number of the leaflet we would code: PRINT OBJ_ARRAY(2). 1 : rem Located at west of house data "leaflet". We will create a for-next loop which will look at 129 .You will notice that I placed the leaflet at a different location.
Take a look at it and you will see it is a simple addition. we will add those words too: PRINT "There is a " + OBJ_ARRAY(I). REM Project: Zork Tutorial REM Created: 5/19/2008 7:37:05 PM REM REM ***** Main Source File ***** REM REM MAXIMUM NUMBER OF LOCATIONS MAX_LOC = 7 REM CREATE A TYPE DEFINITION FOR THE LOCATION VARIABLES type LOC LOCATION as string NORTH as integer SOUTH as integer EAST as integer WEST as integer UP as integer DOWN as integer endtype REM CREATE AN ARRAY OF LOCATIONS dim LOC_ARRAY(MAX_LOC) as LOC 130 ." Now.' So.LOCATION = PLR_LOC PRINT "There is a " + OBJ_ARRAY(I). Thus: IF OBJ_ARRAY(1). it usually says: 'There is a small mailbox here.each object one at a time starting with object #1. you will see the leaflet. Have fun. If the location of the object = location of player we will display the name of the object. For instance." ENDIF NEXT I I have added all the above code into the game. print the name of the object: PRINT OBJ_ARRAY(1).LOCATION = PLR_LOC Next. We will discuss player inventory in the next update. Run this code and you will see the mailbox at the starting location.NAME + " here.NAME Instead of just displaying the name of the object we can spice it up a bit. If you move north. here is the FOR-NEXT loop that checks each object and display's it if it is at the player's location: REM LIST ANY OBJECTS AT PLAYER LOCATION FOR I = 1 TO MAX_OBJ IF OBJ_ARRAY(I).NAME + " here. in Zork I.
" 131 .5.0 "South of House".5 REM OBJECTS REM DECLARE MAXIMUM NUMBER OF OBJECTS IN GAME MAX_OBJ = 2 REM CREATE A TYPE DEFINITION FOR OBJECTS type OBJ NAME as string LOCATION as integer endtype REM CREATE AN ARRAY OF OBJECTS dim OBJ_ARRAY(MAX_OBJ) as OBJ REM POPULATE THE ARRAY WITH OBJECT DATA for i = 1 to MAX_OBJ read OBJ_ARRAY(i). 188.8.131.52. 0.0. 0. REM LOCATION NUMBER 1 = WEST OF HOUSE REM PLAYER CAN'T GO THAT WAY MESSAGES MAX_NOGO = 3 DIM NOGO$(MAX_NOGO) FOR I = 0 TO MAX_NOGO READ NOGO$(I) NEXT I REM MESSAGES 0 . -2.0.WEST read LOC_ARRAY(i).0.0.0. 2.0.NORTH read LOC_ARRAY(i).NAME read OBJ_ARRAY(i). 1 : rem Located at west of house data "leaflet". 0.5.0 "Living Room".-2.0.3.REM POPULATE THE ARRAY WITH MAP LOCATION DATA for i = 1 to MAX_LOC read LOC_ARRAY(i).4.0 "Kitchen".0.0.7.3.LOCATION read LOC_ARRAY(i).SOUTH read LOC_ARRAY(i).3." DATA "The door is boarded and you can't remove the boards.DOWN next i REM data data data data data data data LOCATION N S E W U D .-3.0.LOCATION next i REM NAME LOCATION data "small mailbox".0 "East of House". 2 : rem not on the map yet (hidden inside mailbox REM PLAYER STARTING LOCATION PLR_LOC = 1.EAST read LOC_ARRAY(i).-1.1.UP read LOC_ARRAY(i).0." DATA "The windows are all boarded.0 "North of House"." DATA "The door is nailed shut.NORTH SOUTH EAST WEST UP DOWN "West of House".0.1.6.0 "Attic". 0.3 DATA "You can't go that way.
REM DISPLAY CURRENT LOCATION GOSUB CRNT_LOC REM DISPLAY CURRENT LOCATION DESCRIPTION GOSUB CRNT_LOC_DESC REM LINE SPACING PRINT REM DISPLAY OBJECTS AT PLAYER LOCATION GOSUB DISPLAY_OBJECTS REM MAIN GAME LOOP DO REM LINE SPACING PRINT REM GET PLAYER COMMAND INPUT "> ". and all" PRINT "the windows are boarded up. There is no door here. A path leads into the forest to the east. In" 132 . To the north a narrow path winds through the trees. CMD$ REM RESET TO TOP OF SCREEN AFTER FOUR COMMANDS CLS_CNT = CLS_CNT + 1 IF CLS_CNT > 3 CLS_CNT = 0 CLS ENDIF REM PROCESS COMMAND GOSUB PARSE REM LINE SPACING PRINT SYNC LOOP REM END MAIN LOOP REM DISPLAY CURRENT LOCATION CRNT_LOC: PRINT LOC_ARRAY(PLR_LOC). with a boarded front door." ENDCASE CASE 2 PRINT "You are facing the north side of a white house." ENDCASE CASE 3 PRINT "You are behind the white house.LOCATION RETURN REM END GOSUB REM DISPLAY CURRENT LOCATION CRNT_LOC_DESC: SELECT PLR_LOC CASE 1 PRINT "You are standing in an open field west of a white house.
" ENDCASE CASE 4 PRINT "You are facing the south side of a white house.NORTH PLR_MOVE = 1 ELSE MOV_MSG = ABS(LOC_ARRAY(PLR_LOC)." ENDIF NEXT I RETURN REM END SUB REM PARSE PLAYER COMMAND PARSE: MOV_MSG = 0 PLR_MOVE = 0 SELECT CMD$ CASE "n" IF LOC_ARRAY(PLR_LOC). There is no door here. There is a doorway to the east. and all" PRINT "the windows are boarded.NAME + " here. and a large oriental rug in the center of the room." ENDCASE CASE 5 PRINT "You are in the kitchen of the white house. which appears to be nailed shut.PRINT "one corner of the house there is a small window which is slightly ajar." ENDCASE CASE 7 PRINT "This is the attic.SOUTH > 0 PLR_LOC = LOC_ARRAY(PLR_LOC). a trophy" PRINT "case.NORTH > 0 PLR_LOC = LOC_ARRAY(PLR_LOC)." ENDCASE CASE 6 PRINT "You are in the living room. A dark chimney leads down and to the east" PRINT "is a small window which is open." ENDCASE ENDSELECT RETURN REM END GOSUB REM DISPLAY OBJECTS DISPLAY_OBJECTS: REM LIST ANY OBJECTS AT PLAYER LOCATION FOR I = 1 TO MAX_OBJ IF OBJ_ARRAY(I). A table seems to have been used" PRINT "recently for the preparation of food.NORTH) ENDIF ENDCASE CASE "s" IF LOC_ARRAY(PLR_LOC). The only exit is a stairway leading down. a wooden door with" PRINT "strange gothic lettering to the west.LOCATION = PLR_LOC PRINT "There is a " + OBJ_ARRAY(I). A passage leads to the west and a dark" PRINT "staircase can be seen leading upward.SOUTH 133 .
SOUTH) ENDIF ENDCASE CASE "e" IF LOC_ARRAY(PLR_LOC).WEST > 0 PLR_LOC = LOC_ARRAY(PLR_LOC).UP > 0 PLR_LOC = LOC_ARRAY(PLR_LOC).UP) ENDIF ENDCASE CASE "d" IF LOC_ARRAY(PLR_LOC).WEST PLR_MOVE = 1 ELSE MOV_MSG = ABS(LOC_ARRAY(PLR_LOC).DOWN) ENDIF ENDCASE CASE "l" PLR_MOVE = 1 ENDCASE ENDSELECT IF PLR_MOVE = 1 REM DISPLAY NEW LOCATION GOSUB CRNT_LOC REM DISPLAY NEW LOCATION DESCRIPTION GOSUB CRNT_LOC_DESC REM LINE SPACING PRINT REM DISPLAY OBJECTS AT PLAYER LOCATION GOSUB DISPLAY_OBJECTS ELSE REM DISPLAY APPROPRIATE MESSAGE THAT PLAYER CANNOT GO THAT DIRECTION PRINT NOGO$(MOV_MSG) ENDIF 134 .PLR_MOVE = 1 ELSE MOV_MSG = ABS(LOC_ARRAY(PLR_LOC).DOWN PLR_MOVE = 1 ELSE MOV_MSG = ABS(LOC_ARRAY(PLR_LOC).DOWN > 0 PLR_LOC = LOC_ARRAY(PLR_LOC).UP PLR_MOVE = 1 ELSE MOV_MSG = ABS(LOC_ARRAY(PLR_LOC).WEST) ENDIF ENDCASE CASE "u" IF LOC_ARRAY(PLR_LOC).EAST PLR_MOVE = 1 ELSE MOV_MSG = ABS(LOC_ARRAY(PLR_LOC).EAST > 0 PLR_LOC = LOC_ARRAY(PLR_LOC).EAST) ENDIF ENDCASE CASE "w" IF LOC_ARRAY(PLR_LOC).
INVENTORY next i Now we need to add either a 1 or a 0 for each object. LOCATION. 1 135 .LOCATION read OBJ_ARRAY(i). 0. The number 1 tells us that the player has the object in his inventory. a 0 if object is not with player and a 1 if object is. REM NAME. INVENTORY data "small mailbox". objects have attributes such as what their name is and where they are located. This attribute lets us know if the object is with the player. let's add that to the object array that we created earlier. 1. 0 data "leaflet".RETURN REM END SUB UPDATE I INVENTORY As you learned earlier. LOCATION data "small mailbox". Add it to the OBJ type: type OBJ NAME as string LOCATION as integer INVENTORY as integer endtype Then add it here where we read in the object data: for i = 1 to MAX_OBJ read OBJ_ARRAY(i). 0 Now. REM NAME. 1 data "leaflet". For the mailbox we will assign it a 0 and the leaflet a 1. here it is after we add the third attribute of INVENTORY. We will add another attribute to our objects. The number 0 tells us that the object is not with the player. Here is the original object data.NAME read OBJ_ARRAY(i). with NAME and LOCATION attribute. So. so the player will be carrying the leaflet. Here is the original object type: type OBJ NAME as string LOCATION as integer endtype Now we will add the third attribute and call it INVENTORY.
Like this: FOR I = 1 TO MAX_OBJ IF OBJ_ARRAY(I). LOCATION. Whenever the player enters the letter 'i' we will display whatever he is carrying in his inventory. You can create any attribute like this.LOCATION read OBJ_ARRAY(i).NAME read OBJ_ARRAY(i). You could call it the WEIGHT attribute. it's that easy. 0. 1. 1 We still only have two objects but now each object has three attributes. Now that we have added the INVENTORY attribute we will add a new command to the parser. pretty simple.NAME ENDIF NEXT I And if the player is not carrying anything? In that case we will reply with the standard Zork reply: 136 .INVENTORY = 1 PRINT OBJ_ARRAY(I). 0 data "leaflet". Of course we will check each object and see if it's INVENTORY variable is = 1 and if it is then we list that on the screen as the player's inventory. if you wanted to add weight to objects you could make another number which would represent how much the object weighs. So we would code: IF OBJ_ARRAY(I).NAME If 1 then print object name. But what if the player is carrying a bunch of objects? What we need to do is make our trusty FOR-NEXT loop and go through each object one at a time and check each INVENTORY attribute. INVENTORY data "small mailbox".And here is the complete object array that we created: REM DECLARE MAXIMUM NUMBER OF OBJECTS IN GAME MAX_OBJ = 2 REM CREATE A TYPE DEFINITION FOR OBJECTS type OBJ NAME as string LOCATION as integer INVENTORY as integer endtype REM CREATE AN ARRAY OF OBJECTS dim OBJ_ARRAY(MAX_OBJ) as OBJ REM POPULATE THE ARRAY WITH OBJECT DATA for i = 1 to MAX_OBJ read OBJ_ARRAY(i). For instance.INVENTORY next i REM NAME.INVENTORY = 1 Then PRINT OBJ_ARRAY(I).
INVENTORY = 1 137 . check to see if this is the first object and display the 'You are carrying:' message. only starting with the first object. When we find the very first object that's in the player's inventory we can display the message: 'You are carrying:' We print that message just once. IF INV_CNT = 1 THEN PRINT "You are carrying:" Next display the name of the object. The remaining objects in the inventory will be printed below that. PRINT OBJ_ARRAY(I). it would look like this: You are carrying: leaflet sword bottle So.INVENTORY = 1 If it is. we put all of that code together and it looks like this: REM INVENTORY CASE "i" INV_CNT = 0 FOR I = 1 TO MAX_OBJ IF OBJ_ARRAY(I).' To do this we can make a counter variable named INV_CNT." Now.NAME After we finish checking every object and we don't find any in the inventory then we display the 'You are empty handed. Once we print that message then we print the name of the object. So. Each time we find an object in the player's inventory we simply add 1 to the INV_CNT. let's look at the code: First check the object to see if it's in player inventory: IF OBJ_ARRAY(I). We will know if it's the first object because the INV_CNT will equal 1. so that's the variable we need to check: IF INV_CNT = 0 PRINT "You are empty-handed. then increment the INV_CNT (add 1 to INV_CNT) INC INV_CNT Next. If the INV_CNT is 0 then there is nothing in inventory.'You are empty-handed.' message. one at a time.
Once we have a working Zork parser we will be able to add the full TAKE and DROP commands for all objects. because if the object is in the kitchen and the player is outside the house we don't want him to be able to take it! So. So we need to code: OBJ_ARRAY(2).LOCATION = PLR_LOC OBJ_ARRAY(2). Run this code and go north and take the leaflet. So we have to change the object's LOCATION attribute from a 1 to a 0. What you learn here will put to use when we get the full parser going. In the latter part of the tutorial I will go more indepth on the parser and we will learn how to make a Zork parser. to drop the leaflet you just reverse this and check to see if the player has the object because you don't want him dropping something he doesn't have. So what do we have to change when the player takes the leaflet? Well.in player's inventory now OBJ_ARRAY(2). 138 .INVENTORY = 1 i.INC INV_CNT IF INV_CNT = 1 THEN PRINT "You are carrying:" PRINT OBJ_ARRAY(I)." ENDIF RETURN ENDCASE Now. because the 1 means the player has the object.e. I'm going to add a simple TAKE and DROP command to the parser. but that will have to wait till later. You will only be able to take and drop the leaflet for now.NAME ENDIF NEXT I IF INV_CNT = 0 PRINT "You are empty-handed. But we also need to remove the object from the ground at wherever the player is located.INVENTORY = 1 OBJ_ARRAY(2). .LOCATION = 0 i." ELSE PRINT "You can't see any leaflet here!" ENDIF RETURN ENDCASE Now. we code: IF OBJ_ARRAY(2).LOCATION = PLR_LOC The simple parser code would look something like this: CASE "take leaflet" IF OBJ_ARRAY(2). Here is the full code of everything we have covered so far.e .no longer on ground at location Before we do that we should check to see if the object is at the player's current location.LOCATION = 0 PRINT "Taken. we need to change the leaflet INVENTORY attribute from 0 to 1. The leaflet object is object #2.
0 "Living Room".-2.0. 0.WEST read LOC_ARRAY(i).1.-1.3.5 REM OBJECTS REM DECLARE MAXIMUM NUMBER OF OBJECTS IN GAME MAX_OBJ = 2 REM CREATE A TYPE DEFINITION FOR OBJECTS type OBJ NAME as string LOCATION as integer INVENTORY as integer endtype REM CREATE AN ARRAY OF OBJECTS dim OBJ_ARRAY(MAX_OBJ) as OBJ 139 .0 "North of House".LOCATION read LOC_ARRAY(i).5.0.0.EAST read LOC_ARRAY(i).DOWN next i REM data data data data data data data LOCATION N S E W U D .0. 0.3.0.0.0.UP read LOC_ARRAY(i).0. 2. REM Project: Zork Tutorial REM Created: 5/19/2008 7:37:05 PM REM REM ***** Main Source File ***** REM REM MAXIMUM NUMBER OF LOCATIONS MAX_LOC = 7 REM CREATE A TYPE DEFINITION FOR THE LOCATION VARIABLES type LOC LOCATION as string NORTH as integer SOUTH as integer EAST as integer WEST as integer UP as integer DOWN as integer endtype REM CREATE AN ARRAY OF LOCATIONS dim LOC_ARRAY(MAX_LOC) as LOC REM POPULATE THE ARRAY WITH MAP LOCATION DATA for i = 1 to MAX_LOC read LOC_ARRAY(i).Now look at your inventory 'i' command and you will see it.0.0.7.0 "East of House".SOUTH read LOC_ARRAY(i).0 "South of House".0. Try dropping it somewhere and enter the look command 'l' to see if the leaflet you dropped is there. 2.0 "Kitchen".4. 0. -2.NORTH SOUTH EAST WEST UP DOWN "West of House".3. 0.0.0 "Attic".-3.NORTH read LOC_ARRAY(i).184.108.40.206.0.6.
LOCATION read OBJ_ARRAY(i). INVENTORY data "small mailbox". LOCATION.REM POPULATE THE ARRAY WITH OBJECT DATA for i = 1 to MAX_OBJ read OBJ_ARRAY(i). 2." DATA "The windows are all boarded." DATA "The door is boarded and you can't remove the boards.3 DATA "You can't go that way.INVENTORY next i REM NAME. 0 data "leaflet". CMD$ REM RESET TO TOP OF SCREEN AFTER FOUR COMMANDS CLS_CNT = CLS_CNT + 1 IF CLS_CNT > 3 CLS_CNT = 0 CLS ENDIF REM PROCESS COMMAND GOSUB PARSE REM LINE SPACING PRINT 140 . 0 REM PLAYER STARTING LOCATION PLR_LOC = 1. 1." REM DISPLAY CURRENT LOCATION GOSUB CRNT_LOC REM DISPLAY CURRENT LOCATION DESCRIPTION GOSUB CRNT_LOC_DESC REM LINE SPACING PRINT REM DISPLAY OBJECTS AT PLAYER LOCATION GOSUB DISPLAY_OBJECTS REM MAIN GAME LOOP DO REM LINE SPACING PRINT REM GET PLAYER COMMAND INPUT "> ".NAME read OBJ_ARRAY(i)." DATA "The door is nailed shut. REM LOCATION NUMBER 1 = WEST OF HOUSE REM PLAYER CAN'T GO THAT WAY MESSAGES MAX_NOGO = 3 DIM NOGO$(MAX_NOGO) FOR I = 0 TO MAX_NOGO READ NOGO$(I) NEXT I REM MESSAGES 0 .
a trophy" PRINT "case. a wooden door with" PRINT "strange gothic lettering to the west." ENDCASE CASE 5 PRINT "You are in the kitchen of the white house. The only exit is a stairway leading down. which appears to be nailed shut. and all" PRINT "the windows are boarded up. To the north a narrow path winds through the trees. and all" PRINT "the windows are boarded.LOCATION RETURN REM END GOSUB REM DISPLAY CURRENT LOCATION CRNT_LOC_DESC: SELECT PLR_LOC CASE 1 PRINT "You are standing in an open field west of a white house. In" PRINT "one corner of the house there is a small window which is slightly ajar. There is no door here. A table seems to have been used" PRINT "recently for the preparation of food." ENDCASE CASE 4 PRINT "You are facing the south side of a white house." ENDCASE CASE 3 PRINT "You are behind the white house. and a large oriental rug in the center of the room. with a boarded front door. There is no door here." ENDCASE CASE 2 PRINT "You are facing the north side of a white house." ENDCASE CASE 7 PRINT "This is the attic." ENDCASE ENDSELECT RETURN REM END GOSUB REM DISPLAY OBJECTS DISPLAY_OBJECTS: 141 .SYNC LOOP REM END MAIN LOOP REM DISPLAY CURRENT LOCATION CRNT_LOC: PRINT LOC_ARRAY(PLR_LOC). There is a doorway to the east. A dark chimney leads down and to the east" PRINT "is a small window which is open." ENDCASE CASE 6 PRINT "You are in the living room. A passage leads to the west and a dark" PRINT "staircase can be seen leading upward. A path leads into the forest to the east.
UP) ENDIF ENDCASE CASE "d" IF LOC_ARRAY(PLR_LOC).EAST PLR_MOVE = 1 ELSE MOV_MSG = ABS(LOC_ARRAY(PLR_LOC).WEST PLR_MOVE = 1 ELSE MOV_MSG = ABS(LOC_ARRAY(PLR_LOC).UP > 0 PLR_LOC = LOC_ARRAY(PLR_LOC).SOUTH > 0 PLR_LOC = LOC_ARRAY(PLR_LOC).REM LIST ANY OBJECTS AT PLAYER LOCATION FOR I = 1 TO MAX_OBJ IF OBJ_ARRAY(I).DOWN > 0 PLR_LOC = LOC_ARRAY(PLR_LOC).EAST > 0 PLR_LOC = LOC_ARRAY(PLR_LOC).NORTH > 0 PLR_LOC = LOC_ARRAY(PLR_LOC).DOWN) ENDIF 142 .SOUTH PLR_MOVE = 1 ELSE MOV_MSG = ABS(LOC_ARRAY(PLR_LOC).WEST > 0 PLR_LOC = LOC_ARRAY(PLR_LOC).DOWN PLR_MOVE = 1 ELSE MOV_MSG = ABS(LOC_ARRAY(PLR_LOC).NORTH) ENDIF ENDCASE CASE "s" IF LOC_ARRAY(PLR_LOC).NORTH PLR_MOVE = 1 ELSE MOV_MSG = ABS(LOC_ARRAY(PLR_LOC)." ENDIF NEXT I RETURN REM END SUB REM PARSE PLAYER COMMAND PARSE: MOV_MSG = 0 PLR_MOVE = 0 SELECT CMD$ CASE "n" IF LOC_ARRAY(PLR_LOC).SOUTH) ENDIF ENDCASE CASE "e" IF LOC_ARRAY(PLR_LOC).UP PLR_MOVE = 1 ELSE MOV_MSG = ABS(LOC_ARRAY(PLR_LOC).LOCATION = PLR_LOC PRINT "There is a " + OBJ_ARRAY(I).NAME + " here.EAST) ENDIF ENDCASE CASE "w" IF LOC_ARRAY(PLR_LOC).WEST) ENDIF ENDCASE CASE "u" IF LOC_ARRAY(PLR_LOC).
INVENTORY = 1 OBJ_ARRAY(2).LOCATION = PLR_LOC PRINT "Dropped.ENDCASE REM LOOK CASE "l" PLR_MOVE = 1 ENDCASE REM INVENTORY CASE "i" INV_CNT = 0 FOR I = 1 TO MAX_OBJ IF OBJ_ARRAY(I).NAME ENDIF NEXT I IF INV_CNT = 0 PRINT "You are empty-handed.INVENTORY = 1 INC INV_CNT IF INV_CNT = 1 THEN PRINT "You are carrying:" PRINT OBJ_ARRAY(I).INVENTORY = 1 OBJ_ARRAY(2).LOCATION = 0 PRINT "Taken." ELSE PRINT "You can't see any leaflet here!" ENDIF RETURN ENDCASE CASE "drop leaflet" IF OBJ_ARRAY(2).INVENTORY = 0 OBJ_ARRAY(2).LOCATION = PLR_LOC OBJ_ARRAY(2)." ELSE PRINT "You don't have any leaflet!" ENDIF RETURN ENDCASE ENDSELECT IF PLR_MOVE = 1 REM DISPLAY NEW LOCATION GOSUB CRNT_LOC REM DISPLAY NEW LOCATION DESCRIPTION GOSUB CRNT_LOC_DESC REM LINE SPACING PRINT REM DISPLAY OBJECTS AT PLAYER LOCATION GOSUB DISPLAY_OBJECTS ELSE REM DISPLAY APPROPRIATE MESSAGE THAT PLAYER CANNOT GO THAT DIRECTION 143 ." ENDIF RETURN ENDCASE CASE "take leaflet" IF OBJ_ARRAY(2).
and that will be the subject of the next part of the tutorial. For now. add a new object or even a new location if you like. 144 .PRINT NOGO$(MOV_MSG) ENDIF RETURN REM END SUB We are not finished with objects and managing objects but this is a good start. We have to learn the Zork parser now. play with the code.
This action might not be possible to undo. Are you sure you want to continue?
We've moved you to where you read on your other device.
Get the full title to continue reading from where you left off, or restart the preview.