24 Jul, 2012, Idealiad wrote in the 1st comment:
Votes: 0
I've been browsing some server source recently, and it's common to see player commands written as functions (if not classes), for example

def look():


def get(x):


….


This is straightforward, but it also makes me wonder, is it a good idea to express player verbs with the server language verbs like this? One obvious problem is the shadowing of any language symbols that might share the same name. Of course you can get around that with naming conventions or some kind of namespace clarification, but on a more abstract level do you want to couple your command language so closely with your server language anyway?

The first answer to this I thought of was a data-driven approach, (XML, YAML, something in your language or whatever) with accompanying bits of logic for each command. What might be some other solutions? Or is it not an issue in your eyes?
25 Jul, 2012, Rarva.Riendf wrote in the 2nd comment:
Votes: 0
I think it is generally a bad idea, and I use do_get for the get command as an example (do_ is short enough, and makes it clear it is a command as well, win win situation)
25 Jul, 2012, Lyanic wrote in the 3rd comment:
Votes: 0
I use cmdCommandFunctionName.
26 Jul, 2012, Runter wrote in the 4th comment:
Votes: 0
I don't think he's really asking what the naming convention should be. Namespacing issues will swing wildly from language to language. I think what he's asking if implementing your commands as functions/methods/whatever is a good idea.

Quote
The first answer to this I thought of was a data-driven approach, (XML, YAML, something in your language or whatever) with accompanying bits of logic for each command. What might be some other solutions? Or is it not an issue in your eyes?


I think you're on to something here. I'll share my thoughts on the topic. I think no matter how you implement it, it's basically going to be some code running. I decided that if commands look like a script, swims like a script, and quacks like a script then I can reasonably treat it like a script. With all the caveats and implementation details that you might expect from whatever powers your world. I think it vastly simplifies your design once you start treating basically any arbitrary logic as a script. So the interesting thing to me was what is exposed to the code, and what it belongs to. My goal was to remove all of the boilerplate logic and separate the concerns of the command logic (a script) from the command parsing logic. By boilerplate I mean argument types, number of arguments, variations of arguments, and the traffic copping of which logic to call based on invocation, like different logic for different types.

This doesn't really answer your question about how it should be developed in code, but I think when we think about logic in the most flexible way we have to look at closures, lambdas, etc. Most languages have some type of closure or anonymous function support. Or pointers to functions. I think the best way to make use of them is by looking at commands as events, and logic as anonymous functions.

This is a really naive example, but when you start treating all logic as decoupled, stuff starts to get nicer. Evented code optional. We might get something like this:
# from some other source
purge = ->
self.destroy_and_remove_from_game()

ch.after "input", ->
if good_input
ch.trigger("self delete")

ch.after "self delete", logic
ch.after "received third strike", logic
ch.after "character didn't login for 3 years", logic

npc.after "death", logic


The great things here to me are how well we can decouple the logic and focus on test driven development with not much more code. The evented nature is optional, but it makes it pretty obvious why reusing code might be a great idea, and why we can't expect the context of where the code being fired to always be the same circumstances.
26 Jul, 2012, plamzi wrote in the 5th comment:
Votes: 0
Runter said:
My goal was to remove all of the boilerplate logic and separate the concerns of the command logic (a script) from the command parsing logic. By boilerplate I mean argument types, number of arguments, variations of arguments, and the traffic copping of which logic to call based on invocation, like different logic for different types.

We might get something like this:

if good_input
ch.trigger("self delete")


The great things here to me are how well we can decouple the logic and focus on test driven development with not much more code. The evented nature is optional, but it makes it pretty obvious why reusing code might be a great idea, and why we can't expect the context of where the code being fired to always be the same circumstances.


First off, I want to say that I think your argument is solid, and the general concept is something I've been practicing myself: isolating common checks, cross-referencing command code (not only to increase re-usability but also to improve user-friendliness, as in automating certain chains of commands that we know for sure the user would want to kick off if a certain event gets a certain kind of response, for instance "re-attempt flee from certain death as soon as possible if I fail to flee").

Second, I just want to point out that there's one obvious and unavoidable downside to abstracting (if good_input…). It is that adding new commands will require touching multiple places and it will not be apparent from looking at the main command code what this particular command considers to be good_input.

Sometimes, I find myself intentionally writing less-than-elegant stuff just so that it's less opaque to a newcomer, and more centralized than dense code, just to make it generally easier to maintain.
26 Jul, 2012, Idealiad wrote in the 6th comment:
Votes: 0
plamzi said:
Second, I just want to point out that there's one obvious and unavoidable downside to abstracting (if good_input…). It is that adding new commands will require touching multiple places and it will not be apparent from looking at the main command code what this particular command considers to be good_input.


Well I wouldn't be one to advocate for dense or elegant code over simple, well-organized and clear code, but one thing I have been seeing that also 'feels wrong' is people parsing input in each individual command. This does put more things relevant to a command in one place, but I feel like it does make the individual command logic more complex to look at. I was reading about Aspect-oriented programming lately and it seems to raise a lot of issues relevant for muds in particular. Another appropriate technique here might be rules-oriented programming which Runter's declarative-style example seems to lean toward.
26 Jul, 2012, Runter wrote in the 7th comment:
Votes: 0
Opps, the code should have been:
# from some other source
purge = ->
self.destroy_and_remove_from_game()

ch.after "input", ->
if good_input
ch.trigger("self delete")

ch.after "self delete", purge
ch.after "received third strike", purge
ch.after "character didn't login for 3 years", purge
npc.after "death", purge


Sorry about that, can't edit it now. :(

Quote
Second, I just want to point out that there's one obvious and unavoidable downside to abstracting (if good_input…). It is that adding new commands will require touching multiple places and it will not be apparent from looking at the main command code what this particular command considers to be good_input.


Yes, it's a problem one would need to take care to avoid, but actually you can write a really strong parser that will determine if the input is good or not virtually for any conceivable command based on some really simple format. I decided to use nested arrays. So I can set meta data for the command to something like this to represent the arguments it accepts…
Consider a look command that you want to validate all input for and traffic cop it.

{
:look =>
{
:default => look_lambda
[[:at, LivingThing], [:LivingThing]] => look_at_lambda,
[[:in, Item]] => look_in_lambda
}
}


You use data like this to automate across all of your commands the way it works and you don't have to add more code or boilerplate anywhere. Where you define this data at is up to you, no reason it can't be at the same place you write the rest of the command. Actually, this sort of thing makes a good case for instantiating commands. In any event, I'm a firm believer the logic should be completely separate from the parser that determines if values are legit before the command is called. In other words, I think the command should only be called if it will succeed and it shouldn't know where it's getting the already resolved parameters from, or care.
26 Jul, 2012, Rarva.Riendf wrote in the 8th comment:
Votes: 0
Well you need to parse at some point be it in the command itself, (because not every command use the same arguments list) or before so it calls the right method. But wherever it is, do it in a 'single point of entry'. Nothing is more annoying when you maintain code that to have to read multiple config files to see what a single line of code does when you do step by step debugging.
so no 'one config file for look, then another for at then another for in'.

Quote
I think the command should only be called if it will succeed and it shouldn't know where it's getting the already resolved parameters from, or care.

I guess you have a nice config files with all the error messages to send back to your user then depending on wich parameters failed. I agree it is the best solution, but not necessarily the easiest to look it when you maintain code.

As a user nothing is more annoying than just having a 'Huh', when he mistyped something.

The only thing that is wrong in parsing arguments in the commands if the code is nothing but copy paste. usually though it is not that of a clear cut. I do not think I will ever separate the parsing for the command for a single reason: I will not translate the mud in any other langage, so putting messages outside of the command for the sake of writing beautifully architectures code has absolutely no point.
If I want to change anything in look, I go for the look command, and that's it.
26 Jul, 2012, Runter wrote in the 9th comment:
Votes: 0
Quote
I guess you have a nice config files with all the error messages to send back to your user then depending on wich parameters failed. I agree it is the best solution, but not necessarily the easiest to look it when you maintain code.


Actually, it's easy to generate something based on the argument type you expected using the structured data I presented above. Then you can give something like this without any additional work:

Quote
>look fireball

Fireball is a Spell. This command accepts Other Characters and Items as arguments.
Did you mean one of these?
look
look [at] <Other Character>
look [in] <Item>


This is what I've done, and it works great. Additional filters can be set in the data for other parameters like "Can't use it in combat", "can't use it while in stun state" etc. It doesn't require duplicating logic, or use confederated custom error messages all over the place.
26 Jul, 2012, Tyche wrote in the 10th comment:
Votes: 0
Idealiad said:
I've been browsing some server source recently, and it's common to see player commands written as functions (if not classes), for example
This is straightforward, but it also makes me wonder, is it a good idea to express player verbs with the server language verbs like this? One obvious problem is the shadowing of any language symbols that might share the same name. Of course you can get around that with naming conventions or some kind of namespace clarification, but on a more abstract level do you want to couple your command language so closely with your server language anyway?


It might be an intrinsic part of your mud programming language.

MOO:
@verb container:get this in any

COOL:
verb "put place insert" : "in into" = put_in;
verb "get remove take" : "from in" = remove_from;
verb "l*ook" : "in into inside" = look_in;


Or you might have meta-programmed into a reflective language like Ruby:
class Living < GameObject
verb /say (.+)/, :cmd_say

def cmd_say *args
end
end

Idealiad said:
The first answer to this I thought of was a data-driven approach, (XML, YAML, something in your language or whatever) with accompanying bits of logic for each command. What might be some other solutions? Or is it not an issue in your eyes?


I used the YAML approach with TeensyMud.
class Command
attr_reader :cmd, :name, :help

def self.load

cmds = YAML::load_file("cmd/#{i}.yaml")
cmds.each do |c|
Kernel::load("cmd/#{i}/#{c.cmd}.rb")
cmdtable.insert(c.name, c)
end

end
end


ColdCore defines an object which can be added as a property on another object. It's a dictionary which contains templates for matching, a syntax message, the name of a method, and an argument list.
$has_commands local = #
$has_commands local = #[
["wh?isper", [["wh?isper", "* to *", "wh?isper <any> to <any>", 'whisper_cmd, #[[1, ['any, []]], [3, ['any, []]]]]]],
["pose", [["pose", "*", "pose <any>", 'pose_cmd, #[[1, ['any, []]]]]]],
["get|take", [["get|take", "*", "get|take <descendant of $thing>", 'get_cmd, #[[1, ['descendant, [$thing]]]]],
["drop", [["drop", "*", "drop <descendant of $thing>", 'drop_cmd, #[[1, ['descendant, [$thing]]]]]]]
]; [/code]

Of course there are user commands that manipulate the structure.

All the above approaches are really fundamentally data-driven.
26 Jul, 2012, Runter wrote in the 11th comment:
Votes: 0
The coldcore snippet looks pretty flexible, but it looks like it might be hard to learn to write the rules.
26 Jul, 2012, Idealiad wrote in the 12th comment:
Votes: 0
Great reply Tyche!

Thinking about separating parsing from commands, I realized that doesn't make sense because a command is all about parsing. This is what we've been talking about in this thread all along but the terms are so commonly mixed up in mud source.
26 Jul, 2012, Rarva.Riendf wrote in the 13th comment:
Votes: 0
@Runter: It is nice at code level and as an extensive help, but I don't like realy like auto generated messages. It feels automated (well it is , obviously) and if I find it great as a programmer to have help like these, as a player, it kinds of kill the mood (but that is just my pov)
26 Jul, 2012, Runter wrote in the 14th comment:
Votes: 0
Rarva.Riendf said:
@Runter: It is nice at code level and as an extensive help, but I don't like realy like auto generated messages. It feels automated (well it is , obviously) and if I find it great as a programmer to have help like these, as a player, it kinds of kill the mood (but that is just my pov)


I don't know how it kills the mood to have clear consistency in how you display messages to players. If for some reason you really wanted to hand roll define every message, or override defaults, you still could do that in configuration. I see absolutely no reason to do that, though.

Can you give an example of where automating that wouldn't be as good?
26 Jul, 2012, Tyche wrote in the 15th comment:
Votes: 0
Runter said:
The coldcore snippet looks pretty flexible, but it looks like it might be hard to learn to write the rules.


Fortunately it's documented pretty well.

Quote
help commands
————— Cold Help System: General Information: Commands —————

This section outlines some of the basics of commands, from understanding the logic
behind why they are named what they are, to using them, to programming them.
(Programmer's note: Commands in the Cold Dark are handled entirely in the
database, the driver is oblivious to the intent or meaning of what it is you type).

[VR vs Non-VR] The difference between the two
[Types] Local, remote, shortcuts and aliases
[Matching] Conventions used in matching commands
[Enhanced] ColdCore's Enhanced Commands

[All Commands] An index of all commands
[Programming] Programming Commands

———- .. [Appearance] [Security] . [Interaction] [Interface] .. ———-
help types
———– Cold Help System: General Information: Commands: Types ————

Each command is associated with a specific method. When a command match is
found, that method is executed with arguments depending upon what you typed.
The Cold Dark recognizes three types of commands:

Local Commands

Local commands are any non-directed command, or any command which does
not require a target in the command template. Matching for local commands
uses ColdC template matching. The parser looks for local commands on the
user, the user's ancestry, and the user's location (but not anything the
user is carrying or in the user's location).

Remote Commands

Remote commands differ from Local Commands in that they require the
target of the command, in the command string. For instance, "get button"
would be a remote command, because it requires the target of button to
function correctly. Matching for Remote Commands uses ColdC template
matching

Shortcuts

Shortcuts are intented as wrap-arounds for commands, using ColdC pattern
matching instead of template matching.

Aliases

Command aliases are personal shortcuts added by the user. They are very
similar to command shortcuts, with the primary difference being their
scope. A command alias is defined on a per-user basis. To add a command
alias for more than one person, use a shortcut and add it to a command-ui
object.

See also: [@add-command], [@del-command], [@add-shortcut], [@del-shortcut],
[@commands], [@add-command-alias], [@del-command-alias], [@command-aliases]

—————————— [VR] . [Matching] ——————————
help matching
———– .. General Information: Commands: Matching Conventions ————

Two types of general matching systems are used in Cold. The base matching
system is pattern matching. The asterisk character ('*') in a pattern match is used
to represent a wildcard. It can be placed anywhere in the pattern. It tells the
interpreter that when matching the pattern with a string, anything can match the
wildcard. This becomes useful for matching different strings. Some examples:

Pattern: "match *"
String: "match anything after this"
Wildcard Match: "anything after this"

Pattern: "match *t not this"
String: "match only this but not this"
Wildcard Match: "only this bu"

Template matching expands upon the basic idea of pattern matching. Template
matching is a little smarter about matching. It recognizes words as anything
bounded by spaces or the beginning and end of the string. In a template the
wildcard must be its own word–it must have spaces around it. Templates also
add two more special characters; the question mark ('?') for optional matching
and the pipe character ('|') for multiple matches.

If a question mark is placed within a word, it means that the word must match up
to the point of the question mark, but everything after that point is optional. For
instance, all of the following would be valid against the specified template:

Template: "th?is"
String: "th"
String: "thi"
String: "this"

The pipe character (|) is used to specify several different words that can match
the place of one. For instance, the template "this|that|there" would match "this"
OR "that" OR "there". It is easiest to logically think of the pipe character as OR.
With these elements drawn together you get a simple yet dynamic matching system.
Some full template examples:

"l?ook at *"
"give|put * to|in *"
"@who *"
"@lock * to|with *"

——————————- [VR] [Types] . ——————————–
help enhanced
—— Cold Help System: General Information: Enhanced Command Templates ——

The Cold Dark further expands upon the above matching systems, specifying certain
types of arguments which can be accepted for a command, where the wildcard ('*')
is located. These are specified within less-than and greater-than signs (< and >).
These tags simply tell the parser what type of arguments to accept in that location
on the command line. An example would be:

push <user>

Where <user> must be a valid user of the system, some more examples follow.

"l?ook at <thing>"
"get|take <any> from <thing>"
"@who <user>"
"@show <object>"

More information on Enhanced Command Templates can be found in the section
[Programming Commands].

———— .. [Security] [Commands] [Interaction] [Interface] . ————-
help @add-command
————————— Commands: @add-command —————————-

PROGRAMMER COMMAND

Syntax: @add-c?ommand|@ac "<Command Template>" [to|for] <objref>

Used to add a command. The method called if the command matches must be specified.
The command template must be an [Enhanced Command Template]. Arguments send to
the method are always ordered the same, the first argument is always the command
string, followed by subsequent arguments relative to how the command template would
match using template matching (see [Matching Conventions]).
For instance, the following enhanced command template:

"@get <any> from <this>"

Would evaluate to the following standard template:

"@get * from *"

From this point, you can simply evaluate the template matching to see how its
results would be ordered:

;match_template("@get something from nothing", "@get * from *")
=> ["@get", "something", "from", "nothing"]

Based off these results the arg definition for your method could be:

arg cmdstr, cmd, arg1, prep, arg2;

When a command is added it will not immediately begin working. Command caches
must be updated first. There are three different situations that will arise when adding
a command:

Adding a local command to yourself

This situation is the simplest to update. If you have added a command to
yourself simply type [@rehash], and your local caches will be updated.

Adding a remote command to an object

When adding a remote command to something you can rehash the remote
caches by simply dropping the item and picking it back up.

Adding a local command to another object

If you are adding a local command to an object other than yourself, you
will have to rehash the entire system's caches. This is done with the
command [@rehash-all], which is an administrative command.

——————————————————————————-
help @add-shortcut
————————— Commands: @add-shortcut —————————

PROGRAMMER COMMAND

Syntax: @add-s?hortcut|@as "<shortcut>" [to] "<command>" [on] <objref>

This command adds a shortcut to a <command> on <object>. This is similar to the
[@add-command] command, however, @add-shortcut allows you to add a shortcut
to an existing command, with a different name or syntax. The scope of shortcuts is
global, depending upon where it is added. A shortcut added to a command-ui object
which others also use will be inherited by the other users (this is the primary
difference between command shortcuts and command aliases).

The shortcut may include wildcards for where arguments are accepted (where it will
match anything). These wildcards are defined using an ordered mechanism of a
percent-sign and a number. An example would be the following shortcut, which
defines two ordered wildcards of %1 and %2:

"page %1 with %2"

The command side of the definition must also include equivalent ordered wildcards,
matching the same cards defined on the shortcut (but not necessarily in the same
order). You can leave out wildcards on the command side. Wildcards on the shortcut
side must be defined in order, starting from one.

A full example would be the shortcut of a dash (-) for paging:

@as "-%1 %2" to "@page %1 with %2" on $user.page_cmd

See also: [@del-shortcut], [@add-command-alias], [@del-command-alias]

——————————————————————————-
help @add-command-alias
———————— Commands: @add-command-a?lias ————————

Syntax: @add-command-a?lias|@aca?lias "<alias>" [to] "<command>"

@aca allows you to alias one command to another name, for yourself (to do this
globally see [@del-command-alias]

Example:

>@aca "poof %1" to "pub %1"
New command alias "poof %1" => "pub %1" added.

The new alias 'poof %1' will now allow you to talk on the public channel (to which
'pub %1' points to) as such:

>poof hello all!
<Public> Vampire: hello all!


See also: [@del-command-alias], [@command-aliases]

——————————————————————————-


The help system is pretty nice. It's based on heirarchical nodes, modeled after VMS help system.
It remembers where you were. If you are using Pueblo client all the nodes are clickable links.
It's also web accessible.
27 Jul, 2012, Tyche wrote in the 16th comment:
Votes: 0
Idealiad said:
Thinking about separating parsing from commands, I realized that doesn't make sense because a command is all about parsing. This is what we've been talking about in this thread all along but the terms are so commonly mixed up in mud source.


There's also been this long running debate between global and local commands.
Should there be a global command table (i.e. defined on player characters),
or should they be defined on the objects that implement them? Or both?
27 Jul, 2012, Rarva.Riendf wrote in the 17th comment:
Votes: 0
Quote
I don't know how it kills the mood to have clear consistency in how you display messages to players.

Because what you generate is really great (really I mean it) for an help or man file, not when typing the command with wrong argument or a typo.
When someone try to eat a stone, I prefer an humourous message than [stone] is not of type [food].
or Huh, what ? (meaning that the player have to type help [isnert commande name here]
I sometimes provide those kind of messages, but for more technical commands like map.
And even then if you cannot see because there is no light, there is an humourous message as well.

As a player I prefer a system like yours to be the fallback when nothing has been defined, not the primary one. They are not mutually exclusive.
Ideally, I don't want to see automated error messages or help when I play, but only when I request them.
27 Jul, 2012, Runter wrote in the 18th comment:
Votes: 0
I think it's okay if people want to do that, but it doesn't really add anything to the game for me so I can't really relate to someone who doesn't see campy error messages and feels like something is missing from the game. I'd much rather see a clear technical reason than something drenched in sarcasm about why I can't target myself with a command.
31 Jul, 2012, Runter wrote in the 19th comment:
Votes: 0
Back to the original topic, I think a interesting discussion related to this, especially when you start talking about making the command name the method name, it might be interesting to have public interfaces exposed in precisely this manor.

In ruby you might do something like this:

class Socials
def smirk; "You say something."; end
def laugh; "You laugh."; end

def method_missing method
closest_cmd = match_partial_command(method)

if closest_cmd
self.send(closest_cmd)
end
end

private
def match_partial_command value
self.methods.select{|name| name =~ /\A#{value}/ }[0]
end

def not_exposed
"Does something, does not return nil."
end
end

class FelineSocials < Socials
def pur; "You pur."; end
end

interface = FelineSocials.new

interface.smirk # You smirk.
interface.smir # You smirk.
interface.pur # You pur.
interface.not_exposed # nil
interface.non_existant # nil


It's a low cost for a lot of milage. In this case I'm not doing everything that would be required to make this safe. In ruby it would need to have all of the public methods undefined from it, but yeah. It's a fairly common pattern for programming languages that allow this kind of meta programming. (Blank objects)
31 Jul, 2012, Runter wrote in the 20th comment:
Votes: 0
Oh, and if you wanted to extract out the logic for the parser just need to inherit from a blank parser class that implements the method_missing part.
0.0/23