16 Mar, 2010, Deimos wrote in the 1st comment:
Votes: 0
In keeping with the encapsulation theme lately, I was kind of wondering what the prevailing views were on commands/actions organization. In other words, where do actions belong? Good encapsulation would have me reject out of hand the first two of the following options, even though they do seem to make more sense semantically (especially the first one):

# This?
dude.move(room)

# Or this?
room.move(dude)

# Or maybe this?
world.move(dude,room)

# Possibly even this!
cmd_interpreter.move(dude,room)


Personally, I'm leaning toward the world.move() paradigm. I don't think game entities should be aware of other game entities in the way that the first two options would allow. Assuming dudes and rooms are part of the world, this keeps encapsulation intact, since world is only manipulating its own properties. However, I've seen the last paradigm a lot recently, and I'm not sure what to make of that. To me, a command interpreter would be part of the engine, same as the world, and it seems like bad encapsulation to allow one to muck about with the other. Maybe I'm looking at it from the wrong point of view, though?

Note that I'm not just asking in relation to movement. I'd like opinions about commands/actions in general, and where you think they fit.


Edit: My example was a failure. I shouldn't be posting so late. What I really meant to say was this:
# This?
dude.move_direction(direction)

# Or this? - lol, this one no longer makes sense at all
# room.move(dude)

# Or maybe this?
world.move_direction(dude,direction)

# Possibly even this!
cmd_interpreter.move_direction(dude,direction)
16 Mar, 2010, Scandum wrote in the 2nd comment:
Votes: 0
Semantically only the first one makes sense to me.

As far as I know the point of encapsulation is to provide an interface objects can use to interact with each other without directly accessing the other object's properties. So having one object fiddle with another object is perfectly fine, as long as it uses a proper interface.

You'd probably want something like: room.remove(object) and room.add(object) to be called by dude.move(object)
16 Mar, 2010, donky wrote in the 3rd comment:
Votes: 0
Scandum said:
Semantically only the first one makes sense to me.

As far as I know the point of encapsulation is to provide an interface objects can use to interact with each other without directly accessing the other object's properties. So having one object fiddle with another object is perfectly fine, as long as it uses a proper interface.

You'd probably want something like: room.remove(object) and room.add(object) to be called by dude.move(object)

This is pretty much what I have as well.

class Object:
def MoveTo(self, dest):
if self.container:
self.container.Remove(self)
self.container = dest
dest.Add(self)
16 Mar, 2010, Deimos wrote in the 4th comment:
Votes: 0
I suppose that was bad example, since I wasn't trying to describe a simple "move x to y" type of method, but rather a generic movement command. The difference being that I wanted to know where the logic for things like checking locked doors, levitation, terrain types, etc. would be located. Having all of that be inside the "dude" class would assume a dude knows anything about doors, traps, terrain, etc. I'm not sure that I believe that should be the case.

If it clarifies things, pretend I had used the method "move_direction(direction)" instead of "move(room)". Sorry, it's late and I'm not 100%.
16 Mar, 2010, donky wrote in the 5th comment:
Votes: 0
Deimos said:
I suppose that was bad example, since I wasn't trying to describe a simple "move x to y" type of method, but rather a generic movement command. The difference being that I wanted to know where the logic for things like checking locked doors, levitation, terrain types, etc. would be located. Having all of that be inside the "dude" class would assume a dude knows anything about doors, traps, terrain, etc. I'm not sure that I believe that should be the case.

If it clarifies things, pretend I had used the method "move_direction(direction)" instead of "move(room)". Sorry, it's late and I'm not 100%.

No-one would suggest having that in the Dude, of course. But you could have it in a handler.. if you were to do it in a data-driven way! :devil:

But putting that aside, and looking at room-based, this is one way of doing it. The move command might ask the room if the player can move in the given direction. So the room might define the logic that checks whether the exit exists, what type of exit it is, and whether the player can pass through it. Then given that the player can leave the room, you might ask the room on the other side of the exit if the player can enter it. If your exits are defined by objects, the room may ask the exit if the player can pass through it. Or the room may keep track of this state itself.

And illustrating with a sequence of calls. In the following, any call that returns result would of course display an error message that described to the player the reason for failure, in event of that failure.
  • COMMAND: result = ROOM->has_exit(direction)
  • COMMAND: result = ROOM->attempt_exit(direction, actor)
  • ROOM: result = EXIT->attempt_pass_through(actor)
  • ROOM: dest = EXIT->get_other_side(room)
  • ROOM: actor->move_to(dest)

  • Now, if the direction led over a pit and that was the exit object. The pit exit object subclass would implement a attempt_pass_through which checked if the player was levitating. Now if the direction led through a door, the door object would check if it was closed, and if it was closed whether it was locked. At this point, depending on your tastes, it might either return a message saying the door was locked, or it might look in the player's inventory and try and find a compatible key, and if it did use it automatically unlocking the door. And similarly, if it were closed and unlocked, then it might automatically open it to go through.

    How terrain types factor in depends on how you choose to use them in your game. I guess if the exit was a rock slope, then you could have that exit subclass check for the use of climbing tools.
    16 Mar, 2010, flumpy wrote in the 6th comment:
    Votes: 0
    I have number one too, it's fine.

    The way I do it is encapsulate all the movement logic in the exit object. The go command is a script executed using "method missing" on the mob object via the command interpreter

    That way, as I add more commands to the mud, objects can make use of all or any command in the mud..
    16 Mar, 2010, KaVir wrote in the 7th comment:
    Votes: 0
    flumpy said:
    I have number one too, it's fine.

    I use the first one as well, although I provide an optional second argument for sending a message before you move (it's only sent if you succeed) and the method returns a bool to indicate whether or not the move succeeded.

    Examples:

    if ( !pObject->MoveTo(pCreature) )
    {
    apCreature->PutOutput( "You can't seem to pick it up.\r\n" );
    }
    else …


    if ( !pCreature->MoveTo(pLocation, &Message) )
    {
    apCreature->PutOutput( "You're unable to enter it.\r\n" );
    }
    else …
    16 Mar, 2010, Runter wrote in the 8th comment:
    Votes: 0
    Well, the example you made makes example 1 seem most appropriate.


    However…
    Room.add ch
    Room.add item


    There's actually nothing wrong with treating Room as a container.
    I don't know a lot of people who do

    list = []
    ch.push list
    16 Mar, 2010, shasarak wrote in the 9th comment:
    Votes: 0
    Deimos said:
    I wanted to know where the logic for things like checking locked doors, levitation, terrain types, etc. would be located.

    I think a system like this should probably be fairly distributed. The values you're talking about here would be held in a number of different objects which are responsible for managing them; so, a door object would know whether it is open or shut, and whether it is locked; the playercharacter object would probably know whether it was levitating or not; a room would know what type of terrain type it contains; and so on.

    There would probably also be some logic associated with those objects. So, for example, it might be reasonable to have an exit decide whether any given object (e.g. a mob or player character) can pass through it. There might be an Exit class, which has a subclass that represents an exit with a door. The superclass might have a method on it, canPassThough, which takes the thing that is moving as an argument. The superclass version might always return true. The subclass would override that and add code that checks to see that the door is open (or at least unlocked).

    I would think there would be a single object which, at a high level of abstraction, models the whole attempted movement process; it would probably have the playercharacter and the exit as properties. You would create an instance of this, initialise it, and tell it to start processing. It would then perform a series of checks - can the player move, can he pass through the exit - and, if they all pass, eventually actually remove the player from the contents of one room and add him to the contents of another. But the code that controls how most of those checks are done would not be contained within that MovementManager class, it would be delegated to various other objects; so, the player character might be responsible for checking if he is able to move (e.g. he is not overloaded or paralysed); an exit would know whether or not a player can pass through it; and so on.
    16 Mar, 2010, Deimos wrote in the 10th comment:
    Votes: 0
    shasarak said:
    I would think there would be a single object which, at a high level of abstraction, models the whole attempted movement process; it would probably have the playercharacter and the exit as properties. You would create an instance of this, initialise it, and tell it to start processing. It would then perform a series of checks - can the player move, can he pass through the exit - and, if they all pass, eventually actually remove the player from the contents of one room and add him to the contents of another. But the code that controls how most of those checks are done would not be contained within that MovementManager class, it would be delegated to various other objects; so, the player character might be responsible for checking if he is able to move (e.g. he is not overloaded or paralysed); an exit would know whether or not a player can pass through it; and so on.

    This is pretty much what I was getting at.

    It's fairly obvious that every object involved in the command process would have a hand in the decision-making at an atomic level, but I was really wanting to know what would oversee the process as a whole. Shasarak is suggesting a MovementManager class. I was leaning toward letting the World fill in that role. Some people suggest letting the CLI do it. And I'm asking in terms of commands/actions in general, too, not just movement. Spell-casting, for example, would produce the same situations. Do you let "dude" oversee his own spell-casting? Or do you let the World, CLI, or SpellManager classes do it? Obviously "dude" would be responsible for, say, checking whether or not he has that spell, and other such atomic actions required for the process, but what controls the actual casting process itself?

    Hope that clears it up! Thanks for the opinions thus far.
    16 Mar, 2010, KaVir wrote in the 11th comment:
    Votes: 0
    Deimos said:
    Spell-casting, for example, would produce the same situations. Do you let "dude" oversee his own spell-casting? Or do you let the World, CLI, or SpellManager classes do it?

    I deal with spellcasting and other combat moves somewhat differently. Each Thing can have a number of Combat location objects. Location-specific commands are sent to the appropriate Combat object, which then handles any attacks or spells that need to be performed. If the spell should have a lasting effect, then a Spell object is created and attached to the Thing.
    16 Mar, 2010, Runter wrote in the 12th comment:
    Votes: 0
    Quote
    Spell-casting, for example, would produce the same situations. Do you let "dude" oversee his own spell-casting? Or do you let the World, CLI, or SpellManager classes do it? Obviously "dude" would be responsible for, say, checking whether or not he has that spell, and other such atomic actions required for the process, but what controls the actual casting process itself?


    I definitely would not go the "World" implementation you mention. The more methods to delegate to a generic cure-all-handler like this the more it approaches procedural style. IMO the best way to think about this is letting your "dude" represent a factory for spells. With this way of thinking spells should be constructed and originate with the caster and this starts to make a lot of sense:

    dude = Character.new

    magic = dude.cast(:refresh)

    # What do we do with our fireball? At this point it's a bit undecided but I think this is elegant.
    # Let's assume it was cast on self.

    dude.absorb(magic)


    effect = dude.cast(:enchant_weapon)

    item.absorb(effect)


    dude.spell_book do |spell|
    magic = dude.cast(spell)
    ### Do something with each spell that can be cast? Maybe for the purpose of gathering information?
    ### Maybe for mock casts for AI to judge actual effect of spellcast?
    magic.inspect
    end


    fire = dude.cast(:fireball)
    ice = dude.cast(:frostlance)

    fireice = fire.combine(ice)

    enemy.absorb(fireice)
    16 Mar, 2010, flumpy wrote in the 13th comment:
    Votes: 0
    Here's my exit implementation:

    class ExitImpl extends GroovyMudObject implements Exit{

    String direction;
    String arrivalDirection;

    ObjectLocation destination;

    transient View view

    void go(object, String args) {
    if(shortNames.contains(args) || direction == args){
    def event = new MovementEvent(object.currentContainer, destination, object as Alive, this)
    object.fireEvent(event);

    event = new MessageEvent(scope: EventScope.ROOM_SCOPE)
    event.setScope(EventScope.ROOM_SCOPE);
    event.setSource(object);
    event.setSourceMessage("You go ${direction}.");
    event.setScopeMessage(object.getDepartureMessage(this));
    movingObject.fireEvent(event);

    registry.mudObjectAttendant.moveToLocation(object, destination)

    def otherExit = movingObject.currentContainer.getExit(arrivalDirection)
    ArrivalEvent arriveEvent = new ArrivalEvent(scope: EventScope.ROOM_SCOPE, direction: otherExit)
    arriveEvent.setSource(object)
    arriveEvent.setSourceMessage(null)
    arriveEvent.setScopeMessage(object.getArrivalMessage(otherExit))
    movingObject.fireEvent(arriveEvent)

    movingObject.look()
    return true
    }
    return false
    }

    }


    and my door code:

    class DoorImpl extends ExitImpl {
    MudObjectAttendant objectAttendant
    boolean open = false
    transient DoorImpl otherDoor = null;

    def open(obj, String args){
    boolean consume = shortNames.contains(args)
    if(!open && consume){
    sendMessageToRoom(obj, "You open the ${direction} door.", "${obj.name} opens the ${direction} door.")
    doorEvent(obj, true)
    }else if(consume){
    sendMessageToPlayer(obj, "You try to open the ${direction} door but it is already open.")
    }
    return consume
    }

    private def doorEvent(obj, boolean op){
    this.setOpen op
    if(otherDoor == null){
    DoorImpl od = findOtherDoor(arrivalDirection, destination)
    od.setOpen op
    if(!od.otherDoor){
    od.setOtherDoor this
    this.setOtherDoor od
    }
    od = null
    }
    sendMessageToRoom(otherDoor, "", "The ${otherDoor.direction} door ${op ? 'opens' : 'closes'}.")

    fireDoorEvent(obj)
    }

    def close(obj, String args){
    boolean consume = shortNames.contains(args)
    if(open && consume){
    sendMessageToRoom(obj, "You close the ${direction} door.", "${obj.name} closes the ${direction} door.")
    doorEvent(obj, false)
    }else if(consume){
    sendMessageToPlayer(obj, "You try to close the ${direction} door but it is already closed.")
    }
    return consume
    }


    DoorImpl findOtherDoor(String direction, ObjectLocation location){
    def otherRoom = objectAttendant.findOrClone(location)
    def od = null
    if(otherRoom != null){
    def otherExit = otherRoom.getExit(direction)
    if(otherExit instanceof DoorImpl){
    od = otherExit
    }
    }
    return od
    }

    void fireDoorEvent(player){
    DoorEvent openEvent = new DoorEvent(source:this, targetRoomLocation: destination,
    targetDirection: arrivalDirection, open: this.open)
    fireEvent(openEvent)
    }


    void go(player, String args) {
    if(getOpen()){
    super.go(player, args)
    if(otherDoor == null){
    otherDoor = findOtherDoor(arrivalDirection, destination)
    }
    }else{
    sendMessageToRoom(player, "You try to go ${getDirection()} but the door is closed!",
    "${player.getName()} tries to go ${getDirection()} but the door is closed!")
    }
    }
    }



    The open and close methods are called by the command interpreter which parses the user input and tries to call the method that matches the first word of the command on the players environment (the room). The room tries to do the command on the inanimate things in it's self, and then on the player (and, subsequently the player on it's own inventory). If nothing reports the command as "done", the command is looked up as a script in the "player/commands" directory. If all that fails, a generic "You cannot do that" is given (unimaginative, but serves the purpose).

    Once I had a strategy for the order of the calls (container methods, inanimate object methods, player, player inventory, scripts) and stuck to that, it all worked it's self out.

    Additionally, in my model, the object that prevents or allows movement (or enforces movement rules) would always be the exit and the room and player or mob have nothing to do with it. The exit can check the contents of the container it's in, if it wants, because it can access its own container as a reference.

    Hope this helps some.

    *edit: my implementation may appear a little over-engineered because I am messing with an event system in there too. Ignore the events being fired and you'll see the basics.
    16 Mar, 2010, Idealiad wrote in the 14th comment:
    Votes: 0
    For what it's worth, in my last project I went with a different approach than what's been mentioned here. The caveat is this was just a week-long project so it's not very well tested for completeness or scalability.

    When a command occurs (initiated by a PC or NPC, run by a generic action object), the command sends a message to an event broadcaster with the name of the event and the enactor. The broadcaster passes the message to a list of objects registered to listen for the event. These objects may be components of game-world things, or generic components not part of a particular thing but maybe responsible for some game logic.

    For example, a PC might have a position object that tracks where it is, and the game world might have a movement history object that tracks what moves where.

    Before the event broadcasts the game sorts of the list of listeners according to priority, i.e. who should receive the message first. As each object gets the message, it runs some logic that may add to/modify the data. Typically the object that would actually move the PC comes last in the list; by the time the message gets there, the move may have been marked for failure, changed in its parameters, etcetera.

    Some obvious problems with this approach – everything listening for an event type gets the event. So there'll be a lot of wasted messages in a mud. You could filter this with multiple event broadcasters I suppose.

    Another problem might be cause/effect. A priority list may not be the best way to order logic flow control.

    In any case, it's another spin on it I think.
    16 Mar, 2010, Runter wrote in the 15th comment:
    Votes: 0
    I simply have exits which things can enter. For example:
    begin 
    ch.enter(exit)
    rescue ObjActionFail => FailMsg
    ch.print(FailMsg) ### Probably that the door is closed.
    end


    I don't find doors to be important enough to merit their own object. In fact, I don't make players open them. They just obstruct vision. If an exit has a door then the player automatically opens it upon enter. (Or by using the open command.)

    If the door is locked essentially the exit is locked and enter fails.
    16 Mar, 2010, flumpy wrote in the 16th comment:
    Votes: 0
    I am guessing, but is rescue is like catching some kind of exception?

    If so, in my language throwing exceptions is expensive so I tend to avoid it.

    Other than that, yea, there's no reason a standard exit couldn't be a door too but can never be closed (with an implicit "locked" property that is always unlocked) unless some other values are set. Each to their own :)
    16 Mar, 2010, Deimos wrote in the 17th comment:
    Votes: 0
    @Runter:
    In the chain of events required to cast a spell, the following happens: A user issues a command. This input string gets passed up your network structure until it hits whatever's responsible for turning that string into a game-understandable command/event/whatever. Let's assume this is handled by a CLI class. Let's say the string was "cast refresh", and the CLI has now determined that this string needs to cast refresh on the issuing PC. A couple things could happen at this point, which is what I'm trying to figure out:

    1) Our CLI calls "dude.cast(:refresh)" and washes its hands of any further processing. All processing is then under the control of the Dude class.
    2) Our CLI calls "world.spell_manager.cast(dude,:refresh)" and washes its hands of any further processing. All processing is then under the control of the SpellManager class.
    3) Our CLI handles the processing itself.

    Your code above appears to be using #3, but you seem to advocating #1 in your explanation. So, I guess it seems confusing to me. Where, exactly, would your code be located? If it's in the CLI, then you're subscribing to #3, but if it's in the Dude, then I'm not sure how you got from interpreting the command into the Dude class. Certainly you aren't letting Dude interpret its own user input strings?
    16 Mar, 2010, Runter wrote in the 18th comment:
    Votes: 0
    flumpy said:
    I am guessing, but is rescue is like catching some kind of exception?


    It's a specific exception I've defined to be associated with actions that can be taken by objects such as entering an exit.
    16 Mar, 2010, Runter wrote in the 19th comment:
    Votes: 0
    Quote
    Your code above appears to be using #3, but you seem to advocating #1 in your explanation. So, I guess it seems confusing to me. Where, exactly, would your code be located? If it's in the CLI, then you're subscribing to #3, but if it's in the Dude, then I'm not sure how you got from interpreting the command into the Dude class. Certainly you aren't letting Dude interpret its own user input strings?


    I'm simply advocating that
    World.do_something(character, args);

    is a bad direction to take it. If it looks a lot like
    do_something(character, "stuff");


    It should. I haven't made any other statement on the design. I think you're extrapolating something from my examples that were intended to be indicative of not knowing/(especially not caring) how you do your CLI or whatever you want to call it.
    16 Mar, 2010, flumpy wrote in the 20th comment:
    Votes: 0
    Runter said:
    flumpy said:
    I am guessing, but is rescue is like catching some kind of exception?


    It's a specific exception I've defined to be associated with actions that can be taken by objects such as entering an exit.


    Generally I would consider (in my own world, especially) an exception to be something, er, exceptional. Like if the exit was actually supposed to exist but somehow had been dereferenced for example. You know implicitly that an exit may not be able to be entered, so its not really an "exceptional" circumstance, you can handle it with logic, and probably should.

    I wouldn't advocate doing it that way, but as I say, each to their own :D
    0.0/163