Larry's ACTION! Tutorials
- The Beginning
- Hello World
- The First Mistake
- Sing-a-Long!
- Vocabulary
- Overview I
- Lets Start Programming
- Adding To Our Understanding
- A Wealth Of Knowledge
- The Effect Of Change
- Off And Running
- Kilroy Strikes Again!
- Details, Details, Details!
- Deal Me In!
- A Bit Of Digression
- Caught In The Flow
- Victory
- Breaking The Record!
- An Object Lesson
- Number Twenty!
- Final Episode...
Larry's ACTION! Tutorial was a monthly addition to the SPACE club Newsletter written by Larry Serflaten.
Larry wrote a total of twenty-one tutorials. The first tutorial appeared on January 1995 and the final tutorial appeared on March 1996.
Larry granted everyone permission to distribute any portion or all of his tutorials.
| 1. The Beginning | January 1995 |
The ACTION! cartridge is currently available from CSS for $44.95 + $5 S/H. Order line PH# is (716) 429 5639.
| 2. Hello World | January 1995 |
OPEN(chan, <filespec>, mode, aux2)where
filespec = <filepath> : <filename>This means you must use a proper file path, then a colon (:), then a proper filename. To help explain this type of symbolism (as used in the manual), filepath could be further broken down to:
<device ID> [|: : <sub-directory>:|]Device ID is the one letter identifier for whichever device you are addressing (D for disk drive). To denote an optional parameter, I will use the '[' and ']'. The |: and :| indicate you can repeat whatever they enclose, one or more times. The declaration from above now means; you must use a proper device ID, then you may optionally include the colon and a sub- directory name, and you can repeat the colon and sub-directory name as is needed (or desired). The manual breaks it all out, so that the construction of the <filespec> would be:
<device ID> [|: :<sub-directory>:|] : <filename>These symbols are explained and used in the Language section, but are included here for those of you who may be reading ahead, and to get them explained at the start. The Editor is that part of Action! that first appears when you boot up the system. The Editor will read and write text files, as well as allow you to create or edit text. If you desire, you can split the text editing portion into two sections, called windows. This feature allows you to read in two files at one time, useful for including portions of one file into another! The Monitor is the part of Action! that gives you control over the rest of the Action! system. Take time to read about the Editor and Monitor in the first few sections of the manual. Go ahead and 'get your feet wet' by typing in the indoctrinating "Hello World." program found at the start of the manual. (See "How To Write and Run an Action! Program") The manual leads you step by step to help get your first program typed in, compiled, and run. In "Text File I/O" (Editor section) you can read about saving and loading files from the disk.
No questions yet, next month a little help with error correction, including typos the manual has, and typos you may yet make.
| 3. The First Mistake | February 1995 |
PROC stop()
PrintE("End of program.")
RETURN
PROC hello()
PrintE("Hello World")
stop
RETURN
Now press <CNTRL> <SHIFT> 'M' to get to
the Monitor. Enter 'C' to compile the
program. You will then be exposed to a
compilation error. At this point, the
program will not run. Note the Error
number and open your manual to the Error
Code Appendix. You can try to guess what
the error is if given the correct line,
however, sometimes you are not given the
correct line and you must decide what to
look for by the description supplied in
the Error Code explanation.
If text was displayed in the Monitors'
message area, the cursor would be moved to
the start of that same line. If no text
was displayed, the cursor is right where
it was when you left the Editor. In my
case, I had no text line displayed in the
Monitors message area, only the error
number. Your cartridge may be a different
version, and act slightly differently.
The manual explains this error as an
Illegal assignment. It is also an Illegal
function call, for which there is no other
error number. In my manual I have added
in the words "Jump should be Jump()" to
remind me to look for this type of error
too. Other than this ommission, I believe
all the error code explanations are right.
To correct the error, add the parenthesis
after the word "stop" ("stop()") in the
'hello' procedure, then return to the
Monitor to compile it again. It should
compile correctly now, if it doesnt,
recheck your text to find any mistakes.
If everything is correct, go ahead and run
it. Everybody makes mistakes, especially
when first learning the language. Here
are the typos you should be aware of in
the August 1983 Action! manual:
- = This is the line to look for.+ = This is how it should be.
> = Correct the notes for this line MONITOR / RUN - Program Execution
>RUN "<filespec>" only works when used on programs saved from the Editor. (When it is still in text form.)
-RUN PrintE() <RETURN> does not work.
+XECUTE PrintE() <RETURN> is correct. LANGUAGE / Arithmetic Expressions
>TECH NOTE Using '*', '/', or 'MOD' will not work correctly on large CARD values. LANGUAGE / Record Manipulations
-TYPE idinfo=[BYTE level,
-TYPE idinfo=[BYTE level (omit comma) LANGUAGE / Advanced use of Extended Types
>newrecord=idarray+(reccount*recordsize) The pointer will point to the next record not the end of the array.
>In the main procedure;
-"mode=InputB()" won't work correctly.
+"mode=GetD(7)" works fine. LIBRARY / The PUT Procedures
-PROC PutDE(BYTE channel, CHAR character)
+PROC PutDE(BYTE channel) will compile. LIBRARY / BYTE color
>For Graphics(0) and text windows:
+Chr. luminance (color number = 1)
+Background (color number = 2) LIBRARY / PROC Fill
>col and row must be the numbers of the lower LEFT corner of the box. LIBRARY / PROC SCopy
>The description is wrong, DEST should be dimensioned larger than source, or else identify an array with enough space. While in the Monitor, enter the line: ? $B000<RETURN> The '?' requests the Monitor to display the contents of a memory address. My cartridge displays: 45056,$B000 = 6 $0136 54 310 This means I have version 3.6 which is the last version I am aware of. If yours is 7 or greater, LET ME KNOW
Until next time!
| 4. Sing-a-Long! | February 1995 |
MODULE
PROC verse1()
PrintE("You say good-bye,")
RETURN
PROC verse2()
PrintE("and I say ""hello!""")
RETURN
PROC song()
PROC main()
BYTE ch=764
Graphics(O)
PrintE("PRESS ANY KEY;")
song=verse1 song()
do until ch<255 od
ch=255
song=verse2 song()
do until ch<255 od
ch=255
RETURN
This example gives an indication of why
the parentheses are needed in any call to
a procedure or function. (Remember Error
10 from the last lesson?) Type it in just
as it appears, compile, and run it. In
this example, the word 'song' is used both
as a procedue name and as a variable.
The only distinction between the two uses
are the parentheses following the variable
name:
song=verse1 ; as a variable song() ; as a procedure song()=verse1() ; will not compileHere is another example program:
PROC main()
BYTE ch=764,atachr=763,tmp
PrintE("KEYBOARD TEST PROGRAM")
PrintE("Press any key; Esc=exit")
do
while ch=255 do od
tmp=ch
GetD(7)
PrintF("ch=%U atachr=%U (^[%C)%E",tmp,
atachr,atachr)
until tmp=28
od
RETURN
The above example illustrates one method
of testing for a keypress. In the main
procedure, the BYTE variable 'ch' was used
to test for a keypress. The operating
system from ATARI uses one address to hold
the keyboard value of the most recent key
pressed. From this location the computer
can tell when you have pressed a key, and
will then translate it to ATASCII. The
address of this location is 764 or $2FC.
Action! allows the assignment of addresses
to variables, arrays and procedure names.
By declaring the variable to be residing
at address 764, the program can watch for
any keypresses, just as the computer does.
When no key has been entered, and after
each translation the address will be set
at a value of 255.
Location 763 holds the ATASCII value of
your keypress, after it has been entered
and translated.
Because the GetD(7) statement will reset
ch to 255, tmp was used to temporarily
store the value so that it may be used
in the PrintF statement. You can use this
example to compare the ATASCII values with
the actual keyboard values.
Next month, Vo-cab-u-lary!
| 5. Vocabulary | March 1995 |
DECLARATIONS- Used in declaring the
procedures, functions, or variables;
ARRAY -Preceded by BYTE, CARD or INT to
declare a list of data
BYTE -Declares an 8 bit variable
CARD -Declares a 16 bit variable
CHAR -Same as BYTE
DEFINE -Allows substitution during compile
FUNC -Preceded by BYTE, CARD or INT to
indicate the start of a function
INCLUDE-Adds text from a file, to program
INT -Declares a 15 bit variable with a
sign bit as the 16th bit
MODULE -Indicates the start of global
variable declarations
POINTER-Preceded by BYTE, CARD or INT to
declare a variable reference
PROC -Indicates the start of a procedure
RETURN -Indicates the end of a procedure
RETURN(x) the end of a function
SET -Alters memory during compile
TYPE -Declares a Iist of mixed data
= -Used in DEFINE and SET to assign
equality
$ -Indicates Hexadecimal notation
^ -Contents of a Pointer
@ -Address of a variable
. -TYPE reference
[ ] -Start / End of data block
" -String contents (delimiter)
' -Character conversion to value
; -Remarks (delimiter)
PROGRAM CONTROL-used to structure or
control program execution;
DO -Start of program loop
ELSE -FALSE condition of IF, ELSEIF
ELSEIF -Dependant IF conditions
EXIT -Exit from program loop
FI -End of IF, THEN, ELSE conditions
FOR -Enables loop parameters
IF -Conditional expression
TO -upper limit of FOR loop
UNTIL -Conditional EXIT from loop
WHILE -ConditionaI entrance to loop
( ) -Precedence setting parenthesis
OPERATORS- Used in program statements to
alter or combine variables;
AND -Logical AND
LSH -Bit-wise left shift
MOD -Remainder after division
OR -Logical OR
RSH -Bit-wise right shift
XOR ! -Bit-wise exclusive OR
+ - * /-Plus / Minus / Times / Divide by
& -Bit-wise AND
% -Bit-wise OR
= -Assigns equality
<> # -Not equal to
> -Greater than
>= -Greater or equal to
< -Less than
<= -Less or equal to
MODULE BYTE ARRAY Note= [ 32 16 24 16 24 16 40 ], Delay=[ 30 8 55 6 8 6 60 ] DEFINE note_count="7" PROC Wait(BYTE jif) BYTE rtclk=20 rtcik=0 WHILE rtclk<jif DO OD RETURN PROC Play() BYTE ARRAY Audio(8)=53760 BYTE c AUDIO(8)=3 Audio(1)=166 Audio(3)=170 FOR c=0 to note_count-1 DO Audio(0)=Note(c) Audio(2)=Note(c) LSH 1 +1 Wait(delay(c)) OD Audio(1)=0 Audio(3)=0 RETURNWhen you have this typed in, COMPILE it, then if there are no errors, WRITE the text file to disk. You can alter the Notes and Delay times, but note_count must equal the number of elements in Notes and Delay. If you make something good, write the compiled code, and send it to friends who can simply load it from DOS!
Stay ACTive!
| 6. Overview I | March 1995 |
BYTE w,x CARD y INT zBoth 'w' and 'x' are 8-bit values. They use one memory location to store values in. Their value may be 0-255. The CARD 'y' is a 16-bit variable, it uses two memory locations in the 6502 LSB/MSB format. That is, if 'y' is assigned to address 1536, (Ex CARD Y=1536) then the least significant byte will be in address 1536 while the most significant byte will be in address 1537. CARDs hold any value from 0 to 65535. INTeger variables are 15-bit values, with the most significant bit used as a sign bit. INTegers hold the values from -32768 to 32767. As seen before, when declaring variables, you also have an option to assign that variable to a specific address. Another option allows you to assign a value to it at compile time.
BYTE ch=764 ; assigns an address BYTE spd=[3] ; assigns a valueYou need to use some sort of compiler or numeric constant in the assignment option. Any numeric or compiler constant works! This can lead to some interesting results:
PROC SeeFormat()
BYTE lsb,msb
CARD num=lsb
PrintE("LSB/MSB program O=exit")
do
Print("Enter a number >")
num=InputC()
PrintF("%U > LSB=%U MSB=%U%E",
num, lsb, msb)
PutE()
until num=0
od
RETURN
In this program, the variable 'num' is
assigned the same addresses that are used
for 'lsb' and 'msb'. Once 'lsb' has been
declared, its address becomes "fixed", the
'lsb' identifier is assigned a specific
address. Because its address is now a
compiler constant, it may be used later,
where ever a compiler constant is allowed.
Its the fixed address that is used in the
declaration of 'num'.
As you can see from above, the compiler
usually ignores any spaces you may want to
use. An exception to this is while trying
initialize a string.
Read the LANGUAGE section of the Action!
manual, typing in their program examples.
This will get you familiar with using the
Editor, Monitor, Compiler and language.
I am still looking to answer your own questions. If you have any questions, send them to me on SPACE BBS, or send a note to the SPACE address including a line with the words 'ATTN: Larry S' on the outside of the envelope.
| 7. Lets Start Programming | April 1995 |
- Decide what the inputs and outputs of the program will be.
- OutIine the major tasks that need to be done in the order they are to be done.
- If there are many decisions to be made by the computer, deveIop a flowchart.
- Divide each task into its component parts and look for similarities.
- Group the simiIar tasks together to determine which can be combined.
- Develop a memory map of the computer, showing where in memory the different parts of the program are to reside.
- Type in/write the MAIN procedure first. Fill out the rest of the program, testing each newly added routine for errors and accuracy.
- For inputs, I will send it the current score, and a string containing the amount of increase: AddTo(score,"500") The output should be placed back into the score string before returning.
- I must first find the least significant digit of both strings, add these, store the result and calculate a carry if necessary. I must then add, store, and carry the next set of digits, and the next, until there are no digits Ieft to add. Finally, the new value must be placed back into SCORE.
- There are not that many decisions, the flowchart can wait for another issue.
- This is a component task: increment. There are repetitive tasks, but they are specifically related to adding two strings. For now, I don't see any advantages to breaking them down, and writing them as separate routines.
- Nothing to group, its only one routine.
- SCORE and the other string are provided in the call, I will need a place to hold the results until the operation is done. This string can simply be assigned a location by ACTION!
PROC AddTo(BYTE ARRAY s1,s2)
BYTE ARRAY result(15)
BYTE d1,d2,carry,digit,i
d1=s1(0) ;LSD (digit) of s1
d2=s2(0) ;LSD of s2
IF d1>d2 THEN ;Assign LSD of result
digit=d1+1 ;s1 is longer
ELSE
digit=d2+1 ;s2 is longer
FI
result(0)=digit ;Storage string length
result(1)='0 ;MSD just in case
carry='0 ;clear carry
WHILE d1>0 OR d2>0
DO
result(digit)=carry ;Handle carry
IF d1>0 THEN ;Still more s1
result(digit)==+s1(d1)-48
d1==-1 ;Next digit
FI
IF d2>0 THEN ;Still more s2
result(digit)==+s2(d2)-48
d2==-1 ;Next digit
FI
IF result(digit)>'9 THEN
result(digit)==-10
carry='1 ;Calculate carry
ELSE
carry='0
FI
digit==-1 ;Move to next digit
OD
result(1)=carry ;Store carry
IF carry='0 THEN ;Delete leading 0
FOR i=1 TO result(0)-1
DO
s1(i)=result(i+1)
OD
s1(0)=i-1 ;New length
ELSE ;carry=1 so copy as is
FOR i=0 TO result(0)
DO
s1(i)=result(i)
OD
FI
RETURN
It's YOUR turn!
| 8. Adding To Our Understanding | April 1995 |
PROC Test() BYTE ARRAY score(20) BYTE i score(0)=1 ;Initializing score to 0! score(1)='0 FOR i=0 to 20 DO AddTo(score,"8001") PrintE(score) OD RETURNThis month, we will again use the PROGRAM DEVELOPMENT CHART to make a new routine that will multiply two strings. As you might guess, we will make use of the AddTo routine from last month!
- Like before, the routine will get two string parameters and store the result in the first parameter.
- To multiply, the 1st parameter must be added to itself as many times as are indicated in the ones digit of the 2nd parameter. Moving to the tens digit of the 2nd parameter and a value that is ten times greater than what the 1st parameter was, we again add according to the value in the tens place, etc.
- Flowcharting comes later when there are more conditions to consider....
- We have a similarity, we need to add one string to another, luckily it is already available. (Reusable code!)
- This is only a routine, not too many similarities to try to group them yet.
- Again we can let ACTION! handle string memory assignments.
DEFINE smax="100"(READ in AddTo in this area)
PROC MultiplyS(BYTE ARRAY s1,s2)
BYTE ARRAY temp(smax)
BYTE digit,value,inc,i
FOR i=0 to s1(0)
DO
temp(i)=s1(i) ;Init temp for adding
s1(i)='0 ;Clear result
OD
s1(0)=1 ;Init result
digit=s2(0) ;Ones place
inc=temp(0) ;*10 increment
WHILE digit#0
DO
value=s2(digit)-48 ;value of digit is
WHILE value#0 ;the # of additions
DO ;needed
AddTo(s1,temp)
value==-1
OD
digit==-1 ;Next digit
inc==+1 ;Increase temp * 10
temp(0)=inc ;by adding to length
temp(inc)='0 ;and storing a new 0
OD
RETURN
To help manage the string sizes, I have
included a DEFINE statement at the start
of this procedure. This statement must
manage the string in AddTo also. To
allow for this, change the declaration
line 'result(15)' to 'result(smax)' in
the AddTo procedure.
DEFINE simply substitutes whatever is in
quotes with every occurrence of the string
listed in the DEFINE statement. The
result of this is that the compiler will
use 100 every time it finds smax, this
makes for easy editing, without having to
parse through the entire program looking
for places that may need a new value!
Changing smax once, effects all other
occurrences, pretty neat eh?
Of course we want to see it in ACTION!
PROC Test()
BYTE ARRAY sc(smax)
BYTE i
sc(0)=1 ;MUST be initialized!
sc(1)='1
PrintE("Multiplication!")
FOR i=0 TO 15
DO
MultiplyS(sc,"99")
PrintE(sc)
OD
RETURN
Hey! This could be the start of a huge
calculator! Adding subtract and divide,
and a temporary register for complex
equations, and there it is! Hmmm....
Next month, we get back to BASICs!
| 9. A Wealth Of Knowledge | July 1995 |
PROC Main() BYTE c CLOSE(4) OPEN(4,"D:TEOF",4,0) DO c=GetD(4) PutŠ UNTIL EOF(4) OD CLOSE(4) RETURNCompile and run this program. Is the list exactly as you typed it in? Mine has a funny little character at the end. If yours does too, you will benefit from this next routine. Now above the MAIN routine, type in:
BYTE FUNC SOF(BYTE a) BYTE POINTER bp bp=((a&7) LSH 4) + 835 IF bp^=3 THEN RETURN(1) FI RETURN(0)(Main should be here!) Now change the EOF in the Main procedure, to SOF and compile and run it. Isn't that better? Since you now may have a better routine, rename SOF to EOF and change the Main procedure back to EOF. Try it again. Did you notice the funny extra character? Naming the new routine 'EOF' will in effect replace the EOF array as used in the library. You can do this for any of the routines. The names of the procedures and functions in the library ARE NOT RESERVED names. In fact, this is why a runtime library allows you to make programs that do not need the cartridge. The supplied routines are called instead of the library routines of the same name. Take the time to look over the library, studying how to call each routine. Write tests for the harder ones to be sure you understand their usage. For those of you wanting to dig a little deeper into programming, type this in:
PROC See() BYTE i=$E0,j=$E1 i=0 j=i+1 RETURNAfter compiling this short procedure, use "* See" in the Monitor to see the compiled machine language code. You can use the '*' (Dump) option in the Monitor to check out how the ACTION! system changes your text into machine language. By typing in short programs, you can manually disassemble the code to help you learn machine language. It will come in useful when you have larger programs that you want to optimize. You can expect to cut out about half of the machine code that ACTION! uses, if you want to do some optimization.
Thats the ACTION! system, next month we take a brief look at the ATARI computer system. After a few key points about the ATARI computer, we can then, finally, get to creating our own library.
| 10. The Effect Of Change | July 1995 |
PROC registers()
BYTE ch=764, random=53770,
wsync=54282, toggle
CARD ARRAY regs=[710 53272]
BYTE POINTER bp
PrintE("Hardware/Shadow Esc=Exit")
PrintE("Press any key to alternate")
toggle=0
bp=regs(toggle)
PrintE("Shadow")
do
if ch<255 then
toggle==!1
bp=regs(toggle) ;Change registers
if toggle=0 then
PrintE("Shadow")
else ;Toggle=1 (!)
PrintE("Hardware")
fi
if ch=28 then EXIT ;Esc pressed
else ch=255 ;Reset ch
fi
fi
wsync=1 ;Wait for sync
bp^=random & $E6 ;Dark colors only
wsync=1
od
bp=regs(0) ;Restore original
bp^=148 ;color
RETURN
Location 710 is a shadow register of the
hardware register at location 53272.
The variable wsync is used to synchronize
the processor with the display screen.
Any time wsync is written to, the CPU is
halted until the scanning beam is at the
start of a new scan line. This program
demonstrates the difference between the
shadow and hardware register used to
give color to the background. Changing
the shadow register will change the
background color, only after the vertical
blank period (the time when the scanning
beam is turned off, going back to the top
of the screen). Changing the hardware
register will cause a color change in the
middle of displaying the screen. The new
value will take effect as soon as it is
stored (which happens every couple of
lines due to wsync). Remove the wsync
statements to see a new effect.
As this was written, SPACE BBS was in the
process of being moved to a new location.
Perhaps the best way to receive your
questions is by mail, otherwise, don't
hesitate to ask me about Action!
Next month, practical examples!
| 11. Off And Running | August 1995 |
- No errors. Any error will halt your program. You can tell if your program has an error by running it with the cartridge installed. If it works OK, the Monitor message area will be clear when you exit from your program.
- No library calls. You have to supply all the necessary code to run. Any call to the library will generate an error when run without the cartridge.
- No more than 2 bytes of parameters in any function or procedure call. More than 2 requires a little parameter saving routine found in the cartridge. Thats 2 BYTEs or 1 CARD and no more.
- No MOD, multiplication, or division. You can add and subtract to your hearts content, but you can't try to multiply. These also call a cartridge routine.
- No shift operations using a variable, or using a constant larger than 4.
TO CODE THE STATEMENT: USE:
X= X LSH Y FOR Z=1 TO Y
DO X==LSH 1 OD
X= X LSH 7 X==LSH 4
X==LSH 3
To help you get started on your very own
library, I have adapted the multiplication
program found on page 171 in ATARI ROOTS
to Action! The major changes include the
storing of the Accumulator and X register
to $E0 and $E1 respectively. Action!
expects the value of a function to be
returned in location $A0 and $A1, these
locations were used instead of $C0, $C1.
This function will multiply two BYTE
values and return a CARD value. Often
that is enough to squeek by in a pinch!
CARD FUNC MultiplyB=*(BYTE a,x)
[$85 $E0 $86 $E1 $A9 $00 $85 $A0 $A2
$08 $46 $E0 $90 $03 $18 $65 $E1 $6A
$66 $A0 $CA $D0 $F3 $85 $A1
$60] ;$60 is a RETURN command
PROC Test()
BYTE i,j
CARD z
FOR i=10 TO 200 STEP 15
DO FOR j=0 TO 200 STEP 50
DO z=MultiplyB(i,j)
PrintF("%U*%U=%U%E",i,j,z)
OD OD
RETURN
The '=*' after the word MultiplyB simply
means to omit the allocation of memory
for the parameters. Action! passes the
parameters using the accumulator and X
register. No reference was made in the
function to either 'a' or 'x' so there
is no need to reserve memory for them.
The function obtained the values directly
from the accumulator and X register.
Although the MultiplyB routine will run without the cartridge, the Test routine calls a library procedure. Guess whats coming next issue!
| 12. Kilroy Strikes Again! | August 1995 |
MODULE CARD savmsc=$58,x=$55 BYTE y=$54 CARD FUNC MultiplyB=*(BYTE a,x) [$85 $E0 $86 $E1 $A9 $00 $85 $A0 $A2 $08 $46 $E0 $90 $03 $18 $65 $E1 $6A $66 $A0 $CA $D0 $F3 $85 $A1 $60] BYTE FUNC AscToInt(BYTE chr) BYTE i i=chr&128 ;Inverse bit... chr==&127 ;Gone! IF chr<32 ;Conversion THEN RETURN(chr+64+i) ELSEIF chr<96 THEN RETURN(chr-32+i) FI RETURN(chr+i) ;Implied ELSE PROC CLS() BYTE POINTER bp FOR bp=savmsc to savmsc+959 DO bp^=0 OD RETURN PROC EchoS(BYTE ARRAY sa) BYTE ARRAY dst BYTE i i=0 dst=MultiplyB(y,40)+savmsc+x-1 DO i==+1 dst(i)=AscToInt(sa(i)) UNTIL i=sa(0) OD RETURNYou might want to save this portion for later use, then type in the Test program to see it in use. These and the test program will run, in object form, from DOS! Make sure, then save them. MODULE simply lets the compiler know that you are declaring variables that you want available to ALL FOLLOWING routines. Here we declare a variable to use the systems pointer to screen memory (savmsc), the cursor X position and, cursor Y position. MultiplyB, Hello! Reuseable code is FUN! AscToInt is a routine that changes ASCII to Internal code. This code is used by the system to determine what characters to display on the screen. Why the people at Atari didn't simply use ASCII is not a big concern NOW! You can use the next routine to play around with different values. It basically strips off the inverse bit, then calculates the new value needed, and adds back the inverse bit before returning. CLS is my ClearScreen routine. Changing the 0 to some other value will fill the screen with a different character. EchoS will duplicate whatever is in quotes including non-printable characters. To make it as fast as possible, I did not include any error checking or alter X or Y. With 40 bytes in a line, 'dst' has to be Y*40 bytes from the top (savmsc). It also has to be X number of spaces from the left edge, then offset by 1 to align with the incoming string. If not offset, then 'dst(0)' would be at the X and Y position, but 'sa(0)' is not supposed to be printed, it holds the length of the string. The first byte of the string is actually in sa(1). Offsetting allows 'dst(1)' to be at the correct X and Y position, thereby setting up a means of direct (1 to 1, 2 to 2, etc.) assignments. Using these routines as examples, perhaps you can write your own Echo, EchoB, EchoI, and EchoC. I used echo because, I saw the name used in another program, to do about the same thing, and because it keeps it separate from Print and its options which provide error and boundary checks. Of course you'll want to test them:
PROC Test()
BYTE ch=764
CLS()
X=11 Y=10
EchoS("KILROY WAS HERE!")
x=4 y=12
EchoS("And he wants you to press a key!")
DO UNTIL ch<255 OD
x=2 y=16
ch=255
RETURN
Remember to set X and Y each time!
| 13. Details, Details, Details! | October 1995 |
turn==+1 turn==&3> insures turn will only count from 0 to 3 and then wrap around back to 0 again. Using a mask value of 10 will not limit the count to 10. It will limit turn to be either 0,2,8 or, 10, if that was desired.... The following is an example using alot of bit-wise manipulation, it converts only alpha-numeric characters to their lower case normal video equivalents:
BYTE FUNC ToLower(BYTE a)
BYTE t
t=a&$1F
IF (a&$70)=$30 THEN
IF (a&$0F)<10 THEN
RETURN (a&$3F) ;Its a number
FI
ELSEIF (a&$7F)<$41 THEN ;Too small
ELSEIF t>0 AND t<$1B THEN
RETURN(t%$60) ;Its a letter
FI
RETURN(32) ;Too large
PROC TEST()
BYTE A,B
A=0
DO B=TOLOWER(A)
PRINTF(" %C%C = %C%E",27,A,B)
A==+1
UNTIL A=255 OD
These are the characters that will be accepted; NORMAL INVERSE Numbers $30 - $39 $BO - $B9 Capitals $41 - $5A $C1 - $DA Low case $61 - $7A $E1 - $FAYou might know that the only difference between normal and inverse is that bit 7 is set for the inverse video characters. Once this bit is stripped away, we can limit our efforts to the values under the NORMAL column. The high nibble (a nibble is 4 bits) of every Number is a 3. We can test for that, then a test to be sure the low nibble is 9 or less will insure we have a number to work with. Numbers need no translation, once we have one, we can simply pass it back. Returning a&$3F will strip off the inverse bit of the incoming variable. There are several characters between 0 and $41, To insure no attempt is made to translate them, a simple 'less than' test will eliminate them. Since the next statement after the IF/FI is a RETURN(32), valuable space can be saved by letting control 'fall through' out of the IF/IF condition as opposed to including a RETURN(32) after the THEN. We know the alphabet has only 26 letters. In both CAPS and low case, the letter A has a lower nibble value of 1. It stands to reason that the value for Z must be 25 steps up from the value of A. 26=$1A, so each bit of the low nibble is needed along with one bit of the upper nibble. The command t=a&$1F strips away all attributes from 'a and gives us a value in the range of 0 to 31. A previous test has taken care of values too low to use, so we know this is going to be a letter. If this value is equal to or between 1 and 26, it must be a letter. That test is performed, and if true, the value $60 is added to it to bring it up to the range of lower case letters.
Next month; RELATIONAL operators.
| 14. Deal Me In! | October 1995 |
DEFINE DECKS="4"
MODULE
BYTE ARRAY shoe(52)
;INCLUDE: (From my tutorial #12)
;Global variables; savmsc
; x, y
;Routines; MultiplyB
; AscToInT
; CLS
; EchoS
PROC Echo(BYTE a)
BYTE POINTER dst
dst=MultiplyB(y,40)+savmsc+x
dst^=AscToInt(a)
RETURN
BYTE FUNC PickCard()
BYTE crd,r=53770,vc=54283
DO
crd=(r&$38)%(vc&$07)
UNTIL crd<52 AND shoe(crd)<DECKS
OD
shoe(crd)==+1
RETURN(crd)
BYTE FUNC ShowCard(BYTE a)
BYTE ARRAY pip="A23456789TJQK",
suit=",.p;" ;(Use Control key)
BYTE v,s
s=(a&3) +1 ;Mask out suit
v=(a RSH 2) +1 ;Remove suit
Echo(pip(v)%128) ;Display card
y==+1
Echo(suit(s)%128)
y==-1
IF v<10 THEN ;Calculate value
RETURN(v)
FI
RETURN(10)
PROC Main()
CARD tot,cnt
BYTE i,ch=764
FOR i=0 to 51 ;"Shuffle"
DO shoe(i)=0 OD
tot=MultiplyB(DECKS,52);Total cards
cnt=0 ;Init counter
DO
CLS() ;Clear screen
y=O ;Init Y
DO
x=3 ;Init X
DO
i=PickCard()
ShowCard(i)
cnt==+1 ;Increment counter
x==+2 ;Next column
UNTIL cnt=tot OR x=37
OD
y==+3 ;Next row
UNTIL cnt=tot OR y=21
OD
x=11 y=23
EchoS("Press any key...")
ch=255
WHILE ch=255 DO OD ;Wait for keypress
UNTIL cnt=tot ;All cards out
OD
CLS()
ch=255
RETURN
This month you get a full blown program
to shuffle cards and display them on the
screen. This entire program can be run
from DOS. If you have not written an Echo
procedure, I have supplied one. You must
supply the routines you typed in from
issue #12. When typing in the ShowCard
function, use the card characters in the
suit string. I used printable characters
so that our editor could easily print out
the program: You must hold down Control
when typing the characters between quotes.
Some casinos use 4 decks in what they call
a shoe, for their Blackjack tables. Other
games use less. DECKS is given to allow
for an easy method of determining how many
decks are to be shuffled together.
The first relational operator is in the
PickCard function. In 4 decks of cards,
there are only 52 different cards, and
only 4 of any one card. Both conditions
must be met before the card is accepted.
The next relational operator is in the
Main procedure. I know I cant draw more
cards than are in the shoe, nor do I want
to attempt to print a card somewhere off
the screen. At the start of the loops, X
or Y is initialized. I may or may not run
out of cards while in either of the loops.
As you can see, if cnt=tot, then every
loop will be exited from. The statement;
IF cnt=tot THEN EXIT FI, would work. I
like keeping things small, adding another
IF condition would create the need for
additional code.
Further discussion coming next month.
| 15. A Bit Of Digression | November 1995 |
BYTE FUNC AscTolnt=*(BYTE a) [$85 $E0 ;STA $E0 Save Accu. $29 $80 ;AND #$80 Mask out $85 $E1 ;STA $E1 inverse bit $45 $E0 ;EOR $E0 Gone! $C9 $20 ;CMP $20 a<32 $B0 4 ;BCS +4 >+ Jump if false $09 $40 ;ORA $40 | add 64 $90 6 ;BCC +6 >-|-+ Jump to RTS $C9 $60 ;CMP $60 <+ | a<96 $B0 2 ;BCS +2 >--|-+Jump if false $E9 $1F ;SBC $1F | |subtract 32 $05 $E1 ;ORA $E1<---+-+add Inv bit $85 $A0 ;STA $A0 Still in Accu. $60] ;RTSI listed it in this form for those of you trying to optimize your own code. You can compare the two routines, which operate in much the same fashion. One bit of razzle-dazzle, you may notice I didn't clear or set the carry flag, even though I performed branches that depend upon it. The CMP instruction affects the carry, if operand >= accumulator, carry is set. Also 'SBC $1F' supposes it needed a borrow (Carry is clear) so it takes one away also. The total subtracted from the accumulator is 32 ($20). Zero page memory, used because of its high access speed, is not hard to find. Making efficient use of it is another matter. Action! uses $A0-$A1 to store returned values from functions, and, $A3-$AF to Store parameters. I use $E0-$EF.
Next month, flowcharts!
| 16. Caught In The Flow | December 1995 |
- Black boxes are small, medium, or large. Black items weigh 10 Lbs.
- Red has small, medium, and large boxes. Red items weigh 10 Lbs.
- Blue has 1 large and 2 medium boxes. One of the medium blue weighs 20 Lbs, the other blue boxes weigh 50.
- Black and red has 2 small and 1 med. One of the small black and red items weighs 20 Lbs, the others weigh 50.
When a user has to wait for the computer to finish processing, the computer is most often being slowed down by many iterations in a loop structured algorithm. The programmer must use a good algorithm, coupled with the speed of machine language commands, to reduce the wait time. Moving from flowcharts to actual machine code is part of the programming process and is the subject of the next issue. Advanced users of ACTION! might want to "look in" and give me feedback and/or assistance!
| 17. Victory | January 1996 |
The 12 different boxes of product need 11 gates to provide 12 conveyors to the 12 automatic shelves. My set of eleven tests began with a scale weighing out any package less than 15 Lbs. If your solution has something different, that is perfectly acceptable, provided it meets the needs of the job. Basically the first test should divide the group of products roughly in half, but it is not mandatory. The important part is that it performs as required, and you can check that by using your flowchart to see where each box would get diverted to. The criteria was that the boxes should end up on separate shelves. You might imagine the supervisor may want to invest more cash into the system and ask you to add in the needed equipment to reject certain boxes which fail in a unique way, and return all rejects on a new conveyor to the plant. Such is the way it goes in programming, just when you think your done, somebody shows up and wants to change the program. A good flowchart helps in modifying an already complete program. With a flow chart, the programmer can make necessary changes to the algorithm, and can quickly add in the needed code in the proper spot. This is just a reminder to always document your programs, either by the use of a flow chart, or in the source code itself. You never know when you may decide to add more stuff to a finished program. Moving from words in a flowchart, to code on the computer can be made easy to do. The more details included in a flowchart, the easier it is to translate into code. Flowcharts also help in optimization of algorithms. Careful inspection of a flow chart can reveal redundant loading of a variable, or testing of a value, that may be consolidated or reduced. Looking at the whole process, might give insight to a different algorithm, or method, that will do the same thing even quicker. Some times a better algorithm will be enough, and should be attempted first, but there are occasions when assembly is the best choice. Remember you need only to rework the code that causes a delay, or that will make a perceptable difference. There is no big advantage in optimizing the part of a program that is already performing well. As if to fly in the face of good advice, look back at issue #12's procedures. Here we had a few good routines that were made even better by translating part of them to machine code (AscToInt). EchoS has a loop in it, one of the most basic programming structures, and the most time consuming. Translating this routine to machine code will provide an example of the loop structure as it appears in machine language. First the flowchart:
- Save STRING address
- Calculate DESTINATION address
- Initialize counter
- Load byte from STRING
- Save byte to DESTINATION
- Decrement counter
- Test for end condition
- Loop to "4" until done
- RETURN
PROC EchoS=*(BYTE ARRAY S) ;1. Save STRING address [$85 $A2 ;STA $A2 Save S LO $86 $A3 ;STX $A3 Save S HI ;2. DEST=(Y*40)+SAVMSC+X $A5 $54 ;LDA $54 Y pos $A2 40 ;LDX #40 $20 MultiplyB ;JSR MultiplyB $18 ;CLC $A5 $58 ;LDA $58 Screen LO $65 $55 ;ADC $55 X pos $65 $A0 ;ADC $A0 (Y*40) LO $85 $A4 ;STA $A3 DEST LO $A5 $59 ;LDA $59 Screen HI $65 $A1 ;ADC $A1 (Y*40) HI $85 $A5 ;STA $A4 DEST HI ;3. Init counter $A0 $00 ;LDY 0 $B1 $A2 ;LDA ($A2),Y $A8 ;TAY $88 ;DEY ;START=START+1 S(0) is string Length! $18 ;CLC $E6 $A2 ;INC $A2 $D0 $02 ;BNE 2 $E6 $A3 ;INC $A3 ;4. LOOP start, load byte $B1 $A2 ;LDA ($A2),Y $20 AscToInt ;JSR AscToInt ;5. Store byte $91 $A4 ;STA ($A4),Y ;6 Decrement counter $88 ;DEY ;7 & 8. Test and Loop until done $10 $F6 ;BPL (LOOP) ;9. RETURN from whence we came $60] ;RETURNAs you can see, most of this procedure is spent setting up the parameters for the loop. The loop itself is quite small, only 10 bytes (Two bytes are used to indicate where AscToInt is located). This loop can still be optimized further, if desired, by moving all the code from the AscToInt routine to its proper place in the loop. This results in eliminating the need for jumping (using time to store values on the processor stack and in the program counter) which is time consuming when done repeatedly. The code from the AscToInt routine is needed for this loop to function, so moving it 'in line' will make the loop larger, but will actually reduce the execution time.
Were you wondering how to call a procedure using machine language? You can also call ACTION! routines that you have written. ACTION! passes variables using the A and X registers, and returns function values in $A0 and $A1.
| 18. Breaking The Record! | January 1996 |
TYPE DAY=[BYTE hi,lo ;Record FORMAT
INT change]
BYTE ARRAY memory(400) ;Records storage
DAY POINTER temp ;Record ID
DEFINE RECORD_SIZE="4" ;2 BYTES + 1 INT
DEFINE MAX_DAYS="9" ;99 max!
PROC Make_Record()
BYTE i,r=53770,oldtemp
INT chg
temp=memory ;# 1a
oldtemp=75
FOR i=0 TO MAX_DAYS
DO
chg=r&7
chg==-4
temp.change=chg
temp.hi=oldtemp+chg
temp.lo=temp.hi-5-(r&3)
oldtemp=temp.hi
temp==+RECORD_SIZE ;# 1b
OD
RETURN
PROC SHOW()
BYTE i
Make_Record()
Graphics(0)
PrintE(" ")
printE("DAY High Low Chg")
FOR i=0 TO MAX_DAYS
DO
temp=memory+(RECORD_SIZE*i) ;# 2
PrintF(" %U %U %U %I",
i,temp.hi,temp.lo,temp.change)
PrintE(" ")
OD
RETURN
You can read your manual to get further
information on declaring records. This
program is nothing special, it creates a
list of each days high and low temperature
and indicates the change of the high temp
from one day to the next. Take note,
there are two methods used to access the
records. Method #1 sets 'temp' to the
start of the storage area (1a) and steps
through each record by adding RECORD_SIZE
to 'temp' (1b) each pass through the loop.
Method #2 calculates the proper location
of each record (#2). This random access
method of locating each record is simple
to use and works well for records that
reside completely in memory.
Because each record can be accessed in a
uniform manner, the need for special data
handling code is reduced. To give you a
little practice in using records, try to
write a routine that would allow you to
enter the data for this program. You can
have the user input the high and low
temperatures, and the computer calculating
the amount of change in high temperature
from the previous day. When you have that
routine working, write a program to keep
track of the time of the sun rising and
setting each day. Also keep track of how
many hours of daylight there are in each
day and the change in daylight hours from
one day to the next. The more practice
you get, the better you become.
Records are a very useful tool. As will be shown next month, they can be used to provide an easy means of working with data objects (the manual calls them 'virtual records'), we're going to call them worms!
| 19. An Object Lesson | February 1996 |
INT xx=$E0, yy=$E2 ;DIR parameters
TYPE worm = [BYTE X1,X2,X3,X4,
Y1,Y2,Y3,Y4
INT DX, DY]
DEFINE SIZE="12"
DEFINE COUNT="80" ;1 - 99 MAX WORMS
DEFINE SPEED="250" ;0 - 255
worm POINTER wm
BYTE ARRAY memory(1200),
mask=[$7F $BF $DF $EF
$F7 $FB $FD $FE]
CARD ARRAY screen(96) ;PLOP/FIND table
;-----------------------------------------
; ( Clip and Save!)
PROC PLOP(BYTE PX,PY) ;Faster PLOT
BYTE ARRAY ROW ;command
BYTE XB,XV,PM
ROW=screen(PY) XB=PX RSH 3
XV=PX &7 PM=COLOR LSH (7-XV)
ROW(XB)=(ROW(XB)& mask(XV)) % PM
RETURN
BYTE FUNC FIND(BYTE PX,PY)
BYTE ARRAY ROW ;Faster LOCATE
BYTE XB,XV,PM ;command
ROW=screen(PY) XB=PX RSH 3
XV=PX & 7 PM=mask(XV)!255
RETURN(ROW(XB)&PM)
;-----------------------------------------
PROC PICK_DIR()
BYTE R=53770
xx=0 yy=0
IF R<20 THEN yy=1
ELSEIF R<60 THEN xx=1
ELSEIF R>175 THEN xx=-1
ELSEIF R>225 THEN yy=-l
FI
RETURN
PROC START_UP()
BYTE R=53770,I
CARD SCRMEM=88
;---------Clip this too-----------
GRAPHICS(6+16) ;160 x 96, 1 color
screen(0)=SCRMEM ;Setup array for
FOR I=1 TO 95 ;PLOP and FIND
DO screen(I)=screen(I-1)+20 OD
COLOR=1 ;Border
;---------------------------------
PLOT(0,0) DRAWTO(159,0)
DRAWTO(159,95) DRAWTO(0,95)
DRAWTO(0,0)
wm=memory
FOR I=0 TO COUNT ;Random placement
DO ;of worms
wm.X1=(R&127)+25
wm.Y1=(R&63)+15
wm X2=wm.X1 wm.X3=wm.X1 wm.X4=wm.X1
wm.Y2=wm.Y1 wm.Y3=wm.Y1 wm.Y4=wm.Y1
PICK_DIR() wm.DX=xx wm.DY=yy
PLOT(wm.X1,wm.Y1)
wm==+SIZE
OD
RETURN
PROC MOVE()
BYTE I,NX,NY,Z,J=$AA,R=53770
wm=memory
FOR I=0 TO COUNT
DO
COLOR=0 PLOP(wm.X4,wm.Y4)
wm.X4=wm.X3 wm.X3=wm.X2 wm.X2=wm.X1
wm.Y4=wm.Y3 wm.Y3=wm.Y2 wm.Y2=wm.Y1
COLOR=1
Z=FIND(wm.X1+wm.DX,wm.Y1+wm.DY)
IF Z=0 THEN ;OK to move
IF R<240 THEN ;Do move
wm.X1==+wm.DX wm.Y1==+wm.DY
ELSE ;Change direction
FOR J=0 TO 3
DO
PICK_DIR()
IF FIND(wm.X1+xx,wm.Y1+yy)=0
THEN EXIT FI
OD
wm.DX=xx wm.DY=yy
FI
ELSE ;Not OK to move
FOR J=0 TO 7 ;Find new direction
DO
PICK_DIR()
IF FIND(wm.X1+xx,wm.Y1+yy)=0
THEN EXIT FI
OD
wm.DX=xx
wm.DY=yy
FI
PLOP(wm.X1,wm.Y1)
wm==+SIZE
OD
RETURN
PROC WAIT() ;Slow down display
BYTE I,J
I=255
WHILE I>SPEED
DO
J=255
WHILE J>SPEED
DO J==-1 OD
I==-1
OD
RETURN
PROC MAIN()
BYTE K=764
START_UP()
DO
MOVE() WAIT()
UNTIL K<255
OD
RETURN
As stated at the start of this monthly column, I intended to give you routines you can use, and a little insight into programming with ACTION!. This issue gives you new PLOT and LOCATE routines that are faster than those supplied with ACTION!. In addition to the clip and save section, part of the START_UP routine must be included to initialize the screen array. You may remember it is faster for the computer to look up data in a table than it is to calculate the needed values every time. This is what the array does in the PLOP and FIND procedures.
| 20. Number Twenty! | February 1996 |
TYPE CLASS=[BYTE namlen
CARD n1,n2,n3,n4,n5
BYTE test1,test2,test3,avg,
grade]
DEFINE CLASS_SIZE="16"
CLASS POINTER student
BYTE ARRAY memory(l00)
PROC Enter_Data()
BYTE ARRAY prompt= "Enter test score "
CARD av,gr,tot
PrintE("Enter NAME of student;")
InputS(student)
IF student.namlen>10 THEN
student.namlen=10
FI
PrintF("%S 1; ",prompt)
student.test1=InputB()
tot=student.test1
PrintF("%S 2; ",prompt)
student.test2=InputB()
tot==+student.test2
PrintF("%S 3; ",prompt)
student.test3=InputB()
tot==+student.test3
av=tot/3
student.avg=av
IF av>=92 THEN student.grade='A
ELSEIF av>=84 THEN student.grade='B
ELSEIF av>=76 THEN student.grade='C
ELSEIF av>=68 THEN student.grade='D
ELSE student.grade='F
FI
RETURN
PROC MAIN()
BYTE i,j,x=85,y=84
BYTE POINTER ptr
student=memory ;Locate storage space
FOR i=l to 3
DO
Graphics(0)
PRINTF("Student number %U%E",i)
Enter_Data()
student==+CLASS_SIZE
OD
Graphics(0)
student=memory
PutE() x=2 y=2
PrintE("NAME AVG GRADE")
y=4
FOR i=1 TO 3
DO
x=2 Print(student)
x=16 PrintB(student.avg)
x=22 PrintF("%C%E",student.grade)
student==+CLASS_SIZE
OD
ptr=memory ;Show records
FOR i=1 TO 3
DO
FOR j=0 to 15
DO
y=18+i x=2+j ;Insert spaces
IF x>12 THEN x==+2 ;Tests
IF x>17 THEN x==+l ;Avg
IF x>19 THEN x==+l;Grade
FI
FI
FI
put(ptr^)
ptr==+1
OD
OD
RETURN
This program runs funny, in that whenever the name is less than 10 characters long, the records still hold old data behind the student name. See if you can fix this problem. To see this effect, run the program and enter AAAAAAAAAA for the first name, then run it again and enter Z for the first name. You will see that part of the name AAAAAAAAAA is still in memory.
| 21. Final Episode... | March 1996 |
It has come time to close this column and move on. I want to thank Mike Schmidt our newsletter editor for the great job he has done organizing, printing, and mailing our club newsletter to all our members. All the people in the club who volunteer their time to help SPACE provide support for the ATARI community, deserve a hearty thanks. Its the members, who support SPACE through membership and the purchase of DOMs, that are to be congratulated on keeping this group together for so long. Nice going. This is my last submission, I had a fun time, I hope you gained a little more confidence in your own efforts.