QBASIC 5 - arrays and a simple database Perhaps one of the must useful functions a computer can carry out is that of a database. Storing a great long list of numbers, names or other information on a computer allows it to be easily sorted, searched, etc. It is so much more easy to do this than to mess about with lots of little bits of paper that it has pretty much revolutionised the way companies work. So, how do you feel about writing you own database? Okay, first we need to learn some new principles. Arrays are pretty much essential, and I'll also tell you a little bit about subroutines before going on to write a simple database for storing names and telephone numbers. Firstly, arrays. For those of you who just panicked as soon as you heard the word, don't. An array can be thought of as simply a list or table of values. Here's how you use an array from QBASIC: DIM a(100) This gives you 101 variables, numbered from a(0) to a(100). The really immensly useful bit about this is that you can specify which one to use with a variable, so to print a(30) you could use: n = 30 PRINT a(n) Not much use, but look at this: FOR n = 0 TO 100 PRINT a(n) NEXT This will print out every single value in the array. Now, is that useful or what? And of course, arrays can also be made up of strings: DIM a$(100) FOR n = 0 TO 100 PRINT a$(n) NEXT So, that's pretty much all there is to it. Except... what about tables, you might be thinking. A list is pretty good, but say you wanted to store which piece was on every square of an (8,8) chess board? Easy: DIM board(7, 7) This would mean that you have 64 different variables, accessed using board(0,0), board (1,0), board(2,0), etc, all the way up to board(7,7). In this way you can store the value of every piece on a chess board. You are not limited to two dimensions; you can have as many as you want, although bear in mind it is limited by space in memory: DIM cube(4, 4, 4) This given you a total of 125 (5*5*5) variables to play about with. Again, FOR...NEXT loops can be used to print out, or doing anything else with, all of the numbers in an array: DIM cube(4, 4, 4) FOR x = 0 TO 4 FOR y = 0 TO 4 FOR z = 0 TO 4 cube(x, y, z) = 1 NEXT NEXT NEXT Okay, so that about covers arrays. If this is still confusing you, as always try having a look at the QBASIC help file definition, and also try using the example program, which can be copied straight out of the help file into the editing window. Okay, now onto subroutines. Here is an example of a subroutine: a$ = "Hello World!" GOSUB print.message END print.message: CLS PRINT a$ PRINT a$ RETURN In this case, the subroutine clears the screen, prints a$ twice then returns. But how does it work? I think a line by line analysis will clear things up a bit. a$ = "Hello World!" This sets up the string a$ with the message that the subroutine will use. GOSUB print.message This GOes to the SUBroutine. When the subroutine ends with RETURN, the computer will carry onto the next command, which is: END you've not seen this often before, the reason being that the computer will stop anyway when it reaches the end of the file. However, as soon as you start to use subroutines this is important, otherwise the computer will get upset as it comes across a RETURN command when it wasn't expecting one. print.message: This is a label; a point in the program which doesn't actually do anything but which you can intruct the computer to go to at any point. There are two commands which do this: GOTO does not expect a RETURN command; it merely carries on from the specified label. GOSUB carries on until it gets a RETURN command, then it returns to just after the GOSUB command. CLS Clears the screen. PRINT a$ PRINT a$ Prints a$ twice. RETURN Returns the computer to whence it came; in other words it carries on from just after the GOSUB command. But, you might ask, what is the point of using a suibroutine in the example above? And the answer, of course is to show you how to use subroutines. Other than this, there are two reasons for using subrouties: if you want to do things several times from different parts of your program, and if you want to make your program look much neater and more structured. Both are pretty important. Anyway, onto the really important stuff. Here's a sample database program: DIM number(100) DIM name$(100) DIM address$(100) main.loop: CLS PRINT PRINT a$ = "1....Add number" GOSUB print.message a$ = "2....Search for number" GOSUB print.message a$ = "3....Search for name" GOSUB print.message a$ = "4....Search for address" GOSUB print.message a$ = "5....Exit" GOSUB print.message k$ = "" WHILE k$ = "" k$ = INKEY$ WEND IF k$ = "1" THEN GOSUB add.number IF k$ = "2" THEN GOSUB search.number IF k$ = "3" THEN GOSUB search.name IF k$ = "4" THEN GOSUB search.address IF k$ = "5" THEN GOSUB exit.program GOTO main.loop add.number: n = -1 FOR a = 1 TO 100 IF number(a) = 0 THEN n = a NEXT IF n = -1 THEN PRINT "Database full.": SLEEP: RETURN a$ = "Please enter a name? " GOSUB print.message INPUT "", name$(n) a$ = "Please enter an address? (NB: no commas)" GOSUB print.message INPUT "", address$(n) a$ = "Please enter the phone number? " GOSUB print.message INPUT "", number(n) IF number(n) = 0 THEN number(n) = -1 RETURN search.number: a$ = "Please enter a phone number?" GOSUB print.message INPUT "", Num n = -1 FOR a = 1 TO 100 IF number(a) = Num THEN n = a NEXT IF n = -1 THEN PRINT "No match found.": SLEEP: RETURN a$ = "Number" + STR$(Num) + " found." GOSUB print.message a$ = "Name: " + name$(n) GOSUB print.message a$ = "Address: " + address$(n) GOSUB print.message SLEEP RETURN search.name: a$ = "Please enter a name to search for?" GOSUB print.message INPUT "", n$ n = -1 FOR a = 1 TO 100 IF name$(a) = n$ THEN n = a NEXT IF n = -1 THEN PRINT "No match found.": SLEEP: RETURN a$ = "Name " + n$ + " found." GOSUB print.message a$ = "Number: " + STR$(number(n)) GOSUB print.message a$ = "Address: " + address$(n) GOSUB print.message SLEEP RETURN search.address: a$ = "Please enter an address to search for?" GOSUB print.message INPUT "", a$ n = -1 FOR a = 1 TO 100 IF address$(a) = a$ THEN n = a NEXT IF n = -1 THEN PRINT "No match found.": SLEEP: RETURN a$ = "Address " + a$ + " found." GOSUB print.message a$ = "Name: " + name$(n) GOSUB print.message a$ = "Number: " + STR$(number(n)) GOSUB print.message SLEEP RETURN exit.program: a$ = "Are you sure (y/n)?" GOSUB print.message k$ = "" WHILE k$ = "" k$ = INKEY$ WEND IF k$ = "y" THEN END IF k$ = "Y" THEN END RETURN print.message: p = 40 - (LEN(a$) / 2) FOR c = 1 TO p PRINT " "; NEXT PRINT a$ RETURN This is a pretty simplistic database which allows for searching but not sorting (although that's reasonable easy). It also does not allow the entire database to be displayed and has pretty poor presentation, but at least it shows the basic principles. I'm not going to give a line by line breakdown of this one because believe it or not I do have something of a life, and also I'm hoping that you will get reasonable good at working this sort of thing out for yourself. However, I'm not completely heartless and I realise that other people's programs often look like a load of gibberish, so I'll give you some pointers and explain the commands that you've not seen before. By the way, if you've just suffered typing that lot in, you might appreciate the idea of loading this text file into QBASIC and ismply stripping away the text around the program, leaving you free to enjoy it without the hassle of typing it in. Okay, first things first. Here is a rundown of the commands you've not seen before: SLEEP This is a really simple command which, used like this, simply waits for the user to press a key. If you put a number after it is waits for that number of seconds. WHILE...WEND In a sort of similar way to FOR...NEXT, the WHILE...WEND combination is immensly useful. Immitation is the best form of flattery, and the best way to learn is by immitation, so learn this and I'll feel thoroughly flattered: WHILE a = 0 WEND END This little bit of code will keep looping round until a=0, when it will carry on and get to the end statement, ending the program. Not impressed? Here is how it is used in the above program: k$ = "" WHILE k$ = "" k$ = INKEY$ WEND This is an immensly useful snippet of code that is used in virtually every QBASIC program in some form or another. It repeatedly uses INKEY$ to see if the user has pressed a key. When they have, it puts this result in k$. That's not the clever bit. The clever bit is that the WHILE...WEND loop then recognises that k$ no longer equals "", and stops looping. The result is that it waits until the user presses a key, and this keypress ends up in k$. Okay, to help you a bit more with your quest to understand how the heck the above program works, here are a few pointers: - The subroutine print.message is used to print A$ centred on the screen. Okay, it looks a bit of a mess, but the only reason for that is because I want you to try and tidy it up. - number() is an array which stores the phone numbers. Note that because of the system I use for determining where the next free variable is, the phone numbers start at number(100) and count downwards from there; in other words, only if you are storing 100 numbers will number(1) be used. - name$() is the array used to store the names inputted. Again, they start at the 100th variable in the array. - address$() stores the addresses, again starting at address$(100). - At the beginning of the add.number routines and the search routines there is a short piece of code which gets the position in the array of a free space, and stores it in n. If one is not found then n=-1 and the program reports database full. This is the bit of code which makes the information start at 100 and work down; see if you can work out why. Okay, have a go at working it out and improving the presentation (please!). There are some serious desing flaws with this version, including it's inability to sort the data or print out a full list, and the fact that you have to keep capitalisation the same otherwise it won't recognise that two strings are the same. This could be solved using the ucase$() or the lcase$() commands. Why not expand it to include more details, like different contact numbers, etc. There is no real limit to what a database written in QBASIC can do; the only limit is one speed, because QBASIC isn't exactly blisteringly fast. For a simple database like this one, however, it performs the job more than adequately. Some people may feel a little constrained by the 100-number limit of this program. Can you increase it? It is not a question of memory, but of dimensioning enough variables and making use of them. By the way, if you are having difficulty working out what a program does, try using the "Step" option from the Debug menu in QBASIC, of pressing F8. This executes (does) one line of your program at a time, so you can see exactly what the program is doing. If at any point you want to find out what a variable is, you can go into the immediate window and PRINT it out. By the way, instead of typing PRINT all the time a question mark (?) does exactly the same job. Anyway, by following through what the computer is doing, you can often work out how the program works. As always, don't feel stressed if you are finding any part of this program beyond comprehension; it's not a reflection on you, everyone finds programming (and especially other people's programming) difficult to pick up. I've been programming for a number of years now and still I find it difficult to analyse other people's programs. So stick with it, and come back to the bits you didn't understand when you get more confident. Anyway, good luck. By the way, I wondered if you'd yet managed to covert the game from last lesson to two players. If not, here's my attempt. You must have Num Lock on for the second player to be able to control themselves; their controls are 5 for up, 1 for left, 3 for right and 2 for down. And here it is: SCREEN 12 x = 300 y = 240 d = 3 xx = 340 yy = 240 dd = 1 INPUT "Please enter a skill level (1 is hardest)? ", s IF s = 0 THEN s = 1 CLS FOR n = 1 TO 29 IF n < 14 OR n > 18 THEN GOSUB print.stuff ELSE PRINT NEXT s = s * 5 main: c = c + 1 IF (c / s) = INT(c / s) THEN GOSUB move.player: GOSUB move.player2: k$ = "" IF k$ = "" THEN k$ = INKEY$ GOTO main print.stuff: b$ = "" skill = s + 15 FOR a = 1 TO 80 r = INT(RND * skill) + 1 IF r = 1 THEN b$ = b$ + "*" IF r = 2 THEN b$ = b$ + "x" IF r = 3 THEN b$ = b$ + "#" IF r = 4 THEN b$ = b$ + "@" IF r > 4 THEN b$ = b$ + " " NEXT PRINT b$; RETURN move.player: IF k$ = "q" AND d <> 2 THEN d = 0 IF k$ = "a" AND d <> 0 THEN d = 2 IF k$ = "o" AND d <> 1 THEN d = 3 IF k$ = "p" AND d <> 3 THEN d = 1 IF d = 0 THEN y = y - 1 IF d = 1 THEN x = x + 1 IF d = 2 THEN y = y + 1 IF d = 3 THEN x = x - 1 IF x = 640 THEN x = 1 IF y = 480 THEN y = 1 IF y = 0 THEN y = 479 IF x = 0 THEN x = 639 IF POINT(x, y) > 0 THEN GOTO crash PSET (x, y), 15 RETURN move.player2: IF k$ = "5" AND dd <> 2 THEN dd = 0 IF k$ = "2" AND dd <> 0 THEN dd = 2 IF k$ = "1" AND dd <> 1 THEN dd = 3 IF k$ = "3" AND dd <> 3 THEN dd = 1 IF dd = 0 THEN yy = yy - 1 IF dd = 1 THEN xx = xx + 1 IF dd = 2 THEN yy = yy + 1 IF dd = 3 THEN xx = xx - 1 IF xx = 640 THEN xx = 1 IF yy = 480 THEN yy = 1 IF yy = 0 THEN yy = 479 IF xx = 0 THEN xx = 639 IF POINT(xx, yy) > 0 THEN GOTO crash2 PSET (xx, yy), 14 RETURN crash: CLS PRINT "Player one loses." SLEEP END crash2: CLS PRINT "Player two loses." SLEEP END There have been some other alterations too; the score is no longer printed as it's pretty pointless in a two player game since the game ends as soon as one person dies. This speeds up the game a lot, so I've had to slow it down again by multiplying the speed factor, in this case by five. If your computer is too fast, just increase this figure and it should be playable. Alternatively, increase the skill level (making it easier) but this will also reduce the number of obstacles. Another alteration is the creation of an empty space in the middle to allow the players to get their bearingsas the game starts. I have also changed the initial directions of movement so they start on a collision course... Have fun!