!!! Action! and BBS Express! PRO Tutorial

by Thomas M. Johnson

Available from

Villa Video's Bargain Cellar (414) 265-5149 ExpressNet Node X11

Action! is copyright of ACS, OSS, ICD. BBS Express! PRO is copyright Orion Micro Systems.

This tutorial is copyright Thomas M. Johnson.

This tutorial can be distributed under the following conditions: 
1. It is free. 
1. All of the above information is intact.


[{TableOfContents }]


!! Lesson 1

Welcome to the Action!/PRO tutorial. In it, I plan to teach you how to write command modules for BBS Express! PRO.  But to write these, you must know the Action! programming language. So this is a 2 for the price of 1 tutorial.

I strongly suggest you print the files of this tutorial out.  There are a number of programs that accompany the text part (the part you are reading now) and smaller program included in the text part that may take a long explanation.  So instead of trying to view this on the screen and paging around looking for what I am talking about, having it on paper would make things easier.

First thing you should do is turn on your computer with the Action! cartridge plugged in.

You should see a flashing cursor in the upper left hand corner of the screen and the words "ACTION! (c) 1983 ASC" in inverse letters at the bottom.  This is the EDITOR mode. This is where you enter and edit your program.  The Action! editor is very versatile.  I am using it right now to write this tutorial.

I guess we should start with the "standard" first program that you see in ANY book that teaches a programming language.  The infamous "Hello, World" example.

To set aside parts of this text that should be entered into the editor, I will indent from here on in.

PROC main()

PrintE("Hello, World")


OK, so now what does it mean?  The word PROC is a Action! reserved word (that means you can't name a variable, procedure or function that name).

PROC is short for PROCedure and that simply means it is a section of code.

The word "main" comes down to us from the language C.  A short digression is needed.  In the language Pascal, the last procedure is the controlling procedure.  It can calls all the other procedures and it is not named.

In C, the controlling procedure is the one called "main".  But it can be anywhere in the program.

In Action!, we compromise.  It is named like C, but it must be last like Pascal.  But in Action!, we don't have to call it "main", we can call it anything we want.  But most programmers call it main.

Then comes the "()".  This mean there are no parameters.  The main PROC can NEVER have any parameters.  If you are wondering what parameters are, we will cover them in a later lesson.

Next is the "PrintE()" line.  This is part of the Action! library.  It prints what ever is between the quotes and puts the cursor on the left side of the screen on the next line down.

Why is the PrintE() line indented? In Action!, indention and capitalization do nothing.  "PRINTE()", "printe()" and "PrintE()" are exactly the same thing.  But to keep programs from 1 programmer to another looking the same, the "PrintE" is used.  And it is indented to show that it belongs to the PROCecude "main".

Lastly, is the word "RETURN".  This signifies the end of a procedure. When it occurs at the end of the "main" procedure, your program returns control back to the Action! cartridge.

How do you run this program?  When it is typed into the editor exactly as shown, you press the CONTROL key, the SHIFT key and the M key at the same time (I will abbreviate this as <C><S>M).

You will hear a buzz and the screen will go blank.  There will be a inverse line at the top of the screen. You are now in the MONITOR mode.  From here you must Compile your program.

You press C then the RETURN key.  If there are errors, you will get a message telling you so.  If you have errors, press E and the RETURN key to go back to the Editor.  If not, you press R and the RETURN key to Run you program.

This is a good place to end lesson one.  If you want to experiment until the next lesson, try adding some more PrintE lines in the program.  Like:

PROC main()

PrintE("Hello World!") 
PrintE("I am programming in") 



!! Lesson 2

In the last lesson, we went over how to compile and run a Action! program.
We also learned that the last PROC in a program is the first one that
is run.  Finally, we learned our first Action! library PROC - PrintE.

Now we are going to cover varibles. In Action!, you must know what type
of values a variable will take on before you can use it.  This is so
the compiler can set aside the right amount of space for that variable.

In Action!, we have 3 basic variable types: BYTE, INT and CARD.

Note: the keywords BYTE and CHAR are identical and can ALWAYS be used in
place of each other.

First the BYTE.  A BYTE can have a value from 0 to 255 and that is all.
When you add 1 to a BYTE with a value of 255 you get 0.  The same when
you subtract 1 from 0 you get 255.

BYTE values are used for loops and most other general values.

The next basic type is a INT.  INTs can have values from -32768 to 32767.
If you need to use negative numbers, a INT is the only way to do it.

Lastly we have a CARD.  CARDs can have values from 0 to 65535.  Use CARDs
when you need larger numbers.  But 65535 is the largest number you can
have.  When you add 1 to 65535 you get 0 for an answer.

The are no real numbers like 5.7 in Action!.  Nor are there scientific
notation like 5.8E4. 

Why does Action! have these restrictions?  I know BASIC programmers are
say "In BASIC we don't have to declare variables and they can be
real and larger than 65535."

But in BASIC, EVERY variable takes up 6 bytes of memory.  In Action!,
a BYTE takes up 1 byte and INTs and CARDs take up only 2.  And this
is the way the computer really looks at numbers.  The floating
point package is the thing that really slows BASIC down.

So we have this chart:

|| name ||  size in bytes  ||  low val. ||   high val
| BYTE |     1     |    0     |      255
| INT  |     2     |  -32768  |    32767
| CARD |     2     |    0     |   65535

Ok, so how do you use them? Here is a sample program.

  PROC main()
    BYTE i
    Print("Give me a number. ")

The first line we know.  It is required by all Action! program to
define a PROC.

The next line declares i and j as variable of type BYTE.  They will only
have a value from 0 to 255.

The Print statement prints what is between the quotes without returning
the carriage after.

The i=InputB() line inputs a BYTE from the keyboard and places its value
in i.

j=i set the value of j to be the same as the value in i.  

PrintBE(j) prints the value of j as a BYTE and returns the carriage.     

If a number larger than 255 is entered the Action! system will take what is
called the "least significant byte" of that number and place that in i.  I
won't go into LSB right now.  But if you want to experiment...

What if you wanted INTs instead of BYTEs?

  PROC main()
    INT i
    Print("Give me a number. ")

OK, the last thing I will do in this lesson is the related Input and
Print functions. 

INT  i

Just declaring some dummy variables.

||Example     ||  Description 
|i=InputI()  |  input a INT         
|c=InputC()  |  input a CARD
|b=InputB()  |  input a BYTE
|Print("hi") |  print string without the carriage return
|PrintE("hi")|  print the string with the carriage return
|PrintB(b)   |  print a BYTE without the CR 
|PrintBE(b)  |  print a BYTE with a CR
|PrintC(c)   |  print a CARD without a  CR
|PrintCE(c)  |  print a CARD with a CR
|PrintI(i)   |  print a INT without a carriage return
|PrintIE(i)  |  print a INT with a CR

Next we will go into expressions and the IF statement.

!! Lesson 3 

In the last lesson we went over    
variables.  The name of a variable
doesn't have to a name like the "i" I
used in the examples.  In fact, they
shouldn't be like that.  Variable
names should be descriptive to you
know what their purpose is.  In
Action!, the only restriction is that
variable names start with a letter.
They may contain letters and the 
underline "_" character later in the
name.  Which is easier to understand?



total_price = (price * tax) + price

This brings us to our next subject,

Action! supports the following 

|| Operator || Description 
|     - | as in negative numbers. Remember only INT can be negative.
|     * | multiply 
|     / | divide. This is integer division because Action! doesn't have real numbers. So when you take 5/2 it is equal to 2 NOT 2.5
|   MOD | This is the remainder when you divide. 5 MOD 2 equals 1 because 5/2=2 with a remainder of 1
|     + | addition
|     - | subtraction 

Action! also has a number of bit-wise
operators but we will cover those 

I guess a sample program would be
good about now.  This sample program
is a little too long for you to have
to type in to your editor.  So I
guess this is a good time to say that
when this occures, the sample program
will be called APROG.301

This means it is a program with the
ATUTOR series.  It goes with lesson
3 and it is the 01 (first) program.

To load the program into your Action!
editor, you press <control><shift><R>
at the same time.  The bottom line
of the editor will print "Read?"

Here you type in the filename and it
will load.  Then you can compile 
and run the program like normal.

You will notice that the variables
are of type INT.  I did this so you
can see how Action! handles negative
numbers.  Try -32767 and 4 for
sample numbers once.

You will see that Action! does some
weird things when you try to go
past the boundaries of the variable
type.  In this case, INT can only
be -32768 to 32767.

Try some nicer numbers to show the
program works fine.  Like 10 and 6.
Or any other number you may want to
try.  You will have to recompile and
rerun the program for each different
set of values.

You may have noticed a PutE() command
in the program.  This places a
carriage return on the screen.  In
other words, it just puts a blank
line on the screen.  I just put it
in there to make it easier to read.

The next things we will cover are
relationals.  Relationals yield a
value of TRUE or FALSE.  An example
might be: 4=7.  This is false.
Here is a list of the Action!

 a=b tells if a and b are equal

a<>b tests to see if a and b are
     not equal

 a\#b the same as a<>b

 a>b is a greater than b

a>=b is a greater than or equal to b

 a<b test to see if a is less than b

a<=b is a less than or equal to b

The are 2 more, AND and OR and I will
cover them in the next tutorial.

Now we will go into the simplest case
of the IF-FI statements.  They are
used like this:

IF <relational is TRUE> THEN


If the relational expression between
the brackets is true, Action! will
execute all the statements between
the IF line and the FI line.

If the relational is false, Action!
will skip all the statements between
the IF and FI and start with the
statement after the FI.

The statements between the IF and the
FI should be indented a few spaces
to show that they are dependent on
the IF statement.  And it also makes
it alot easier to read!

try this program:

   PROC main()

     IF 4>9 THEN
       PrintE("4 is greater than 9")
       PrintE("Do you believe it?!?")


The above program should just print
the word "done".  If 4 should happen
to be equal to 9 today (maybe its
Friday the 13th?) then it would print
the other 2 lines followed by the word

This is the end of lesson 3, I will
leave you with another program that
is too big to print here.  It is
called: APROG.302

!! Lesson 4 

Well, I guess its time we started
talking about loops.  Loops give us
the ability to repeat a section of
code over and over again until
we want it to stop.

The section of code we want to repeat
is marked off with a DO at the start
and a OD at the end.

  PROC main()

      Print("Hello!! ")

This is print Hello!! over and over
and there is no way to stop it other
than pressing the BREAK key or
the RESET key.  So we need a way
to control the number of times
a loop get executed.  Action!
provides us with 3 ways.

The first and probably the least
powerful is the FOR loop.  It is the
most used loop.  Then why do I say
it is the least powerful?  Because
it executes a loop a fixed number of

  PROC main()

    BYTE i

    FOR i=1 TO 8

This program just prints the numbers 1 to 8 vertically on the screen.  ANY
commands can be places between the
DO and the OD and they will get
executed 8 times.

The next loop controller Action!
gives us it the WHILE loop.  It looks
like this:

  WHILE (a condition is true)


The WHILE loop evaluates its 
condition at the top of the loop, so
if the condition is FALSE to begin
with, the loop will never execute.
Try program APROG.401 to see.

The last type of loop control Action!
gives us is the UNTIL loop.

  UNTIL (condition is true)

The major difference between the WHILE
and the UNTIL is that the UNTIL get
evaluated at the bottom of the loop.
This means it will be executed at
once.  After it is executed once, if
the condition is FALSE it will get
executed again.

    PrintE("Press any key or 0")
    PrintE("to exit")
  UNTIL i=0

If the condition i=0 is TRUE the loop

The last loop command I saved for last
to mention because in theory, it is
never needed.  I say in theory because
in the REAL world it can be useful.

The command name is EXIT.  Lets
say your program has to input a
large number.  And the next numbers
are divided into the bug number
until the answer gets down below
10.  But if the user inputs a 0 to
divide into, the is very bad since
a division by 0 doesn't exist.

Look at program APROG.402 for this
program.  You will notice that the
divison is not what you would expect.
That is because Action! only has whole

In Action! if you:

  PrintBE( 1 / 3)

That is take 1 divided by 3, you will
get 0, NOT .33333
Action! has no decimal notation.

I am getting of the subject but I
feel that since it is used in the 
program it is a good idea to explain

Anyway, the EXIT command is good when
you use it in emergency situations,
when you have to get out of a loop
in a hurry.

A EXIT is also useful when you have
alot of conditions on when to end
a loop.

Like: UNTIL (the user answers yes AND
the record number =0 AND you haven't
reached the end of the file AND you
aren't running out of memory space)

I haven't gone into the actual Action!
statements of those situation yet so
I put them in words.  But you get
the picture, there are 4 conditions
to end this loop.

The better thing to do is just have 1
or 2 conditions in the UNTIL statement
and put the others in IF FI with
can EXIT.

MOST other times, if you must have
an EXIT in your loop to make it work
right, you have chosen the wrong
type of loop.

APROG.403 compares doing the same
loop with the 3 looping statements.
Remember, each time a program must
do a loop, it is up to you, the
programmer, do decide which type of
loop to use.  For APROG.403, try
and guess which one is BEST.  They
all work, but one is best for what
I am trying to accomplish.

That's it for Action!'s looping 
commands.  In the next file, we
won't be going into any new Action!
commands.  It will cover some
editor and monitor commands that will
make your writing and debugging of
Action! programs faster and easier.

!! Lesson 5

As promised, now we are going to talk
about advanced editor and monitor
commands.  Knowing these will make
your writing, editing and debugging
of Action! programs many times faster
and easier.

In this file, when I use something
like this: <C><S>M      
That means to press the Control, the
Shift and the M keys at the same
time.  And we all know that doing
that will take us into the monitor.

There are more commands to help
you than I can possibly list.  If I
did it would take me over 100K.  I am
just going to cover the ones I use
most.  I use almost all the commands
at sometime or another.  Please look
at your Action! manual and carefully
read over the sections on these
commands.  They are very important.

I guess we should dive right in with
some editor commands.  You already
know <C><S>R and <C><S>W for reading
and writing to and from the editor.

Pressing <C><S>- and <C><S>= (these
are the arrow keys) will jump you
around the editor 1 screen at a time.
You can really get around the editor
fast using these.  

If you are looking for a certain
word or words in you Action! program,
the editor has the <C><S>F command.
Action! will ask you for the string
to search for.  And zip you are there
or a not found message appear.  This
only searches from the point you
are in, downward.  So if you are at
the end of your file, no matter
what you search for you won't find it
because there is nothing below it.

<C><S>S will also find a string of
characters but it will also replace
it with another string.  This will
come in very handy when we start
programming for BBS Express! PRO.

A word of caution, when you
substitute, use a word that does not
have the old string contained in it.

Let's say you want to change all the
occurrences of the word 'score' in 
your program into 'u_score'.  Changing
the first occurrence is great.  The
cursor will be on the 'u' in 'u_score'.

The next occurrence it will find is the
'score' in 'u_score' so you will get 

If you change a existing line but have
not yet pressed the RETURN key,
<C><S>U will restore it back as good
as new.

To delete a line just press       
<SHIFT><DELETE>.  To delete a bunch of
line just hold this key down.  Oh no!
You just deleted the wrong line, well
<C><S>P will put the entire block of
deleted lines back.

This is also how you move and copy
blocks of text.  Just delete them,
and go to where you want to appear.
Press <C><S>P and it is moved.

If you just want a copy somewhere
else, make sure you <C><S>P it in
the same spot where you deleted
it, the move and <C><S>P again.

To erase you file, <Shift><Clear>
does the trick.

Remember, look in the Action! manual
and read over the commands I did not
cover.  They are very useful!

Now the monitor.  You already know
the C command compiles a Action!
program and R runs it.

B reboots the Action! system without
having to turn your computer off and
on.  This erases anything you have
in the editor and starts you from

O takes you to the options menu.  In
here you can change some features
of the Action! system and also turn
on some debugging flags.

The first option is the Display
option.  When you turn this on,
it speeds up reading and writing
to the disk and printer.  It also
speeds up compiling your program.  It
does this by turning the screen off
while doing these operations.  The
first thing I always do then I load
my Action! cartridge it turn this

If that stupid bell is driving you
nuts every time you go to the monitor
or come across an error while    
compiling, the Bell flag can turn
this off.

The Case Insensitive flag can turn
on the.... well I better show you.

In Action!, the variables:

are all the same.  Turning this on
will make them all different
varialbles.  It will also make you
spell the Action! routines correctly
too.  PrintE is spelled with a
cap P and a cap E.  If this is off,
the default, you can spell it anyway.

The Trace flag prints the PROC
or FUNC you are currently entering
when you run your program.  We haven't
covered PROCs and FUNCs yet but I
will say this is VERY, VERY useful
when debugging.

Most of the time when Action! finds
an error while it is compiling, it
will take you right to the spot in the
editor where the error occurred.
Sometimes Action! doesn't know where
the error is and setting List to y 
will print each line to the screen
while that line if being compiled.
This in only useful when the
Display flag is on.  Compile 
a sample program with this on to
see what it does.

Window size, Line size and Left margin
we won't go into.

EOF character will change the last
character of each line from a blank
space to something you can see.
The Action! manual say it will "aid
visualization of the program."

That's it for the Options menu.  Back
to the monitor commands.  D will
take you to DOS.  If you are
using DOS 2.0 or 2.5 the editor
will be erased so be sure to save
it before you use this.  If you have
DOS XL or SpartaDOS, the editor
remains.  To get back to Action! from
DOS use the B (to cartridge) command
in DOS 2.0-2.5 and CAR in DOS XL and

You can compile a Action! source
program without having to load
it into the editor.  Just put a 
filename after the C for compile.
Make sure you use the whole filename
and device and make sure there are
"quotes" on both ends of the filename.

C "D:APROG.402"

will compile APROG.402 off disk drive #1.  This is useful when your program
won't compile because it is out of

W "D:MYPROG.CMD" will write the
compiled code to disk.  That way
you don't have to recompile it each
time.  Using R "D:MYPROG.CMD" will
load and run your Action! program if
it was saved using W.

If you have the Action! runtime
package, this is how you write       
programs that don't need the cart.
to run.

This is also how you save BBS Express!
PRO command modules.

Well that's about it for now.  I have
barely scratched the surface with the
number of commands you have available
to you.  But, These bare minimums will
help you get around easier.

!! Lesson 6 

Well, now it is time to cover the most
powerful of all the Action! Print
routines.  It is called PrintF for
print formatted.

This baby does it all!

The first thing PrintF must have it
a string in "quotes." If PrintF is
used like this, it is just like

|| command    ||         output
| PrintF("Hello")   |    Hello
| Print("Hello")    |    Hello

But if some special characters appear
inside the "quotes", PrintF can do
anything you want.

The first of the special characters
is the %E. That is a percent sign
followed by an E.  This can appear
anywhere in the "quotes" and Action!
will do a RETURN where the %E appears.

Here is a comparison of the old way
versus PrintF

old way

PrintE("What is your name?")
PrintE("Only 10 characters please.")

new way

PrintF("%EWhat is your name?%EOnly 10 characters please.%E")

It may look a little harder to read
but it actually takes up less memory
and time.  Why? Because with each
call to an output procedure, Action!
must open a channel to the screen,
print what you want and then close the
screen.  So the old way there are 4
opens and closes.  With PrintF there
is only 1.

The next special character is the %U.
What does the U stand for?  It means
Unsigned.  INTs are signed with a +/-
so it must be used by BYTEs and CARDs.
But how does it know what value to
print where the %U is?

Assume a and b are BYTES.  

a=5 b=10

old way

Print("The sum of ")
Print(" and ")
Print(" is ")

new way

PrintF("%EThe sum of %U and %U is %U%E",a,b,a+b)

Both print the line:

The sum of 5 and 10 is 15

After the second quote, there is a
comma and then the BYTEs that get
substituted.  The first BYTE in that
list goes with the first %U. And so
on with the rest.

Then what does a %C stand for?  %C
prints the value associated with it
as a character.  The ATASCII value
for the letter 'A' is 65.  So,

PrintF("The letter %C.%E",65)

will print:

The letter A.

This may not seem very useful but  
when you want to print special
graphics characters it is real nice.

Here is a list of special characters
and how they output the data in the

|| format char || description
| %I | INT
| %U | CARD (the U stands for Unsigned) and BYTE 
| %C | print as a character
| %H | a Hexdecimal number
| %E | the RETURN character
| &#37;&#37; | output the percent sign
| %S | output as a string (we'll cover this in a later lesson)

So what is wrong with PrintF? Well,
it can only print to the screen.
Not to a external device like a
disk or your printer.  Don't worry
about that now, we'll cover that in
a later lesson also.

Try APROG.601 for an example of using
PrintF for handling some complex
output formats.

Well that's is for this instalment.
Short, sweet and to the point.  Next,
we will go into the heart of Action!,
the ability to call PROCs.
!! Lesson 7

Now its time to learn about PROCs.
What is a PROC?  We already know about
PROC main() but what else is there?

A PROC is an independent block of
code that can be called.  Ideally,
a PROC should use only local variables
and not global.  We'll get into that 
in a minute.

You define a PROC the same way you
define main().        


PROC my_proc()

  local variables



The "my_proc" can be any name that
isn't already used by the Action!

For you BASIC programmers, a PROC is
like a GOSUB.  But much more powerful.

PrintE and the most of the other
routines we've looked at so far are
PROCs that are built in to the Action!

Let's take a look at a sample.


PROC my_proc()

  BYTE a


PROC main()

  BYTE a



Can you guess what will be printed?
The output will look like this:



First 'a' is set to 5 in main().
Remember that the last PROC is always
the first one run.  The value of
'a' is then printed.  Then when the
computer gets to the line "my_proc()"
it goes up to the spot where     
"PROC my_proc()" is and starts running

Ok, now we have another 'BYTE a'
and we already declared a BYTE a.
These 2 "a's" are as different as
apples and oranges.  They are local
to the PROC they were declared in.

So 'a' is set to 10 and is printed.
When the RETURN statement is executed,
the computer goes to the next line
after the call to the PROC.  In this
case it will print 'a' again.

And it prints 5.  But in my_proc we
set a=10.  Once again, these are 2 different variables because each
can only be used by the PROC it is
defined in.

A even more powerful feature of PROCs
it the ability to pass it parameters.
Parameters and local variables allow
PROCs to not know or care anything
about the outside program.  A PROC
will have a specific task and it
won't care what called it or why.
Take a look at APROG.701 for an

The call to the PROC in main() looks
like "square(num)".  Action! takes
whatever num is equal to and places
in the variable "number" in
PROC square(BYTE number).

You can have a number of parameters
and they can be any type.  You can
even mix types.  To pass 2 BYTEs and 1 CARD to a PROC use this:



and the call would look like:


There are commas between like variable
types and a space between different

You can have PROCs call other PROCs.
It's important to understand the use
of local variables and PROCs.  Even
more so of the PROCs.  Next time
we will cover FUNCs, which are similar
to PROCs, and the opposite of local
variables, globals.

!! Lesson 8

Do you remember what a function is
from high school algebra?  It take
an number, performs some operation on
it and return only 1 number back.

y = f(x)

where f(x) = x^2

So if x=3 then y=9.

In Action!, a function is like a PROC
only it has a type associated with it.
We can write the x^2 function in
Action! like this.


CARD FUNC squared(BYTE x)

  CARD y

  y = x * x


PROC main()

  CARD answer

  answer = squared(3)


And it will output a 9.

Notice that there is a variable type
before the word FUNC.  This tells
what is going to be returned. A BYTE,
CARD or INT can be used.  As you can
see, the rest is alot like a PROC.
The next different thing is the
RETURN statement.

Whatever is on the left side of the
equals sign in the call, in this
case the variable 'answer' will
get the same value as what is inside
the () after the word RETURN in the

You can see that the input routines
we've been using so far are really  
FUNCs.  When you do a:  

value = InputB()

the first line of InputB in reality
looks like this.


meaning it returns a BYTE to value.

When we first talked about IF FI,  
I said that the conditional after the
IF returned a TRUE or a FALSE.  In
reality, the FALSE is the number 0.
And TRUE is actually any other number.
The most common values for TRUE are 1 or 255 but ANY positive value will

So if you wrote a FUNC that returns
a 0 or a 1, you could use it in
place of a conditional.  This is a
powerful use of FUNCs.


BYTE FUNC values_match(BYTE x,y)

  BYTE rval

  IF x=10 AND y=20 THEN

PROC main()

  BYTE v1,

  PrintE("Please give me 2 numbers.")
  Print("v1 = ")
  Print("v2 = ")

  IF values_match(v1,v2) THEN
    PrintE("Your values match!!")


This is almost a trivial sample of  
the power this type of construct.
If you have seen a program I wrote
running on BBS Express! PRO called
"The Wheel", 50% of that program is
BYTE FUNCs that return a 1 or a 0 to
IF FI statements.

Now, what if you need a variable that
will be used by alot of PROCs and 
FUNCs?  Do you keep sending it as
a parameter?  Well, you could, but
using globals would be better.

We know that using a local variable
doesn't affect any of the other
variable used outside that PROC or
FUNC.  What if we want to change
something in the outside program?
Again, global variables are the

A global variable is a variable that
is not declared between a PROC and
its RETURN or a FUNC and its RETURN.
They are declared at the start of the
program and between PROCs and FUNCs.

The compiler knows a variable is a 
global by use of the work MODULE.
The word MODULE is assumed as
the first line your program.  But it
never hurts to put it in.  If you
declare globals anywhere else in your
program (like between PROCs etc.) then
you MUST have the MODULE there.


  BYTE score

  PROC you_win()

    PrintE("You won this game!!!")
    score = score +100

  PROC main()

    Print("Your score is: ")


You can see that the value of score
in the main() was increased by the
"score = score +100" in you_win().
You can't do this with locals
because when you change a local (this
includes parameters too) it is only
changed within that PROC or FUNC.

Try to resist the urge to use all
globals in your programs.  Globals
make bugs nearly impossible to find.
And there are times when you don't
NEED a variable to change in the
outside program.

When I first introduced PROCs I said
that they were independent blocks
of code.  Using globals makes a PROC
dependent on the rest of the program.

!! Lesson 9

An ARRAY is a group of numbers that
are the same type.  Whether that means
they are a BYTE, INT or CARD.

A string is something like this:
"Hello, how are you today?"

So why are these 2 concepts begin 
presented in the same lesson?  Because
to a computer, they are the same
thing.  A string is just an ARRAY of
BYTE values.  But since a BYTE and
a CHAR are EXACTLY the same thing,
we will use CHAR just so we know
that we are using that ARRAY as a 
string instead of just holding
numbers.  There is one small
difference between strings and
BYTE ARRAYs and we cover that in a
little bit.

An ARRAY is a indexed group of
numbers.  Lets say you have 4 people
playing a game and you want to keep
track of all their scores.  You
could use 4 different variables
like this:


PROC print_score()

  BYTE score1,

  PrintF("Score 1: %U",score1)
  PrintF("Score 2: %U",score2)
  PrintF("Score 3: %U",score3)
  PrintF("Score 4: %U",score4)

Everytime you needed to do something
with a persons score, you have to
figure out who is playing and then
use their particular score variable.

A better way is to use an ARRAY.


PROC print_score()

  BYTE ARRAY score(5)
  BYTE player
  FOR player=1 TO 4
    PrintF("Score %U: %U",player,score(player))

Now any time you need a persons score,
you just place that player's number
in score( ) and it is given to you.

The declaration of an ARRAY is

type ARRAY name(dimension)

type can be any of the fundamental
types; BYTE (CHAR), INT or CARD.
The dimension is how big the ARRAY is
going to be.  In Action!, ARRAYs go
from 0 to dimension-1.  That is why
we declared score(5), now the player
numbers go from 0 to 4.  We could have
used score(4) but then the numbers
would go from 0 to 3 and humans
usually don't think that way.

The type has nothing to do with how
big an ARRAY can be.  The type is
what the ARRAY will hold.  For
example, you can have this:

BYTE ARRAY total_pay(5000)

Now you can have 5000 employees but
each can only have a pay of 0 to 255.
(Sounds familiar huh?)


total_pay(1) = 210
total_pay(2) = 170
total_pay(3) = 250
total_pay(4) = 100
total_pay(4999) = 30

If you want to input a number into
an array you could use:


PROC main()

  CARD ARRAY lottery(10)
  BYTE i

  FOR i=1 TO 9

You use ARRAY just like any normal
variable, but you must subscript
"(i) or something" each time you
use it.

So, what is the difference between a
ARRAY of BYTEs and a string?  The
zeroth (0th) BYTE of a string holds
its length.  For this reason, you
can't do a direct assignment, the
compiler won't let you.  Here's what 
I mean:


  CHAR ARRAY prompt(30)

  prompt="What is your guess? "

That is illegal.  Instead, Action!
provides you with a ton of PROCs and
FUNCs to make working with strings

To assign a string variable some
text, you use:

SCopy(prompt,"What is your guess? ")

This just copies what ever is second
into what ever is first.  So you can
also use:


This will make the variable "again"
equal to the same text as the variable
"prompt".  This is assuming that
"again" was dimensioned to the same
size or bigger than "prompt".

Printing strings is easy, we just
use the normal Print() and PrintE()
we have always been using.  Just now,
you use the string name inplace of
the text.        


Inputing strings is a little different
that inputing normal variables.
Here we use:


Notice that it is a PROC and not a 

Action! also has PROCs that take
part of one string and copy them
into another (sub strings) and taking
a string and copying into part of
another (appending or inserting).
Look in you Action! manual on how
to use these PROCs.

Another thing you can't do in Action!
is compare string like this:

IF prompt=again THEN ...

You must use a built in FUNC that
does this for you.

SCompare(prompt, again)

This FUNC returns an INT value.  It
return a number <0 if prompt is less
than again.  It return 0 if they
are equal.  And it returns a number
>0 if prompt is greater than again.

This is great for alphabetization.  In
fact, APROG.902 does a little

Before I let you go I have to tell
you a little something I left off of

In an IF statement, if the conditional
is not true you can have an ELSE


  PrintE("A is less than B")
  PrintE("A is greater than or")
  PrintE("equal to B")

Action! will execute the second part
only if the first is not true.  You
can also have multiple IFs.


  PrintE("A is less than B")
  IF a=b THEN
    PrintE("A is equal to B")
    PrintE("A is greater than or")

In fact, this happens so often that
we have a special statement for it:
the ELSEIF. The next example is
the exact same thing as the above one.


  PrintE("A is less than B")
  PrintE("A is equal to B")
  PrintE("A is greater than or")

The reason I am mentioning this now
is APROG.901 uses both the ELSE and

!! Lesson 10

Well, I guess its time to talk about
files.  Now, when I say files, I mean
both files and devices.  The only
Atari device that uses files is the
disk drive.  But when it comes down
to using files and devices, things
are done the same.

Your Atari has a few devices built in
to it when you turn it on.

E: - input and output
   - This is the standard Atari editor that is used.  When you do a PrintE() or other simple output, this is the device it goes to. Also, when you do a InputB() this is the device it comes from. It is the screen and the keyboard combined.

C: - input and output
   - This is the cassette player.

P: - output only
   - This was originally meant to stand for parallel but Atarians now know it as printer.  It makes sense that it is for output only, right?

S: - output only 
   - This is the screen.  This is the device that is opened by the Atari when you do a Graphics command.  (We'll cover Graphics later in the tutorial.)

K: - input only     
   - The keyboard.  When you input information from the keyboard, nothing is echoed on the screen.

There are 2 more devices that we call
standard, even though they aren't 
built in to you computer.  They have
to be loaded in before you can use

D: - input and output
   - Your disk drive.  DOS must be loaded before you can use it. Also, a filename must be supplied when you first open this device.

R: - input and output
   - The RS232 port on the 850 or P:R: Connection.  This is used by modems that need the interface.

There is also 1 custom device that
is "famous" enough to merit me
mentioning it here.

T: - input and output
   - This is your 1030 or XM301 modem.

OK, those are the devices.  How do you
use them?  Good thing you asked.  The
first thing you must do before you
can use a device, is open it.


The number "1" is the device number.
After you open a device, from the then
on you refer to it by its device

The "K:" is what device you want to
open. It is a string and must either
be in "quotes" or a CHAR ARRAY

The next number is the command.  Use
this chart to find out which command
to use.

|| Access || Value
| Input Only        | 4
| Output Only       | 8
| Input and output  | 12
| Append to end of file          | 9
| Disk drive directory        | 6

Since a K: device can only do input,
the number "4" is an easy choice.

Just because a device can do input
and output DOES NOT mean you should
choose the number 12.  If you want
to output to a disk file use 8.
The reason the command number 12 is
there is in a situation like this:

If you want to change the 5th BYTE
in a file, you read the first 4
and then over write the 5th.

The last number "0" is required and
there must always be a "0" there.

If you know BASIC, you will see there
is a little difference between the 
order Action! uses and the order
BASIC uses.

In BASIC it is like this:

OPEN #1,4,0,"K:"

The arguments mean the same things,
just a different order.

To output to a file, you use the
standard Atari Print PROCs except
you put a D (for device) at the
end in the proper place.  For example,
if you want to print a string with
a carriage return on the end
to a device, you'd use:

PrintDE(2,"Hi there")

The number "2" is the device number
you assigned it in the Open().  This
can be done to any device that can
do output and the statement will be
exactly the same.

Here are the output PROCs.    

|| Proc || output
| PrintD    | string
| PrintDE   | string with CR
| PrintBD   | BYTE
| PrintBDE  | BYTE with CR
| PrintCD   | CARD
| PrintCDE  | CARD with CR
| PrintID   | INT
| PrintIDE  | INT with CR

The input statements are close to the
normal input FUNCs as well.



Input a byte from device number 1.

Here are the input FUNCs.

InputBD - BYTE
InputCD - CARD
InputID - INT

And for strings:

This get a string from device number
3 and places it in name.

There are 3 more statements you can
use with devices.

c=GetD(1)  - get a single character
             from device number 1

PutD(1,c)  - put a single character
             on device 1.
PutDE(1,c) - same but CR after

InputMD(3,name,20) - Get a string from
                     device number 1
                     and only 20
                     characters long.

When you are done with a device, you
must always close it.

Close(4) - close device number 4.

There are a few things that are   
different when you use D:

The first is you must supply a
filename.  It can be 8 characters
long with a 3 character extension.


If you use a DOS that has
subdirectories, they can be included
in the device string as well.

If you use Open a disk file with
a command number of 8, it will
create a new file if it does not
already exist or erase the old file
if it does.

When you Open a disk file with
command number 4, it always starts
reading at the beginning.

It is a good idea to get in the habit
of close a device number before
you open it just to make sure it is
ok to use.  If you try to open a 
device number when it is already
open, you program will bomb.

The device numbers that you are     
allowed to use are 1 though 7.

Lastly, every time you compile an
Action! program, a BYTE ARRAY is
automatically declared for you.  The
ARRAY is called "EOF".  EOF holds
either a 0 or a 1 so it can be used
in a IF, WHILE or UNTIL statement.

When using disk or cassette files,
you cannot try to read past the end
of the file.  If you do try, the
program will crash.

So you have 2 choices, either you
have to save the number of entries
in that file and use a FOR loop
to read in exactly that number of
entries.  Or use EOF.

EOF holds a 1 if you are at the end
of file and 0 otherwise.  For


If device number 2 is at the end of
the file, then this will be a 1.  The
best way to use EOF is in a WHILE
loop because a WHILE loop checks
before the loop is run the first


PROC read()

  BYTE character

  WHILE EOF(1)=0 

This PROC prints ATUTOR.001 on the
screen character by character.  The
file ATUTOR.001 must be on the disk in
disk drive #1 for this program to 
work.  Also, you may have noticed that
I didn't call it main() this time.
Remember, you don't have to call
it main().

This type of PROC might be good if
you wrote a game and this will print
the instruction file to the screen
for the user.

You may also have noticed that reading
character by character is not the most
efficient way to do this.  A better
way would have been to read the whole
line in with a InputSD() and echo
it with a PrintE().  But to so this 
the last line must end with a CR
before the end of file.  If you KNOW
that it does, you can use the next
PROC instead.  But, if you are
unsure, you don't want to make
assumptions.  (By the way, ATUTOR.001
does have a CR before the end of file
so it is OK to use this PROC)


PROC better_read()

  BYTE ARRAY line(50)

  WHILE EOF(1)=0 

!! Lesson 11

We have just finished pretty much of
the basics of Action!  Now it's time
to get into the more advanced topic
that Action! offers.

The first of these is the POINTER.
A POINTER does not hold a value like
a "normal" variable.  Instead it tells
us where that value physically is 
inside your Atari.  To declare a 
POINTER we first have to declare what
type it will point to.



There are 2 new operators that can
be used with POINTERS. If we have
a variable i of type INT we can
use this:



This means g points to i.  Well, it
really mean that we have assigned
g to point to the same memory location
as i.  g holds the memory location
of i.

Another new operator we now have is:


This says to put the value 5 into
the memory location that g points to.

Try APROG11.001 and try and follow
what is going on.  You can see that
POINTERs are a nice way of simulating
the BASIC POKE command.  But Action!
has even better ways to accomplish

They say "A picture is worth a
thousand words" to I'll try it now.
I'll try and show you this PROC


PROC m()

  CARD c




Ok, we have 2 objects to begin with: 
I'll use ? to mean we don't know for
sure that these value hold since we
didn't assign them to anything yet.


  ?               ?
  cp              c
When the assignment c=10 come up
the variable look like this:


  ?               10
  cp              c

When cp=@c is executed, cp is assigned
to point to the memory location that
c occupies.  NOT to the value in c.


  -------|        10
  cp     -------> c

Then, we change the value in the
location cp points to to 6.


  -------|        6
  cp     -------> c

I hope that helped a little.

Have you ever wanted to change the
value of parameter you passed to a
PROC or FUNC?  Well, before now you
have to create a global and not use
a parameter at all.  POINTERs allow
you to keep PROCs and FUNCs
independent by not using globals
and allowing you to change the value
of a parameter outside of the PROC. 




PROC main()
  BYTE a


Well, I bet you are saying, "That's
nice, I MIGHT use that once in a
while, but big deal otherwise."  I am
going to introduce 2 more types of
new Action! ideas then you will see
the importance of POINTERs.
We are going to leave POINTER for a
moment to talk about 2 things.  The
first is VERY short.  It is the 
Action! directive DEFINE.  You use
DEFINE to clarify your programs and
make them easier to read.  And they
take up no extra memory so you can
use tons of them without any overhead.

DEFINEs simply substitute one thing
for another.  We know from a previous
APROG that Put(125) clears the screen.
But you really can't tell that just
by looking at it can you?  But CLS
looks like it might mean clear screen.
So we can use:


DEFINE CLS = "Put(125)"

PROC m()


Or you can use it like this:



PROC any()

  FOR i=1 TO MAX
     ;read in values
  FOR i=1 TO MAX
     ;print out values

This way, if you want to increase the
maximum number of entries, you only
have to change the value in MAX, and
all the times it is used are changed.

The next thing is records.  You may
have noticed that when you declare
an ARRAY, all the items have to be
the same type.  All INTs, all CARDs
etc.  Records allow us to mix types
into one object.


TYPE new = [ BYTE player_number,

             CARD season_total,
                  lifetime_total ]
new bball_player

We have just made a new type of
variable.  When we use the

new bball_player

it is just like


"new" is the type and bball_player is
the variable name.  To use a record
we have "dot notation".  That is just
a fancy way of saying we do this:



And to retrieve this information we
can just:


I think this is a good time to stop.
We have gone over alot in a short
time and this stuff is pretty hard to
get the hang of right away.  And it
is probably hard to think of what
you can use this for.  And your
also saying, "Hey, you said there  
would be more with POINTERs!"  We'll
go over how to get more out of
records using POINTERs next time.

!! Lesson 12

Well, as you probably noticed in the
last file that POINTERs and records
aren't all that useful by themselves.
In fact, I decided not to include
a sample program about records because
I couldn't think of a good example.

But, when you mix POINTERs and 
records you get alot of power.  You
may have tried to make a ARRAY of
records.  This is illegal in Action!
But, if you use POINTERs to accomplish
this it will work.

Also, you cannot have a ARRAY as a
field in a record.  Since strings are
just CHAR ARRAYs, you can't, for
example, associate a name with other
information about a person.  Again,
POINTERs make this possible too.

Even more, you can't have ARRAYs of
strings.  You guessed it, POINTERs to
the rescue again.  I will be covering
all of these in this lesson.  You
don't have to fully understand records
and POINTERs to use the concepts I am
introducing.  You can just copy the
routines, etc. and modify them for
whatever use you have in mind.  But,
a good understanding of POINTERs will
allow you to even more powerful things
in Action!

First, how do you make an ARRAY of
records in Action!?  First you have
to decide what your record will look


  TYPE employee=[CARD ssnumber1
                 BYTE ssnumber2
                 CARD ssnumber3
                 BYTE department,
                      salary ]

Now we have to count up the total
number of bytes in the record. CARDs
and INTs take up 2 bytes and BYTEs
take up 1.  So, here we have 2 CARDs
and 3 BYTEs for a total of 7.

  DEFINE size = "7"

We have to decide how many records we
want to hold.  Our company is kind of
small, we only have 6 employees.  So, 6 employees each taking up 7 bytes.
We have to reserve 42 bytes of memory
to hold our information because 6*7=42.

  BYTE ARRAY company(42)

Now its time to make that POINTER I
have been talking about.

  employee POINTER info  

'employee' is the type of record that
the POINTER will point to.  'info' is
the name of the POINTER.

Lastly, you need an equation to
figure out where in memory this really
is.  This equation is the same one we
will use for all advanced record and
POINTER manipulations.
  info = company + (counter * size)

That's it!

label | use
info    | The name we gave our POINTER
company | The ARRAY we used to reserve memory.
counter | This is the record number we wish to look at. Since we have 6 employees this will be a number from 0 to 5.  This can be a constant like 3 or a variable like in a FOR loop.
size    | The number of bytes in a record in our DEFINE line.

Ok, so how do you use it?  Easy...
If we want to enter employee number 3's information.

  info = company + ( 3 * size)
  info.ssnumber1 = 392
  info.ssnumber2 = 80
  info.ssnumber3 = 4593
  info.department = 3
  info.salary = 7

And if employee #2 got a raise to
paycode #10

  info = company + ( 4 * size)
  info.salary = 10
If you want to print employee #0's
social security number:

  info = company + ( 0 * size)
  PrintC( info.ssnumber1)
  PrintB( info.ssnumber2)
  PrintCE( info.ssnumber3)

The numbers in the record can be used
just like any other variable.

  info.department = 4 * 5

Or whatever.  Here is a little picture
that I hope will help you to see what
is going on.  In case you didn't know,
when you declare a ARRAY in Action!,
the ARRAY name is actually a POINTER
to where the ARRAY is in memory.
So, let's say our ARRAY starts at
memory location 16000.  Also, I'll
use - to represent bytes.  So a CARD
would have 2 bytes, --.


  TYPE employee=[CARD ssnumber1
                 BYTE ssnumber2
                 CARD ssnumber3
                 BYTE department,
                      salary ]



record 0     record 1    record 2
-- - -- - -|-- - -- - -|-- - -- - -|

To get record 1 we use this formula:

  info = company + ( 1 * size)

Sticking in the numbers:
  info = 16000 + ( 1 * 7)
  info = 16007

So, we move our pointer over 7 bytes
to location 16007.


record 0     record 1    record 2
-- - -- - -|-- - -- - -|-- - -- - -|

To get record 2:
  info = 16000 + ( 2 * 7)
  info = 16014

So, we move our pointer over 14 bytes
from location 16000 to 16014

record 0     record 1    record 2
-- - -- - -|-- - -- - -|-- - -- - -|

But records can't contain strings.  So
POINTERs must be used again to trick

What if you wrote a game where there
are multiple levels.  When the first
player dies, the second player takes
over, right where he left off.  Well,
you have to keep track of the players
score, level and name.  The score and
level are easy with records, but the
name?  We'd use this:


  TYPE record = [BYTE level 
                 CARD score
                 BYTE name]

Why only 1 BYTE for the name?  This
is just the first BYTE of the players
name.  We'll save more space for it
in a second.  Count up the bytes    
without the name BYTE. 1 BYTE and 1 CARD = 3 bytes

  DEFINE offset = "3"

We'll save 20 bytes for the name. So
add the length of the name and the
offset value for the size.

  DEFINE size = "23"

How many players maximum can play our
game?  We'll just say 8.  So, 8 * 23 = 184

  BYTE ARRAY players(184)

Now we need 2 POINTERs.  One like
normal, and 1 to point to the name.

  record POINTER active_player

  CHAR POINTER his_name

The first POINTER we use just like
we have been before.

  active_player = players + (count * size)

But to get the name we use:
  his_name = active_player + offset

That's it!  We'll so some assignments
to record number 4.


  active_player = players + (4 * size)
  his_name = active_player + offset


Now lets output player 6.


  active_player = players + (6 * size)
  his_name = active_player + offset

  Print("Level: ")
  Print("Score: ")
  Print("Name:  ")

Ok, the last situation like these
is if you need an ARRAY of different
size strings.  Let's say a you need
a program to keep track of your
customers first name, lastname and
the last date the ordered from you.
We'll say the date is of the form

How big to we want each field?

field    |         size in bytes
firstname       |      11
lastname        |      14
date            |      9

Try to declare your sizes 1 bigger
because strings go from 0 to 1 less
than their size.  This is needed
because, remember, the 0th byte is the
length of the string.

There are no records involved this
construct.  Here we just use
POINTERs to get around.  The first
DEFINE always starts at 0.

  DEFINE firstname = "0"

If you want to the firstname to be
11 bytes, the lastname must start
at the 12th byte.  0 + 12 = 12

  DEFINE lastname = "12"

If the lastname is 14 bytes long the
date must start 15 bytes later than
the lastname.  This means the 27 byte
overall.  12 + 15 = 27

  DEFINE date = "27"

The total size is 27 + 9 = 36

  DEFINE size = "36"

How many customers do you have?  Let's
just say 100.  100 * 27 = 2700

  BYTE ARRAY data(2700)

Since we don't have a record only 
characters, our POINTER is just: 


The POINTER is just like normal

  ptr = data + (counter * size)

Once again, that's it! For customer #53:
  ptr = data + (53 * size)

  Print("First name: ")
  InputS(ptr + firstname)
  Print("Last name: ")
  InputS(ptr + lastname)

  Print("Last order date: ")
  InputS(ptr + date)

APROG12.002 is a full featured phone
book based on this last subject.

!! Lesson 13

Well, here we are at the last lesson
of Part 1.  And I have to apologize
for this one.  There are a lot of
little things I want to mention. Most
of them have nothing to do with any 
of the others.

The first has to do with assigning
variables and routines to specific
memory locations.  You saw how you
can use POINTERs to change memory
locations (like the background
color of the screen).  But there
is a better way.  When you declare
a variable, you can assign it to a

  BYTE bkgnd=710

Now any change you make to bkgnd will
go into location 710.


This will change the background color
to black.  You can also assign
PROCs to specific memory locations
too.  That way, if there is a routine
loaded into memory, you can call
it easily.  

  PROC machine_routine=$3500 (BYTE ARRAY s)

In Part 2 of this tutorial we will use
this quite a bit.

There is another thing you can do with
variables where you declare them.  You
can assign them to a value using
~[square brackets\].

  INT i=[-32]
  BYTE b=[0]

So, if you do a:      


you will get


Square brackets are also used for
inserting machine language subroutines
in Action! programs.  You can use
hex values or decimal.  You can even
access Action! variable.  Here's an

  [$A9 my_val $2A $8D $CD $04]

Action! is very fast but sometimes
you can use that little extra burst
from machine language.

If you want your Action! code to 
compile to a specific memory location
you just insert this at the start of
your program.

  SET 14 = $4000
  SET $491 = $4000

Now you program will compile to 
location $4000.


If you create a bunch of global
routines that you in all your programs,
you don't have to type them in each
time.  They can be loaded in while
the program is compiling.  That way
they are compiled in with your code.
You can INCLUDE a file anywhere as
long as if is above whatever PROCs
are going to call them.  You usually 
INCLUDE files after the global
variable and before your first PROC.

I have included a Public Domain
runtime package.  If you INCLUDE
this when you compile your programs,
they will work without the Action!
cartridge.  It would look like:


  INT global1,


  PROC My_first_proc()


I mentioned that Action! predeclares
some variables for you.  There is an
important one that HAS to be mentioned.

Normally, if you Action! program
finds an error, it aborts.  Your
can trap errors by making PROCs that
will execute when an error occures.


  PROC my_error(BYTE errno)

    Print("I found an Error number ")

A BYTE parameter is always needed.
To make Action! go to this PROC 
instead of aborting, you just put in:

  Error = my_error

Lastly, Action! has the same graphics
command built in as BASIC.  I am
not going to go into great detail
on graphics.  There have been thousands
of magazine articles written on the
subject.  I will just list the BASIC
graphics command and their Action!

BASIC   |         Action!
GRAPHICS 0      | Graphics(0)
SETCOLOR 1,0,0  | SetColor(1,0,0)
COLOR 2         | color=2
POSITION 3,4    | Position(3,4)
PLOT 10,10      | Plot(10,10)
DRAWTO 20,20    | DrawTo(20,20)

I have not listed all the routines
in the Action! library.  There are 
sound and game controller routines as
well as a ton more.

Take a look at your Action! manual
again.  Hopefully you will understand
it better than the first time you
opened it and said "What does that

Well, I hope you all have enjoyed
this tutorial.  Action! is a great 
programming language and Action!
programmers are very friendly.

Stay tuned for Part 2, writing
programs for BBS Express! Pro.