-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- QB CULT MAGAZINE - Issue 3 - May 2000 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
Editor: Matthew R.Knight Email: horizonsqb@hotmail.com
Design and HTML coding: Merlin of Master Creating Email: mail@master-creating.de Website: http://master-creating.de
QBCM archive, latest news, and discussion forum:
http://picksoft.zext.net
QBCM is also available on the following host sites:
http://alienqb.cjb.net http://aurskogcity.8m.com http://master-creating.de http://www.neozones.com/geoshock http://neozones.tekscode.com http://qbversions.cjb.net
Much thanks to the owners of the above sites for hosting QBCM. If you are hosting QBCM on your site, and it is not listed above then please contact me at horizonsqb@hotmail.com so that it can be added.
We need QBCM to be available on as many Qbasic related websites as possible. That way ALL Qbasic coders can benefit from it. If you have a website dedicated to Qbasic then please become a host of QBCM too!
All you have to do to become a host is email me telling me the URL of your site, and then I'll send you the new issues every month, and your site will be listed on the QBCM host list!
Copyright (C), 2000, All rights reserved worldwide. QB Cult Magazine is the exclusive property and copyright of Horizons Interactive Entertainment. The HTML coding and magazine design is the exclusive property and copyright of Master Creating.
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- FOREWORD -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
Welcome to the third issue of Qbasic's #1 magazine!
You'll recall that last issue was really huge and very different from the issue before it, well, this month see's some big changes once again in QB Cult Magazine! Merlin of Master Creating is now doing the design and HTML coding for QBCM and as you can see, he is doing a much better job of this than I did with last issue! Thanks again Merlin :)
QBCM now has an official archive and discussion forum! Much thanks goes to Chris Charabaruk for this. It is greatly appreciated. Be sure to check this out at http://picksoft.zext.net in the QBCM section.
Many of you probably heard the rumours early last month that QBCM is closing down. This is NOT going to happen. QBCM will continue for a very, very long time I can assure you. Certain unforseen circumstances last month almost brought about an end to QBCM. These have fortunately been resolved. Once again, I will NOT be stopping QBCM, and can't forsee doing so.
As far as errors in the last issue are concerned, it appears that there was only one. In my review of Mysterious Song, I awarded the game 90%. At the time of writing the review I had played very little of the game. However, I have since completed it, and I must say that the game is far better than I originally thought. I was highly impressed with Mysterious Song at the time of reviewing it, however, having now played the entire game, it has become apparent that the game deserves far more credit than I gave it. It would be a crime for it to get anything less than 100%. It is not often that a game leaves such an impression on me...however, Mysterious Song is more than just a game. It has a wonderfull and touching story, behind which are some very original and intelligent philosophies. In addition to that, the game boasts stunning visuals, beautifull background music, and gripping gameplay. Everything in MS has been seemlessly integrated and moulded with such perfection - it's almost magical! So, let me now take this oppertunity to correct this mistake and give Mysterious Song the rating it truly deserves - 100%. My sincere apologies go to Darkness Ethereal for not giving MS the review it deserved in the first place.
The rate at which QBCM has grown never ceases to amaze me. What started out as only a small text-based magazine two months ago, has grown into the biggest and quite possibly the best Qbasic magazine our community has ever seen. Every time I check my email I have loads of emails from fans of QBCM, which is very nice to see. Unfortunately however, few people are actually contributing towards QBCM. I simply do not have the time to write as many articles for QBCM as I'd like, so if you want QBCM to be really big and interesting, then it's really up to you. I can't write more than 3 articles per issue. I'd like to have at least 6 articles every issue, but that is far more than I can write in a month. So PLEASE CONTRIBUTE SOME ARTICLES PEOPLE! Please send all articles to me at horizonsqb@hotmail.com
The phenomenal growth of QBCM, not only in size but also in quality, is by no means due to my efforts alone. I would like to take this oppertunity to thank the many people who have contributed in some way or other to QB Cult Magazine. Your efforts and support are greatly appreciated.
Even more amazing than the growth of QBCM is the growth in the number of quality Qbasic programs. What seemed impossible in the language only a year ago is a reality today. Needless to say, this is due to the sudden surge in the amount of libs over the past year, or so. Keep coding everyone...from now on QuickBASIC is the only language we need! POWER TO QB!!! ^_^
Hope ya like this issue! Enjoy! ^_^
-Matthew River Knight Horizons Interactive Entertainment
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- YOUR LETTERS -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
QBCM takes a lot of time and work to complete every month, and I am thus always delighted to hear from its readership. Please write to QBCM at horizonsqb@hotmail.com with your comments and suggestions, or basically anything you have on your mind to do with Qbasic ^_^
All of last months letters were lost due to unforseen circumstances. My sincere apologies go to all QBCM readers for this.
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- ASK QB CULT MAGAZINE -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
Don't you just hate it when you are really stuck on something in Qbasic and no matter how hard you try you just can't figure it out? Doesn't it drive you completely nuts? Yep, I thought so, so that's why I included this section in the mag for ya! If you get stuck on anything in Qbasic, just send an email to me at horizonsqb@hotmail.com and you'll get a reply within three days. In addition to that, your problem will be printed here in QBCM, along with my reply, so it can help other coders too! ^_^
All of last months questions were lost due to unforseen circumstances. My sincere apologies go to all QBCM readers for this.
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- BUZZ - NEWS FROM AROUND THE QB WORLD -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
* April was a very bad month for Nekrophidius. Amongst other things, a great deal of his work was lost due to a virus. Consequently, Nek soon announced his retirement from, not only the QB world, but programming in general. Fortunately, Nek later changed his mind. Almost all work on most of his projects has been lost due to the virus. The only project untouched by the virus was Killers. Nek has decided to start both QuickPower and Wrath of Sona from scratch! We wish Nek the very best of luck in getting these projects up and running again. Nek is probably one of the most intelligent coders in the Qmunity today, and also one of the most helpfull, having helped countless coders with their coding problems. It's sad that such a cool guy became the victim of the stupidity that exists in the world today - people who write viruses think they're being clever...heh, BULLS**T! They are just showing how damn stupid they are, being incapable of thinking of something constructive to do.
* Tsugumo recently posted the following article on the Darkness Ethereal discussion board:
Bear with me, this is long winded. It also might not describe the board it's posted on, but check it out and see what you think.
After visiting boards around the net, FrozenEmu and myself have found that basically WWWBoards suck right now. They generally have only a few people that visit, and even less posts that even relate to game development. The boards that are still active (except for a few) are active only because of people posting new flames and such. This is usually because the maintainer of the board doesn't give a crap and lets idiots run free.
From going around, we've found that there are a lot of people who want to get back to game discussion, the way things used to be a few years ago... So what we want to do is start up a central WWWBoard, not attatched to anyone's game/site, where everyone can meet.
The benefits of a board like this, is imagine if everyone went to one place to release and find information. For the people who want to know what's going on, they need only stop at one board, rather than search through the spam at 30 different boards around the net. For the people who want to show off new demos, screenshots, or whatever, they only have to post on one board for a ton of people to see it and give feedback.
The board will be moderated by FrozenEmu and myself, and we will delete threads that get way out of hand, spammers, etc. and ban people that need banning. Like many others, we want a WWWBoard where we can go to discuss game related stuff without the worry of being flamed or being faced with tons of idiots.
Anyway, we're calling it the Game Developer's Refuge: (http://www.swoo.net/gdr/index.pl) and we're posting this to whatever WWWBoards we know of...Please spread the URL around so we can get a "community" together like the old days. You can read more about our philosophy and rules at http://www.swoo.net/gdr/rules.htm because believe it or not, this is the "short" version, heheh...
We're also working on a good design, graphical and game-ish looking and such, but it's not ready yet and we want to see if we can draw many people to this idea first.
So for a greater communication level throughout the programming world, please take part."
* There's quite a bit of news from Darkness Ethereal. Firstly, serious work has started on Dark Crown. DarkDread has announced that he hopes to have a tech demo up within the next month or so.
On the Distant Promises side of things...well, there hasn't been much said about that in ages! DarkDread has however announced that he still intends to complete the project. Nekrophidius is still composing some awesome tunes for it. DD is also considering re-coding the game, so it might take a bit longer than originally anticipated for the game to come out. One more demo is planned to come out, before the final release. ^_^
Oh no! My favoutite upcoming Darkness Ethereal game, Eternal Frost, has hit the QB dustbin. =( This has been done, as DarkDread said, because he simply doesn't have enough time to make this game of the quality he'd like it to be.
But wait...DarkDread has announced that Mysterious Song 2 will be made! It's a long time off, but this is very good news indeed! :) The story will continue where the first one left off...hmm...this is good...I still would like to know what happened to...ehm, I don't want to give away the story to those who haven't finished episode 1...so never mind ;)
Visit the Darkness Ethereal website at http://welcome.to/DarknessEthereal
* Jaws V soft, best known for their MiniRPG series, have just released MiniRPG1 Boardgame! This game totally rocks!!! It is available for download at http://members.aol.com/changev/index.htm
* The new game engine behind Magnet Entertainment's RPG, Dark Aura, is pretty much complete. I saw a demo of it, and it's certainly much better than the old engine. Check out the Magnet Entertainment website at http://www.geocities.com/Magnetent
* The second demo of Badjas (of Future Software)'s 3d engine has been released. A lot of new stuff has been added to the demo, and it's also running at a MUCH better pace. Visit Future Software's awesome webiste at http://www.qb45.com
* Zed (aka Kristoffer Ström) is busy on a console style RPG called Dark Fall. The game boasts many new and original features, an intelligent and well-conceived story, and high quality visuals and sound. Good luck on this game Zed! We'll keep you posted on the latest developments regarding this project as it arrives.
* Will Moores is busy on a 13h graphics library called ZipLib. It's still in the early stages of production, however, I saw an early demo of it and it ran at a good pace. Good luck on this project Will! More news on this will follow, as it arrives.
* AlienQB is busy on a pretty rockin' new SVGA game called Infernal Forces! Find out everything you could possibly want to know about this cool new project at http://alienqb.cjb.net
* Dark Ages II, demo 9 has been released! Go check it out RIGHT NOW!!! You can download it from http://www.tcnj.edu/~hoopman2
* CarterSoft has released a cool new 32 bit BASIC variant called Novascript. Check out this month's review to see if it's da bomb or a bomb! You can get Novascript from http://www.cole_carter.tripod.com/novascript1.zip
* Gabriel Fernandez has released a very fast, 90% QB compatible compiler that works in pmode!!! It's called GABASIC and you get get it from http://gab_soft.tripod.com. The compiler still needs a bit of work, but it looks to be the one to watch.
* Phat Kids has returned to the QB scene. They dissapeared several months ago, but it was recently announced at the Darkness Ethereal discussion board that they're back and will be completing Kids of Karendow as planned! Yay!!! ^_^
That's all the news we got for you this issue. Please send any news or rumours to us at horizonsqb@hotmail.com
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- OBJECT ORIENTED BASIC - Possibility or Pipe Dream? -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
Writer: Unknown
TABLE OF CONTENTS
1.0 Introduction 1.1 Key Terminology and Concepts 2.0 BASIC-Specific Considerations of Object Paradigm Implementation 2.1 Standardization of Terms in Object Oriented BASIC 2.2 An Introduction to Advanced Topics in OOP 3.0 Closing Notes
1.0 Introduction
BASIC has evolved from the time-sharing "Beast of Dartmouth" into a powerful, structured language fit for the programming needs of the nineties. Despite this evolution, however, major software compiler developers have failed to introduce object oriented extensions into the language.
This article will explore some possible extensions to modern BASIC that would truly expand the language. Since, because of its nature, this article will use a speculative approach, the reader should bear in mind that no particular implementation is being suggested as the "best" way to bring object-orientation to BASIC. Moreover, some BASIC programmers may feel that certain low level features such as in-line assembler and more diverse data types should be introduced into the BASIC language before object- orientation is even considered. These readers should remember the theoretical nature of this discussion, and leave all such preferences out of the exploration at hand.
1.1 Key Terminology and Concepts
First, I must define some key terms and concepts. My use of the generic term BASIC (Beginner's All-purpose Symbolic Instruction Code) will, unless otherwise stated, refer to the Microsoft QuickBASIC v4.5 dialect of BASIC, since this represents a widely accepted implemenation of modern, structured BASIC. The term OOP (Object Oriented Programming) will be used to refer to those programming practices that rely on the object paradigm. Although the terminology differs from compiler to compiler, the object oriented paradigm is considered by modern usage to intrinsically encompass the following concepts, to be defined later:
1. Encapsulation 2. Inheritence 3. Polymorphism 4. Overloading
Therefore, when I say that a given concept is "object oriented" I specifically mean that it involves the above four concepts. Other important terms that cannot be ignored in any discussion of OOP, due to their repeated use in the discussion of such are:
5. Class 6. Method (or Member Function) 7. Object (or Class Instance) 8. Information or Data Hiding
Not able to decide which term to define first, I will begin with a general overview fo the underlying philosophy of OOP.
In classical structured programming, data and code are considered separate entities. Code manipulates data. Data fuels code. For example, wanting to implement a graphics font engine, a classical BASIC programmer might read a list of DATA statements into a globally accessible array, and then have a series of globally accessible SUBPROGRAMS manipulate those raster data on the screen in such a way as to produce the desired visual effect. The problem with this approach is that both the data and the related code are equally accessible, and they are only loosely cohesive. Wanting to enhance code written by a colleague, a second programmer will encounter data structures that he should neither modify nor even poll, but that may not always be possible. Having modified an essential data structure, the second programmer may introduce errors into the whole underlying logic of the system.
For instance, suppose the original programmer had defined the font data structure thus:
TYPE FontDataType FontName AS STRING * 12 FontPointSize AS INTEGER RasterDataPtr AS LONG END TYPE
Now, looking at this, Programmer Two decides that he can avoid a FUNCTION call to funGetFontPointSize() by just reading the value of Font.FontPointSize directly. Programmer Two alters his code to access this variable directly, and in doing so, avoids what he considers costly calls to funGetFontPointSize(). He is promoted to another department (presumably for having sped up the code of his predecessor). Enter Programmer Three. Quite within his bounds, he discovers that point size is never greater than 255, and so redefines the whole structure to account for this, thereby reducing overall memory consumption by one byte:
TYPE FontDataType FontName AS STRING * 12 FontPointSize AS STRING * 1 RasterDataPtr AS LONG END TYPE
Of course, being conscientious, he modifies funGetFontPointSize() to account for this innovation. He compiles the program. It crashes. Why? Because, this is now an illegal statement:
Font.FontPointSize = 12
What to do? He must use his search and replace to go through the entire program and change all such instances to:
Font.FontPointSize = CHR$(12)
Or, he can forget his alterations altogether.
In BASIC, there is no INFORMATION HIDING that would prevent such problems from occuring. Since FontPointSize is a public member of FontDataType, Programmer Two was well within his rights to do as he saw fit as far as accessing it. Had the original programmer had an object oriented BASIC, however, he could have prevented the entire problem with little difficulty by making FontPointSize a PRIVATE data member of the CLASS font. This might have looked similar to this:
CLASS FontClass FontName AS PUBLIC STRING * 12 FontPointSize AS PRIVATE INTEGER RasterDataPtr AS PRIVATE LONG funGetFontPointSize AS PUBLIC FUNCTION subSetFontPointSize AS PUBLIC SUB END CLASS
DIM Font AS FontClass
[Please bear with the strange new syntax, since it will be covered in more detail in section 2.0.]
Now, the only way to access Font.FontPointSize is indirectly. This would NOT work, since this data member is now PRIVATE:
Font.FontPointSize = 12
This, then, would be the ONLY way to achieve such a thing:
Font.subSetFontPointSize 12
In the above example, the item Font is what is called a CLASS INSTANCE. That is to say, Font is "an instance of the class FontClass." This is what is commonly called an OBJECT, and it is from this that we arrive at the phrase "object oriented" programming.
Now, when Programmer Two comes along, he CANNOT pull off his stunt, and he is not promoted to another department. Programmer Three comes along, and sees room for improvement and redefines the class thus:
CLASS FontClass FontName AS PUBLIC STRING * 12 FontPointSize AS PRIVATE STRING * 1 RasterDataPtr AS PRIVATE LONG funGetFontPointSize AS PUBLIC FUNCTION subSetFontPointSize AS PUBLIC SUB END CLASS
Since all calls to change FontPointSize are through the centralized subSetFontPointSize, Programmer Three just modifies that a bit, and earns himself a nice raise in salary for shaving a byte off the memory requirements of the structure.
Consider the above example. The data are:
1. FontName 2. FontPointSize
The code portions (called MEMBER FUNCTIONS or METHODS, since they are "methods of acting upon or accessing" the data) are:
1. funGetFontPointSize 2. subSetFontPointSize
Since it is unlikely that subSetFontPointSize will ever be needed for anything other than the setting of FontPointSize, it makes sense to bind the code to the data it works with. This binding is called ENCAPSULATION.
Having examined these more essential terms, there is the issue of OVERLOADING. Although not object oriented in the strictest sense, it does aid in generalizing classes to an extent that they can operate upon different types of data.
Consider the following:
subQuickSort A%()
Now, in classical BASIC programming, if we wanted to sort anything other than INTEGER arrays, we would have to write another SUBPROGRAM and modify the algorithm to account for this new data type. This SUBPROGRAM would have to be named something other than subQuickSort. For example:
subQuickSortSTR A$()
might be used for STRING arrays, and
subQuickSortLONG A&()
might be used for LONG INTEGER arrays. And, of course, should a programmer ever want to sort a user-defined TYPE array:
subQuickSortUserTYPE UserArray()
would be the only way to do it.
But, consider the above. All of these routines do the same thing. It seems a waste to have three names to do what amounts to the same thing: sorting arrays. The answer is to "overload" a SUBPROGRAM name with three corresponding pieces of code. Once subQuickSort is overloaded, it can do tripple-time thus:
subQuickSort A%() subQuickSort A$() subQuickSort UserArray()
Of course, each call invokes DIFFERENT CODE to do the actual sorting, but this detail is handled by the compiler in a transparent fashion. The programmer's only responsibility would be to provide the code for each instance of subQuickSort, in the following manner:
SUB subQuickSort (Array AS INTEGER) | | code to sort INTEGER arrays goes here | END SUB
SUB subQuickSort (Array AS LONG) | | code to sort LONG INTEGER arrays goes here | | END SUB
SUB subQuickSort (Array AS UserDefinedType) | | code to sort arrays of UserDefinedType goes here | | END SUB
Upon seeing the second instance of subQuickSort in the source listing, the object oriented BASIC compiler would know that it is dealing with an overloaded SUBPROGRAM.
Overloading is already done by BASIC compilers, but it is done at a level not within the control of the programmer. Consider:
PRINT a PRINT a$
Each case of PRINT prints a different data type. The PRINT statement, we could say, then, is overloaded. Also to consider is the overloading of operators such as occurs already in BASIC:
A$ = B$ + C$ A% = B% + C%
The addition operator is serving two masters here. In the first case, it is being used to concactenate strings. In the second, it is being used to add two numbers. The processes are internally dissimilar. How, then, does the BASIC compiler contend with these cases? The addition operator is overloaded at an internal level. If a programmer using an object oriented BASIC were to step into the scene, however, we very well might see this type of overloading of the addition and assignment operators:
OVERLOAD "+" FOR ArrayOne(), ArrayTwo() TotalElements = UBOUND(ArrayOne) + UBOUND(ArrayTwo) DIM ReturnArray(TotalElements) FOR i = 1 to UBOUND(ArrayOne) ReturnArray(i) = ArrayOne(i) NEXT i FOR q = i + 1 TO i + UBOUND(ArrayTwo) ReturnArray(q) = ArrayTwo(q-i) NEXT q REDIM ArrayOne(TotalElements)
' The following uses an overloaded assingment operator ' whose overload definition follows. ArrayOne() = ReturnArray() END OVERLOAD
OVERLOAD "=" FOR ArrayOne(), ArrayTwo() FOR i = 1 TO UBOUND(ArrayOne) ArrayOne(i) = ArrayTwo(i) NEXT i END OVERLOAD
This bit of sophisticated operator overloading would allow the programmers to add entire arrays to one another as follows:
NewList() = ListOne() + ListTwo()
For some readers, all this may be a new concept in programming. If it seems hard to understand, please take time to reread this section before continuing, since the next part of this discussion relies on the reader's comprehension of all eight terms pertinent to the object oriented programming paradigm, which are, again:
1. Encapsulation, 2. Inheritence, 3. Polymorphism, 4. Overloading, 5. Class, 6. Method (or Member Function), 7. Object (or Class Instance), 8. Information or Data Hiding.
[Polymorphism has been purposely avoided for the purposes of this discussion, due to its rather esoteric nature.]
2.0 BASIC-Specific Considerations of Object Paradigm Implementation
When considering whether BASIC in its present form could be expanded to include object oriented extensions, we must first look at what is already possible in standard BASIC. For example, the following code resembles inheritence, at least in part:
TYPE ColorType R AS INTEGER G AS INTEGER B AS INTEGER END TYPE
TYPE CoordinateType X AS INTEGER Y AS INTEGER END TYPE
TYPE CircleType Point AS CoordinateType Color AS ColorType Radius AS INTEGER END TYPE
This is not classical inheritence, but the analogy suffices. Looking at the syntactical elements of the above code, we see that a similar structure could easily be adopted for use with CLASS definitions:
CLASS CircleClass Point AS CoordinateType Color AS ColorType Radius AS INTEGER END CLASS
A question arises, however. The above definition of the CircleClass CLASS is not executable code, but merely a definition template. It defines CircleClass, but does not assign a "class instance." That is to say, there are not yet any objects of CircleClass defined in the program. Consider this standard BASIC:
TYPE AddressType Street AS STRING * 10 City AS STRING * 32 State AS STRING * 2 ZIP AS STRING * 12 END TYPE
DIM Envelope AS AddressType
The DIM statement is used to create an instance of a variable called Envelope that is of the user defined type AddressType. It makes perfect sense, then, that the DIM statement could be used in this manner:
CLASS CircleClass Point AS CoordinateType Color AS ColorType Radius AS INTEGER END CLASS
DIM Orb AS CircleClass
(Remember, having DIM serve this double purpose is known as overloading the DIM statement.) This syntax serves our purposes wonderfully, since it does not involve the introduction of completely foreign operators and follows the present syntactical structure of standard BASIC.
Another consideration in the creation of classes is the fact that classes may contain both variables and methods in their definitions, as shown in the introduction:
CLASS FontClass FontName AS PUBLIC STRING * 12 FontPointSize AS PRIVATE INTEGER RasterDataPtr AS PRIVATE LONG funGetFontPointSize AS PUBLIC FUNCTION subSetFontPointSize AS PUBLIC SUB END CLASS
This shows a suggested means of expressing both the scope and the type of each part of the definition. Note, however, that, although subSetFontPointSize is defined in this template, there is, as yet, no code attached to the definition. It is said, in OOP parlance, that the "the scope of the member function is unresolved." The method is prototyped, but that is all. In C++, what is known as the "scope resolution operator" is used to resolve a method, that is, assign executable code to it. This is done as follows:
void FontClass::subSetFontPointSize (int PointSize) { | code to achieve this end goes here | }
Essentially, this translates into the English statement:
"Define funGetFontPoint size of the class FontClass as follows...."
In an attempt to avoid convoluted syntactical introductions into the BASIC language, what follows is a possible solution:
SUB FontClass.subSetFontPointSize (PointSize AS INTEGER) | | code that assigns the point size goes here | | END SUB
Since the compiler would presumably recognize FontClass as being a class from the earlier CLASS ... END CLASS block, this should suffice as a means of resolving the scope of the method subSetFontPointSize, while avoiding the introduction of :: as a new BASIC operator.
Next comes the issue of overloading both keywords and operators. A simple extension of BASIC would allow this to be sufficient in the case of SUBPROGRAMS and FUNCTIONS:
SUB subQuickSort (Array AS STRING) | | END SUB
SUB subQuickSort (Array AS INTEGER) | | END SUB
The second SUB definition would imply overloading. This would be prototyped at the beginning of the source listing thus:
DECLARE SUB subQuickSort (Array AS STRING) DECLARE SUB subQuickSort (Array AS INTEGER)
Operators, however, are completely different in that BASIC has no way of referring to them explicitly. A proposed extension:
OVERLOAD "=" FOR LeftArgument, RightArgument | | definition code goes here | | result returned in LeftArgument | | END OVERLOAD
Of course, the "=" could be any ASCII character or even multiple ASCII characters. This would allow the object oriented BASIC program to do this, for example:
OVERLOAD "**" FOR LeftArgument, RightArgument
' Some langauges use ** for raising to a power LeftArgument = LeftArgument ^ RightArgument
END OVERLOAD
The following, however, would not be possible, since it would involve late binding and interpreted evaluation at run-time:
OVERLOAD Operator$ FOR LeftArgument, RightArgument SELECT CASE Operator$ CASE "**" LeftArgument = LeftArgument ^ RightArgument | | etc. | | END SELECT END OVERLOAD
2.1 Standardization of Terms in Object Oriented BASIC
Before the discussion continues, perhaps it would be wise to step aside to establish a set of standard terms. Since certain OOP concepts carry many different names (ie. "member function" is also "method") a standard way of refering to any particular device should be adopted. But, really, this could become quite involved; what is more appropriate, the term "method" or "member function?" Perhaps, rather than debate too long and hard on the subject, Microsoft's terminology as used for Visual Basic should be adopted:
1. OBJECT rather than "class instance" 2. METHOD rather than "member function" 3. PROPERTY rather than "member variable"
For terms not used by Visual Basic, I suggest the following use by object oriented BASIC:
1. DATA HIDING rather than "information hiding" 2. METHOD DECLARATION rather than "scope resolution" 3. METHOD DECLARATOR rather than "scope resolution operator" 4. OBJECT BINDING rather than "encapsulation" 5. OVERLOADING remains unchanged 6. CLASS remains unchanged
I use these substitutes for the other terms because they have a BASIC sound to them, whereas the other terms, like "scope resolution operator" may sound odd to BASIC programmers. DECLARATOR rings of BASIC's DECLARE statement, thereby reducing the foreigness of the term METHOD DECLARATOR. (In case you have forgotten, the :: is the scope resolution operator in C++, whereas the . is used in this theoretical object oriented BASIC of ours.)
Using this terminology, we have this model:
/ CLASS VectorClass ' This is a CLASS DECLARATION | X AS PRIVATE INTEGER ' This is a PROPERTY of VectorClass O B | Y AS PRIVATE INTEGER ' As is this B I | ' ^^^^^^ J N | ' Use of PRIVATE demonstrates DATA HIDING E D | ' Whereas use of PUBLIC demonstrates the oposite--\ C I | ' | T N | ' /-------------------------------/ G | ' VVVVVV | subSetVector AS PUBLIC SUB ' This is a METHOD \ END CLASS
' This operator is the METHOD DECLARATOR in this context ' | ' V D / SUB VectorClass.subSetVector ( X AS INTEGER, Y AS INTEGER ) E | M C | E L | T A | H R | O A | D T | I | O | N \ END SUB
2.2 An Introduction to Advanced Topics in OOP
To this point, most fundemental concepts of the object oriented paradigm have been examined. The reader should have a concept of class, object binding, method declaration, overloading, and data hiding, and should also understand the essence of how these object oriented extensions may be added to BASIC.
There are other considerations, however. When an object is created, for instance, how is it initialized? That is to say, how are its properties set to appropriate starting values? A typical standard BASIC program might accomplish this thus:
CALL subFontInit()
This is fine, but remember that there can be more than one OBJECT of the same CLASS as in this case:
DIM Helvetica AS FontClass DIM Courier AS FontClass DIM TimesRoman AS FontClass
Now, to initialize the data for each of these, we must do something like this:
CALL subFontHelveticaInit CALL subFontCourierInit etc.
In C++, there is away around this that we can adopt for BASIC use. In every class in C++ there is an implied "constructor." This is a new term. Essentially, the constructor is a method within the class definition that is executed whenever an object is created. For an example of this, consider this method declaration:
SUB FontClass.FontClass | | code to initialize object goes here | | END SUB
(Visual Basic programmers will recognize this as being analogous to the Load_Form event.) Note that the method declaration uses FontClass twice. This informs the compiler that it is dealing with the explicit definition of a CONSTRUCTOR.
In the actual binding declaration of the class, this syntax is suitable:
CLASS FontType | etc. | FontType AS CONSTRUCTOR | etc. | END CLASS
The CONSTRUCTOR type then, signifies that this template will be followed by a method declaration for a constructor. Now, when the programmer includes this code:
DIM Helvetica AS FontType
The compiler will include appropriate initialization routines.
Another aspect of this, the "destructor," is exactly the same, except that it operates after the object falls from scope. (Visual Basic programmers again will note the analagous use of the Form_Unload event.) Destructors deinitialize data, cleaning up things when the program ends execution, for instance. In C++, a special operator is used to indicate the deconstructor: ~FontClass. This use of the tilde is foreign to BASIC, however, so perhaps it would be better to introduce another keyword rather than a new operator:
CLASS FontType | etc. | FontType AS CONSTRUCTOR FontType AS DESTRUCTOR | etc. | END CLASS
Now, the method would simply be declared:
SUB FontType.FontType DESTRUCTOR | | code to deinitialize data structures goes here | | END SUB
This is syntacally familiar to a BASIC programmer in another form:
SUB subPrintToScreen (InText AS STRING) STATIC | | END SUB
The STATIC keyword modifies the nature of the SUBPROGRAM. Consquently, I have suggested the DESTRUCTOR keyword be used in a similar syntactical fashion.
3.0 Closing Notes
Indeed, BASIC has evolved from the time-sharing days of Dartmouth. Despite this evolution, however, major software compiler developers have failed to introduce object oriented extensions into the language. Perhaps this article has introduced some new concepts to the reader, perhaps not. At the very least, it has explored some ways an object oriented paradigm might be introduced successfully into BASIC programming with as little pain possible. Programmers tend to maintain their old programming habbits despite the innovations that come into their languages, and consequently, any major changes to the way BASIC operates may prove to be obstacles rather than useful tools. I feel that my suggestions involve minimal relearning of the syntax of BASIC, since they adopt the flavor of existing structures. In the end, though, the question is not what is the better method or terminology to use, really, but rather:
"Object Oriented BASIC, possibility or pipedream?"
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- BUILDING A LIBRARY -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
Writer: Chris Charabaruk
Instead of having some dumb ole introduction, I am going to cut to the chase here regarding making your own library. I'm sure you've thought of making something to compete with Future.Library or DirectQB, but for this article, I won't be dealing with making a library like those. Instead, I will describe to you how to create a library with your own routines or with other people's OBJ files.
A library is really nothing except a bunch of OBJ files each with one or more routines (read SUBs or FUNCTIONs) inside, none of which are called main (main is only found in executable files). Let's not discuss libraries for a minute though, I want to talk about these routines. In languages like C or C++, everything is in a routine, even the actual program. You see, when the operating system runs an executable file, it actually calls the main() routine inside the program. main() is like a FUNCTION, because it takes in an array of characters (a string) and returns an integer value. In C/C++ you write the main() routine like this:
int main() { // code goes here return 0 }
Each QuickBasic program that's compiled has this main() routine as well, except that by just using QB, you can't return an "errorcode" (the value that main() returns). There is a way, however, but I won't be telling you about it here. A SUB in QuickBasic is similar to a FUNCTION when written in C/C++, it looks like this:
void mysub() { // code goes here }
The "void" means that the routine does not return a value, unlike the main() routine above which returns an integer with value 0. Since mysub() is void, you can forget about putting in "return", or you can add "return null" which says to the compiler, return nothing. Now that we have covered routines, now we can go on to OBJ files.
An OBJ (object) file is nothing more than one or more routines ready to be put in a library or linked to make an executable file. When you use QuickBasic's compiler at the command line, all that it does is generate a bunch of OBJ files. Then, you have to use the LINK program to turn them into an executable, or LIB to make them into a library. If you use the options in QB or QBX's run menu, it does the entire process of compiling and linking/libbing for you, but you have less control over it all. I generally use the command prompt or a makefile to do the job for me.
So you have the routines, you've used the compiler to make the OBJ files (or you've found some OBJ files that you know have some useful stuff in them), now how to make it into a library you can use in QuickBasic? Well, actually, to make a library for QB, you have to use LINK as well as LIB. You need LINK to make the QLB file for running the program that needs the routines inside the IDE and the LIB file for compiling. So let's look at how to make this library.
LIB, according to it's online documentation that came with MASM 6.11, is to create and to maintain libraries of compiled or assembled object modules. LINK, on the other hand, is to create an executable program out of these OBJ files. So how does that help us make a QLB file? Well, the version of LINK that comes with QB4.5 and all of Microsoft's PDS packages have a /Q switch which tells LINK to make a QLB file instead of an EXE. So say that your routines are in the object files mysubs.obj and myfuncs.obj, to make a QuickLibrary named mylib.qlb, use this line at the command prompt:
LINK /Q mysubs.obj myfuncs.obj, mylib.qlb,,qbqlb.lib;
Or if you use QBX/PDS use this instead:
LINK /Q mysubs.obj myfuncs.obj, mylib.qlb,,qbxqlb.lib;
That makes the QLB file. To make the normal LIB file, you use LIB like this:
LIB mylib.lib + mysubs.obj + myfuncs.obj
So now you have your library. But later on, you have some more routines (new.obj) you want to add. So rename your mylib.lib and mylib.qlb files to myoldlib.lib and myoldlib.qlb, and do this to rebuild the QLB:
LINK /Q new.obj myoldlib.lib, mylib.qlb,,qbqlb.lib;
Or for QBX:
LINK /Q new.obj myoldlib.lib, mylib.qlb,,qbxqlb.lib;
To rebuild the LIB file:
LIB mylib.lib + new.obj + myoldlib.lib
So there you go. There's one more topic that I want to mention though, granularity. When you use a library, all the routines that share an OBJ with the ones you were using are included in the program. It's better to have each routine in it's own file because this way, only what is used from the library is incorporated into your program. On the other hand, if a routine in your library needs to call another included routine, then it's best to have them share a BAS file, and therefore a OBJ file.
Well, that's the basics of making your own library. If I was too confusing, then just email me at evilbeaver.picksoft@zext.net and I will help you further if I can.
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- INTRODUCTION TO QBASIC SERIES #3 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
Writer: Matthew River Knight
Welcome to the third part in the Introduction to Qbasic Series! We went over quite a lot of information last time, having covered some more of the capabilites of Qbasic with regard to the handling of text, demonstrated how to allow the user input from the keyboard, did some LOOPing, and we even took a brief look at how to display graphics in Qbasic. This time I'd like to cover some more of these simple things before we go onto more difficult areas of programming, hopefully picking up some information that we left out along the way.
First of all, let's take a look at what else we can do with Qbasic as far as text handling is concerned. For starters, it would be nice if we could place text at a specific location on the screen wouldn't it? Lucky for us, Microsoft wrote made a nice little routine to do this for us. This is the LOCATE statement. The LOCATE statement tells the computer at which location on the screen the next PRINTed text must be placed. You'll recall that last issue, in order to explain how PSET works, I described the screen as a grid. When using LOCATE you also have to think of the screen as a grid, but it works a little differently. The 'grid' that we're talking about when using LOCATE is not made up of pixel spaces, it is made up of much larger blocks that are basically the same size as your average ASCII character. And what is an ASCII character you ask? Well, all the letters and numbers you see on the screen in DOS are ASCII characters. Apart from the number and letter characters, there are also some other ASCII's which look quite strange. You have seen some of them before. Some of them are used by the Qbasic editor to draw the menu's and the scrollbar, etc. ASCII stands for American Standard Code for Information Interchange.
Another way of thinking about how LOCATE works is like this: suppose you wanted to put some text, for example "Qbasic rulz!", 5 ASCII spaces across the screen, and 10 ASCII spaces down. Then all you'd have to do is this:
LOCATE 10, 5 PRINT "Qbasic rulz!"
Try experimenting with different numbers and see what results you get. Using LOCATE will make your life much easier when using text! There are some other similar text handling routines, but there is no need to use them since LOCATE is generally capable of doing exactly the same as them, but is easier to use and more versatile.
Okay, now onto something more fun ^_^ Last time when we looked at using graphics in Qbasic we really didn't do very much. All we used was PSET and LINE - not too interesting. So we'll cover some better stuff this time round.
It is sometimes necessary that you know the color code of a certain pixel on the screen. You can actually do some pretty cool graphics effects just by knowing this simple information. In Qbasic, we use the POINT statement to obtain the color code of a certain pixel on the screen. The POINT statement is very versatile in the way that it may be used, and this makes it very easy to understand.
For example, if you wish to get the color of a pixel at the coordinates (2,5) on the screen 'grid' into the Col variable then all you do is this:
PSET (2, 5), 2 'Place a green pixel at coodinates (2,5) Col = POINT(2, 5) 'Get the color code of the pixel at (2,5)
You can also use POINT like this:
IF POINT(2, 5) = 2 THEN PRINT "That pixel is green"
Try playing around with this, and maybe you'll figure out how to do something cool using a combination of POINT and one of the other Qbasic drawing routines. I once wrote a little program using PSET and POINT that allowed you to print text on the screen, and it melted what looked like lava around it. There is no reason why you couldn't write a program to do exactly that!
Okay, now on to something a bit more cool. You have probably found that using LINE to draw graphics is rather difficult and time consuming. There is another Qbasic command for drawing that is much easier to use. It's called DRAW! It works quite unlike any other Qbasic routines we have covered so far, as you'll soon see.
The DRAW statement has its own commands for doing things. For example, it has the U command for drawing lines up, and the D command for drawing lines down. Let's demonstrate this by means of an example:
SCREEN 13 PSET (10, 10), 2 'Start drawing at (10,10) in green DRAW "U100" 'Draw a line up, 100 pixels in length
PSET (100, 100), 14 'Start drawing at (100,100) in yellow DRAW "D20" 'Draw a line down, 20 pixels in length
There are lots of commands used with DRAW, but you'll probably never use half of them. We'll cover the most commonly used ones. If you want to know the others then you'll have to take a look at the Qbasic help file.
You have probably already guessed that in order to draw right, you use the DRAW command R, and to draw left you use L. This should have been obvious. But what if you want to draw diagonally? Well, to draw diagonally up and right you use the DRAW command En, where n is a number specifying the pixel length of the line to be drawn. To draw diagonally down and right you use the commnd, F. To draw diagonally down and left you use G, and lastly, to draw diagonally up and left you use H. Simple no? Okay, here's an example:
SCREEN 13 PSET (160, 100), 2 'Start drawing at (160,100) in green DRAW "H20 E20 F20 G20"
Note that I have put a space between each of the DRAW commands in order to make it easier for you to read. You don't have to do it this way though. You could just as easily have left them out, and it would do exactly the same thing. Like this:
SCREEN 13 PSET (160, 100), 2 'Start drawing at (160,100) in green DRAW "H20E20F20G20"
It really doesn't make a difference.
Another usefull DRAW command is C which sets the DRAWing color. All you do is say Cn where n is the color code of the color in which you'd like to DRAW, and voila - you're DRAWing in that color. Could it really be any easier?! Let's demonstrate this by means of an example:
SCREEN 13 PSET (160, 100), 2 'Start drawing at (160,100) in green DRAW "C2 H20 C3 E20 C4 F20 C5 G20"
Once again you can see that I've put spaces between each of the DRAW commands in order to make this easier to read and understand. If I were writing a program in which readability was not a necessity, I'd have left the spaces out since they're a waste of space.
There are many other DRAW commands that can be used, but I'm not going to cover it all here, when there is a perfectly good description of all of them sitting right there in the Qbasic help files. One thing that I should mention before we go onto something else is that you are able to use DRAW with strings instead of specifying it right after the command. This allows you to save a lot of space. To clarify this further, here's an example:
Diamond$ = "C2 H20 C3 E20 C4 F20 C5 G20" SCREEN 13 PSET (160, 100), 2 'Start drawing at (160,100) in green DRAW Diamond$
In the above example you can clearly see that we have used DRAW with a string variable, instead of placing all the commands right after the DRAW statement. You will find this ability very usefull when you find the need to DRAW an object more than once. Without this ability you'd have to specify all those commands to draw the object again, and again, and again, which is really a waste of space.
Right. Now we've covered PSET, POINT, LINE and DRAW. With that you can, with some ingenuity, draw some fairly good graphics. But what if you want to draw a circle? Can you imagine writing the DRAW code for all of that! Yeah, it would be a total nightmare! Fortunately there is a Qbasic statement called CIRCLE which allows you to draw circles. It's actually a bit more versatile than that in the light that it can not only draw circles, but also ellipses, and fractions of circles/elipses. It's a really great little routine. So let's learn how to use it!
We'll start off using CIRCLE just to do simple stuff, and then we'll work our way up, step-by-step, finally getting onto some more difficult stuff.
Okay. In order to draw a yellow (color code 14) circle with a radius of 15 at the coordinates (160,100) on the screen 'grid' we'd type the following code:
SCREEN 13 CIRCLE (160, 100), 15, 14
Now that wasn't so hard, was it? Right, now that we've mastered the drawing of circles, I think it's time that we move onto ellipses. You may not have heard of an ellipse before, so this is what it is: an ellipse is, in the most simple terms I can think of, basically like a squashed circle! You can probably imagine what I mean. It can be squashed from the top, or from the side. It doesn't really matter. Any squashed circle is an ellipse!
Let's, for example, say you wanted to have a circle that looked squashed from the top. First thing you have to think about is how squahed you want it to be. If we wanted to squash the circle, from the top, by 50% then we'd do this:
SCREEN 13 CIRCLE (160, 100), 15, 14, , , 50 / 100
If we wanted it squashed by 70%, from the top, then we'd do this:
SCREEN 13 CIRCLE (160, 100), 15, 14, , , 30 / 100
Simple no? Unfortunately, although we are able to describe the squashing effect in degrees here, the CIRCLE routine doesn't actually think of it that way, and that makes life quite difficult when we want to squash a circle from the sides - the degrees thing doesn't work anymore. It actually works like this: the division (above it is 29/100) which is actually called the aspect describes the ratio of the x radius to the y radius (x;y). The defualt aspect ratio depends on the screen mode, but gives a visual circle in any graphics mode, assuming a standard monitor screen aspect ratio of 4:3.
Did that make you a little dizzy? Yep? Okay, well, then let's go about it this way...practise! Play around with this until you understand! That's the best way to understand programming. Just to get you started, here's an example program showing you how to squash a circle from the side:
SCREEN 13 CIRCLE (160, 100), 15, 14, , , 170 / 100
Play around with this and it'll soon make sense.
Okay, as I mentioned earlier, it is possible to draw fractions of the circle, but that is slightly mathematical so I'll leave that for another time. I think I've confused you enough for one day!
There are still other graphics routines to cover, but we'll have to leave that for another issue. We have said more than enough about graphics this month!
Okay, we'll cover one more aspect of programming before I leave you to play around with what you've learned this issue. Sound in Qbasic! Before we even start this I need to explain one simple thing - Qbasic is, ordinarily, only able to produce sound through the internal PC speaker. You probably didn't even know that you have a speaker inside your computer, but you do. In the old days of computing there was no such thing as sound cards, and sound amplifiers for the computer. Such things were unheard of. In those days everyone used to have to play sound through the little speaker inside the computer. Unfortunately the internal speaker is really junky. Any sound that comes from it sounds terrible. But, as a newbie, it's the only sound that'll be available to you for a while. There are ways to use the sound card in Qbasic, but it's really difficult, so we are not going to cover that at this stage.
Okay, I have rambled on long enough! We have quite a few Qbasic statements at hand which generate sound through the PC speaker. We'll start off with some simple stuff. It's fairly clear that the simplest form of sound we could possibly get the computer to do is just a beep. What could be easier than that? Well, there is a Qbasic statement called BEEP that does just that! BEEP generates a beep-like noise from the internal speaker at 800hz (800 cycles per second) for one-quarter of a second. It's really easy to use BEEP. Let's test it:
BEEP
Yep! That's it! That's all you have to type to use BEEP. Could anything be any easier than that?! Well congratulations, you've just mastered yet another Qbasic statement, and it's only taken you two seconds! ^_^
Okay, soon you're likely to get bored with BEEP. With BEEP you are not able to specify the pitch of the BEEP and the duration for which it plays. And that is no good. No good at all. Okay, well this isn't much of a problem. There is a Qbasic statement called SOUND which allows you to do exactly what BEEP won't allow you to do! The syntax of SOUND is:
SOUND frequency, duration
frequency is the pitch of the sound. For those of you who don't know what this means (get a dictionary!) here's some examples - an example of a low pitch would be thunder. It sounds deep and has a low pitch. An example of a high pitch would be the squeek of a mouse. It has a high pitch. The higher the number is in place of frequency, the higher the pitch of the sound. The frequency may not be lower than 37, and not higher than 32767.
duration specifies the amount of time the computer has to spend making the sound. It is measured in clock ticks. Clock ticks occur 18.2 times per second. The duration may not be less than 0, and not higher than 65535.
Ah, okay, now is a good time for an example, no?
SOUND 300, 70
If you really want to give yourself a headache (why would you want to do that?!) then run the following program:
SOUND 5000, 65535
I would not advise running that program. You'll get a terrible headache. You could easily write a program that would trick somebody into thinking there's a mosquito in the room ;)
Now the problem with this stuff is that it's really hard to write music with it. It's even harder to try and write sound effects. With music we are accustomed to sounds being described as A,B,C,D,E,F and G. Well here's a list of the equivelent frequency's for them! ^_^
I took this list from a book I have on Qbasic. For some reason there are a number of different A's, B's, C's...etc. I know nothing about music so I have no idea why this is the case. Those of you who know about music will be able to make more sense of this than I can ;)
Note Frequency Note Frequency
C 130.810 C* 523.250 D 146.830 D 587.330 E 164.810 E 659.260 F 174.610 F 698.460 G 196.000 G 783.990 A 220.000 A 880.000 B 246.940 B 987.770 C 261.630 C 1046.500 D 293.660 D 1174.700 E 329.630 E 1318.500 F 349.230 F 1396.900 G 392.000 G 1568.000 A 440.000 A 1760.000 B 493.880 B 1975.500
*Middle C.
By doubling or halving the frequency, the coinciding note values can be estimated for the preceding and following octaves.
To produce periods of silence, use the following statement:
SOUND 32767, duration
To calculate the duration of one beat, divide the beats per minute into the number of clock ticks in a minute (1092).
The following table illustrates tempos requested by clock ticks:
Tempo Notation Beats/Minute Ticks/Beat
very slow Larghissimo Largo 40-46 27.3-18.2 Larghetto 60-66 18.2-16.55 Grave Lento Adagio 66-76 16.55-14.37 slow Adagietto Andante 76-108 14.37-10.11 medium Andantino Moderato 108-120 10.11-9.1 fast Allegretto Allegro 120-168 9.1-6.5 Vivace Veloce Presto very fast Prestissimo
Like I said, I know nothing about music, and I thus have no idea what the above table is talking about. The only thing that I know about music is that it comes out of the radio! ;) Those of you who know about music will probably find the above information very usefull, but as for the rest of you who know no more about that stuff than me, don't worry about it. It's still possible to do cool stuff with SOUND without any knowledge of music.
There's another way to use music with the internal speaker in Qbasic, but we'll cover that (and other things) next issue. Until next month, just play around with what you have learned, and see if you can come up with something cool. If you make a little program, and you are proud of it and would like others to see it, then email it to me at horizonsqb@hotmail.com and I'll include with the next issue of the QBCM! Have fun with Qbasic!!! ^_^
Cya next month!
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- MANIPULATING SOUND IN QBASIC -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
(A while ago I found a bunch of ZIPs which contained posts from old Qbasic BBS's. Most of the posts were from 1991-93. A lot of the information in the ZIP's was either very primitive or was just code with no explanation of how it works. I did however find this one good tutorial which explains some very cool stuff about the use of sound in Qbasic. This document has been left in its original state. -ed)
From: EDWARD SCHLUNDER Sent: 07-24-93 05:18 To: MATTHEW MCLIN Rcvd: -NO- Re: FORMAT OF MOD, SAM (1/9)
<-=-=-=-=- Matthew Mclin to All -=-=-=-=> MM> Does anybody know the format of MOD/SAM/WAV/VOC file? Info on any MM> of those formats (how to read/write/play them using a PC Speaker or MM> LPT 1 with a mono DAC) would be greatly appreciated.
You know, you are quite lucky that I just decided to pickup the Pascal echo even though I'm not a Pascal programmer. I have ALL of these file formats! Lucky you! I have had to search high and low all over the place for this junk and you're getting it all in one shot.
Not only do I have those file formats, but I also understand how to play them back on the PC's Internal Speaker, LPT DACs, and Sound Blaster. I'll be posting that too.
I have been interested in this field for quite a while, that's how I gather up all this information. If I had enough ambition, time, and patience, I'd probably write a book on it all because there is not ONE SINGLE book that explains how to play digital sound directly (ie, without specail drivers), with such drivers, what the file formats are, and includes code to do all that stuff.
Gee, I bet that would make a lot of money, perhaps I should do that after all.... Those guys on the 80XXX Assembler echo would probably be able to do a better job as they are more knowledgable on this, but most of them are into writing demos and creating faster/better MOD players..
Ok, since this will take up a lot of room, I'll be splitting it up into seperate messages. The simpilest stuff goes in this message.
MM> I would also like info on raw sound data and how to edit/play it.
Newbe to Digital Sound, eh? Well, you've come to the right place for information, or rather, the right person has come to you. Ok, the basics. A digital sound file is basically just a bunch of volume settings. On the PC, a volume setting of 128 is normally silence. Values farther away from 128 in either direction are louder depending on its distance from 128. 0 and 255 are the loudest volumes. One thing I should make clear, 128 is not nessicarily silence. When making a recording, there is always background noise. So, what may sound like silence to you, is actually 126-130 or so.
Now, you have probably seen those neat little graphs that some programs make when displaying a digital sound file. VEdit (which comes with the Sound Blaster) shows the waveform in the modify part of it. If you wanted to display a graph yourself, you could just load in a byte from the file, then, use that byte for the Y location. The X location is where in the file you are at (which byte). You just keep loading in bytes until the end of the screen. I could go on and on, but this is just a message, not a book! Hmm, you said you wanted to play a digital sound file on the PC's Internal Speaker and on a printer port DAC. Well, here comes that part. I'll explain usage of printer port DACs first because they are easier to understand. To play a VOC, WAV, SND, etc file on the DAC, you just read in one byte from the file, output it to the printer port, and do it again but on the next byte. To get the I/O address of the printer port, read the word at memory location 40h:8h for LPT1, 40h:0Ah for LPT2, 40h:0Ch for LPT3, and if on a non-ps/2, 40h:0Eh for LPT4.
The internal speaker is a bit more tricky, you have to do certain things to set it up correctly before outputting sound. Before you do ANY sound output, you must do the following (sorry, I'm not a Pascal programmer, so this is in Assembler):
Out 43h, 0B6h ;Please make note: This code was Out 42h, 0FFh ;written by a friend of mine in Out 42h, 0 ;australia named Phil Inch. He Out 43h, 90h ;posted code in the 80x86 Assembler In ax, 61h ;echo (GTPN, not Fido) for the Or ax, 3 ;public domain. Thanks Phil!! Out 61h, ax
Ok, the above sets the timer chip up correctly. From there it is pretty simple. Get a byte from the sound file. Divide the byte by a 'shift' number (I'll explain about this later). Then, output this new byte to port 42h. Repeat this for the whole file.
Ok, now, about that shift value. The PC's Internal Speaker wasn't designed for playing digital sound on it, it's just that brainy guys like Phil have figured out how to do with software what should have been done with hardware.. Anyway, the PC's Internal Speaker isn't very loud, so the range of volumes is much less than on a Sound Blaster or printer port DAC. This shift value varies from computer to computer, it depends on the size of your speaker and other stuff. Genernally, a shift value of 4 works on all computers. On my computer, I can get anyway with 3 on most files. The smaller the shift value, the louder the file will be played, but too small a shift value will cause distortion. Experiment! After you are finished playing the sound file, you must put the timer chip back the way it was supposed to be, or otherwise the next program that tries to make a noise on the internal speaker will make the noise but will not stop! Here is the code for that (again, sorry about the Assembler, it's just that I'm not a Pascal programmer):
Out 43h, 0B6h In ax, 61h And ax, 0FCh Out 61h, ax
There, that should do it. I hope I haven't totally confused you. Please write back if you have ANY questions what-so-ever. Gee, I'm already on line 107, time to go to a new message!
<-=-=-=-=- Matthew Mclin to All -=-=-=-=> MM> Note that these .MOD MM> and .SAM files are in the Amiga Module format (just incase there are MM> any others). Oh, there's also the .SND files. Or even .MID/.MDI files MM> if you can play them thru a DAC on an LPT port or the PC Speaker. Note MM> that I don't have a Sound Blaster (or any other sound card). Thanks.
SAM Files:
As far as I know, these do not contain any header or specific structure. They are just raw sound files. The only trick you have to remember about these files are that they are signed, which means that when the 7th bit is set, the number is negative. When the 7th bit is clear, the number is positive. This is completely different from digital sound files that originated on the PC. Remember, MOD and SAM files originated from the Amiga, so they have this weird encoding.
To convert a signed file to an unsigned file, just read in one byte from the original file. Add 128 to that byte. Output the answer to a new file. In the Amiga world, a byte of 0 is equalivilent to silence. A byte of -128 (and +128) is as loud as it gets on the Amiga. On the PC, however, 0 (and 255) is as loud as it gets. A byte of 128 is equalivilent to silence on the PC. So, when we add 128 to a -128, we get a zereo, which is the same volume for a 128 on the Amiga.
The following text was written by Edward Schlunder and was based on information provided by Tony Cook on the GT Power Network's 80x86 Assmebler echo.
WAV File Format By: Edward Schlunder. 5-17-93
BYTE(S) NORMAL CONTENTS PURPOSE/DESCRIPTION
----------------------------------------------------------------------- 00 - 03 "RIFF" Just an identification block. The quotes are not included.
04 - 07 ??? This is a long integer. It tells the number of bytes long the file is, includes header size.
08 - 11 "WAVE" Just an other I.D. thing. 12 - 15 "fmt " Just an other I.D. thing.
16 - 19 16, 0, 0, 0 Size of header to this point. 20 - 21 1, 0 Format tag.
22 - 23 1, 0 Channels
24 - 27 ??? Sample rate, or (in other words), samples per second.
28 - 31 ??? Average bytes per second.
32 - 33 1, 0 Block align.
34 - 35 8, 0 Bits per sample. Ex: Sound Blaster can only do 8, Sound Blaster 16 can make 16. Normally, the only valid values are 8, 12, and 16.
36 - 39 "data" Marker that comes just before the actual sample data.
40 - 43 ??? The number of bytes in the sample.
VOC File Format:
This file format was written by Phil Inch on the 80x86 Assembler echo on the GTPN. Thanks Phil!!
BYTE(S) NORMAL CONTENTS PURPOSE/DESCRIPTION -----------------------------------------------------------------------
00 - 19 "Creative Voice File", 26 Just an identification block. The quotes are not included, and the 26 is byte 26 (1Ah) which is an end-of-file marker. There- fore, if you TYPE a VOC file, you will just see Creative Voice File.
20 - 21 26, 00 This is a low byte, high byte sequence which gives the offset of the first block of sound data in the file. Currently this is 26 ( 00 x 256 + 26 ) which is the length of the header, but it's probably good programming practice to read and use this value anyway in case the format changes later.
22 - 23 10,1 These bytes give the version number of the VOC file, subnumber first, then main number. The default, as you can see, is 1.10.
24 - 25 41,17 These bytes are "check digits". These allow you to be absolutely SURE that you are working with a VOC file. To use them, convert the version number (above) and this number to integers. Do this with the formula below, where for convention the above bytes have been listed as byte1, byte2.
(byte2*256)+byte1 Therefore, for the default values we get the following integers:
(1 x 256)+10 = 266 (17 x 256)+41 = 4393
When you add the two results, you get 4659. If you do these calcs and get 4659, then you can be almost certain you're working with a VOC file.
OK, that takes care of the header information. I hope you realise that I'll never get a registration for VOCHDR now! Oh well <sigh> perhaps people will buy my games!
Having gotten to byte 26, we now start encountering data blocks. There are eight types in all, conveniently numbered 0 - 7. For each block, the first byte will always tell you the type.
For notational convenience, bx means byte x, eg b5 means byte 5.
BLOCK 0 - THE "END BLOCK"
Structure: Byte 1: '0' to denote "end block" type
This block is located at the END of a VOC file. When a VOC player encounters a block 0, it should stop playing the VOC file.
BLOCK 1 - THE "DATA BLOCK"
Structure: Byte 1: '1' to denote "data block" type
2: \ 3: | These bytes give the length: 4: / b2 + (b3*256) + (b4*65536)
5: Sampling rate: Calculated as 1000000 / (256-b5)
6: Pack type byte: 0 = data is not packed 1 = data is packed to four bits 2 = data is packed to 2 bits 3 = data is packed to 1 bit
7: Actual sample data starts here
BLOCK 2 - THE "MORE DATA BLOCK"
Structure: Byte 1: '2' to denote "more data block" type
2: \ 3: | These bytes give the length: 4: / b2 + (b3*256) + (b4*65536)
5: Actual sample data starts here
The point of this is simple: If you have a sample that you want to chop up into smaller portions (the maximum block length in a VOC file is 16,842,751 bytes but who's counting?), then define a "more data" block. This "carries over" the previously found sampling rate and pack type byte, so a "data block" should have been encountered earlier somewhere along the line.
BLOCK 3 - THE "SILENCE" BLOCK
Structure: Byte 1: '3' to denote "silence block" type
2: \ 3: | These bytes give the length: 4: / b2 + (b3*256) + (b4*65536)
(Note that this value is usually 3 for a silence block.)
5: Duration ( b5+(b6*255) ). This gives the equivalent 6: number of bytes to "play" during the silence. 7: Sampling rate: Calculated as 1000000 / (256-b5)
A silence block is used for long periods of silence. When long silences are required, it's more efficient in size terms to insert one of these blocks, as seven bytes can then represent up to 65,536.
BLOCK 4 - THE "MARKER BLOCK"
Structure: Byte 1: '4' to denote "marker block" type
2: \ 3: | The length of the block, as usual 4: /
5: Marker value, as low-high (ie b5 + (b6*255) ) 6:
The marker block is read by CT-VOICE.DRV. When a marker block is encountered, the value in the marker value bytes (5 and 6) is copied into the status word specified when CT-VOICE was initialized.
This allows your program to judge where in the sample you currently are, thus allowing for progress counters and the like. It's also useful if you're trying to synchronize other processes to the playing of the sound.
For example, by using appropriate marker blocks, you could send signals to your software to move the lips of a person on-screen in time with the speech in the VOC. However, this does take some doing and a VERY good VOC editor!
BLOCK 5 - THE "MESSAGE BLOCK"
Structure: Byte 1: '5' to denote "message block" type
2: \ 3: | The length of the block, as usual 4: /
5 - ?: Message, as ASCII text.
?: 0, to denote end of text
The message block simply allows you to embed text into a VOC file. Presumably you could use this to detect when other people have pinched your VOC files for their own applications.
BLOCK 6 - THE "REPEAT BLOCK"
Structure: Byte 1: '6' to denote "repeat block" type
2: \ 3: | The length of the block, as usual 4: /
5: Number of times that data should be repeated 6: Total = 1 + b5 + (b6*255)
Every "playable" data block between a block 6 and a block 7 will be repeated the number of times specified in b5 and b6. Note that you add one to this value - the data blocks are ALWAYS played at least once. However, if b5 and b6 are zero, then you really don't need a repeat block, do you!
I'm told that you cannot "nest" repeat blocks, but I've never tried it. This limitation would only apply to CT-VOICE.DRV I would have thought, but it depends how good other VOC players are.
BLOCK 7 - THE "END REPEAT BLOCK"
Structure: Byte 1: '7' to denote "end repeat block" type
2: \ 3: | The length of the block, as usual 4: /
This, as explained, marks the end of the block of blocks (!) that you wish to repeat. Note that the "length" is always zero, so I don't know why the length bytes are required at all.
There, finally... Ahh. Well, next up is the MOD and SND file formats...
This was picked up off the 80XXX Assembler echo on FidoNet. There are many other file formats for MODs, but I have found this one to be most complete
Protracker 2.3A Song/Module Format: -----------------------------------
Offset Bytes Description ------ ----- ----------- 0 20 Songname. Remember to put trailing null bytes at the end... When written by ProTracker this will be only uppercase; there are only historical reasons for this. (And the historical reason is that Karsten Obarski, who made the first SoundTracker, was stupid.)
Information for sample 1-31:
Offset Bytes Description ------ ----- ----------- 20 22 Samplename for sample 1. Pad with null bytes. Will only be uppercase. The samplenames are often used for storing messages from the author; in particular, samplenames starting with a '#' sign will generally be a message.
This convention is a result of a player called IntuiTracker displaying all samples starting with # as a message to the person playing the module.
42 2 A WORD with samplelength for sample 1. Stored as number of words. Multiply by two to get real sample length in bytes. This is a big-endian number; for all PC programmers out there, this means that to get your 8-bit-orginated format, you have to swap the two bytes.
44 1 Lower four bits are the finetune value, stored as a signed four bit number. The upper four bits are not used, and should be set to zero. They should also be masked out reading; you can never be sure what some stupid program could have stored here...
45 1 Volume for sample 1. Range is $00-$40, or 0-64 decimal.
46 2 Repeat point for sample 1. Stored as number of words offset from start of sample. Multiply by two to get offset in bytes.
48 2 Repeat Length for sample 1. Stored as number of words in loop. Multiply by two to get replen in bytes.
Information for the next 30 samples starts here. It's just like the info for sample 1.
Offset Bytes Description ------ ----- ----------- 50 30 Sample 2... 80 30 Sample 3... . . . 890 30 Sample 30... 920 30 Sample 31...
Offset Bytes Description ------ ----- ----------- . 950 1 Songlength. Range is 1-128. 951 1 This byte is set to 127, so that old trackers will search through all patterns when loading. Noisetracker uses this byte for restart, ProTracker doesn't.
952 128 Song positions 0-127. Each hold a number from 0-63 (or 0-127) that tells the tracker what pattern to play at that position.
1080 4 The four letters "M.K." - This is something Mahoney & Kaktus inserted when they increased the number of samples from 15 to 31. If it's not there, the module/song uses 15 samples or the text has been removed to make the module harder to rip. Startrekker puts "FLT4" or "FLT8" there instead. If there are more than 64 patterns, PT2.3 will insert
M!K! here. (Hey - Noxious - why didn't you document the part here relating to YOUR OWN PROGRAM? -Vishnu)
Offset Bytes Description ------ ----- ----------- 1084 1024 Data for pattern 00. . . . xxxx Number of patterns stored is equal to the highest patternnumber in the song position table (at offset 952-1079).
Each note is stored as 4 bytes, and all four notes at each position in the pattern are stored after each other.
00 - chan1 chan2 chan3 chan4 01 - chan1 chan2 chan3 chan4 02 - chan1 chan2 chan3 chan4 etc.
Info for each note:
_____byte 1_____ byte2_ _____byte 3_____ byte4_ / \ / \ / \ / \ 0000 0000-00000000 0000 0000-00000000
Upper four 12 bits for Lower four Effect command. bits of sam- note period. bits of sam- ple number. ple number.
One thing you should keep in mind about MOD files is that they originated from the Amiga, so the samples are signed, see the discussion about SAM files for more information.
Note: Sounder and Sound Tool both use the same file extension, but have different file formats. To tell the difference, Read the first 6 bytes of the file. If it matches the magic number for Sound Tool .SND files, it is a Sound Tool file. Else, it's a Sounder file or a raw file.
Sounder File Format:
BYTE(S) NORMAL CONTENTS PURPOSE/DESCRIPTION
-----------------------------------------------------------------------
00 - 01 0, 0 Bits per sample. Ex: Sound Blaster can only do 8, Sound Blaster 16 can make 16. Normally, the only valid value is 0, which is the code for an 8 bit sample. Future versions of Sounder and DSOUND.DLL may allow 16 bit samples and such.
02 - 03 ??? Sampling rate. Currently, only 22 KHz, 11 KHz, 7.33 KHz, and 5.5 KHz are valid. If given a value like 9 KHz, it will be played at the next closest rate (in this case, 11 KHz). The sampling rate is calculated as follows:
SampRate = Byte1 + (256 * Byte2) 04 - 05 ??? Volume to play the sample back at. Note: On the PC's Internal Speaker, there is a definite upper limit as to the volume, depending on the shift value (see below). The Sound Blaster and the Disney Sound Source aren't quite as restricted, but still are at some high value.
06 - 07 4, 0 Shift value. This is the number that each byte is divided by to "scale" the volume down to a point where the PC's Internal Speaker can handle it. See the discussion on playing back digitalized sound for more details.
Information from Sounder text files and Sound Tool help (.HLP) files.
Sound Tool File Format:
BYTE(S) NORMAL CONTENTS PURPOSE/DESCRIPTION
-----------------------------------------------------------------------
00 - 05 "SOUND", 26 Just an identification thing. Helps a lot when you are trying to distinguish between Sounder .SND files and Sound Tool .SND files.
08 - 11 ??? This is the number of bytes in the sample. It is calculated as follows:
ByteSam = Byte1 + (256 * Byte2) + (512 * Byte3) + (768 * Byte4)
12 - 15 ??? This points to the first byte to play in the file. It is calculated the same way as the number of bytes in the sample (see above).
16 - 19 ??? This points to the last byte in the sample to play. Calculated the same as above.
20 - 21 ??? Sampling rate of the sample. Valid values are 22 KHz, 11 KHz, 7.33 , and 5.5 K, but if given a number not listed above, it will be played at the closest valid sampling rate. So, 9 KHz would be played at 11 Khz. This is calculated as follows: SamRate = Byte1 + (256 * Byte2)
22 - 23 ??? Bits per sample. Ex: Sound Blaster can only do 8, Sound Blaster 16 can make 16. Normally, the only valid value is 0, which is the code for an 8 bit sample. Future versions of Sounder and DSOUND.DLL may allow 16 bit samples and such.
24 - 25 ??? Volume to play the sample back at. Note: On the PC's Internal Speaker, there is a definite upper limit as to the volume, depending on the shift value (see below). The Sound Blaster and the Disney Sound Source aren't quite as restricted, but still are at some high value.
26 - 27 4, 0 Shift value. This is the number that each byte is divided by to "scale" the volume down to a point where the PC's Internal Speaker can handle it. See the discussion on playing back digitalized sound for more details.
28 - 123 ??? This is the name of the sample. It is followed by an ASCII 0.
Information from Sounder text files and Sound Tool help (.HLP) files.
Whoo! That was a TON of typing. WHOA!! I just literaly spend all night preparing those messages for you. I believe I started it around 12:00 am and now it's 5:00 am! Let me apologize if I made any mistakes in the previous messages, hard to type perfectly when your eye lids keep falling down <g>.
I don't know the file format for MDI and MID files, and I don't think that they can be played on the internal speaker or printer port DACs. Sorry!
Well, I hope I've answered all your questions, if you get anymore, just post to me! Have fun with the new information!
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- INTRODUCTION TO 3D SERIES #1 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
Writer: Matthew River Knight
Have you ever played Quake and wished you could make a similar game in Qbasic? Have you ever tried to learn how to code a 3d program in Qbasic, only to find that there are no good tutorials out there? Yep? Well then this tutorial is just for you! ^_^
I remember when I wanted to learn how to do 3d, it was a total nightmare. I went to every shop I knew of in search of a book - they had books on the subject, but they were all aimed at C programmers...there was nothing for Qbasic coders such as myself. Next I decided to search around the internet for a decent tutorial. I found several tutorials, but to my alarm, they were all so complicated! I'm sure that some of you have, at some stage, downloaded a certain 3d tutorial by Matt Bross. What a mistake that was eh?! It is a pathetic excuse for a tutorial. It consists only of extremely complicated and difficult to read code, then followed by a very brief explanation of what it does, and an even briefer explanation of how it goes about achieving this.
In order to fill the obvious void in the QB world for a decent 3d programming tutorial, I have decided to do a series about it right here in QBCM! We are going to take things step-by-step, progressing from the very simplest aspects of 3d programming, right up to the more complicated, but extremely fun stuff! ^_^
Don't worry if you aren't good at maths, or aren't a programming guru. I am going to explain things in such a way that they are going to be very easy to understand, regardless of your ability! I do however recommend that you have at least an intermediate knowledge of Qbasic. As far as the maths is concerned, there is a large misconception amongst the programming circles that in order to do 3d, you have to be a genius with mathematics. Not so. You will need to have a good knowledge of Algebra, Geometry, and Analytical Algebra. That's it. It does help to know Trigonometry, but it is not a necessity.
Okay, I have rambled on long enough! Let's start learning how to program in 3d!!! To begin with, we need to know how to define a point in a 3d world. You already know how to define a point in 2d - it's simple...you have your X coordinate to describe the point/pixel's location across the screen, and you have your Y coordinate to describe the point/pixel's location down the screen. In 3d it works exactly the same way, however, we have to add another coordinate: Z. The Z coordinate describes the depth, or in other words, how far the point/pixel is away from the viewer. In a 3d world, the exact center of the world is X,Y,Z = (0,0,0). So, a point that is 100 pixels to the left of the center of the 3d world would be at (-100,0,0). A point that is 50 pixels to the right of the center of the 3d world, 100 pixels higher than the center of the 3d world, and 10 pixels closer to the viewer than the center of the 3d world, would be at (-50,-100,10). All of the points in your 3d world are collectively called the world coordinates.
Using the above method, it is easy to create a 3d world. If, for example, you wanted a quadrilateral (such as a cube) in your 3d world, you would compose it by defining points which are the vertices of the object. Later on, when actually drawing the object on the screen, we connect all of the points/pixels together by using the LINE statement. This gives us what is called a wireframe representation of the object. But, before we can draw our 3d objects on the screen there's some stuff you have to know - we'll cover that at a later stage.
Once we have defined all of our world coordinates, we have to place the camera in the 3d world. The camera (also called the eye or the viewer) is what actually looks at the world. If you place it high above the 3d world, then you see the world in much the same way that it would be seen from a high tree, or a building. If you place the camera much lower down, then you will see the 3d world from an ants point of view! The camera's position is defined in exactly the same way as the world coordinates: you describe the camera's position in the 3d world with X, Y, and Z coordinates. It is important to note that the camera will ALWAYS 'look' to the center of the 3d world (0,0,0).
Now that we have defined our world coordinates, and have defined our camera position, we have to find a way to plot the 3d world on the screen, relative to the camera's point of view. Now the most obvious question is "how on Earth do I plot a 3d object on a 2d surface like the screen??!!" Well, let's take this one step at a time.
Okay, let's say we have defined a single point in a 3d world where:
x_world = 100 y_world = 20 z_world = 10
The first step in getting our 3d world onto the screen is changing these world coordinates into what is called the camera coordinates. The camera coordinates describe the world coordinates as they are seen from the camera's point of view.
Now, in order to change the world coordinates into camera coordinates, we have to use a set of mathematical formulae. "Nooooo!" I hear you say. Heheh, don't worry, it isn't difficult at all...and I am going to explain how it works. ^_^
Now let's think about this logically. Suppose we are in a 3d car racing game. Our camera (which is basically the same as the person inside our car in the game) is at (100,10,10) and is looking at the center of the 3d world (0,0,0) (you will recall that the camera in a 3d world will always look to the center of this world). With the camera at this position, the center of the world is at (-100,-10,-10). From this information, it seems that:
x_camera = x_world - camerapos_x y_camera = y_world - camerapos_y z_camera = z_world - camerapos_z
where:
x_camera = x coordinate of a point (camera coordinate system). Ditto with y_camera, and z_camera. x_world = x coordinate of a point (world coordinate system). Ditto with y_world, and z_world. camerapos_x = x coordinate, describing the position of the camera in the 3d world. Ditto with camerapos_y, and camerapos_z.
And guess what...those equations are actually the equations we use to convert world coordinates into camera coordinates!!! ^_^
It is important to note that you can place the camera anywhere in the 3d world, except for one place - you CANNOT place the camera in the center of the 3d world (0,0,0). You can put it anywhere but there. Why? Well, since the camera will always look to the center of the world, if the camera was in the center of the world it would be looking at itself. Can you look at your eyes??!! Nope, of course not (not without a mirror anyway.) So you can't put the camera at (0,0,0). If you do put the camera in the center of the world, then some of the mathematical formulae which we are about to describe will not work, and the program will crash.
Okay, so now we have converted our world coordinates into camera coordinates. But we still can't draw our 3d world/object(s) on the screen yet. We still have points which are described by X, Y, and Z, but the PSET and LINE statements can only draw with points defined by X and Y (which is 2d) - this is logical really...the screen is, after all, a 2d surface.
In order to draw in 3d on the screen using PSET or LINE, we have to convert our camera coordinates (which describe the 3d world coordinates relative to the camera's point of view) into 2d screen coordinates. How? Simple. We use a set of mathematical formulae!!! (Do I hear groaning?! ^_^)
Once again, we have to think about the problem at hand with a bit of logic and common sense. In real life, objects that are further away from us look smaller than they do when you are right next to them. The principle is the same with 3d computer graphics. For example, imagine that in our 3d world we have two points/pixels that are 10 pixels length apart. If their Z coordinates are both decreased by 1, then the two points are each going to move one pixel length closer to the other. This information demonstrates the golden wisdom of every 3d programmer: "The screen coordinates could be calculated by dividing the X and Y position through the depth (z coord- inate)."
This shown as a formula is:
x_screen = x_camera / z_camera y_screen = y_camera / z_camera
where:
x_screen = x coordinate of the pixel on the screen. y_screen = y coordinate of the pixel on the screen. x_camera, y_camera, z_camera = (see above).
However, we have several different screen modes at our disposal, and (0,0) on the screen is actually the top left hand side of the screen, so we have to change the formulae a little if we want the 3d object to be centered on the screen and if we want the program to work in all screen modes. The final set of formulae for conversion of camera coordinates to screen coordinates is as follows:
x_screen = (x_camera / z_camera) * srx + srx / 2 y_screen = (y_camera / z_camera) * sry + sry / 2
where:
srx and sry describe the screen resolution. In SCREEN 13, srx would be equal to 320, and sry would be equal to 200.
Another very important thing that I must mention is that if any of our points in the 3d world go behind the camera, or in line with the camera, then the math for projection of the camera coordinates onto the screen goes all haywire. This is logical really. In real life, if an object is behind you, or in line with your eyes on the side, then you cannot see it. This is the same with 3d computer graphics, but the problem is that the computer doesn't yet know that we must not be able to see these points, so it attempts to use the math on them anyway, and it goes nuts!!! ;)
We'll say that points in our world coordinates that are in line with or behind the camera are "out of range". In order to fix this problem, it is necessary that we do a test on all of the points in our 3d world (the world coordinates) to see if they are "out of range" and if they are, then we have to temporarily eliminate them. When they are back in range again, then we can put them back and have them plotted on the screen. Let's explain this another way: if the value of the world Z coordinate of a point is equal to camerapos_z or greater than camerapos_z then it is "out of range" and we must eliminate it (ie. we don't draw it!) Simple eh?! Here's some code for this:
IF x_world >= camerapos_z THEN 'Don't PSET this point!
Please note that when you are doing a 3d engine you should NEVER use PSET do draw everything in the world. That would be terribly slow. LINE is a much faster alternative, however, I used PSET here in order to make the explanation easier to understand.
Well, this concludes the first entry into the INTRODUCTION TO 3D SERIES. I hope you enjoyed it! Next issue we'll be covering 3d rotation, how to code a 3d program in which you can 'walk' about the world, and hidden surface removal. We'll also add some source code to help ya out! ^_^
Cya next month!
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- QB TIPS, HACKS, AND TRICKS -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
----------------- Using COM 3 and 4 -----------------
Writer: Matthew River Knight
One of the biggest reasons that we don't see many QB programs around that use the modem is that Qbasic is limited to using COM ports 1 and 2. The problem with this is that these days most people have internal modems, and most of these modems use either COM ports 3 or 4 - and Qbasic can't access them. However, fear not, there's a way around this! ^_^ Here it is:
EXAMPLE 1: Accessing COM3 from Qbasic.
DEF SEG = 64 'Move QB segment pointer to BIOS data area. POKE &H00,&HE8 'Change COM1 address in BIOS data area to COM3. DEF SEG 'Return to QB's DGROUP.
'Open COM3 by issuing OPEN "COM1:" command. OPEN "COM1:1200,N,8,1" FOR OUTPUT AS #1
PRINT #1,"ATDT844-1212" 'PRINT to COM port.
CLOSE #1 'Close COM port.
DEF SEG = 64 'Point to BIOS data area. POKE &H00,&HF8 'Restore COM1 address in BIOS data area to COM1. DEF SEG 'Return to DGROUP.
EXAMPLE 2: Accessing COM4 from Qbasic.
DEF SEG = 64 'Move QB segment pointer to BIOS data area. POKE &H02,&HE8 'Change COM2 address in BIOS data area to COM4. DEF SEG 'Return to DGROUP.
'Open COM4 by issuing OPEN "COM2:" command. OPEN "COM2:1200,N,8,1" FOR OUTPUT AS #1
PRINT #1,"ATDT844-1212" 'PRINT to COM port.
CLOSE #1 'Close COM port.
DEF SEG = 64 'Point to BIOS data area. POKE &H02,&HF8 'Restore COM2 address in BIOS data area to COM2. DEF SEG 'Return to DGROUP.
--------------------- Fun with the keyboard ---------------------
Writer: Matthew River Knight
Hmmm...how about we disable all input from the keyboard??!! ^_^
Heheh, this is a really nasty trick to do to someone...you could compile the following program and add a line to AUTOEXEC.BAT to run the proggy when the computer is next rebooted ;)
It could also make a great addition to a security program...if the wrong password is entered then you disable the keyboard ^_^
To disable all input from the keyboard: DEF SEG = 64 OUT 97, 204
To re-enable input from the keyboard: DEF SEG = 64 OUT 97, 76
As soon as the computer is rebooted, the keyboard will revert back to its normal self. However, that can always be fixed by running the proggy again! Heheh ;)
Last year I played this prank on my computer teacher...she went off to get some diskettes from the stock room, so while she was away I copied this program onto her computers hard-drive, and added a line to AUTOEXEC.BAT to automatically run it. Heheheheheh ^_^ If you also want to try this at school and you get caught, don't blame me! ^_^
That's all the tips, hacks, and tricks we have for ya this issue. If you know and cool QB tips, hacks, or tricks, then please share them with us by emailing them to me at horizonsqb@hotmail.com so I can print them in the next issue of QBCM!!!
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- BASIC MUSEUM -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
Writers: Daniel P.Hudson, Jean E.Sammet, and Matthew R.Knight
Over the last two issues of QBCM we have covered the history of raytracers/ raycasters, and the history of 3d in BASIC, however we have yet to actually look at the history of BASIC itself. Thusly, in this month's BASIC MUSEUM, we are going to take a brief look at how the BASIC cult began ^_^
BASIC (standing for Beginner's All Purpose Symbolic Instruction Code) is a system developed at Dartmouth College in 1964 under the directory of J.Kemeny and T. Kurtz. It was implemented for the G.E.225. It was meant to be a very simple language to learn and also one that would be easy to translate. Furthermore, the designers wished it to be a stepping-stone for students to learn one of the more powerful languages of that time such as FORTRAN or ALGOL.
Bill Gates and Paul Allen had something different in mind. In the 1970's when M.I.T.S.'s Altair personal computer was being conceived Allen convinced Gates to help him develop a BASIC language for it. When M.I.T.S. answered with interest, the future of BASIC and the PC began. Gates was attending Harvard at the time and Allen was a Honeywell employee. Allen and Gates licensed their BASIC to M.I.T.S. for the Altair. This version took a total of 4K memory including the code and data used for source code!
Gates and Allen then ported BASIC to other various platforms and moved back to their hometown of Seattle where they had attended grade school together. It was at this time that the Microsoft Corporation began it's reign in the PC world. By the late 70's, BASIC had been ported to platforms such as the Apple, Commodore and Atari computers and now it was time for Bill Gates's DOS which came with a BASIC interpreter.
The Commodore 64 was probably the most popular home-computer throughout the 80's. Almost every game ever made for the machine was coded with a mixture of BASIC and ASM.
The IBM-DOS version of this interpreter became known as BASICA, and at the time IBM was in major competition with clones so it was setup to require the BIOS distributed with IBM computers. The version distributed with MS-DOS was GW-BASIC and ran on any machine that could run DOS. There were no differences between BASICA and GW-BASIC which seems to make IBM's idea useless.
Microsoft realized just how popular their BASIC interpreter was and decided to distribute a compiler so users could code programs that ran without an interpreter. QuickBASIC was the solution Microsoft came up with. It was distributed on through the years until version 4.5. At this time Microsoft decided to release a product with more kick and started distributing PDS BASIC (Professional Development System) and ended it with version 7.1 (Also called QuickBASIC Extended), PDS was a short lived idea and was not followed through to its true capabilities. [Though it was an improvement over QB4.5]. Microsoft got hooked on GUI's and started Visual BASIC both a DOS and WIN version. The DOS version was ended at 1.0 with a professional update. Differences between VB for DOS and QB are not as much as one might think, in fact VB still compiles QB4.5 code and the professional edition will compile PDS7.1 Code. One last thing: PDS will compile to true OS/2 code, VB-DOS Pro/std and QB4.5 will not.
BASIC is still in a state of change and progression. Visual BASIC is still being updated and improved, and a number of other variants of this great language are also being churned out from various sources. Although many people may currently say that the future of BASIC lies within the Windows OS, it is likely that this is not so. Linux has grown enormously in popularity over the past few years, and is continuing to do so. It is more than likely that in a few years Linux will be the OS of choice. It is difficult to say exactly where the future of BASIC lies, however, the obvious pro's of the language, along with its vast user base who's love of the language exceeds a hundred fold what it visible with other languages, should be more than enough to keep BASIC as one of the leading programming languages for decades to come.
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- REVIEWS -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
We have three programs for review this month. So check out these reviews to see if they're da bomb or a bomb ^_^ If you have a Qbasic program that you would like us to review, then please email me at horizonsqb@hotmail.com and tell me the URL of the website where I can get the program from. Please DO NOT email your program to me!
------------------------------------------------- Program: Botwarz Program type: Game Developer: Psycorey Minimum system: 286, 640KB RAM Recommended: As above Available at: http://www.angelfire.com/nh/elfingo -------------------------------------------------
You own a copy of QuickBASIC. You walk up to me in the street and prod me gently but firmly in the chest with a well manicured forefinger. "Hey fella," you demand aggressively, "which is the best two player shooter currently available on God's own recently resurrected programming platform?" I would have no hesitation in recommending several releases to you, The Labyrinth, Space War, even my own Qbasic Tank Commander II (at a push) but crucially, I would stop short of urging anyone to go out and spend a minute downloading Botwarz.
Botwarz, as the name suggests, is a two player game in which you and a friend (or foe) take the role of robots, and go around trying to shoot eachother up. There is also a one player mode, however, it really isn't worth playing, and the two player mode is far more enjoyable. One of the coolest things about this game is the number of different weapons you have at your disposal. In addition to that, you have a wide selection of different robots to choose from, which makes the game a lot more interesting than it would otherwise have been.
The graphics are of a VERY low standard. Although the game uses SCREEN 13, there is no shading, no form of lighting in any way, and it is impossible to see what any of the graphics are actually supposed to represent. There is no background graphics either, just a plain black screen, which is very boring indeed. The animation, while fast and flicker free, shows absolutely no effort whatsoever. Just static bots sliding across the screen...yawn! As far as the sound department is concerned, well, I don't really know. There are a number of WAV's included with the game, but I couldn't get the sound to work on my computer, so I can't really say. It does seem pretty odd, I must confess, that the sound never worked...I have a Sound Blaster 16 sound card...that's pretty much standard in the computer world...
And this, my friend, is the graphics...need I really say more?
Okay, so now we know the graphics and sound aren't any good...how about the gameplay? Argh, I wish you hadn't asked! Let's start with the one player mode...well, as I have said before, it isn't really worth playing. There's no plot, no mission, no real levels...so it's all pretty pointless. The two player mode is a little better...at least it is worth playing, but the controls are unwieldy, and the poor graphics, and in my case lack of sound, makes even the two player game a torture to play. And anyway, there's many two player Qbasic games out there, much higher in quality...so why bother with Botwarz in the first place?
I must admit, I hate to give programmers bad reviews on their games, and I always try to like something about the game, but Botwarz was depressing from the very beginning...sigh...you can't say I didn't try to like it :(
QBCM VERDICT: 12%
--------------------------------------- Program: Diamond Fighter IV Program type: Game Developer: Master Creating Minimum system: 486DX2/66, 4MB RAM Recommended: Pentium 100, 4MB RAM Available at: http://master-creating.de ---------------------------------------
Master Creating is a fine old development company who've been turning out classic games since the days of the Spectrum and Commodore 64. Shadow of Power was theirs, and now they're back with Diamond Fighter IV. The Diamond Fighter series goes back a long way. The first episode in the series was in fact created on a Commodore 64 almost a decade ago!
Diamond Fighter is an interesting piece of work in the light that it's a puzzle game - a genre that has been subject to much neglect in the QB world since its very birth. The game throws you into futuristic USA, where the aim of the game is to complete each of the various levels in order to complete the game. Completing a level is easier said than done! It involves solving a sequence of puzzles in order to get to various parts of the level, whilst collecting all of the diamonds found there. Once you have collected each diamond, then you may proceed to the next level. There are many levels in the game (each state in the USA represents a level) and they are all nicely varied and have been realised with much care and imagination.
The graphics in DF are of an exceptional standard. Most of the graphics is in SVGA, and surprisingly, it runs at an acceptable pace on even the most humble of systems. All of the artwork is nicely detailed, and there's also some nice effects such as flashing lights, various lighting effects, and many others, which look very cool indeed. There's one effect where the aliens sort of melt into a fleshy pile on the floor, which I found pretty amusing...if not disgusting! As would be expected with a game from Master Creating, the sound effects are utterly fabulous. There's also a bit of speach now and again, which is all in German, but has been nicely done. My only reservation is that there's no music in the game. I usually find that some catchy tunes add immeasurably to the enjoyment derived from a game.
Diamond Fighter IV is an addictive game, and has been produced with quality in mind. You may find it difficult to figure the game out at first, however, it shouldn't take you long, and once you have mastered the basics, this game will be a big favourite in your collection. Highly recommended.
QBCM VERDICT: 93%
--------------------------------------------------------------- Program: Novascript 1.0 beta Program type: Programming language Developer: CarterSoft Minimum system: 386, 640KB RAM Recommended: As above, or better Available at: http://www.cole_carter.tripod.com/novascript1.zip ---------------------------------------------------------------
Yet another Qbasic contender steps into the ring. Take your places gentlemen. ^_^
Qbasic is, politely spoken, starting to get a bit, well, old, and sooner or later we are going to going to either need a new version, or convert to some other variant of BASIC. Many are in production right now, and while the Qbasic world merrily twiddles its thumbs waiting for Nekrophidius' QuickPower, Novascript intends to dash swiftly into the gap.
As Qbasic contenders go, NS scores and fails on various points. It scores because it is quite a bit faster than Qbasic in every respect, 386+ registers and opcodes are used, and users of the language will be able to get help easily since NS's creator can easily be contacted through email or via #qbchat. However, NS fails on various points too. For one, NS cannot be called a true variant of BASIC. The code resembles C in many ways, and for this reason, it's unlikely that QB users will want to convert to it. Here's some example NS code serving as proof of its similarities to C:
-- Press Shift + F5 to execute this example -- Let's the user type in musical notes -- 8:14AM 4-3-00 -- You have a royalty-free right to use, modify, reproduce -- and distribute the sample applications provided with -- Novascript for MS-DOS (and/or any modified version) -- in any way you find useful, provided that you agree that -- CarterSoft has no warranty, obligations or liability for -- any of the sample applications or. Declare; music$ Begin{ Printw; Type in some musical notes itext = Notes... input music$ !notes$ music$ printw; Thank You for playing Music end }
The way in which the code is used is also very inconsistent which makes the language difficult to learn. Another problem is that there is no real syntax or anything in NS for many of the commands. For example, to use the LINE statement, you first have to define variables for drawing the line from one point to the other, for the color of the line, etc. Here's a bit of example code:
-- ============================= -- This Sample graphic examples -- (c) 2000 CarterSoft -- ============================= Begin{ cls scrn 2 Printw; Circle C1# 320 C2# 100 C3# 200 delay cls scrn 13 Printw; Line L1# 0 L2# 100 L3# 319 L4# 100 deLay cls scrn 1 Printw; Triangle Pict C2 Pict F60 L120 E60 Pict BD30 Pict P1,2 delay end }
Lastly, the language is not as versatile as Qbasic, and the user doesn't have quite as much power, or control over the machine. However, it's more than likely that the missing features will be there in the full version. The IDE itself is very much like the one employed by Qbasic. It is pleasing to use, and easy to master. There is also some fairly good documentation on the language, however, the online help feature has yet to be implemented.
Well, NS 1.0 beta is far from complete, so I'll, for the most part, reserve my judgement until the final release. The speed of NS, and the high quality IDE, makes NS a promising project, and one certainly worth keeping an eye on. Be sure to check it out!
QBCM VERDICT: 72%
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- COMPETITIONS -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
--------------------------- QBCM mini game of the month ---------------------------
This is a competition for the best Qbasic game under 40KB ZIPed. The best game I recieve will be included with the next issue of QBCM! In order to enter this competition, simply email your game to me at horizonsqb@hotmail.com This month's winner is Joe King for his little game, Toadman. Congrats! It has been included with this issue of QBCM...it's TOADMAN.ZIP!
Those of you who entered your games and didn't win, don't worry. They are automatically re-entered into the competition for the following month, so who know...maybe you will be the winner next month! ^_^
------------------------- QBCM website of the month -------------------------
This is a competition for the best website dedicated to Qbasic. To enter, simply email the URL of your site to me at horizonsqb@hotmail.com. This month the winning site is Chris Charabaruk's site: http://picksoft.zext.net Congratulations!
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- CLOSING WORDS -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
I must say that I thoroughly enjoyed compiling this issue of QBCM. I hope it has been just as much fun for you to read it! It really amazes me how much this magazine has grown over the past three months. I certainly have no doubt that it will continue to do so!
Once again, please email any articles, letters, etc to me at horizonsqb@hotmail.com. Any input you can provide will be greatly appreciated. :)
Thank you for reading issue 3 of QBCM! Keep codin' in QB!
Cya next month!!! ^_^
-Matthew River Knight