─────────────────────────────────────────────────────────────────────────────
>> PCBoard Programming Tutorial, by Psychic Release (c) 1997
>> issue #2
─────────────────────────────────────────────────────────────────────────────
─-- i -──────────────────────────────────────────────────────────────────────
Contents
─────────────────────────────────────────────────────────────────────────────
# Title Written by
────────────────────────────────────────────
Introduction
i. "Contents" ...............
ii. "Contents of archive" ....
iii. "Welcome!" ............... PR Crew
iv. "Tips on how to learn.." . SleepWalker
The Tutorial
1. "Functions" .............. SleepWalker
2. "File I/O" ............... SleepWalker/Flux
"basic file i/o.." ..... SleepWalker
"of channels and.." .... SleepWalker
"binary files" ......... Flux
"dbase 3 files" ........ Flux
3. "The User" ............... SleepWalker
"the user's records" ... SleepWalker *
"dealing with nodes.." . SleepWalker *
4. "Using the DOS env." ..... SleepWalker
"different os'es" ...... SleepWalker
The End of the Whole Mess
v. "The End" ................ PR Crew
vi. "About Us" ...............
* = fully-working source example related to chapter in archive
─-- ii -─────────────────────────────────────────────────────────────────────
Contents of archive
─────────────────────────────────────────────────────────────────────────────
File Description
─────────────────────────────────────────────────────────────────
pplt2.doc the main ppl tutorial text-file (this one!)
pcbdat.doc explains the different lines of pcboard.dat
sw-ulist.pps a sample user lister source, see chapter 3
bc-who.pps a sample who-command source, see chapter 3
bc-who.cfg config-file used by bc-who.ppe
pr-info.zip archive containing info about pr - appgen, etc.
uforia.nfo information about the Uforia-webring
─────────────────────────────────────────────────────────────────────────────
─-- iii -────────────────────────────────────────────────────────────────────
Welcome!
─────────────────────────────────────────────────────────────────────────────
Right, this is the second issue of our ppl tutorial. We have learned from
our mistakes from the first issue (were there any?;), and we present you
with an issue with more advanced stuff than the first one. After reading
issue number one, you could perhaps code a simple ppe, or at least get
aquainted with the basic techniques of ppl programming. After reading this
issue you will hopefully be able to make rather complex ppes, just like
the pros. :)
You can easily see what are examples and what is regular text by looking
at the examples header, here is an example(sniff!) of an example:
────────-- example -────────────────────────────────────────────────────
1 Boolean MBBSisInstalled, PCBisInstalled
2 If (MBBSisInstalled and (!(PCBisInstalled))) Then
3 PrintLn "The men in white coats will be along shortly."
4 PrintLn "Press any key to continue, or any other key to quit."
5 Wait
6 EndIf
──────-- end example -──────────────────────────────────────────────────
It won't work or anything, but that's how an example looks anyway! :)
As you can see, the lines have been numbered, and we go through the code,
often line-by-line, after the example. The numbers make it easier for you
to keep track of what we are actually talking about, and where it is in
the source example...
This time we also go more in-depth with the features we discussed in #1,
such as file i/o, and we supply more examples and complete source-codes
for you this time, along with full syntax and other kinds of reference
for many of the features you will want to use.
This time we've tried to improve the layout/design a bit too, to make it
easier to read and understand.
I (SleepWalker) and Flux have different programming-styles, but I hope
you'll be able to understand our code. We have tried to make the code
as tidy and nice as possible, usually they are a mess... :)
─────────────────────────────────────────────────────────────────────────────
─-- iv -─────────────────────────────────────────────────────────────────────
Tips on how to learn PPL SleepWalker
─────────────────────────────────────────────────────────────────────────────
Here are some suggestions for getting the most of the time you spend
learning PPL:
1 Learn PCBoard first
─────────────────────
The more you know about PCBoard, the better prepared you'll be to learn
PPL. Sometimes PPE's replace command in PCBoard, so it might be useful
to know how the original command works, and what it does. So read the
manuals for PCBoard and if you are serious about PPL, you might benefit
from reading the developer information too, which describes the structures
of some of the most frequently used files.
Also, when you know PCBoard, you have a better chance of knowing how is
the best way to approach a certain PPL-problem. If you know PCBoard
good enough, you might not need a huge amount of code to acheive some
result.
If you don't know anything about PCBoard before you learn PPL, you might
write pretty useless programs at first. Some people don't know there is
a command in PCBoard to send(broadcast) messages across nodes, so they
write big PPE's to do that. The command in PCBoard is BROADCAST <node> msg
by the way. :)
2 Structure the program first
─────────────────────────────
By structuring the program, I mean thinking about what it should do, and
how that should be done. I have always hated so-called flowchart-diagrams
and such, but it might be a good idea to think of what functions and
statements you may need, and how you want to build up the program.
By doing this you can avoid some nasty problems later on, you can get
'lost' in your own code, forgetting what you need to do next, and similar
stupid stuff.
As you learn more PPL, this phase will hopefully get shorter and shorter.
It may not be necessary at all if you are making a really simple PPE.
But what's the fun in that anyway? ;)
3 Experiment while learning
───────────────────────────
If you, while reading about different things in this tutorial, code
small PPE's yourself, testing the stuff described here, you might learn
faster. Although we have included examples to a certain extent here, it
would probably be smart to try and use what you have learned in your own
programs. We could have written something like "now, please make a PPE
that does this and that" at the end of each chapter, but we didn't bother.
So it's pretty much up to you to use what you have learned. Including
special 'tasks' at the end of the chapters reminded us too much of our
school-books, so we didn't include it. :)
4 Use help!
───────────
If you use PowerPPL(highly recommended), look at the help section alot.
You should learn a few things by checking the syntax and reading what
the different statements do. Some of the help-pages even have examples...
─────────────────────────────────────────────────────────────────────────────
─-- 1 -──────────────────────────────────────────────────────────────────────
Functions SleepWalker
─────────────────────────────────────────────────────────────────────────────
Let's start off showing you how to use functions. Functions were also
described briefly in the first tutorial, this is more detailed...
A function has basically the same syntax as the procedure:
function something()
;code
endfunc
You can use functions like that, just like a procedure. But, the function
has a major advantage over the procedure: it can return values. As you can
remember, there are several types of variables, like string and integer.
You can actually make a function return a value with these variable-types.
Let's look at an example - you want to see if a file exists, so you can
test if your graphics-file or cfg-file is there before you execute the
main ppe. To save typing you make a function that will test to see if
any file it is given, exists. Here is how...
────────-- example -────────────────────────────────────────────────────
1 declare function filexist(string name) boolean
2 if (filexist("CONFIG.CFG")) print "cfg-file found!"
3 function filexist(string name) boolean
4 if (!(exist(name))) then
5 let filexist = false
6 else
7 let filexist = true
8 endif
9 endfunc
──────-- end example -──────────────────────────────────────────────────
Right, this should be easy enough to understand I guess, but there is
a somewhat new syntax here: look at line 1, and notice the "boolean"
statement right after the declaration. This sets the output to be of
the datatype "boolean", which means either true or false, 1 or 0.
Inside the paranthesis, you can see "string name". This means that the
function expects a string there, so if you type filexist("hey!") the
string "hey!" is given to the function, and thrown into the varible
name, of the string-datatype. Later, inside the main function you can then
use this variable name for something useful, as I have here, I would say. :)
When you type string varname or integer varname, or whatever the datatype,
inside a procedure or function when declaring and stating (line 1 and 3 in
this case) you don't need to have previously declared that variable. You
don't need to type string name before declaring. Another thing: these
variables can't be used outside the function/procedure. It's therefore
useful that you can set a function to output a value, so you don't
have to declare everything in the main program.
Let's skip line 2 for a while, and jump down to line 4. These are the
basic statements used to check if a file exists. If you put a ! in front
of the exist statement, it means "not exist". So, line 3 means: if the
string written inside the function's paranthesis - name, does not exist,
then...
Now let's take a look at line 5. This line makes the actual output, it
sets the function's returnvalue (remember it's supposed to return a value
of either true or false - boolean) to false. And the rest of the function
should be easy to understand.
Now, let's look at line 2. Before you would have needed something like
if (!(exist("CONFIG.CFG"))) then ....
But with the function listed above you only need something like line 2.
Remember you wrote the (!(exist(... bit *inside* the function, so that's
nolonger required outside. As you remember, the function returned either
true or false, depending on the result of the search for the file.
Therefore you only need to write if (filexist("CONFIG.CFG")), and not
if (filexist("CONFIG.CFG") = true). This is essentially the same thing,
but there is no point in writing it, because if you just use the function-
call, the ppe will automatically test and return the value. So, if our
fileexist function can't find the given file, it'll just go on, but now
with a false value. You can then extend the loop a bit, you could make
default cfg-file automatically if none can be found, etc.
Functions are also handy in searching, you can write a function that
searches a file for something, then return true or false, depending on
whether it found it or not. For example. :)
Another kind of usage for functions is math. If you want to calculate
something, and type the result/answer to the screen, a function is ideal.
Something like this VAT-calculator perhaps:
────────-- example -────────────────────────────────────────────────────
1 declare function vat(integer price) integer
2 println "50 NKR is ", vat(50), " NKR including VAT." : wait
3 function vat(integer price) integer
4 integer result
5 let result = price * 1.23
6 let vat = result
7 endfunc
──────-- end example -──────────────────────────────────────────────────
This function makes a calculation and sends the result of the calculation
as output. Line 2 calls the function with 50 as the parameter, and line 5
makes the actual calculation. The result is then thrown into the integer-
variable "result", which is an in-function variable, meaning it can't be
used outside the function. After the result has been assigned to "result",
the "result"-variable is made the return-value in line 6.
To say to the function that "this is what you'll return", you just use
the function-name as a regular variable, assigning the data you want to
be return to it. E.g. - "vat" is the name of the function, and as you can
see from line 6, the variable "result" is made it's return-value by assigning
it to "vat".
If you look at the source, you'll see that the extra variable, "result"
isn't really necressary. You could just use "let price = price * 1.23", but
it might be a little easier for beginners to have a seperate variable for
the result.
Functions are very convenient for math-operation usage like this one. Since
math is usually about taking some numbers, doing something to them, and then
returning a value, a function is perfect, since is allows you to call it using
the input-numbers as parameters, and then returning the result from the
function.
NB! Functions are only supported by PPLC 3.10 or above - from PCB 15.21
─────────────────────────────────────────────────────────────────────────────
─-- 2 -──────────────────────────────────────────────────────────────────────
File I/O SleepWalker and Flux
─────────────────────────────────────────────────────────────────────────────
We already looked at some basic filehandling in the first pplt, when
we talked about configuration files and that sort of stuff. Now, I'll
try to mention most of the variants of file I/O (input/output) I know of,
or the ones you will probably find most useful.
Basic file i/o programming
─────────────────────────────────────────────────────────────────────────────
To refresh your memory, here's how to read a line from a file as easy as
possible:
let myvar = readline("myfile.txt", 5)
This will read line 5 from myfile.txt, and put whatever is found into a
variable called myvar. If you had a cfgfile with 20 lines, it would be
rather boring to write 20 such lines, only changing the variable-name and
the line-number. That's why we have loops! Remember the for..next loop?
That's the best one to use in these situations, here is an attept at a
shorter, more automatic way:
────────-- example -────────────────────────────────────────────────────
1 integer line
2 string var(20)
3 for line = 1 to 20
4 let var(line) = readline("myfile.txt", line)
5 inc line
6 next
──────-- end example -──────────────────────────────────────────────────
Line 2 is an array. This is a record of variables with the same name,
but with different places in the record. So in this case, you declare
20 variables in line 2 - var(1) .. var(20). They are all different, but
you save some time by using such a short declaration, rather than
typing string var1,var2... and so on!
Lines 3-6 are the for-loop. First we tell the loop how many times you
want whatever inside the loop repeated. Here we start at record 1 and
stop at record 20.
In line 4 we read the lines. We have said that the string-array var should
have 20 records, and we have said that the integer line should also end
at 20, from 1. So, what we need to do is throw line 1 into var(1), line 2
into var(2) and so on. That is accomplished by replacing a number with
line.
Line 5 increases the integer line, so you always read the next line, until
the max value (here; 20) is reached.
Line 6 simply tells the loop to go on until the max value of the integer
line (still 20) is reached. When line equals 20, the loop is terminated,
and the ppe continues.
As mentioned in the first tutorial, we also have other ways to read from a
file. We have the "f-statements". There are quite a few of these
statements, here is a quick walkthrough of them, listed alphabetically:
Creating, closing and deleting
──────────────────────────────
fappend - adds some data to the end of a file, not erasing anyting.
fclose - closes the channel the datatransfer is on
fcloseall - same as above, but closes all channels
fcreate - create a file
delete - delete a file
Channel definitions
───────────────────
fdefin - specify a default input channel, speeds up reading
fdefout - same as above, but the output channel is specified
fdget - the same as fget, but the channel is set by fdefin
fdput - same as fput, only it uses the default channel set by
fdefout
fdputln - same as fdput, except with carriage return after each line
fdputpad - same as fputpad except with default channel
fdread - see fread, still using default channels
fdwrite - see fwrite, using default channel
Regular text-files
───────────────────
ferr - returns true if a file-access error has occured
fflush - flush a channels changes to disk
fget - read a line from a file, assigning it to a variable
fnext - returns the next available channel
fopen - open a file for processing, according to the accessmodes
fput - write a string to a file
fputln - fput with a carriage return
fputpad - write a string to a file, padding and truncating it
Binary files
────────────
fread - read binary data from a file
frewind - rewind a channel after flushing the buffer
fseek - position to any random location within a file
fwrite - write binary data to a file
Searching
─────────
findfirst() - find the first occurance of somehing in a directory
findnext() - find the next occurance in the same directory
DOS-like statements
───────────────────
delete - deletes a file
mkdir - creates a directory
rmdir - removes a directory
rename - renames a file
General
───────
cwd() - returns the current working directory
fileinf() - returns specified information about a file:
1 = Return "true" if file exists
2 = Return file date stamp
3 = Return file time stamp
4 = Return file size
5 = Return file attributes
01h = Read Only
02h = Hidden
04h = System
20h = Archive
6 = Return file drive
7 = Return file path
8 = Return base name
9 = Return file extension
I'll only use the most basic statements here, look up the syntax and usage
of the others somewhere else. :)
Of channels and different modes...
─────────────────────────────────────────────────────────────────────────────
In order to open and read from/to files you need to determine what channel
you want to use. You can assign different files to work in different
channels. This whole channel thing is invented so you can have several
files opened at the same time. You cannot open several different files
within the same channel, they have to be different. Could be useful if you
want to speed the ppe up a bit, by opening all at once, and in different
channels, rather than opening and closing all the files in turn on the same
channel.
Note that channel 0 is reserved for script questionnaires.
The access modes you set when you open a file, are these:
o_rd - open file in read only mode
o_rw - open file in read and write mode
o_wr - open file in write only mode
You have to decide on which one you want to use, according to what you
want to use the file for. For instance, it wouldn't be very smart to
open a cfgfile you intend to read from, with the o_wr mode...
The share modes are as follows:
s_db - deny read/write access from other processes
s_dn - allow both read and write access from other processes
s_dr - deny read access from other processes
s_dw - deny write access from other processes
These determine how the file will act towards other files that are opened
and other things on the board that try to access the file as well.
The access and share modes are be used in fcreate, fopen and fappend
statement.
Ok, here is a small example that will read a line from a file.
────────-- example -────────────────────────────────────────────────────
1 string mystring,another,andanother
2 fopen 1, "cfgfile.cfg", o_rd, s_dn
3 fget 1, mystring
4 fget 1, another
5 fget 1, andanother
6 fclose 1
──────-- end example -──────────────────────────────────────────────────
Line two opens the file "cfgfile.cfg", lines 3-5 assigns the string mystring
to the first line in that file. Line six closes the whole channel for
input. The file is opened on channel 1, allowing both reading and writing
of the file, and denying access from other processes.
And here is an example using channel definitions:
────────-- example -────────────────────────────────────────────────────
1 string mystring,another,andanother
2 fopen 1, "cfgfile.cfg", o_rd, s_dn
3 fdefin 1
4 fdget mystring
5 fdget another
6 fdget andanother
7 fclose 1
──────-- end example -──────────────────────────────────────────────────
This code is in reality exactly the same as above, except the channel
definition stuff in line 3, and the lines 4-6 changing from fget to the
faster fdget, with the channel already predefined.
As mentioned - the predefined approach is faster since you don't have to
access the channel that often, you just open a channel, and get all the
data you want, and that's it. With the first example, you ask the channel
all the time to bring the data to you, all the time posting requests to
the channel.
In reality you won't notice such a tremendous improvement in speed, during
"normal" usage, but you might if you are dealing with large amounts of data.
Binary files
─────────────────────────────────────────────────────────────────────────────
Many times when you want to make ie. an directory selector, you may find out
that the directory list file is CRYPTED, more correctly; its a binary file.
You try to read it with fget/fdget or readline, but it wont work at all! Of
course it would return a bunch of weird strings here and there. The solution
of this problem is what I am going to teach you now...
First, you can not use fget/fdget, nor readline, because this command
reads LINE BY LINE. But binary files is not constructed LINE BY LINE, in
this case:
offset type length description
─────────────────────────────────────────────────
000 string 30 directory textfile
031 string 30 directory path
061 string 35 directory name
096 byte 01 sort type (see pcbsetup)
explanation;
offset - position in line(/file), type - what variable type you should use,
length - how long the information is (in chars)
Second, you use FREAD/FDREAD (depends on use of fdefin) instead of fget/fdget.
This command is used to read x number of chars from a file. This is how I
would have done this job:
────────-- example -────────────────────────────────────────────────────
1 String textfile,path,name
2 Byte type,j
3 Begin
; before this, you should have found the dirlist path&name
4 FOpen 1, "dir.lst", O_Rd, S_Dn
5 FDefIn 1
6 For j = 1 to (FileInf("dir.lst",4) / 97)
7 FDRead textfile, 30
8 FDRead path, 30
9 FDRead name, 35
10 FDRead type, 1
11 PrintLn textfile
12 PrintLn path
13 PrintLn name
14 PrintLn type
15 Next
16 FClose 1
17 End
──────-- end example -──────────────────────────────────────────────────
If you look at the line "For j = 0 to (FileInf("dir.lst",4) / 97)" you can
see that I used "FileInf", which is a command that returns information about
any file. In this case, I read the filesize of "dir.lst" and divided it with
97 - the total number of chars in/size of each directory record.
Now, this was pretty simple. But when you want to find out this information
(what is what in what position etc.), you should load the binary file in any
text-editor that does not WRAP the "lines". Then you see just how it is const-
ructed, or/and you should try ALOT of things in your PPE.
Now, I'll describe the construction of the FLAG LIST, and how you read it.
offset type length description
────────────────────────────────────────────
000 string 66 filename w/path
067 string 12 filename
080 ?? ?? dunno what this is
..
────────-- example -────────────────────────────────────────────────────
1 String filename,fnpath,flist
2 Byte j
3 Begin
4 Let flist = GetEnv("PCBDIR") + "\FLIST"
5 If (PcbNode() <> 0) Let flist = flist + "." + String(PcbNode())
6 If (!Exist(flist)) Stop
7 FOpen 1, flist, O_Rd, S_Dn
8 FDefIn 1
9 For j = 1 to (FileInf(flist,4) / 130)
10 FDRead fnpath, 66
11 FDRead filename, 12
12 FSeek 1, 51, Seek_Cur
13 PrintLn fnpath
14 PrintLn filename
15 Next
16 FClose 1
17 End
──────-- end example -──────────────────────────────────────────────────
Here I used "FSeek", just to skip the information that wasn't necessary.
dBASE 3 files
─────────────────────────────────────────────────────────────────────────────
When storing alot of information in some textfiles, its always slow in ppe.
Something all PPE-coders know as well as I do. So, there's another solution,
dBASE 3. A format that stores all the information you want in a very fast way.
It is also a standard format, so that others can read your files (that may
be the disadvantages, but sometimes not). The use of this feature is easy to
learn, it's as easy as the ordinary fcreate/fopen/fappend commands.
Now, what I'll write about in this small tutorial, is how you can use it, and
I'll try to convince you to use this feature. For example, this may be very
useful when making a oneliner,lamerlister or bbs-lister and much,much more.
first, I'll introduce you to the commands that are mainly used..
dcreate,dopen,dappend,dclose means exactly the same as fcreate,fopen,
fappend,dclose but does not create an
ordinary textfile
dnew,dadd,dput used when adding a new record to the dbase file
dgo,dbottom,dskip used to move between records
dget used to read from a record
There's also abilities to seek for a certain string/number in all the records,
but I won't tell about that yet. I think its enough to learn this for now.
Take a good look at those examples, and remember them.
────────-- example -────────────────────────────────────────────────────
1 string fields(2)
2 begin
3 let fields(0) = "FIRST,C,25,0" ; these are required to make the fields
4 let fields(1) = "LAST,C,30,0" ; that you're going to use afterwards
5 let fields(2) = "HANDLE,C,25,0"
6 dcreate 1,ppepath()+"names",false,fields
7 dnew 1
8 dput 1,"FIRST","hans"
9 dput 1,"LAST","hansen"
10 dput 1,"HANDLE","ola nordmann"
11 dadd 1
12 dclose 1
13 end
──────-- end example -──────────────────────────────────────────────────
─────────────────────────────────────────────────────────────────────────────
─-- 3 -──────────────────────────────────────────────────────────────────────
The User <sw-ulist.pps> <bc-who.pps> SleepWalker
─────────────────────────────────────────────────────────────────────────────
A thing you'll probably do very often, is dealing with the user. You change
his password, print his stats, check how much he's downloaded or uploaded,
and so on. There are many functions that returns data about the user, it's
just a question of finding the right ones, and using these correctly.
The user's records
─────────────────────────────────────────────────────────────────────────────
Most of these functions start with "u_". The function returning the user's
password, for instance, is u_pwd().
One very important thing you need to remeber, is the getuser and putuser
statements. These retrieve the functions I mentioned above, and saves the
changes you have made to the user's record. If you use PowerPPL, the
functions that need the getuser statement, are coloured bright cyan(if you
haven't changed that colour, of course).
Functions like u_name() doesn't need getuser.
Now, what if you want to change the records of any user, not just the
user who's currently online? In that case, you have to use the
getaltuser statement. Getaltuser is made to take the record number as
an argument, and find the data on that specific record. So, a getaltuser 23
would make ppl find data from record number 23, sort of like a getuser,
only you decide what record number to scan.
If you are making a user lister/editor, you would use this statement inside
a loop, to print u_name() from a record number that's increased inside the
loop, and is between 1 and the highest record number. We'll take a look
at a fully functional user lister at the end of the chapter, check
pr-ulist.pps, which is included in the archive.
Another function you might find useful when dealing with user records,
is the u_recnum(), which returns the recordnumber a certain user has.
────────-- example -────────────────────────────────────────────────────
1 getaltuser u_recnum("ARNOLD SCHWARZENEGGER")
2 if (u_recnum(name) == -1) print "Name does not exist!"
3 let u_sec = 20
4 putaltuser
──────-- end example -──────────────────────────────────────────────────
We use the u_recnum() to return a record number for the name we have entered,
which is then passed on to getaltuser as an argument.
If the recordnumber is -1, the name exist, so we'll give an error message
if that's the case. Then we change the security level, and save the
changes with putaltuser.
One of the source codes included in this archive, sw-ulist.pps, shows you
these functions in use - in a simple user lister, written by SleepWalker...
Dealing with nodes and users
─────────────────────────────────────────────────────────────────────────────
To see the users online, you need to send a "ping" across the nodes, asking
for information about the current state of your "network". We do this by
using a statement called "rdunet". This statement takes one argument, the
number of nodes your board has. You can compare it to getuser I suppose.
In order to print the info we need on the person online, we use functions
that are essentially the same as the ones in the previous chapter, except
they are named un_?() instead of u_?(). So un_name() will return the name
of the current user online on the chosen node...
But, what if nobody is on the node? We need to check the state of the node,
and return a message according to the info we retrieve. We do this with
the un_stat() function, which returns the current state of the node.
You could use it like this:
if (un_stat() <> "A") status = "Unavailable for a chat"
if (un_stat() = "") status = "Node is offline or down"
if (un_stat() = " ") status = "Waiting for a caller"
As you can probably understand, PCB returns an "A" if the user is available
for chat, nothing(!) if the node is down or doesn't exist, and a space if
the node is up and running, but no user is online(I experience this too
often...).
In order to make a who-program work, you would need a loop, printing
some information on each node in each turn.
You might want to add the ability to send messages to people(or ppe-coders)
on other nodes, most good who-ppes have this feature. The statement
for messages is "broadcast", which you use like this:
broadcast "Get the hell out you damn lamer!", 1, 10
This will send the first argument(the message) to nodes 1 to 10.
In case you want to send it to node 2 only, both argument 2 and 3 should
be set to 2: 'broadcast "Get the hell out you damn lamer!", 2, 2'.
Another thing you may remember from pplt #1 is that you can send a message
to the net saying what you are doing. So you can change the "unavailable"
text to something more useful when people are running your ppe. In case
you have forgot about the whole thing, looky here.
rdunet pcbnode()
wrunet pcbnode(), "Q", un_name(), "Running a PPE", "Using Arnolds ppe.", " "
log "Arnolds cool ppe started. Woo-hoooo!", false
The log statement has nothing to do with the nodes or anything, it copies
the text between the quotation marks to the node log. You can access the
the logs if you have sysop security by typing "13 <nodenr>" in case you
didn't know. The false thing just means that it's not left justified.
Anyway, the wrunet writes the data to the nodestatus. The "Q" means the
new nodestatus, un_name() is your name, "Running a ppe" replaces the city.
The next argument is the new operation text, and the last, empty argument
is the text to be broadcasted. Here is the wrunet syntax and description:
" WRUNET node:integer, nodestat:string, nodeusername:string,
newnodecity:string, newoptext:string, broadcasttext:string
Write information to USERNET.XXX for node "node", where "nodestat" is
the new node status, "nodeusername" is the new node username, "newnodecity"
is the new node user city, "newoptext" is the new node operation text, and
"broadcasttext" is broadcast text. "
Take a look at bc-who.pps, a simple who-command replacement, coded by
Black Candel. The source isn't commented, but if you have read this
chapter, you should have a pretty good idea on what's going on. :)
So take a look at the source and experiment. There are many crappy who-
ppes out there, it's time somebody made one worth using.
─────────────────────────────────────────────────────────────────────────────
─-- 4 -──────────────────────────────────────────────────────────────────────
Using the DOS-environment SleepWalker
─────────────────────────────────────────────────────────────────────────────
If you use PCBoard with MS-DOS you have the oppertunity to use ppl to
retrieve information from the os itself. But, be careful when you use these,
remember the user might be running OS/2 or something. This is of course
a consideration you only need to think of if you plan to distribute the
ppes you code. In this chapter, we'll try to look at a few ways you can
use your os to make your ppes cooler, and differences between OS/2 and DOS.
One main function you use to retrieve data from the os, is GetEnv(). GetEnv()
gets the so-called 'environment variables' in the os. You declare these
variables in config.sys or autoexec.bat usually, by typing SET WHATEVER=?.
Then you use GetEnv() to get this value: GetEnv("WHATEVER"). Rembember not
to include the =. This statement will give you the current value of
WHATEVER. You can assign this to a variable like this:
Let EnvVar = GetEnv("WHATEVER"). EnvVar should then be a string.
Most people have their temporary storage-directory declared like this:
SET TEMP=C:\DOS\TEMP in config.sys. You can take advantage of this, if you
want to save some file in a temp-dir. But, there is of course the chance
that this variable hasn't been declared, and what the heck do you do then?
Well, the obvious solution would be not to use the GetEnv() function at
all. You can just create a directory above the dir you are running the
ppe from, ppepath(), and put the files in there. This is the easiest method,
and the most used one.
But, you can set the variable if it hasn't been set; for instance like this:
────────-- example -────────────────────────────────────────────────────
1 string tmp, newdir
2 integer dummy
3 if getenv("TEMP") == "" then
4 if (!(exist("c:\dos\temp"))) then
5 shell true, dummy, "SET TEMP=C:\DOS\TEMP\", true
6 else
7 mkdir ppepath() + "\temp"
7 let newdir = ppepath() + "\temp"
9 shell true, dummy, "SET TEMP=" + newdir, true
10 endif
11 endif
12 let tmp = getenv("TEMP")
──────-- end example -──────────────────────────────────────────────────
I suppose this should be easy enough to understand... if the TEMP-
variable is empty, we check to see if the path c:\dos\temp exists, if it
does the TEMP variable is set to this directory. Otherwise, a directory
above the ppe-directory is created, called "temp". Then this directory is
set to be the TEMP-directory. To accomplish this, we need to shell to dos
once and awhile. The syntax is pretty straight-forward, as you can see...
The shell-statement also lets you do things with your os. You can use
a shell in your ppe for many things - you can make an exe handle something
the ppe can't, and then shell to dos and execute this exe when needed. Or
you can, as we did here, shell to do regular things like making directories,
setting environment-variables, and so on.
Speaking of directories, here are some statements you can use to control
them: MkDir, RmDir, CWD. Most of you know that MkDir and RmDir create and
remove directories, respectively. CWD however, is used to return the
current working directory. You use it like this:
println "Current working directory: ", CWD()
You might benefit from using CWD() when testing and debugging your ppe,
especially if your program deals alot with files. It might be practical
to know where they all go when they are saved.
The file system is a very important part of any os, and the chapter entitled
"Binary files" above, describes to a certain extent how to use this system
in your ppes.
Different Operating Systems
─────────────────────────────────────────────────────────────────────────────
PCBoard is available for both DOS and OS/2, but are there any differences
you as a ppl programmer have to consider? What are your limitations, and
should you care at all?
If you for some weird reason don't know what os you are running, try this:
────────-- example -────────────────────────────────────────────────────
1 Select Case (Os())
2 Case 1: PrintLn "Running DOS version of PCBoard"
3 Case 2: PrintLn "Running OS/2 version of PCBoard"
4 Case 0: PrintLn "Running some weird OS I've never heard of"
5 End Select
──────-- end example -──────────────────────────────────────────────────
Os() returns 1 if PCBoard is running in DOS, 2 if your os is OS/2, and 0 if
the os is unknown. But most people do know what os they run, I should think.
These commands are OS-specific:
Command Specific to
───────────────────────────────────────────────────────
DoIntr DOS
MkAddr DOS
PeekW DOS
PeekDw DOS
PeekB DOS
PokeW DOS
PokeDW DOS
PokeB DOS
Reg... DOS
Sound Emits a 1/4 second tone under OS/2.
VarOff DOS
VarAddr DOS
I suppose you can see why these statements are os-specific. Most of them
deal with the core of the os, by using the memory and other things
actively. Using DoIntr and some Reg.. statements, you can actually include
some "inline assembly" language, since these commands deal with the
computer/os at basically the same level the Assembly language does.
A small note on the Sound statement, which doesn't work the same way in
DOS and OS/2: From version 3.20 of PPLC, a new statement has been added
to overcome this rather annoying limitation - SoundDelay. The syntax of
this statement goes like this:
SoundDelay INTEGER frequency, INTEGER duration
Parameters: frequency = frequency at which to sound the PC speaker
duration = length, in clock ticks (18 = 1 second), to leave
the speaker on
SoundDelay was added to replace the Sound statement, so these lines:
Sound 300
Delay 10
Sound 0
...should be replaced by:
SoundDelay 300, 10
...which will work in both DOS and OS/2.
If you are going to distribute your ppes, you should at least warn people
that you are using commands and functions that do not work in OS/2. You
might use the example above to determine the os that's running, and adjust
your ppe after the result. If you need to use DoIntr and Reg..., it's
impossible for OS/2 users to get the most of your program, and they should
be warned... Be aware that the ppe will skip the parts it won't understand,
it won't crash. At least the PCB manual says it won't. :)
─────────────────────────────────────────────────────────────────────────────
─-- v -──────────────────────────────────────────────────────────────────────
The End
─────────────────────────────────────────────────────────────────────────────
That concludes the second installment of the PPL tutorial series from
Psychic Release. I must say the 2nd one is much more structured and correct
than the first one, which was really just some of my own and flux' writings
mashed together, without much thought about coordination or "testing" of the
things written there.
This time I (SleepWalker) have written most of the stuff myself, but I'd
like a few other coders to write too, but I guess it's difficult to write
something like this when you are too busy coding. ;)
A big thanks to Black Candel, for bringing us example sources to use in the
tutorial. Thanks to Virago for doing some graphics work on the pplt website,
and to all readers who have written email to us, telling us how much they
appreciated the first issue.
Speaking of issues, I don't know if there'll be a third, but in case it will,
we need more people to write! It's quite alot of work to write this much,
and come up with good examples as well, so I hope people will contact us
about it, and hopefully write about something they know alot about, or
telling us about things they'd like to read about.
Anyway, I hope you enjoyed this second issue of PPLT!
─────────────────────────────────────────────────────────────────────────────
─-- vi -─────────────────────────────────────────────────────────────────────
About Us
─────────────────────────────────────────────────────────────────────────────
PSYCHIC RELEASE was formed in 1995 sometime by Flux and Sect.
They made several PCBoard PPEs and some Pascal programs too. After about
a year with 2-3 members, SleepWalker was offered a place among these
talented modders. He was also a PCB sysop, specializing in PPEs. After
awhile SleepWalker got an Internet connection, and made pr's website. This
caused the number of members to increase; the number of HQs and distros
around the globe also expanded rapidly.
PR is concentrating on PCB now too, but we'd like modders from as many plat-
forms as possible to apply. We want to expand, and we NEED modders, artists
and coders of most kinds.
Now a few words about the authors of PPLT #2:
FLUX is the dictator/leader of Psychic Release. He is 17 years old, he codes
in PPL and Pascal, and he isn't completely talentless drawing ansi either.
He used to be a sysop, until some strange phone-bill incident caused him
to shut it down for awhile. It's now open again, but on a friend's computer.
SLEEPWALKER is a senior member of Psychic Release. He is 18 years old, he
likes reading horror-novels, listening to Pink Floyd; and he occasionally
codes the odd ppe too. He is also PR's internet dude, in charge of the
website among other things. Oh, and he's a sysop too, at Minas Tirith...
BLACK CANDEL is a ppe coder with PR, who has contributed some nice example
sources for this issue. The bloke comes from Brazil
FLUX
leader - ppe, pas, art(?)
age: 17
email: flux@countzero.bbs.no
bbs: Oobe - down at the moment...
location: Norway
SLEEPWALKER
senior member - ppe, internet
age: 18
email: slwalker@applausenett.no
bbs: Minas Tirith +47 61190590 (Psychic Release world headquarter)
icq #: 2301492
location: Norway
BLACK CANDEL
coder - ppe
age:
email: bcandel@hotmail.com
location: Brazil
PSYCHIC RELEASE - modding and art
World Headquarter: Minas Tirith +47 61190590
Web: pr.home.ml.org
E-mail: pr.countzero.bbs.no, or Flux' address above
TO APPLY
Apply generator can be found in each infopack (released monthly), it's also
included in this pplt-package. You can also apply using the apply-form on
the website. Another way to apply is to email PR's or Flux, including some
information about yourself.
─────────────────────────────────────────────────────────────────────────────