ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ >> 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 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 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 " 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. ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ