20091009

My Fake Name (Alias)

Over the last several weeks (actually, over a month - I just checked svn log) I've been working on implementing aliases. Yes, yet another amenity rather than an actual MUD feature. Implementing actual MUD features are really hard, because they have all kinds of pesky considerations like game design and entertainment value. If I just stick to adding little dinkies like this, I won't have to bother with all that heavy stuff.

My other rationalization is that every feature I implement expands the flexibility of the codebase as a whole. As you'll see in either this post or another (depending on length), I added quite a bit of other functionality to make aliases work well. I tell myself that this extra flexbility will come in handy someday when I get to implementing the real MUD features.

What thems are

Aliases are a way to type a (usually short) command and have it turn into a different (usually longer) command. They're actually a pretty fundamental part of MUDding, and can be used for a lot of useful things. For instance, when I used to PK (nowadays called PvP in all the cool kids games), when I knew whom I was fighting, I'd make an alias mapping the single letter "j" to the command to initiate an attack on him. In this way, I wouldn't even have to move my hand off of the home row of the keyboard to start attacking. Aliases made that possible.

Client vs. server side aliases

Actually, with a sufficiently advanced client program, aliases are more or less obsolete on the MUD. I used to use MUSHclient, a very fully featured MUD client. It had enough customization that anything I could do alias-wise with the help of the MUD (on the server side) I could do at least as well on the client side. Still, sometimes I was away from home and had to use old fashioned telnet, so it's good that server side aliases existed anyway.

As a quick aside, MUSHclient's killer feature in my eyes was the ability to wire it up directly to a perl script that could essentially automate it for you. Sure, you could write perl to be a full telnet client for you, but MUSHclient let you relinquish control temporarily to the script by typing special commands. I'll admit, I wrote at least one script that after I'd left went on to be banned by that particular MUD's administration... but that's a story for another time.

Back to what thems are

The fundamental components of an alias are the command you type in and the string that it gets expanded to. I think many MUDs (stock ROM, for example) leave it at that. I went a little further, for fun. In my MUD, the expanded command can take arguments, too. Since non-programmers don't know or care what arguments are, I'll illustrate what this means with some MUD output.

> alias donate tell &2 Here you go, my good man, have &1 dollars.
Added alias donate => tell &2 Here you go, my good man, have &1 dollars..

> donate 5 man
You tell a man, 'Here you go, my good man, have 5 dollars.'

Here I've added an alias where I can tell someone about some money I'm giving them. The &1 and &2 are placeholders that correspond to the first and second things I type after donate, respectively. Those are called "arguments to donate". I have no idea how that word came about there. The &1 and &2 are inserted into the resultant text in the places I indicated.

Along with the ability to add aliases, it's expected to be able to overwrite them, remove them, and list them. Using the alias command like before, you can do exactly this.

> alias
Aliases:
  donate => tell &2 Here you go, my good man, have &1 dollars.

> alias donate
donate => tell &2 Here you go, my good man, have &1 dollars.

> alias donate ''
Removed alias donate.

And since they are often useful over the long term, they save to your playerfile in your preferences, so they're there automatically whenever you load up.

The rest of this post is more about how they were implemented and what happened along the way. As maybe you can tell, there are a few distinct parts involved in this work: the alias processing, the alias management command, and persistence.

Interpreter changes - lockout

The alias processing had a few interesting parts. For one, it required hooks in the interpreter to make it work. Normally, the interpreter has a single list of commands that it looks up in when you type in a command. Now, for a few reasons, I wanted to make it store aliases separately in another list.

  • I wanted alias lookup to follow different rules than normal command lookup
  • I wanted to be able to show a list of all the built-in commands separately, with the commands command
  • I wanted to be able to display the list of only aliases in the alias command
  • I wanted to be able to add and remove to the alias list without having to traverse or manipulate the list of built-in commands.

Most of those reasons are relatively self-explanatory, but the first one bears more detail. The fundamental difference between how aliases are looked up and how built-in commands are looked up is that aliases use an exact match and builtins use a prefix match. So you can type "al" and get the alias command above, but you can't type "don" and expect to get the donate alias from above.

The prime example I used in my rationale here is making aliases for the alias command itself. It's of utmost importance that users not be able to lock themselves out of getting to the alias command, because then they've have no way of removing a faulty alias.

Consider what would happen if I did prefix matching for aliases. What if the user made an alias named "alias"? Well, maybe the lookup could prefer the builtin there. What about one named "alia"? And then what if they type in "al"? The prefix matches both the alias "alia" and the built-in alias command. It's not obvious in the general case (for other built-in commands) which precedence should take place.

So the interpreter first tries an exact match against the alias list, then a prefix match of the builtins list. In the end I think this will yield the least confusing behavior.

Infinite recursion in the interpreter

Whenever you talk about aliases, the possibility of infinite recursion becomes very real. What happens when I map an alias back to itself?

> al
Aliases:
  abc => def
  def => abc

Oh noes! Danger lurks! Unchecked, this will keep expanding forever. Why would I even want to allow this? Well, it could be convenient. For instance, in the previous section, I talked about how we don't prefix match aliases. Well, with recursive alias mapping the user can do this.

> alias
Aliases:
  donate => tell &2 Here you go, my good man, have &1 dollars.
  don => donate

Now the player can type "don" to get the donate alias. I'd say that's useful enough to keep around. So how to combat against the recursion problem? My solution isn't terribly elegant, unfortunately. The interpreter just keeps track of how deep it is in lookup and bails out if it gets too deep.

def interpret(line)
    if interpretDepth() > MaxInterpretDepth
        DebugOutput.debugOut("possibly recursive alias or command", LWarning, LCommand)
        return nil
    end

    self.interpretDepth = interpretDepth() + 1

    # do lookup and recursive resolution of alias...

    self.interpretDepth = interpretDepth() - 1
    return ret
end # function interpret

Since the depth is stored as a member variable of the interpreter, there are obvious problems if the interpreter ever needs to be accessed, say, on two separate threads. I'll cross that bridge when I come to it.

Alias management

This is actually pretty straightforward. This class of commands is in line with other "management" commands like who, and so already has access to all the objects it needs to manage the list of aliases. Boooooring.

As I start to write the section about persistence, I'm getting the feeling it could use a post all to itself, so that's what I'm going to do. And I feel the urge to talk at least a bit about the way I did testing during this work, because I feel I did some neat things. So maybe that will get a post too.

I have a strange relationship with blogging. I like coding, but I also like blogging about what I did. Sometimes when I near the completion of a feature, I look forward to the blog post I'll write about it soon as a kind of reward for the work I did over the last weeks. One might ask why I don't just blog whenever I want. Well, for this sort of blog post I only feel like I want to when I have something concrete and complete to talk about. I don't really like presenting something that's obviously (to me) half baked or incomplete. As we call it at work, I like to be "code complete" before talking much about it.

1 comments:

leia said...

you are insane.

Post a Comment