PROGRAMMING IN QuickBasic 4.5 for Complete Beginners. ***************************************************** By the Raze 18th-21st March 2000, Updated 3rd April 2000 LESSON IV Skill Level - Intermediate IN THIS LESSON: =============== Operators: Keywords: AS (OPEN Syntax) BINARY CIRCLE CLOSE # DRAW FOR (OPEN Syntax) GET (File I/O) GET (Graphics) INT INPUT (File Mode) INPUT (File I/O) LINE LEN(OPEN Syntax) OPEN OUTPUT PAINT PSET PUT (File I/O) PUT (GRAPHICS) RANDOM RANDOMIZE RND SCREEN TIMER WRITE (File I/O) CONTENTS: ========= PART 1: The Journey So Far... PART 2: Practical Stuff - Random data sequences - Accessing Files - Output/Input Mode - Random Mode - Binary Mode - Modify ASCII Adventure Map Data - Primitive QB Graphics Commands - Screen Modes - Changing Screen Mode - Drawing Shapes - The DRAW Statement - The SCREEN 13 Colour Palette - GET/PUT Graphics - Integer Memory Storage - PUTting the Image PART 3: Putting It All To Good Use PART 1 - THE JOURNEY SO FAR... ============================== Well, now we're JUST beginning to get some decent programs happening, and they're beginning to show a degree of size, structure and complexity. Now we've got modules and loops done over we can chuck away that now disgusting method of that text adventure completely! Now, (If you still REALLY want a plain text adventure that is) you can make it the best you can possibly make a text-adventure. Good structure, sounds, music, ASCII images to illustrate the current scene... But we've already created a simple realtime adventure game using a graphical map and cursor keys, and now it's time to move on to even better things. In this lesson, we will look at returning random data, file access and even begin to dip into some true graphics, and about time too, eh? PART 2 - PRACTICAL STUFF ======================== RANDOM DATA SEQUENCES ===================== If you want good AI (Artificial Intelligence) in a game, like for example the way an enemy moves on the map, not only will you need some complex algorithms to give the enemy some strategical decisions on what to do, but also chuck in a random value to make it act more varied and make it seem more 'real'. And yep, as logical as computers are such chaos can be acheived! Computer circuits are indeed fixed logic gates which are complex but nowhere NEAR as complex as the real world, so chaos in this manner is impossible to achieve. However, what QB does to generate a 'random' number is by calculating the current state of many parts of the system(e.g variable values) to get some number. Here is the basic syntax to return a random number: RND(1) The above syntax generates a random single-precision number between 0 and 1. The 1 simply implies that a number of a random sequence is to be selected. QB Help has a more detailed explanation, but as a rule of thumb you only really need this value as '1' to generate random numbers. Of course, you probably have no use for these kind of numbers, so there is a way to specify a certain range to pick from: RND(1) * range% + lowest% The range is the amount of numbers from highest to lowest. Say you want a maximum difference of 10, this value would be 10. The lowest value is what whole number the range starts from. So you want a random number between 40 and 50? randomNum% = RND(1) * 10 + 40 This may still be useless as the numbers are still floating points (decimal places). There is a function that rounds any number to the closest integer, and it's syntax is: INT(number%) So, if you entered INT(4.6) the returned value is 5. To apply this for randomizing... randomNum% = INT(RND(1) * range% + lowest%) Simple as that. But you may notice one very annoying thing: Every time you re-run the exact same program unaltered, the 'random' sequence is also the same every time. So if the sequence is 10, 4, 21 the first time, this will be the sequence next time you run it also. What we need to generate a different sequence each time is to get one value in a computer that is ALWAYS changing... The PC clock. Also called the TIMER, the clock keeps track of how many seconds have gone by in the day, even when the computer is off. Because so many tens of thousands of seconds pass in a day, there is a huge range of values to choose from, therefore the current timer status is the ideal random generator seed. Here's a program to give you an idea of the status of TIMER... CLS DO PRINT TIMER LOOP UNTIL INKEY$ <> "" As you can see, the time doesn't just update every second, but milliseconds and microseconds and so-forth, so the sequence can be constantly randomized. How we do this is by adding the line: RANDOMIZE TIMER Above anywhere in the program we want to get some random values, ideally you should put it near the start of the program where you initialise your data. Now, the sequence is as random as possible and completely unpredictable, every time. ACCESSING FILES =============== When you begin to make games that require a lasting 'hall of fame' type scoreboard for example, or a game that requires so much data that there will be just too many lines of DATA statements alone, or if a game is so challenging or long that you can't play it in one sitting, you need to make use of files. You can create files and read/write data from them, be it scores, text strings , graphic images, map data, the list goes on... If you used DATA statements alone to hold all the data of a large game, you would be stuck with one ridiculously long .BAS file. As you begin to use files more and more, DATA statements will begin to lose their relevance (but still be of some help in some situations!) Before we begin, you must know there are many ways and 'formats' we can access a file from, so we will go through the main ways one-by-one. OUTPUT/INPUT MODE ================= Syntax: OPEN fileName$ FOR [OUTPUT/INPUT] AS #fileNum% ... CLOSE #fileNum% OPEN is the statement we use to open a file. fileName$ is where the name of the file is mentioned OUTPUT/INPUT are the modes. Use OUTPUT to write to file, INPUT to read. fileNum% is the number used to refer to the open file. CLOSE must always be used to close the already opened file. This example program puts a string and a numerical value into a file: TYPE scoreType pname AS STRING * 8 points AS INTEGER END TYPE DIM SHARED score(10) AS scoreType DIM SHARED fileName$ fileName$ = "SCORE.DAT" RANDOMIZE TIMER 'Activate random sequence seed RESTORE namedat FOR x% = 1 TO 10 READ score(x%).pname 'Reads 10 names from DATA score(x%).points = INT(RND(1) * 100 + 0) 'Gives each a random score NEXT x% OPEN fileName$ FOR OUTPUT AS #1 'Open the file FOR x% = 1 TO 10 WRITE #1, score(x%).pname, score(x%).points 'Put info file sequentially NEXT x% CLOSE #1 'Close file END namedat: DATA "Jack", "Jill", "James", "Julie", "Jason" DATA "Jennifer", "Jeremy", "Janet", "Jebediah", "June" Now, this program opens the file above up, INPUTS the data to elements, then deletes the file: TYPE scoreType pname AS STRING * 8 points AS INTEGER END TYPE DIM SHARED score(10) AS scoreType DIM SHARED fileName$ fileName$ = "SCORE.DAT" OPEN fileName$ FOR INPUT AS #1 'Open the file FOR x% = 1 TO 10 INPUT #1, score(x%).pname, score(x%).points 'Input file data NEXT x% CLOSE #1 'Close file KILL fileName$ 'Delete file CLS LOCATE 1, 30: COLOR 15: PRINT "HALL OF FAME!" COLOR 7 FOR x% = 1 TO 10 LOCATE x% + 4, 25 PRINT "Name: "; score(x%).pname; " Score: "; score(x%).points 'Reads 10 names from DATA NEXT x% END *Things to note*: - In the first program we open the file as OUTPUT because data is going OUT to it. - In the second we open it as INPUT as we are inputting data already in the file, into our array elements. - The file number (#) in this case 1, is the reference number given to the file while it is open. This way, multiple files can be opened at once and given different numbers (#1, #2, #3 etc). But if I were you, I'd stick to opening files ONE at a time, or things could get messy!! - See the element 'pname' is defined as STRING * 8? Well, the 8 simply means the element is a string that can be up to 8 characters long. Normally this is optional but for elements in types it MUST be done, or you get an error... - KILL is a statement in QB that performs the DOS action of deleting a file, and I've always found it an amusing word to belong to a coding language... - When you open a file for output or input, you can't write and input data at once, you can only do whatever the current mode lets you. By the way, are you confused by the use of FOR and INPUT? Aren't these other QB statements? You're most certainly right... but one thing you'll learn is that SOME QB keywords, not all, have multiple uses. In this context, INPUT is the file mode, and FOR always fits between the file name and mode so the whole line makes sense to understand what it does. In this context, INPUT has nothing to do with user input, nor does FOR have anything to do with the FOR/NEXT procedure! This mode is ideal for the sort of examples we've used, player names, scores, generally normal strings and numerical data. RANDOM MODE =========== Syntax: OPEN fileName$ FOR RANDOM AS #fileNum% LEN = reclen% reclen% is the fixed length of each record in the file. Default is 128 bytes, minimum is 2 bytes, and the maximum for a record length in any mode is 32,767, but come on... Who the hell is gonna need THAT?! Opening a file in RANDOM mode gives much more versatility than OUTPUT/INPUT. You can not only read AND write to the file in one OPEN procedure, but you can specify the exact point in the file (by record) you wish to access. Whereas the record length was automatically set for each data for OUTPUT/INPUT , because we need to be able to pick a position in the file by its record, we need to set a fixed length for each record in the file. Syntax for PUT: PUT #fileNum%, curPos%, Value Syntax for GET: (As above) curPos% is the current record to read/write from. Value is whatever data type the data is being read into or written from. Let's try a program that writes a stream of numbers to a file using RANDOM: DIM SHARED stream(0 to 255) AS INTEGER CLS FOR x% = 0 TO 255 stream(x%) = x% NEXT x% OPEN "stream.dat" FOR RANDOM AS #1 LEN = 2 FOR x% = 1 to 256 PUT #1, x%, stream(x% - 1) NEXT x% CLOSE #1 END After you've run this, open stream.dat in MS-DOS editor. What's this... ASCII codes?? We didn't ask for ASCII codes to be put in there! Well, as it turns out, RANDOM mode stores numerical data representing it by ASCII characters. The reason why the minimum length is 2 is because when it comes to integer storage, you need at least two bytes to represent a number up to 32,768. As you can see, numbers 0 to 255 are represented by their ASCII counterparts and a null. If you alter the program to go beyond 255, the null is replaced by the second ASCII character. Of course if you want to store string data, you will have to make the record length long enough to hold the maximum size of the string/s you use. RANDOM mode is good for storing lots of numerical data at a length of 2, as 2 digits representing a 3 or more digit number can save file space. It can be good for holding strings also at longer record lengths. It is best not to mix string and numerical data with the RANDOM format because of the fixed record length. BINARY MODE =========== Syntax: OPEN fileName$ FOR BINARY AS #fileNum% Essentially, in BINARY mode you can place a byte of any value (0-255) to any byte position in the file. Because we're dealing with bytes only, there is no record length, only the byte length which is of course, 1. Try the RANDOM mode program, except change the mode to BINARY and omit the LEN = 2. Run it again and then view the stream.dat file in editor. Notice, no null spaces? Of course, this means you can also only represent 256 numbers instead of 32,768. BINARY mode could have been the ideal mode for data storage such as maps and 256 colour images, but the value of one byte when retrieved using GET depends on its neighbouring byte, leading to all sorts of numbers, even negatives. Why this is the case I donot know. There are other syntaxes of the OPEN command but the 3 main file modes and their uses is really all you need. In future lesssons we will explore even further advanced methods of saving and loading file data. MODIFY ASCII ADVENTURE MAP DATA =============================== To put your knowledge of file access to good practice, let's have a go at changing the ASCII adventure from last lesson so it loads the map data from a separate random mode file instead. What we need to do is: - Create a sub that saves the data from the map array to a file - modify the Loadmap sub so it loads from the file instead - delete the DATA statements for the map data as it will be no longer used. Okay. Let's make a sub called 'Savemap', that will go something like this: SUB Savemap curpos%=1 'Begin from file position 1 OPEN "MAP.DAT" FOR RANDOM AS #1 LEN = 2 FOR y% = 1 TO 20 FOR x% = 1 TO 20 PUT #1, curpos%, map%(x%, y%) 'Put from current x/y into file curpos% = curpos% + 1 'Moves one spot along in file NEXT x% NEXT y% CLOSE #1 END SUB After this has been copied into the game, insert a new line between where 'Loadmap' and 'readmusic' are called in the data initialization section, and enter 'Savemap'. This means first the array will load data from the statements, then the array data will be saved to a file. Run it so the file is made, then exit again. Now we must modify Loadmap as such: SUB Loadmap 'Load from Data curpos% = 1 OPEN "MAP.DAT" FOR RANDOM AS #1 LEN = 2 FOR y% = 1 TO 20 FOR x% = 1 TO 20 GET #1, curpos%, map%(x%, y%) curpos% = curpos% + 1 NEXT x% NEXT y% CLOSE #1 'Clear Screen and Draw Map CLS FOR y% = 1 TO 20 FOR x% = 1 TO 20 LOCATE y%, x% puttomap x%, y% NEXT x% NEXT y% END SUB You can delete the call for Savemap too, as we've already saved the map. Now try it... If you get it to work, then you can throw those clumsy DATA statements out the window! CHALLENGE: Modify the ASCII adventure even further so that the music is also read from a file. HINT... Use RANDOM mode with longer record lengths, as PLAY uses string data of dozens of characters each. PRIMITIVE QB GRAPHICS COMMANDS ============================== Yes, we've finally gotten to the stage you've been waiting for!! What is meant by 'primitive' is that these commands are in-built QB abilities to do things such as draw lines, squares, circles, paint an area, put to a single pixel location... SCREEN MODES ============ All computers made in the early 90's and later should almost definitely have at least a VGA card capable of 8-bit (256) colour, so assuming you are on such a machine right now is also almost definitely, you should have no trouble when it comes to screen modes. What screen modes are is a certain pre-defined video setting that your card can switch to. Each screen mode is a certain resolution (the amount of pixels across and down the screen), a certain color setting and has certain ways of handling video memory. The screen mode we have been working in, by default is mode 0, which is plain text. This is the mode DOS is in, and even the QB IDE itself. 'Graphics' can only be simulated as you now know by combining certain ASCII characters on the screen. When you go into a game, or Windows for example, the screen mode changes to a certain mode. Windows for example can be many modes, but at the lowest possible can be 640*480*16 (640 pixels across, 480 down, 16 colours). By default, QB offers 13 pre-defined screen modes, mode 0 which is our text mode, and 12 other modes, each of different resolutions/colour settings and all capable of graphics. Some modes are designed for now obselete video cards like Hercules and won't work, but we don't want them anyway! The basic syntax we use to change screen modes is: SCREEN screenmode% If you want info on each individual mode, again QB Help has it all! There are three main graphical SCREEN modes of interest... SCREEN 7 (320x200x16): One advantage about this mode is it divides video memory into 'pages'. This means multiple screens can have their data held and they can be flipped between to display the current one. Using page-flipping in graphics intensive games reduces flicker drastically. The downfall is, 16 colours at 320x200 looks pretty ugly to put it straight, and it is harder for more experienced programmers to handle the memory. SCREEN 12 (640x480x16): This is the one graphical screen mode that supports 640x480 graphics, which gives it one major advantage. The downfall is it can also only use 16 colours, and the memory is difficult to handle for experienced programmers. SCREEN 13 (320x200x256): This mode has the uninspiring 320x200 resolution, but one thing makes this mode miles ahead of even mode 12: The 256 colour palette. This leads for much better looking games all in all, so in the long run the lower resolution is really a small price to pay. Screen 13 is the mode we will be doing all our graphical programming in. One more advantage is that the memory is much easier to handle for more experienced programmers. CHANGING SCREEN MODE ==================== This example program shows the visual difference to the text's resolution when switched from mode 0 to 13: CLS PRINT "This is Screen Mode 0." PRINT "Press Any Key to Switch to Screen Mode 13 ..." DO LOOP UNTIL INKEY$ <> "" SCREEN 13 PRINT "Now in Screen Mode 13." DRAWING SHAPES ============== Let's start using some primitive QB commands to draw shapes. Let's start with the PSET statement. PSET places a pixel to the specified x/y location and with the specified colour. Here's a program that randomly places pixels on the screen: SCREEN 13 RANDOMIZE TIMER col% = 1 'Blue DO x% = INT(RND(1) * 320 + 0) y% = INT(RND(1) * 200 + 0) PSET (x%, y%), col% LOOP UNTIL INKEY$ <> "" END Here is the basic syntax to draw a line: LINE (x1%, y1%)-(x2%, y2%), lineCol%, [B[F]] To draw a line, we need 4 coordinates... The starting x/y location, and the ending x/y location. once we specify that, QB automatically draws the line in-between. If you omit the colour, it automatically picks 15 (white). This program below randomly draws lines indefinitely: SCREEN 13 DO x1% = INT(RND(1)* 320 + 0) y1% = INT(RND(1)* 200 + 0) x2% = INT(RND(1)* 320 + 0) y2% = INT(RND(1)* 200 + 0) rndCol% = INT(RND(1)* 255 + 0) LINE (x1%, y1%)-(x2%, y2%), rndCol% 'Draw line in random colour LOOP UNTIL INKEY$<>"" END Lines can also be used to draw boxes. The first and second x/y coordinates represent opposing corners of the square, which QB draws between. LINE (x1%, y1%)-(x2%, y2%), col%, B 'Draws the outline of a box in the colour. LINE (x1%, y1%)-(x2%, y2%), col%, BF 'Draws box filled with the colour. The basic syntax to draw a circle is: CIRCLE (x%, y%), radius%, circleCol% The x/y location in this case refers to the centre of the circle. The radius is the distance in pixels from the centre to the border, and circleCol% is the colour to draw the circle shape in. To paint a section of the screen from a location, the syntax is: PAINT (x%, y%), paintCol%, BorderCol% Where paintCol% is the colour to paint with and BorderCol% is the colour to stop painting at. If BorderCol% is omitted, the whole screen will be painted. You can use this technique to make a circle solid by painting somewhere inside it, using the circle's border colour as the paint's border colour. DRAW STATEMENT ============== This is very similar in syntax to the play statement, in that it is a string of sequential instructions that are followed to draw across the screen. DRAW expression$ The Syntax: n=pixels to move Un=Up Dn=Down Ln=Left Rn=Right En=Up-Right Fn=Down-Right Gn=Down-Left Hn=Up-Left B =Optional prefix, move but donot draw line N =Optional prefix, donot move but draw line Mx,y=Move to coordinates (is relative to current x/y if preceded by + or -) Sn=Change drawing moves (default n=4) TAn=(Rotate 0-360) An=(Rotate (0-3) * 90) Cn=Change drawing colour For more details again see QB Help. This program uses some various drawing commands: SCREEN 13 DRAW "u10h10c1d20c2l30c7e40" END I find virtually no practical use for the DRAW statement when it comes to bitmap graphics, and virtually never use it. LINE comes in handy however, particularly where clearing an area of the screen with a color is concerned (by drawing a filled box over it). THE SCREEN 13 COLOUR PALETTE ============================ This program simply shows the range of default colours in mode 13 by drawing a line sequentially in each colour: SCREEN 13 CLS FOR x% = 0 to 255 LINE (x%, 0)-(x%,10), x% NEXT x% END What a range, eh? What's more, you can actually customise the RGB values of each individual colour and create your own custom palette(more on that next time). GET/PUT GRAPHICS ================ Well, now you know how to draw images to the screen. But they're useless if we can only draw them to the screen then can't use them anymore... What if we want sprites? Incase you don't know what sprites are, sprites are moving graphics that are not part of the background. Using Mario for example, things like the Mushrooms, monsters and of course Mario himself are sprites. QB has two statements called GET and PUT that are very useful indeed. Again, these are multi-use statements that as you know are also used for file access. In a graphical context, GET grabs a selected area of the screen and stores that section as data in an array. PUT places image data from an array back to a chosen coordinate on a screen. The Syntax for GET: GET (x1%, x2%)-(x1%,x2%), arrayname What this does is picks an area from the first to second set of coordinates as a box. Then, arrayname is where you specify the name of th array to put the data in. (Generally you donot include the array's brackets or any offset, but more on this later.) One thing you must keep in mind when creating an array to store data is, 'Will it be big enough to store the part of the screen I need?' Say you need to GET an area of 20x20 pixels, this means you space to store the data for 400 pixels, so the array would need to be dimmed such as: DIM SHARED image(400) AS INTEGER This is fine, but it is actually TOO MUCH space, twice as much as you need. Now this may be a little confusing to beginner programmers, so we'll go through the concept bit-by-bit on explaining the perfect size to make arrays. As I said, the above example will work for 20x20 images as it is more than enough, but we want to conserve as much memory as possible, especially for QB as by default it doesn't give us much memory to play with. INTEGER MEMORY STORAGE ====================== Now you should already know fundamentally that memory is where data can be stored temporarily (until it is erased or the computer turned off), but to get the most out of programming you need to understand more than that alone. As you may have noticed by the random file format test, all integer numbers are represented by two bytes. Because two bytes can be combined in 65,536 ways, Integers can have a value from -32,768 to +32,768, no decimal spots. When you define an array as an integer, each position in the array is worth 2 bytes. This means when you dimension an array as 400 for example, you are actually giving it 800 byte positions. Seeing 8-Bit pixel data requires only one byte, you need 400 byte positions for a 20x20 array. Each byte position in a memory segment is called an OFFSET. Also, if a part of the screen is stored in an array, the array needs to somehow memorize the size of the image's width and height. When you GET an image into an array, the height and width are stored in the first two offsets of the array, in array positions 0 and 1 respectively, as the data header. More on the width/height data header in future lessons. So, seeing one position is worth 2 bytes, and the width/height header is another 2 bytes, we would dimension an array for a 20x20 image as: DIM SHARED image(201) AS INTEGER We will come back to GET format data in an integer array in future lessons, where we will do even more intensive, advanced stuff. PUTTING THE IMAGE ================= Generally, you'll want to GET all your images at the start of your programs while you initialise your data. So, now you've got your image in the array. Now what? You can use PUT to replace the image at any position on the screen, so long as the coordinates are within the screen limits and part of the image doesn't trail off the edge of the screen if you are close to the sides. The Syntax for PUT is: PUT (x%, y%), arrayname, [putMethod] The x/y location is where the image will be placed on the screen by it's top-left corner. Of course 'arrayname' is the array the image data is already stored in. 'putmethod' is the method you want the colours of the image to be put on the screen. Generally at this stage we will use PSET as it completely erases over any data that was already on that spot of the screen before when placing the image with PUT. The following example program draws an image, GETs it into an array then PUTS it randomly across the screen many times: ' "Peak Hour" Graphics Demo By RAZE 20th March 2000 DIM SHARED car(65) AS INTEGER 'Array large enough for 16x8 image SCREEN 13 'Set screen mode to 13h (320x200x256) DO rndCol% = INT(RND(1) * 150 + 0) 'Pick a random colour from the first 151 RESTORE imgDat FOR y% = 0 TO 7 FOR x% = 0 TO 15 READ datCol% IF datCol% = 14 THEN PSET (x%, y%), rndCol% 'If the data value is 14 then use random colour ELSE PSET (x%, y%), datCol% 'Otherwise draw in original data colour. END IF NEXT x% NEXT y% GET (0, 0)-(15, 7), car 'GET image into array rndx% = INT(RND(1) * 300 + 0) 'Select random x/y location rndy% = INT(RND(1) * 190 + 0) PUT (rndx%, rndy%), car, PSET 'Place car image at x/y location LOOP UNTIL INKEY$ <> "" END 'Standard car image to draw all coloured cars from... imgDat: DATA 000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000 DATA 000,000,000,000,000,000,014,014,014,014,014,014,014,014,000,000 DATA 000,000,000,000,000,014,000,000,000,014,000,000,000,000,014,000 DATA 000,000,000,000,014,000,000,000,000,014,000,000,000,000,000,014 DATA 014,014,014,014,014,014,014,014,014,014,014,014,014,014,014,014 DATA 014,014,024,027,024,014,014,014,014,014,014,014,024,027,024,014 DATA 024,024,027,031,027,024,024,024,024,024,024,024,027,031,027,024 DATA 000,000,024,027,024,000,000,000,000,000,000,000,024,027,024,000 Just incase you are confused at first as to how the one set of image data can draw many different coloured cars, it's because we first PSET from the data one pixel at a time. If value 14 is encountered, a random colour is PSET'ed (The car body). Then the corner image is placed into the array and put to a random location. PART 3 - PUTTING IT ALL TO GOOD USE =================================== These supplement programs are just starting to get too damn big to put in here, so the PART 3 files I normally include here are now separate. Included in the .ZIP this tutorial comes in, you'll find a program called PSET.BAS which is basically a graphical update of the ASCII Adventure from last lesson, and it's quite a welcome change... You can even see the light sabre your character holds in his hands. Yeah, I know, the game itself is pretty pointless, but it's all part of the big learning process! *NOTE*: Can't get the file to work? That's because it needs the file for the map data, and you may not have done the exercise earlier this lesson that creates it for the ASCII version of the game. Hmm, trying to jump the line, eh? ;) The program's a little 'buggy', like I slapped it together late at night from the first ASCII version and the tiles are the wrong size so the character overlaps tiles to his right. It still runs a beaut though. To finish off, I guess I'd better mention incase you haven't noticed already that from here on, it ain't exactly a *Beginner's* guide to QuickBASIC no more, well if you can do all the stuff up to now with no sweat, you're already about intermediate level! From here on we begin to get into some more intensive graphics and memory stuff. Until then, c-ya later.