Darkbasic Guide= Dbproguide

145
MADLAD DESIGNS 2008 DBPro Guide Beginners Guide to DarkBasic Pro Nickydude R EVISION 1

Transcript of Darkbasic Guide= Dbproguide

Page 1: Darkbasic Guide= Dbproguide

0

MADLAD DESIGNS

2008

DBPro Guide Beginners Guide to DarkBasic Pro

Nickydude

R E V I S I O N 1

Page 2: Darkbasic Guide= Dbproguide

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

Page 3: Darkbasic Guide= Dbproguide

2

Introduction

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).

Andy.

Page 4: Darkbasic Guide= Dbproguide

3

TDK_Man's Dark Basic Programming for Beginners

Part 1 - 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. In part 1 you will find a very brief introduction to programming languages

and an explanation of variables.

The examples will be in Dark Basic Classic, but the examples and theory should apply with little or no

modification to any dialect of BASIC - including DB Pro.

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,600,16

CLS

PRINT "Hello World"

Where Do I Start? OK, so you've downloaded the DB demo or bought it, loaded it up and don't know what to do next. You've no programming experience and can't make any sense out of the help files and/or manuals. Well, you need to learn the ABC's and start simple, so that's what we will do...

Programs: In a nutshell, computers manipulate data by transferring numbers around in memory. For example, when a number is placed in a certain part of memory called the video memory, a dot will appear on your monitor screen. The colour of the dot depends on the value of the number. Every image, text character and coloured dot you see on the computer screen is nothing more than a number in your computer's memory. When you play a computer game, all you are seeing is the results of all these numbers being manipulated to create what you see on the screen. A computer program is simply a list of instructions the computer has to follow to do it. To write a game, you need to create the machine code which tells the computer's CPU where to move the data from and where to put it. However, as binary (all 0's and 1's) is not the easiest way to go about it, back when I first started programming, a language called 'Assembler' was developed. This turned the programming process into something more manageable and easier to learn. call_oz(GN_Nln) push iy pop hl ld de, 2 ld b, 6

Page 5: Darkbasic Guide= Dbproguide

4

call_oz(GN_Gdn) ld d,b ld e,c ld hl, 10 call_oz(GN_M16) ld b,h ld c,l Even so, as you can see from the above snippet of Z80 assembler, the commands were still obscure and looking at it you had very little idea exactly what it did. Besides that, something like clearing the screen - which we now take for granted with CLS in DB - took many lines of code, because each individual dot on the monitor had to be manually set to black in a loop - and all you had at your disposal were simple instructions like loading values into registers. This was known as a 'Low-Level Language' as you had to get down to talking directly to the hardware. Also, you had to learn the version of ASM for the CPU in your machine. This might be Z80, 6502, 68000 or one of many others - none of which for the main part were interchangeable. The BIG advantage that programming the hardware like this was FAST - blindingly fast! And, 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, but a thousand times bigger and more difficult to write. Instead, short assembler routines are written for specific purposes where speed is essential. Over time, other programming languages appeared which removed the low-level commands like LD, PUSH and POP and replaced them with higher level commands like PRINT and REPEAT...UNTIL. When these programs were run, the high-level instructions were compiled into low-level machine code that the computer could use. Examples of these languages were Pascal, Forth, C and COBOL - each having their own areas of expertise. And then along came BASIC or Beginners All-purpose Symbolic Instruction Code which as the name suggests was aimed firmly at beginners. Clearing the screen was reduced to CLS, outputting text to the screen used PRINT and so on. It was easy to learn but slow as it was interpreted (not compiled into machine code), though later on, compilers appeared which sped things up a bit. 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.

Variables: All versions of BASIC are built using the same building blocks. A program which uses these building blocks can be run on practically any machine, though things start to alter when you get to the commands which make use of the hardware differences between machines - for example, versions of BASIC running on a PC and a Mac will both have unique commands. One of the building blocks in any higher level language is variables. Without them you would have an impossible task writing anything but the smallest program. So, what is a variable? Well, a variable is in effect a substitute for something which can change - either a numeric value or an alphanumeric string of characters. A numeric variable can be used in a formula or calculation in exactly the same way as a number can. Take the simple example: A=100

Here we are creating a variable called A and giving it the value 100. I found it easiest when learning to program

by thinking of it as follows…

Page 6: Darkbasic Guide= Dbproguide

5

The computer creates a cardboard box in memory, sticks a label on the front of the box and writes 'A' on the

label. It then puts 100 into the box.

From this moment on, whenever your program refers to 'A', 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.

B=50

OK, another box only with B on the label and 50 stored inside it. 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, the computer gets the 100 out of box A and the 50 out of box B and adds them together, putting the resulting 150 into a new box it creates and labels 'C'. 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'. This is different to the way you do it in conventional maths, so be aware of this. 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. The variable 'A' can change, 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, A already equals 10 (as defined on the first line), so A=100 is a false statement as A actually doesn't equal 100 - it equals 10! If the first line also said A=100, then the second line would be true as A does indeed equal 100 at that point. You should now see the reason for reading it as 'A becomes equal to' rather than just 'A equals'. This may sound very confusing at first, 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 - where the answer to a question is always either yes (1) or no (0). For example, 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. Computers never say 'I don't know' or 'maybe' in these cases!

Page 7: Darkbasic Guide= Dbproguide

6

As the A=100 is the same as what we have just covered, it is important to have firmly in your mind the difference between the two. 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. As an aside, in the programming language Pascal, these two tasks have a different syntax to avoid this confusion. Setting the variable A to equal 100 in Pascal is done with A:=100 with the colon meaning 'becomes'. As such it does actually read A becomes equal to 100. IF A=100 in Pascal is exactly the same as BASIC (without the colon).

Variable Names: The only rules you should apply are:

1. Don't use reserved words as a variable names (words used as BASIC commands like Print, Do, Loop etc), though you can use variable names of which only a part is a reserved word. For example, Sprite is a reserved word and not recommended as a variable name, but MySprite would be OK.

2. Always start a variable name with an a..z or A..Z character - never a number. A number can be placed on the end or in the middle of the variable name if you wish though. Even a variable name like ABC123DEF is acceptable!

3. Don't use spaces or any special symbols in variable names other than the _ (underscore) character. This can be used to separate words to make them more readable. Eg: Time Left is a no no. Time_Left is OK.

You can use any combination of characters and numbers in a variable name as long as you follow rule 2 above, so make use of the ability and use a name which makes sense. So, if you are storing a value which represents the players score then use a variable called 'Score'. 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. Also, unlike some programming languages, you don't have to declare variables prior to using them. The first time you refer to them in your program they exist, and if you don't specify that they contain anything, numeric variables automatically contain 0 (zero). OK, that's numeric variables… or is it? Well, not quite. In DB there are two types of number - Integers, or whole numbers like 10, 123 or 1000 and Reals (or floats - floating point fractional numbers), like 1.373, 328.45 or 1000.09. Integer and real variables need different amounts of memory to store values, so you need to tell DB what type of value it is you are storing. You do this with the variable name. Variables which need to store real numbers are identified by putting the # symbol on the end of the variable name. This effectively tells DB to make a bigger cardboard box to hold the decimal point and numbers after the decimal point. So, the following examples are correct:

Age=21

Apples=8

Height#=137.45

ObjAngleX#=120.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. For example:

Page 8: Darkbasic Guide= Dbproguide

7

A#=100.23

B#=10.14

C=A#+B#

What is C equal to this time? If you said 110.37 you would be wrong. If you said 110 then you would be correct. A# and B# are added together to get the correct result of 110.37, but the result is placed into C which is an integer variable, so the .37 bit is lost. This effect is known as variable casting in some programming languages. (The result is comparable to the DB function INT() in DB). Sometimes you may want to do this, but when learning, the chances are that you do not, so beware!

Strings: As well as numeric variables, you can also have string variables which are pretty much the same, but hold alphanumeric characters (a..z A..Z 0..9) rather than numbers. This can be a single character, a word or a complete sentence up to 255 characters long. And, 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. For example:

A$="The cat sat on the mat."

Name$="Fred Bloggs"

Age$="21"

FaveHobby$="5-A-Side Football"

…are all legitimate string declarations. But, note that in the third example Age$ may equal 21, but adding the variable to itself would equal 2121 - NOT 42 as it's a string variable - not a numeric variable.

Arrays: Arrays are very useful. Arrays are easy if you are shown how they work in the right way. Arrays are a nightmare to new programmers if you are not! Arrays are nothing more than groups of normal variables, each with an index number with which to access them. 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). This is done with the DIM command. 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, along with their best score. The variable Name$ could hold their name and the variable Score could hold their best score. But, that's only good enough for just one single player. Use the same variables for player number two and player number one's name and score are lost! So, we use Name1$ and Name2$ for the names along with Score1 and Score2 for the scores. I think you can see where we are going with this… If you had 100 people playing this game, we would need Name1$, Name2$, Name3$ all the way up to Name100$ - and the same for the variable Score.

Page 9: Darkbasic Guide= Dbproguide

8

Also, the line in your program which increments the score would have to be repeated 100 times - once for each player. Arrays rid you of this hassle. Using:

DIM Name$(100)

DIM Score(100)

...will create two arrays, the first allowing you to store 100 strings of up to 255 characters (1 to 100, though

you also have number 0) and the second, 100 numbers. This is a 1 dimensional array.

Memory is allocated in a continuous block large enough to store the requested number of variables stated in

the DIM statement. The Score array would look something like this:

Each of the 100 variables is accessed by using its index number. This way you can have:

Name$(1)="John"

Name$(2)="Dave"

Name$(99)="Pete"

And so on.

Score(1)=2000

Score(2)=2500

Score(3)=2100

…would be the respective scores. For example, Score(12) would belong to player Name$(12) and so on. 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. So that's single dimensioned arrays. What about Multi-Dimensioned arrays? Well, they are basically the same, but instead of the array being one single line from 0 to 100, they are created in a 2 dimensional grid format. 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. Dimensioning an array like this just needs you to use two values in the DIM statement - the number of boxes across and the number down. Bearing in mind that array indexes start at 0, to get a numeric array grid of 25 variables (5 across and 5 down) called Location, you would use:

DIM Location(4,4)

However, while learning, if you wanted an array of say 10 across and 5 down you would be better off using DIM ArrayName(10,5) and completely ignoring the 0 element until you get the hang of things. Doing this, for our 5x5 integer array we would therefore use:

Page 10: Darkbasic Guide= Dbproguide

9

DIM Location(5,5)

It doesn't matter which method you use - 0 based arrays or 1 based arrays - just use the one you understand

and are happiest with. Remember you don't have to use the 0 part of an array - it's just a waste of memory if

you don't. With small arrays, this isn't really a problem, though the larger the array, the more memory is

wasted.

When created, you access the contents using Location(1,1) or Location(1,3) etc. 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). In the above diagram, the box with the 200 in it would be set with Location(3,2)=200. Once again, in your programs, you can replace the numbers with variables and use something like Location(XLoc,YLoc). Finally to complicate things even further, you can take the dimensioning one step further by creating 3 dimensional arrays. 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. This would be done with:

Dim CubeArray(3,3,3)

In this example, the three values correspond to the X, Y and Z axis of the array and this will create a block of variables as shown in the image on the right. As you can see, the 'top slice' is just like the 2 dimensional array discussed in the previous section. However, as the DIM statement has a further Z value, then the array is stacked four deep - using the indices 0 to 3. This gives you 4 variable grids, each one 4 across and 4 down and to set the variable highlighted in red to the value 10, you would use CubeArray(1,3,2)=10. In other words, 1 along the X axis, 3 along the Y axis and 2 along the Z axis.

Page 11: Darkbasic Guide= Dbproguide

10

But, 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. In essence, 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. For example: Print "Please Enter Your Name: ";

Input Name$

CLS

Print "Hello ";Name$

This simple program will print the message 'Please Enter Your Name.' onto the screen. The next line uses BASIC's INPUT command. When your program reaches this line, it will stop for the user to type their name in. Whatever they type will be stored in the string variable Name$ when they press the Enter key. On the next line, CLS will clear the screen and on the last line, the PRINT command will print 'Hello' followed by the contents of Name$, (whatever name they typed in). Another example:

Print "Please Enter Temperature In Centigrade: ";

Input C

F# = 1.8*C+32

Print C;" degrees centigrade equals "; F#;" degrees Fahrenheit."

This example asks the user to enter a number for a temperature in Centigrade (Celsius) and stores the value in a variable called C. The next line uses the variable C in a formula to convert the temperature to Fahrenheit and stores the result in the variable F#. Notice that this is a real number variable as the formula includes the value 1.8 which means that the result may be a real number too. The last line prints out the result in a formatted sentence. There you have it - two very simple programs which demonstrate the use of variables. However, these programs run just once and then end. You have to re-run them every time you want to use them again. 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 - 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, so a number of options are available to you for this task. Some of these options can be applied to numeric variables, some to strings and some to both.

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, subtract, multiply and divide are represented by +, -, * and / respectively. Therefore, adding together two numeric variables is just as easy as adding two numbers. For example to add 30 and 50 normally we would put 30+50 and the answer would be 80. In a program, that would equate to:

Page 12: Darkbasic Guide= Dbproguide

11

A=30

B=50

C=A+B

Print C

...and when C is printed to the screen you see the number 80 printed. More complicated formulas can also be built up with a combination of the four basic operators such as A=M*V/X+Z. However this brings into play a very important aspect of maths when programming - Order Of Precedence. This is best explained with the following example so type it in and run it. 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. So why does it print 13?... The reason is that Dark BASIC (like all programming languages) carries out maths in a specific order. Multiply and divide (* and /) calculations are done first, followed by addition and subtraction (+ and -). So, 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. Now this may not be what you want - you may want it to be done in the order that results in 27. You can force DB to do this by use of the '(' and ')' symbols (parentheses). Anything enclosed in parentheses is given a higher order of precedence and will be calculated before anything else. So: D=(A+B)*C

...will force A to be added to B first before the result is multiplied by C.

Relational Operators: These are used to compare data items (numeric and strings) and use the = (equals), < (less than), > (greater than), <= (less than or equals), >= (greater than or equals) and <> (does not equal) symbols. 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. In a nutshell, we need in our programs a method to make decisions. Do something only if another thing has already been done or act on a users input - that sort of thing. With relational operators and IF...THEN that is possible. So, let's have a quick rundown on IF...THEN first:

IF...THEN Without IF...THEN statements, programs would be impossible to write as there would be no way to make decisions. The basic form is as follows:

IF condition

Do stuff here only if the condition is met

ENDIF or...

Page 13: Darkbasic Guide= Dbproguide

12

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, the condition is deemed to have been met if the condition is tested True (returns a 1) and the relevant code is executed. Using IF...THEN you don't actually get to see the 0 or the 1, 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 - otherwise nothing is done. 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. 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. 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. If the variable contained ANY other value the code between the ELSE and the ENDIF is executed. OK, that out of the way, on with the description of relational operators. It is taken for granted that the following examples are used with variables as conditions in IF..THEN statements so instead, I'll show examples with proper numbers along with what they would return.

Equals (=) Checks to see if two items are equal. 1 is returned if they are, 0 if they are not. Eg: 10 = 10 - Returns True (1) as 10 does indeed equal 10. 10 = 11 - Returns False (0) as 10 does not equal 11.

Less Than ( < ) Checks to see if the first item is less than the second. 1 is returned if it is, 0 if it is not. Eg: 2 < 10 - Returns True (1) as 2 is less than 10. 7 < 5 - Returns False (0) as 7 is not less than 5. 9 < 9 - Returns False (0) as 9 is not less than 9.

Greater Than ( > ) Checks to see if the first item is greater than the second. 1 is returned if it is, 0 if it is not. Eg: 2 > 10 - Returns False (0) as 2 is not greater than 10. 7 > 5 - Returns True (1) as 7 is greater than 5. 9 > 9 - Returns False (0) as 9 is not greater than 9.

Less Than or Equals (<=) Checks to see if the first item is less than or equals the second. 1 is returned if it is, 0 if it is not. Eg: 2 <= 10 - Returns True (1) as 2 is less than or equals 10. 7 <= 5 - Returns False (0) as 7 is not less than or equals 5. 9 <= 9 - Returns True (1) as 9 is less than or equals 9.

Greater Than or Equals (>=)

Page 14: Darkbasic Guide= Dbproguide

13

Checks to see if the first item is greater than or equals the second. 1 is returned if it is, 0 if it is not. Eg: 2 >= 10 - Returns False (0) as 2 is not greater than or equals 10. 7 >= 5 - Returns True (1) as 7 is greater than or equals 5. 9 >= 9 - Returns True (1) as 9 is greater than or equals 9.

Does Not Equal ( <> ) Checks to see if the first item is not equal to the second. 1 is returned if it is not, 0 if it is. Eg: 2 <> 10 - Returns True (1) as 2 does not equal 10. 9 <> 9 - Returns False (0) as 9 does equal 9. So, to finish, a VERY basic little number game as an example of using what we have learnt in this tutorial. Don't worry if there are any commands that haven't been covered in the tutorial. I've commented the program and they will be covered eventually. If you like, look those up in the DB help files by pressing F1 when in the DB editor. 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. See how quickly you

can guess it!"

Print

Input "What is your guess? ",Guess

Print

If Guess < MyNumber: Rem If chosen number is lower than computer's number

Print "Your guess was too low. Try again."

Sleep 2000

Endif

If Guess > MyNumber: Rem If chosen number is higher than computer's

number

Print "Your guess was too high. Try again."

Sleep 2000

Endif

If Guess = MyNumber: Rem If chosen number is equal to computer's number

Print "Your guess was correct. Well done!!"

Print

Print "Do you want to play again (Y/N)?"

Repeat: Rem Repeat...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...": 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. If you like, try changing it so that:

Page 15: Darkbasic Guide= Dbproguide

14

1. The program asks for the players name at the start and uses the name in the in-game comments. Eg:

"Your guess was too high Peter. Try again."

2. When the user guesses correctly the program tells them how many guesses they took to do it.

3. When the player exits the game the program tells them how many games they have played along with

their quickest and slowest games - guess-wise.

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, don't look until you have attempted it.

There is no right or wrong way to do these three tasks - some ways are just better than others. If you alter the

program to do these three things and it works, then consider yourself having passed the test.

--ooOOoo—

Part 2 - Layout, Structure And Style Before I start with the tutorial, how did you get on with the programming task I set at the end of Part 1? If you got a working program, then well done. Below is how I would have done it. In a program this minor, there is little difference between any of the methods you use, so if you did it differently, then no problem. The method I have used is not meant to be the best or definitive way to do it - it's just the way I decided to do it. Who knows, there may be reasons why your method is better than mine! Anyway, here's my solution to the task… Randomize Timer(): Rem Initialise the random number generator

Input "Please Enter Your Name: ";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. See how quickly you

can guess it!"

Print

Input "What is your guess? ",Guess

Inc GuessCount

Print

If Guess < MyNumber: Rem If chosen number is lower than computer's number

Print "Your guess was too low ";Name$;". Try again."

Sleep 2000

Endif

If Guess > MyNumber: Rem If chosen number is higher than computer's

number

Print "Your guess was too high ";Name$;". Try again."

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. Well done ";Name$;"!!"

Page 16: Darkbasic Guide= Dbproguide

15

Print "That time you took ";GuessCount;" guesses."

Print

Print "Do you want to play again (Y/N)?"

Repeat: Rem Repeat...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, you played ";GamesPlayed;" games."

Print "Your best attempt was ";Best;" guesses and your worst was

";Worst;" guesses."

Print

Print "Goodbye...": Rem Print Goodbye message

End: Rem End the program

Endif

Endif

Loop

Only a couple of things worth mentioning... First of all, notice that the variables Best and Worst are initialised at 1000 and 0 respectively which seems odd. Whenever you are recording the highest and lowest of something you should always start the variables off at the opposite end of the scale. Here, lower number guesses are best, so we start the variable Best off at 1000. That way, when the player has finished the first game and has taken less than 1000 guesses (pretty likely), then Best is replaced with that number. In subsequent games, Best is replaced by the number of guesses ONLY if it is less that the number already stored in the variable Best. Worst works in the same way only in reverse. The first game played, 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. By the end of the game, the least number of guesses will be stored in Best and the most in Worst.

OK, 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, at the same time, pointing out that they weren't much use as they had to be re-run each time you wanted to use them. 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. This is actually quite simple to do, but before we do it's important to know why it ends so abruptly. When all DB programs are run, the computer executes each line of instructions starting with the very first line. 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. At any time during the execution of your program, 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, it drops down to the next one and continues. When there are no more lines, the program ends. It's like reading a book. 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. Let's remind ourselves of the last example program from tutorial 1: Print "Please Enter Temperature In Centigrade: ";

Input C

F = 1.8*C+32

Print C;" degrees centigrade equals "; F;" degrees Fahrenheit."

Page 17: Darkbasic Guide= Dbproguide

16

Here, the message asking you to enter a value is printed to the screen, the computer stops and waits for input on the next line. When the user enters a value and presses the Enter key, the next line calculates the result and on the final line, the result is printed to the screen. As there are no more lines, at this point the program has nothing more to do so it ends. There are however commands which you can use to control the order in which the lines of instructions are carried out. Our example above doesn't use any of them so the program just zips through all the lines in order and then ends.

GOTO One such command is GOTO which tells the program counter to jump to a specific location in the program, skipping all lines in between. This can be a point anywhere in the program but has to be a defined label. A label is a single word ending with a colon. The rules for naming labels are the same as for variables. 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, you will see that the 'Program started' message is printed to the screen followed by the 'This line appears!' message. The 'This will never be printed!' message never appears because on the second line, the program counter is told to jump to line four, skipping line three. 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, making it very difficult to follow when your program grows in size. Continuing with our book analogy, imagine if at the bottom of page 1 it said 'continues on page 96'. 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. In theory you might never finish the book - and that's what keeps a computer program from ending.

Loops Sometimes, you want your code to do something a certain number of times and repeating the lines isn't a viable solution. As a silly example, you may want to print three times, the sentence 'This is printed three times.' Obviously, this can be done with three lines - one for each time you want the sentence to appear: Print "This is printed three times."

Print "This is printed three times."

Print "This is printed three times."

But, what about printing something 20 or even 100 times. As you can imagine, this would involve a lot of typing. So, to make life easier, this is where control loops come in. There are many types of loop - all of which could be used to print our lines, 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. For N=1 To 20

Print "This is printed twenty times."

Next N

Page 18: Darkbasic Guide= Dbproguide

17

This loop uses a variable to count from the first supplied number to the last - 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 first time the For N= line is encountered, the variable N is set to the first value (1) and the line number is

recorded.

On the next line, the text is printed. When the Next N line is reached, the value of N is incremented and

compared with the end value (20).

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. This continues until the value of the loop counting variable N matches the end value, at

which point the loop is ended and program control drops down to the line following the Next N line.

In the process, the above loop will print the text message to the screen 20 times. Obviously you can have as

many lines between the For and the Next lines - they will all be carried out the stated number of times.

For…Next loops can be used to count from any number to any other number - you don't have to start at 1. 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).

For example: For N=1 to 100 Step 10 will start with N equalling 1, but the next time around the loop, 10 will be

added making N equal 11. The next time it will be 21 and so on up to 91.

Notice that the highest value it reaches is 91. This is because adding another 10 to 91 would take it over 100 -

the stated end value, so when it can't add any more the loop is ended. Remember this as it might save you a

few headaches in the future!

Hint: If you wanted it to count 10, 20, 30 etc up to 100 you would use For N=10 to 100 step 10. (Starting at 0

instead of 10 would give you 0, 10, 20 and so on).

Finally, there is the ability to count backwards. 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.

Note: The Step -1 is required for counting backwards - even if you are counting down 1 number at a time. 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 counter variable always equals the whole range of values from the declared start and end range

inclusive (unless Step is used). This makes it safe to use the counting variable for other things inside the loop.

Page 19: Darkbasic Guide= Dbproguide

18

In the process of a For…Next loop, the value of the variable used as a counter will change. For this reason, do

not use names which have been used elsewhere in your program. As a tip, I use N, N1, N2, N3 etc ONLY as

For…Next counting variables and do not use them anywhere else in my programs.

Other Loops: As mentioned previously, there are other types of loop and printing a message a number of times could be

done with any of them, 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 - you

would usually exit the program with the END command without leaving the loop.

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. This is covered later on. For now, it's enough to know that we would not use

this type of loop to do the above example task.

Repeat…Until

The Repeat…Until loop is exited from only when the condition on the Until line is met. Unlike the For…Next

loop, somewhere in this loop variables must be altered in order for the condition to be met.

Let's see an example based on the above For…Next loop example:

Counter=1

Repeat

Print "This is printed twenty times."

Inc Counter

Until Counter=21

In this example, you can see the condition on the end. A condition is basically a test which will return 0 (false)

or 1 (true). In this case the condition for exiting the loop is that the variable Counter must equal 21. 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.

(Inc is short for increment and adds 1 to the named variable).

Usage Notes:

As the counter starts at 1 and the counting variable is incremented at the end of the loop (after the Print line),

the variable will be incremented to 21 after the line has been printed 20 times. That's why the exit condition

variable has to be equal to 21 - after which the loop is immediately exited and the Print line not executed a

21st time.

As this loop is repeated until a specified condition is met and the condition is tested for at the end, the lines in

this loop will always be carried out a minimum of once - regardless of whether the condition is true or false

before entering the loop.

Page 20: Darkbasic Guide= Dbproguide

19

While…EndWhile This loop is essentially the Repeat…Until loop with the condition at the

start of the loop instead of the end.

Counter=1

While Counter<21

Print "This is printed twenty times."

Inc Counter

EndWhile

While even having this type of loop may not seem worth the bother, already having Repeat…Until, there is one subtle but significant difference. As the condition is tested at the beginning of the loop, if the condition fails then none of the code is executed - unlike Repeat…Until which has to carry out the code once before the condition is tested for at the end. Usage Notes: The code in the loop is only executed if the counter variable is less than 21. 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, as such, the lines inside this loop may never be carried out at all. So, back to the original theme - stopping our little temperature program ending until we want it to... 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. The final program might look like this: Do Print "Please Enter Temperature In Centigrade: ";

Input C

F = 1.8*C+32

Print C;" degrees centigrade equals "; F;" degrees Fahrenheit."

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 main Do...Loop is never actually exited from - its main use is to stop the program from ending until we are ready. This is done with the End command when the user says no to converting another temperature. OK, that's loops covered, but before we cover any more BASIC commands and get too bogged down with them, let's take a look at how best to put them into a program that will work with optimum performance and still be legible.

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, has Goto's everywhere leaping all over the place and has no structure. It doesn't help if it's your own code either! So, while you are a newcomer, it's best to get into the habit of writing your programs neatly now.

Page 21: Darkbasic Guide= Dbproguide

20

Writing programs in DB or any other language is finicky - as no doubt you have already discovered. This is because of something called syntax. When you write a paragraph of text in a word processor for a friend to read, all your speeling mystaykes make your text look bad, but your friend can still read it and understand what you mean. Unfortunately, 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. This means that you have to type everything EXACTLY as it is supposed to be - right down to every full stop, comma or semi-colon being in the right place. Spaces too are also important. Too many isn't usually a problem, but leave one out when it should be there and your program line will not be recognised. Luckily, DB will stop you running a faulty program and highlight the iffy line - even though it sometimes can't tell you exactly what is wrong with it!

Indentation: Few people indent their code. 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. Indenting makes your code a LOT easier to follow and thus bugs are easier to find - especially when you have more than a couple of hundred lines. You may have noticed that all of the examples in the tutorials so far all use indentation - the code inside loops is offset from the rest of the code. One of the more common errors in programs by new programmers is failing to close loops. Your program may have 20 Repeat lines but only 19 Until lines and once you have a decent sized program, these can be difficult to find - if you don't use indentation... As a rule of thumb, 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. Some people use three spaces, 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. 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

Page 22: Darkbasic Guide= Dbproguide

21

As you can see, indentation makes all the loops stand out so missing Next, Until and EndWhile lines are easier to pick up.

Layout: As well as indentation, you should also adopt a suitable layout for your programs. Once again, it doesn't always make them run any quicker (though it can), but program development time and more importantly error tracking time can be reduced dramatically. I have always said to people who have asked me how long it would take to write an application for them, "two months to write it, six months to get it working properly and forever to get rid of all the bugs". Although a joke, this isn't too far from the truth, so anything you can do to help along the last one is a bonus. Finally, a proper layout for your programs will make them smaller and more efficient. Useless or repeated code can be avoided and modifications are made a lot easier. So what does this involve? First of all, 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. You can write programs without them, but once you have used them, you wouldn't dream of going back to being without them! Basically, you can think of a subroutine as a little stand-alone program which you can call on at any time. The code in a subroutine is something that can be called many times in your program - and hence prevents you having to retype the code each time you need it. If you needed to print ten lines of text in three different places in your program, rather than have thirty lines of code, 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. Alternatively a subroutine can simply be code that you may only need once, but just want to keep in a separate location making your program tidier. Subroutines start with a label and end with the line Return. The command GOSUB is used to call the subroutine and as the calling line is stored, the Return bit at the end knows where to jump back to after the code in the subroutine has been executed. For this reason, you should never exit out of a subroutine by using GOTO, though calling another subroutine from a subroutine is OK as it too will return automatically. On return from a subroutine, control is passed to the line immediately after the GOSUB line. Although a subroutine is a separate block of code to the code in the main program Do…Loop, it is still classed as part of your main program. As such, 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. Having now covered subroutines, you now know enough to continue with our list above (if you were wondering why I didn't start with item 1 - Initialisation).

Page 23: Darkbasic Guide= Dbproguide

22

Initialisation At the start of your program, you need to initialise all the required variables, set the screen mode, maybe create a matrix, load images and objects, turn off the mouse if necessary and all those sort of tasks. I put all of these into a subroutine called Setup. 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.

Main Menu Next, you may want your program to display a menu screen with buttons for things like Options, 1 Player Game, 2 Player Game, Start Game and Exit - rather than just starting the game. As you need to call this menu at the end of each game, you should place it in a procedure. Inside the procedure you should have a conditional loop like Repeat..Until which is only exited if the user clicks on one of the Start Game buttons. At the end of any game, you can simply call this procedure again with Gosub to display the menu screen again.

Main Program Do…Loop Your main program should be enclosed in a Do…Loop so that when it runs, it doesn't end until you want it to. Some coders prefer to use Repeat...Until for the main loop. Either will do - it's a matter of personal preference. When the time comes for it to end, you can use the END statement inside a condition along the lines of 'if the user presses the X key then end the program'. From within the main program loop, subroutines can be called and they will always return back to continue round the loop. If you are writing a 3D program, then the last line inside the loop should be a Sync. More will be said about Sync later in the tutorials, but for now all you need to know is that in most 3D programs, for optimum speed, you will want to control the screen output yourself and issuing a Sync updates all the 3D information on the screen. Each Sync takes time, so one placed at the end of the main loop refreshes the screen with optimal performance.

End Of Program This is simply the word END which stops your program from running. Placed on the line following the LOOP line, it should never be executed as there is no way to get out of the Do…Loop to even reach it. However, if program control ever reached this point, 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. So, putting it in doesn't do any harm - I do it out of habit…

Subroutines This is the place in your programs where you put all your subroutines, one after another. I tend to put a Rem line immediately before each subroutine with a brief description of what it does. Note: Rem is short for REMark and is purely a comment. 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. Remarks have no overhead on your programs as they are completely ignored when your program is compiled - they only exist in the editor!

Page 24: Darkbasic Guide= Dbproguide

23

So, 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 - even if you did write it yourself. Lots of comments will remind you of what does what and you'll be glad you added them.

Functions (In brief): I'm not going to cover functions in great depth here as there are more detailed tutorials on the subject available. Functions are for all intents and purposes the same as subroutines but with a couple of differences. They are blocks of code like subroutines and after being used return to the line after the one they were called from - just like subroutines. But, 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. Functions end with the word ENDFUNCTION and an optional variable which is used to pass data back to the calling line. The calling line uses the function name and must include a list of parameters identical to the function header (if any parameters are used). This sounds quite complicated so let's see an example: Function Pointless(A,B,A$)

L=Len(A$)

C=L*A+B

EndFunction C

OK, this aptly named function receives integer values which it places into the numeric variables A and B, and a single string which it puts into the string variable A$. It calculates the length of the supplied string using the Len() function and stores it in the variable L. Next, it multiplies the value of L with the value passed to it in A and adds the value in B, storing the result in the variable C. Finally, the function ends and passes the value stored in C back to the calling line. So, an example of this function being called might be:

NumVar=Pointless(10,5,"Hello") As the function returns a value, we have to use a variable to 'catch' what is returned - hence the NumVar= but at the start. If the function returned no value, this bit could be omitted making the calling line Pointless(10,5,"Hello"). Next comes the function name and the parameter list in parenthesis. When the three items get to the function they are placed in A, B and A$ respectively. We are passing proper numbers and a literal string here, but we could have used variables instead. If we did, the variable names would be irrelevant, but the variable types must match. The same goes for the return variable type too. The only restriction is that you cannot pass arrays to a function. In the function, L becomes 5 (the length of the word Hello), which is multiplied by A (10) and has B (5) added to it. The result (55) is returned by the EndFunction and is caught be the variable NumVar. Note however that only a single item of data can be returned from a function. That's one numeric variable or one string - no multiple variable lists like the entry parameters. Another important difference with functions is that they use local variables (see subject Scope below). Variable names inside functions can be the same as those outside the function but still be completely separate entities which can co-exist together. Local variables are destroyed when exiting the function.

Page 25: Darkbasic Guide= Dbproguide

24

Before we go any further, I think it would be best to spend a few moments covering local variables and the other type - global variables. 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, so I left it until now to cover the subject.

Scope In non-technical terms, scope is where in your programs your variables can be seen. In many programming languages you have both Local and Global variables which are declared before you use them. Where you declare them affects where you can see them. Global variables can be seen anywhere in your program. This sounds a bit obvious until you realise that Local variables are local only to functions - they cannot be seen outside of the function that they are used in.

Local Variables: DB Classic is slightly different to most programming languages as global variables don't officially exist. For example, if in your main program you say A=10 then A can be seen throughout your program - including procedures but excluding functions. Inside a function, 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 - in your main program. When you exit from the function, the function's A variable is destroyed and you will find that the original A still contains 10 - not 100! Likewise, if in your main program you had B=100, when you enter a function, B does not exist, 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. 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. This is what local variables are all about.

Global Variables:

OK, earlier I said that in DB, global variables don't officially exist. Well they don't, but there is a work-around you can use if necessary. 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, 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. With arrays we have no need to pass more than one as we can use arrays. Arrays declared inside a function however are still local and are destroyed on exit. They cannot be seen on return to the main program. Also, as they are destroyed when exiting the function, you can DIM an array at the start of a function and call it many times without getting an 'Array Already Dimensioned' error. You don't have to 'Undim' as you would in the main program. This also conveniently gets around the limitation mentioned earlier that you cannot have an array in the parameter list you pass to a function. Now you don't need to as the array can be seen and modified inside the function anyway! And that's program layout covered. Our skeleton program layout should therefore look something like this (not real code - layout example only):

Page 26: Darkbasic Guide= Dbproguide

25

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,Y,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,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 - 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

Page 27: Darkbasic Guide= Dbproguide

26

section and add the calling line into the main program loop. Doing this keeps the code in the main loop as

short as possible and easier to follow.

That's it for Part 2 of the Programming For Beginners tutorial series.

--ooOOoo—

Part 3 - Elementary Commands

In this part of the series, we will be looking at some of the elementary commands in Dark Basic - many of

which will work in any version of Basic and are therefore 2D only. DB's more advanced 2D and 3D commands

will be covered later in the series. For now, we are just learning how to program, so we need to keep things

simple.

If you look at a toddler’s first reading books, you won't find any Chaucer or Shakespeare - just Janet and John

stuff. It's important to remember that if you don't get to grips with the Janet and John stuff in DB (Text & 2D),

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, but in no particular order.

The commands in this part of the series will all be 2D, but 3D will be mentioned occasionally only if there is

anything important to say. 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.

Finally, it's worth mentioning that the order in which the commands are covered may seem strange. 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.

CLS is a good example as you can use the RGB command as an additional parameter. So, RGB is covered first

when really CLS (being one of the more basic commands) would have normally come before it.

When you write any computer program, you need a method of putting information onto the screen. A number

of commands are available for both text and graphics - some of which do the same thing, but with subtle

differences. You choose which to use depending on the circumstances.

Screen Output

A screen consists of a grid of dots - initially all set to black. Each dot (or pixel) on the screen has a co-ordinate

comprising of an X and Y value.

The X value starts at 0 on the left hand side of the screen and increases as you move right. The maximum value

for X depends on the current screen size. By default, unless you specifically change the screen size, DB

programs run in a screen size (resolution) of 640x480. This means that X runs from 0 to 639.

The Y value starts at 0 at the top of the screen and increases as you move down the screen. 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.

Page 28: Darkbasic Guide= Dbproguide

27

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.

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,Yrez,BitDepth.

Most of the usual X/Y Windows modes are allowed, such as 800x600, 1024x768 and so on and the BitDepth

parameter is usually 16 or 32 to select the maximum number of colours available. 16 is the norm as some

graphics cards will not run at 32 bit in some screen resolutions. Personally, I tend to do everything with:

Set Display Mode 800,600,16

[Edit] This tutorial was written a few years ago when most DB users worked in this resolution. These days,

systems are a little better and 1024x768x32 (or even higher) is more common.

RGB(Rval,Gval,Bval)

RGB stands for Red, Green, Blue and is the method DB uses to specify a colour. All colours on a standard CRT

monitor are created by red, green and blue guns which light up dots on the screen.

The intensity of each of the colour components defines the colour of the dot produced. The lowest intensity

value for the three components is 0 (zero) and with red, green and blue all set to 0, you get a black dot. The

highest value for each component is 255 and with red, green and blue all set to 255, you get a white dot. By

varying the values for all three components, any desired colour can be reproduced.

The three intensity values are supplied in parenthesis in the order red, green blue. So, if you wanted full

intensity green, you would use RGB(0,255,0), whereas RGB(0,100,0) would produce a darker shade of green.

The colour created can be applied to any screen output and the command must be issued again to switch to

another colour. The colour is always selected before the command which draws to the screen - not after.

Anything already on the screen will remain the designated colour when RGB is used to select another colour.

Here are some other common colours as RGB commands:

Red - RGB(255,0,0)

Blue - RGB(0,0,255)

Magenta - RGB(255,0,255)

Yellow - RGB(255,255,0)

Cyan - RGB(0,255,255)

Grey - RGB(150,150,150)

Internally, these intensity values are converted to a colour number which you can also use rather than RGB if

you want. One advantage of doing this is that the removal of a calculation makes your program run quicker -

albeit only by a miniscule amount. Don't forget however that there may be many, many RGB calculations in

your programs and all these tiny amounts soon add up...

However few people do use colour numbers as three RGB values are infinitely more user-friendly to use.

What's easier to use for a colour: RGB(100,255,50) or a number like 32742?

Page 29: Darkbasic Guide= Dbproguide

28

RGBR(), RGBG() and RGBB()

If however you do have a colour number like 32742, how do you turn it into red, green and blue colour values?

Well, it's easy with the RGBR(), 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, green and blue intensities from our example colour value of 32742 placing the values

into the variables red, green and blue respectively, after which:

Ink 32742,0

... and

Ink RGB(Red,Green,Blue),0

will produce the same colour.

INK

Having the ability to define a colour, you now need to apply it. This is done with the Ink command which uses

the syntax:

Ink ForegroundCol,BackgroundCol

The two parameters can both be RGB values, colour values or a combination of both.

If you imagine the capital letter A is produced by dots on a grid, 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. If you have a red screen and want to print white text onto it, then you would set the foreground

text colour to white and the background colour to red with:

Ink RGB(255,255,255),RGB(255,0,0)

Once again, colour values can be used with the Ink command, but are seldom used. The only exception is the

colour black which is 0, so setting either the foreground or background to black can be done with a 0 rather

than RGB(0,0,0) - it's one less calculation for DB to carry out so it's faster. So, white text on a black screen

would be done with:

Ink RGB(255,255,255),0

CLS

The CLS command clears the 2D screen. It has no effect on 3D screens. If you have a screen with both 2D and

3D areas, then CLS will only clear the 2D portion of the screen. Used on its own, CLS clears the screen to black,

(assuming you have not changed the current ink background colour), though you can also use an optional RGB

Page 30: Darkbasic Guide= Dbproguide

29

command (or colour number) to clear the screen to any other desired colour. CLS RGB(255,0,0) for example

will clear the screen to red.

Text Commands:

PRINT

Print is the easiest way to get text onto the screen and can print literal strings enclosed in quotes, or variables.

The screen output appears at the current cursor position (which after a CLS, is in the top left corner of the

screen). You can however position it anywhere you want.

Some examples:

Print "The cat sat on the mat"

Print A$

Print NumVar

The first example is a literal string. You can use this to print a simple text message on the screen like "Press Any

Key To Start".

The second and third examples both print the contents of variables - not the names of the variables used (A$

and NumVar). Output appears on the screen at the current cursor position.

After printing to the screen, the cursor position drops down to the start of the next line on the screen - ready

for the next print statement. You can prevent this from happening (so you can print more on the end of the

same line) by placing a ; (semi-colon) on the end of the Print line.

Following a Print statement with a ; on the end, the next Print statement will appear on the end of the line just

printed.

Print "Somewhere"

Print "Over"

Print "The"

Print "Rainbow"

will make the following appear on the screen:

Somewhere

Over

The

Rainbow

Whereas...

Print "Somewhere";

Print "Over";

Print "The";

Print "Rainbow"

will result in:

Page 31: Darkbasic Guide= Dbproguide

30

SomewhereOverTheRainbow

...appearing. Notice that everything is all joined together? That's because we didn't include a space after each

word inside the quotes. Don't worry too much about that as it's very unlikely you would ever use the Print

statement like that anyway - you would just use:

PRINT "Somewhere Over The Rainbow"

Another useful variation of using PRINT is:

Print "Somewhere ";"Over ";"The ";"Rainbow"

Which at first glance doesn't appear to be of much use. However, when combined with variables allows you to

created formatted strings. Take for example:

A$="Henry VIII"

WifeCount=6

Print A$;" had ";WifeCount;" wives."

This will print onto the screen:

Henry VIII had 6 wives.

As you can see, this allows you to present personalised messages in your games. Also worth noting is that you

can also include variable formulas in Print statements too. Such as:

A$="Henry VIII"

WifeCount=6

Print "If ";A$;" had married again he would have had ";WifeCount+1;" wives."

This will print:

If Henry VIII had married again he would have had 7 wives.

There are other ways to control what appears on screen when using strings, but they are covered later in the

'More Strings' section.

SET CURSOR

As mentioned in the Print command section, Print will print your text at the current cursor position. If however

you want to print something somewhere else, you need to be able to position the cursor where you need it.

This is done with the SET CURSOR X,Y command where X is the position across the screen and Y is the position

down the screen.

Set Cursor 10,10

Print "Cursor position 10,10 is here!"

Note: The X and Y referred to here is NOT a pixel position, but a character position, so the above message does

not print at 10 pixels across and 10 pixels down. The values you can use depend on how many characters will

fit across and down the screen - i.e. the screen mode.

Page 32: Darkbasic Guide= Dbproguide

31

STR$()

Str$() is a function which will convert a number or numeric variable into a string. This is most commonly used

for the Text command - why it is introduced here. An example:

A=42

MeaningOfLife$=Str$(A)

Print MeaningOfLife$

This will print '42' on the screen, but it's important to realise that it is the string 42 - not the number 42! Also,

as a string, any of the many string functions can be applied to it which couldn't when it was a numeric variable.

This is a valuable feature which will become more apparent later on.

TEXT

This is a much improved version of the Print command and uses the format:

TEXT X,Y,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. The output part is similar to when using the Print command so you can use

literal strings or variable strings. The main difference is that you cannot use numeric variables with the Text

command as you can with Print - you need to convert them to strings first using the Str$() function.

Another difference is that when using Text to create formatted strings, the + symbol is used instead of the ;

symbol.

Also, unlike the Print command, anything placed on the screen with Text is printed in the current font face and

size - Print just uses the default system font. 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,100, A$+" had "+Str$(WifeCount)+" wives."

CENTER TEXT

A variation on the Text command is the Center Text command which will - believe it or not - print a message

centred around a given X position on the screen. The command syntax is:

Center Text X,Y,Output$

...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. In other words, X is the position on screen at which the centre of your string

will be positioned. If your screen is 800x600, then for your message to be in the centre of the screen along the

X axis you would use:

Center Text 400,300,"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. You must figure out the Y position yourself!

Page 33: Darkbasic Guide= Dbproguide

32

Graphics Commands:

As well as text output using the Print and Text commands, you also have the ability to output basic pixel

graphics. Once again, DB is aimed at the 3D games programmer, so you do not have a substantial subset of

such commands - they just aren't called for often enough.

DOT

This command lets you 'turn on' a single pixel on the screen and is basically a 'Plot' function. Using the syntax:

DOT X,Y

...the pixel at co-ordinate X,Y will appear in the current Ink colour.

BOX

This command creates a filled box and uses the syntax BOX Left,Top,Right,Bottom. In effect, 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 co-

ordinates of the box's bottom right corner. Unlike some BASICs, in DB you do NOT define the top left corner

position then the box's width and height.

As with all 2D graphics commands, the Box command uses the current Ink colour.

LINE

This command will draw a line in the current ink colour. The syntax is:

LINE StartX,StartY,EndX,EndY

...and you simply supply the X and Y screen co-ordinates of the start and end of the line.

CIRCLE

This command will draw a circle in the current ink colour. The syntax is:

CIRCLE CentreX,CentreY,Radius

...and you simply supply the X and Y screen co-ordinates of the circle's centre along with the circle's radius in

pixels.

ELLIPSE

This command draws an ellipse in the current ink colour. The syntax is ELLIPSE

CentreX,CentreY,Radius1,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.

POINT()

This function will return the colour number of a pixel on the screen. Using the syntax

Point(X,Y). X and Y are the required pixel's X and Y co-ordinates. The value returned is the colour value which

needs to be converted to R, G and B values to be of any real use.

Page 34: Darkbasic Guide= Dbproguide

33

Non-Output Commands:

The following commands do not send anything to the screen - they just alter the way things are sent.

SET TEXT OPAQUE

This command switches text transparency off and applies only to the Text command - not Print. When

transparency is off then the current text background colour is shown. 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.

SET TEXT TRANSPARENT

This command switches text transparency on and applies only to the Text command. When transparency is on,

then the text background colour is not displayed and the background screen colour shows through. Enter and

run this example...

CLS RGB(30,0,50)

Ink RGB(255,255,255),RGB(150,0,0)

Set Text Opaque

Print "1. This is text produced with the Print command"

Text 100,100,"Text produced with the Text command (Opaque)"

Set Text Transparent

Print "2. This is text produced with the Print command"

Text 100,120,"Text produced with the Text command (Transparent)"

In this example, the screen is cleared to a dark purple colour and the ink set to white foreground and red

background.

The text is set to opaque and a message is then printed to the screen using both Print and Text commands.

Notice that only the message produced with the Text command has the visible background red colour whereas

the Print command text is unaffected.

The text is then set to transparent and the colour scheme left unaltered. The messages are printed again and

this time the Text command does not show the red background colour.

You will also notice that the Text command has no effect on the screen cursor position. The message created

with the Print command appears on the next line to the last printed message - even though a Text command

has placed a message in the middle of the screen since the Print command.

More Strings:

As mentioned earlier, there are a lot of useful string formatting functions which can be used - some of which

we will cover now. Many can be used to present numeric information on the screen in a more tidy fashion. For

example, ever noticed in games where the player's score is say 3250, 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. So, you convert the score

from a number to a string, use the string functions to add those zeros and print the resulting string onto the

screen rather than the contents of the numeric variable. Just one of the many things you can do with strings.

So let's look at what we need in order to do the score thing...

Page 35: Darkbasic Guide= Dbproguide

34

LEN()

Len is short for Length and as the name suggests, the Len() function will tell you the length of a string. If our

player's score (say it's 200 for example) is stored in the numeric variable Score, 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.

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. 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 - not another string. This is so we can use the number in

calculations. In this case, Len() will return the value 3 as the score 200 is three characters long. We can use this

number to find out how many 0's to add to the front of the string. We want our score to always be printed

onto the screen with 7 digits, 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. When the last 0 is added to make ScoreLen equal to 7 then the loop is

exited.

All well and good - apart from one major problem... can you spot it?

What if the current score is say 1253843? In this case, the length of score as a string is already 7 so the loop

will add another 0 to the front making it 8 long. When you get to the Until ScoreLen=7 condition for continuing

the loop, it will not be 7 so the loop will continue adding 0's trying to reach 7 - and of course it never will!

So, what we have hit on is a prime example of when you have to choose your loops carefully. 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, so therefore faster at doing the job, but more importantly the While...EndWhile loop will not be

entered at all if the current score is already 7 characters long (or in fact longer) - unlike the Repeat...Until

version. This is because the condition for carrying out the code inside the loop is at the beginning of the loop -

not the end!

Page 36: Darkbasic Guide= Dbproguide

35

Knowing which type of loop to use and where, comes when you have a little more experience, but the point is

that they do all have their differences - and uses.

On the subject of speed, you will quite often hear people talking about doing something 'this way' instead of

'that way' as it's faster. Most computers these days have fast processors - measured in GigaHertz rather than

MegaHertz. My first PC had a 33MHz processor and 4MB of memory! So, you will probably think that speed is

no longer an issue. However, there are a couple of things you have to consider:

1. Not everyone has a super-fast computer. There are still a lot of older machines out there, so what runs

smoothly on your machine may not do so on everyone else's machines. This however is only important if you

intend other people to use the programs you write. If you only write stuff for yourself then you don't need to

worry about this aspect.

2. Dark Basic Classic is an interpreted language. When your program has grown quite large (as they always do)

and there is a lot going on, it will slow down even on the fastest of machines. DBPro is a compiled languages

and is therefore a lot faster and less of a problem in this respect.

So, although using one method may only be a tiny fraction quicker, all these fractions can add up and overall

make a significant difference in the speed that your program runs.

Chopping And Changing

Sometimes, you need to manipulate strings. As with all BASIC's, 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. Those who are completely new to programming and jump straight into 3D don't

usually last very long.

Actually, you would probably be surprised how vital the string commands can be for all types of program - 2D

and 3D as well as text programs. Formatting information printed on the screen, shuffling a pack of cards and

saving files to disk all involve working on strings...

LEFT$()

This function will pull out a number of characters (a substring) from the beginning (left end) of any string. The

syntax is:

Left$(Main$,NumChars)

...where Main$ is the string you want to extract the substring from and NumChars is the length of the required

substring. If the value used for NumChars is greater than the length of Main$ then just the available characters

are returned.

As with all DB commands, you can use literal strings where the data is entered directly into the command, or

variables. Examples:

Print Left$("Children",5)

... will print 'Child'

So, assuming that A$="ABCDEFGHIJKLMNOPQRSTUVWXYZ":

Page 37: Darkbasic Guide= Dbproguide

36

Print Left$(A$,5)

... will print 'ABCDE'

B$=Left$(A$,3)

... will take 'ABC' from A$ and place it in B$

A trivial example which demonstrates the sort of thing possible with Left$(). Just copy it into DB and run it!

A$="Darth Vader was the baddy in Star Wars."

B$="k is the 11th lower case letter of the alphabet."

C$="Basil is a herb."

D$="icicles are made of frozen water."

Print Left$(A$,3);Left$(B$,2);Left$(C$,3);Left$(D$,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. The syntax is also the same - Right$(Main$,NumChars).

Print Right$("Children",5) ... will print 'ldren'

And again assuming that A$="ABCDEFGHIJKLMNOPQRSTUVWXYZ":

Print Right$(A$,5)

... will print 'VWXYZ'

B$=Right$(A$,3)

... 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. To be honest though, saying 'substring' is a little misleading as the syntax is

Mid$(Main$,StartPos)

...and as you can see there is no 'length' parameter like many versions of BASIC provide - just a start position.

This means that you can only grab one character at a time.

If you need to extract more than one character to build a substring you can do so with a loop. Example time:

Print Mid$("Children",3)

... will print 'i'

Once again, assuming that A$="ABCDEFGHIJKLMNOPQRSTUVWXYZ":

Page 38: Darkbasic Guide= Dbproguide

37

Print Mid$(A$,5)

... will print 'E'

B$=Mid$(A$,4)

... 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$,N)

Next N

... after which Sub$ would equal 'GHIJK'. For those of you with prior knowledge of other BASIC's, this is the

same as Mid$(A$,7,5).

As you can see, using Left$(), Right$(), Mid$() and adding strings together, you can do some really clever stuff.

There are however some other goodies...

VAL()

This function will return the numeric value of a string containing a number. In effect it is the reverse of Str$()

so if you have Score=1000, A$=Str$(Score) will create a string called A$ containing '1000'. Score=Val(A$) will

then turn the '1000' string back into a numeric variable called Score - replacing what was there already.

Things You Should Know About VAL()

If the string you are getting the value of starts with anything other than a number, 0 (zero) is returned.

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. It's up to you to

check that the string you are using with Val() is in the correct format.

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. Each alpha-numeric

character capable of being printed to the screen has an ASCII code. The character 'A' for example has the ASCII

code 65, 'B' is 66 and so on. Also, remember that numbers also have ASCII codes, so Asc("7") is legitimate and

will return the ASCII code of the character '7'.

Asc() can accept a string, but only the first character will be checked. Asc("F") will return exactly the same ASCII

code as Asc("Fred") as only the F is decoded. Likewise, Asc("7") will return exactly the same ASCII code as

Page 39: Darkbasic Guide= Dbproguide

38

Asc("73621").

Asc() can also be useful for sorting lists of strings into alphabetical order.

CHR$()

This is the reverse of ASC() and given an ASCII code will produce the equivalent string character. Printing

CHR$(65) to the screen will result in a capital 'A'.

UPPER$() and LOWER$()

These two functions are used on strings to convert the string's contents to upper and lower case respectively.

A good use for Upper$() is when you need to compare two strings in your programs.

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. In a space game for example, your 'on-board computer' may ask for a

destination planet which the user types in. Your code may say:

Input "HAL: What is your new destination? ",Planet$

If Planet$="Neptune" Then Gosub Dest_Neptune

But, what if the user typed NEPTUNE or neptune - or any other combination of upper and lower case

characters? Well, the subroutine would never get called - and they would never get to Neptune!

You would have to have an If line for every possibility - and that's only for a single destination!

Or, you could just use Upper$()!

Input "HAL: What is your new destination? ",Planet$

If Upper$(Planet$)="NEPTUNE" Then Gosub Dest_Neptune

Now, it doesn't matter how Neptune is typed, as long as it's spelled correctly, as the upper case version will

always be 'NEPTUNE' and the subroutine will always be called. The original contents of Planet$ in this example

are unchanged - you are just creating a temporary upper case version to compare with. If you want to do a

permanent conversion, you would use:

Planet$=Upper$(Planet$)

... when the user originally enters the information.

Yet More String Functions!

If you are reading these tutorials as you are new to DB but not programming, then you may notice that DB isn't

bursting with string functions. This is because DB is primarily a game writing language and generally speaking

there isn't a great need for them in games. What you do get is usually sufficient though.

Page 40: Darkbasic Guide= Dbproguide

39

Other Useful Commands For Beginners

INC

Inc is short for Increment and is a quick way to do addition with variables. If we wanted to add an amount to

the variable 'A' we would normally use A=A+1 (or + any other value). This can be replaced with the following:

Inc A: Rem Increments the value currently in numeric variable A by 1

Inc A,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. If we wanted to subtract an

amount from the variable 'A' we would normally use A=A-1 (or - any other value). This can be replaced with

the following:

Dec A: Rem Decrements the value currently in numeric variable A by 1

Dec A,3: Rem Decrements the value currently in numeric variable A by 3

RND()

All games rely on an amount of randomness. 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. So, you have a function

RND() in DB which will return a random number from a given range. Using the syntax Rnd(Value), it returns a

value between 0 and the supplied value. The number returned is inclusive, so Rnd(10) can return 0, 10 or any

number between.

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. As such, the random numbers are the same every time you run

the program - hardly random. 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, the same 5 numbers are produced. To generate a different sequence of

numbers each time it is run, we need to re-seed the random number generator with RANDOMIZE.

RANDOMIZE

This command will re-seed the random number generator and uses the syntax:

Randomize Seed

...where Seed is the numeric seed value.

However, if the seed is a constant number then the next set of randomly generated numbers will be different,

but only the first time they are generated - from then on they will be repeated. Still not random!

So, 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. Timer() is a DB function which will return the number of milliseconds which

have elapsed since the PC was turned on. Using this as a seed will give us a different set of random numbers

each time the program is run. Simply use:

Page 41: Darkbasic Guide= Dbproguide

40

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:

Page 42: Darkbasic Guide= Dbproguide

41

MouseClick()

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.

Page 43: Darkbasic Guide= Dbproguide

42

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.

HIDE MOUSE

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.

SHOW MOUSE

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.

INPUT

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.

Input Name$

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.

Input Age

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.

Page 44: Darkbasic Guide= Dbproguide

43

This raises an important point. As nothing appears on the screen, 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: ",Name$

Input "Please Enter Your Age: ",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. You can of course replace the literal string with a string

variable if you wish.

At some later stage, you will decide that you don't like the way that the standard Input command works. With

a bit more knowledge you will probably decide to write your own function to do it.

Inkey$()

This function is called a 'polling' function as it polls the keyboard for keypresses without stopping like Input

does. When a key is pressed, it is stored in a string variable for use. Press the b key and the letter b is stored.

Press the p key and the letter p is stored and so on.

The next time the Inkey$() function is called the previous value is lost so if you don't 'grab' it while it's there,

it's immediately replaced by something else. When no key is pressed, the variable used contains a NULL string

("").

Inkey$() only works with characters which have ASCII codes. Some keys do not return any value at all - such as

the Shift, Control and Function keys.

So a good way to use this function is to create a loop which is 'locked' until the correct key is pressed. 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, I know I said using Goto is bad, but this is only an example to demonstrate using Inkey$() and I wanted to

keep it short. I could have written a mere half a dozen lines to avoid it, but it wouldn't have demonstrated

Inkey$() any differently. 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. Inside the loop, Inkey$() grabs the current state of the keyboard and places the upper

case version of it in the string variable I$.

As soon as the user presses either Y or N, the condition will be met and the program drops out of the loop and

Page 45: Darkbasic Guide= Dbproguide

44

into the If..Else...Then test where I$ is tested to see if it equals "Y" or "N". If it is Y then the program ends. 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), then the program is not exited and is allowed to continue.

How about "Press Any Key To Continue" messages?

This is just as easy using Inkey$(). We know that when no key is being pressed, then Inkey$() will return a NULL

string, 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. The loop will keep going until I$ doesn't

equal "" and that can only happen if a key is pressed.

"OK, clever clogs" I hear you ask, "What can you use for menus"?

Inkey$() again! Take a look at the following simple example and then we'll go through it...

Center Text 320,100,"1. Menu Item 1"

Center Text 320,120,"2. Menu Item 2"

Center Text 320,140,"3. Menu Item 3"

Center Text 320,160,"4. Menu Item 4"

Center Text 320,180,"5. Menu Item 5"

Center Text 320,200,"6. Menu Item 6"

Repeat

I$=Inkey$()

Until Asc(I$)>48 and Asc(I$)<55

CLS

If I$="1" Then Print "You selected menu option 1"

If I$="2" Then Print "You selected menu option 2"

If I$="3" Then Print "You selected menu option 3"

If I$="4" Then Print "You selected menu option 4"

If I$="5" Then Print "You selected menu option 5"

If I$="6" Then Print "You selected menu option 6"

The first six lines simply print the menu entries 1 to 6. The loop is like the previous examples with the

exception of the exit loop condition. This time, we use ASC() to test the ASCII value of the key pressed. The

number 1 key has an ASCII value of 49 and 6 has the value 54. So, we only exit the loop if the key pressed has

an ASCII value greater than 48 and less than 55 - in other words only the number keys between 1 and 6.

On exiting the loop, the screen is cleared and the value of I$ is checked and the appropriate message printed

depending on what I$ equals. In your program, you would Gosub a routine rather than print a message, but

the method is still the same.

That will do for the Inkey$() function so let's take a look at the last keyboard input method - 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. This code value is totally unconnected with ASCII codes. For example, with Asc(), pressing

the A key on its own will return 97 - the ASCII code for lower case 'a'. Pressing the A key with the shift key will

Page 46: Darkbasic Guide= Dbproguide

45

return 65 - the ASCII code for upper case 'A'.

Scancode uses the syntax NumVar=Scancode() and returns 30 when you press the A key. There is no shifted

version as the shift key has its own Scancode. Remember, the 30 represents the key itself - not what's on it.

It's also worthwhile remembering that not all keyboards across the world are the same.

For example, your program uses the Q button to accelerate your vehicle and it uses the Scancode 113 to

detect when it's pressed. On your instruction screen you say 'Press Q to go faster'.

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, but AZERTY. His Q key is in a different place and the Scancode of his Q

key is different. I use a Spanish keyboard and it has a different layout too.

If you had used Inkey$() instead of Scancode, then the Q key would have worked on all keyboards! You may

need to use Scancode, but bear this in mind.

Keystate

As each and every key on the keyboard generates 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.

To get around this, you can use Keystate() which uses the syntax:

Keystate(Keycode)

...where Keycode is the Scancode for the key you are polling. When the corresponding key is not being

pressed, Keystate returns 0 and a 1 when it is.

The cursor up key has the Scancode 200 and Space Bar is 57. If you wanted to use the cursor up key to move

forward and the space bar to fire, then Scancode on its own would not work as you would stop moving every

time you fired. 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, at this point we have only scratched the surface of commands in the DB library, but the

commands covered - few as they may be, are quite sufficient to write some quite sophisticated programs.

The only thing left to cover which we haven't done already is File Access, so we'll do that in the next part of the

tutorial.

Page 47: Darkbasic Guide= Dbproguide

46

Part 4 - File Access

All but the most basic programs use file access. Although strictly speaking this also encompasses DB's

commands for loading media files for your games such as images, sounds and models, this part of the tutorial

series covers saving your program's data to disk and reading it back in again.

This process is required for reading INI files, saving hiscore tables or creating new file formats for your new

world editor.

The basic process is to open a file for reading or writing, read in (or write out) the data then close the file.

What you write is up to you, so long as you read the information back in the same order.

Now I know many people will argue with me, but I have decided that it's far simpler to write data as ASCII text

files when you are learning to program. 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. Some of DB's save data

commands create encrypted files which can't be opened for examination - despite any advantages they may

have.

The first thing you have to do is open a file. Assuming that we need to write a file before we are able to read it

back in, this is done with OPEN TO WRITE.

OPEN TO WRITE

This command will create a new file on your hard disk and uses the syntax:

OPEN TO WRITE Channel,Filename$

Channel is an integer number and is like a 'stream' number. Filename$ is the filename you want to use.

The channel number is used because you can open more than one channel at a time. For example, you can

open channel 1 to read and channel 2 to write simultaneously, allowing you to read from one file and write

selected parts of it to a second file at the same time. By including the channel number in all of the commands,

DB knows which file to access.

It's like connecting a pipe from DB to the file on disk. The channel number tells DB which pipe to send the data

down when writing and which pipe to take the data from when reading. As long as you number the pipe(s),

open the correct valves (READ or WRITE) before using the pipe and remember to close the valves when you

are finished, you can use as many pipes as you need. Filename$ can be a specific filename including the full

path like:

C:\Program Files\MyprogData\Mydata.dat

It can also be relative, so using just a filename like 'Mydata.dat', the file will be opened in the current project

directory (where your DB program is located).

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.dat'. The process will fail if the directory DATA does not

exist though. So, to open a file called 'Mydata.dat' in the current directory we would use:

Open To Write 1,"Mydata.dat"

Page 48: Darkbasic Guide= Dbproguide

47

This creates an empty file called "Mydata.dat" and connects our 'pipe' which is labelled '1'. But, it is very

important that the named filename DOES NOT ALREADY EXIST. If it does, then you will get an error. To avoid

this, you have the FILE EXIST() function.

FILE EXIST()

So, before creating a new file, 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 - rather than entering the filename literally:

Filename$="Mydata.dat"

If the file does exist, then the File Exist() function will return 1 (true) and if it doesn't exist will return 0 (false).

So, in our example, the code between the If and Endif lines will only be carried out if the file does exist.

I put 'Do Something About It' in the above example because you have two options at this point. As you cannot

open a file to save if it already exists, it HAS to be deleted so you can re-create a new one. But, what if the file

is there and contains data which you don't want to lose?

Well we'll cover that later, but for now, we'll assume that it can just be deleted. So, 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, if you don't say =1, then it is 'implied' - in other words, DB assumes you are testing for true (=1). Also, as

you only have a single action to carry out - not multiple lines of code, you can add the keyword THEN and

include the action on the end of the IF line.

So, having checked for the existence of the file, deleted it if it was found and opened a new file for writing, we

now have to write our data to disk.

Normally, this data would be variables. 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, MatrixHeight, TilesX and TilesZ etc. All we

need to do is write all these relevant variables to disk.

Once the file has been opened, there are a number of commands to write different types of data. These

include WRITE BYTE, WRITE FLOAT, WRITE FILE and WRITE LONG - each of which writes data in an encrypted

format.

When I say encrypted I simply mean that you can't read the data with anything other than DB's respective

READ command. Use WRITE FLOAT and you can only access the data with DB's READ FLOAT - you can't open it

with say Windows Notepad and examine the contents. I am also reliably told that the formats DB uses cannot

be loaded into other programming languages like VB either.

Page 49: Darkbasic Guide= Dbproguide

48

There is a way around this though, by using WRITE STRING for everything. As mentioned earlier, 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.

The fact that all your output is strings is irrelevant - the same data is still stored and you are still learning how

to save data to disk.

So, let's see some WRITE STRING examples:

Write String 1,"This is a sample text string!"

A$="This is a sample text string!"

Write String 1,A$

OK, these both do the same thing. The first example writes the literal string enclosed in the quotes to disk, (but

not the actual quotes). 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, they would see what the file was for. For

example, to identify them, MatEdit's MA0 matrix files all have the following first line:

MatEdit .MA0 File

Lines can be ignored by your loading routine, so you can create as big a header as you like.

The second example is what you use to write string variables. But, what if your variables are numeric - not

string?

That's not a problem, we just convert them to strings when we write them out. For example:

MatrixWidth=20000

MatrixHeight=20000

TilesX=70

TilesZ=70

FloatVar#=44.82

Write String 1,"This is the header"

Write String 1,Str$(MatrixWidth)

Write String 1,Str$(MatrixHeight)

Write String 1,Str$(TilesX)

Write String 1,Str$(TilesZ)

Write String 1,Str$(FloatVar#)

As you can see, the use of Str$() converts the numeric variables to strings before writing them. The original

variables are not altered in any way by this process. As you can see, the process also works with float (real)

numbers too. If you opened the above resulting file with Notepad you would see:

This is the header

20000

20000

70

70

44.82

Having written our data out, we need to close the file. This is done very simply with:

Page 50: Darkbasic Guide= Dbproguide

49

Close File Channel

...where Channel is the channel number used when opening the file.

The complete routine for our example would therefore be:

Filename$="Mydata.dat"

If File Exist(Filename$) Then Delete File Filename$

MatrixWidth=20000

MatrixHeight=20000

TilesX=70

TilesZ=70

FloatVar#=44.82

Open To Write 1,Filename$

Write String 1,"This is the header"

Write String 1,Str$(MatrixWidth)

Write String 1,Str$(MatrixHeight)

Write String 1,Str$(TilesX)

Write String 1,Str$(TilesZ)

Write String 1,Str$(FloatVar#)

Close File 1

OK, that's written an example file, 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. 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.dat"

If File Exist(Filename$)

Open To Read 1,Filename$

Read String 1,T$: Rem Ignore This Info

Read String 1,T$: MatrixWidth=Val(T$)

Read String 1,T$: MatrixHeight=Val(T$)

Read String 1,T$: TilesX=Val(T$)

Read String 1,T$: TilesZ=Val(T$)

Read String 1,T$: FloatVar#=Val(T$)

Close File 1

Endif

OK, first of all, we check for the existence of the file we are trying to load. To avoid errors we only open the file

if it's there. If it isn't then we don't attempt to open it. That's why all the reading code is enclosed inside the If

File Exist(Filename$) loop.

If the file does exist then we use OPEN TO READ along with READ STRING to get the data. As we know that all

the data in the file is of type string, we can use the same string variable (T$) to read each data item in and then

convert it where necessary.

There's no way to detect automatically what type of data is in a file, but as you are reading the same data that

you wrote out, you already know what each string you read in has to be converted to - if it isn't actually a

string. 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. As this is unwanted information, we can ignore it once it is loaded,

though it MUST be loaded as it's part of the file. Data files are sequential so in order to read say the third item

Page 51: Darkbasic Guide= Dbproguide

50

in the file, the first two must be loaded first. So the rule is load EVERYTHING and ignore what you don't want!

The next item of our example is MatrixWidth which is numeric, 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().

After it is read in, T$ will equal "20000" so MatrixWidth=Val(T$) will convert T$ to the number 20000 and place

that value into the numeric variable MatrixWidth.

The process is repeated re-using T$ for the remaining numeric variables in the file.

The last data item is a float. Val() doesn't mind, it will still convert the string "44.82" to the numeric value 44.82

as long as you use a float type variable to receive it. FloatVar#=Val(T$) will result in FloatVar# containing 44.82

which is what we want. 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 .82 off the end!

Finally the file is closed.

Saving Arrays:

There is a command in DB for saving arrays, but you cannot save more than one array in the same file as the

command has to be supplied with the filename. Using the method we will discuss next allows you to save all

the arrays from your program that you want - all in the same file. This is essential if you want to create your

own file format.

Arrays are no more than simple variables in blocks. Each variable in the array can be accessed by using the

array's index number and if you can access a variable, you can save it out to disk. Here's a useful example...

Hiscore Tables

Creating a hiscore table in your program is easy enough, but if it doesn't write the data to disk, the next time

the program is run, all the hiscores are lost.

So, 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. For this we need two very simple arrays - Hiscore() and PlayerName$(). Hiscore() is

an integer array as the hiscores will be numeric and PlayerName$() is naturally a string array.

These are created with:

Dim Hiscore(10)

Dim PlayerName$(10)

For these tutorials, once again I am purposely ignoring the fact that element 0 exists in an array as it makes life

easier - we can refer to players/hiscores 1 to 10 rather than 0 to 9. The file on disk will be called HISCORE.DAT.

So, when your game runs it checks to see if the file HISCORE.DAT exists. If it's the very first time it has been

run, then the file will not exist so it must be created and the arrays written out to disk. At this time they will

obviously all be empty or contain 0 (zero).

At this point, the arrays written to disk are the same as in memory. The player plays the game and if their score

gets on the hiscore table, the arrays are modified. Obviously the first time the game is played, ANY score will

get onto the table so they enter their name and the data is stored in the two arrays.

Page 52: Darkbasic Guide= Dbproguide

51

When the game is exited, the existing file HISCORE.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.DAT.

The next time the game is run and it checks to see if the file HISCORE.DAT exists, it will be there, so instead of

creating a new one, the old hiscore table is read in. Once in memory, our two arrays can be modified when a

new hiscore is attained and on exit the hiscore table is just written out again - regardless of whether or not it

has changed since last time.

Writing arrays are very simple. All we have to do is write the data in a loop which matches the size of the array.

For Next loops are ideal for this. So, to write our array Hiscore() to disk with 10 elements, we would use:

For N=1 To 10

Write String 1,Str$(Hiscore(N))

Next N

As you can see, Str$() is used as before to convert the numeric array data to string when writing it out to disk.

Reading the array back in is also just as simple:

For N=1 To 10

Read String 1,T$: Hiscore(N)=Val(T$)

Next N

When writing string arrays, there is no need to convert the data, so we skip the Str$() section and just use:

For N=1 To 10

Write String 1,PlayerName$(N)

Next N

Reading the string array back in is done with:

For N=1 To 10

Read String 1,T$: PlayerName$(N)=T$

Next N

Saving Multi-Dimensioned Arrays:

If the array you want to save is a multi-dimensioned array, then the process is identical - we just alter the loop

accordingly. To save a numeric integer array which was created with DIM MultiArray(10,5) we would use:

For Ny=1 To 5

For Nx=1 To 10

Write String 1,Str$(MultiArray(Nx,Ny))

Next Nx

Next Ny

Here, this nested loop will use Nx to write the 10 Nx array values for every Ny value in the Ny loop. So, the

contents of MultiArray() will be written using Nx from 1 to 10 with Ny=1, followed by Nx from 1 to 10 with

Ny=2 and so on until Ny=5.

Page 53: Darkbasic Guide= Dbproguide

52

Reading back in is the same as with single dimensioned arrays, but using exactly the same nested loop.

For Ny=1 To 5

For Nx=1 To 10

Read String 1,T$: MultiArray(Nx,Ny)=Val(T$)

Next Nx

Next Ny

OK, that's how data in arrays is saved to disk and read back in again. Once again, I will stress that it's very, very

important that you read in the information in EXACTLY the same order that it was written out. Failure to do

this can cause problems - especially when you realise that it is possible for the data you are reading in to be

fed into the wrong variables. 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 - they just won't work properly

and the problem could be very difficult to trace.

So back to our hiscore example...

What we have to do now is place a small routine at the beginning which checks for the hiscore data file,

creates it if it doesn't and reads it in if it does:

Dim Hiscore(10)

Dim PlayerName$(10)

Filename$="HISCORE.DAT"

If File Exist(Filename$)

Open To Read 1,Filename$

For N=1 To 10

Read String 1,T$: PlayerName$(N)=T$

Read String 1,T$: Hiscore(N)=Val(T$)

Next N

Close File 1

Else

Open To Write 1,Filename$

For N=1 To 10

Write String 1,Str$(Hiscore(N))

Write String 1,PlayerName$(N)

Next N

Close File 1

Endif

In your game, 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, ask for the players name, inserts the name and score into the two arrays

- pushing the bottom entry off the list.

On exiting the program, 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 - ready for being read in the next time the

program is run.

Delete File FileName$

Open To Write 1,Filename$

For N=1 To 10

Write String 1,Str$(Hiscore(N))

Write String 1,PlayerName$(N)

Next N

Close File 1

Page 54: Darkbasic Guide= Dbproguide

53

File Formats

As you have seen, you can write many different types of variables while a file is open for writing, so when

there is a lot of data to be written it's worth planning what order to write the data.

The structure of your data file is called a 'File Format' and all files created with Windows applications have one.

There's a bitmap file format, a Microsoft Word file format and so on.

The file format defines for other users the layout of your file and what information can be found where, so

they can add routines to their programs giving them the ability to load files created by your programs.

For example in a graphics file format one part of the file is the header, one is reserved for the colour palette

and another part of the file will be the data which makes up the picture. You decide where the data goes in

your own file format.

There are no fixed rules for designing a file format, just write the data out sensibly and logically. MatEdit for

example creates a .MDF file with the Build option. If you were to look at an MDF file you would just see

numbers - lots of them. Publishing the file format simply describes to others what these number are, what

variable types they are and so on.

As a rule of thumb, you should have a description of the file type at the start saying what the file is used with.

The numeric and string variables should come next and finally all the array data. Try not to have too much

unwanted information like comments scattered about the file as it complicates the load routine - you still have

to load all the useless information even though you are immediately going to discard it.

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. 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.

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. If you don't provide them with a simple way to do this, then they are

not going to want to use your program.

Reading Other Files

Open To Read isn't just restricted to reading files you created yourself with Open To Write. It can also be used

to read information in from other files too. As long as you know the file format, you can read data in from

graphics and text files.

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.

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. We'll ignore this point for the moment

though and return to it later...

Also, another question is 'how much data do we read in'? As we didn't create the file, we have no idea how

long the file is!

Page 55: Darkbasic Guide= Dbproguide

54

FILE END()

Luckily, DB gives us a function called FILE END() which uses the syntax:

File End(Channel)

...where Channel is the same as the channel used with Open To Read. 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.

Using this function in a loop, we can read all of the data in the file without having to know how much is there

first. 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.

Let's see an example:

Dim TextLines$(5000)

Filename$ ="DOCUMENT.TXT"

If File Exist(Filename$)

Open To Read 1,Filename$

LineCount=0

Repeat

Inc LineCount

Read String 1,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. The filename is set to DOCUMENT.TXT and we use our usual method of placing the loading code inside an

If...Endif which checks to see if the named file exists.

The important part of this example is that we are not using a For...Next loop any longer as we don't know how

many lines there are in the file - and we therefore don't have any start and end values for this kind of loop.

Instead we use a Repeat...Until loop which uses File End() to check if the end of the text file has been reached.

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. If anyone is wondering why I use:

Read String 1,T$: TextLines$(LineCount)=T$

rather than

Read String 1,TextLines$(LineCount)

it's because I have encountered problems in the past when reading array values directly. Since using a normal

string variable to read the data and then transferring the contents to an array I haven't encountered those

errors. Feel free to use whichever method you like - the end result should be the same...

As the variable we use for counting in the loop would normally be the For...Next counting variable - which

obviously is not available here - we have to increment LineCount manually each time around the loop. This is

done with Inc LineCount and the line LineCount=0 is used before entering the loop - to ensure that the

counting loop starts at 0 (in case the routine is used more than once).

This loop continues reading lines of text from the text file until there is no more lines to read and then drops

Page 56: Darkbasic Guide= Dbproguide

55

out of the loop. At this point, LineCount is equal to the number of lines read in from the text file. Knowing this,

we can add a For...Next loop to the end of the program which will print the lines read in to the screen:

Print LineCount;" lines read in."

Print

For N=1 To LineCount

Print TextLines$(N)

Next N

And that's all there is to reading a text file.

Line Too Long?

Going back to earlier in this tutorial, I briefly mentioned that DB will error if you try to read in a string which is

longer than 255 characters. So, what do you do if this happens?

Well basically, you switch to reading the line in a character at a time rather than a line at a time. This is quite a

bit slower than reading in a line, but as it's the only way around the problem it's better than nothing.

For MatEdit Pro's in-built help files, I needed a text file of the MatEdit documentation, but with each line short

enough to fit on the screen. The problem was that the existing docs contained quite large paragraphs and

when exported as a text file, each paragraph became one single line - most of which were a lot larger than 255

characters in length!

Below is the small program I wrote to solve the problem. 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, (or it reads in the two bytes 13 and 10 - the two values

which record the end of a line in all text files), at which point a new line is started.

The two variables EndLineTrigger and ContainsWords are worth mentioning. 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, just data.

EndLineTrigger is the length of the required lines after reading in the data and what it does depends on what

ContainsWords is set to. If ContainsWords is set to 0 and EndLineTrigger is set to 80 then each line is cut off at

the 80th character.

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.

When all the lines have been read in and shortened, they are written out to another file. Here's the program:

Page 57: Darkbasic Guide= Dbproguide

56

Set Display Mode 800,600,16

Dim Lines$(10000)

InputFile$="test.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 - Reading File And Truncating Lines..."

Open To Read 1,InputFile$

Repeat

READ BYTE 1,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,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,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..."

Rem Write out converted file

Open To Write 1,OutputFile$

For N=1 To LineNum

Write String 1,Lines$(N)

Next N

Close File 1

CLS

Print "Written new text file containing ";LineNum;" lines!"

End

Use this program on any text file which you can't read with the Read String method. This will convert the file

and give you a new file which can be loaded with the Read String method. The two filenames at the beginning

of the program allow you to set the input and output filenames. OK, that's it for the File Access tutorial. 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.

Page 58: Darkbasic Guide= Dbproguide

57

Choosing The Correct Variables

Looking at the code posted by many users on the forums, it's clear that many of you are not sure about the

difference between float and integer variables - or when you use them.

Back when I started programming, (prior to CPU's having Maths Co-Processors if anyone else remembers

them), 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. This included using integer variables rather than

floating point at every opportunity.

This was because it took a lot longer for a computer to do a floating point calculation than an integer

calculation.

These days, this is still true, but as computers are so fast, it's not as critical any more. Even so, it's still pointless

using floating point variables with numbers which can only ever be integers!

More to the point, using the wrong variable type can lead to errors in calculations and difficult to trace

problems in your programs later on.

Variable Types:

The three basic variable types common to both versions of DB are String, Numeric Integer and Numeric Float

(also called Reals).

String variables have the $ symbol (dollar) on the end of their names - like MyString$.

Integer variables have nothing on the end of their names - like Score.

Float variables have a # symbol (hash) on the end of their names - like FireAngle#.

(DBPro has a number of other useful variable types, but are not included in this text so the information given

here applies to both versions of DB).

Choosing The Type Of Variable To Use:

What variable you use depends on what sort of information you need to store. If your variable is only ever

going to contain whole numbers then you should use integer variables. If you are going to need the ability to

calculate and store fractions then you should use floats.

Choosing the wrong one can cause problems. For example, run the following example in DB:

A=5

B=A/2

Print B

Wait Key

The answer is of course 2.5 but the program prints 2. This is because A and B are integer variables and

regardless of the calculation, the result becomes an integer when it's placed into an integer variable. So, you

lose the '.5' off the end.

Page 59: Darkbasic Guide= Dbproguide

58

To store a floating point number, you have to use a floating point variable - but there are pitfalls to be aware

of even then. 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, you still get the answer 2 instead of 2.5!

This is because in the line B#=A/2 both A and 2 are both integers so the result is an integer. Even though the

variable B# is a float, the result is still turned into an integer when it's stored. This is called variable

typecasting.

So what do you do if you have an integer and need to turn it into a float?

Well, you simply make sure that the parameters in the formula you use are not all integers. For example, run

the following snippet:

A=5

B#=A/2.0

Print B#

Wait Key

Notice that as we have changed the divisor from 2 (integer) to 2.0 (float), when you run it, the answer is now

correct - 2.5!

When To Use What:

As mentioned previously, integers are faster than floats so are preferable to use when we can - we just need to

know when to use the correct type.

Working in 2D with the screen is a good example of where you should only use integers.

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.

In 800x600 mode the 800 pixels running across the screen start at number 0 on the left and end at 799 on the

right. Running down the screen 0 is at the top and 599 is at the bottom.

Placing something on the screen requires the X and Y position values as in:

Paste Image 1,100,100

which places the image 100 pixels across and 100 pixels down the screen.

The important thing to realise here is that a pixel co-ordinate is a whole number like 100, 250 or 399. It can

NEVER be a floating point number like 100.5 or 250.77 because you can't position anything on the screen

between two pixels.

So, any variables in any way related to 2D screen positions or moving screen objects around should be integer

variables.

Page 60: Darkbasic Guide= Dbproguide

59

Another area where you frequently see float variables misused is with the mouse.

The mouse can only ever return whole numbers, so store the results in integers - not floats. 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.

In 3D however, we aren't talking about pixels any more, but 'units' - because they are not a fixed size like

pixels. It's not easy to get your head around the concept at first, but 3D units are relative. 1 unit doesn't equal

a centimetre, a metre, an inch or a mile - it depends on the size of the objects in the scene to convey 'size'.

For example, think of two matrices - one of them 100000x100000 units and the other 100x100 units in size. If

all the objects are scaled down on the smaller matrix, it can look the same size to the camera as the larger one

- despite the hugely differing unit sizes of the matrices. A tree 20 units high would look enormous on the

smaller matrix but miniscule on the larger one.

So, 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.

What's more, they are not whole units - you can have fractional parts of a unit. Objects can be placed at

locations like 100.2, 10.3, 100.7 and moved 0.1 units in any direction.

As such, you would use float variables to store these values.

So, in summary, only use float variables when you have to and your programs will be faster - and you'll avoid

many problems when your programs grow bigger.

Page 61: Darkbasic Guide= Dbproguide

60

Dark Basic Functions

Whereas a subroutine, (often called a 'procedure'), starts with a label and ends with a RETURN and is called

using GOSUB, functions are a somewhat different beast. Although similar to look at code-wise as subroutines,

functions in Dark Basic have three main differences:

1. Local Variables

All variables in a function are local as opposed to global. In programming terms global means that they are

visible to all of your program, whereas local means that they are only visible inside a function they are used in.

The visibility of variables is called the variable's 'scope'.

In Dark Basic Classic however, proper global variables don't really exist officially as they cannot be declared,

(unless you are using an IDE which supports them). DBPro does have global variables.

A normal variable in your main program can be seen inside a procedure, but not inside a function. So, although

I'll refer to them as global variables, DBC users should think of them as being 'semi-global'.

Another thing to remember about local variables is that they can co-exist at the same time as other variables

with the same name.

This is very important to remember as not knowing this can lead to some very hard to find bugs in your

programs.

For example, if you set the variable A to equal 10 in your main program with A=10, then call a function in

which you say A=20, 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, the variable A would be a

newly created local variable called A - entirely separate from the previously created variable A outside the

function. When you exit from the function, you revert back to the original variable A, which is of course is still

equal to 10.

This feature can be very useful, but if you are used to programming in other languages you can be lured into a

trap, so beware.

Variables in BASIC do not need to be declared at the start of your program like in Delphi or C - they exist from

the moment you refer to them. For example, 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. 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.

In other languages, local variables are 'destructive', which means that when you exit a function, any local

variables are destroyed.

The next time you visit the function, the variables are created again as entirely new entities. However, in Dark

Basic local variables in functions are 'non-destructive' which means that when you return to a function, the

variables still exist and contain whatever values they did when you were last there. This 'feature' can be very

useful if you know it's there, but an annoying source of difficult to trace bugs if you don't!

Page 62: Darkbasic Guide= Dbproguide

61

2. Entry & Exit Parameters

Functions can be used to do a specific task without any external information and then exit without returning

any information. If no entry parameters are required, empty parenthesis can be used when calling the function

and in the function header - or they can be omitted entirely. DB will accept either format.

Alternatively, functions can do a task calculated on the information supplied and then return information. One

example of a return value would be a success value. A 1 returned would denote that the function's task was

completed successfully whereas a 0 could denote that something went wrong.

As described above, all variables in a function are local, 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 '()'. The function itself must have the exact same parameter

list to accept the same variables. So, you would have functions something like these:

No Entry Or Exit Parameters:

Function StartScreen

CLS RGB(100,0,100)

Ink RGB(255,255,255),0

Print "Screen Now Cleared And Ready For Use!"

EndFunction

Using Entry And Exit Parameters:

Function MyFunction(Var1,Var2,Var3,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 - the

first three being integer numbers and the fourth a string. These variable names are used inside the function as

local variables to calculate the value of RetVal. RetVal is then returned back to the calling function. The actual

calculation is of course nonsense, but demonstrates how things work.

So, how are functions called? Well, it depends on whether you need to pass parameters or not and whether

your function returns anything. 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. 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. Think of it as passing the

contents of each variable to the function - not the variables themselves.

The function also returns the contents of the variable RetVal, so we must take that into account when calling

the function. This is done by using a variable of the same type in the call. As our example function

MyFunction() returns an integer, we use the following call:

ValueBack=MyFunction(MinLen,MaxLen,Score,"Elephant")

Page 63: Darkbasic Guide= Dbproguide

62

After calling the function, the variable ValueBack will contain the returned contents of RetVal from the

function. The value returned can be an integer number, a real number or a string, but the subtle point is that in

Dark Basic, functions can only return a single value which can be a bit limiting.

The answer in many programming languages is the declaration of true global variables which can also be

accessed within functions.

Sadly, you don't have the ability to do this in Dark Basic Classic, though as it happens, the work-around is on a

similar theme - using arrays. Remember though, using arrays is only a work-around and there are limitations.

You can't pass arrays to functions or return them.

It was discovered a long time ago that in DB Classic, arrays behave like proper global variables and an array

declared at the start of a program can be seen, used and modified inside a function and is still intact upon

exiting the function. Arrays declared inside a function remain local though and not accessible outside it. So

using arrays, a function can return multiple values - or the equivalent anyway.

3. #Include Files

Functions can be used in Include files - something widely misunderstood by the newcomer to programming

with Dark Basic.

In a nutshell, 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. A good example is the keyboard/mouse control code

which you use for controlling the game character or ship in your games. If all the actions for moving and firing

etc. are all done with functions, then they can all be saved in a single file called, let's say Control.dba for

example.

The next time you write a program which uses the same control method, all you have to do is put the

Control.dba file into the same folder as your new program and use the following line as the first line of your

program:

#Include "Control.dba"

When the program is run, all the functions in the #Include file become available - without you having to re-

code them again. A big time saver - especially if you have functions to cover all possible control input methods.

You'd never have to write a piece of input code again! You must remember however, that Include files can

ONLY contain lists of function and no code can reside outside of each function header and its associated

EndFunction. To the best of my knowledge, the REM statement is the only exception allowed.

Why Use Functions?

Well speed-wise, there is little or no difference between using a procedure and a function, 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. (In #Include files you can ONLY have functions - nothing else). In fact in some cases, a procedure

is better due to the lack of the local variable problem. Some users will swear by using functions for everything,

but my advice is to go with whichever you are happiest with.

There are many more tutorials and example code snippets on TGPF - my game programming forums. Click on

the link below - it's free to join and everyone's welcome!

Page 64: Darkbasic Guide= Dbproguide

63

Everything you wanted to know about strings

You wouldn't think so at first glance, but strings are quite important in every DB game you will write. After all,

what good is a game which doesn't put messages onto the screen, let you talk to other characters or let you

type your name into a highscore table. They all use strings!

In DB, 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 - as in the variable PlayerName$.

Strings can be cut up, joined together (concatenation), searched, jumbled up and have sections in them

replaced with something else.

In this tutorial we'll be taking a look how to do all of these things. But first, a bit of background info...

In DB, strings have a maximum length of 255 characters. Add one more character on the end and you'll get a

String Overflow error and DB will die.

A character can be any alphanumeric symbol your computer can produce - either printable or not. A very

simple example would be:

A$="My Name Is Fred"

When you put this in your program, DB will take everything inside the quotes and place them inside the string

variable A$. The actual quotes are not stored - they are just markers so DB knows where the string starts and

ends.

The section in quotes is called a 'string literal' as opposed to A$ which is a string variable.

You can add strings together:

A$ = "ABC"

B$ = "DEF"

C$ = A$+B$

Print C$

This will print "ABCDEF" to the screen.

You can type the sentence 'My Name Is Fred' as it's an English sentence, however there may be times that you

want to create a string of non-printable characters that aren't available on your keyboard.

There are a number of string-based commands in DB, which we'll cover later. One of them is CHR$() which

we'll look at now...

Take our above example 'My Name Is Fred'. The first letter is a capital M. This, like the rest of the alphabet is

an ASCII character and as such has an ASCII code.

The ASCII code for A is 65, B is 66, C is 67 and so on. As it happens, the code for M is 77 and if you tell DB to

Print CHR$(77), then it will print an M on the screen.

Armed with a list of ASCII codes you could build up a string with CHR$():

Page 65: Darkbasic Guide= Dbproguide

64

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, as mentioned

a moment ago, you might want a string built up of characters that you can't type in. 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), a

standard ASCII character set only has room for 256 characters.

To interrogate a character and find out what it's ASCII code is, you can use the ASC() function:

ASC(String) or ASC(String Variable) will return the ASCII code of a character so:

Print ASC("A")

...will print 65 on the screen.

ASC is meant to be used only on single characters as shown above, but you can actually use it with strings of

more than one character - though in these cases only the first character is tested. Eg;

Print ASC("Alan")

Print ASC("Andy")

Print ASC("Arthur")

will all print the number 65. However, this can still be useful for a rudimentary string sort - based on the first

character of each string.

The simple code below will sort strings, but it can't be done with normal string variables - they have to be in a

list using string arrays. Don't panic though - they are really easy when you get to know how they work...

String Arrays

A string like Character1Name$ is a single entity. In a program, what if you had 3 characters with names?

You would need Character1Name$, Character2Name$ and Character3Name$ to store them. You would also

need a separate line of code referring to them for every occasion in your program.

What if you had 100, 200 or even more characters? The answer is that normal strings would be impossible -

you would have to use string arrays.

Imagine an apartment block with 10 apartments. In reception on the wall there's a row of mailboxes

numbered 1 to 10 for each of the apartments.

The postman puts the mail for apartment 4 in box 4 and when the guy from apartment 4 comes down, he

simply opens mailbox 4 for his mail. If all 10 mailboxes are collectively called 'Mailbox$' and the letters inside

the boxes is our string data, then in effect we are describing a string array.

The postie putting letters into mailbox 4, in DB terms is doing:

Page 66: Darkbasic Guide= Dbproguide

65

Mailbox$(4) = Letters$

And when the guy from apartment 4 gets his mail:

Owner$ = Mailbox$(4)

In DB, we just have to say how many mailboxes we require before we can start using them. This is called

DIMensioning an array and with our mailbox example it would be:

DIM Mailbox$(9)

Wait a minute!... Nine? You said 10 mailboxes!...

Yes - that's correct I did. But, unlike our apartment block, DB has an apartment number 0. So for 10 actual

boxes, we only need to DIM 9 which gives us boxes 0-9 - 10 in total.

You can however use DIM Mailbox$(10) and ignore box 0 altogether if you find it easier. Just pretend that no-

one lives in that apartment!

So what's the point of all these boxes?...

Well, the number in parenthesis () is called an index and as always when programming, you can replace a

number with a variable. This means that instead of using Mailbox$(2) or Mailbox$(9), you can refer to

Mailbox$(X). In turn, that means that you can use them in loops...

Imagine printing the contents of 5 'mailboxes' the 'old' way:

Print Mailbox1$

Print Mailbox2$

Print Mailbox3$

Print Mailbox4$

Print Mailbox5$

Now again with 100 mailboxes!

No thanks... Try this instead:

For X = 1 To 100

Print Mailbox$(X)

Next X

Now you see the power of arrays!

Actually, what we've been talking about so far is a 'single-dimension array' - just one row of boxes and one

index number.

However you can also have 'multi-dimensioned arrays' which are best thought of as being like a wall of lockers

in a changing room. Say there is a stack of lockers ten wide and six high - sixty in total.

The lockers running across might be numbered 1 to 10 and the rows running down labelled A, B, C and so on to

F.

If your stuff is in locker 5D, you need to count across to 5, then count 4 down to D to get to it.

Page 67: Darkbasic Guide= Dbproguide

66

In DB, this translates to an array like Locker$(X,Y) where X is counting across and Y is counting down. Locker 5D

would be;

Locker$(5,4)

Anyway, back to the issue in hand - sorting...

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. 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, I've chosen to ignore element 0 of the array).

OK, they aren't sorted at the moment. All we have to do is look at the ASCII codes of the first characters of

strings 1 and 2.

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. We now repeat the process with strings 2 and 3, then 3 and 4 and so on.

We use a 'DidWeSwap' flag and set it each time we have to do a swap when we run through the loop. If we run

through the loop and a swap wasn't done, the flag isn't set and we know the strings are now all in the correct

order. 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, a complete run though the array is within the Repeat..Until loop. 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.

Page 68: Darkbasic Guide= Dbproguide

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 drop out of the loop.

The only other thing of note is the use of the string variable Temp$. This is used during the swap process

because we don't want to lose string 1 when we copy string 2 into it. We copy string 1 into Temp$, string 2 into

string 1 and then Temp$ into string 2.

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.

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. Of all of the sorting methods, this one is probably the easiest to program, but least

efficient.

At this point let's introduce a few more DB string commands before we need them...

Left$(), Mid$(), Right$() and Len()

Left$(StringVar$,NumChars) will extract NumChars characters from StringVar$ starting at the left. So:

A$="ABCDEFGHIJK"

Print Left$(A$,5)

...will print 'ABCDE' on the screen - the leftmost 5 characters.

Right$(StringVar$,NumChars) will extract NumChars characters from StringVar$ starting at the right. So:

A$="ABCDEFGHIJK"

Print Right$(A$,5)

...will print 'GHIJK' on the screen - the rightmost 5 characters.

Mid$(StringVar$,CharPos) will extract the character at position Charpos in StringVar$. So

A$="ABCDEFGHIJK"

Print Mid$(A$,6)

...will print 'F' on the screen - the character at position 6.

Len(StringVar$) will return the length of StringVar$. So

A$="ABCDEFGHIJK"

Print Len(A$)

...will print '11' on the screen - the length of A$.

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()

Page 69: Darkbasic Guide= Dbproguide

68

WordLen = Len(GameWord$)

For N=1 To WordLen

RandChar = Rnd(Len(GameWord$)-1)+1

ShowWord$ = ShowWord$ + Mid$(GameWord$,RandChar)

GameWord$ = Left$(GameWord$,RandChar-1)+Right$(GameWord$,Len(GameWord$)-

RandChar)

Next N

Print ShowWord$

Wait Key

For this example, we set GameWord$ as the word to scramble, (though in a real situation, words could be

selected at random from a pre-loaded text file, or read from Data lines).

After randomizing the random number generator, we get the length of the word and store it in WordLen.

The main For..Next loop counts from 1 to the length of the word - 8 with the word elephant. The RandChar line

picks a random number which is always between 1 and the number of letters in GameWord$. It's done like this

because in the following lines, the length of GameWord$ will change.

After selecting the random number, the character at that position in GameWord$ is added to ShowWord$.

Finally, the chosen letter is removed from GameWord$ so it won't be chosen again.

The loop is repeated so, by the end of the loop, GameWord$ has been reduced from 8 characters to an empty

string and ShowWord$ has gone from an empty string to being 8 characters long - but a jumbled version of the

word.

You could then display the word and time how long it takes the user to enter the correct word - awarding

more points, the quicker it's done.

How about highscore tables then...

A highscore table also uses string array lists - usually two of them: one for the name and one for the score.

The only tricky part of creating a hiscore list is deciding where to insert an entry at the end of a game. So let's

cover the theory before tackling the code...

OK, we start with the name and score arrays both empty. When the game has ended and we have a final score,

we simply loop through the score array starting at the bottom comparing the player's score with the score in

the array.

If the array entry is smaller than the player's score then we continue round the loop.

If however the array entry is greater than the player's score - or if the hiscore table is empty and we reach the

top of the array list - then we have reached the required 'slot'. All we need to do is shuffle all the entries below

it down one in both arrays - with the last entries on both lists 'dropping off the end'.

All that is required then is to feed our new player information into the array at the calculated position.

Page 70: Darkbasic Guide= Dbproguide

69

Once a highscore table is created it's saved to disk and loaded when required.

So how is this done in DB?...

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)+". "+HiScoreName$(N) + " - " + HiScoreValue$(N)

Next N

Wait Key

The important code is inside the Rem lines of stars.

The Repeat..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 - or the counting loop variable N = 0 (at which

point all the array has been checked).

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. So, we need to move all the elements of the

array down one - including the one we need to fill.

So entry 10 gets replaced with entry 9, entry 9 gets replaced with entry 8 and so on until we reach the slot we

want.

If we want to use slot 4 for example, that slot will be N+1 so the last move in the For..Next loop will be moving

the contents of N+1 into N+2 - leaving slot N+1 (4) free.

Finally we place the players name and score into the respective arrays at position N+1 - and in your game,

Page 71: Darkbasic Guide= Dbproguide

70

write them to a file on disk.

You can alter the value on the PlayerScore = 3500 line before running the program to confirm that the correct

slot is always selected.

Armed with the commands we've seen in this tutorial, we can also do some pretty unusual stuff with strings.

To end this tutorial, 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. All you have to do is look at strings in a slightly different way...

As a string is simply a long joined list of numbers - just like a numeric array, you can treat them as such - you

just need to remember that being a byte, the maximum size for the numbers is 255.

The following example may not be the best way to do what it demonstrates, but it serves nicely as an example

of the process using strings - which you can adapt to other things.

So, say you have 10 enemies in your game and their health values vary from 100 (full health) to 0 (dead). At

the start of your program, you use:

EnemyHealth$="dddddddddd"

What!!?? I hear you say... Well, d just happens to have the value of 100 in the ASCII table and in the above line

there are 10 d's - one for each of the enemies.

So what do we do with it?

Well, our string currently contains 10 d's - each having the value of 100. The first 'd' belongs to enemy 1, the

second to enemy 2, the third to enemy 3 and so on.

Say you give enemy 3 a smack in the eye and his health is reduced by 3 points.

All we have to do is get the current ASCII value of the third character in our health string, reduce it by 3, turn it

back into a character and put it back into the string.

EnemyHealth$ = "dddddddddd"

EnemyNumber = 3

HitPoints = 3

EnemyHealthVal = ASC(Mid$(EnemyHealth$,EnemyNumber))

Before$ = Left$(EnemyHealth$,EnemyNumber-1)

After$ = Right$(EnemyHealth$,Len(EnemyHealth$)-EnemyNumber)

Dec EnemyHealthVal,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 first three lines simply set up the variables.

The fourth line look more complicated than it actually is. All it does is use Mid$() to get the character in

EnemyHealth$ which corresponds to our enemy (number 3). When we have it, we convert it to a number using

ASC() - this gives us 100 and store it in EnemyHealthVal.

Page 72: Darkbasic Guide= Dbproguide

71

The fifth line grabs the first two characters of EnemyHealth$ into Before$. Remember, we are only interested

in the third character (enemy). If EnemyNumber is 3 then using EnemyNumber -1 with Left$ will grab the first

2 characters.

The next line needs to grab all of the characters after the third one and store them in After$. It does this by

using RIght$().

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$(). In this case, 10-3 gives us 7 characters -

which we store in After$.

Next, we deduct HitPoints (3) from EnemyHealthVal (100) which gives us 97.

The last important line builds up the new EnemyHealth$ by adding together: Before$ (the first two characters),

the new health of enemy 3 which we convert back to a string with Chr$(EnemyHealthVal) and After$ (the

remaining seven characters).

This equates to:

"dd"+"a"+"ddddddd"

which you will see if you run the above snippet.

I'm not suggesting that this is either the best or fastest method to use for this particular task - I'm just using it

as an example of this particular way of using strings.

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, C, D and S). For hearts, the string would be:

Hearts$ = "1H2H3H4H5H6H7H8H9HTHJHQHKH"

Each card is two characters - the second being the suit and the first being the numbers 1 to 9, T for ten, J for

Jack, Q for Queen and finally K for king. 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).

When we deal from the pack, we pick a random number based on the length of Pack$. Multiplying that

random number by 2 and adding 1 gives us the position in Pack$ of the two characters for that card. Eg:

With a new pack = (104/2)-1 = 51

Get random number between 0 and 51 - let's say the number 8 comes up (the ninth card).

(8*2)+1=17

The 17th character in Pack$ is 9 and the 18th character is H so the card dealt is 9H - the 9 of Hearts.

Page 73: Darkbasic Guide= Dbproguide

72

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.

The next time a random number is required, the length of Pack$ has been reduced by 2 so it's now:

(102/2)-1 = 50 - which gets a random number between 0 and 50.

When all the pack has been dealt, restore Pack$ by adding the four suit strings together again and off you go...

Well that's it for this tutorial on strings. I hope it's covered all the topics you wanted it to.

Page 74: Darkbasic Guide= Dbproguide

73

Timer Tutorial

One subject you often see questions about on forums is that of timers. These may be for showing an on-screen

display of either time left or time elapsed, though timers have many other uses.

This tutorial will show the seasoned DB user nothing new, but is instead aimed at the new programmer and

looks at the way timers work and how they can be used in your programs. Feel free to copy any of the code in

this tutorial into DB and run it.

All PC's have built-in timers which place values into registers for programmers to access. DB has a function

called Timer() which accesses the computers timer register and returns values in one thousandths of a second

increments.

To convert these values to seconds, we simply have to divide them by 1000. If you need finer timings than one

second intervals, then you can divide by 100, 10 or not divide by anything at all to return 10th, 100th and

1000th of a second increments respectively.

The value can obviously be stored in a variable, so if for example, you use:

RetVal=Timer()

...then the current value of the PC's timer is stored in the variable RetVal.

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), on-screen clocks & timers or any

other timed events in your programs, (maybe it goes dark after playing for an hour, or a plane flies overhead

every 15 minutes).

You are also not restricted to a single timer either. You can have as many independent timers as you like in

your programs by using different variables. For example:

T1=Timer()

T2=Timer()

T3=Timer()

Will give you three timers which can be used for timing three separate events.

Copy and paste the following code into DB and run it:

Set Text Opaque

Do

Text 0,0,Str$(Timer())

Loop

The value you see is the contents of the timer register and it is continually changing, and fast! - even when

your program is not running! This value is not a lot of use, so modify the code as shown below.

Set Text Opaque

Do

Text 0,0,Str$(Timer()/1000)

Loop

Page 75: Darkbasic Guide= Dbproguide

74

Now the value changes, but ticks over at a more useful once per second. It's still not totally useful as it will

display a random value on every machine. To fix this, we need to grab this value into a variable and deduct it

from the value of every subsequent use of Timer(). The result is a second counter that starts at 0 (zero):

Set Text Opaque

T=Timer()

Do

Text 0,0,Str$((Timer()-T)/1000)

Loop

ELAPSED TIME

To create an 'elapsed time' display, the basic programming procedure is as follows:

1. Grab the current value of Timer() into a 'start time' variable

2. In a loop, read updated values of Timer() into a 'current time' variable

3. Subtract the 'start time' value from the 'current time' value

4. Divide the result by 1000 to give the number of seconds elapsed.

In DB, the code for a 30 second timer would look something like this:

Rem Simple 30 Second Clock

Set Text Opaque

Ink RGB(255,255,255),0

T=Timer()

Repeat

Elapsed=(Timer()-T)/1000

Text 0,0,Str$(Elapsed)+" "

Until Elapsed=30: Rem change this value to alter the length of the timer

In your own programs, '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, this program counts the number of elapsed seconds in the variable 'Elapsed' and then checks to see

if that value equals 60 (1 minute). If it does, the timer goes back to zero and continues indefinitely.

When the timer hits 60 seconds, (or whatever value you set), then what your program does is up to you. In the

above example, the variable MinutesPassed is incremented - effectively counting the number of minutes

elapsed. The program could then be set do do something specific when MinutesPassed equals a specific

number of minutes.

Your program could just as easily call a function or subroutine when Elapsed reaches a given value.

Page 76: Darkbasic Guide= Dbproguide

75

Once the target value has been reached and the required task completed, you need to reset the timer. So, in

our example above, we set the variable Elapsed to equal zero.

The next part of the program is the bit that most new programmers trip up with:

Having reset the variable Elapsed to zero, 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.

So, 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 value of Elapsed will then calculate the number of seconds elapsed

from this point instead of the old one - ie from zero again.

COUNTING TIME DOWN

Counting down is essentially the same as counting up, so if say you want to give the user of your program a set

amount of time to complete a task, 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,255),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,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, placing the result in the variable 'TimeLeft'.

For example, when 10 seconds have elapsed, TimeLeft equals 30-10, or 20 seconds. When Timeleft gets to

zero then the example program ends.

A PROPER CLOCK DISPLAY

If you want a digital clock display, then it really is quite simple and only needs a single timer.

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

Page 77: Darkbasic Guide= Dbproguide

76

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,0,Hrs$+":"+Min$+":"+Sec$+" "

Loop

In this example, the timer is used simply to get the seconds elapsed. When the value of the variable 'seconds'

hits 60, it is reset to zero and the variable 'minutes' is incremented. In the same way, when 'minutes' hits 60, it

too is reset to zero and 'hours' is incremented. 'Hours' is reset to zero when it hits 24 (not 60) as there are 24

hours in the day.

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. The time is then printed to the screen.

If you have been able to follow the examples in this tutorial, you should have a good working knowledge of

how timers work and can go away and implement your own ideas using Timer().

Page 78: Darkbasic Guide= Dbproguide

77

Dark Basic Matrix Primer

Note: Dark Basic Pro has additional matrix options that DB Classic does not have. In order to keep this tutorial

compatible with both versions, these are not covered in this tutorial.

What Is a Matrix?

Not to be confused with a mathematical matrix, a matrix in DB is the floor or terrain in your programs and

although fairly simple to master, a couple of aspects are quite difficult for the novice to get to grips with. Apart

from that, when you do know what you are doing, they are painfully long-winded to create manually, (as you

will see later) - hence the popularity of matrix editors. Very few people type in all the Dark Basic commands to

create a matrix - it would take days! The actual matrix is a simple grid which you can think of as being similar to

a chessboard. Each square can be painted with a texture and each of its four corners raised or lowered to

create hills and valleys.

Dark Basic gives you the ability to create your 'chessboard' with any number of squares (tiles) across and down

that you like, as well as the physical width and depth that you want in pixels. A matrix in DB can have up to

10,000 polygons (triangles), so with each square tile consisting of two polygons, simple maths tells us that a

matrix cannot have more than 5,000 tiles. This means you can create a matrix 1 tile deep by 5,000 tiles wide,

or 10 deep and 500 wide, or 100 deep and 50 wide, or any combination up to 5,000 tiles maximum - you get

the idea. So, the biggest square matrix you can have in DB is 70x70 tiles which equal 4,900 tiles or 9,800

polygons - within our 10,000 limit. If the matrix was one tile bigger - 71x71, this would add up to 10,082

polygons and not be allowed - just in case you were wondering why DB has the strange number 70x70 as a

maximum!

Most matrices (the plural of matrix, not matrixes), are produced square - mainly because the maths is easier.

Plus, textures are also designed to look best on a square matrix tile, and a matrix 10 tiles wide and 5 tiles deep

but having the same pixel width and height would have rectangular tiles - not square.

To correct this, you need to calculate the pixel width in relation to the height. With a square tile-sized matrix,

no calculations are necessary as the pixel width and height is always the same.

If you want bigger landscapes than 70x70 tiles, you can create more than one matrix in your program and join

them together, though if you have too many on screen at the same time, DB will start to slow down on lower

spec machines. There are clever ways around this which we'll look at later.

Page 79: Darkbasic Guide= Dbproguide

78

The matrix is created with the command: MAKE MATRIX Mn,Pw,Pd,Tx,Tz where Mn is the matrix number, Pw

& Pd is the pixel width and depth and Tx & Tz are the tiles across and down. When you create a matrix you

tend to think of it most as being viewed from above in 2 dimensions - as shown in figure 1. 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 - NOT the Y axis. Hence the variable Tz instead of Ty in the example above.

MAKE MATRIX 1,5000,5000,4,4

This will create the matrix in figure 1 and the bottom left corner is always placed at 0,0,0 in 3D space. You can

of course move it, but it isn't advised until you are more experienced. Looking at figure 1 again, you will see

that there are two sets of numbers - white and purple. The larger white numbers are the X and Z values you

use when referring to tiles when texturing. For this 4x4 tile matrix, the numbers along the X and Z axis run

from 0 to 3. In your DB code, your matrix tile width and height would be stored in variables such as TileWidth

and TileHeight, so when texturing, you would use a loop like FOR N=0 TO TileWidth-1. That way, should the

tiles across variable change, the loop will still texture all tiles.

The smaller purple numbers are used when altering the matrix height and as there are two points along both

axis for every matrix tile, there has to be an extra co-ordinate to handle this. As such, 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). When a matrix is first created each of these height values is set to zero. Positive

values raise the point and negative values lower it.

Let There Be Height

Raising the height of any part of a matrix means altering the value of one of the tile intersect points, (corners).

You cannot raise any other part of the matrix - the middle of a tile for example. Altering the height of one of

the tiles means altering one or more of the four associated tile corner points. Setting all four points of a single

tile to the same value will raise or lower the tile but keep it flat.

The DB command you use is SET MATRIX HEIGHT Mn, X, Z, H where Mn is the matrix number, X and Z are the

intersection co-ords (the smaller purple numbers in fig 1) and H is the required height of that point. So, If we

wanted to raise the tile highlighted with a red circle in fig 1, we need four commands - one for each of the four

corner points:

Page 80: Darkbasic Guide= Dbproguide

79

You will see that the 'red blob' tile's bottom left corner is 2 across (X Axis) and 1 up (Z Axis), so the command

for raising that corner to a height of 20.0 would be:

SET MATRIX HEIGHT 1, 2, 1, 20.0: Rem Bottom Left Tile Corner

The remaining three lines for the other three corner points would be:

SET MATRIX HEIGHT 1, 3, 1, 20.0: Rem Bottom Right

SET MATRIX HEIGHT 1, 3, 2, 20.0: Rem Top Right

SET MATRIX HEIGHT 1, 2, 2, 20.0: Rem Top Left

The height value should be a real (floating point) number, so you need to remember to put the .0 on the end.

In practice integers do work, 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, you won't have to go back and alter all of your code.

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...

Another useful matrix height command to know about is RANDOMIZE MATRIX Mn, MaxHeight which will set

the height values of ALL intersection points of a matrix with a single command. The height used is a random

value between 0 and whatever you enter for the value MaxHeight.

IMPORTANT! After using any matrix command, it is important to remember to tell DB to refresh the matrix on

the screen. This is done with UPDATE MATRIX Mn where Mn as usual is the matrix number. 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. To make the matrix a little more colourful

and realistic, you need to texture the tiles you have created. This is done with SET MATRIX TILE Mn, X, Z,

TextureNum and isn't helped by the confusing help files that come with DB that say "SET MATRIX TILE Matrix

Number, X, Z, Tile Number".

To be able to use this command, it is very important that you have told DB to prepare your texture image

ready for use with PREPARE MATRIX TEXTURE Mn, ImageVal, Across, Down - where ImageVal is the texture's

image number and Across/Down is the texture grid size. So let's split all of that down into more easily followed

sections:

Page 81: Darkbasic Guide= Dbproguide

80

A texture like the one above is a graphic image and can in theory be any size, but the smaller they are (and if

they are square), then the faster DB will run.

Example sizes can be 32x32, 64x64, 128x128 or 256x256, (sizes in pixels). 512x512 can be used, but some

graphics cards will start to struggle. Older Voodoo cards don't like anything over 256x256 or they throw a

wobbler. The most commonly used size is 128x128 as the smaller the texture, the worse the quality. Having

said that, if anyone remembers Equilibrium, 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 - including

a small collection on this web site.

OK so far. Now for the rules which cause the problems...

First of all, each matrix can only have one single texture image associated with it. What!!?? Only one texture?

No - only one texture IMAGE, though that image can have lots of individual textures in it. (see image below).

This is done by placing them in a grid just like the old chessboard pattern again. This is when the texture size

starts to get a little more important.

128x128 for individual textures is a happy medium between size and quality, but 16 textures of that size on a

4x4 grid in a texture image would be 512x512 pixels. Most new graphics cards would have the speed and

memory to handle this size of texture image, 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, big,

high quality textures can prevent an awful lot of users from using your programs!

OK, so what can you do? Easy - just reduce the size of your textures. A 256x256 texture image can contain 64

textures of 32x32 on an 8x8 grid and you'd be surprised how good they can look. Go up to textures 64x64 in

size and you can get 16 textures on a 4x4 grid with double the quality. You can always go for the sod 'em

attitude and not even worry about whether others can use your programs or not!

Next problem - making your texture. Your texture needs to be square, as too does the grid of textures in it. 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, 4x4, 8x8 or 16x16. It doesn't matter if some slots on the grid

aren't used.

Note: These are recommended grid sizes, though I have also used others, like 3x3 and 6x6 and had no problems

whatsoever. I think that the important thing is that they are square grids - ie NOT 3x4 or 4x5. Use what you

like, but bear this in mind if you have texture glitches or problems.

For example, let's say you have 5 textures. A 2x2 grid will only hold 4 textures so you would use the next one

up - 4x4 and only use the first 5 slots. The same size texture image could be used for up to 16 textures before

having to move up to the next size. On this 4x4 grid image, each texture could be 64x64 and still fit in a

256x256 pixel image.

Page 82: Darkbasic Guide= Dbproguide

81

So, in summary, you need to decide on your texture size, which depends on the size you want your image to

be and what grid size to use - which in turn depends on how many textures you use. Then, you have to create

the image all using the correct sizes - either by hand in a paint program like Paintshop Pro, or via code in Dark

Basic. If you add more textures as they are required, you have to edit the image - after possibly recalculating

all the sizes!

All in all, it's not a straight forward process and once again it adds to the argument that you can't beat a matrix

editor. For example, MatEdit allows you to have up to 100 textures in the texture palette and when you use

the Build option, it does all the calculations and creates the texture image to the correct grid dimensions and

texture sizes to fit - but only includes the textures from the palette that you actually used and not those you

didn't!

OK, so let's say you now have your texture image with 4 seperate textures in a 2x2 grid. What next? Well, the

texturing process is reasonably straight forward from here on. The textures are usually stored in a BMP file and

we need to get them into an IMAGE, so we need to use LOAD IMAGE "ImageName", ImageNum. If we want to

use the 2x2 example texture image above (and it was called texture.bmp) we would use:

LOAD IMAGE "texture.bmp", 1

Note the image number is 1 - it's needed in the next stage. Now we have the texture in an image we can use:

PREPARE MATRIX TEXTURE 1, 1, 2, 2

The first 1 is the matrix number, the second is the image number we loaded texture.bmp into. The last two

numbers are the grid size X and Y for the textures in the image. Here we have 4 images on a 2x2 grid, so the

values are 2 and 2. Basically, this command tells DB to prepare the image for the matrix by cutting it up into

two images horizontally and two images vertically. The four resulting textures are numbered 1 to 4 and stored

in memory ready for when you need them. Obviously, if your texture image was an 8x8 grid of textures, you

would use the value 8 instead of 2.

Now the image has been prepared, we can now use DB's matrix commands to 'paint' the matrix.

If you want the whole matrix textured with the same texture, you can use FILL MATRIX Mn, Height, TileNum

which will also set all the tiles to a given height at the same time with a single command. TileNum will be a

number from 1 to x where x is the number of available textures in the texture image you loaded.

Note: Many new users don't realise that if you use PREPARE MATRIX TEXTURE with an image containing just

one single texture, then FILL MATRIX is automatically called by DB and the whole matrix will be textured -

without you asking for it! If like our example, you load a texture image which contains more than one texture,

PREPARE MATRIX TEXTURE does nothing to the actual matrix.

If you want to texture individual tiles on the matrix, the process is very similar to the previously used method

to alter the matrix heights - you just need to remember that there is one less co-ordinate to deal with.

Let's refer back to fig 1 for a moment below, but this time from a texturing perspective.

Page 83: Darkbasic Guide= Dbproguide

82

This time we are going to texture the tile with the blue blob using the water texture from our 2x2 texture

image. We will do this using SET MATRIX TILE Mn, X, Z, TextureNum. Looking at fig 1, we can see that using the

larger white numbers, that particular tile is at X=2, Z=2 and looking at the texture image, water is texture

number 4. We know we are using matrix number 1, so this gives us:

SET MATRIX TILE 1, 2, 2, 4

Once again, you can see that creating a good looking terrain using this method would be a slow laborious

process. However, there are ways to speed up the texturing process without a matrix editor. One method is to

create your matrix grid on paper and list your textures - each one numbered. If you 'colour in' your matrix by

putting the number of the texture you want in each grid square, you can transfer all the numbers to DATA

statements then read all the texture values in a loop.

Let's assume a 5x5 matrix which has 6 used textures. Your sketch would look something like the one on the

here.

Feeding the texture numbers into the matrix with a loop like FOR...NEXT usually counts from 0, so with this in

mind, we need to build our data statement up reading from left to right working our way up the matrix

starting at the bottom - remember, the bottom left corner tile is 0,0.

So, our data statement (placed at the end of the program) would look like this:

DATA 2,2,3,3,2,2,3,4,5,3,2,3,4,5,3,1,2,3,3,6,1,1,2,6,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, Nx, Nz, TextureNum

Page 84: Darkbasic Guide= Dbproguide

83

Next Nx

Next Nz

Update Matrix 1

Rem Data Statements At End Of Program

DATA 2,2,3,3,2,2,3,4,5,3,2,3,4,5,3,1,2,3,3,6,1,1,2,6,6

You could use the same loop method to make matrix height setting easier as well, 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. If you are

clever though, you can create an algorithm with SIN/COS to calculate the heights to create nice hills etc.

Alternatively, you can do what many people do - write your own simple matrix editor if there isn't one out

there that suits your purposes.

As if you didn't know already, 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, check out the MatEdit page on there.

Clever Stuff

OK, it's been such a long tutorial, you may have forgotten that a little earlier I mentioned that there were

clever ways to get around the 70x70 tile matrix limit. In the downloads section of my web site, there's a small

demo where you can wander around a landscape which is 300x300 tiles square! I haven't tried it myself, but

I'm informed by someone who has, 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 on-

screen in 800x600 screen mode. 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. My main PC has 1.5 Gigabyte of memory and in

theory I could create a terrain of such a size that it could take weeks - not hours - 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. So let's cover the theory first so you can go away and try to write something yourself...

The Theory

Normally, you control your character and he moves across the matrix landscape, the camera in tow. Another,

slightly more difficult method is to place the character at 0,0,0 in space and reposition the matrix so that the

character looks like it's standing in the centre of it. When the character is moved, it may animate and look like

it is moving, but it doesn't - 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, down, left or right - without actually moving the matrix itself. Rows and columns which are scrolled 'off'

the matrix re-appear automatically at the opposite edge of the matrix. Unfortunately, shifting the matrix by

such huge amounts looks incredibly jerky and totally crap. So, 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, at

which point you put the matrix back at its starting position and use the SHIFT MATRIX command.

If this is done carefully, it looks like the character is smoothly walking over the matrix. What's more, as the

matrix is continuously scrolling with the SHIFT MATRIX command, if it has been designed correctly, you never

reach the edge of the matrix whichever direction you travel. The result is a matrix which goes on forever - but

it certainly isn't a huge matrix - the biggest it can be is 70x70 tiles and with the same landmarks encountered

Page 85: Darkbasic Guide= Dbproguide

84

at regular intervals, the player will soon notice what is happening. There is an example of a never-ending

matrix in the downloads section.

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. I didn't bother for MatEdit, (as it's only a matrix editor, not a world editor), 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, though any size could be chosen. The same method of character control is used as described above

by moving the matrix and then using SHIFT MATRIX, but the big difference being that just before the SHIFT

MATRIX is used, the relevant tile row or column is completely replaced from the arrays. The routines keep

track of the character's direction so it knows which rows/columns to update and the terrain doesn't repeat. Do

the same thing with 100,000x100,000 arrays and you still only have a 10x10 tile matrix on screen, so there's no

additional slowdown or lag when you are playing, just an amazingly large playing area!

Page 86: Darkbasic Guide= Dbproguide

85

Dark Basic 3D Collision (DBC & DBP)

Very few games can be written without some form of collision. You need a way to stop your character from

walking through walls/other characters or dropping through the floor he/she is walking on. This is what the

collision commands are for.

In a nutshell, the DB collision commands simply alert you when certain objects bump into each other. This is in

3D by the way - sprites in 2D also have collision, but sprites are the topic of another tutorial.

Before we start though, I have to explain that you will find a number of third party aids to help with collision -

like Sparky's external dll, 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. I've not had cause yet to stretch them that far, so I cannot

confirm this from personal experience but have to believe that this is probably true.

However, having said that, I think it's important to get to know how the built-in commands are used. Try them.

Once you understand how they work, implementing a third party solution is made that much easier. What's

more, 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.

The two basic collision commands in DB are:

OBJECT HIT(ObjNumA, ObjNumB)

OBJECT COLLISION(ObjNumA, ObjNumB)

The first command alerts you if object A bumps into object B, whereas the second lets you know if object A is

overlapping object B.

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) - both are integers.

Copy and paste the following code in the DB classic editor and run it. Use the cursor keys in this top-down view

to run the red cube into the white one.

Sync On: CLS 0

Sync Rate 60

AutoCam Off

Hide Mouse

Make Matrix 1,2000,2000,20,20

Create Bitmap 1,128,128

CLS RGB(0,128,0)

Get Image 1,0,0,128,128

Set Current Bitmap 0

Delete Bitmap 1

Prepare Matrix Texture 1,1,1,1

Make Object Cube 1,3: Rem Our Cube

Position Object 1,1000,3,990

Color Object 1,RGB(255,0,0)

Make Object Cube 2,3

Position Object 2,1000,3,1000

Position Camera 1000,50.0,980

Point Camera 1000,3,980

Do

If UpKey()=1 Then Move Object 1,0.1

Page 87: Darkbasic Guide= Dbproguide

86

If DownKey()=1 Then Move Object 1,-0.1

If LeftKey()=1 Then YRotate Object 1,WrapValue(Object Angle Y(1)-2.0)

If RightKey()=1 Then YRotate Object 1,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#,30.0,ObjPosZ#

If OBJECT HIT(1,2)=1

Color Object 2,0

Sleep 20

Repeat

Until OBJECT HIT(1,2)=0

Color Object 2,RGB(255,255,255)

Endif

Sync

Loop

End

You will have noticed that when you bump into the white cube, it flashes black. In the code, you can see that

the colour of the stationary cube is set when OBJECT HIT(1,2) returns a 1. The Repeat...Until loop immediately

after this executes continuously until OBJECT HIT(1,2) returns a 0, at which point the stationary cube is

returned back to white.

However when the program is running and you hit the other cube, it turns black then immediately returns to

white and you continue passing through the other cube.

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's not automatic).

As for the colour flashing, that's because OBJECT HIT does just that - it detects the first contact between two

objects. It isn't triggered again while the two objects are colliding - 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.

So how do we know if a collision is still occurring after the initial hit?

Enter the OBJECT COLLISION command...

This is similar to OBJECT HIT, but returns a 1 if two objects are overlapping. Try running the following program:

Sync On: CLS 0

Sync Rate 60

AutoCam Off

Hide Mouse

Make Matrix 1,2000,2000,20,20

Create Bitmap 1,128,128

CLS RGB(0,128,0)

Get Image 1,0,0,128,128

Set Current Bitmap 0

Delete Bitmap 1

Prepare Matrix Texture 1,1,1,1

Make Object Cube 1,3: Rem Our Cube

Position Object 1,1000,3,990

Color Object 1,RGB(255,0,0)

Make Object Cube 2,3

Position Object 2,1000,3,1000

Position Camera 1000,50.0,980

Page 88: Darkbasic Guide= Dbproguide

87

Point Camera 1000,3,980

Do

If UpKey()=1 Then Move Object 1,0.1

If DownKey()=1 Then Move Object 1,-0.1

If LeftKey()=1 Then YRotate Object 1,WrapValue(Object Angle Y(1)-2.0)

If RightKey()=1 Then YRotate Object 1,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#,30.0,ObjPosZ#

If OBJECT HIT(1,2)=1

Color Object 2,0

FirstHit=1

Endif

If FirstHit=1

C=OBJECT COLLISION(1,2)

If C=0 Then Color Object 2,RGB(255,255,255): FirstHit=0

Endif

Sync

Loop

End

It's basically the same as the first example apart from inside the main Do..Loop. This time, when you bump into

the stationary cube, it stays black until you move off it. This is because once the initial 'hit' is recorded, we set

a flag (change the value of a variable) to denote that a collision has occurred. In this case, we set the variable

FirstHit to 1.

When this is set to 1, the code in the If FirstHit=1 block is carried out each time around the loop. Before the

initial collision, FirstHit was set to 0 (zero), so it was ignored.

In the If FirstHit=1 block, we test with OBJECT COLLISION. While objects 1 and 2 are overlapping, this will

always return a 1. The next line checks to see if it returned 0 and when it does, (only when the objects are not

colliding), it returns the other cube back to white and turns the flag back off so the If FirstHit block isn't

executed again unnecessarily.

So now, we know how to detect a collision between two objects. But, have you noticed the downside?

Here we are just testing for collisions between objects 1 (our cube) and 2 using OBJECT HIT(1,2) and OBJECT

COLLISION(1,2). But what if we have another object - or dozens more objects? Won't we need a line of code

for each of them?

Well, no actually, we don't. Remember the ObjNumA and ObjNumB from earlier?

A useful little option is to use a 0 (zero) for ObjNumB, making it HIT(1,0) and COLLISION(1,0). With this option,

the command no longer returns a 0 or a 1, 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?...

So let's see how to detect collision with many objects when you don't know what object numbers they are...

Sync On: CLS 0

Sync Rate 60

AutoCam Off

Hide Mouse

Randomize Timer()

Make Matrix 1,2000,2000,20,20

Page 89: Darkbasic Guide= Dbproguide

88

Create Bitmap 1,128,128

CLS RGB(0,128,0)

Get Image 1,0,0,128,128

Set Current Bitmap 0

Delete Bitmap 1

Prepare Matrix Texture 1,1,1,1

Make Object Cube 1,3

Position Object 1,1000,3,990

Color Object 1,RGB(255,0,0)

For N=2 To 11

Make Object Cube N,3

Position Object N,1000+(Rnd(60)-30),3,1000+(Rnd(60)-30)

Next N

Position Camera 1000,60.0,980

Point Camera 1000,3,980

Do

If UpKey()=1 Then Move Object 1,0.4

If DownKey()=1 Then Move Object 1,-0.4

If LeftKey()=1 Then YRotate Object 1,WrapValue(Object Angle Y(1)-2.0)

If RightKey()=1 Then YRotate Object 1,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#,60.0,ObjPosZ#

H=OBJECT HIT(1,0)

If H>0: Rem collision detected

Color Object H,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,NowColliding)

Rem And if not, recolour cube to white and turn off flag

If C=0 Then Color Object NowColliding,RGB(255,255,255): NowColliding=0

Endif

Sync

Loop

End

Important Note: In this example, if two objects are close to each other, then it's possible to still be colliding with

one object when you hit another. In such a case, the hit on the second object is not detected. This is because if

OBJECT COLLISION() is returning any value other than 0 then OBJECT HIT() is not tested for. It doesn't matter

that the object you hit is different to the one you are currently colliding with.

This is normal and won't be a problem when we actually come to handling the collision later.

So we now know how to detect when we hit another object - regardless of object number. Next we have to

deal with it because as you have seen, detecting the other object doesn't stop you moving through it.

What method we use depends on the method used to move the object we are controlling. There are two main

ways to move a 3D object around:

The first is to turn it in the required direction, then use the Move command to move it the required amount in

the direction it is facing.

The second is to use variables for the X, Y and Z position and use Position Object along with the Rotate

commands to move the object about.

Page 90: Darkbasic Guide= Dbproguide

89

Our examples so far use the first method. 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.

Here's the next example...

Sync On: CLS 0

Sync Rate 60

AutoCam Off

Hide Mouse

Make Matrix 1,2000,2000,20,20

Create Bitmap 1,128,128

CLS RGB(0,128,0)

Get Image 1,0,0,128,128

Set Current Bitmap 0

Delete Bitmap 1

Prepare Matrix Texture 1,1,1,1

Make Object Cube 1,3

Position Object 1,1000,3,990

Color Object 1,RGB(255,0,0)

Make Object Box 2,80,20,3: Rem Create a wall

Position Object 2,1000,3.0,1000

Ghost Object On 2

Position Camera 1000,50.0,950

Point Camera 1000,3,990

Speed#=0

Do

If UpKey()=1 Then Speed#=0.4: Move Object 1,Speed#

If DownKey()=1 Then Speed#=0-.4: Move Object 1,Speed#

If LeftKey()=1 Then YRotate Object 1,WrapValue(Object Angle Y(1)-2.0)

If RightKey()=1 Then YRotate Object 1,WrapValue(Object Angle Y(1)+2.0)

ObjPosX#=Object Position X(1)

ObjPosZ#=Object Position Z(1)

Position Camera ObjPosX#,50.0,ObjPosZ#-40

Point Camera ObjPosX#,3.0,ObjPosZ#

If OBJECT COLLISION(1,0)>0: Rem collision detected

Repeat

Move Object 1,0-Speed#

Position Camera Object Position X(1),50.0,Object Position Z(1)-40

Point Camera Object Position X(1),3.0,Object Position Z(1)

Until OBJECT COLLISION(1,0)=0

Endif

Sync

Loop

End

You will notice first of all that this example does not use OBJECT HIT(). You don't have to if you know that all

the objects in your program are simply objects that you cannot pass through. If this is the case - like this

program - you just stop your object from passing through any them.

However, if some of your objects are not solid, then you would use OBJECT HIT() to detect the number of the

object you just collided with, before deciding how to deal with the collision.

Also, the actual collision effect used here when the cube hits the wall isn't the best for a game. When a game

character hits a wall at an angle, the game's continuity and smooth running is interrupted if the character

comes to a dead halt. 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 - but it does look and feel much better in a

game.

Page 91: Darkbasic Guide= Dbproguide

90

The changes to this program from the last one are a little more substantial too.

First of all, we create a wall as object number 2. We also place the camera a little further 'South' of our cube so

we can see the wall at a better angle.

We also create a new variable called Speed# with which to move our cube around.

The reason for this is that when a collision is detected, we have to move the cube backwards, away from the

wall, but only if we are moving forwards! The cursor down key lets us back away from the wall, but what if we

bump into the wall while moving backwards? Well, when the collision is detected we would move the cube

backwards further into the wall.

So, to prevent this from happening, we use a variable to store the current direction. When we press cursor up,

Speed# is set to a positive number so the cube moves forwards. When we press cursor down, Speed# is set to

a negative number so the cube moves backwards.

The advantage of this is that it doesn't matter what direction the cube is heading, moving it 0-Speed# ALWAYS

moves it in the opposite direction by the same amount.

So, in the example, in the loop we test to see if a collision has been triggered with:

If OBJECT COLLISION(1,0)>0

If our cube hits anything, the code in this block will be executed. In the event of a collision therefore, we use a

Repeat...Until loop moving the cube back with Move Object 1,0-Speed#, which as mentioned previously moves

the cube back. As this is in a repeat loop, it's repeated until the cube is no longer in collision with the wall, at

which point the loop is exited.

This method isn't perfect because at certain angles, you can clip the wall with the corner of the cube when

rotating it - in which case, the backing out routine kicks in and you are instantly 'transported' to the other end

of the wall! For this reason, there's a little more work involved, but it's better to use the X, Y and Z variables for

moving objects around if you need collision rather than Move.

So let's now take a look at Sliding Collision...

Sliding collision isn't that much different to what we've just been doing.

The basic difference is that we have to use collision boxes. We don't just move back to the last known good

position, we use other DB commands to tell us where to go back to.

Collision boxes are invisible 'wrappers' that you apply to objects. These wrappers can give you feedback on

collision and you simply size them to fit the object you are applying them to. If you move the object, then the

collision box moves with it.

The new command we are going to use is MAKE OBJECT COLLISION BOX Object Number,x1,y1,z1,x2,y2,z2,flag

and looks rather complicated with all those parameters. But believe me it's not really. Let's go through them...

Page 92: Darkbasic Guide= Dbproguide

91

Object Number

Obviously this is the number of the object that you are creating the collision box for.

X1,Y1,Z1

OK, remember that I just said that when you move an object, then the collision box moves with it? That's

because the collision box is attached to the object number. As an object can be placed anywhere in your world

with the Position Object X,Y,Z command, we can't use 'real' X,Y,Z world location co-ords to define the collision

box because if we moved the object, the collision box would still be at the old location.

So, instead, when you define a collision box, you use X,Y,Z OFFSETS which assume that the object is at point

0,0,0 in space. This is a neat method as we can use negative and positive values which remain true wherever

the object is placed.

OK, an example. Imagine a box or wall 100 wide (X), 50 tall (Y) and 10 Deep (Z). We make the object with Make

Object Box 1,100,50,10 and the CENTRE of this box is positioned at 0,0,0 in space.

Therefore, 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.

We repeat the process for the Y and Z axis. With a box 50 tall, Y1 is -25 and with a depth of 10 then Z1 is -5.

Notice that the values are the negative equivalent of the boxes X, Y and Z dimensions divided by 2. Eg: Width

(X) = 100/2 = 50 which makes -50.

X2,Y2,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, so this is

used for the X2 parameter. In the same fashion, Y2 is 25 and Z2 is 5. 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.

Here's a small image to show you what exactly is happening:

In the image, our object (the wall) is green. The X, Y and Z world axis are the black lines and where they all

intersect, is position 0,0,0. As you can see, this is exactly in the centre of our wall. The dimensions of the wall

are shown in blue.

If the X axis is 0 at the centre of the object, the values run negative to the left and positive to the right.

Page 93: Darkbasic Guide= Dbproguide

92

The red distances are those used for the offset parameters. The important thing to realise using this method is

that as mentioned earlier, once these offsets are defined, even if the wall was moved to another X,Y,Z location,

the offsets would still define the collision box correctly, whereas real X,Y,Z locations would not.

The origin for the offsets is based on the X,Y,Z world position of the Object whose number you supply. Easy

eh?

Let's see the code:

Set Display Mode 800,600,16

Autocam Off

Sync On: CLS 0

Sync Rate 60

Hide mouse

Make Matrix 1,2000,2000,20,20

Create Bitmap 1,128,128

CLS RGB(0,128,0)

Get Image 1,0,0,128,128

Set Current Bitmap 0

Delete Bitmap 1

Prepare Matrix Texture 1,1,1,1

Position Matrix 1,-1000,0,-1000

Make Object Cube 1,5

Color Object 1,RGB(255,0,0)

Make Object Collision Box 1,-2.5,-2.5,-2.5,2.5,2.5,2.5,0

XPos#=0: YPos#=2.5: ZPos#=0

Position Object 1,0,2.5,0

Make Object Box 2,150,50,10

Position Object 2,0,25,100

Make Object Collision Box 2,-75,-25,-5,75,25,5,0

Do

If Upkey()=1 Then Move Object 1,2

If Downkey()=1 Then Move Object 1,-2

If Leftkey()=1 Then Yrotate Object 1,Wrapvalue(Object Angle Y(1)-4)

If Rightkey()=1 Then Yrotate Object 1,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,0)>0

Dec XPos#,Get Object Collision X()

Dec YPos#,Get Object Collision Y()

Dec ZPos#,Get Object Collision Z()

Endif

Position Object 1,XPos#,YPos#,ZPos#

Angle#=Object Angle y(1)

CamDist#=40.0 : CamHeight#=YPos#+10.0 : Camsmooth#=3.5

Set Camera To Follow

XPos#,YPos#,ZPos#,Angle#,CamDist#,CamHeight#,CamSmooth#,0

Sync

Loop

When the program is running, in the main loop, 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#, YPos# and

ZPos#.

We then test for a collision with Object Collision(1,0) and if the result is anything but a zero then we've hit

something. The value returned is the number of the object we are banging our head against. We next use

Page 94: Darkbasic Guide= Dbproguide

93

three functions we've not seen before: Get Object Collision X(), Get Object Collision Y() and Get Object

Collision Z().

If you cast your mind back to when I was describing how you use the Make Object Collision Box command, 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. Remember? Well, if you set that flag to zero, you can use the Get Object Collision functions.

These return the sliding collision values which we use to back up our object. Remember though that they ONLY

return data if that little flag is set to 0!

So, armed with the amount to back up in the X, Y and Z directions, we decrement them from our object's X, Y

and Z positions and use Position Object to place the object in its new position. The rest of the code simply

handles the camera to follow the cube.

Well, that's the end of this tutorial on Simple DB Collision and armed with this new knowledge, you can

investigate the many other collision commands DB supplies you with.

Page 95: Darkbasic Guide= Dbproguide

94

2D Shooting - 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, where it is being aimed, and then plotting it's course between the two points.

In 3D games, which I'll cover in a different tutorial, most beginners make the same mistake as I did - the fatal

one of not thinking about what happens in real life.

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. But, that's the problem...

When you fire a bullet from a gun or a sniper rifle, the bullet moves that fast you don't see it. So why waste

time in your programs trying to show what you shouldn't be able to see anyway?

First though, we'll cover the less realistic environment of the 2D shooting problem.

2D Shooting

In a 2D program the bullet can be a sprite, an image, or even an ASCII character. Whatever the type of bullet,

the process is as follows:

1. Place the bullet in front of the gun barrel

2. Calculate the next position of the bullet

3. Replace the background for the old bullet position

4. Place the bullet in the new position

5. Check to see if hit the enemy or left the screen

6. If 'no' to both questions at number 5, repeat from number 2

7. 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. Let's try a very simple example of the type that most beginners usually make. To keep it simple, we

will use the Text X,Y command and ASCII characters - remember the method is basically the same with images

and sprites.

Note: The example programs have been written to demonstrate the process as clearly as possible - not to

demonstrate good programming practices. Once you understand how these programs work, you can optimize

and improve on them.

Set Display Mode 800,600,16

Sync On: CLS 0

Sync Rate 60

Hide Mouse

Set Text Opaque

BasePosX = 400

Text BasePosX,580,"M"

Rem Main Program Loop

Do

If Leftkey()=1 Then Dec BasePosX,4

If BasePosX<0 Then BasePosX=0

If Rightkey()=1 Then Inc BasePosX,4

If BasePosX>788 Then BasePosX=788

CLS

Page 96: Darkbasic Guide= Dbproguide

95

Text BasePosX,580,"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,N,"|"

Sleep 1

Text BulletPosX,N," "

Rem Check to see if we hit anything here and if we did

Rem handle it.

Next N

Return

Essentially, BasePosX is integer variable used to store the horizontal position on the screen and when the left

and right cursor keys are pressed, we decrement or increment this variable, clear the screen and print our base

(the letter M) in the new position.

When the space bar is pressed, we gosub the MoveBullet procedure.

In 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, printing and erasing the bullet on the screen.

But, if you run the above code, you'll see there's a major problem: When you fire, the base freezes while the

bullet is on the screen.

That's because this little program isn't the correct way to do this. But because we learn from our mistakes,

seeing the wrong way to do something makes the right way easier to understand.

To solve this problem, 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.

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. 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...Next loop.

At this point, the keen-minded of you may have a question: "If we do that, won't the bullet be firing all the

time"?

Well the answer initially is yes. So, we use something called a 'flag'. 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.

Usually this would be by setting the variable to 0 (zero) if it hasn't and 1 if it has. (Some languages have

Boolean variables which can be set to True or False, but as DB Classic does not, in this tutorial we'll just use a

normal integer variable set to 0 or 1 for the same result).

So, when the space bar is pressed, we set a flag to 1 to say a bullet has been fired and it remains 1 until the

bullet no longer exists - at which point the flag is set back to 0 (zero).

In our main loop, we gosub the MoveBullet procedure only if this flag is set to 1. Easy eh?

Page 97: Darkbasic Guide= Dbproguide

96

But another problem then creeps in. If we keep pressing the space key, each time we do, the bullet currently

moving up the screen starts again from the beginning!

Stopping this is easy. All we do is tell the program to ignore the space bar if a bullet is in flight. As we already

have a flag which equals 1 when a bullet is fired, we just tell the program to ignore the space bar unless the

bullet flag equals zero!

So let's modify our above example...

Set Display Mode 800,600,16

Sync On: CLS 0

Sync Rate 60

Hide Mouse

Set Text Opaque

BasePosX = 400

Text BasePosX,580,"M"

Rem Main Program Loop

Do

If Leftkey()=1 Then Dec BasePosX,4

If BasePosX<0 Then BasePosX=0

If Rightkey()=1 Then Inc BasePosX,4

If BasePosX>788 Then BasePosX=788

CLS

Text BasePosX,580,"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,5

Text BulletPosX,BulletPosY,"|"

Sleep 1

Text BulletPosX,BulletPosY," "

Rem Check to see if we hit anything here and if we did

Rem handle it. Reset flag if bullet is done with

If BulletPosY<=0 Then FiredBullet=0

Return

In this example, when the space bar is pressed, so long as there isn't a bullet already in flight, we calculate the

bullet start position and turn on the 'fired bullet' flag.

Next, if the flag has been set, we gosub the MoveBullet procedure which moves the bullet a bit then

immediately returns to allow the user to move the base.

You'll also notice that there is also an 'Else' section. This is because when you fire a bullet, a delay is used in the

procedure (Sleep 1) so the bullet can be seen. This isn't good practice, but keeps the example nice and simple

for the tutorial.

Page 98: Darkbasic Guide= Dbproguide

97

This also means that when there is no bullet being fired the program runs quicker. The result is that the base

moves at two speeds - one when no bullet is visible and slower when one is.

So, this If..Else..Endif section basically says 'if there is a bullet on screen then don't use a delay - if there isn't

then do use a delay'. In a nutshell it means that the base moves at roughly the same speed whether firing or

not.

OK, that covers simple 2D firing, but "wait!" I hear you cry... "you can only fire a single bullet and I want more!"

Well, for multiple firing the process is just the same, but you need separate variables to store the X and Y

screen positions of EVERY bullet.

This task is exactly what arrays were designed for so if you don't fully understand arrays, go and read the

tutorial which covers them now, then come back when you know how they work. In this next section I have to

assume that arrays are not a mystery to you as I make no attempt at explaining them.

Arrays are covered in my Beginners Guide To Programming Part 1 tutorial.

Multiple Bullets

In the last example it gets a little more complicated as we need to keep essentially the same method, but

handle more than one bullet in the MoveBullet procedure, when we don't know how many bullets there are -

if any! This is done with arrays. We also add another procedure for the creation of a new bullet, but more on

that after the example code.

Before we start, we also need to decide on a maximum number of bullets to display at any one time. Also, our

flag from the last example can no longer be a single flag - there has to be one for each possible bullet.

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.

In the MoveBullet procedure, we go back to using a loop and use the flag array to decide if the respective

bullet should be displayed. Let's see the code:

Set Display Mode 800,600,16

Sync On: CLS 0

Sync Rate 60

Hide Mouse

Set Text Opaque

BasePosX = 400

Text BasePosX,580,"M"

MaxBullets = 20

Dim FiredBullet(MaxBullets)

Dim BulletPosX(MaxBullets)

Dim BulletPosY(MaxBullets)

Rem Main Program Loop

Do

If Leftkey()=1 Then Dec BasePosX,8

If BasePosX<0 Then BasePosX=0

If Rightkey()=1 Then Inc BasePosX,8

If BasePosX>788 Then BasePosX=788

Page 99: Darkbasic Guide= Dbproguide

98

CLS: Text BasePosX,580,"M"

If Spacekey()=1 Then Gosub AddBullet

If BulletCount > 0 Then Gosub MoveBullet

Text 0,0,"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),BulletPosY(N),"|"

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. If this is set to 20, then

when you have fired 20 bullets, you can't fire any more until at least one has hit something or gone off the top

of the screen.

There are also a couple of changes in the Main Loop worth mentioning...

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, for this explanation, think of the array as one of those ammo

belts, but as a continuous loop containing 'MaxBullets' bullets. Each bullet is in a slot and once a bullet has

been fired, it's slot is empty and a new bullet can be clipped into the belt at that position.

First of all, when the space key is pressed, we now Gosub AddBullet where we loop through every element of

the FiredBullet() array looking for an empty slot. If you remember, when bullet 1 is in use, then FiredBullet(1)

equals 1 and when it is not, it equals 0.

When we press space, this routine finds the first empty slot in the array, turns the FiredBullet() flag on (sets it

to 1), stores that bullet's X and Y start position and increments the variable which holds how many bullets are

currently in use.

The next line, the Exit, basically exits the For..Next loop at that point without unnecessarily running through

the whole loop. For example, if it was the first bullet and there were a maximum of 10 bullets, the first slot (1)

Page 100: Darkbasic Guide= Dbproguide

99

is used and continuing the loop testing the other 9 would be a waste of time. So, placing an Exit inside the

block when an empty slot is found saves our program a lot of wasted testing.

OK, at this point, BulletCount equals 1 so in the main loop, Gosub MoveBullet is called. This is another similar

loop but this time, the array flag is used to say whether the bullet is drawn or not.

If the bullet goes off the top of the screen or hits something (that's the bit you can write yourself), then we set

the array flag back to 0 to show that the slot is available for use again and decrement the BulletCount variable.

If the space is pressed again while another bullet is on screen, then as we are using arrays, another one is

created and as this is done in a For..Next loop which counts from 1 to the maximum number of bullets you can

have (MaxBullets), you can never create more than the required number of bullets, (or as we are using arrays,

ever get the dreaded 'index out of bounds' error)!

Page 101: Darkbasic Guide= Dbproguide

100

BitMap Font Tutorial

by Craig Chipperfield

Bitmap fonts; what are they and what can you do with them? Well, before we look at bitmap fonts it is

important to look at ‘normal', or True-Type fonts.

DarkBASIC Professional has a number of text commands which can be found in the help file under the section

... wait for it ... Text Commands. Using these commands we can load in any font that is in the windows/fonts

directory and set its point size, set it to bold, underline or italic and we can change its colour. Other than that,

there is little else we can do to change the appearance of the fonts. If we want to make fonts like in the title of

this tutorial, that look like they are made of metal, fire and neon lights, or indeed any appearance other than

plain, single coloured text then we need to use bitmap fonts.

So, what are Bitmap fonts? A bitmap font is a collection of images that represent text characters, and because

they are images they can be made to look as graphically pretty as we like. The advantage of using them is, of

course, the ‘prettiness'. The disadvantage is that they are a little trickier to set up and use but don't worry, this

tutorial (and the accompanying code) will make things much easier for you.

There are two ways we could display bitmap fonts.

1. Using Sprites

2. 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.

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. So, you see, it is a little more complicated

than a simple TEXT or PRINT command to use true-type fonts but I

think you will agree; the end results are worth it.

Anyway, those four steps:

1) Load the font file into DBP

2) Cut the fonts into single character images

3) Trim the wasted space to the left and right of each character.

4) Get them on screen

Depending on which bitmap fonts you are using, it is not always

necessary to go through step 3. 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', which is why all my fonts use the trimming

method.

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, then skip ahead to the end of this tutorial where I will

tell you the basics of how to use them.

You still here? In that case, you obviously want to know how this all works. So, let's look at those four steps in

a little more detail.

Page 102: Darkbasic Guide= Dbproguide

101

Step 1: Loading the font file.

The font file is a simple image file. So, to load it, all we have to do is use the LOAD IMAGE command.

Step 2: Cutting up the fonts into single images.

The best method of doing this is to use memblocks so that we can retain any transparency in the image. Firstly

transfer the font image into a memblock, then copy an area of the memblock into a second one and make an

image of that. Repeat those steps until all the characters have been made.

You can see how this is done if you download the code but don't worry if you don't understand memblocks. 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 ... brilliant!

Step 3: Trimming the wasted space from each side of the characters.

Again we will use memblocks for this and look at each character in turn. We can look at each column of pixels

from the left side and if there are no non-transparent pixels, we can safely say that we have found our first bit

of wasted space. We continue with that until we do hit a non-transparent pixel and then repeat from the right.

Instead of actually trimming the image file down, 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; 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. 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.

Step 4: Displaying the fonts.

To display the fonts, we need to find the next available sprite number, create a sprite for each character, store

the sprite number for the first character and store the length of the string. 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. It sets up variables and arrays ready for use

in the other Bitmap font functions.

fontStyle = LoadBMfont( filename )

This function loads a Bitmap font, cuts the individual images and trims

the waste space from the edges.

fontStyle is a variable that you can use to reference a Bitmap style,

which is very helpful if you load in more than one Bitmap font.

stringID = DrawBMfont( X, Y, string, fontStyle, Kern )

Used to display the Bitmap font on screen.

stringID is a reference to the Bitmap string. You can use this reference

to show, hide etc. the entire string in one go without having to

Page 103: Darkbasic Guide= Dbproguide

102

reference each sprite image individually. X & Y are the screen co-ordinates of the top left of the string.

String is the string to display on screen. fontStyle is the reference to the previously loaded Bitmap font.

Kern establishes how much space to leave between each character.

hideBMfont( BMstring )

showBMfont( BMstring )

scaleBMfont( BMstring, scale )

sizeBMfont( BMstring, sizeX, sizeY )

The above functions will use the associated sprite commands on all of your bitmap string simultaneously.

positionBMfont( BMstring, X, Y )

This will position a previously created bitmap string at the X and Y screen co-ordinates specified.

alterBMFont(BMstring, X, Y, S as string, fontStyle, Kern )

This function will change any or all aspects of your bitmap font string.

BMstring is the ID of a previously created bitmap string.

X is the screen X co-ordinate to display the string.

Y is the screen Y co-ordinate to display the string.

S is the string to display. It doesn't have to be the same as it was, you can use this function to change the

contents. Ideal if you bitmap string is constantly changing (like an ingame score for example).

fontStyle As in the DrawBMfont function this variable is the style of Bitmap font to use and MUST already be

loaded. 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. Kern Again, Kern is used to establish the x distance between each character. This function can be

used to alter it.

Page 104: Darkbasic Guide= Dbproguide

103

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 - Space

Invaders. It's a lot more complex than Pong, but not too involved to cloud the tutorial with intricate

mathematical formulas and highly advanced techniques. It also covers a lot of the concepts we've studied over

the past few months, including asset management, timing and various other topics. There's no room for chit-

chat, so let’s get started!

Design, Design, Design! I cannot over-emphasise how important designing your game is before you start. Get it right now, and the rest

of the journey is far easier. Picking a game that is so familiar will help to illustrate the design process, as we

know what to expect. Everyone finds their own comfortable way of fleshing out the game idea, but right now

you'll have to go with mine.

Step 1 is to "brainstorm", "mindmap", or whatever the current buzzword happens to be. Here is a map of all

the components we need, in no particular order of importance, but in a way that will aid us as we progress.

This map contains concepts, game components, methods, and resources. Icons help me identify the different

aspects. This is far from structured, but all of the ideas are thrown onto the page for reference. It's also

important to visualise the look and feel of the game too. With Space Invaders it's not difficult, but it will still

help to ascertain positioning and style.

Page 105: Darkbasic Guide= Dbproguide

104

Structure Maintainability is a very important notion, and once again it is important to get this right at the start of the

programming process, not part way through. Rather than delve into the details of good programming practise,

we'll dive straight in and see it in action. Functions are the perfect partner when writing making it easy to read,

and easy to maintain. Subroutines also have their place, but functions have one major advantage - they are

self-contained. Later on down the line, when things don't go to plan (an inevitable fact of programming), being

able to isolate individual functions to debug will dramatically improve your ability to locate, fix and move on

swiftly.

The flow of the program can be defined in functions. Let's start by thinking about the broader picture:

Initialise()

Menu()

PlayGame()

Exit()

We have our 4 broadest areas, now let's take one of these, PlayGame(), and break it down a little further:

InitialiseGame()

PlayLevel()

EndGame()

Again, let's break down InitialiseGame() to a third level:

LoadObjects()

PositionObjects()

LoadSounds()

InitialiseData()

And so we can proceed, taking each section, and breaking it down into its smaller components. It's very logical,

it matches the way the human mind processes information and it produces a very neat and easy-to-follow

process. This top-down approach makes designing on-the-fly very easy too. As we progress, wherever there is

a complex or repetitive routine, it can be added as another unit of code in the form of a function. At this point

in the process, it isn't necessary to create the complete structure, the design allows us to improvise as we

code. As we will see later, it is also a fantastic way to test ideas on a basic level, and build them up into fully-

fledged game pieces later.

Let's take the first step in writing the code. Download the tutorial file, install and view example 1. It is our

framework for the game. We will add much more as we progress. Note that the number of comments at this

stage outweigh the lines of actual code. It is essential that everything is well documented for your own benefit;

even the most finely tuned of minds need relieving of the mundane task of remembering, to allow the creative

process to evolve unhindered.

Page 106: Darkbasic Guide= Dbproguide

105

Game Data Behind the action of any game is a mass of data; object information, scores, lives, positions, and a great deal

more. When building business applications, it is the data that drives and feeds the application design. Likewise,

it is important that we maintain game information in an orderly and efficient manner.

Now is the time to put the data structures into place. We will take advantage of User Defined Types to collate

associated information, such as player and opponent variables. We will use arrays to make a scalable solution.

Scalability of the data is the key to allowing a game to be easily modified. This has been discussed in a previous

article, which may be worth reviewing at this point.

Once again, we could discuss the theories and concepts around data storage and manipulation ad infinitum,

but instead take a look at the Stage 2 example, which now includes the data types and arrays that we initially

require to build our game. We've accommodated the general operation, player, invaders, bases and everything

else we see the need to monitor and control.

Standing on 2 Feet

We still have a long way to go before there's anything visible to show for our efforts. But believe me, all of this

effort will be worthwhile in the long run. What we need to do now is start the application breathing. We

already know where to start, the Init() function is in our framework and that is the focus of our attention now.

Let's set up the environment (Sync rate, screen size etc), and put some default data in there for the player and

invaders. Putting the data in the initialisation routine makes it extremely easy to locate later when the game

needs adjusting and fine-tuning. Stage 3 includes all of these components, and also a static backdrop which

will be a permanent feature of the end product. Remember, the initialisation isn't necessarily finished right

now, but only time will reveal what else we require.

At this stage, the program can be compiled to prove the code is syntactically correct. The logic can also be

tested to a certain extent, by running the compiled program. The result should be a simple backdrop, ready to

complement the game to come.

The Story So Far The amount of effort to date, in relation to the observable rewards are poor to say the least. But in terms of

the total effort required to complete our Space Invaders tribute, the task is well under way. Our delayed

gratification will be rewarded with a fast transition from very little to a fully fledged, easy to maintain game in

a very short space of time. Let's recap on the steps taken so far:

Page 107: Darkbasic Guide= Dbproguide

106

Design - The game idea, components, supporting structures and data have been mapped out in visual form.

Visuals - There is a rough draft of the way the game will be presented, including gameplay area, scoring system, player, enemies, and bases.

Program Structure - The game flow has been mapped out into the initial layers of a well-structured program. Coding the game will follow a natural, logical progression, with navigation and expansion of the code simply and effectively implemented.

Data Structure - The supporting data components are not only in place, but populated with the raw information needed to start the engine rolling slowly forwards.

The Plan Ahead What is next in the creation of our game? Visually, we need to build our player, enemies, bases and interface,

our presentation to the player. Programmatically, it's time to put the logic in place that will allow a simple

progression, bringing to life each part of the game one by one. We'll be using a method that provides the

flexibility to test each component in turn, ensuring the development is smooth and precise at every step.

In the meantime, the complete, compiled version of the program is included in the download to give you a

taste of what we will achieve in the forthcoming instalments.

Building Blocks Having put our framework into place, we will now start to build the game itself. Solid, working logic is the

prime aim at this point, proving that our concept is workable and codable. To this end, we will not be adding

fantastic, well polished models, astounding sounds and out-of-this world effects. We will, however, bear in

mind that this is the ultimate goal, and will create the code in a way that allows us to replace simple building

blocks with the finished article at a later stage.

Rapid Development

As discussed in the competition programming article, building your game from the anchor-point of a menu is

good practise. We will use a simple graphic and add a couple of keyboard-driven options. In our well-

structured functionalised program, I know we already have a menu() function ready and waiting. In fact, it's

already working, it simply doesn't do anything visible or useful just yet.

Stage 4 shows our menu in place, albeit in draft form. Study this small piece of the game carefully, and you will

understand how the rest of the game will be built up part by part. The options are very easily added by

directing the program to the relevant function. Notice especially that our game is already working, and adding

the menu is so effortless. Our previous hard work is already paying off, and we are coding in self-contained

functions that are unaffected by anything else. Our top-down approach facilitates the calling of different parts

of the overall application, and simply continuing to run as normal on the return.

Page 108: Darkbasic Guide= Dbproguide

107

Option 1, playing the game, does nothing but redirect to the currently empty GamePlay() function and return

again. A small delay has been added for effect at this stage. The menu is simply but effectively excluded from

the screen, and included again afterwards. Option 2, ending the application, similarly calls the deinitialisation

function and exits in the simplest way possible.

Create and Destroy Now we are really moving. It's time to create our game objects and place them on the screen. At the same

time, the code to destroy the objects will also be written. 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, as set out

in the original framework. We simply have to add the relevant code. Stage 5 does exactly this. Open the

provided example and compare the 2 functions, noticing that everything that is created is also destroyed. We

are using the data set up earlier in the invader arrays and player variables to quickly generate and position the

objects. Each object has also been introduced into the collision system.

Again, the program is simply ready to compile and run, with no additional effort. The most obvious

observation is that at the moment, we have only basic boxes representing the objects. Remember, our aim at

this stage is to prove everything fits into place. We could, if necessary, return now to the Initialisation function

and make adjustments to the positioning of the various entities quickly and with very little effort.

In just a few minutes, the world has been populated with the game objects.

Adding a state machine The actual game itself is a collection of actions and reactions. Depending on the current "state", each action

will or will not happen. For example, when the player is alive, the movement action can happen; when the

player is dead, it can't. By creating what is known as a Finite State Machine, we can very simply implement a

structure where every action is called at the right time. At the end of the state (or cycle), the decision is made

to stay in the same state, or switch to another one, triggered by the events that have taken place. Some states

may last for some time, while others may come and go in an instant.

This system isn't as difficult to implement as it may seem; in fact there is a programmatic method for

implementing this method - the SELECT/CASE clauses. 8 states have been identified:

Game Start

Page 109: Darkbasic Guide= Dbproguide

108

Level Start

Player Alive

Player Dead

Life End

Life Begin

Level End

Game End

In each state, the function calls to the necessary actions are placed. For example, when the player is alive we

must checkKeys() for player input, and checkInvaderBullets() for a hit. We must also animateInvaders(), which

will also happen when the player is dead and also when the level is starting. MoveInvaders() is unique to this

state however. As you can see, we can quickly build up a picture of what needs to happen and when.

Stage 6, which you can open and review, has this arrangement added to the GamePlay() function. In order to

make this complete, a further level of functions have been added to our Top-Down design. The functions

aren't all populated with code just yet, but bringing the game to life will once again be simple and methodical.

The naming of the functions also make the flow easy to follow.

The moveInvaders() function is actually populated, to give you a feel for this process. You can follow the state

from the game starting and initialising a few variables, to the level starting, initialising more data and waiting a

few seconds to start the gameplay. Then, the "Alive" state is reached, the moveInvaders() function is triggered

and the game starts to move!

Right now, we have the simple beginnings of the game. The engine is moving from state to state. Actions,

although very sparse, are being triggered. Most importantly, this skeleton engine can now be easily populated

with more actions.

The Plan Ahead Everything is now in place to build our game features, and to build them rapidly. In the next instalment, all the

various components, from movement, to collision detection, invader weaponry and sound effects will be

literally dropped into place. At each stage, we will be able to compile and immediately see the results, even

though the full game is not completed.

Big Changes, 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, is the speed at which it comes together in the final stages. It can inspire new features

and functionality, simply because the process is now so simple!

Hopefully the pattern we are following is becoming clearer, and big changes will happen fast now. Work

through the following stages, and investigate the changes at each step. Each stage is much more densely

populated with actions, including acting on keypresses and firing at the invaders. Don't worry about the

intricacies of each action, but study how the state engine has been built up using functions, and especially how

each function is designed to act solely on one cycle of the program. The engine does the cycling, the actions

react to one moment in time. Note also that the results of actions change the state of the engine.

Page 110: Darkbasic Guide= Dbproguide

109

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,

Page 111: Darkbasic Guide= Dbproguide

110

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

Conclusion 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 self-

contained 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.

Page 112: Darkbasic Guide= Dbproguide

111

Zork Tutorial - Part One

by Pluto

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 "West of House"

DATA "North of House"

DATA "Behind House"

DATA "South of House"

DATA "Kitchen"

DATA "Living Room"

DATA "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:

Page 113: Darkbasic Guide= Dbproguide

112

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 "West of House"

DATA "North of House"

DATA "Behind House"

DATA "South of House"

DATA "Kitchen"

DATA "Living Room"

DATA "Attic"

REM PLAYER STARTING LOCATION

PLR_LOC = 1; 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, 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. For instance, the player cannot go east if he is at the 'west of house' location. How

does the program know this? Simple, you tell it!

One thing to remember about Zork, the original designers were not consistent with their directions. In the real

world, if you take five steps due north, you can take five steps due south to get back to where you started

from. This is not so in Zork. In many cases the player can go north but cannot go south to get back! It is

strange, but that's the land of Zork.

We will now define 6 directions of travel for each location on the map. The six directions are, north, south,

east, west, up and down.

Let us start with north.

We create a north array that will have a north number for each of the 7 map locations. This north number tells

us if the player is allowed to move north from that location.

Page 114: Darkbasic Guide= Dbproguide

113

DIM NORTH(7)

FOR I = 1 TO 7

READ NORTH(I)

NEXT I

DATA 2,0,2,0,0,0,0

So, if the player is at location 1, we can look up NORTH(1).

If NORTH(1) = 2, then that means the player is allowed to move north and the player's new location is 2, which

happens to be 'North of House'

NORTH(1) can equal any of the 7 map locations. It's just a number that tells us what the player's new location

is that he just moved to. 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'.

So let's see how this would work.

The player is at location 1 which is 'west of house'. He decides to go north. Now we look at the NORTH array to

see if he is allowed.

What is NORTH(PLR_LOC) equal to? Well, the PLR_LOC variable is equal to 1, and that is the player's location.

So, NORTH(PLR_LOC) is the same as NORTH(1). NORTH(1) is equal to what? What number did we assign

NORTH(1) in our array?

We assigned it the number 2. So, NORTH(1) is equal to 2. That means the player is allowed to move north and

we assign the player's new location as 2. Here is the code that does that:

PLR_LOC = NORTH(PLR_LOC)

Now that we know how to define the map movement, let us add the rest of the arrays for the other directions.

REM SOUTH PATHS FOR EACH LOCATION

DIM SOUTH(7)

FOR I = 1 TO 7

READ SOUTH(I)

NEXT I

DATA 4,0,4,0,0,0,0

REM EAST PATHS FOR EACH LOCATION

DIM EAST(7)

FOR I = 1 TO 7

READ EAST(I)

NEXT I

DATA 0,3,0,3,3,5,0

REM WEST PATHS FOR EACH LOCATION

DIM WEST(7)

FOR I = 1 TO 7

READ WEST(I)

NEXT I

DATA 0,1,5,1,6,0,0

Page 115: Darkbasic Guide= Dbproguide

114

REM UP PATHS FOR EACH LOCATION

DIM UP(7)

FOR I = 1 TO 7

READ UP(I)

NEXT I

DATA 0,0,0,0,7,0,0

REM DOWN PATHS FOR EACH LOCATION

DIM DOWN(7)

FOR I = 1 TO 7

READ DOWN(I)

NEXT I

DATA 0,0,0,0,0,0,5

The next step is to put this movement into our main loop. Before we enter the main game loop we should

display the player's position on the screen:

PRINT LOCATION$(PLR_LOC)

Now, let's code the main game loop.

REM MAIN GAME LOOP

DO

REM GET PLAYER COMMAND

INPUT "> ", 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. The next step

calls a subroutine to parse those instructions, and do whatever the instructions tell us.

These two steps are the core of the game engine. Since we are working with map and movement in this part of

the tutorial, the commands will be kept simple. I will cover the parsing, more complicated command sentences

in a later part of the tutorial. For right now, the commands we will accept are 'n' - north, 's' - south, 'e' - east,

'w' - west, 'u' - up, 'd' - down, and 'l' - look.

Here is the PARSE subroutine. Look at the first condition, which is 'n'. If the player enters the letter n, the IF

statement checks the NORTH(PLR_LOC) variable. 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. If the player cannot move north then the PLR_MOVE variable does

not get changed and remains equal to 0. Since PLR_MOVE = 0 we will return a message telling the player he

cannot go that direction.

Page 116: Darkbasic Guide= Dbproguide

115

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."

ENDIF

RETURN

REM END SUB

So, now you know how to code the map locations, and the player movement. I have included the full code for

this part of the tutorial. 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. This is not needed but helps keep the screen from getting too cluttered.

Page 117: Darkbasic Guide= Dbproguide

116

Remember, the best way to learn is to play with this code. Change some of the north numbers or other

directional numbers. You can make your own paths for the player if you choose to. Currently, this code follows

the same movement as the original Zork I game. In the next tutorial I will show you how to create objects and

player inventory.

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 "West of House"

DATA "North of House"

DATA "Behind House"

DATA "South of House"

DATA "Kitchen"

DATA "Living Room"

DATA "Attic"

REM PLAYER STARTING LOCATION

PLR_LOC = 1; REM LOCATION NUMBER 1 = WEST OF HOUSE

REM DIRECTIONS

REM NORTH PATHS FOR EACH LOCATION

DIM NORTH(7)

FOR I = 1 TO 7

READ NORTH(I)

NEXT I

DATA 2,0,2,0,0,0,0

REM SOUTH PATHS FOR EACH LOCATION

DIM SOUTH(7)

FOR I = 1 TO 7

READ SOUTH(I)

NEXT I

DATA 4,0,4,0,0,0,0

REM EAST PATHS FOR EACH LOCATION

DIM EAST(7)

FOR I = 1 TO 7

READ EAST(I)

NEXT I

DATA 0,3,0,3,3,5,0

REM WEST PATHS FOR EACH LOCATION

DIM WEST(7)

FOR I = 1 TO 7

READ WEST(I)

Page 118: Darkbasic Guide= Dbproguide

117

NEXT I

DATA 0,1,5,1,6,0,0

REM UP PATHS FOR EACH LOCATION

DIM UP(7)

FOR I = 1 TO 7

READ UP(I)

NEXT I

DATA 0,0,0,0,7,0,0

REM DOWN PATHS FOR EACH LOCATION

DIM DOWN(7)

FOR I = 1 TO 7

READ DOWN(I)

NEXT I

DATA 0,0,0,0,0,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 "> ", 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

Page 119: Darkbasic Guide= Dbproguide

118

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:

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

Page 120: Darkbasic Guide= Dbproguide

119

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."

ENDIF

RETURN

REM END SUB

UPDATE I

There is a more convenient method of programming the map locations and directions. It would be nice if we

could group all the location data together. As IanM mentioned, it makes for less arrays and changing or adding

locations is more convenient. This is not easily done in older BASIC languages, but as it turns out there is a nice

way to do this in DBPro. You use typed arrays. Basically, what you will do is create your own type. The good

thing about this is that your type definition can be made up of several variables.

So here is what our type definition might look like:

Page 121: Darkbasic Guide= Dbproguide

120

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, and we can make an array of Locs, just like you would make an array of

integers or strings.

dim loc_array(7) as Loc

Next we read in all the grouped data:

for i = 1 to 7

read loc_array(i).loc_name

read loc_array(i).north

read loc_array(i).south

read loc_array(i).east

read loc_array(i).west

read loc_array(i).up

read loc_array(i).down

next i

data "West of House", 2,3,0,0,0,0

data "North of House", 0,2,0,7,0,0

data "East of House", 0,0,3,0,0,0

data "South of House", 2,2,0,0,0,0

data "Kitchen", 0,0,0,4,0,0

data "Trophy Room", 0,1,0,0,0,0

data "Attic", 0,0,0,0,0,5

Let's look at the first location of the data:

data "West of House", 2,3,0,0,0,0

This is location #1 so if we were to print the location name:

PRINT loc_array(1).loc_name

It would print 'West of House'. If we were to print the north number we would code:

PRINT loc_array(i).north

This would display the #2. Again, let's look at the data statement:

data "West of House", 2,3,0,0,0,0

Those numbers following 'West of House' represent north, south, east, west, up, and down.

Thus:

Page 122: Darkbasic Guide= Dbproguide

121

2,3,0,0,0,0 is north=2, south=3, east=0, west=0, up=0, down=0

So, at the 'west of house' location if the player goes north he will move to location #2. If player goes south he

will move to location #3. The player cannot go east, west, up or down because those directions are equal to 0.

Let's look at that data one more time. Each location has the location name and the directional numbers.

data "West of House", 2,3,0,0,0,0

data "North of House", 0,2,0,7,0,0

data "East of House", 0,0,3,0,0,0

data "South of House", 2,2,0,0,0,0

data "Kitchen", 0,0,0,4,0,0

data "Trophy Room", 0,1,0,0,0,0

data "Attic", 0,0,0,0,0,5

Now, all our location data is in one place so we don't have to scroll through too many arrays to make any

changes. Here is a small code snippet that you can run and it prints the location from the typed array we just

made. I will change the program code by replacing the simple arrays we used in the beginning with this typed

array.

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 = 1 to 7

read loc_array(i).loc_name

read loc_array(i).north

read loc_array(i).south

read loc_array(i).east

read loc_array(i).west

read loc_array(i).up

read loc_array(i).down

next i

data "West of House", 2,2,0,0,0,0

data "North of House", 0,2,0,7,0,0

data "East of House", 0,0,3,0,0,0

data "South of House", 2,2,0,0,0,0

data "Kitchen", 0,0,0,4,0,0

data "Trophy Room", 0,1,0,0,0,0

data "Attic", 0,0,0,0,0,5

print loc_array(1).loc_name

print loc_array(1).east

do

sync

loop

Page 123: Darkbasic Guide= Dbproguide

122

Of course, there are even more variations of this as well and I'm not going to go into everyone of them here. 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.

Another thing to mention is the number of locations. We know it is 7 but what if we add another location?

Then we have to find every array and change the number. Instead of doing that, create a constant variable

such as MAX_LOC = 7, and change array(7) to array(MAX_LOC). Now, anytime you want to add a location you

only have to change the MAX_LOC constant one time.

I will have more updates to this part I soon. I will add some more shine to it. Then we will move on to part II.

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." This is fine but you can make it more interesting if

you tell the player why he can't go that way. One message might be, 'The bridge is out.' In Zork you will find

that the game is sprinkled with such messages, so we will include those messages in our program.

If you'll recall, we used an array to look up the directional number. If it was equal to 0 then that told us that

direction was off limits. If it was a positive number then the player's location would change to that number, a

new location. We can expand on this idea and use negative numbers to represent different messages telling

the player he can't go that way. For instance:

0 = 'You can't go that way.'

-1 = 'The door is nailed shut.'

-2 = 'The gate is locked'

etc...

Zero is still the generic message of 'You can't go that way.' So, our array data may look something like this:

data 0,-2,2,0,3,1,4

or a typed array could be:

data "North of House", 0,-2,3,1,0,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. 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, let us put it into action. 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."

DATA "The door is boarded and you can't remove the boards."

Page 124: Darkbasic Guide= Dbproguide

123

DATA "The windows are all boarded."

DATA "The door is nailed shut."

The messages are the same as Zork I, around the house area.

Notice the messages will be represented in numeric order like this:

NOGO$(0) - "You can't go that way."

NOGO$(1) - "The door is boarded and you can't remove the boards."

NOGO$(2) - "The windows are all boarded."

NOGO$(3) - "The door is nailed shut."

These are our 4 messages.

We use the number 0 and negative numbers to identify them.

0, -1, -2, -3

We have to change these to positive numbers after we get them so that we can match them up to the NOGO$

messages.

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.'

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. So, ABS(-3) is the same as 3.

Now that we have a positive number we can use it on our message array to print the matching message.

First we use the ABS function:

So, if NORTH(1) = -3

MESSAGE_INDEX = ABS( NORTH(1) )

Same as:

MESSAGE_INDEX = 3

Now, we can use it as our index for the message array NOGO$

PRINT NOGO$( MESSAGE_INDEX )

Page 125: Darkbasic Guide= Dbproguide

124

Is the same as:

PRINT NOGO$( 3 )

Remember what message NOGO$(3) is? Let's list them:

NOGO$(0) - "You can't go that way."

NOGO$(1) - "The door is boarded and you can't remove the boards."

NOGO$(2) - "The windows are all boarded."

NOGO$(3) - "The door is nailed shut."

This concludes this part of the tutorial, as I will not make any new updates for this first part. If you are having

trouble with this part of it don't worry, you can always just use the standard message in the original program.

Of course you may think of other ways to make them yourself if you choose.

I have added the final program, complete for PART I of the tutorial. It includes the message codes and I have

replaced the simple location arrays with typed arrays. I have included lots of comments to help make it clear.

You will notice that the code has not changed that much, so you know most of it already and the rest of it is

pretty simple.

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).LOCATION

read loc_array(i).NORTH

read loc_array(i).SOUTH

read loc_array(i).EAST

read loc_array(i).WEST

read loc_array(i).UP

read loc_array(i).DOWN

next i

REM LOCATION N S E W U D - NORTH SOUTH EAST WEST UP DOWN

data "West of House", 2,4,-1,0,0,0

Page 126: Darkbasic Guide= Dbproguide

125

data "North of House", 0,-2,3,1,0,0

data "East of House", 2,4,0,5,0,0

data "South of House", -2,0,3,1,0,0

data "Kitchen", 0,0,3,6,7,0

data "Living Room", 0,0,5,-3,0,0

data "Attic", 0,0,0,0,0,5

REM PLAYER STARTING LOCATION

PLR_LOC = 1; 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 - 3

DATA "You can't go that way."

DATA "The door is boarded and you can't remove the boards."

DATA "The windows are all boarded."

DATA "The door is nailed shut."

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 "> ", 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).LOCATION

RETURN

REM END GOSUB

Page 127: Darkbasic Guide= Dbproguide

126

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

Page 128: Darkbasic Guide= Dbproguide

127

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

Page 129: Darkbasic Guide= Dbproguide

128

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'

Page 130: Darkbasic Guide= Dbproguide

129

You will notice that I placed the leaflet at a different location, #2 which is 'north of house'

To display each object we would code a print statement with the array name. For instance, to display the name

of the first object, the 'small mailbox' we would code this:

PRINT OBJ_ARRAY(1).NAME

To display the location number of the leaflet we would code:

PRINT OBJ_ARRAY(2).LOCATION

Let's try it. Here is the code to print object information (attributes). 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).NAME

read OBJ_ARRAY(i).LOCATION

next i

REM NAME LOCATION

data "small mailbox", 1 : rem Located at west of house

data "leaflet", 2 : rem not on the map yet (hidden inside mailbox

PRINT OBJ_ARRAY(1).NAME

PRINT OBJ_ARRAY(2).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.

We will display the name of the object right after the location and the description. So it will look something

like this:

West of House

You are standing in an open field west of a white house, with a boarded front door.

There is a small mailbox here.

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. We will create a for-next loop which will look at

Page 131: Darkbasic Guide= Dbproguide

130

each object one at a time starting with object #1. If the location of the object = location of player we will

display the name of the object. Thus:

IF OBJ_ARRAY(1).LOCATION = PLR_LOC

Next, print the name of the object:

PRINT OBJ_ARRAY(1).NAME

Instead of just displaying the name of the object we can spice it up a bit. For instance, in Zork I, it usually says:

'There is a small mailbox here.'

So, we will add those words too:

PRINT "There is a " + OBJ_ARRAY(I).NAME + " here."

Now, 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).LOCATION = PLR_LOC

PRINT "There is a " + OBJ_ARRAY(I).NAME + " here."

ENDIF

NEXT I

I have added all the above code into the game. Take a look at it and you will see it is a simple addition.

Run this code and you will see the mailbox at the starting location. If you move north, you will see the leaflet.

We will discuss player inventory in the next update. Have fun.

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

Page 132: Darkbasic Guide= Dbproguide

131

REM POPULATE THE ARRAY WITH MAP LOCATION DATA

for i = 1 to MAX_LOC

read LOC_ARRAY(i).LOCATION

read LOC_ARRAY(i).NORTH

read LOC_ARRAY(i).SOUTH

read LOC_ARRAY(i).EAST

read LOC_ARRAY(i).WEST

read LOC_ARRAY(i).UP

read LOC_ARRAY(i).DOWN

next i

REM LOCATION N S E W U D - NORTH SOUTH EAST WEST UP DOWN

data "West of House", 2,4,-1,0,0,0

data "North of House", 0,-2,3,1,0,0

data "East of House", 2,4,0,5,0,0

data "South of House", -2,0,3,1,0,0

data "Kitchen", 0,0,3,6,7,0

data "Living Room", 0,0,5,-3,0,0

data "Attic", 0,0,0,0,0,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).NAME

read OBJ_ARRAY(i).LOCATION

next i

REM NAME LOCATION

data "small mailbox", 1 : rem Located at west of house

data "leaflet", 2 : rem not on the map yet (hidden inside mailbox

REM PLAYER STARTING LOCATION

PLR_LOC = 1; 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 - 3

DATA "You can't go that way."

DATA "The door is boarded and you can't remove the boards."

DATA "The windows are all boarded."

DATA "The door is nailed shut."

Page 133: Darkbasic Guide= Dbproguide

132

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 "> ", 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).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,

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"

Page 134: Darkbasic Guide= Dbproguide

133

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 DISPLAY OBJECTS

DISPLAY_OBJECTS:

REM LIST ANY OBJECTS AT PLAYER LOCATION

FOR I = 1 TO MAX_OBJ

IF OBJ_ARRAY(I).LOCATION = PLR_LOC

PRINT "There is a " + OBJ_ARRAY(I).NAME + " here."

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).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

Page 135: Darkbasic Guide= Dbproguide

134

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

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

Page 136: Darkbasic Guide= Dbproguide

135

RETURN

REM END SUB

UPDATE I

INVENTORY

As you learned earlier, objects have attributes such as what their name is and where they are located. We will

add another attribute to our objects. This attribute lets us know if the object is with the player. The number 0

tells us that the object is not with the player. The number 1 tells us that the player has the object in his

inventory. So, let's add that to the object array that we created earlier.

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.

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).NAME

read OBJ_ARRAY(i).LOCATION

read OBJ_ARRAY(i).INVENTORY

next i

Now we need to add either a 1 or a 0 for each object, a 0 if object is not with player and a 1 if object is.

For the mailbox we will assign it a 0 and the leaflet a 1, so the player will be carrying the leaflet. Here is the

original object data, with NAME and LOCATION attribute.

REM NAME, LOCATION

data "small mailbox", 1

data "leaflet", 0

Now, here it is after we add the third attribute of INVENTORY.

REM NAME, LOCATION, INVENTORY

data "small mailbox", 1, 0

data "leaflet", 0, 1

Page 137: Darkbasic Guide= Dbproguide

136

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).NAME

read OBJ_ARRAY(i).LOCATION

read OBJ_ARRAY(i).INVENTORY

next i

REM NAME, LOCATION, INVENTORY

data "small mailbox", 1, 0

data "leaflet", 0, 1

We still only have two objects but now each object has three attributes. You can create any attribute like this,

it's that easy. For instance, if you wanted to add weight to objects you could make another number which

would represent how much the object weighs. You could call it the WEIGHT attribute.

Now that we have added the INVENTORY attribute we will add a new command to the parser. Whenever the

player enters the letter 'i' we will display whatever he is carrying in his inventory. 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. So we would code:

IF OBJ_ARRAY(I).INVENTORY = 1

Then

PRINT OBJ_ARRAY(I).NAME

If 1 then print object name, pretty simple. 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. Like this:

FOR I = 1 TO MAX_OBJ

IF OBJ_ARRAY(I).INVENTORY = 1

PRINT OBJ_ARRAY(I).NAME

ENDIF

NEXT I

And if the player is not carrying anything? In that case we will reply with the standard Zork reply:

Page 138: Darkbasic Guide= Dbproguide

137

'You are empty-handed.'

To do this we can make a counter variable named INV_CNT. Each time we find an object in the player's

inventory we simply add 1 to the INV_CNT.

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, only starting with the first object. Once we print that message then we print

the name of the object. The remaining objects in the inventory will be printed below that, one at a time. So, it

would look like this:

You are carrying:

leaflet

sword

bottle

So, let's look at the code:

First check the object to see if it's in player inventory:

IF OBJ_ARRAY(I).INVENTORY = 1

If it is, then increment the INV_CNT (add 1 to INV_CNT)

INC INV_CNT

Next, check to see if this is the first object and display the 'You are carrying:' message. We will know if it's the

first object because the INV_CNT will equal 1.

IF INV_CNT = 1 THEN PRINT "You are carrying:"

Next display the name of the object.

PRINT OBJ_ARRAY(I).NAME

After we finish checking every object and we don't find any in the inventory then we display the 'You are

empty handed.' message. If the INV_CNT is 0 then there is nothing in inventory, so that's the variable we need

to check:

IF INV_CNT = 0

PRINT "You are empty-handed."

Now, 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).INVENTORY = 1

Page 139: Darkbasic Guide= Dbproguide

138

INC INV_CNT

IF INV_CNT = 1 THEN PRINT "You are carrying:"

PRINT OBJ_ARRAY(I).NAME

ENDIF

NEXT I

IF INV_CNT = 0

PRINT "You are empty-handed."

ENDIF

RETURN

ENDCASE

Now, I'm going to add a simple TAKE and DROP command to the parser. You will only be able to take and drop

the leaflet for now.

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, but that will have to wait till later. Once we have a working Zork parser we will be able to add the full

TAKE and DROP commands for all objects. What you learn here will put to use when we get the full parser

going.

So what do we have to change when the player takes the leaflet?

Well, we need to change the leaflet INVENTORY attribute from 0 to 1, because the 1 means the player has the

object. But we also need to remove the object from the ground at wherever the player is located. So we have

to change the object's LOCATION attribute from a 1 to a 0.

The leaflet object is object #2. So we need to code:

OBJ_ARRAY(2).INVENTORY = 1 i.e - in player's inventory now

OBJ_ARRAY(2).LOCATION = 0 i.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, 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, 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).LOCATION = PLR_LOC

OBJ_ARRAY(2).INVENTORY = 1

OBJ_ARRAY(2).LOCATION = 0

PRINT "Taken."

ELSE

PRINT "You can't see any leaflet here!"

ENDIF

RETURN

ENDCASE

Now, 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.

Here is the full code of everything we have covered so far. Run this code and go north and take the leaflet.

Page 140: Darkbasic Guide= Dbproguide

139

Now look at your inventory 'i' command and you will see it. Try dropping it somewhere and enter the look

command 'l' to see if the leaflet you dropped is there.

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).LOCATION

read LOC_ARRAY(i).NORTH

read LOC_ARRAY(i).SOUTH

read LOC_ARRAY(i).EAST

read LOC_ARRAY(i).WEST

read LOC_ARRAY(i).UP

read LOC_ARRAY(i).DOWN

next i

REM LOCATION N S E W U D - NORTH SOUTH EAST WEST UP DOWN

data "West of House", 2,4,-1,0,0,0

data "North of House", 0,-2,3,1,0,0

data "East of House", 2,4,0,5,0,0

data "South of House", -2,0,3,1,0,0

data "Kitchen", 0,0,3,6,7,0

data "Living Room", 0,0,5,-3,0,0

data "Attic", 0,0,0,0,0,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

Page 141: Darkbasic Guide= Dbproguide

140

REM POPULATE THE ARRAY WITH OBJECT DATA

for i = 1 to MAX_OBJ

read OBJ_ARRAY(i).NAME

read OBJ_ARRAY(i).LOCATION

read OBJ_ARRAY(i).INVENTORY

next i

REM NAME, LOCATION, INVENTORY

data "small mailbox", 1, 0

data "leaflet", 2, 0

REM PLAYER STARTING LOCATION

PLR_LOC = 1; 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 - 3

DATA "You can't go that way."

DATA "The door is boarded and you can't remove the boards."

DATA "The windows are all boarded."

DATA "The door is nailed shut."

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 "> ", 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

Page 142: Darkbasic Guide= Dbproguide

141

SYNC

LOOP

REM END MAIN LOOP

REM DISPLAY CURRENT LOCATION

CRNT_LOC:

PRINT LOC_ARRAY(PLR_LOC).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,

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 DISPLAY OBJECTS

DISPLAY_OBJECTS:

Page 143: Darkbasic Guide= Dbproguide

142

REM LIST ANY OBJECTS AT PLAYER LOCATION

FOR I = 1 TO MAX_OBJ

IF OBJ_ARRAY(I).LOCATION = PLR_LOC

PRINT "There is a " + OBJ_ARRAY(I).NAME + " here."

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).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

Page 144: Darkbasic Guide= Dbproguide

143

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).INVENTORY = 1

INC INV_CNT

IF INV_CNT = 1 THEN PRINT "You are carrying:"

PRINT OBJ_ARRAY(I).NAME

ENDIF

NEXT I

IF INV_CNT = 0

PRINT "You are empty-handed."

ENDIF

RETURN

ENDCASE

CASE "take leaflet"

IF OBJ_ARRAY(2).LOCATION = PLR_LOC

OBJ_ARRAY(2).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 = 1

OBJ_ARRAY(2).INVENTORY = 0

OBJ_ARRAY(2).LOCATION = PLR_LOC

PRINT "Dropped."

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

Page 145: Darkbasic Guide= Dbproguide

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, and that will be the subject of the next part of the tutorial. For now, play with the code, add a new

object or even a new location if you like.