Volume 2, Number 3 September 15, 1991 ************************************************** * * * QBNews * * * * International QuickBASIC Electronic * * Newsleter * * * * Dedicated to promoting QuickBASIC around * * the world * * * ************************************************** The QBNews is an electronic newsletter published by Clearware Computing. It can be freely distributed providing NO CHARGE is charged for distribution. The QBNews is copyrighted in full by Clearware Computing. The authors hold the copyright to their individual articles. All program code appearing in QBNews is released into the public domain. You may do what you wish with the code except copyright it. QBNews must be distributed whole and unmodified. You can write The QBNews at: The QBNews P.O. Box 507 Sandy Hook, CT 06482 Copyright (c) 1991 by Clearware Computing. The QBNews Page i Volume 2, Number 3 September 15, 1991 ---------------------------------------------------------------------- T A B L E O F C O N T E N T S 1. From the Editor's Desk Greetings From The Microsoft Developers Conference ........... 1 2. Ask The Doctor QB's Hidden Data Copying by Ethan Winer ...................... 2 3. Advertisement GFA-BASIC for MS-DOS and Windows 3.0 ......................... 6 4. View Print Spill the Wine By J.D. Hildebrand ............................ 7 5. Graphically Speaking Fonts in a Can by Larry Stone ................................ 12 Manipulating Sprites in Screen 13 by Fred Sexton Jr. ......... 17 6. Who ya gonna call? CALL INTERRUPT A Bug Free CALL INTERRUPT by Cornel Huth ..................... 21 7. The QBNews Professional Library An Event-driven Mouse Support Libary by Tony Elliot .......... 23 8. Advertisement E-Tree Plus By EllTech Development, Inc. ..................... 37 9. Algorithms Cardans's Method for Solving Cubes by Richard Jones .......... 39 10. Fun and Games Lines With Style by Larry Stone and Charles Graham ........... 46 11. EOF Receiving The QBNews ......................................... 48 Submitting Articles to The QBNews ............................ 49 The QBNews Page ii Volume 2, Number 3 September 15, 1991 ---------------------------------------------------------------------- F r o m t h e E d i t o r ' s D e s k ---------------------------------------------------------------------- Greetings From The Microsoft Developers Conference I've just returned from the Microsoft Developers Conference held in Seattle at the end of August. I'm sure it was a good time for all who attended. It was nice to finally meet some of the people I've been speaking to electronically over the years. For those who didn't or couldn't attend, I recommend you try to make the next one when ever it is held. The good news from Seattle came during Bill Gates keynote address. When asked point blank if Microsoft has abandoned QuickBASIC for DOS to concentrate on their Windows products, BG said no. In fact, he stated that Microsoft was currently working on an upgrade to QuickBASIC, and I believe him. So here is your chance. I want you to send in you suggestions on what should be added to the next QuickBASIC. I'll compile the best suggestions and see that Microsoft receives them. I'll also publish the top 10 in the next QBNews. With a little luck, we may get some of our suggestions incorporated into the next release. When making your suggestions, use all the new features included with BASIC 7.1 PDS as the baseline as to what will be in the next QuickBASIC. Also, keep in mind that QuickBASIC is a low end product. Therefore, you aren't likely to see ISAM or OS/2 support ever. They will always be contained in the PDS version of BASIC. Look forward to hearing your suggestions. Until next time, happy reading. Dave Cleary - Editor of The QBNews The QBNews Page 1 Volume 2, Number 3 September 15, 1991 ---------------------------------------------------------------------- A s k T h e D o c t o r ---------------------------------------------------------------------- Ask The Doctor Dear Dave, Enclosed are two sets of functions which are in effect identical, but their differences cause a strange phenomenon. The code in Listing 1 appears to me to be much more involved and confusing. Constantly re assigning variable names from one to another and GOSUB'ing to a called routine. The code in listing 2 directly calls the called routine without using an intermediate variable. This code is more readable, this code is tighter, this code is smaller, this routine runs microscopically faster. When I slow my 386 to an effective 8mhz and I run the routines in a FOR 1000 NEXT Loop, you will notice a time savings of a wonderful 3 seconds. This code compiles 502 bytes LARGER !. . . . Something is not right. . . Obviously I am missing something. . . I am not one to quibble over milliseconds, I will sacrifice the 502 bytes for readability, but I would really like to understand why this happens. Richard Gray [See listings at end of the article] Ethan Winer responds, Richard, What you have discovered is BASIC's hidden copying of data, and in your program this happens in two different but related ways. In the first program only variables are being passed to the ConcaveFrame and QPrint routines. The second program passes both numeric and text constants (that is, literal numbers and quoted strings), and also numeric and string expressions. Regardless of whether you are passing constant data or an expression, BASIC must make a copy of the data, place it somewhere in memory, and then pass the address where the result was stored. In contrast, when a variable is passed to a SUB or FUNCTION procedure, BASIC can simply obtain the variable's address and place it on the CPU stack. The called routine may then read the data at that address, or assign a new value based on the statements in your program. But a constant or expression must be protected from being changed. Consider The QBNews Page 2 Volume 2, Number 3 September 15, 1991 the following CALL statement and SUB definition: CALL MySub("Test", 123) ... ... SUB MyProc(Work$, Value%) Work$ = "Hi mom!" IF Value% > 100 THEN Value% = 100 END SUB BASIC can't allow your program to alter either of the incoming parameters, since that could affect other parts of the program that use the same constants. When the same quoted string is used more than once in a program, BASIC stores a single copy of the data. For example, if you have the statement PRINT "Insert disk in drive A" four times in your program, the quoted text is added only once. If BASIC allowed your program to assign new characters to this constant, the other PRINT statements would display the wrong text! The same goes for numeric constants. If BASIC stored the number 123 in memory and passed that address, a subprogram that changed its incoming parameter would change the number when it is used in other places in your program. Therefore, BASIC always makes a copy of constant data before sending it to a SUB or FUNCTION procedure. A similar situation occurs with data expressions. When you pass the expression LCol + 1 as an argument to a subprogram, BASIC must calculate the result and store it somewhere. And when passing the expression STRING$(Length, "Ä") + "Ù" to QPrint, BASIC again has to save the result somewhere before passing it on. To make matters worse BASIC also adds code to delete the temporary strings it creates, to release the memory they occupy when they are no longer needed. In your case you assigned Temp$ manually, and did not clear that string each time after using it as BASIC would. When fixed-length strings are passed as parameters the situation is even worse still. In that case BASIC first copies the fixed-length string to a regular string, passes the copy to the routine, makes the call, copies the data back in case the routine changed it, and finally erases the temporary string. I have a few suggestions that you may find useful. First, if you have a copy of Microsoft's CodeView debugger you will find it fascinating to trace through the assembly language instructions that BASIC creates. Indeed, this is precisely how I learned about BASIC's hidden data copying. You should also consider buying my book "PC Magazine's BASIC Techniques and Utilities" which is published by Ziff-Davis Press. This book is available at your local Waldenbooks and B. Dalton book stores, and in it I go into great detail about BASIC's internal workings. There are also many performance and code size optimization tips, as well as numerous other advanced BASIC topics. --Ethan Winer, Crescent Software The QBNews Page 3 Volume 2, Number 3 September 15, 1991 LISTING 1 --------------------------------------------------------------------- DEFINT A-Z DECLARE SUB SetColor (Colur%) DECLARE SUB ConcaveFrame (TRow%, LCol%, BRow%, RCol%) DECLARE SUB QPrint (Text$, Row%, Col%, Colur%) TRow = 8 BRow = 22 LCol = 7 RCol = 73 CLS ConcaveFrame TRow, LCol, BRow, RCol SUB ConcaveFrame (TRow, LCol, BRow, RCol) STATIC Length = RCol - LCol - 1 R = TRow C = LCol Clr = 120 Txt$ = "É" GOSUB ShowIt1 C = LCol + 1 Txt$ = STRING$(Length, "Í") GOSUB ShowIt1 C = RCol Clr = 127 Txt$ = "¸" GOSUB ShowIt1 FOR N = TRow + 1 TO BRow - 1 R = N C = LCol Clr = 120 Txt$ = "º" GOSUB ShowIt1 C = RCol Clr = 127 Txt$ = "³" GOSUB ShowIt1 NEXT R = BRow C = LCol Clr = 120 Txt$ = "Ó" The QBNews Page 4 Volume 2, Number 3 September 15, 1991 GOSUB ShowIt1 C = LCol + 1 Clr = 127 Txt$ = STRING$(Length, "Ä") GOSUB ShowIt1 C = RCol Txt$ = "Ù" GOSUB ShowIt1 EXIT SUB ShowIt1: QPrint Txt$, R, C, Clr RETURN END SUB LISTING 2 --------------------------------------------------------------------- DEFINT A-Z DECLARE SUB SetColor (Colur%) DECLARE SUB ConcaveFrame (TRow%, LCol%, BRow%, RCol%) DECLARE SUB QPrint (Text$, Row%, Col%, Colur%) CLS ConcaveFrame 8, 7, 22, 73 SUB ConcaveFrame (TRow, LCol, BRow, RCol) STATIC Length = RCol - LCol - 1 QPrint "É" + STRING$(Length, "Í"), TRow, LCol, 120 QPrint "¸", TRow, RCol, 127 FOR Row = TRow + 1 TO BRow - 1 QPrint "º", Row, LCol, 120 QPrint "³", Row, RCol, 127 NEXT QPrint "Ó", BRow, LCol, 120 QPrint STRING$(Length, "Ä") + "Ù", BRow, LCol + 1, 127 END SUB If you have a question for Ask The Doctor, please submit it to: The QBNews P.O. Box 507 Sandy Hook, CT 06482 The QBNews Page 5 Volume 2, Number 3 September 15, 1991 ---------------------------------------------------------------------- A d v e r t i s e m e n t ---------------------------------------------------------------------- ONLY ONE PROGRAMMING LANGUAGE LETS YOU CROSS DEVELOP FOR WINDOWS 3.0 AND MS-DOS WITHOUT REWRITING CODE. INTRODUCING GFA-BASIC GFA-BASIC gives you a simple, but powerful language for developing sophisticated, state-of-the-art Windows 3.0 and MS-DOS applications. Write a program with either the DOS or Windows version of GFA-BASIC and port it to the other platform-Doubling the fruits of your effort and maintaining a common look and feel between both platforms. One Set of Source Code ---------------------- Both versions of GFA-BASIC include 500 system and mathematical commands and functions to facilitate software development. At the same time, common commands and functions enable you to develop and maintain a single set of source code for each program. Simplifies GUI Development -------------------------- Another 400 commands and functions in the Windows version simplify the development of a complete GUI interface, including clipboard, DDE, DLL's and dialog boxes. And, you don't need any additional libraries or the SDK In the DOS version, a subset of the same commands and functions lets you bring Windows-like programs to AT and XT-class PC's without using any additional tools. HALF PRICE INTRODUCTORY OFFER list intro GFA-BASIC for Windows 3.0 $ 495 $ 295 GFA-BASIC for MS-DOS $ 295 $ 195 Both Windows & DOS versions $ 790 $ 395 Leverage your existing knowledge of BASIC and with GFA you can write more powerful Windows and DOS programs easily right away. To see for yourself how powerful GFA-BASIC is, just ask for our free Demo Disk - or better yet, order either or both products including all documentation on a trial basis with a full 100% money-back guarantee. Call 1-800-766-6GFA or write GFA Software Technologies, Inc. 27 Congress Street Salem, MA 01970 Fax: 1-508-744-8041 VISA/MasterCard accepted The QBNews Page 6 Volume 2, Number 3 September 15, 1991 ---------------------------------------------------------------------- V i e w P r i n t ---------------------------------------------------------------------- Spill the Wine By J.D. Hildebrand The editors of this publication have asked me to share my observations about language choices, and about where BASIC fits into the world. It's my pleasure to toss a few thoughts into the ether. I hope you'll respond with your own ideas. I've edited computer magazines for nearly a decade. It's a pretty good job, but people do hit you up quite a bit for free advice. "Should I buy a Mac or a PC?" they ask. (Buy a PC.) "Should I upgrade to UNIX?" (Sure, if you don't like software.) "How much memory and disk space do I need?" (Fifty percent more than you can afford.) These questions come up at cocktail parties, usually at trade shows. I'm attending in my official guise as Editor In Chief, which means I've got a tie on and I've taken the trouble to shake hands with the PR director for the sponsoring company, plus whoever he wants to impress. (PR people accumulate brownie points--and earn commissions, for all I know--by getting magazine editors to shake hands with company executives. The theory is that the executives will think the PR guy is so chummy with the editor that the company's products will gain favorable attention within the editor's magazine. It's all very cozy. Since neither the PR guy nor the executives ever bother to read the magazine, it's quite harmless to let them go on believing what they want to believe.) So I'm balancing a plate of fresh shrimp and oysters (they do feed editors well at these shindigs) in one hand while holding a glass in the other...and wondering how I'll manage to light a cigarette with both hands full...when some door-crasher comes wandering over and squints at my name badge. His eyes light up when he sees he's cornered an editor. "So you work for a programming magazine," he says, "even though it's one I've never heard of. So you must be some kind of expert, right? What programming language should I use?" I sigh, resisting somehow the urge to make a too-frank comment about the door-crasher's complexion. It's the notion that I have something valid to say in response to this question that gets me invited to these spill-wine-on-the-carpet bashes, after all, and ultimately make it possible for me to pay the mortgage. So I look both ways to make sure no one is listening, lean toward the door-crasher, and whisper confidentially: "Use the language you know." "Huh?" "Look," I continue, "all programming languages do pretty much the same things. It's amazing how alike they are. The main difference among them, as far as you're concerned, is that you know one of them and The QBNews Page 7 Volume 2, Number 3 September 15, 1991 you're considering a jump to another. Don't do it. Your knowledge of one language's idiosyncracies is at least as valuable as the flashy features some other language might offer. Use the language you know and don't apologize for it." The door-crasher sips his drink (his glass, I notice, is still nearly full, while I'm stranded across the room from the bar with a tumbler full of ice cubes) and shakes his head. "No," he says, "I really want to learn a new language. Which should it be?" I know what he's doing...he's waiting for me to say he should learn C. Years ago, I might have. C was, after all, the fastest-growing language on the PC platform. But those days are gone. This year, for the first time since C challenged Pascal in the middle of the last decade, the C market has stopped growing--even, I'm sure, begun to shrink. So I couldn't just tell d-c to learn C, nor C++ (while C was the clear successor to Pascal, C so far has no clear successor). "Well," I say to him, "in the absence of original thought of your own, you might do well to emulate your peers." "Huh?" He gawks at me. "Consider," I say, "that programmers in different kinds of organizations choose different kinds of languages. Viz: Programmers who work in shrinkwrap software companies--at Lotus and WordPerfect, companies like that--use C and assembler. There are exceptions to the rule, but the rule is that shrinkwrap software is written in C and assembler." "Yes, but--" "Then there are in-house corporate developers, programmers who develop software within companies whose main business is not software," I continue. "These guys use conservative, stodgy, standardized languages because the bottom line is riding on the programs they write and their reliability. They use C, COBOL, and FORTRAN." "I don't think--" "Yes, there are isolated pockets of Pascal and Modula-2," I say, "and of course there are the database systems, dBASE and Paradox and the like--they're very strong in the in-house corporate developer community." "But what about--" "Then there are the consultants," I continue. "This is the largest group so far, and the most highly motivated. They have to finish their software today so they can ship out the invoice first thing in the morning. They can't afford to wrestle with bit-twiddling--they have to install working apps. And they have to be able to correct or change The QBNews Page 8 Volume 2, Number 3 September 15, 1991 the apps months or years later. These guys use high-level languages to write maintainable code--BASIC, Pascal, dBASE. The programs they write often perform well, but performance isn't the primary concern for these programmers. Staying in business is the important concern." "I still think--" "And hobbyists," I conclude, "use all kinds of languages, especially BASIC, Pascal, and C. They don't have customers or deadlines, generally, so they can afford to use any language they like." At this point the door-crasher is starting to look for an excuse to sidle away. He holds up his now-empty glass and gestures toward the bar, but I grab him by his tie. "Unless of course you're looking to become a Windows programmer," I say, giving his tie a tug as his face turns red. "That's it, isn't it?" "Mrffle," he gasps. "Good thinking," I congratulate him. "These days there are three kinds of PC programmers: the ones who already support Windows, the ones who are developing a Windows strategy, and the ones who are out of business but don't realize it yet." The door-crasher wheezes. "The way I see it," I say, "there are four routes to Windows programming. Want to hear 'em?" The door-crasher shakes his head, signalling "no," but I'm on a roll. "First, there's C and the SDK. That's the Microsoft Software Development Kit, got it? You've heard of it. For years, this was the only way to write WinApps. But unless you've got the financial resources of Lotus Development Corp. and the intellectual resources of Charles Petzold to throw at the project, this just isn't a reasonable way to write software. Life's too short." The door-crasher struggles as his face starts to flush. "Then there's C++ and class libraries. C++ is turning out to be a pretty good language for writing Windows applications. I expect most professional Windows programmers will use C++. Are you learning C++?" The door-crasher doesn't respond. "If you don't like C++, you could try one of the proprietary object-oriented languages: Smalltalk, Actor, Turbo Pascal, TOOL, one of those. The OOP languages promise to make it simple to write WinApps, but so far most of them don't have very good libraries of reusable classes. Without reusable class and object libraries, you don't get many benefits from OOP." The QBNews Page 9 Volume 2, Number 3 September 15, 1991 The door-crasher shakes his head vigorously. He seems to agree with me quite fervently. "Or you could check out one of the drag-and-drop tools," I say, "like Visual Basic. These tools let you design your application visually, then integrate the graphical front-end with efficient processing engines written with the same tool or in some other language. Descendants of the screen-painter UI tools available under DOS, these flashy new tools offer the best payoff in making Windows programming accessible. If you're looking to get started as a Windows programmer, I'd recommend these tools. Got it?" I release the door-crasher and he falls to his knees wheezing. "Look," I say, "I know you appreciate the advice, but this is ridiculous. Get up, get up." The door-crasher wanders off and I make another quick cruise past the food tables. Oddly enough, no one else comes over to talk with me. It's good being an editor. Now, this is a funny little story, and portions of it aren't true... but there are some nuggets of truth in it too. We get caught up too often in defending our language choices. Bah. They don't need defending. Use whatever works for you--that's the strategy that's kept me writing 15,000 lines of BASIC code per year, at a minimum, since 1978. When I joined the Programmer's Journal staff last month, I was appalled to find a bunch of workers clustered around a table counting names on a mailing list. Turns out they had to do a ZIP Code zone- count for the Postal Service for third-class mailing. "I could write a program to do this," I said. "Oh, we know," they said, "but it would take too long." Take too long? Well, maybe. They'd asked some of the other programmers on the staff about the project, and they rightly estimated that it would take a week or two of C coding. I wrote the program for them in three hours using BC7. I'm pretty sure a C version of the program would work faster than my BASIC version...but Programmer's Journal has been around for 10 years, and the zone counts have always been a manual process. Scary, I think. BASIC is the language I choose whenever I have to finish a project the same day I start it. Nine out of 10 BASIC programmers don't get paid for the programs they write. That's one of the reasons BASIC has a reputation as a hobbyists' language. The QBNews Page 10 Volume 2, Number 3 September 15, 1991 What most folks don't know is that the 1 out of 10 professional BASIC programmers outnumber the professional Pascal programmers and match the number of professional C programmers. That's a lot of people. If I were to leave the editorial trades and return to programming, and if I could choose just one language, it would surely be BASIC. I can always earn a living with a good BASIC compiler. I may not get much business from the Fortune 500, but the Unfortunate 5 Million will know where to find me. Stay warm. JDH ********************************************************************* J.D. Hildebrand has edited computer magazines since joining Miller Freeman Publications as technical editor of PORTABLE COMPUTER in 1983. Along the way, he has worked on MICRO COMMUNICATIONS, PORTABLE 100, EPSON WORLD, DATA GENERAL MICRO WORLD, PROFESSIONAL COMPUTING, VAR, PC COMPANION, PORTABLE COMPUTER REVIEW, LAPTOP USER, CFO, AI EXPERT, COMPUTER LANGUAGE, EMBEDDED SYSTEMS PROGRAMMING, SOFTWARE DEVELOPMENT INTERNATIONAL, UNIX REVIEW, PROGRAMMER'S JOURNAL and the C GAZETTE. He currently serves as editorial director at Springfield, Oregon-based Oakley Publishing Co., and as editor of WINDOWS TECH JOURNAL, which will commence monthly publication in January 1992. ********************************************************************* The QBNews Page 11 Volume 2, Number 3 September 15, 1991 ---------------------------------------------------------------------- G r a p h i c a l l y S p e a k i n g ---------------------------------------------------------------------- Fonts in a Can by Larry Stone You know how it goes - in the process of creating a nifty program, you get side tracked on an even more nifty subprogram. Eventually, this nifty subprogram turns into an extrodinary piece of work. Such is the story behind PrintROMtable - a routine to produce a library of graphics fonts without a font library! The original PrintROMtable was developed for FUSION (published in this issue). Based upon code published by PC Magazine, PrintROMtable was limited in it's scope. Because my time, like most of us, is limited, I made an early release of the code on the Quik_BAS internation echo, and asked others to join in it's development. The responce was outstanding. Rob Smetana immediately jumped into it with code to create italics. Francois Roy supplied code to elongate char- acters for a bold appearance. Bill Beasley supplied code to create tall characters, inspiring Mike Kelly to offer code that creates fonts that are 8, 14, 16, 28, or 32 pixel points in height. Both Rob and Mike supplied code to allow CGA systems access to the extended ASCII set (characters 128 through 255). Of course, my contribution continued with left-handed italics, underlined characters, inverse and backward characters, strike through, stencil characters, double wide and even condensed characters, and, not least of all, intergrating the various code supplied by the other, aforementioned contributors, into the one highly useful and variable module. You know how it goes - while in the process of creating a nifty program, it goes on and on and on... Before we get much further, you should know that PrintROM is run from a test program called, ROMtest.bas, and, although ROMtest traps and sets the screen to your highest resolution, the pixel positioning is coded for SCREEN 12. This means that if your computer monitor can't handle this resolution then you will see only some of the nifty things displayed or, in some cases, some of the diplay will be outside of the monitor's graphic boundaries (you can always tweek the xAxis and yAxis locations coded in the ROMtest program to place any string into your system's view area). So, what's so special about PrintROMtable? Plenty! It is a huge library of fonts without needing external libraries! The external libs supplied provide extended ASCII set 128 through 255. These are not necessary to the program unless your system is CGA and passes a string containing a high ASCII character to PrintROMtable. Even if your pro- gram runs on EGA or VGA, you may wish to include one or more of these font files for the special characters they provide, such as the copy- right and registered trade mark symbols (more on this subject later!) PrintROMtable compiles to around a 13K object file - as small or smal- ler than most "standard" font libraries! As small as this is, you are provided fonts ranging from 8 to 64 pixels tall in condensed, normal, bold, or double wide sizes; characters can be underlined, shadowed, italic (slanted left or right), inverted, backwards, stenciled, or The QBNews Page 12 Volume 2, Number 3 September 15, 1991 contain a strike through mark. Furthermore, you can print from left to right, right to left, top-down, bottom-up, or even at an angle! PrintROMtable has limitations. CGA systems can only use characters 8 pixels tall - this can be expanded to 16 by setting PR.Tall = True. EGA systems cannot access sizes 16, 32. However, EGA can have size 8 expanded to 16 by setting PR.Tall = True. If you try to have an EGA system use font size 16 or 32, PrintROMtable will reset the size to 14 or 28, respectively. This means that using PR.Height = 32 and PR.Tall equals True on EGA systems will produce a font 56 pixels high, not 64 as intended. Only VGA has the entire range of 8, 14, 16, 28, 32, 56 and 64 height characters. The heart of PrintROMtable is it's code to access the where-abouts of the ROM BIOS segment containing the character shape table. Once PrintROMtable has the pointer into the ROM segment that con- taining the character shape tables, it loops through the passed-in string, once for each character in the string. If a background color is called for, it uses LINE with a box and fill (BF) argument to paint a background color. If a background color is not requested then the background color is transparent. The code then loops 4, 8, 14, 16, 28 32, 56, or 64 times for each scan line of the character (depending on such factors as PR.Height, PR.Condesned, and PR.Tall). The 1st item of business is to determine what value, based upon the font size, need to be loaded into the BX register so that a subsequent call to BIOS interrupt 10h, function 1130h can point us to the correct memory address containing our scan lines: SELECT CASE PR.Height CASE 8 ' 8x8 font reg.bx = &H300 CASE 14, 28 ' 8x14 font or 8x14 font double high reg.bx = &H200 CASE 16, 32 ' 8x16 font or 8x16 font double high reg.bx = &H600 CASE ELSE CLS : PRINT "Invalid Character Size": END END SELECT Having determined the BX value, PrintROMtable then sets AX = &H1130 before it calls BIOS Video Service, &H10: reg.ax = &H1130 InterruptX &H10, reg, reg ofst& = reg.bp sgmt& = reg.es For those with inquiring minds, function AX = 1130h is called with AX and BX set as follows: AX = 1130h BX = pointer specifier The QBNews Page 13 Volume 2, Number 3 September 15, 1991 &H0 INT 1Fh pointer &H100 INT 44h pointer &H200 ROM 8 by 14 character font pointer &H300 ROM 8 by 8 double dot font pointer &H400 ROM 8 by 8 DD font (top half) &H500 ROM alpha alternate (9 by 14) pointer &H600 ROM alpha alternate (9 by 16) pointer On return, the ROM BIOS function supplies: ES:BP = specified pointer CX = bytes/character DL = character rows on screen Even though PrintROMtable has the appropriate segment and offset to the character shape table, they may not be correct! The above call to the BIOS is only good for EGA, MCGA, or VGA systems. If PR.ScreenMode is less than 7 or, if you tell PrintROMtable to force the CGA address then PrintROMtable sets the character shape table's segment to &HFFA6. IF PR.ForceAddress OR PR.ScreenMode < 7 THEN sgmt& = &HFFA6 Segment &HFFA6 contains the character table but, unlike our INT 10h table pointer, this table only goes to the first 127 ASCII characters. This means that if PR.ScreenMode is 1, 2, or 3 (same as SCREEN 1, 2, or 3) or, if you instruct PrintROMtable to force the address then, you cannot access the upper ASCII set without defining a disk font file. Since the objective of all of the contributors to this program was to create a self-contained, linkable module, we wanted to avoid asking users to have to have, or load, a 3rd-party file just to access the upper ASCII character set. Several people answered our call for ideas. Mike Kelly was 1st to supply code to accomplish this aim. Although his code was self-contained, it required 4k of memory to use the data. Cornel Huth described an approach similar to the one eventually taken with PrintROMtable: to use Int 1Fh to point to an array containing the high characters. Similar approaches were suggested by Bill Beeler and Dave Cleary. The final approach developed was by Rob Smetana and is similar in operation to the DOS program, Graftabl. Unlike Graftabl, Rob's approach does not require a TSR and it uses a meager 1024 bytes of memory. It does, however, use small, 1024 byte external files. External fonts are loaded by the routine with one GET statement: j% = FREEFILE 'Get a handle OPEN FontFile$ FOR BINARY AS #j% 'Open with this handle font$ = SPACE$(1024) 'Our fonts need just 1024 bytes (128 * 8). GET #1, , font$: CLOSE #j% 'Close file with handle j% PrintROMtable is supplied with four disk font files that access the characters used in international, U.S., Portuguese, and French-Canada ASCII sets. There can be additional 1024 font files added to the list by altering the appropriate area of the PrintROMtable routine. To add The QBNews Page 14 Volume 2, Number 3 September 15, 1991 a new font file, search PrintROMtable for: '**** You could create your own 1024 byte The code is self-expanatory and you should not have any trouble adding to the list. If your program is to access one or more of these font files, you might want to establish a default file to use (if you don't designate a default file then the routine sets the default to number 1 -- inter- national). To set a default font file, before you make any calls to PrintROMtable, LET PR.DefaultFile = ? Where ? is the number of the file to use (presently, 1 through 4). Whether a default font file is established or not, you can, at any time, instruct the routine to load any of the font files. First, you should reset the current font file: PR.ReadHiAscFile = -1 CALL PrintROMtable(a$, PR) Then, to set the new file, simply: PR.ReadHiAscFile = 2 (or 3, or 4, etc.) The next call to PrintROMtable will then read and use the new fonts. If your font files are not located in the default path, before you you make your first call to PRINTROMtable, you need to inform it where to look. This, too, is a simple instruction: LSET PR.DiskFontLoc = "C:\OFF\IN\URANUS" Many QB programmers set their SCREEN mode by using literals, ie., they use code similar to, SCREEN 9. PrintROMtable needs to know what screen you are using. To inform it which screen mode is in use, do something like: PR.ScreenMode = 12 SCREEN PR.ScreenMode Putting it all together, your program should look something like: REM $INCLUDE: 'printrom.bi' LSET PR.DiskFontLoc = "D:\SNAZZY\FONT\AREA\" PR.DefaultFile = 1 PR.ScreenMode = 9 SCREEN PR.ScreenMode PR.Height = 8: PR.xAxis = 20: PR.yAxis = 30 PR.StepX = 9: PR.CharClr = 15: PR.Shadow = -1 PrintROMtable "Hello there, my name is", PR The QBNews Page 15 Volume 2, Number 3 September 15, 1991 PR.Tall = -1: PR.xAxis = 20: PR.yAxis = 48 PrintROMtable "John Doe", PR When working from within the QB environment, you need to load QB by typing, "QB /Lqb". The sample program that demonstrates how to use PrintROMtable is named, "ROMTEST.BAS". It is in the file, PRINTROM.ZIP along with PRINTROM.BI, and ROMTEST.MAK. Run QB with the "/Lqb" switch then open the file RomTest.Bas. That's all you have to do. One final note. PrintROMtable can do a great deal of fancy work to your strings. The fancier your output, the more time required to per- form the work. One saving grace is that all of the fancy manipulation performed by PrintROMtable is considerably faster once compiled as an EXE file. However, compiled or not, expect certain processes, such as PR.Elongate = 2 (double wide) to be slower than the simpler processes like 8 pixel high, normal character writes. [EDITOR'S NOTE] All code for this article can be found in PRINTROM.ZIP ********************************************************************** Larry Stone is President of LSRGroup and is involved in writing instructional and large data base application systems for business and institutional clients. He is also the author of SERVICES, a shareware application program rated a trophy by "Public Brand Software". He can be reached at LSRGroup, 2945 "A" Street, North Bend, OR 97459, or in care of this newsletter. ********************************************************************** The QBNews Page 16 Volume 2, Number 3 September 15, 1991 Manipulating Sprites in Screen 13 by Fred Sexton Jr. In January I bought my first real computer(386SX).I don't count the TI-99 that's still in the closet.After a month or so of GWBASIC I bought QuickBasic 4.5 .I was instantly hooked,and have spent alot of my spare time coding. I wanted to make a few animated educational games for my kids.I soon found that animation would require alot of "sprites".I needed an easy to use drawing program to create sprites in my favorite mode SCREEN 13 (320x200x256).So I set out to create one.I ended up with "THE SPRITE MASTER" (so named because I finally had mastered them). Along the way I learned alot about SCREEN 13.I decided to pass along some of it. NOTE:I always use DEFINT A-Z.So,all arrays discussed here are integer arrays. The easiest way to store an image is with Q.B.'s GET statement. To find the minimum size to dimension an array,I set up a loop getting a 10w X 10h image decreasing the number of elements each time until an error occurred.I don't care for the formula in the book.The result was that a 10w X 10h image required 52 elements or, DIM ary(51).The elements are numbered starting with zero by default. So,always remember to DIM an array with a value that is one less than you really want.Knowing that the colors are in the range of 0 - 255 we can determine that each pixel requires 8 bits (1byte) to store it's color value (255 = 11111111 B).So,in a 10w X 10h image there are 100 bytes or 50 words.We can conclude that GET needs the number of words in the image plus two additional words. Therefore : elements = ((width * height) + 1) \ 2 + 2 (the "+ 1" takes care of odd values) To find out what the two extra words are for,we'll GET a couple of different size images from a blank screen. Then we'll compare the values in the resulting arrays. With image1 = 10w X 10h and image2 = 20w X 5h: ary1(0) = 80 ary2(0) = 160 ary1(1) = 10 ary2(1) = 5 ary1(2-51) = 0 ary2(2-51) = 0 Now we know the first two elements are used by GET to store the image size information.The first element is the image width in bits. Remember we said 1 pixel = 8 bits.To find pixel width just divide by 8.The second element is the height. To determine the rest of the pattern let's set a few points and get the image. FOR t = 0 TO 3 PSET(t , 0), t + 1 PSET(t , 1), t + 5 NEXT GET (0 , 0) - (3 , 1), ary We already know what's in the first two elements so let's look at the rest. The QBNews Page 17 Volume 2, Number 3 September 15, 1991 ary(2) = 513 on the byte level low byte = 1 => value of the pixel (0,0) high byte = 2 => value of the pixel (1,0) ary(3) = 1027 on the byte level low byte = 3 => value of the pixel (2,0) high byte = 4 => value of the pixel (3,0) ary(4) = 1541 on the byte level low byte = 5 => value of the pixel (0,1) high byte = 6 => value of the pixel (1,1) ary(5) = 2055 on the byte level low byte = 7 => value of the pixel (2,1) high byte = 8 => value of the pixel (3,1) This was a 4w X 2h image.Starting at the low byte the third element,the next four bytes correspond to the first row of four pixels.The next four bytes correspond to the second row of four pixels.Analysis of other images will show the same pattern.Thus we can conclude for an image with a width of W : Starting with the low byte of the third element,the first W # of bytes will correspond to the pixels in the first row and the second W # of bytes will correspond to the pixels in the second row and so on for each row. Once the pattern is known manipulating the arrays can accomplish many things.Here are a few examples. Change a color: To change a color in an image array we would search the array at the byte level for the source color.When it is found we will replace it with the new color. In pseudo code: FOR t = 0 TO bytes - 1 (because we start with zero) IF PEEK (t + start offset) = oldcolor THEN POKE t + start offset, newcolor END IF NEXT Mirror an image: To create a mirror image we need to reverse the order of each row of pixels.We know the pixels are stored in groups corresponding to the width of the image.So,in a 10w X 10h image starting with the low byte The QBNews Page 18 Volume 2, Number 3 September 15, 1991 of the third element the first byte would be the first pixel in the first row and the tenth byte would be the last pixel in the first row.To make it easier let's dimension a second array the same size as the first.Then after making the size information the same we can just copy the bytes from one to the other in the proper order (first to tenth,second to ninth,etc.). In pseudo code: aofs=offset of first pixel in first row of source bofs=offset of last pixel in first row of target FOR first row TO last row FOR first pixel TO last pixel PEEK at aofs + increasing pixel number (0 to start) POKE to bofs decrease bofs to next lower pixel NEXT source pixel aofs=offset of first pixel in next row of source bofs=offset of last pixel in next row of target NEXT row Superimpose an image: To superimpose an image ie. put it front of or behind existing images without destroying them or messing the colors up.Again it is easiest to use a second array of the same size.First we get the area of the screen where we are planning to put the image.Then to put in front we take all non-zero (background) pixels from the first array and put them into the second array.To put behind we only poke pixels from the source array there are zeros in the second array. The result can be PUT with PSET and we won't end up with the blacked out area we normally have. In pseudo code: IN FRONT: GET target area into work array FOR first pixel in first row TO last pixel in last row PEEK at a source pixel IF pixel is not zero THEN POKE pixel into work array NEXT PUT work array PSET BEHIND: GET target area into work array FOR first pixel in first row TO last pixel in last row PEEK at a work pixel IF pixel is zero THEN PEEK at same pixel in source POKE pixel into work array END IF The QBNews Page 19 Volume 2, Number 3 September 15, 1991 NEXT PUT work array PSET These are just a few of many possibilities.(The superimpose routine could be easily modified to allow partial PUTS.) The documented source for each routine discussed is in G13UTIL.BAS . I recently started learning MASM and have converted these to faster versions.I plan to release a shareware library of fast routines for SCREEN 13,as well as a shareware version of "THE SPRITE MASTER". I plan to Upload them to COMPUSERVE and AMERICA ON LINE. Comments and/or suggestions would be appreciated. [EDITOR'S NOTE] All code for this article can be found in SPRITE.ZIP ********************************************************************* Fred Sexton Jr. is an electrician for Ford Motor Co. He works extensively with Allen Bradley Programmable controllers and Kuka robotics.He received electrical training in the NAVY as an electronics technician / reactor operator. He can be reached on Compuserve at 70253,163 and on A.O.L. at 7408. ********************************************************************* The QBNews Page 20 Volume 2, Number 3 September 15, 1991 ---------------------------------------------------------------------- W h o y a g o n n a c a l l ? C A L L I N T E R R U P T ---------------------------------------------------------------------- A Bug Free CALL INTERRUPT by Cornel Huth There is a problem with the CALL INTERRUPT routine in QuickBASIC and BASIC PDS 7. This table highlights the problems. Problem 4.0 4.0b 4.5 CH 6.0 7.0 7.1 -----------------------------------------------+-------------- 1. DI parameter X | . . 2. INT25/26 X X X | . . X 3. INT24 environment X X X X | . . 4. INT24 EXE ON ERROR GOTO | . . D 5. INT24 EXE NO ERROR TRAP X X X X | . . D | . not tested X - error present D - DOS INT24 fatal error handler gains control CH - The CH version is the INTRPT.OBJ that's in QBNWS105. -------------------------------------------------------------- 1) typo causes the passed DI register parameter to NOT be used 2) DOS INT25/26 will always report success even if it failed -- DOS fatal error (INT24) in: 3) QB environment causes system crash 4) EXE w/ ON ERROR GOTO handler causes system crash 5) EXE with no ERROR handler causes system crash In the QuickBASIC 4.x environment any fatal DOS error during a CALL INTERRUPT crashes the system. In QB 4.x EXEs with ON ERROR GOTO handlers the error is trapped by the error handler. In QB 4.x EXEs without ON ERROR GOTO the runtime code starts to print the error text to the screen but only gets part of it printed before it locks up. In PDS 7, the evironment does not crash on a fatal DOS error because the BP register is saved (in b$SaveBP) before executing the interrupt. Apparently BASIC needs the original BP if a fatal DOS error occurs so that it can reference the return address of the caller. In PDS 7 EXEs, with or without ON ERROR GOTO handlers, fatal errors are simply passed through to the INT24 error handler. This brings up the Abort, Retry, Fail prompt. Pressing F returns to INTERRUPT. INTERRUPT then returns with the carry flag set and DOS error 83d in AX (83d=Fail on INT24). The ON ERROR GOTO handler is never invoked. Pressing A causes the entire program to immediately exit to DOS. Wouldn't it be great if CALL INTERRUPT worked predictably all the time? Now it does. INTRPT2.OBJ is a direct replacement for either QB or PDS that traps fatal DOS errors and returns control, and decision making, to your program. When a fatal DOS error occurs, the carry flag is set and the DOS error code is returned in AX. For example, let's say you want to see if a file exists. What you The QBNews Page 21 Volume 2, Number 3 September 15, 1991 could do is open the file and check for any errors. This works fine as long as the drive of the file is valid and ready. But if the drive is a floppy and the door is open, boom. In the QB4 environment the system would lockup, even if ON ERROR GOTO were set. In PDS EXE programs the default DOS INT24 handler pops up on a fatal error, even with ON ERROR GOTO. This messes up the screen with the infamous Abort, Retry, Fail prompt and lets the user abort with files possibly open. My B-tree shareware libraries QBTree 5.5 and QBXDBF 1.0 (dBASE .DBF format) have support functions that make use of the INTERRUPT routines and I can't allow them to crash the system or present an Abort prompt. No more surprises! To update your version of INTERRUPT(X) put INTRPT2.OBJ in your library directory and then do the following: C>lib QB.LIB -INTRPT +INTRPT2; C>link /qu QB.LIB,QB.QLB,nul,BQLB45.LIB; BQLB45.LIB pertains to QuickBASIC 4.5. Use BQLB40 for QB 4.00 and BQLB41 for QB 4.00b. For BASIC PDS 7.x replace references to QB with QBX.LIB and QBX.QLB, and use QBXQLB.LIB for the library prompt. The source to INTRPT2.ASM, a sample FileExists() function, a DOS error listing and INTRPT2.OBJ are in INTRPT2.ZIP file. ********************************************************************* Cornel Huth is chief designer and programmer for ScanSoft. He can be reached at 6402 Ingram Road, San Antonio, Texas 78238. When he's not twiddling bits he's thinking of ways he could. ********************************************************************* The QBNews Page 22 Volume 2, Number 3 September 15, 1991 ---------------------------------------------------------------------- T h e Q B N e w s P r o f e s s i o n a l L i b r a r y ---------------------------------------------------------------------- An Event-driven Mouse Support Libary by Tony Elliot The mouse is one of the easiest-to-use interface devices available for PCs. Even though it has been extremely popular over the last several years, Microsoft didn't provide direct support for it in the BASIC programming language until the recent release of Visual BASIC for Windows (although the BASIC Professional Development System [PDS] includes indirect support of the mouse through routines in the "User Interface Toolbox" included with that product). This doesn't mean that the mouse has been unreachable from BASIC programs. Mouse services can be easily accessed through BASIC's "CALL Interrupt" routine (as illustrated in a previous issue of QBNews). Also, most BASIC add-on developers include mouse routines written in assembly language as part of their regular complement of functions. So why the need for *another* set of mouse routines? Well, there are several reasons: 1. If you don't already own one, commercial add-on libraries can get rather expensive. If you are only interested in adding mouse support to your programs, you might have to shell out $100 to $200 (or more) for a product that consists of several hundred routines, only a few of which you'll use for this purpose. However, add-on libraries like MicroHelp's "Muscle" ($189) or Crescent's "QuickPak Professional" ($199) are definitely the way to go if you can afford it. Many of the other routines included in those products will no doubt be useful to you down the road. 2. Many of the public domain or "shareware" mouse support alternatives are written in BASIC, usually invoking BASIC's "CALL Interrupt" service. Although this is a completely functional approach, there is a substantial amount of overhead required (in terms of speed and code generation). CALL Interrupt is a relatively slow service to begin with, and the results returned by it must then be translated into a usable form. Doing all of this from BASIC may not allow you to poll the mouse as frequently as required by some applications, therefore you may occasionally miss some real-time events such as button-clicks. 3. Using routines written in assembly language for minor tasks such as mouse support is, in my humble opinion, the way to go. It adds a minimal amount of overhead to your BASIC programs and does the job as quickly as possible. This equates to smaller, faster, and from the users' point of view, more responsive programs. 4. The set of mouse routines described in this article has one important feature that I haven't seen elsewhere in DOS-based BASIC. Utilizing the "ON UEVENT" service available since QB 4.00a, they allow you to create "mouse event-driven" programs. That is, instead of constantly polling the mouse for activity, you can go about your normal business and when a programmer-defined type of mouse activity occurs (a button press, release, or mouse movement), your program is interrupted and control is transferred to a subroutine that you The QBNews Page 23 Volume 2, Number 3 September 15, 1991 specify. This allows you to logically group your mouse-specific code instead of having it sprinkled here and there throughout your program. The two mouse-programming paradigms, "polled" and "event-driven," each have advantages and disadvantages. We will discuss each in detail as the routines are being presented below. The file MOUSE.REF contains a reference listing each of the routines individually, along with the appropriate descriptions and parameter lists associated with them. The various code fragments used below each assume that the file MOUSE.BI is $Included at the top of your program. It contains the declarations and the TYPE..END TYPE structures required by these routines. Many of the code fragments also assume that you have already reset the mouse and the pointer is visible. Also keep in mind that we won't be talking too much about -how- the routines actually work. Fully commented assembly source code is provided for those enquiring minds that "what to know." An example program called MOUSE.BAS is also included. It demonstrates all of the mouse routines described in this article. ---------------------------------------------------------------------- Resetting the Mouse Driver ---------------------------------------------------------------------- When writing programs that will recognize and accept input from a mouse, the first step is to determine if a software "driver" for the mouse is installed. If one is found, it must be "reset" before our program can actually communicate with the mouse. Calls to any of the other mouse routines described here (besides the three "reset" routines) will simply be ignored if the mouse has not been reset or is not present at all. As a convenience, three routines are provided to determine the presence of the mouse and to reset it, if desired: MouseReset% - An integer function that checks to see if a mouse driver is installed, and if so, resets it. The number of buttons the mouse has is returned as a result, or zero if a mouse driver is not installed. When the mouse is reset, it is left in the following state: - Mouse pointer is at the center of the screen - Display page for mouse set to 0 - Mouse pointer is hidden (off) - Mouse pointer shape is the default arrow shape in the graphics video modes or a reverse block in the text modes. MouseAlreadyReset% - An integer function that checks to see if the mouse has already been reset by this program. If so, it returns the number of buttons the mouse has, otherwise it returns zero and takes no further action. This is handy in situations where you don't want to reset the mouse if it has been reset previously (doing so The QBNews Page 24 Volume 2, Number 3 September 15, 1991 unnecessarily takes a couple of seconds and has other, sometimes undesirable, effects such as those listed above). MouseInstalled% - An integer function that checks for the presence of a mouse without resetting it. Most of the time, MouseReset% will be all that you need. For example: NumberOfButtons% = MouseReset% IF NumberOfButtons% THEN PRINT NumberOfButtons%; " button mouse found and reset!" ELSE PRINT "Mouse not found." END IF Once the mouse has been successfully reset, you are then free to use any of the other functions. ---------------------------------------------------------------------- Obtaining Information About Mouse Hardware and Software ---------------------------------------------------------------------- If you wish to obtain specific information about the mouse hardware and software currently in use, the MouseGetInfo routine can be used to identify the version number of the mouse driver software, the type of mouse (bus, serial, InPort, PS/2, or HP), and the hardware interrupt line (IRQ) the mouse is sitting on. For example: CALL MouseGetInfo(MajorV%, MinorV%, MouseType%, Irq%) PRINT "Mouse software version:";MajorV% + MinorV% / 100 PRINT "Mouse type: "; SELECT CASE MouseType% CASE 1 PRINT "Bus" CASE 2 PRINT "Serial" CASE 3 PRINT "InPort" CASE 4 PRINT "PS/2" CASE 5 PRINT "HP" CASE ELSE PRINT "Beats the heck outta me! Type#";MouseType% END SELECT PRINT "Using IRQ";Irq% This information is useful in some cases, but generally there's no particular need for you to be aware of it. ---------------------------------------------------------------------- The Mouse Pointer ---------------------------------------------------------------------- The QBNews Page 25 Volume 2, Number 3 September 15, 1991 After the mouse has been reset, you'll want to make the mouse "pointer" (or "cursor") visible. The visibility state of the mouse pointer is controlled by the MousePointerOn and MousePointerOff routines. Remember, the MouseReset function initially turns the pointer OFF. For example: NumberOfButtons% = MouseReset% LINE INPUT "The mouse pointer is invisible. Press .. ",A$ CALL MousePointerOn LINE INPUT "The mouse pointer is visible. Press .. ",A$ CALL MousePointerOff PRINT "The mouse pointer is invisible again. Press .. " By default, the mouse driver uses a "software pointer" while in the text video modes. This means that a mouse pointer is visible on the screen because the mouse driver is altering the screen's color attribute and/or the character at the location where the pointer is to be placed. This makes that location stand out from the rest of the data displayed on the screen. The default software pointer simply reverses the color attributes at the pointer position. When the pointer is moved, it restores the original attribute and character at the current position, stores the original attribute and character for the new position, and then applies the defined masks to the new position. Before updating the screen using PRINT, CLS, etc., it's necessary to turn the mouse pointer off. If you were to overwrite the screen at the mouse pointer position while the pointer is visible, the mouse driver would not be aware of this occurrence. The next time the pointer is moved, the original attribute and character stored by the mouse driver would be restored to that location. The result is what I like to refer to as "mouse droppings." For this reason, it's generally considered good practice to leave the mouse pointer off except when your program is waiting for input from the user. The mouse driver provides a service whereby you can customize the effects that the pointer has on the color attribute and character at the pointer position. This is accomplished by providing two "bit-masks" that define which bits comprising the attribute/character will be preserved (ANDed) and which will then be toggled (XORed). Since manipulation of the mouse pointer on this level is of little interest to most programmers and would require a crash course in video memory and bit manipulation, I'll skip the nitty-gritty and describe the supplied routine. If you would like more in-depth information about this function, refer to the Microsoft Mouse Programmers Guide, or leave a message for me on Compuserve. I'll be happy to help. The routine MouseSetSoftwarePointer is used to define the "AND" and "XOR" bit masks for the software cursor. For example: AndMask% = &H77FF XorMask% = &H7700 CALL MouseSetSoftwarePointer(AndMask%, XorMask%) The QBNews Page 26 Volume 2, Number 3 September 15, 1991 The first byte of each mask represents the changes made to the color attribute. The "77" part of the AND mask indicates that the foreground color (bits 0-2) and background color (bits 4-6) will be preserved and the high- intensity (bit 3) and blink (bit 7) bits will be turned-off. The "77" part of the XOR mask indicates that the bits comprising the foreground and background colors should then be toggled. The "FF" part of the AND mask indicates that all bits comprising the character should be preserved. The "00" part of the XOR mask indicates that none of the character's bits will be toggled. In other words, when the mouse pointer appears on the screen, the color of the cell will be modified but the character won't. To take another example, we'll leave the color attribute alone, but will change the mouse pointer to an asterisk: AndMask% = &HFF00 'Preserve all attribute bits and discard the char XorMask% = &H002A 'Don't mess with attribute, make char an "*" CALL MouseSetSoftwarePointer(AndMask%, XorMask%) Another type of pointer available in text video modes is the "hardware pointer." It is called that because it is similar to the normal scan line-oriented text cursor that we're all familiar with. It appears on the screen as a blinking block. You can define it size, and to a degree, it's shape. Like the text cursor, the mouse hardware pointer is comprised of from 0 to 7 scan lines. When defining a hardware pointer, you specify the starting and ending scan line number, with 0 at the top of the character cell and 7 at the bottom, much like you would use BASIC's LOCATE statement to alter the shape of the text cursor. For example: 'A full "block" StartScan% = 0 EndScan% = 7 CALL MouseSetHardwarePointer(StartScan%, EndScan%) 'Or 'A thin line in the middle of the character cell StartScan% = 3 StartScan% = 4 CALL MouseSetHardwarePointer(StartScan%, EndScan%) As some of you may be aware, the actual height of a character cell varies between 8 to 16 scan lines depending on the display adapter and the video mode used. This function automatically translates the requested 0-7 range into the scan line range appropriate for the display adapter and video mode in use. ---------------------------------------------------------------------- What's the Mouse Up To? ---------------------------------------------------------------------- When working with the mouse, the big questions are "Where is the mouse pointer?" and "Is a button pressed or released?" This information is required to process the most rudimentary mouse action, the "click." The QBNews Page 27 Volume 2, Number 3 September 15, 1991 The routine MouseGetStatus can used to answer these questions for you. For example: REM $INCLUDE: 'MOUSE.BI' DECLARE FUNCTION Pressed$(Button%) 'Used only by this example NumberOfButtons% = MouseReset% IF NumberOfButtons% = 0 THEN PRINT "No mouse detected." END END IF CALL MousePointerOn CLS PRINT "Press any key to end:" DO CALL MouseGetStatus(Lb%, Rb%, Cb%, Row%, Column%) LOCATE 3,1 PRINT " Left Button :"; Pressed$(Lb%) PRINT " Right Button:"; Pressed$(Rb%) IF NumberOfButtons% > 2 THEN PRINT " Center Button:"; Pressed$(Cb%) END IF PRINT " Current Row:"; Row% PRINT "Current Column:"; Column% LOOP UNTIL LEN(INKEY$) CALL MousePointerOff END FUNCTION Pressed$(Button%) IF Button% THEN Pressed$ = "Pressed" ELSE Pressed$ = "Released" END IF END FUNCTION Again, the MouseGetStatus routine returns information about what is going on with the mouse -right now-. If your program was busy doing other things (e.g. "polling" the mouse infrequently) and the user clicked the left button quickly, you might not detect it. As you might have expected, there are contingencies for this situation as well. Let's take another situation. Say you are only interested in the mouse pointer position when a button is pressed or released, and you don't want to worry about polling the mouse frequently enough. The routines MousePressInfo and MouseReleaseInfo return the row and column position of the mouse pointer when the specified mouse button was *last* pressed or released, respectively. For example: The QBNews Page 28 Volume 2, Number 3 September 15, 1991 Button% = 0 '0=left, 1=right and 2=center IF MousePressInfo%(Button%, Row%, Column%) THEN 'Returns number of times the specified button was pressed ' since the last check PRINT "Left button last pressed at"; Row%; ","; Column% END IF Button% = 1 'Right button IF MouseReleaseInfo%(Button%, Row%, Column%) THEN 'Returns number of times the specified button was released ' since the last check PRINT "Right button last released at"; Row%; ","; Column% END IF Along the same lines, you can also determine the net physical distance that the mouse has moved. The routine MouseMovement returns the number of "Mickeys" (approximately 1/200th of an inch) the mouse has moved since the last time this routine was called. For example: CLS PRINT "Left button display distance and right button ends program" CALL MouseMovement(Rows%, Columns%) 'To zero the counter DO CALL MouseStatus(Lb%, Rb%, Cb%, Row%, Column%) IF Lb% THEN CALL MouseMovement(Rows%, Columns%) LOCATE 3,1 PRINT " Mickeys moved (vertically):"; Rows% PRINT "Mickeys moved (horizontally):"; Columns% CALL MouseWaitForRelease END IF LOOP UNTIL Rb% If you were paying attention, then you noticed that I snuck a new routine into the above listing. MouseWaitForRelease suspends the program until all mouse buttons have been released. It's very handy for mouse-specific program "flow control." ---------------------------------------------------------------------- Writing Mouse Event-driven Programs ---------------------------------------------------------------------- The various methods of gathering mouse-related information we've discussed so far require you to poll the mouse driver periodically to see if a specific event has occurred (mouse click, etc.). Wouldn't it be much easier if the mouse driver could simply interrupt your program anytime it needs attention and then transfer control to a specified subroutine? This way, you wouldn't have to be concerned about polling the mouse periodically. In QuickBASIC 4.00a (distributed with BASIC 6.x), 4.00b (bug fix update to 4.00 and 4.00a), 4.50, and QBX (distributed with PDS) there The QBNews Page 29 Volume 2, Number 3 September 15, 1991 exists a little-known and seldom-used statement called "ON UEVENT GOSUB LineLabel." It's much like other BASIC event processing statements like "ON TIMER", "ON KEY", "ON COM", etc. in that it allows program control to be transferred to a subroutine when a special event occurs. A "UEVENT" or "User Event" is programmer-definable. In this case, we establish a "hook" into the mouse driver and when a specified type of mouse event occurs, the mouse driver notifies our assembly routine, which in turn notifies BASIC. The next time the BASIC runtime code processes events (at each line label or after each statement, depending on the use of the /V and /W compiler switches) control is transferred to the desired subroutine. Neat, huh? The routine MouseSetEvent is used to define the type of event(s) that you are interested in. These events can include any combination of a specific button being pressed or released, or any mouse movement at all. Once an event has occurred and program control has been transferred to your BASIC subroutine, the MouseGetEventInfo routine is used to determine exactly what type event occurred and where the mouse pointer was located at the time. When you wish to turn off the mouse event trap, call the MouseCancelEvent routine. Here's how it's done: REM $INCLUDE: 'MOUSE.BI' Buttons% = MouseReset% IF Buttons% = 0 THEN PRINT "Mouse not present." END END IF CALL MousePointerOn 'First, define the types of event(s) you want to trap. This is ' done by passing value in EventMask%. Just pick the events you ' wish to trap, add up their values and pass it to the ' MouseSetEvent routine. ' 1 = Any mouse movement ' 2 = Left button pressed ' 4 = Left button released ' 8 = Right button pressed ' 16 = Right button released ' 32 = Center button pressed ' 64 = Center button released 'Let's trap a left or right button release. That's 4 + 16 = 20. EventMask% = 4 + 16 CALL MouseSetEvent(EventMask%) 'Next, tell BASIC to watch for the event and to transfer program ' control to the at the MouseEvent line label when an event ' occurs. The QBNews Page 30 Volume 2, Number 3 September 15, 1991 ON UEVENT GOSUB MouseEvent 'Watch for the event UEVENT ON 'Enable UEVENT checking 'Let's set up an idle loop to demonstrate the event processing CLS PRINT "Each time you release the left or right mouse button, our" PRINT "event-handling code will be called. When you've seen" PRINT "enough, press any key to end this program." PRINT DO LOOP UNTIL LEN(INKEY$) 'Loop until a key press is detected 'To turn off event checking UEVENT OFF CALL MouseCancelEvent CALL MousePointerOff END MouseEvent: 'Transfer control here on a mouse event CALL MouseGetEventInfo(EventType%, Lb%, Rb%, Cb%, Row%, Column%) PRINT "Mouse event occurred: - "; '"AND" EventType% with the value of the event being checked IF EventType% AND 4 THEN PRINT "Left Button was released." END IF IF EventType% AND 16 THEN PRINT "Right Button was released." END IF PRINT "Mouse location:"; Row%; ","; Column% RETURN Using BASIC's event-trapping services generates larger executable programs because of the additional event-checking code required at runtime. However, there are ways to reduce this added overhead to a minimum. 1. Use "UEVENT ON" and "UEVENT OFF" around code in which you wish to have event-trapping active. Putting a "UEVENT ON" at the top of your program without a following UEVENT OFF will cause BASIC to generate event-checking code for your entire program. If you selectively use UEVENT ON and UEVENT OFF (e.g. around your input or selection code), - you- can keep the overhead to a minimum. 2. Programs that use BASIC event-traps must be compiled with the /V or /W switches. /V directs BASIC to check for an events after *every BASIC statement* (within the UEVENT ON and UEVENT OFF boundaries, that is). /W directs BASIC to check for events only when crossing line labels. The latter will obviously generate less overhead, but requires you to be more careful with your placement of line labels - no line labels, no event-checking. The QBNews Page 31 Volume 2, Number 3 September 15, 1991 ---------------------------------------------------------------------- Fine-tuning the Mouse ---------------------------------------------------------------------- In addition to controlling the mouse's visual characteristics, you can control its physical characteristics as well. This includes its sensitivity and double-speed threshold. "Sensitivity" is the ratio between the distance the mouse is physically moved (in Mickeys) and the distance the mouse pointer actually moves (in pixels) on the screen. The default ratios are one Mickey per pixel horizontally and two Mickeys per pixel vertically. The higher the ratio, the more physical mouse movement is required to move the mouse pointer. The routine MouseSetRatio is used to adjust the mouse sensitivity. For example: VertRatio% = 2 HorizRatio% = 1 CALL MouseSetRatio(VertRatio%, HorizRatio%) The "double-speed ratio" is the physical speed (in Mickeys per second) that the mouse must travel before the mouse pointer shifts into overdrive and moves across the screen twice as fast. The default is 64 Mickeys per second (about 1/3 of an inch per second). The routine MouseSetDblSpd is used to adjust the double-speed ratio. For example: MickeysPerSecond% = 200 'Set to one inch per second CALL MouseSetDblSpd(MickeysPerSecond%) Using the MouseGetSensitivity routine, you can also retrieve the current sensitivity settings from the mouse driver. For example: CALL MouseGetSensitivity(VertRatio%, HorizRatio%, _ MickeysPerSecond%) ---------------------------------------------------------------------- Miscellaneous Mouse Routines ---------------------------------------------------------------------- The following list of "miscellaneous" mouse routines can be useful from time-to-time. They allow you to restrict the mouse movement, position the mouse pointer through software, save and restore the mouse "state," allow you to define the coordinate system the mouse pointer location is returned in (character-based row/column format or X/Y pixel-based format), and more. MouseSetWindow - Used to define a rectangular area on the screen in which the mouse pointer movement will be restricted. Once called, the user will not be able to move the mouse pointer out of the defined area. To disable a previously defined window, call this routine again with the dimensions of the entire screen. For example: The QBNews Page 32 Volume 2, Number 3 September 15, 1991 TopRow% = 6 LeftColumn% = 10 BottomRow% = 20 RightColumn% = 70 CALL MouseSetWindow(TopRow%, LeftColumn%, BottomRow%,_ RightColumn%) 'To turn the window off (assuming an 80 X 25 screen) TopRow% = 1 LeftColumn% = 1 BottomRow% = 25 RightColumn% = 80 CALL MouseSetWindow(TopRow%, LeftColumn%, BottomRow%,_ RightColumn%) MouseSetExclusionArea - When the mouse pointer is moved into the rectangular area of the screen defined by this routine, it is made invisible. Use the MousePointerOn routine to make the pointer visible again. For example: TopRow% = 6 LeftColumn% = 10 BottomRow% = 20 RightColumn% = 70 CALL MouseSetExclusionArea(TopRow%, LeftColumn%, BottomRow%,_ RightColumn%) 'To turn the mouse pointer back on: CALL MousePointerOn MouseSetPointer - Used to position the mouse pointer on the screen. For example: Row% = 5 Column% = 20 CALL MouseSetPointer(Row%, Column%) MousePixelsOn - Used to change the default coordinate system used by the mouse routines discussed in this article from character-based row/column coordinates (which is the default) to X/Y pixel-based coordinates. This routine defines the coordinate system used for both input -and- output. This routine is provided because row/column coordinates are generally preferred in text video modes and X/Y pixel- based coordinates are generally preferred in graphics video modes. Use the MousePixelsOff routine to switch back to row/column coordinates. For example: CALL MousePixelsOn 'Switch to X/Y pixel-based coordinates CALL MousePixelsOff 'Switch back to row/column format MouseSaveState - Saves the current mouse "state." This includes the current position, visibility, shape, and other mouse pointer characteristics. You could use this routine just prior to a SHELL so the original state could be easily restored on returning to your The QBNews Page 33 Volume 2, Number 3 September 15, 1991 program. This routine automatically allocates the required amount of memory from DOS and releases it on the call the MouseRestoreState. The MouseSaveState routine can be declared as a SUB or an integer FUNCTION. If declared as a FUNCTION, it returns -1 (TRUE) if the state was successfully saved and 0 (FALSE) if insufficient memory was available. We have it DECLAREd as a FUNCTION in the MOUSE.BI file included with this article. For example: Success% = MouseSaveState% IF Success% THEN PRINT "Mouse state saved!" CALL MouseRestoreState ELSE PRINT "Insufficient memory to save mouse state!" END IF MouseSetPage - Sets the video display page on which the mouse pointer will be visible. Normally, the mouse driver is aware of changes in active video pages, so it is not usually necessary to call this routine. However, if you are using non-standard methods of switching video pages, this is how you can tell the mouse driver what you are up to. The function MouseGetPage% is used to return what the mouse driver believes to be the active video page number current in use. For example: SCREEN ,,1,1 'Switch BASIC into page 1 PageNum% = 1 CALL MouseSetPage(PageNum%) 'Tell the mouse driver about it PRINT "Mouse pointer on page"; PRINT MouseGetPage% MouseExit - Releases all memory allocated by these mouse routines and unhooks the "user mouse service" function required by MouseSetEvent. You need to call this routine ONLY if: You are about to CHAIN to another program and the MOUSE.OBJ is *not* in a BASIC 6.X or PDS extended runtime library, or You are about to SHELL and the MOUSE.OBJ file -is- in an extended runtime library. If your BASIC program terminates normally (with an "END" or "SYSTEM" statement), there is no need to call MouseExit at all. ---------------------------------------------------------------------- Special Considerations ---------------------------------------------------------------------- If your BASIC program terminates via the "END" or "SYSTEM" statements, we automatically release memory that we've allocated and "unhook" the mouse driver. Using a service called "B_OnExit", the BASIC runtime code calls our internal clean-up code just prior to returning system control back to DOS. The QBNews Page 34 Volume 2, Number 3 September 15, 1991 However, if you CHAIN to another program, the CHAINing program actually terminates but BASIC does not process the B_OnExit chain allowing us to clean up our mess. Essentially this means that your system can potentially lock up if you've used the MouseSetEvent or the MouseSaveState routines. See the entry for MouseExit above for more information. ---------------------------------------------------------------------- Using the Routines ---------------------------------------------------------------------- Now since we've gotten all of that technical stuff out of the way, you're probably about ready to actually being using the routines. Included with this article, you find a file called MOUSE.OBJ. It is an object file which contains all of the mouse routines described here. You can put this .OBJ file in a QuickLibrary, a LINK library, or LINK it directly to your compiled programs. It's completely compatible with QuickBASIC versions 4.00a - 4.50, BASIC 6.X, and PDS 7.x. Distributed with this article is a batch program called BLDLIB.BAT. It will automatically build a QuickLibrary (.QLB) and LINK library (.LIB) for your compiler version. Type "BLDLIB" at the DOS prompt for instructions. However, if you prefer to know about the "hows" and "whys" and would like to build your libraries manually, read on. To build a QuickLibrary so you can call the mouse routines from with the QB(x) development environments, issue the following command at the DOS prompt: LINK /Q MOUSE, MOUSE.QLB, NUL, BQLB45; The above LINK command will generate a QuickLibrary for QuickBASIC version 4.50. Change the QuickLibrary support module name (listed above as BQLB45) to BQLB41 or QBXQLB to build a QuickLibrary for QuickBASIC 4.00x and QBX, respectively. To build a LINK library or to add the MOUSE.OBJ file to an existing LINK library, type the following at the DOS prompt: LIB MOUSE +MOUSE.OBJ ; The above LIB command will create a library called MOUSE.LIB. If you wish to add the mouse routines to an existing library, substitute that library name for the first occurrence of MOUSE above. To load the example program into the environment, type the following at the DOS prompt: QB MOUSETST /L MOUSE For QB 4.X QBX MOUSETST /L MOUSE For QBX The above commands load the example program MOUSETST.BAS into the environment along with the MOUSE.QLB QuickLibrary we created earlier. The QBNews Page 35 Volume 2, Number 3 September 15, 1991 To compile and LINK your program, you can select "Make an EXE" from the QB(x) environments (assuming you have constructed matching .QLB and .LIB files per the above instructions), or you can manually compile and link from the DOS prompt. The latter is the preferred method because it gives you more control over compiler switches used. For example: BC MOUSETST /O/W ; LINK /EX MOUSETST + MOUSE ; Or, if you've placed MOUSE.OBJ in a link library, you can do this: LINK /EX MOUSETST,,NUL, LibraryName ; [EDITOR'S NOTE] All files for this article can be found in the file MOUSE.ZIP. ********************************************************************* Tony Elliott has been programming in BASIC and assembly langauge for over ten years. He worked for MicroHelp, Inc. for almost four years as their Technical Support Manager and was directly involved in the development of many of their QuickBASIC/PDS products. He has spoken at a number of BASIC Symposiums conducted around the country between 1989 - 1991, and was a speaker at the August, 1991 Microsoft Developers Tools Forum held in Seattle. He has written articles for BASICPro Magazine and MicroHelp's "BASIC Users Group" Newsletter. He is now Vice President of EllTech Development, Inc. which recently developed and is currently marketing a network-ready replacement for PDS ISAM called "E-Tree Plus" (see the ad included in this issue). ********************************************************************** The QBNews Page 36 Volume 2, Number 3 September 15, 1991 ---------------------------------------------------------------------- A d v e r t i s e m e n t ---------------------------------------------------------------------- E-Tree Plus By EllTech Development, Inc. Finally, a network-ready B+Tree file indexing library designed specifically for BASIC programmers that's FAST and EASY TO USE! If you are already familiar with Novell's Btrieve or Microsoft PDS ISAM, you can be up and running in 15 minutes .. no kidding! For those of you new to network database programming, it may take a little longer. For those of you currently using PDS ISAM, this product is an ideal network solution for you. Our function syntax is very similar, we provide a conversion utility for your existing databases, and we even have a section in our manual that will take you step-by-step through converting your existing program source code. Easy to Use Automatically detects the presence of Novell or NETBIOS compatible networks and handles file and recording locking for you. However, you still have full control over how the locks are implemented, timeouts, etc., should you have a need to override our default actions. 11 high-level functions allow you to: create E-Tree database files; add and delete indexes; change the active index; insert, update, retrieve and delete records; locate a specific record or groups of records quickly and easily. In addition, over 40 low-level functions are provided to give you complete control over almost every aspect of the database manipulation. Convert existing PDS ISAM applications to E-Tree Plus with minimal effort. E-Tree Plus's functions use syntax and naming conventions similar to PDS ISAM. In many cases, existing code can be used with only minor modifications. Simplified database maintenance. We include a utility ("EUTIL.EXE") you can use to create databases, modify an existing database's structure (add or delete a field), import and export ASCII data, convert Btrieve dBase compatible files, and PDS ISAM file to E-Tree, and more. We automatically determine the optimum file page size, record length, etc., for you. There are no complicated formulas and no cryptic function numbers to remember. Includes a fully indexed, reference oriented, 200+ page manual. It includes example programs, a database/network tutorial, complete specs on the E-Tree Plus database format, and even a "Quick Start" section for the seasoned database programmer. The product is provided in LINKable libraries and .OBJect files. No TSR programs to load or to get in the way. Fast and Flexible Written using a combination of BASIC and assembly language for the perfect mixture of versatility and speed. All BASIC data types are supported, and then some. Flexible indexing. You can add or delete indexes at any time. Up to 1023 indexes (more than you'll ever need) can be defined at one time. Indexed "keys" can be defined as unique, duplicates allowed, The QBNews Page 37 Volume 2, Number 3 September 15, 1991 modifiable, non-modifiable, segmented (comprised of pieces of one or more fields), ascending, descending, and auto-incrementing (ideal for invoice or order numbers). File sizes up to 4 billion bytes. The maximum number of records per file is limited only by available disk space. Supports fixed-length records, variable-length records, and combinations of both. Efficiently uses system resources. Automatically detects and uses LIM Expanded Memory for file I/O buffers. Reliable Built-in data integrity checking. Using 32-bit CRC, the integrity of your data is checked with near 100% accuracy. If a problem should develop, the chances are very good that all of your data can be recovered or reconstructed. Stores data records, indexes, and its "data dictionary" in the same file. This means that there are no "index" files or "definition" files for your customers to lose track of, and only one DOS file handle is required per open database. Affordable Distribute E-Tree Plus applications and the EUTIL.EXE database maintenance utility royalty-free. One version supports QuickBASIC 4.x, BASCOM 6.x, Microsoft PDS 7.x, and PowerBASIC 2.x. Includes structured, fully commented BASIC and assembly language source code at no additional charge. Price includes free, full-time technical support. We even provide 24 hour support through our BBS. Customers can download maintenance releases of E-Tree Plus free of charge! Just ask our competitors if they can offer the same! Introductory Offer: $199.00 until 11/30/91 Shipping and handling: $ 6.00 (U.S. addresses) Orders and Product Info: (800) 553-1327 (U.S. and Canada) Technical Support: (404) 928-8960 BBS: (404) 928-7111 (HST) E-Tree Plus will be available for shipping on September 15, 1991. Prices are listed in US Dollars. All trademarks belong to their respective owners. EllTech Development, Inc. 4374 Shallowford Industrial Parkway Marietta, GA 30066 The QBNews Page 38 Volume 2, Number 3 September 15, 1991 ---------------------------------------------------------------------- A l g o r i t h m s ---------------------------------------------------------------------- Cardans's Method for Solving Cubes by Richard Jones One of the most common tasks in mathematics and its applications is that of root-finding. That is, we have some function, f(x), and we want to find the value(s) of x for which f(x) = 0. It is disillusioning to note that all centuries upon centuries of mathematics can say about the general case, including monsters like a * x^2 * exp(-b * x) - sin(c * x) = 0, is that there may be one, several, or an infinite number of roots, or maybe none at all, which really tells us a lot. Fortunately, a lot more is known about a smaller and more common category of functions, polynomials of degree n, of the form a(0) + a(1) * x + ... + a(n) * x ^ n = 0, n >= 0. Back in high school, we all learned about the n = 1, or linear, and the n = 2, or quadratic, cases of polynomials. We found that the linear equation a*x + b = 0 has exactly one root given by x = -b/a and that the two roots of a quadratic a*x^2 + b*x + c = 0 are given by the reknown quadratic formula: x = (-b +- (b^2 - 4*a*c)^(1/2)) / 2a. (3) We then noticed what appeared to be a hint of a relation between the degree of a polynomial and the number of roots. After high school, we went off to college and, depending on what field of study we chose, began to go down the path of one of the various branches of higher mathematics and, along the way, perhaps learned some theorems about polynomial equations and the nature of the roots. It's interesting to note that while a lot may have been said about the nature of the roots, nothing was really said about how to find them for the case of n >= 3. It turns out that analytic solutions for polynomials up to the fourth degree are known (equations of higher degree cannot be solved exactly by any finite number of arithmetic operations - you have to resort to approximate numerical methods). The solutions to cubic and quartic equations are quite involved, though, which is one of the reasons nobody bothers with them. This, at long last, brings us to the subject of this article: a QuickBASIC implementation of the solution to the cubic equation known as the Tartaglia-Cardan solution or simply Cardan's method. Roots of Polynomials A little background theory on polynomial roots will helpful in understanding how our method works. A polynomial of degree n, pn(x), will have exactly n roots provided we expand the range of an acceptable solution to include the set of complex numbers. While the The QBNews Page 39 Volume 2, Number 3 September 15, 1991 linear equation's root, -b/a, is always real, what happens when the term under the square root in equation (3) is negative? The notion of a complex number was actually invented to handle this case. Notice how complex things become when we go from n = 1 to just n = 2; we have to "invent" a new class of number. You might think that more numbers would have to be invented to handle the n = 3 case, but it turns out that the set of complex numbers will suffice for any n. Unlike the set of reals, this set is closed, which means that any operation performed on any of the members will always produce another member of the set. Complex roots of polynomials with real coefficients (this doesn't apply if the coefficients themselves are complex) will occur in conjugate pairs which means that there will always be an even number of such roots. Therefore, an equation of odd degree must have at least one real root. This can be easily seen by considering the values of pn(x) and pn(-x) as x grows large and the high order term dominates. If n is odd, pn(x) and pn(-x) must be of opposite sign and, since polynomial functions are nice and continuous, the curve must then cross the x-axis at least once. Knowing this, we can say that a cubic equation must have either three real roots, or one real and a pair of complex conjugate roots. Cardan's Method. Unlike the quadratic, it's impractical to write the solution to the cubic as a single formula - it's far better presented as a series of steps which are somewhat involved, so hang on. The first step is to divide through by the coefficient of the cubic term and write the equation in the so-called standard form: x^3 + a*x^2 + b*x + c = 0. 4) There is no direct solution for the equation in this form, however, and so we must make a change of variable to eliminate the quadratic term. The substitution x = u - a/3 will accomplish this and we will have a "reduced" cubic of the following form: u^3 + p*u + q = 0, 5) where the new coefficients, p and q are given in terms of those of 4) by p = (3b - a^2) / 3, q = (2a^2 - 9ab + 27c) / 27. Like a quadratic, a cubic equation has a discriminant, D, whose sign tells us some things about the nature of our roots. The discriminant of the cubic in the form of 5) is given by D = q^2 / 4 + p^3 / 27. There are three distinct cases depending on the sign of D: The QBNews Page 40 Volume 2, Number 3 September 15, 1991 D > 0 -> one real root and a pair of complex conjugates. D = 0 -> degenerate case - 3 real roots of which at least 2 are equal. D < 0 -> 3 real and distinct roots. Now calculate the two values A and B given by the following: A = (-q/2 + D^(1/2))^(1/3), and B = (-q/2 - D^(1/2))^(1/3). And, finally, the 3 roots of 5), u1, u2 and u3, are then given by u1 = A + B, 6) u2 = -u1 / 2 + i(A - B) * 3^(1/2) / 2, and 7) u3 = -u1 / 2 - i(A - B) * 3^(1/2) / 2, 8) where i represents the famous (or infamous) square root of -1. The three roots of the original equation, x1, x2 and x3, are given by the substitution relation above, x = u - a / 3. Note that if D > 0, x1 is real and x2 & x3 are complex conjugates. If D = 0, then A = B and the imaginary terms in 7) and 8) are zero and u2 = u3 and hence x2 = x3. Also, our substitution may produce p = q = 0, in which u1 = u2 = u3 = 0, and our 3 roots will all be equal and given by x1 = x2 = x3 = -a / 3. Such cases are known as multiple roots - we still say the equation has 3 roots but it just happens that two or more are equal to each other. Now for the hard part, the D < 0 case. Notice that if D < 0, the calculation of A and B requires us to extract the cube roots of two complex numbers. Twenty years ago, this would be a time for despair, but now that we all have enormous amounts of computing power sitting on our desks and handy-dandy high-level language compilers like MS QuickBASIC with a wealth of built-in math functions to utilize that power, it's a piece of cake. A complex number, z, which is usually written in rectangular form as some x + i*y, also has a so-called polar form, z = r * exp(i*R), where r, known as the magnitude or modulus, and R, known as the argument or simply the angle, are given by a simple pythagorean relation: r = (x^2 + y^2)^(1/2), and R = arctan(y / x). In polar form, exponentiation is simple. We raise the modulus (always a positive real) to the desired power and simply multiply the angle by this power. So, to take the cube root of a complex number, we put it in polar form, take the cube root of the modulus, and divide the angle by three. Trigonometric functions then get us back to rectangular The QBNews Page 41 Volume 2, Number 3 September 15, 1991 form. Since in this case A and B are conjugates, things can be simplified somewhat and we can write our u's as follows: u1 = 2r * cos(R). 9) u2 = r * (3^(1/2) * sin(R) - cos(R)), and 10) u3 = -r * (3^(1/2) * sin(R) + cos(R)), 11) where r is given by r = (q^2 / 4 - D)^(1/6), and the angle R is given by R = arctan( -2 * D^(1/2) / q ) / 3. It's rather interesting that, after all this complex arithmetic, the results are purely real, and that the D < 0 case requires transcendental operations, elementary trigonometric ones in our case. (Transcendentals are called such because they "transcend" the realm of ordinary arithmetic - the only kind we know how to do. They cannot be calculated exactly by any finite number of additions, multiplications, or exponentiations.) It's actually cheating a bit to say that we have an analytic solution for the D < 0 case because of the transcendentals. We can write a formula down only because we've invented a special notation for the trig functions, namely "sin", "cos", and "arctan", which can only be approximated. (Think about it. Using only a pad and pencil, try to calculate the sine of a 37 degree angle. If you remember the power series and don't mind doing long division with 10 - 30 digit numbers, you might get it to 5 places in an hour or two. Personally, I'd get a protractor and a carpenter's square and "experimentally" measure it and might manage to get two or three places.) Cardan's Method in QuickBASIC Whew! Now that we've got Cardan's method down pat, it's easy to write a QB program to carry out the steps. The listing is of the program which I've written to implement Cardan's method. It consists of a subroutine, CUBERT, which does the work, and a simple driver which gets the coefficients of equation from the user, prints out the roots, and prints out the value of the function at one of the supposed roots that the subroutine returns as a check. I've also included a function, POLY, which evaluates polynomials the "right" way. I'll say more about this later. The driver is fairly simple and pretty much self-explanatory in function. CUBERT follows the procedure above fairly closely, and I've tried to use variable names that correspond to those in the equations above. The routine expects to be passed an array, A#(), containing the coefficients of the cubic, an array, X#(), in which to place the roots it calculates, and a flag, F%, to indicate the type of the roots returned. If F% = 1, all the roots are real and distinct and their The QBNews Page 42 Volume 2, Number 3 September 15, 1991 values will be in elements 1 through 3 of X#(). F% = 2 corresponds to the D >= 0 cases and X#(1) will contain the one real root and the next two elements will contain the real and imaginary components of the conjugate pair of complex roots. Notice the use of the STATIC keyword on the subroutines. This causes the compiler to allocate local variables statically in DGROUP rather the on the stack and can save time in routines with a lot of local variable accesses on older 8088 machines. This is not compatible with recursion, though. The first thing CUBERT does is calculate the coefficients of the standard form and from this calculates the p and q coefficients and then finds the discriminant. And IF statement checks the sign of the discriminant. If D >= 0, the routine calculates the roots according to equations 6) - 8). The calculation of the numbers A and B requires the calculation of the cube roots of two values that can be negative. The QB library code can't raise negative numbers to non-integral powers (because the method used has to take the logarithm of the base) and this requires the code to check the signs. If one is negative it calculates its cube root as the negative of the cube root of the absolute value. (Related to the fact that a polynomial of degree n has n roots is the fact that any number has exactly n nth roots. While for odd n, the principle nth root of a negative number is always complex, one of these n roots will simply be the negative of the principle nth root of that number's absolute value. While we are biased toward principle roots, any of the n nth roots will usually work in most situations.) After taking these cube roots, the code returns the roots of the equation as outlined above. I didn't bother to include an ELSEIF clause to check for the case of D being exactly equal to zero for a number of reasons. Remember that computer floating point arithmetic is finite and approximate, not exact. It is very rare indeed when you'll find two things exactly equal because there is always a little (or a lot, with unstable algorithms) bit of "noise" involved in every calculation. Also, the D = 0 case corresponds to the case of multiple roots and numerical analysis tells us that in this case the roots are poorly conditioned with respect to the coefficients anyway. ("Conditioning" is a term from numerical analysis which can be thought of as a kind of signal- to-noise ratio. It is a measure of how sensitive a calculation is to this floating point noise.) So what you'll find in this case is that the imaginary components returned are very small relative to the real components, or that two or more of the real and "distinct" roots are very close if the "noise" makes D slightly negative. If the discriminant is negative, the code follows equations 9) - 11) above. The magnitude calculation is straightforward, but the angle calculation is a tricky affair. We have to calculate the angle as an arctangent of the ratio of the imaginary and real components. But what happens if the real part is zero? In this case the angle is 90 degrees, but we'll overflow when we take the ratio if the real part is small enough. The way to avoid this is to check the denominator before taking the ratio. If it's > 1, we can go ahead, but if it's < 1 we The QBNews Page 43 Volume 2, Number 3 September 15, 1991 have to make sure the division won't overflow. We want to make sure that, say, a / b <= c, but without doing the division. This will be true only if a <= c * b, and this what CUBERT does. If the ratio will be greater than a constant, which I called BIG# and set to 1E40, the code sets the angle to 90 degrees. (While the tangent of a right angle is infinite, 1E40 is about as close to infinity as we need in this case.) Finally, if the ratio is negative QB's ATN function will return an angle in the fourth quadrant, but the angle we want lies in the second, so the code has to adjust the angle if needed. That just about wraps up the operation of CUBERT. Once CUBERT returns the roots, the driver calls function POLY to evaluate the cubic at the first root. As I said before, POLY evaluates polynomials the right way. At first blush, we might try something like this to evaluate a polynomial of degree n at x whose coefficients are in an array: P# = A#(0) FOR I% = 1 TO N% P# = P# + A#(I%) * X# ^ (N%) NEXT I% But this requires n additions, n multiplications, and n exponentiations. POLY# does the same thing using Horner's method with only n additions and multiplications and no exponentiations. The above method is also much more "noisy" than POLY#. Cardan's method certainly falls into the category of the obscure and as a result many people aren't aware of the way to solve cubic equations and resort to numerical root-finders when they need to solve them. CUBERT can be used in any program that needs to find the roots of cubic equations and, I think, will be a helpful addition to your library. I hope this program will make solving cubics as easy and as common as solving quadratics. If you have in questions, comments or witticisms about this article or the program, feel free to contact me anytime. For a bibliography and a "for further reading" section I would recommend the following texts: Thompson, J. E.: "Algebra for the Practical Worker," 4th ed., Van Nostrand Reinhold Co., New York, NY, 1982. (While, as its title implies, the above is a lower-level text, it neverless contains an excellent discussion of Cardan's Method.) Churchill, R. V. & Brown, J. W.: "Complex Variables and Applications," 4th ed., McGraw-Hill, New York, NY, 1984. Beyer, W. H.: "CRC Standard Mathmatical Tables," 28th ed., CRC Press, Boca Raton, FL, 1988. Mizrahi, A. & Sullivan, M.: "Calculus and Analytic Geometry," Wadsworth Publishing Co, Belmont, CA, 1982. The QBNews Page 44 Volume 2, Number 3 September 15, 1991 Stoer, J. & Bulirsch, R.: "Introduction to Numerical Analysis," Springer-Verlag, New York, NY, 1980 The above constitute my standard references on the math covered in the article, but please note this represents a personal preference and shouldn't be considered the best possible sources. ********************************************************************** Richard Jones is an "almost" graduate of Clemson University in SC. He majored in Physics there, but lack 4 hrs. of a foreign language for his B.S. His main interests include math, physics, and programming, and is currently employed with P.C. Electric Co. in Greenville, SC. He can be reached on Compuserve at 76636,536. ********************************************************************** The QBNews Page 45 Volume 2, Number 3 September 15, 1991 ---------------------------------------------------------------------- F u n a n d G a m e s ---------------------------------------------------------------------- Lines With Style by Larry Stone and Charles Graham During the Winter of the Big Freeze, with nothing to do and much time on his hands, Charles Graham, during a state of extreme boredom, found himself alone with a glass of wine in one hand and his fingers at the keyboard with his other hand. Thus was born an algorithm later to be shared with us all as source code for his program, "ENERGY". Aside: Charles' called to complain that the glass of wine wasn't in hand until AFTER he created his code . FUSION, based upon Charles' original algorithm, produces a kaleido- scope of colors, design, music and sounds that will provide hours of delightful fascination. Before we get much further, you should know that FUSION only runs in EGA screen 9 or VGA screen 12. Even if your monitor cannot handle these modes you will still benefit from FUSION's many other, useful routines. For example, the PrintROMtable subprogram gives you pixel by pixel control for the ASCII characters 1 through 128. See the article Fonts in a Can earlier in this issue for more on this subroutine. If you study FUSION.BAS, you will notice that except for one PAINT and a couple of PALETTE commands, all graphics are produced via the BASIC LINE or CIRCLE statement. Closer examination reveals the heart of the program is the LINE statement. QB's LINE statement is one of the most powerful graphic commands available. Conversely, it is may be one of the least understood graphic command. Although entire books could be written about the LINE statement, only one of its optional variables will be examined here -- the "style%" argument. Simply put, the integer variable, style%, is a mask or stencil laid over the line. When we "paint" the line, the paint falls through the holes of the "stencil" and is held back by the "mask". Probably the easiest way to show this is by following the logic of the subprogram called, PrintROMtable. One final note to you Tech-Heads. The algorithm that computes and displays "energy" and "plasma" drawings is derived by calculating the polar equations for each ellipse generated from weighted, random numbers. Random numbers are weighted by expressions such as: g = INT(RND * 590) + 50 '50 - 640 possible points to each drawing w = INT(RND * 1.9) + .1 'weighted factor used for yavg, etc. Where g is a random number whose value is forced to lie between 50 and 640 and, where w is a random number whose value is forced to range between 0.1 and 1.9, respectively. Albert Einstein would have loved The QBNews Page 46 Volume 2, Number 3 September 15, 1991 this algorithm - the programmer's universe is totally random but the programmer is using "dice" that are loaded. The effect is random, yet symmetrical designs. My additions to Charles' algorithm continue this mix of order and chaos - "muon" and "quark" trails are produced and displayed with weighted, random numbers. The same mix of symmetry and randomness is employed with color and with the number of drawings that display at one time. Thank you, Charles Graham, for the wonderful algorithm. [EDITOR'S NOTE] All code for this article can be found in FUSION.ZIP ********************************************************************** Larry Stone is President of LSRGroup and is involved in writing instructional and large data base application systems for business and institutional clients. He is also the author of SERVICES, a shareware application program rated a trophy by "Public Brand Software". He can be reached at LSRGroup, P.O. Box 5715, Charleston, OR 97420, or in care of this newsletter. ********************************************************************** ********************************************************************** Charles Graham is a division head for a local government agency in St. Louis County, Missouri. He also teaches QuickBASIC part time at a local community college. He is the author of several shareware products including MOVIES . ON . LINE and, Quick Dial. He can be contacted at Post Office Box 58634, St. Louis, MO 63158, and on the National QuickBASIC Conference. ********************************************************************** The QBNews Page 47 ---------------------------------------------------------------------- E O F ---------------------------------------------------------------------- Receiving The QBNews The QBNews is distributed mainly through BBS systems around the world. Some of the networks it gets distributed through are SDS (Software Distribution System) and PDN (Programmers Distribution Network). Ask the sysop of your local board about these networks to see if there is a node in your area. The QBNews can also be found on CompuServe in the MSLang (Microsoft Language) forum. It can be found in file area 1 or 2 of that forum. Just search for the keyword QBNEWS. The QBNews will also be available on PC-Link. I send them to Steve Craver, who is the BASIC Programming Forum Host on PC-LINK and he will make them available. I would appreciate anybody who could upload The QBNews to other services such as GENIE since I don't have access to these. I have also set up a high speed distribution network for people who would like to download The QBNews at 9600 baud. The following boards allow first time callers download privileges also. They are: Name Sysop Location Number Node # --------------------------------------------------------------------- Treasure Island Don Dawson Danbury, CT 203-791-8532 1:141/730 Gulf Coast BBS Jim Brewer New PortRichey,FL 813-856-7926 1:3619/20 221B Baker St. James Young Panama City,FL 904-871-6536 1:3608/1 EMC/80 Jim Harre St. Louis, MO 314-843-0001 1:100/555 Apple Capitol BBS Bob Finley Wenatchee, WA 509-663-3618 1:344/61 Finally, you can download The QBNews from these vendors BBS's: The Crescent Software Support BBS 203-426-5958 The EllTech Support BBS 404-928-7111 The Microhelp BUG BBS 404-552-0567 404-594-9625 You do not have to be a customer of these vendors in order to download The QBNews, but the Microhelp BBS only allows non-members 15 minutes of time per call. If you would like to receive The QBNews on disk, I offer a yearly subscription for $15.00. This includes four disks containing each The QBNews Page 48 Volume 2, Number 3 September 15, 1991 issue as it is published. If you would like a disk with all the back issues of The QBNews, please enclose an additional $5.00. The pricing structure is as follows: Base Price for 1 Year - $15.00 Disk with Back Issues - $5.00 3.5" disk surcharge - $5.00 Canada and Mexico surcharge - $5.00 All other foreign orders - $10.00 The base price includes 5.25" 360k disks. Send a check or money order in U.S. funds to: The QBNews P.O. Box 507 Sandy Hook, CT 06482 Please be sure to specify what archive format you want. The QBNews normally uses PKZip as it's archiver. ---------------------------------------------------------------------- Submitting Articles to The QBNews The QBNews relies on it's readers to submit articles. If you are interested in submitting an article, please send a disk of Ascii text of no more than 70 characters per line to: The QBNews P.O. Box 507 Sandy Hook, CT 06482 Articles can also be submitted via E-Mail. Send them via Compuserve to 76510,1725 or via FidoNet to 1:141/777. I can be reached at the above addresses as well as on Prodigy as HSRW18A. The QBNews Page 49