3.2 Overview: MUF (cont'd) Refining the Program (Last Page!): There are some weak points to this version of tinker.muf. If it is anM1 program, and someone tried to use it in a room she
doesn't own, the program would crash with a Permission
denied error. Also, there are no comments. And, it doesn't
include online documentation such as a #help function.
We'll rectify these weaknesses and add some new capabilities in our
final version, and in the process cover new aspects of MUF
programming.
Tinker.muf, final product... ==================================== ( tinker.muf, v1.0, by jessy @ genericmuck 5/96 A builder's utility program that checks the exits in a room to make sure they have @succ, @osucc, @odrop and @desc messages, and that none are unlinked. This program was written to accompany the MUF tutorial in the muck manual. While it is slightly more convenient than examining all the exits -- and possibly overlooking some -- its primary purpose is to introduce different aspects of MUF. Besides, your MUCK probably already has a @check command which does the same thing better. INSTALLATION: Port the program and set it Link_ok if it is to be public. Create an action and link it to the program. If the program is set M2 or higher, edit the first line of code following the header comment to read `$def runningM2'. The program requires the standard libarary lib-match, which should be available on any established muck. USE: Typing the action name checks all exits in the room. Typing the action name followed by an exit name checks just that exit. Clean up: Your MUCK really doesn't need multiple copies of tinker.muf lying around. If you are using this program while learning muf with the muck manual, it would be best to recycle it and the action when finished or -- better yet -- put your own new program in it. Tinker.muf may be freely ported. Please comment any changes. ) ( define owner's mucker level. if not m1, replace `runningM1' with `runningM2' ) $def runningM1 $define tell me @ swap notify $enddef ( include lib-match for finding exits by name ) $include $lib/match lvar ourExit ( stores exit dbref ) lvar counter ( store count of exits checked as an integer ) : help ( -- ) ( show help screen ) "tinker.muf v1.0" tell " " tell "a builder's utility that checks to make sure all exits " "in a room have a @succ, @osucc, @odrop and @desc set." strcat tell " " tell "to use, simply type \"" command @ strcat "\"." strcat tell " " tell "to check a specific exit, type \"" command @ strcat " <exit name>\"." strcat tell ; : plural? ( s -- s i ) ( return true if s is not "1" ) ( leave test string on stack ) dup "1" smatch if 0 else 1 then ; : checkexits ( -- ) ( report on exits ) 0 counter ! ( init counter ) dup if ( match specified exit name ) .noisy_match not if ( couldn't find it; ) exit ( lib-match will notify; exit ) else ourExit ! ( found it; store dbref; store 9999 in ) 9999 counter ! ( counter; will use for loop-exit test ) then else pop ( not doing a specific match; ) loc @ exits ourExit ! ( init ourExit to first one ) then begin ( BEGIN EXIT-CHECKING LOOP ) ourExit @ while ( break if no more exits ) ourExit @ ( put current exit on stack ) dup getlink not if ( exit linked? ) dup unparseobj " is unlinked (not secure)." strcat me @ swap notify then $ifdef runningM2 ( only check room-to-room exits; M2+ only ) dup getlink room? not if pop ourExit @ next ourExit ! continue then $endif dup "_/sc" getpropstr not if ( has a @succ? ) dup unparseobj " needs a success message." strcat me @ swap notify then dup "_/osc" getpropstr not if ( ... @osucc? ) dup unparseobj " needs an osuccess message." strcat me @ swap notify then dup "_/odr" getpropstr not if ( ... @odrop? ) dup unparseobj " needs an odrop message." strcat me @ swap notify then dup "_/de" getpropstr not if ( ... @desc? ) dup unparseobj " needs a description." strcat me @ swap notify then pop counter @ 9999 = if ( break if we're only checking one ) break then counter @ 1 + counter ! ( increment counter ) ourExit @ next ourExit ! ( increment ourExit ) repeat ( END EXIT-CHECKING LOOP ) counter @ intostr ( report how many exits checked. ) Plural? if " exits" else " exit" then " checked." strcat strcat Tell ( all done! ) ; : main strip "me" match me ! dup if ( check: wants help? ) "#help" smatch if Help exit then then $ifdef runningM1 ( bail out if someone else's room; M1 only ) loc @ owner me @ dbcmp not if "Sorry, this program is only for rooms that you own." me @ swap notify exit then $endif CheckExits ( go check those exits ) ; ==================================== Comments: This version is properly commented. A header comment provides the
author of the program, its purpose, discusion of how to install and use
it, and a statement of conditions for porting the program to other
Compiler Directives: This version makes use of several `compiler directives' (also called `preprocessor directives')... additional steps the compiler performs before compiling the program. (Compiler directives are discussed more fully in Section 3.2.4)
The first of these directives, Because the term
begin depth while pop repeat Rather than include all this every time we want to clear the stack, and rather than putting in a separate function that the program would have to jump to each time we use it (a slight additional overhead), we could put... $define NukeStack begin depth while pop repeat $enddef...somewhere near the top of the program, and use NukeStack
whenever we want to clear the stack. (This technique is known as
`in-lining' or creating an `in-line' function. The effeciency gained by
not having to jump to a separate function can be significant in a tight
loop that can run many times.)
Libraries: This version makes uses of a $include $lib/match (The server is able to find the right library because we're specifying
it by its registered name of
Error Checking: In this instance, we're doing a little bit of error checking: If the program is running at Mucker Level 1, it checks toward the end of main if the user owns the room. If not, it gracefully bails out, instead of crashing.==================================== $ifdef runningM1 ( bail out if someone else's room; M1 only ) loc @ owner me @ dbcmp not if "Sorry, this program is only for rooms that you own." me @ swap notify exit then $endif ==================================== Good error checking is one of the most difficult aspects of writing solid programs: As you're writing the code, you know how the program is supposed to be used, and may tend to make unconscious assumptions based on that. At such-and-such point in the program, it asks the user for say the amount of pennies to charge. Of course this should be a number... but rest assured, someday someone will enter not "100" or the like, but "All of them" or "I don't know". So, get in the habit of thinking `What could go wrong? What assumptions am I making that might not always be true?', and include code to check for and manuever around these conditions. Cultivate friends with wacko sick minds that never do what they're supposed to, and ask them to test your programs. Different programs will have different error-checking needs, but here are some common error conditions:
Of course, these aren't the only possible error conditions... but
these do tend to pop up repeatedly in Formatting: This version reports how many exits were checked. Before doing so, it
looks at the number with the |