Search website

Rules Based Programming in RMUD

Today I want to talk about Rule Based Programming. (I say that as if I post every day and not once ever three years.) This will all be in the context of RMUD. Around the first of the year or right before, I was struggling with implementing features of the mud using the interface based design I had employed up to that point. It wasn’t necessarily going badly, but I could see what the end game of that design was going to look like. It was not going to be pretty.

A specific problem I encountered was changing the name of a player based on some sort of status effect. I started with the assumption that status effects are things that I wanted to have. Status effects had to affect properties of objects. Here is my first implementation.

public interface IStatusEffect
{
  void Apply(MudObject to);
  void Update(MudObject on);
  void Remove(MudObject from);
}

This simple design works terribly well for a large range of problems and if it works for yours, you should use it. It has one terrible flaw. For this example, use this definition of MudObject

public class MudObject
{
  public string Name;
  public List<IStatusEffect> StatusEffects;
}

Also, you should assume that wherever example code does not actually work, because it either does not compile or would produce runtime errors, that I’m omitting things for brevity and the mistake is intentional. Even if it obvious that I really just didn’t bother to check that it worked.

If I wanted to define a status effect that changes the name of the object, I might write it like this

public class WereEffect : IStatusEffect
{
  public void Apply(MudObject to) { to.Name = "wolf"; }
  public void Remove(MudObject from) { from.Name = OriginalName; }
}

Add some bits and pieces to make this effect automatically expire using a timer, or something. It’s simple enough. Until we have two of them. We apply a WereEffect, which changes the name. Then we apply another effect, which changes the name to something else. Then the WereEffect expires, and changes the name back to the original name – even though the second effect is still active. Worse, the second effect expires, and if it remembered the name ‘wolf’ as the original name, it changes the object’s name to that.
There are a few ways to make this work. I can think of four and I explored two of them. The simplest way is to limit objects to having one status effect at a time. I did this, at first. It worked for about a week, and then I wanted to have more then one effect at a time. I won’t write an example because it is so simple.
The second is to give all of the status effects priorities and use these to rigidly control what supersedes what, what can coexist with what, and how they stack. I did not explore this option. I won’t write a sample because it is so complex.
A third option is to make every variable a stack of values. The name of an object is a stack, and status effects push their change onto it. They remove their specific change, so while they are always stacked up, they can be yanked out from anywhere.
This method might be implemented like this

public class MudObject
{
  private List<Tuple<int, string>> _Name;
  private int _NameID = 0;
  public string Name { get { return _Name.Last; }}

  public int PushName(string Name)
  {
    _Name.Add(Tuple.Create(_NameID, Name));
    return _NameID++;
  }

  public int PopName(int ID)
  {
    _Name.Remove(p => ID == p.item1);
  }
}

public class WereEffect : IStatusEffect
{
  private int ID;
  public void Apply(MudObject to) { ID = to.PushName("wolf"); }
  public void Remove(MudObject from) { to.PopName(ID); }
}

Now repeat this for every property a status effect can manipulate. I would probably go for a generic set of stacks if I stuck with this solution.

And the final option, and help me God, this is the one I actually chose when I encountered the failings of the first option, is to, when you ask an object for the value of a property, check with every single status effect and ask them if they want to tamper with it first. This is best explained with example code. (Well, explained, at any rate.)

public interface IStatusEffect
{
  public string ModifyName(String Name);
}

public class MudObject
{
  public string _Name;
  public string Name {
    get {
      var r = _Name;
      foreach (var statusEffect in StatusEffects)
        r = statusEffect.ModifyName(r);
      return r;
    }
  }
}

If a status effect doesn’t want to change the name, that’s fine, it can implement public string ModifyName(String Name) { return Name; }. Now repeat this for every property an object can have. Again, I would probably go with some generic solution if I kept this design. But I did not keep this design because it is awful.

All of these methods handle changing the properties of an object. None of them handle changing the behavior of an object. They could, in theory, do so, but there is a fundamental incompatibility with how I was implementing behavior and how these status effects worked. Behavior was implemented using interfaces, such as IOpenable. If an object implemented IOpenable, it could be opened. It was dreadfully simple, and intuitive, and easy to work with.
Except, now I wanted status effects that would change the behavior of objects. Perhaps I wanted a status effect to make something openable. How would I do that? C# doesn’t allow me to add or remove interfaces from an object at runtime. I could make every interface – including basic things like IOpenable – a status effect. And, actually thinking about it, this sounds like a perfectly reasonable solution. The Open command wouldn’t check to see if the mud object implemented IOpenable, it would check to see if the object had the Openable status effect. I did not explore that solution for two reasons, first because I already knew of a fantastic solution, and second because I only just thought of it.

The solution I went with was rules. Each object has a set of rulebooks, and each rulebook has a name and a type. There is also a set of global rulebooks which act as ‘default’ rules, or implement behavior that’s global to every mud object. There are three kinds of rules, check rules, perform rules, and value rules. We’ll discuss value rules in detail here, in the context of implementing the name changing WereEffect.
In the current system, there are no actual status effect. But something does have to apply the effect (Change the name to ‘wolf’) and remove it (Change the name back.) The change can be achieved by adding or removing rules, or making rules contingent on other properties.
I hope the vulgarities of the C# implementation don’t get in the way. This example implements the were change by adding and removing rules from the object.

…when the were change is triggered. Perhaps in the ‘transform’ command?…
ActorThatIsTransforming.Value<MudObject, MudObject, string>("printed name").Do((viewer, actor) => "wolf").ID("lycanthrope");

Later, to remove it…
ActorThatIsTransforming.RemoveRule("lycanthrope");

Somewhere, the rulebook ‘printed name’ as been declared as taking two mud object’s and returning a string.

Rules can have when clauses, that govern when they should be applied, so perhaps a better way to handle this change is to have a global rule like this

GlobalRules.Value<MudObject, MudObject, string>("printed name").When((viewer, actor) => actor.IsTransformed).Do((viewer, actor) => "wolf");

Now this rule will only apply when the actor is transformed. In this system, to actually get an object’s name, you would consider the ‘printed name’ rulebook with the arguments of who is asking for the name, and the object being named. Shoving this method of programming into C# has not been pretty and has unfortunately left me with some repetitive rule declarations, but it has proven very powerful. I think one of the biggest advantages of this style is that things that don’t need to worry about a particular system just don’t have to. Don’t define rules that change it and you won’t affect it. A disadvantage, of course, is that this flexibility comes with a great lack of safety.

I want to look at these topics in future entries –
– The different types of rulebooks.
– How applicable rules are discovered.
– How rules are sorted.

Switch to our mobile site