The BASIX Fanzine

Issue #17
April 2000


Edited By David Groulx


Contents


 
 
Articles
Article
Author
Manipulating External Files
Crash Neth
Formatting Text into Checkese
Romel Anthony S. Bismonte
Using The Modem in QBASIC
Matthew River Knight 
Programming Challenge

 
Utilities
Article
Author
EZGUI
Chris Boss

 
Miscellaneous & Credits
 
Website Award
Credits
Contact Info
Mailing List
Next Issue


Articles


Manipulating External Files
Crash Neth

The commands you will use with frequency for do this is:
OPEN, LINE INPUT, GET, PUT, EOF, LOF, and the basic BASIC command.

Let's see one by one:
 
 
This is the basic command to use with external files, the usage is 
OPEN OPEN "file" FOR [INPUT, OUTPUT, RANDOM, BINARY, APPEND] AS #n 
 
where:
"file" the file that you want to open, if the file isn't at the directory that you start QB from you have to put the path.
INPUT Use this to GET data from the file
OUTPUT Use this to send data to the file
RANDOM Use this to do random-access to the file using

GET and PUT to read from a determinated RECORD (records are numbers that point to the data on the file.)
 
BINARY Use this when you want to read from any position of the file, also using GET and PUT
APPEND Use this when you want to write the data in the end of the file, the cursor is automatic positioned at the end

 
n The number the program will use when refer to the file  eg. "GET #1, a$"


 
This command is used to get a line from a previously OPENed file
LINE INPUT LINE INPUT #n, lin$
 
where:
n The number of the file you used in the OPEN command
lin$ where the line from the file is stored

When you do this command first time, he will get the first line
from the file then when you do it again he gets the second and so on.


 
This command is used with file OPENed AS BINARY or RANDOM
GET GET #n, pos, dat$
 
where:
pos For RANDOM access file is the record number and for BINARY files is the position for start reading.
dat$ Where the GETed data is stored

Let's say you OPENed the file FOR BINARY AS #1 and want to get
the data 5 bytes long at the position 10, so you do the commands:

dat$ = SPACE$(5) or dat$ = "     "   'U do this to tell QB
                                     'that you want 5 bytes
                                     'from the file.
GET #1, 10, dat$                     'And this command will
                                     'get it for you.

 


 
 
This is almost equal to GET
PUT PUT #n, pos, dat$
 
where:
pos For RANDOM files is the record number and for BINARY files is the position in the file.
dat$ Data to send to the file

To write data to files OPENed with INPUT you will use:
PRINT #n, dat$


 
End Of File
EOF EOF(n)
 
where:
n The number of the file you used in the OPEN command

Let's say you want to read all the data from "text.txt", you will do this commands:

OPEN path$ + "text.txt" for INPUT AS #1
DO WHILE NOT EOF(1)
  LINE INPUT #1, lin$
  'here you use the data
LOOP
 

 


 
Length Of File, used to get the size of a already OPENed file
LOF LOF(n)
 
where:
n The number of the file you used in the OPEN command
 

Now I will give some useful source that you can use, but please give me some credits!

* A better way to get all the data from the file:

OPEN file$ FOR INPUT AS #1
DO WHILE NOT EOF(1)                     'Here is a loop that calculate
  LINE INPUT #1, lin$                   'the number of lines in the
  lt = lt + 1                           'file
LOOP
CLOSE #1
DIM lines$(lt)                          'Here we dimension a array to
OPEN file$ FOR INPUT AS #1              'store the data
FOR i = 1 TO lt
  LINE INPUT #1, lin$
  lines$(i) = lin$
NEXT i

* A way to store an then get data in a file

'Here is the data you want to store
DATA 1,2,3,4,5
DATA 6,7,8,9,10
DATA 11,12,13,14,15
DATA 16,17,18,19,20

OPEN "datatest.dat" FOR OUTPUT AS #1
FOR i = 1 TO 4
  READ a, b, c, d, e
  lin$ = STR$(a) + "," + STR$(b) + "," + STR$(c)
  lin$ = lin$ + "," + STR$(d) + "," + STR$(e) + ","
  PRINT #1, lin$
NEXT i
CLOSE #1                                'Simple command to close the
                                        'file
OPEN "datatest.dat" FOR INPUT AS #1
DO WHILE NOT EOF(1)
  LINE INPUT #1, lin$
  FOR i = 1 TO LEN(lin$)                'Loop that goes from 1 to the
                                        'the size of the read line
    ch$ = MID$(lin$, i, 1)              'This command will get char
                                        'by char from the line
    IF ch$ = "," THEN
      fd = fd + 1
      GOTO jump
    END IF
    var$(fd) = var$(fd) + ch$
jump:
  NEXT i
  a = VAL(var$(0))                      'Here we put the data on a
  b = VAL(var$(1))                      'array
  c = VAL(var$(2))
  d = VAL(var$(3))
  e = VAL(var$(4))
  PRINT a, b, c, d, e
  FOR d = 0 TO 4                        'We need to erase the data in
    var$(d) = ""                        'the temp array to use it
  NEXT d                                'again
  fd = 0
LOOP

* Here is a program to find word in a text:

CLS
INPUT "Word to find: "; word$
INPUT "File to search: "; file$
CLS
OPEN file$ FOR INPUT AS #1
DO WHILE NOT EOF(1)
  LINE INPUT #1, lin$
  lt = lt + 1
LOOP
CLOSE #1
DIM lines$(lt)
OPEN file$ FOR INPUT AS #1
FOR i = 1 TO lt
  LINE INPUT #1, lines$(i)
NEXT i
FOR i = 1 TO lt
  FOR ii = 1 TO LEN(lines$(i)) - LEN(word$)
    test$ = MID$(lines$(i), ii, LEN(word$))
    IF test$ = word$ THEN
      SOUND 1000, .5
      CLS
      LOCATE 1, 1: PRINT "Word founded at line "; i; " at position "; ii
      LOCATE 3, 1: PRINT "The line is:"
      LOCATE 5, 1: PRINT lines$(i):
      COLOR 14
      LOCATE 5, ii: PRINT word$
      COLOR 7
      finds = finds + 1
      LOCATE 7, 1: PRINT "Any key to continue"
      SLEEP
    END IF
  NEXT ii
NEXT i
LOCATE 7, 1: PRINT "Total of words found: "; finds



 
the math wizard
on the web
Formatting Text into Checkese

Contact and Disclaimer

You may reach the Math Wizard for questions or comments about this topic at this email address: [ math_wizard44@hotmail.com ] Feel free to share with my thoughts, as long as these messages do not constitute destructive criticism.

This code has been tested to work with near perfect accuracy in home computers running the QBasic 1.1 Interpreter from Microsoft. It has not been tested as part of any subsystem that handles mission-critical (or otherwise critical) data, nor has it been designed for use of that purpose in mind. The main purpose of this code is to demonstrate how QBasic can be used in a problem such as Checkese (see below).

When using/changing this public domain code, keep in mind that I make no representation about its ability and/or fitness, and that you should test all public-domain code thoroughly before using it in your own programs.

Introduction

I myself, as a little boy, have always wondered why checks had to have the English wording of the Amount of Currency included. I've always thought it silly if someone who already knew how to read numbers would need this extra system. Perhaps you are still wondering.

The reason is that people, because of their handwriting, may commit mistakes rendering a number amount unreadable; this opens the value to interpretation. A smudged "3" might look like an "8" to a banker with an eye problem. A writer of a check might misplace the decimal point, making the amount ten times as large or as small as intended.

For this reason, wording currency values in English (from now on referred to as "Checkese") is a sort of check to see if the numeral value is indeed correct. This low level of technology might save avid check writers some money.

Writing the Code

We are going to write a program (or more properly, a "function") that returns the input currency value in Checkese. So we write this into the environment as such:

 
DECLARE FUNCTION Checkese$ (Amount AS STRING)

This does nothing but declare the function we are about to make.

You will note some things about this function. One in particular is that it is designed to return a String value (as indicated by the $ in the function name). This is important because Checkese, which is a "word-for-word" representation of a value, will use letters and ASCII characters.

Another is that the parameter "Amount" is also a string. This makes it easier to read in and manipulate the individual digits of the data, which we will be doing a lot of in this function. A longer explanation is provided further down.

Now we make a new function named Checkese and whose only parameter is a string named Amount. We then make the value in Amount a local variable to that function.

 
FUNCTION Checkese$ (Amount AS STRING)

DIM Amt         AS STRING
DIM WholeValue  AS STRING
DIM DecimValue  AS STRING

DIM Chunk       AS STRING
DIM ChunkVal    AS STRING
DIM SVal        AS STRING

DIM Value       AS INTEGER
DIM OnesValue   AS INTEGER
DIM NumGroups   AS INTEGER

Amt = Amount

We have now converted the value in Amount into a string which we can now read and manipulate sequentially. This is important as we will have the program "read", or make sense out of, the input, and convert these digits into Checkese. We also made variables "WholeValue" and "DecimValue" that will store parts of the finished string. The other variables are holders for temporary data.

Now you might be wondering why our function only takes strings of integers. This is due to a convention to the way we are going to write down our currency values. In this convention, we are going to get rid of the need to use decimals in our value. For example, if we wanted the function to accept a value such as [ $ 5,236.23 ], we will write it thus:

$ 5,326.23      >       523623

The last two digits of the input value will always be after the decimal point (as is in most current systems). If we wanted ten thousand dollars [ $ 10,000.00 ], we will write it thus:

$ 10,000.00     >       1000000

We need to add two extra zeroes to stand for the cents value if we want a whole number. (Notice as well that we do not need the employment of commas; commas are a means to aid in the reading of values.)

I will explain below how the function will read the given string.

    Read the last two digits of the StartString (the decimal portion).

    Place its Checkese equivalent to the DecimValue.

    Delete these last two digits from the StartString.

    While there are more than zero characters in the StartString: 

      Read the last three digits of the StartString. (If there are less, append zeroes.)

      Append its Checkese equivalent to the beginning of the WholeValue.

      Delete these last three or less digits from the Start String.

    Combine the strings from Step 2 (DecimValue) and Step 4 (WholeValue) into one.

    Place this value from Step 5 as the return value of the function.

Notice that this procedure reads from the right of the value to its left. This is because it is predictable what the next translated value (or "chunk", if you will) would be when you start from the right. Consider the following example, wherein the computer reads the value sequentially from the left. 
678750

It would not know (without being told beforehand by some other means) that the first 6 is grouped by itself: [ six thousand ]. However, it knows (because from the logic of our numbering convention) that the last two digits are to be coded as [ 50/100 dollars ]. Once these are deleted, we can take the next three digits [787] and code them as [ seven hundred and eighty-seven ]. And once these are deleted, the computer can now clearly see that [6] is coded as [ Six thousand ].

Of course, for the computer to be able to translate a value to Checkese (which is in essence English), we must teach it a few English words. So we will build three arrays to store some words the computer needs to know to translate with.

    The first would be GroupingAppend(), an array that contains words such as "Million", "Thousand", and "Billion", words that set off groups of three digits (hence their name).

    The second would be a two-dimensional one called Numeral(). It would translate digit values (like "two" as opposed to "twenty"). Take note of the entry at Numeral(2, 1). It is the syllable "TEEN", and thus the number "11" would (at first) be translated as [ TEEN-one ].

    The third array would contain the names of numbers from [10] through [19], the "TEEN" numbers. A special part of the translation subroutine would replace a string like [ TEEN-one ] to [ eleven ] according to this array. This array would be called Teen().

Here is the code for these three arrays.

 
DIM GroupingAppend(1 TO 6) AS STRING
        GroupingAppend(1) = ""
        GroupingAppend(2) = "thousand"
        GroupingAppend(3) = "million"
        GroupingAppend(4) = "billion"
        GroupingAppend(5) = "trillion"
        GroupingAppend(6) = "quadrillion"
       
DIM Numeral(1 TO 2, 0 TO 9) AS STRING
        Numeral(1, 0) = "": Numeral(2, 0) = "and "
        Numeral(1, 1) = "one": Numeral(2, 1) = "TEEN-"
        Numeral(1, 2) = "two": Numeral(2, 2) = "twenty"
        Numeral(1, 3) = "three": Numeral(2, 3) = "thirty"
        Numeral(1, 4) = "four": Numeral(2, 4) = "forty"
        Numeral(1, 5) = "five": Numeral(2, 5) = "fifty"
        Numeral(1, 6) = "six": Numeral(2, 6) = "sixty"
        Numeral(1, 7) = "seven": Numeral(2, 7) = "seventy"
        Numeral(1, 8) = "eight": Numeral(2, 8) = "eighty"
        Numeral(1, 9) = "nine": Numeral(2, 9) = "ninety"

DIM Teen(0 TO 9) AS STRING
        Teen(0) = "ten"
        Teen(1) = "eleven"
        Teen(2) = "twelve"
        Teen(3) = "thirteen"
        Teen(4) = "fourteen"
        Teen(5) = "fifteen"
        Teen(6) = "sixteen"
        Teen(7) = "seventeen"
        Teen(8) = "eighteen"
        Teen(9) = "nineteen"

We now have to construct the "translator". There will be two: one that translates the last two digits, and another to handle the groups of three digits we have allowed for. We will first make the translator for the last two digits, since it is the most intuitive.

We need a function (RIGHT$() in QBasic) that will take the last digits from the Start String. Here is the code to do Steps 1 to 3.

 
' // Get Decimals //

Chunk = RIGHT$(Amt, 2)
DecimValue = Chunk + "/100 dollars"
Amt = LEFT$(Amt, LEN(Amt) - 2)

It is as simple as it looks, while the last statement might need some explanation. The equivalent of "deleting" the last two characters from the string is to keep the first [(Length of Amt) - 2] characters of Amt, and to discard the rest (the last two).

What is left now is the most difficult part of the procedure. We need to carry out Step 4 as a conditional loop. The loop and part of its setup will look something like this.

 
' // Get All Other Digits //

NumGroups = 1
DO UNTIL LEN(Amt) < 1
.
.
.
LOOP

Notice that NumGroups is set to 1. This value will tell the procedure what grouping symbol (thousand, million, and so on) to append to the result.

Here is the code for Step 4 of our algorithm.

 
' // Get All Other Digits //

NumGroups = 1
DO UNTIL LEN(Amt) < 1
        IF LEN(Amt) >= 3 THEN Chunk = RIGHT$(Amt, 3)
        IF LEN(Amt) <= 2 THEN Chunk = Amt

        ' // RIGHTMOST character //
        SVal = MID$(Chunk, LEN(Chunk), 1)
        Value = VAL(SVal)
        ChunkVal = Numeral(1, Value) + ChunkVal
        OnesValue = Value
        IF LEN(Chunk) = 1 THEN GOTO EndChunk
                
        ' // MIDDLE character //
        SVal = MID$(Chunk, LEN(Chunk) - 1, 1)
        Value = VAL(SVal)
        TensValue = Value
        IF OnesValue <> 0 AND Value <> 0 THEN ChunkVal = "-" + ChunkVal
        ChunkVal = Numeral(2, Value) + ChunkVal
        IF LEFT$(ChunkVal, 5) = "TEEN-" THEN ChunkVal = Teen(OnesValue)
        IF LEN(Chunk) = 2 THEN GOTO EndChunk

        ' // LEFTMOST character //
        SVal = MID$(Chunk, LEN(Chunk) - 2, 1)
        Value = VAL(SVal)
        IF Value = 0 THEN
                IF OnesValue = 0 AND TensValue = 0 THEN
                        GOTO AllZero
                ELSE
                        GOTO EndChunk
                        END IF
                END IF
        ChunkVal = Numeral(1, Value) + " hundred " + ChunkVal
        IF OnesValue = 0 AND TensValue = 0 THEN
                ChunkVal = Numeral(1, Value) + " hundred"
                END IF

EndChunk:
        ChunkVal = ChunkVal + SPACE$(1) + GroupingAppend(NumGroups) + ","
        WholeValue = ChunkVal + SPACE$(1) + WholeValue

AllZero:
        Value = LEN(Amt)
        IF Value >= 3 THEN Amt = LEFT$(Amt, LEN(Amt) - 3)
        IF Value <= 2 THEN Amt = ""

        NumGroups = NumGroups + 1
        ChunkVal = ""
        Value = 0

LOOP

Note that while it takes the groups of three digits one at a time, it takes the digits themselves one at a time as well; in fact, there are separate lines of code for the RIGHTMOST character, the MIDDLE character, and the LEFTMOST character. This merely imitates the way humans read digits in.

Notice also that on many of the places, attention is paid to formatting the WholeValue with commas, the word "and", and spacing. We wanted to keep the Numeral library as free of spaces as possible. (The obvious exception would be Numeral(2, 0); see above for details.)

The last part of the algorithm is now ready (Steps 5 and 6). We are to clean up the WholeValue string, combine the two strings (DecimValue and WholeValue), format them so that they fit into each other, and then put them into the return value of the function.

Here is the last part of the Checkese function code.

 
DO WHILE RIGHT$(WholeValue, 1) = SPACE$(1) OR RIGHT$(WholeValue, 1) = ","
        WholeValue = LEFT$(WholeValue, LEN(WholeValue) - 1)
LOOP

MID$(WholeValue, 1, 1) = UCASE$(LEFT$(WholeValue, 1))

Checkese$ = WholeValue + SPACE$(1) + "and" + SPACE$(1) + DecimValue

And there you go. The resulting string is assigned to the Function itself, and is returned by the function to the program.

Quality Control

Of course, as with any imperfect theory, the translation subroutine needed some "tweaking" around with before it would work out right.

For example, one thing I always encountered when I tested the routine were extra spaces and commas to the right of the output caused by extra formatting. I fixed this by using the LEFT$() code in the above code sample, which simply erased them.

Earlier in this article, we have said that "quadrillion" was enough for most professionals to work with. What if the user enters a string that goes beyond "hundreds of quadrillions"? We can trap for this incalculable error beforehand. A "hundred quadrillion" string, plus the extra two decimals, is worth 20 characters. So after all the variable declarations (before the procedure actually manipulates the string), we insert this conditional.

 
IF LEN(Amt) > 20 THEN
        Checkese$ = "ERROR"
        EXIT FUNCTION
        END IF

As can be seen, the value of the function becomes the string "ERROR". The program then exits the function. (If this trap is not introduced, values with more than 20 digits would produce a "Subscript out of range" error message anyway.)

What if this 20-digit stuff is not enough? What if we needed to calculate quintillions, sextillions, and so on? All we need to do is to add functionality to the program. We add an array value to GroupingAppend() and increase the trap amount by three for every GroupingAppend we add.

 
DIM GroupingAppend(1 TO 7) AS STRING
.
.
.
        GroupingAppend(7) = "quintillion"
.
.
.
IF Len(Amt) > 23 THEN

N.B. : This improvement was not made to the source code that comes with this program.

Some Improvement Challenges

Here is a list of things that you, the avid QBasic programmer, might want to do to improve on the algorithm.
    One of the simplest: can you make a program that uses this procedure to read in figures from a file and output the translations to another file? This can be done using the File-Handling routines already provided with QBasic.

    Is there any way to make the main translation subroutine take up less lines of code? I am bothered by this because most of the lines in this subroutine (Step 4) are "formatting" subroutines: they take out extra spaces, look up the TEEN array, and so on. Even if it were just an ingenious way to put all this formatting at the end of the subroutine, it would increase the readability of the code.

    Notice also on the translation subroutine, there are two line labels: EndChunk and AllZero. EndChunk is used to clean up all the temporary variables and AllZero is where the program goes when it wants to skip a three-digit chunk of zeroes. Is there any way of removing these line labels so that it can be more easily ported to C?

    Which takes me to the last challenge: can you port this procedure to C/C++? This time, it would be a function that takes a char array and that will return another char array.

Of course, if you have the answers to any of these challenges, contact me, math_wizard44@hotmail.com; or, better yet, publish it on the Basix Fanzine. Good luck!

Program Source

Here's the complete source code of the program, including a short demonstration of its abilities. Notice that I have added a "bonus" function: CheckeseCurrency$(). This just formats a Checkese-ready string into proper currency notation. (I think the code is self-explanatory; it is easy to figure out since it is derived from the original Checkese function itself. Have fun with the code. N.B.: The last DATA entry was supposed to demonstrate what happens if the string were too long.

 
DECLARE FUNCTION Checkese$ (Amount AS STRING)
DECLARE FUNCTION CheckeseCurrency$ (Amount AS STRING)

CLS

FOR x = 1 TO 25
        READ n$
        LOCATE 2, 1: PRINT SPACE$(80)
        LOCATE 3, 1: PRINT SPACE$(80)
        LOCATE 2, 1: PRINT CheckeseCurrency$(n$) + " > "
        LOCATE 3, 1: PRINT Checkese$(n$)
        LOCATE 25, 1: PRINT "Press any key to continue..."
        SLEEP
NEXT x

END

DATA 13, 763
DATA 8033, 12245
DATA 789003, 3594006
DATA 62095239, 104592350
DATA 1110439595, 93125687090
DATA 6593584958205, 5294582000042
DATA 23846309766532, 634687239587298
DATA 462980003246345, 5246287598729879
DATA 2104820002345283, 72358729798579285
DATA 46349820994868692, 100000006246724355
DATA 210104247600030246, 6348679872200902452
DATA 6049680935200023568, 10000500000004235235
DATA 346343735389470394583048530

FUNCTION Checkese$ (Amount AS STRING)

DIM Amt         AS STRING
DIM WholeValue  AS STRING
DIM DecimValue  AS STRING

DIM Chunk       AS STRING
DIM ChunkVal    AS STRING
DIM SVal        AS STRING

DIM Value       AS INTEGER
DIM OnesValue   AS INTEGER
DIM NumGroups   AS INTEGER

DIM GroupingAppend(1 TO 6) AS STRING
        GroupingAppend(1) = ""
        GroupingAppend(2) = "thousand"
        GroupingAppend(3) = "million"
        GroupingAppend(4) = "billion"
        GroupingAppend(5) = "trillion"
        GroupingAppend(6) = "quadrillion"
       
DIM Numeral(1 TO 2, 0 TO 9) AS STRING
        Numeral(1, 0) = "": Numeral(2, 0) = "and "
        Numeral(1, 1) = "one": Numeral(2, 1) = "TEEN-"
        Numeral(1, 2) = "two": Numeral(2, 2) = "twenty"
        Numeral(1, 3) = "three": Numeral(2, 3) = "thirty"
        Numeral(1, 4) = "four": Numeral(2, 4) = "forty"
        Numeral(1, 5) = "five": Numeral(2, 5) = "fifty"
        Numeral(1, 6) = "six": Numeral(2, 6) = "sixty"
        Numeral(1, 7) = "seven": Numeral(2, 7) = "seventy"
        Numeral(1, 8) = "eight": Numeral(2, 8) = "eighty"
        Numeral(1, 9) = "nine": Numeral(2, 9) = "ninety"

DIM Teen(0 TO 9) AS STRING
        Teen(0) = "ten"
        Teen(1) = "eleven"
        Teen(2) = "twelve"
        Teen(3) = "thirteen"
        Teen(4) = "fourteen"
        Teen(5) = "fifteen"
        Teen(6) = "sixteen"
        Teen(7) = "seventeen"
        Teen(8) = "eighteen"
        Teen(9) = "nineteen"

Amt = Amount

IF LEN(Amt) > 20 THEN
        Checkese$ = "ERROR"
        EXIT FUNCTION
        END IF

DO WHILE LEFT$(Amt, 1) = "0"
        Amt = RIGHT$(Amt, LEN(Amt) - 1)
LOOP

' // Get Decimals //

Chunk = RIGHT$(Amt, 2)
DecimValue = Chunk + "/100 dollars"
Amt = LEFT$(Amt, LEN(Amt) - 2)

IF LEN(Amt) = 0 OR VAL(Amt) = 0 THEN
        Checkese$ = DecimValue
        EXIT FUNCTION
        END IF

' // Get All Other Digits //

NumGroups = 1
DO UNTIL LEN(Amt) < 1
        IF LEN(Amt) >= 3 THEN Chunk = RIGHT$(Amt, 3)
        IF LEN(Amt) <= 2 THEN Chunk = Amt

        ' // RIGHTMOST character //
        SVal = MID$(Chunk, LEN(Chunk), 1)
        Value = VAL(SVal)
        ChunkVal = Numeral(1, Value) + ChunkVal
        OnesValue = Value
        IF LEN(Chunk) = 1 THEN GOTO EndChunk
                
        ' // MIDDLE character //
        SVal = MID$(Chunk, LEN(Chunk) - 1, 1)
        Value = VAL(SVal)
        TensValue = Value
        IF OnesValue <> 0 AND Value <> 0 THEN ChunkVal = "-" + ChunkVal
        ChunkVal = Numeral(2, Value) + ChunkVal
        IF LEFT$(ChunkVal, 5) = "TEEN-" THEN ChunkVal = Teen(OnesValue)
        IF LEN(Chunk) = 2 THEN GOTO EndChunk

        ' // LEFTMOST character //
        SVal = MID$(Chunk, LEN(Chunk) - 2, 1)
        Value = VAL(SVal)
        IF Value = 0 THEN
                IF OnesValue = 0 AND TensValue = 0 THEN
                        GOTO AllZero
                ELSE
                        GOTO EndChunk
                        END IF
                END IF
        ChunkVal = Numeral(1, Value) + " hundred " + ChunkVal
        IF OnesValue = 0 AND TensValue = 0 THEN
                ChunkVal = Numeral(1, Value) + " hundred"
                END IF

EndChunk:
        ChunkVal = ChunkVal + SPACE$(1) + GroupingAppend(NumGroups) + ","
        WholeValue = ChunkVal + SPACE$(1) + WholeValue

AllZero:
        Value = LEN(Amt)
        IF Value >= 3 THEN Amt = LEFT$(Amt, LEN(Amt) - 3)
        IF Value <= 2 THEN Amt = ""

        NumGroups = NumGroups + 1
        ChunkVal = ""
        Value = 0

LOOP

DO WHILE RIGHT$(WholeValue, 1) = SPACE$(1) OR RIGHT$(WholeValue, 1) = ","
        WholeValue = LEFT$(WholeValue, LEN(WholeValue) - 1)
LOOP

MID$(WholeValue, 1, 1) = UCASE$(LEFT$(WholeValue, 1))

Checkese$ = WholeValue + SPACE$(1) + "and" + SPACE$(1) + DecimValue

END FUNCTION

FUNCTION CheckeseCurrency$ (Amount AS STRING)

DIM Amt         AS STRING
DIM DecimValue  AS STRING
DIM WholeValue  AS STRING

DIM StringBuff  AS STRING

Amt = Amount

DO WHILE LEFT$(Amt, 1) = "0"
        Amt = RIGHT$(Amt, LEN(Amt) - 1)
LOOP

DecimValue = RIGHT$(Amt, 2)
Amt = LEFT$(Amt, LEN(Amt) - 2)

DO UNTIL LEN(Amt) < 3
        StringBuff = RIGHT$(Amt, 3)
        WholeValue = "," + StringBuff + WholeValue
        Amt = LEFT$(Amt, LEN(Amt) - 3)
LOOP

Amt = Amt + WholeValue + "." + DecimValue

DO WHILE LEFT$(Amt, 1) = "," OR LEFT$(Amt, 1) = " "
        Amt = RIGHT$(Amt, LEN(Amt) - 1)
LOOP

Amt = "$" + SPACE$(1) + Amt

CheckeseCurrency$ = Amt

END FUNCTION
  That's it for this article. If you would like to see me write more articles in the future, you can do one of two things: a) Send me an electronic message requesting me to write one, or b) post a request to the fine people at the Basix Fanzine.

Have fun coding!
 
 
Content of this article is partly ©2000 The Math Wizard. All rights reserved.
Use of this document for profit may infringe on the rights of its owners.


 


USING THE MODEM IN QBASIC
Matthew River Knight of HORIZONS Interactive Entertainment

The modem has probably been the most influential computing device that has yet
been created. Its creation has resulted in a complete change in the world
of business and advertising, has resulted in cheaper and more efficient
international communication, and last, but certainly not least, has had a huge
impact on both the computer professional and the hobbyist. To most, the modem
is a little magic box that opens our computer to the world. In reality, it is
very simple to use, and more importantly, to program. The modem takes bytes of
data, turns it into a noise, and sends it over the phone line. Theoretically,
if you could whistle fast enough and at the right pitches, you could send a
file with the sounds you make. Basically, the computer sends a file over the
modem like this: the modem sends an ASCII character to tell the other computer
it is sending a file. It then breaks up that file into chunks and sends it in
chunks of bytes. The modem then waits for an ASCII character from the other
computer telling it that all is well, and to send the next chunk.

That is all very interesting, you say, but how do I do it in Qbasic? Well,
that is very simple. There are just 4 commands you are going to need to do
some modem communications! They are as follows:

Let's say you want to read all the data from "text.txt", you will do this
OPEN COM Open up the modem for use by Qbasic
INPUT$ Get data from the modem.
LOC Tells you if the modem has any data from an outside source.
PRINT # Send data to the modem.

To start work with the modem, you have to open a "path" to it. This is just
like opening a file, except you use the "OPEN COM" statement. Here is a
sample:

OPEN "COM2:2400,N,8,1,RB2048,TB2048" FOR RANDOM AS #1

Now, you wonder what that all means, but it really is quite simple, you just
must remember this little secret: opened devices (like the modem) act just
like opened files! Now back to the example code above. I know it looks
confusing at first, but don't worry, the only thing you need to concern your-
self with is the number between "COM" and ":" and the number between ":" and
",N". All the other stuff deals with transmission settings and the RB TB things
deal with uploading and downloading. The first value is the COM port number
that your modem is on. Because Qbasic was made a long time ago, you only have
access to ports 1 and 2. If your modem is on another port, don't worry, there
is a way around that which involves switching the memory addresses on the COM
ports. Don't worry about that now though, I'll go into that in another article
some time.

The 2nd number is the Baud. BAUD is the speed of the modem. Qbasic cant access
COM ports at any higher speed than 9600, so if you have a 56K modem, Qbasic
can still use it, but it wont go any faster than 9600. Don't worry about this
too much though, I am busy writing a library that gives you access to the
modem at any speed.

To send data to your modem you use the PRINT #n statement, where n is the
file number (in the example, 1.) But there is no point sending stuff like
"Hello world" to your modem right now, because you are not connected to
anything. All you have done with the "OPEN COM" statement, is made a path
from your modem to your program so they can talk to each other.

Hmmm, this is quite a problem. So how do we get connected to something then?
Well, when you want your modem to talk to an outside source, like a BBS, you
have to tell the modem to dial a number. To do this you must know that all
modems have a set of commands eched in their read-only-memory chips that allow
them to do different things. You cant just say "Hey modem, dial 555-314-545",
you gotta talk in the modems lingo! This is a lot easier than it sounds. All
of these commands begin with "AT". Here are the most used ones:

Let's say you want to read all the data from "text.txt", you will do this
MODEM LANGUAGE
TRANSLATION TO ENGLISH
ATDT###-###-#### Hey modem, dial ###-###-####
ATZ Hang up the phone!
ATS0=# Wait until you someone calls and the phone rings # number of times, then try to connect modems.
ATM2H1L# Set your speaker volume at # (1-3)

So, if you wanted to call someone with the modem, you would first use an
OPEN COM statement, to get the path between your modem and your prog set up,
then you would use an INPUT statement to get the phone number to dial as a
string, then use PRINT #n to talk to the modem. I think now is a pretty
good time for an example! Here is a simple phone dialer:

(Replace COM# with the COM port your modem is on.)

CLS
PRINT "Opening a path to your modem..."
OPEN "COM2:2400,N,8,1,RB7048,TB7048" FOR RANDOM AS #1
PRINT "Please enter the phone number you wish to call"
INPUT PhoneNumber$
PRINT "Talking to your modem..."
PRINT #1, "ATDT"; PhoneNumber$
PRINT "There you go, pick up the phone and talk!"
PRINT "Press the ESC key to hang up!"
DO
LOOP UNTIL INKEY$ = CHR$(27)
PRINT #1, "ATZ"

Now that wasn't so hard was it? But here comes the biggest problem of modem
control in Qbasic, HOW DO I READ WHAT COMES FROM THE MODEM? Well there is a
little function called LOC that does this. The syntax is:

LOC(n) where n is the file number, which if you used my sample, would be 1.

LOC tells you where in a file you are. File? But I am trying to access the
modem! As I said before, files and devices work the same way. But with a
modem, LOC tells if it has received anything. Fine, now you know if the modem
is getting stuff, but how do you know what it is getting? For that you use the
INPUT$(x,y) statement. x is the number of bytes to get from a file/device and
y is the number of the opened file/device.

x should ALWAYS be 1. I know this means that only 1 character can be read on
each pass, but this way EVERY character is read, and none are skipped. If you
were getting an 11 byte transmission, and x was 2, only the first 10
characters would be read (because it is a multiple of 2.) The last part would
be skipped. This is the way for NORMAL communications. Keep x as 1!

There is just one more modem command to talk about, namely, the "ATSO=#"
command, which you use to wait for a call. But I think it is best explained
with an example. Oh, what the heck, lets just put everything we have learned
here together into a fully commented communications program. You can use this
to call up any BBS and interact with it. Note that it has no uploading or
downloading capabilities, though. I will have to cover this, along with some
other stuff, in another tutorial some time.

CLS
PRINT "HORIZONS Interactive Entertainment - BasHack v1."
PRINT "What COM port does your modem use?"
INPUT ">", port$
baud$ = "9600" '9600 should work fine with most modems. If you have
               'an older one use 2400.
               'Open up that com port.
OPEN "COM" + port$ + ":" + baud$ + ",N,8,1,RB2048,TB2048" FOR RANDOM AS #1

PRINT "OPTIONS:"
PRINT "1-Dial up to another computer"
PRINT "2-Wait for a call"
PRINT "3-Quit"
DO
a = VAL(INKEY$)
LOOP UNTIL a >= 1 AND a <= 3
IF a = 3 THEN CLOSE : SYSTEM
IF a = 2 THEN GOTO waits

PRINT "Number to call?"
INPUT ">", number$
PRINT #1, "ATDT" + number$ 'Tell the modem to dail the number.
GOTO chat
waits:
'
PRINT #1, "ATS0=1" 'Tell modem to conect after 1 ring.

'When a modem connects it returns "CONNECT ####"
'The next hunk of code waits until the modem connects before moving on

a$ = ""
DO
IF LOC(1) THEN a$ = a$ + INPUT$(1, 1) 'if anything in modem add it to a$
LOOP UNTIL INSTR(a$, "CONNECT") 'Wait until modem have connected.

chat:
'If you where waiting for a call, a lot of ASCII characters will be printed
'on the screen. Don't worry, that just the computers getting in sync and
'talking. You also will not see what you type.

CLS
PRINT "You are now ready to chat, press ESC to quit."
DO
t$ = INKEY$
IF LEN(t$) THEN PRINT #1, t$    'if you typed something send it to the modem
                                'this will be send by the modem to the other
                                'computer
IF LOC(1) THEN r$ = INPUT$(1, 1)'if the is something to get, get it and save
                                'it as r$
IF LEN(r$) THEN PRINT r$;       'if r$ <> "" then print it. the ";" means a
                                'line is not started
LOOP UNTIL t$ = CHR$(27)        'keep doing this until ESC is pressed
PRINT #1, "ATZ"                 'tell the modem to hang up
CLOSE                           'close the open com statment

And that's it! Simple huh? Now you have a cool program that you can use to
talk with your friends over the modem in Qbasic! Before I leave you to
experiment with your newly acquired knowledge, I would like to extend my
thanks to the following people who have helped me some way along the line
with my programming in general: LordAcidus, Petter Holmberg, Tek, ZKman, and
last but not least, Christian Garms. You guys have helped me a lot.
 


Programming Challenge

This is the only submission I received. Since the turnout was so poor on this challenge I have decided to terminate this feature, unless anyone has some objections. If you want to keep it alive or have any other suggestions write to me.
 

DECLARE SUB fire ()
DECLARE SUB makeforest ()
DECLARE SUB treeload ()

RANDOMIZE TIMER
ON ERROR GOTO erro
DIM SHARED tree(10 * 10)  ' live tree
DIM SHARED tree2(10 * 10) ' burnt tree
DIM SHARED tree3(10 * 10) ' fire tree 1
DIM SHARED tree4(10 * 10) ' fire tree 2
DIM SHARED forest(16, 10)

CLS
PRINT " FOREST.BAS by HUR(1999)"
PRINT "========================="
PRINT
PRINT "    This program was made by HUR in Euskal Herria  for the"
PRINT "BASIX fanzine contest, it's a emulation of a burning forest, as this:"
PRINT : COLOR 15
PRINT "    the fire should spread and grow"
PRINT "     - each tree within 1 away from the fire, will catch fire"
PRINT "     - after one generation, the fire burn out, and turn to soil"
PRINT "     - in the next generation it turns to a tree"
PRINT "     - if already a tree it stays a tree"
PRINT "    redraw the forest"
PRINT "                        NOTE: With this rules the fire never ends"
PRINT : COLOR 7
PRINT " Email: h20ur@yahoo.com"
PRINT " WEB:   http:www.geocities.com/siliconvalley/campus/9647/"
PRINT
PRINT "     HUR has been programing  in BASIC since he was 10 years"
PRINT "in his old MSX (Z80 based micro). And making this program took"
PRINT "to him about 2 hours, in his 486 where the mouse it doesn't work."
PRINT
PRINT "To exit the program press (ESC) when you get bored, OK!"
PRINT
PRINT "Press any key to start...";
SLEEP
 

SCREEN 13

PALETTE 0, 65536 * 20 + 256 * 20 + 1 ' make the grass color
treeload

FOR x = 1 TO 320 STEP 3  ' this makes the ground leafs
FOR y = 1 TO 200 STEP 2
IF INT(RND * 2) THEN PSET (x, y), INT(RND * 5) + 40
NEXT y, x

makeforest

LOCATE 25, 1: PRINT "    ... a storm begins on the forest.   ";

FOR c = 1 TO 3
FOR i = 1 TO 63
PALETTE 0, 65536 * i + 256 * 20 + 1 ' make the lightning
A = INT(RND * 400)
SOUND A * c + 37 + i, .05
NEXT i, c
PALETTE 0, 65536 * 20 + 256 * 20 + 1 ' make the grass  color
 

forest(8, 5) = 1

LOCATE 25, 1: PRINT "        now a tree is on fire...      ";

DO
time = TIMER
DO
fire
A$ = INKEY$
LOOP UNTIL TIMER - time > 1 OR A$ = CHR$(27)' Each generation will take 1''
IF A$ = CHR$(27) THEN SYSTEM
ge = ge + 1
LOCATE 25, 1: PRINT "HUR(1999) h20ur@yahoo.com | Frame:"; ge;
makeforest
LOOP

SLEEP

erro:
RESUME NEXT

SUB fire
FOR x = 0 TO 310 STEP 20
FOR y = 0 TO 190 STEP 20
 IF forest((x + 20) / 20, (y + 20) / 20) = 1 THEN
   num = INT(RND * 2) + 1
   IF num = 1 THEN PUT (x, y), tree3, PSET
   IF num = 2 THEN PUT (x, y), tree4, PSET
 END IF
NEXT y, x

END SUB

SUB makeforest
FOR x = 0 TO 310 STEP 20
FOR y = 0 TO 190 STEP 20

xf = (x + 20) / 20
yf = (y + 20) / 20

IF forest(xf, yf) = 1 THEN  ' if the tree is burning look beside
 PUT (x, y), tree2, PSET: forest(xf, yf) = 4 'the burnt is dead
 rand = INT(RND * 8) + 1 ' which of the four will survive
 IF rand <> 1 AND forest(xf - 1, yf) = 0 THEN forest(xf - 1, yf) = 3
 IF rand <> 2 AND forest(xf + 1, yf) = 0 THEN forest(xf + 1, yf) = 3
 IF rand <> 3 AND forest(xf, yf - 1) = 0 THEN forest(xf, yf - 1) = 3
 IF rand <> 4 AND forest(xf, yf + 1) = 0 THEN forest(xf, yf + 1) = 3
 IF rand <> 5 AND forest(xf - 1, yf + 1) = 0 THEN forest(xf - 1, yf + 1) = 3
 IF rand <> 6 AND forest(xf + 1, yf + 1) = 0 THEN forest(xf + 1, yf + 1) = 3
 IF rand <> 7 AND forest(xf - 1, yf - 1) = 0 THEN forest(xf - 1, yf - 1) = 3
 IF rand <> 8 AND forest(xf + 1, yf - 1) = 0 THEN forest(xf + 1, yf - 1) = 3

 GOTO final
END IF

IF forest(xf, yf) = 0 THEN  'if the tree is live live
  PUT (x, y), tree, PSET
END IF

final:
NEXT y, x

FOR x = 1 TO 16
FOR y = 1 TO 10
IF forest(x, y) = 2 THEN  ' if the tree is burnt live it
  PUT ((x - 1) * 20, (y - 1) * 20), tree, PSET
  forest(x, y) = 0
END IF
IF forest(x, y) = 3 THEN forest(x, y) = 1
IF forest(x, y) = 4 THEN forest(x, y) = 2
NEXT y, x

END SUB

SUB treeload

'this makes the tree

LINE (4, 0)-(0, 4), 14
LINE (4, 0)-(8, 4), 14
LINE (0, 4)-(8, 4), 14
PAINT (4, 3), 14
LINE (3, 5)-(5, 9), 15, BF

FOR x = 0 TO 9
FOR y = 0 TO 4
IF POINT(x, y) = 14 THEN PSET (x, y), 46 + x
IF POINT(x, y + 5) = 15 THEN PSET (x, y + 5), 39 + x
NEXT y, x

' this loads the tree into a array
GET (0, 0)-(9, 9), tree
CLS

'this makes the burnt tree

LINE (4, 0)-(0, 4), 14
LINE (4, 0)-(8, 4), 14
LINE (0, 4)-(8, 4), 14
PAINT (4, 3), 14
LINE (3, 5)-(5, 9), 15, BF

FOR x = 0 TO 9
FOR y = 0 TO 4
IF POINT(x, y) = 14 THEN PSET (x, y), 16 + x
IF POINT(x, y + 5) = 15 THEN PSET (x, y + 5), 18 + x
NEXT y, x

' this loads the tree into a array
GET (0, 0)-(9, 9), tree2
CLS

'this makes the tree burning 1

LINE (4, 0)-(0, 4), 14
LINE (4, 0)-(8, 4), 14
LINE (0, 4)-(8, 4), 14
PAINT (4, 3), 14
LINE (3, 5)-(5, 9), 15, BF

FOR x = 0 TO 9
FOR y = 0 TO 4
IF POINT(x, y) = 14 THEN PSET (x, y), 37 + x
IF POINT(x, y + 5) = 15 THEN PSET (x, y + 5), 39 + x
NEXT y, x

' this loads the tree into a array
GET (0, 0)-(9, 9), tree3
CLS

'this makes the tree burning 2

LINE (4, 0)-(0, 4), 14
LINE (4, 0)-(8, 4), 14
LINE (0, 4)-(8, 4), 14
PAINT (4, 3), 14
LINE (3, 5)-(5, 9), 15, BF

FOR x = 0 TO 9
FOR y = 0 TO 4
IF POINT(x, y) = 14 THEN PSET (x, y), 39 + x
IF POINT(x, y + 5) = 15 THEN PSET (x, y + 5), 37 + x
NEXT y, x

' this loads the tree into a array
GET (0, 0)-(9, 9), tree4
CLS
 

END SUB


Utilities


EZGUI

Hello;

If you are looking for new subjects to discuss, you may be interested in our latest released addon for the
PowerBasic DLL or CC compiler.

Many DOS Basic programmers, would like to move to working on Windows programs (32 bit). Visual Basic
obviously is a great product, but many would like to be able to write code like they did in DOS (or as close to it as possible).

Rayfield Communications, has just released EZGUI for use with the PowerBasic compilers. It is a 122 KB DLL
which handles all the complex stuff normal done with the Windows API. EZGUI comes with its own Visual
Designer so you can design your Forms (Windows).

What is unique about the EZGUI approach, is that while it is "Event" oriented, is also use similar techniques to
DOS programming. For example, it uses a Character based co-ordinate system. To set the colors for a control, you a command similar to DOS ( EZ_Color FG&, BG& ) which sets the current colors  for control commands that come after it. It has the 16 QB colors already predefined, as well as 16 pastel versions of the QB colors (32 predefined, up to 300 can be defined).

EZGUI apps can be created without any knowledge (or use) of the Windows API. DOS Basic programmers find it very intuitive when moving from DOS to Windows.

Best of all, the program is compiles using the existing PowerBasic DLL (5 or 6) or CC (2.0) compilers, so the
applications is fast and small. Large applications can easily fit on a floppy disk.

If you are interested in helping DOS Basic programmers, gravitate to Windows 95, 98, NT, 2000 , then EZGUI is worth checking out.

Many DOS Basic programmers, use the PowerBasic CC (Console) compiler since it emulates the DOS
environment. EZGUI can be used with this compiler. There is a utility that comes with it, that will remove the
default console, so you can create real GUI apps.

For more info, check out our web site at:

http://ezgui.com

Chris Boss
(Developer of EZGUI)
 


Miscellaneous & Credits


Website Award

nominate a site: 

Or you can vote or nominated for your favourite webpage on the Basix Fanzine homepage.


Credits


Many thanks goes out to the people who contribute to this issue of the Basix Fanzine
 
Crash Neth G Soft Corporation
Romel Anthony S. Bismonte The Math Wizard
Matthew River Knight HORIZONS Interactive Entertainment


Contact Info


 
Email Basix_Fanzine@yahoo.com
Webpage www.come.to/basixfanzine


Mailing List


Currently there are around 210 people on the list. Please note that I assume no responsibility for any message that you receive or don't receive from being on the list. Your email address and/or any other information will not be given to anyone.

To join send an email to "Basix Fanzine" <ListProcessor@mindspring.com> with "SUBSCRIBE" in the subject line of the message.

To unsubscribe send an email to "Basix Fanzine" <ListProcessor@mindspring.com> with "UNSUBSCRIBE" in the subject line of the message.


Next Issue

As always I am always looking for articles, tutorials, comments, newsgroup articles, etc. to put in the fanzine. Please send them in. If you're not sure of what to write, check out what other people have requested.



Edited By David Groulx
Copyright © April 2000 The Basix Fanzine