PAD, a text adventure language written in D

downs default_357-line at yahoo.de
Sat Nov 7 01:42:47 PST 2009


Robert Clipsham wrote:
> downs wrote:
>> This was originally created for my IRC bot, but I added a CGI
>> interface a few days ago.
>>
>> Here's a quick demo:
>> http://demented.no-ip.org/~feep/pad_test.cgi?prev=eJzLL0jNU8goKSmw0tcvSCwuSU3KzNNLzs%2FVzzVKtky0MDFJBQDYDgu%2B
>>
>>
>> It takes pastebin.com/ca/paste.dprogramming.com pages, Dokuwiki pages
>> and raw text. For other sites, you have to wrap the code between %PAD
>> START% and %PAD END%.
>>
>> Source is on http://dsource.org/projects/scrapple/browser/trunk/idc/pad
>>
>> The source is a simplicist imperative language based around the
>> concept of scopes.
>>
>> A scope is a named entity that contains other scopes or commands in a
>> defined order. It can also have a value. Scopes unify functions and
>> variables in a single concept.
>>
>> Evaluating a scope evaluates all contained commands in order, unless
>> it has a "default" scope, in which case that is evaluated instead.
>>
>> "area scopes", scopes prepended with "area", form the rooms of the
>> game. The player is always in a "current room", accessible via the
>> keyword 'location'.
>>
>> "hidden scopes", prepended with "#", cannot be accessed by the user
>> directly.
>>
>> "global scopes", prepended with global, when included below the root
>> scope are always accessible by the player regardless of other lookup
>> rules. "inventory" is a typical global scope.
>>
>> Most of the commands _should_ (well, might) be self-explanatory. Have
>> fun, and feel free to ask back.
> 
> This looks pretty cool. The language seems pretty self explanatory, do
> you have a small tutorial I could read? (or alternatively a version of
> what you pasted above with comments) Saves me figuring it out myself,
> feeling rather lazy right now ;)

I'll try to elaborate further.

The idea of scopes is that they're the basic unit of nesting, that occurs both in function stacks and type definitions. But for the analogy to work, another feature is needed - function "calls". My equivalent to that is the merge. An example!

once { #alreadyDone; if !alreadyDone { alreadyDone = true; param; } }

[later on]

once:{ "This is only printed once. "; }

When using the "scope:[statement]" syntax, scope is duplicated and inserted *flatly* into the surrounding scope. The statement is renamed "param". (Evaluating a string prints it)

Now it would seem that this would lead to the #alreadyDone scope being inserted multiple times.

This is correct. PAD has no problem with a variable existing multiple times. Every variable lookup in once will only see its local copy. Deleting alreadyDone from the surrounding scope will take multiple attempts, however. :)

An example! (from http://pastebin.com/m967e289 )

  global inventory(inv, i) {
    #default inventory.look;
    look {
      if (!inv.length) "You don't have anything. ";
      else {
        "A quick search of your rucksack turns up: ";
        foreach i, visible item: inv {
          if (item.name.string != "look") {
            if shortdesc in item item.shortdesc;
            else item.name.string;
          }
        }
      }
    }
  }

The names in brackets after the "real" name are alternative names. This is such a common functionality that it has its own syntax :)

The rest of the function should be obvious to any D user. Remember, evaluating a string prints it. :)

How would this function be accessed?

Well, it's marked as global and exists under "area world", the root scope. That means the user can access it via "look inv" at any time.

Lookup rules:

Whenever the user inputs a command, the first word is moved to the end - so "hit dog with club" becomes "dog.with.club.hit". This serves to translate common English imperative syntax into pseudo-object-oriented method syntax. Further rewriting can be performed with global regex statements as appropriate.

The first component of the command is first looked up in the location of the player, then in the "parent" location, etc until an area scope is hit. Lookups for player input cannot traverse area scopes. (Internal calls have no such restrictions)

Finally, global scopes below root are always accessible.

There are a set of default scope names.

"default": is evaluated when the surrounding scope is evaluated, instead of all the statements inside. Evaluation does not automatically recurse into scopes.

onEntry, onExit: evaluated when the player's location changes.

onInstantiate: evaluated when a scope is merged.

onChange: evaluated when the value of a scope is modified.

onVisibleChange: evaluated when the visibility (#) of a scope is modified. This can be done via the .visible property.

Here is a complete list of statements:

log: switch debugging on or off. No-op.
abort: Cancel scope evaluation.
pragma(name): call a predefined callback. No-op.
[name of scope]: evaluate that scope.
if (cond) st1; [else st2;]: self-explanatory.
foreach itervar, [visible] stmt: scope   statement: evaluate statement for all (visible?) scopes in "scope" bound to "stmt" via alias.
enter [area scope]: moves the player to a new location. The current location is accessible via the "location" keyword.
term: ends game.
move [scope] [destination]: self-explanatory.
delete [scope]: dito
insert [scope] into [location]: self-explanatory.
[scope] = [value]: self-explanatory.
{ ... }: A list statement. Evaluating it evaluates its contents. It is "flat" to iteration, meaning it introduces no scope by itself.
[something that evaluates to a string]: print that string.
alias name = destination: defines a scope alias. Basically a reference :)
regex [match] -> [substitution]: Rewrites the user input.
scope:parameter: explained above.

More questions always welcome!


More information about the Digitalmars-d-announce mailing list