Copy Link
Add to Bookmark
Report
QBNews Volume 2 Number 3
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 <Enter> .. ",A$
CALL MousePointerOn
LINE INPUT "The mouse pointer is visible. Press <Enter> .. ",A$
CALL MousePointerOff
PRINT "The mouse pointer is invisible again. Press <Enter> .. "
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 <GRIN>.
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