However, there is a way to estimate the size in bytes of your program, although I must emphasise that it is only an approximation, accurate to within a handful of bytes. It could in fact be used as a tool in a statistical determination of the numbers of bytes occupied by given commands, but I'm still working on that one.
The principle of the technique revolves around the fact that the 99/ 4 appears to require 8 bytes (8-bit
bytes, that is, bearing in mind that the 99/ 4 is a 16-bit machine pretending to be an 8-bit) to store the
return address whenever it encounters a GOSUB instruction.
We are fortunate on the 99/4 that the size of the "stack" for these return addresses is not preset, but is determined by the RAM currently available (or so it would appear from my researches), so that a nested series of GOSUB commands (i. e., a recursive GOSUB where the subroutine contains the command to jump to the same subroutine) coupled to a counter (i. e., a numeric variable incremented each time the subroutine is entered) will "fill up" the available RAM until the "MEMORY FULL IN line number" error message is produced.
At this point the counter contains the value corresponding to the number of times the subroutine was accessed, or, looked at another way, the number of 8-byte blocks which the 99/4 was able to use for stacking return addresses.
If we make the incremental value for the counter 8, that will save us having to multiply things out at the end. Try running this program, starting from either NEW or the title page (the line numbers don't matter so much, as long as the GOSUB address is the same address as the previous incremental statement):
110 GOSUB 100
The screen will go green for a few seconds, then blue again as the tone sounds and the "MEMORY FULL IN LINE" error message appears. Now type PRINT A and press ENTER. The magic number 14792 will appear on screen. Those of you who have the July issue of Tidings (Vol. 1, issue 4) will be able to check the "Quotes. from U. S. User-group Newsletters", in which it is stated that:
". . . The 99/4 is not a 16K machine. Basic language resides in 1,208K so we start with 14,792K . . ."
Although it would appear that the U. S. newsletter is referring to decimal fractions of K (using the continental comma in place of the decimal point), it is just too much of a coincidence that the number of bytes obtained by the method given here is identical to the size given by the Americans.
Perhaps they even used a similar approach; however, this means that Basic cannot reside in "1,208K", as 16K is not 16,000 bytes, but 16,384 (16 times 1024).
Obviously the two-line program occupies some of the missing bytes, but it would appear that the item gobbling up our RAM is the "symbol table" or variable list, together with other system variables. If anybody knows otherwise, please write in. There are at least four members who would be very interested to hear more.
Placing the two lines at the beginning of one of your own programs and RUNning until the Memory Full error occurs, then printing not only A but 14792-A will give you the rough size of RAM left, plus the rough size of your program. Don't forget to edit out the two lines when it comes to using your program!
There's nothing to stop you placing the two lines in the middle of your program, where it will be executed while your program is running, and give you some idea of the RAM in use at different points in the program, provided you observe some simple and fairly obvious restrictions; like choosing the variable name in the first of the two lines so that it doesn't conflict with any name used in your program.
And don't edit anything until you have printed out the counter values, otherwise you'll lose them; likewise don't LIST to an external device, like the Thermal Printer, as this also clears all variables.
There's a lot of mileage left in this yet, and I hope to have set some of you off on a trail of discovery which could lead us into the interior of the 99/4. (As I write this, I haven't yet seen the September issue of Tidings, but I understand that member Paul Karis has been uncovering some interesting things with one of the GROMS (more power to his elbow!)
One thing that has come out of this is that it is pointless RESequencing your programs to low line numbers with small increments, as the 99/4 appears to use two 8-bit bytes to store line numbers, with 15 bits for the line number (2 to the power 15 is 32768, and 32767 is as high as we can go) and probably 1 bit to signal that the other fifteen represent a line number. If anybody knows different, let everybody else know: write to us and tell us.
We will eventually elicit much more information (like maybe Basic interaction with GROMs is possible
after all) either from TI direct, or by trial and error and not a little detective work on the part of those
members who are interested in these things.
If anyone does have some little snippets of information, no matter how trivial you may think they are, please write in to Paul Dicks or even me; which reminds me: I recently moved house at very short notice, and for once the GPO acted intelligently and didn't pour post into an empty house. Instead they returned it to the senders if there was a sender's address, so if anyone did try writing to me (I live in hope) my apologies for not replying to you and please can you try again.
Double apologies to anyone who tried ringing me. The new address is:
68 xyzaurne Road
Oxford OX4 9zz
No telephone I'm afraid, unless you're desperate, in which case ring me at work on Oxford 698N1, Ext. 3N5, and if the answering machine is on, please leave a message.
The opening line of this tortuous article mentioned there were a couple of points: here is the second.
Page 151 of the manual states that when INPUTting from a file to the 99/4 it is not possible to give user
prompts. That isn't strictly true.
As far as producing a user prompt WITHIN the INPUT statement, as in
INPUT "ENTER YOUR DOG'S INSIDE LEG :": D
it IS true; but, as I pointed out none too clearly in the last article, if you preface the INPUT statement
with a line containing a pending PRINT statement (i. e., with a trailing semi-colon print separator), then
a user prompt IS possible, in fact a more detailed and more flexible user prompt than otherwise possible.
That means that if you haven't got your cassette recorder under software control (i. e., via the remote socket), you can put up your own PRESS PLAY, AND PRESS ENTER message, and use CALL KEY to wait until you press a key, and respond as necessary.
I'm hoping that sufficient information about the workings of the 99/4 as not told by TI will become available that the newsletter might run a separate column for details of the latest discoveries of intrepid explorers; also perhaps a troubleshooting page for members' queries/ problems to be answered/ solved.
And now, the continuing saga of Luigi Space-Invader, and his sideways motions. No comments, please.
In the last article I rather grandly stated that I would be revealing all about the production of sideways motion of shapes within characters, and that much I hope to do.
However, I also intended to provide working examples of what can be done; I reckoned without my Skantic monitor, which chose that moment to die on me, and had to be sent off for a little surgery.
The result is that I haven't had time to check the routines I was going to give for sideways motion: I have though managed to include one program which shows vertical motion of a shape down through several characters - the shape moves, the characters don't, but hopefully you will understand the explanation of the principles behind the technique sufficiently well to make example programs unnecessary (Ho, ho, ho!).
The kind of hexstring/ binary manipulations required to produce sideways movement of a shape (NOT a
character) are more complex and time-consuming than for simple vertical movement, so real-time
manipulation and re-drawing are out of the question, at least until we are given access to the apparently
marvellous graphics handling which the 99/4 already possesses, but which have been placed off-limits for
some reason, so what follows is an example of what could be possible, given the right tool - i. e., increased execution speed - and can be used to give the necessary hexstrings for subsequent use as DATA
statements in your own programs.
Whereas modifying a definition hexstring to produce movement up or down of a shape (within the "window" afforded by a screen character) consisted simply of either tacking leading/ trailing zeroes onto the hexstring and lopping off the relevant trailing/ leading hexpair - for a 1-off movement - or cycling hexpairs around a long definition string, for repeated movement, things are not quite so easy in practice for sideways movement.
The principle is probably easier to visualise in the above diagram. If we take as our first example the
short-lived motion of a shape from full-blown existence to oblivion, the shuffle sideways affects the
character matrix as shown, by removing the column of binary characters on the left, and replacing it with
a column of binary zeroes on the right.
The new shape then needs to be translated into hex, and the relevant character redefined. (Movement to the right is achieved by reversing the procedure; remove the right hand column, and add a left column of zeroes).
In mathematical terms, the effect is one of multiplying (or dividing) by two, and ignoring the extreme left
or right carry.
It is complicated by the fact that the block corresponding to the right-most character in a hexpair may produce a carry which then has to be incorporated into the character corresponding to the left-most character in a hexpair. This is carried out 8 times until the shape has shuffled off. In fact, DOING it in terms of hex is almost as obscure as it is to explain.
We could, however, keep our definition strings not as 16 (or more) character hexadecimals, but as their binary equivalents, and store them in a string array in such a way that each 8 digit binary string (equivalent to a 2-digit hex string) is stored in a separate element.
This may not seem overly intelligent at first, but when it comes to cycling a multi-digit binary string to
give a large range of characters the dreaded sideways movement, doing it this way is many times faster
than doing it in hex, simply because SEG$ can be used to cycle 1 digit (or more) around for each element
of the array, and then only the first 8 digits are required to produce a hexpair, one for each of eight
elements, to produce the final 16-character definition hexstring. Up to a point, once you have the required
definition hexstrings, you can compress the information into 16 digit hexstrings and place them in DATA
statements in the relevant program.
Vertical motion can be produced by cycling the elements of the array around, and diagonal motion by combining both vertical and sideways operators on a large array of binary digits, where only the first eight digits of the first eight elements are actually translated into hex.
So far the shape manipulation has been limited to movement within a single screen character; if we placed
different screen characters in the relevant positions, we could translate more 8 by 8 blocks from our array,
and distribute the resulting definition strings among the different characters, although at this level the
speed of redefinition is very much slower, and begins to look more like the leisurely screen update seen
on sweep-scan radar screens.
This format however opens up the possibility of having your screen shapes change colour a bit at a time as they are transferred from say the character in one set to the character in another, where the two sets are assigned different colours using the CALL COLOR command. With a black background this can enhance the color change, as the shape appears to "immerse" itself in another color as it moves. The vertical movement example program given overleaf makes use of this effect.
100 CALL CLEAR
120 FOR I=1 TO 4
130 B$= B$& B$
140 NEXT I
150 INPUT A$
160 INPUT C$
180 CALL SCREEN(2)
190 CALL CLEAR
200 FOR I=1 TO 8
210 CALL COLOR(I+8,I+2,2)
220 FOR J=1 TO 31 STEP 2
230 CALL HCHAR(I+14,J,88+I*8)
240 NEXT J
250 NEXT I
260 FOR I=1 TO 113 STEP 16
270 CALL CHAR(96+INT(I/2),SE
280 NEXT I
300 GOTO 260
The program permits entry of hexstrings via two "user-promptless" INPUT statements, so that the
maximum of 128 hex characters can be used.
The maximum is dictated by the number of characters on screen (here there are 8 screen characters, repeated 16 times across the screen), but although the program is primitive and slow, it should demonstrate what can be done with a lot of time and masses of patience.
In order to try and elicit some kind of response I am not going to explain how the program works; if you really want to know how, you'll have to write in and ask (there's cunning!); if more than say one of you responds, I'll give an explanation in a future article. By way of introduction, use the following hexstring in the program:
and just press ENTER when the second INPUT prompt appears.
To stop the cycle you'll need to BREAK with SHIFT C. Try your own shapes and see what happens.
Finally, a little curiosity for the owners of the TI Thermal Printer. Starting from the title page with TP plugged in and switched on (it goes without saying), OLD one of your own programs (using the OLDing monitor if necessary), and add this short program (assuming there are no variable name conflicts with your own program).
nnnn CALL CLEAR
nnnn FOR I=96 TO 104
nann PRINT :CHR$(I); I
nnnn NEXT I
Run this independently of your own program (i. e., RUN nnnn). Immediately list to your TP (LIST
"TP. U. S. E") and watch what happens to characters 98, and 100 to 103. As the characters change, observe
what is being printed on the TP (without wasting too much precious paper). You may be able to see
patterns emerging. If you have some graph paper, or you don't mind drawing out lots of little 8 by 8 grids,
you can "draw" the statements which are being passed to your TP by translating the ASCII code for each
character into binary and combining those in groups of 8 to make up the same matrix as is used by the
99/4 character set.
The word "RETURN" for example appears to look like "ic" on screen in character 100. So far I haven't been able to discover such a simple relationship between statement contents and screen character shape for either the OLDing monitor or for programs under execution (that's something else you can try: a FOR NEXT loop has a characteristic shape on screen while it is being executed), but I'm hoping that some of you may be galvanised into action, and will soon "tell all" to the rest of us.
If you would consider sending the results of your researches to me, I will collate the information and present it from time to time in the newsletter, together with an acknowledgment of your contribution (there's nothing worse than sending in information only to have it presented as the fruits of someone else's labours), and who knows, you may go down in history as the person who made the 99/4 a "cult" machine (and if you believe that, you're as gullible as I am!).
As a matter of fact, going back to the program for finding "bytes free", you can "see" the subroutine return addresses if you print the user-defined characters by using the OLDing monitor, before you run the two line program (i. e., append it to the OLDing monitor), but don't blink: those addresses are stored pretty fast you can even try adding more subroutine jumps, and watching what shape they take: there is a marked difference in shape for a jump to a subroutine which lies AFTER the current line number, compared with a subroutine which lies BEFORE it.
Enjoy your programming, and don't forget to write.
Occasionally it is necessary to use a "toggle" function in programs. Where this toggle has only two states (i. e., ON or its equivalent, and OFF of its equivalent), whether it be as a string or numeric variable, a single statement line suffices, provided that during initialisation (or at least prior to its use if a string) it has been set to one value or the other.
For example, if we wish to toggle the numeric variable A from 0 to 1, we do not have to initialise it (on the 99/4, anyway); the statement line reads:
It can be seen that if A was 0, after the assignment it becomes 1, and if it was 1, after assignment it becomes 0.
Strings are similarly operated upon, except that string and numeric operators are included:
Again, it can be seen that if A$ was "0", VAL(A$) is 0, 1-0=1, and STR$(1)="1". Note though that unless A$ is first set to either "1" or "0" before executing the statement, an error will result, as the 99/4 sets all unassigned strings to NULL, and VAL(NULL) gives an error. Multi-state toggling may require either conditional branching, or modulus equations (q. v.).
The above example shows toggling between two values, 1 and 0, but to toggle between any two values (including alpha-characters) requires a general equation of the form:
E. g.: toggling between 65 and 90 is accomplished by:
nnnn A=155 (i. e., 65+90)-A
and a similar statement may be derived for string variables.
For example, to toggle between "Y" and "N", knowing (or even not knowing) the ASCII character codes:
i) knowing the codes are 78 for N and 89 for Y:
ii) not knowing the codes:
I have used the toggle function in a graphics program which operated upon the binary representation of
a character; toggling between "1" and "0" enabled the program to insert/ delete binary values into/ from
definition groups, thus permitting screen drawing/ erasing from one set of subroutines and one toggling
The usual method of controlling bouncing balls (er . . . you know what I mean) or other graphic characters which move around the screen, is to use conditional jumps (IF . . . THEN. . . ELSE) to prevent a crash resulting from an attempt to CALL HCHAR or VCHAR at a location outside the valid screen locations.
The program in the handbook for a bouncing ball does this, but it means checking for a given value against the limit of travel; it uses up program space (each check usually requires 1 program statement, and usually there are at least 4 checks), and it's a drag to key in, and it's often difficult to get right.
What would be nice is a simple "Modulus"-type statement which automatically places the cursor location at the correct point once the limits have been reached; in other words, if the ball is about to go off-screen at line 24, it reappears at line 1, on a column commensurate with its direction of travel.
For example, a character travelling on a diagonal in the direction North East (towards the top right hand corner of the screen) reaches Row 1, Column 26; it needs to continue from Row 24, Column 27 if the direction of travel is to be maintained. Likewise, if it's at Row 1, Column 1, travelling North West (up into the left-hand corner of the screen), it needs to continue from Row 24, Column 32, etc., etc. Confused? I haven't started yet!
There are two simple equations which fulfil both requirements: that is, controlling the travel from position 1 to the end of the row/ column, and the travel from the end to the start of the row/ column.
All you need are three bits of information: the lowest value of the coordinates, the highest, and the "incremental value" � in other words, the size of the jump from one position to the next.
Suppose we intend using the whole screen to bounce our balls around. The lowest value for the Rows will be 1, the highest 24, and the incremental value (how many squares it is going to jump each time) is say 1.
At present, the equations won't actually give you a bounce, they'll work the same way the Videographics module does; by continuing off at one point, and back on at another (I'm not explaining this very well), but before long I'll have worked out how to make things bounce.
Anyway, the information we have so far is for the Rows. Now for the columns. A lowest value will be 1, highest 32, and increment, again, 1.
The program statement looks like this:
For moving from a low value to a high one "INC":
nnnn V=ABS(V < MAX)*V+S
where V , is the current value of the row or column, max is the highest value, and S is the incremental value.
For moving from a high value to a low value "DEC":
where V is current value of the row or column, max is the highest value, low is the lowest value, and S is the decremental value.
A restriction on this system is that the low and max values must be integer multiples of the
incremental/ decremental values.
All that means is that if low =1, and max =14, then the increment/ decrement must be either 1, 2, or 7. It should become clearer, shortly.
Now does it work? Well, it uses the fact that the 99/4, like many other computers, is capable of producing a numerical value for the conditions TRUE and FALSE. The 99/4 evaluates an expression within brackets usually, involving the relational operators (=, <, >,<>, etc), and returns -1 if the expression is TRUE, and 0 if the expression is FALSE. If you haven't noticed this before, see pages 42 and 79 of the handbook (on Relational Expressions, and IF. . .THEN. . .ELSE).
The ABS() function produces the Absolute Value of whatever is in the brackets, which is defined mathematically as where, whether X is positive or negative, is always positive, so that root gives a positive value for X. In other words, ABS(-23) gives 23, ABS(-17.24) gives 17.24, and ABS(3) gives 3. Used with a relational expression in the brackets, ABS converts the resulting -1 or 0 into 1 or 0.
In the INC equation therefore, (V< max) will return -1 if the current value IS less than the highest, and
0 if the value is equal to, or greater than, the highest. The ABS() function turns those values into 1 and
0, and then is multiplied by the current value, and added to the increment.
Using our values for the Rows of low=1, max=24, S=1, the statement is:
nnnn ROW =ABS(ROW < 24) * ROW +1
Let's put in a few values for ROW to see what happens to them. If ROW is currently 4, then
ABS(ROW<24) will be 1, 1 times 4 (current ROW) is 4, plus 1 is 5, and that is assigned to ROW to make
the new value 5. Ah, but what happens if ROW is 24, I hear you ask (go on, ask, it proves you understand
what I'm saying).
Well, ABS(ROW < 24) gives 0 this time (because ROW is NOT less than 24), and 0 times 24(current value of ROW) is 0, plus 1 is 1, so the new value of ROW is 1. ROW has gone from 24 to 1 without missing a beat. (You don't have to use ROW, use R or something short).
I'm probably making more of a meal of this than is necessary, but it's the way I've been trained to explain things; leave nothing to chance, take nothing for granted, and leave the reader to pore over the text to extract all the information � which IS in there, I assure you!
Now let's try the DEC equation to see how that works. We'll use the same values for ROWS as we did before; LOW=1, MAX=24, S=1.
The statement looks like this:
Let's stick in some values as before. If ROW is 24, ABS(ROW=1) will be 0 (because ROW is NOT 1), 0
times 24 will also be 0, plus 24 (current value of ROW) =24, minus 1 is 23.
All right, how about ROW is 1? Well, ABS(ROW=1) will be 1 (because ROW IS 1), 1 times 24 is 24, plus 1 (current value of ROW) is 25, minus 1 is 24. So ROW has gone from 1 to 24 also without missing a beat.
So where is all this leading? Well, if you use the INC and DEC statements as perhaps subroutines in your
program, you have control aver the position of graphics on the screen without bothering about
IF . . . THEN . . . ELSE lines.
If you want the character to go up the screen by one position, place a call to the DEC routine (going up the screen involves decreasing the row number), and whether the character is in the middle, bottom edge or top edge of the screen, the DEC routine will cover everything.
This means of course that you will need a DEC and INC routine for both rows and columns, a total of 4
lines; using IF statements, you'd need around 4 per routine, so it saves a bit of trouble.
You don't really need to know how the routines operate in order to use them, but as long as you observe the restrictions it should be trouble-free. By the way, one restriction I almost forgot: the starting values for your variables (ROW or whatever) must be exact multiples of the incremental value; in other words, if low is 6, max is 24, and S is 2 (it could also be 3, 4, 6,or 12), the initial value of your variable must be 6, or 8, or 10, . . . or 22 or 24, otherwise funny things may happen.
If you can't make head nor tail of this, and you would like to,(go on, say yes), give me a buzz on't phone;
I'm in most evenings during the week, but at the weekends I usually spread sweetness and light by
travelling huge distances to play with someone else's computer. That ZX81 I mentioned earlier is a mere
21 miles away, closer than the 30 mile ZX80!
Functions: substring sorting
String arrays: W$()
Numeric arrays: none
Numerics: A, B, C, F
1 FOR A=1 TO maximum element number
4 FOR B=C TO 2 STEP -1
5 IF SEG$(W$(A),B,1)>=SEG$(W$(A),B-1,1) THEN 8
6 W(A)=SEG$(W$(A),1,B-2)&SEG$(W$(A),B,1) & SEG$(W$(A),B-1,1)
& SEG$(W$(A),B +1,C-(B+1))
(this is only true for strict accuracy -- otherwise just use C-B)
8 NEXT B
9 IF F=1 THEN 3
10 NEXT A
11 rest of routine
Protocol The sort can cope with variable length strings without difficulty; however, if the strings to be sorted are all of equal known length, then delete line 2, and replace C in lines 4 and 6 by the length of the string.
The A loop will give all the strings in the array W$(), while the B loop will sort them character by character. This routine could be altered to perform a field sort, and would be capable of similar execution times if the fields were of equal known length, but if time was not an important factor, fields of varying length could also be sorted.
Line 6 is complex, but necessarily so, as the manipulations of the particular substrings must be completed
in one statement, unless additional string variables are used to temporarily store intermediate strings for
concatenation, but this will unnecessarily extend the processing time.
Whenever two characters (substrings) are to be swapped, the string is divided into 4 sections; the first section covers the beginning of the string up to the character immediately before the first character to be swapped; the second section is the second character to be swapped; the third section is the first character to be swapped; and the fourth is the section of string continuing immediately after the second character, to the end of the string.
For example, in the string "FUN AND GAMES", beginning from the right-hand end, E is less than S (with regard to character codes) so no swap occurs; but M is not less than E, so the swap routine is called.
The first section of the string is "FUN AND GA" (i. e.. up to the first character to be swapped - first in the string, that is), the second section is the E, the third section is the M (thus swapping M and E around in the string), and the fourth section is in fact just S.
This gives us, after concatenation, "FUN AND GAMES"; to continue, A is less than E, so no swap, but G is not less than A, so a swap occurs again. Each time the swap routine is accessed, the flag "F" is set (i. e., becomes 1 instead of 0), but the test for being set is not made until one complete "pass" has been made along the length of the string, during which the flag may be set several times - it doesn't matter how many times it is set.
After one pass, the string might look like: " FUN AND GAMES" - with a leading space, because the first space (which has a code of 32, and is therefore "lower" than any of the letters here) has been bubbled to the first position on the first pass. Now the test is made to see if the flag, F, is set, and it is, so the routine goes back, resets the flag to zero, and makes a second pass. It goes on like this until the test for a set flag gives zero or false as its result, in which case the routine has been all the way along the string without swapping anything, which means that the string is sorted.
The only drawback to this system is that if the last swap to be made places the string in
a fully-sorted condition, because the swap flag has been set, a second pass will be made along the
already-sorted string; however, this is preferable to some bubble sorts where passes are made (where
N is the length of the string), and no flags are used; these systems require passes even if the string is
already sorted in the beginning!
Functions: sorting, tagsorting
Modified from PCW Numeric Quicksort
During initialisation, the upper and lower limits of the sort need to be set; as standard I use the cursor (chr 30) and the underline (chr 95), neither of which is available from the keyboard. Note: If sorting user-programmable characters, be warned that the relational operators return incorrect values on tests on characters with codes greater than 127.
As the sort stands, it is sufficient for sorting 1024 elements; the capacity is determined by the size (in
elements) of array "S"; antilog(base 2) of the maximum element number gives the capacity: in this case,
the "S" array defaults to 10, and 2 to the power 10 is 1024.
If any increase in capacity is desired, "3" will need to be explicitly dimensioned. Values in the listing which require user-defined limits will be typed in red, lower case letters.
If using the sort as a subroutine, avoid the use of any of the variable names (listed over) which occur in the subroutine, in order to prevent corruption of values in the main routine.
Note that although the arrays are defaulted to OPTION BASE 0, "S(0)" is not utilised. Below a threshold value of items to be sorted, a straight insertion sort is used, as it is faster for small numbers of items; that threshold value (set to 10 in line 6 for a conditional jump) may be altered to achieve the optimum sorting time for a given machine.
The speed of sorting is only marginally affected by the number of characters in a given string, and it is much faster than a bubble sort: 10 secs. for 50 items, 20 for 100 items, 360 for 1000 items. The speed of sorting will also be determined by the proximity of the unsorted state to the sorted state; in other words, how "organised" the items already are.
It is advisable to enter the routine first (i. e. from tape or disk) using the numbering of lines given here, and RESEQUENCE to a suitably high initial line number, and then to enter the main program with
calling routines to the Quicksort subroutine, or to embed the sort within the main program. Alternatively,
the routine may be inserted within a given RESEQUENCED program by adding the initial line number
to the line numbers given here, for jump destinations, etc.
Note that the sort sequence priority is determined by the character codes; thus strings beginning with a leading space, punctuation mark, number, or relational operator symbol, will be given a higher placing than alpha characters.
String arrays: W$()
Numeric arrays: S()
Numerics: A, B, C, D, P
The execution times for large numbers of items are likely to represent an improvement in sorting time by a factor of about 20 or more.
(A slightly slower initial version compared with a bubble sort gave a sort time of 59 seconds for 125 items of between 4 and 12 characters each (used as a Tag sort) compared with over 16 minutes for sorting the same information using a standard bubble sort).
String Quicksort - Listing
2 W$(0)=set lowest character: either CHR$ or " " format
3 W$(1+maximum number of items) =set highest character
5 B=maximum number of items
6 IF B=A < 10 THEN 38
10 IF I$ >=W$(D) THEN 13
12 GOTO 10
13 IF D > C THEN 16
15 GOTO 25
16 REM LINE NOT PRINTED IN ORIGINAL
18 IF W$(C) < I$ THEN 17
19 IF D > C THEN 23
22 GOTO 25
24 GOTO 11
25 IF B -C < C -A THEN 32
28 S(P)=C +1
31 GOTO 6
37 GOTO 6
40 IF D > B THEN 49
43 IF W$(C) <=I$ THEN 47
46 GOTO 43
48 GOTO 39
49 IF P=1 THEN 55
54 GOTO 6
55 end of routine
For Pete's Sake Postscript This article was written and submitted before the revelations by Paul Karis were published. I think you will agree that he is definitely a contender for the position of the person who can make the 99/4 a cult machine. I have no doubt that by the time you read this, further things will be in the pipeline.
With regard to the subprograms uncovered by Paul, the "E" which had him confused is the exponent: as
in 1.25E6 (equivalent to 1.25 times 10 to the 6th power or 1,250,000). In addition, there are a few other subprograms whose
titles are single characters: CALL G: H: P: and S.
The G and H subprograms require parameters of the form: (numeric, numeric, numeric, numeric), but they produce the error message NOT ENOUGH MEMORY.
Perhaps they require the 32K RAM add-on to function. The P subprogram is an odd one; its parameters are (numeric) , but if used in immediate mode while a program is resident, an undefined error message ILLEGAL CALL is produced, but if used in immediate mode after NEW or from the title page, is accepted, although I have been unable to discover its purpose or function.
The S subprogram is likewise an odd one. Its parameters are (string, numeric), but for anything other than a null string produces the error message NUMBER T00 BIG, while a null string causes the whole system to hang up.
The BREAK key can still be made to perform by holding it down and pressing certain other keys; for example, ENTER.
Finally, thank you Mike O'Regan for referring to me as an expert (I am far from that, and that's not being
modest) and the full character range (0 to 255) was to have been the subject of a future article; however,
to prevent the wheel from being re-invented, this is what I have found to date:
character 27: appears to be a form of counter for PRINT instructions.
29: is involved in either Symbol Table generation, or RUN time error checking.
29: signals a Breakpoint.
30: is the cursor (as most of you no doubt found out from the manual).
160 onwards: 160 to 167 are rather odd in that they can be seen to retain their pink color regardless of the screen color; they have a life of their own, but when using the additional facilities; uncovered by Paul Karis, they are altered by the position of the cursor during CALL A.
Curious silence since!
Speaking to Ted Rowthorn of Computer Contacts recently brought this to mind - I use a monochrome TV which cuts off HCHAR column one - I have to amend some progs to take care of this, but of course everyone will have different TV problems)!
OBSTACLE can become a little pointless - either you can make it or you cannot - it becomes a bit more interesting if you add a clock. Immediately after the CALL KEY, add:
FOR CT=1 TO LEN(STR$(TM))
CALL HCHAR (24,2,ASC(SEG$(STR$(TM),CT,1)))
This should come BEFORE the CALL KEY test. Alternatively, if you would like the time to be displayed only at the end, the last three lines can be placed in an appropriate position.
Equally HAMMURABI can drag a bit. I have limited my copy to finish after 25 "years" with an appropriate report. Increment a counter after each "Hammurabi I beg to report. . ." and if it exceeds whatever period you choose, exit to a final report. You can check per cent changes in population and land and comment appropriately!
Have you noticed that with STAR TREK, if you elect to play again you re-enter the same game? The variables have to be reset! I have retained this bug so that I can check the status of the late Klingons vs. the Enterprise. I could of course exit to a separate "final report" and then reset the variables. I'm lazy though!
If you have a copy of 99'er Magazine No. 1, and have tried Invasion from Space, you may now have flat finger tips. To avoid having to hold a key down all the time, add a "key memory", to repeat last instruction until replaced with a fresh one, e. g.:
1530 CALL KEY(0,KK,S)
1532 IF S<> 0 THEN 1560
1560 IF K(etc etc. . .)
Press a different command key to change - press any other key to stop ship and not fire. Otherwise last instruction will be repeated.
If you find that you are having to slowly increase the volume control on your cassette recorder, the problem may be a magnetised head. The data signal seems to have a powerful effect! I found I could drop the volume by 15% after demagnetising, using one of those electronic demagnetisers, built into a cassette shell.
Does anyone have technical contacts in TI? TI dismiss the CALL PEEK subroutine in the Extended Manual, but I think it could be used to obtain, in a program, the following information (obtainable with PEEK on the Tandy):
IS THE PRINTER/ EXPANSION MEMORY/ DISK DRIVE PLUGGED IN AND SWITCHED ON?
This information should be somewhere in memory.
WHAT IS "FILES" CURRENTLY DIMENSIONED TO?
Somehow TI omitted this subroutine, but the info should be available with CALL PEEK.
How about a memory map, TI? (Not writing direct - they seem too busy to reply to letters.) Now is the time for all TI owners to call for better service facilities - before our machines break down! My disk drive arrived with the plug broken in transit. Returned to TI by Securicor 24-hr service. Over ONE MONTH later and they still have not managed to fit a new plug (or send a replacement unit). This bodes ill.
More on CALL A (available in TI Basic when using PRK/PRG module)
If the cursor is flashing over a blank field when you press ENTER, the variable you are trying to fill will take the value of 0, even if you have specified a MIN of 1 or more.
If CALL A has been used previously, with the same variable, or if the variable has been defined previously, it will keep its previous value.
The best way out, if using MIN and MAX, seems to be to test the function variable to ensure it is a "1", if not then return to the CALL A line.
When using CALL A, E, + and - are all accepted - E permits entry of numbers in exponential format.
Thanks Paul for the TI Q'aire with free module! Just got mine in in time, for a free games module. Hope TI have obtained helpful information with this exercise, and will now be able to give this product the full support it deserves.
More BUGS: YAHTZEE although a fine prog does not have a test for the initial question on number of players. Entering over 4 will cause a crash, so if you like neat progs, add a test here.
Sent an SAE to the address given by TI for UK distributor of US software a while back - still waiting for a reply. Oh well. Hopefully things will sort themselves out in due course.
In reply to Mike O'Reagan - Character 30 is the cursor! If you have Extended Basic you can change the
color - it is in SET 0 (CALL COLOR(0, 2, 4)) - but sadly cannot be redefined. Character 31 is (quote) an
edge character (!).
or: How a Program can be Improved for its own Benefit to Run without Error on a 99/4 . . .
When faced with a printed program, which is always for some computer OTHER than a 99/4, do you throw your hands up in dismay and go back to Yahtzee?
Never fear. It is possible (but not always worthwhile!). Your basic tools should be The Basic Handbook by David Lien (about gbp 11) and the October 1981 issue of Computing Today, which has a useful graphics comparison feature.
I shall leave graphics to later (if they are wanted?) and deal here with some other little things you may stub your toes against.
CALL CLEAR. That's clear and easy, but in Apple Basic: CALL-936. Meanwhile in Pet-land chr$(147) does the trick. But in Japan, the MZ 80: chr$(198). In several systems a sort of ASCII standard is: chr$(24) or chr$(12). NB: For future ref: Pet and MZ80 do not use ASCII codes!
Other ASCII codes which you may meet:
7 ring bell
10 line feed
12 clear screen
24 clear screen
13 carriage return
29 return to start of line
30 erase to end of line
31 erase to end of frame
CALL KEY (0, a, b)
Tandy: k$=INKEY$ (possibly easier than the 99/4)
Apple: k$=chr$(PEEK(-16384)) (whew!)
e. g. instead of 20 CALL KEY (0, a, b)
30 IF B < 1 THEN 20
on the Apple you would use:
20 IF PEEK(-16384) <=127 THEN 20
Pet uses a GET command.
The DAI is a nice computer, quite closely related to the 99/4, but with more subprograms. For instance: ENVELOPE 0 12, 30; 12,40 etc, sets up the amplitude waveform of a sound. To call the sound the DAI uses SOUND 1 0 12 0 4000.
To convert this to TI CALL SOUND, the last figure is the frequency (4000=500 cps. (?)), the 3rd figure is the volume(14 maximum). Duration is set with the envelope command - the sum of the 3rd, 5th, 7th etc. figures, where 1=3. 2ms.
WAIT TIME 20 causes the DAI to pause, where 1=20ms.
* * *
SET and RESET can be implemented on the 99/4, but very, very slowly. Best bet is to find out what is going on and rewrite from start. Also PLOT and LINE. More in "graphics" to follow . . .
* * *
In Pet and Apple Basic, CLR sets all numeric variables to zero (as does CLEAR), and string variables to "". Not to be confused with CLS - used in some systems to clear the screen.
DIM If you are converting an Apple prog, you may find string variables in the DIM statement, which appear in the program without subscripts. Just don't use the DIM statement. In Apple, space has to be reserved for strings.
TAKE CARE also - some systems permit the use of variable a(1) and variable a. 99/4 does not permit this, and one of the variables must be renamed. TRS-80 Level 1 does not need dimensioning - 99/4 does, so if you see a subscript over 10, add a DIM statement. (Level 1 limits arrays to a(n) and b(n)).
* * *
Apple: PRINT d$;" NOMONC, I, O" - opens disk file-update. As we all know by now, only TI uses SEG#. This is directly interchangeable with other systems MID$ (except . . . see below). Other systems also use:
LEFT$(A$,2) use SEG$(A$,1,2)
RIGHT$(A$,N) use SEG$(A$,LEN(A$)-N+1,N)
(the figure added to LEN is the last figure less one.)
(web note- right$ above has been amended from printed copy)
You may sometimes come across MID$(A$,4). For this, use: SEG$(A$,4,LEN(A$)-4+1).
* * *
TRS-80 Model 3 has an amusing use of MID$:
will change the 7th, 8th, 9th, and 10th letters of A$ to "yet". For the 99/4, you have to do this the hard way with SEG$ & concatenation.
* * *
If you meet a=13 mod 5 or a=mod(13,5), then use:
(because of internal rounding it will sometimes be necessary to use a=13-INT(13.000001./ 5) *5)
* * *
Note that 99/4 follows ANSI Basic and if the number is a fraction it will be ROUNDED not subject to INT. To clarify matters you may wish to add INT.
Note that if you have difficulty using a program with this line it may have been written for a computer which uses INT. Also, if the number is greater than the number of line numbers quoted, some computers drop to the next line - the 99/4 will return an error message and stop.
* * *
RND may appear as RND(0) or RND(1).
Sometimes RND(0) repeats the last random no. RND(8) is the same as RND * 8, although in some systems you may find RND(n) will return a RND number only when n is a positive value. Note that in 99/4 RANDOMIZE can be used repeatedly if you wish!
Note that 99/4 departs from the standard for INPUT prompts - where other systems use
INPUT "WHAT"; A
we use (more sensibly I think)
INPUT "WHAT": A
You can see why I haven't done graphics this time! Let Paul know if you want graphics details, or if there
is ANY line in any program that you do not follow or cannot translate. Between us we should be able to
Many programs involve so much translation that it is better to find out what they are doing and rewrite from scratch. I have this strong dislike of Pet programs - they seem designed to be difficult to follow. Apple programs are very easy to translate, especially if you have Extended Basic - the PILOT Interpreter in the library is almost unchanged from an original Apple program. (My ExBas version is available in the Emulator Programs section of this web site)
* * *
Just to fill the page, a quick routine to print out the contents of the screen, which may be called by (say) pressing "p" or you can just insert GOSUBs in your program at appropriate points.
PSS=PSS+1 OPEN #1:"TP.U.S.E", OUTPUT PRINT #1: PSS FOR XX=1 TO 24 FOR YY=1 TO 32 CALL GCHAR(XX,YY,SC) PRINT #1: CHR$(SC); NEXT YY NEXT XX CLOSE #1 RETURN
Happy computing . . .
BOOKS: Using Basic - There are two books with this title (at least!) - the one I strongly recommend is written by Page and Didday.
A new version of the Basic Handbook is now out - if you are buying a copy, check - a lot of shops are still selling the first edition. I am ordering a copy shortly and will report in due course. (Now to hand).
The new edition is much better than the first and reflects the growth of the home computer. The only home computer not included is ours - but then we have our own manuals! There are special sections on the Tandy Color, Atari, Acorn Atom, Tektonix , and a discussion of disk Basic.
Although the main work is in alphabetical order, there is also a separate index, which includes references in the special sections. I have used it successfully to translate a prog. for the ZX81 to Extended Basic.
There are many hints on how to implement commands you don't have, such at the MAT commands.
PEEKs and POKEs are not covered.
I am at present collating information to produce a prog to allow the POKE address (and value) to be input and produce the 99/4 equivalent - not too difficult if you allow for reasonable approximations. I'll send a tape in when ready. This will only apply to graphic PEEKs and POKEs!
The Handbook does include the various Tandys, Pets, Apple, Sinclair, TI's 990 computer, SOL, ABC-80 (Swedish!) - the DAI is omitted.
Extended Basic: Faster than TI Basic!
Wot you say? Is he mad? No - running the "space invader" prog from 99er Magazine in TI Basic, under the same conditions, the first ten CALL KEYs took 130 seconds. Adding only one line, and running in Extended Basic, the same run took 85 seconds - quite a bit faster!
a) You need the Expansion Memory
b) Add as line one: CALL INIT :: CALL LOAD(-31878,0)
This stops any sprites moving, but otherwise really speeds up Extended Basic [Update: This only applies to Extended Basic Version 100, Version 110 did it for you].
In Extended Basic:
CALL LOAD(a,b) is not covered in your manual - it is the equivalent of POKE and if used unwisely is a good way to crash your console: (Nasty: but not fatal).
Using CALL LOAD, you can use variables in your progs where TI forbids you to - e. g. DIM(n), GOTO n,
RUN DSK1.N,$, by "pokeing" the values into your appropriate memory location.
The catch is that having established where to POKE you cannot edit or amend your prog (lines up to the POKED line as any editing removes the line from its memory location and puts it at the end (suitably indexed of course). Your program appears in memory locations -1 to -24000 (approx). Line One is at -1. Typically, to read your line you will need to read locations -12 to -1, in that order! Line two, say -30 to-14, and so on.
A value of 0 represents the end of the line (usually but not all the time!) For a full discussion, see 99er Magazine Issue 3, which includes progs for auto-catalog and auto-run of any disk, and auto-REMover.
If you are PEEKing outside the program location, keep in mind that some locations may have "high bit on" the equivalent of adding 128 to the true value - so as well as looking at the value returned, look at the value less 128 (if it's over 128 that is).
If you have edited your prog and want it back in order, use:
SAVE DSK1.X, MERGE
Your prog is now in order and can be saved as SAVE DSK1.PROGNAME. Note: merge dsk.1 can take a long time - the drive light on your disk drive goes on and off and on . . . Don't forget the NEW or it takes even longer . . .
WANTED: Memory location details! John Ashley at TI says these are available but restricted (?) - TI UK are trying to obtain consent to release. Meanwhile in the States TI Inc have a newsletter for their 99/4 owners, giving info like this.
Attention US Users Group: Please send us a copy of TI's newsletter. Ta!
In theory it should be possible to POKE assembly language progs into memory locations, if you know the
language and the locations.
With my Extended Basic I received three sheets of "program errors" but so far I have been unable to repeat any of the error conditions warned against! I suspect the error conditions arose from too much trying to cause an error, and the subsequent problem was not fully understood? Anyone out there had trouble with Extended Basic? I've found it easy to crash TI Basic but so far none with XB -except from badly inserted module.
Happy computing. Stephen Shaw