23 Jul, 2014, jakesmokes wrote in the 1st comment:
Votes: 0
Morning,

I ran into an interesting question last night and was wondering what others had done to address. Currently, I am dealing with containment in what seems to be the popular way: Players have inventory, locations have inventory, containers have inventory. Inventories are lists of objects.

I may be making a bigger deal about this than I should be but.. what about situations where you can have an object on something as well as in it? Or under it? Like, a desk. A desk can have things on top of it and inside of it. A bed could have something on it and under it. Currently, I have the notion of containment behavior which can be attached to any object. If it has the behavior it can contain stuff (pack, pouch, chest, bag, etc). So to deal with this issue I am contemplating two approaches:

1. Add behaviors for prepositional classes. On top. under. Behind. Etc. Each such behavior would have an inventory. Which could be kind of a pain and lead to searching multiple lists to process certain commands.

2. Reconstruct my notion of containment to include type. So instead of a list of objects there would be a tag indicating, for each object (in an inventory) its relationship to its container. So a desk would have a list of things, some of which were on top of it and some that were inside of it (or underneath or behind).

Or, perhaps, I should just not care? I think the only reason I am thinking about caring is because it would add detail to the environment. So if the user says "Take everything" then they might miss something under the bed. Or, there might be a scenario where putting something "on the table" would trigger an action from an NPC. Rather than just saying "drop item".

As a side note, this is leading me to deal with another issue where behaviors on objects need to know about each other. If I have a container, lock, and open/close behavior then the container needs to know if it is opened before items can be retrieved. Similarly, you can't open a container if its locked. I feel like I am close to a reasonably elegant solution. Just not quite there yet. As always, use cases are necessarily triggering changes to the architecture.

As always, thanks for any input.

David
23 Jul, 2014, Rarva.Riendf wrote in the 2nd comment:
Votes: 0
> So if the user says "Take everything" then they might miss something under the bed.

If they did not see it, why should they get it in the first place.
23 Jul, 2014, jakesmokes wrote in the 3rd comment:
Votes: 0
Hi Rarva,

Answer is: They shouldn't. Unless they specifically qualify their search. The example you mention is just one that I am considering. Since certain objects might be "present" in the location, but not directly accessible (hidden, under something else) then I would need a way to determine that while searching through inventory lists. Its just one example of the quandary I propose. It may have been a bad example. The general point is that the relationships of contained objects to their container could have an impact on the surroundings and I am trying to think of the best way to represent that internally so that I can dress locations and model behaviors that I am trying to model. I have a meeting in 2 minutes. I'll try to think of a better use case :-)

David
23 Jul, 2014, quixadhal wrote in the 4th comment:
Votes: 0
LPMUD treats all objects alike, in terms of containment. All items, npcs, rooms, etc have both an inventory and an environment. Usually, rooms have an empty environment, since they aren't normally contained by other things. Likewise, solid objects like swords usually have empty inventories.

This makes it very easy to walk around the relationship. If you have a coin inside a bag inside your backpack on your person, and you want to know the room the coin is in, you can just call environment() until the thing you get back is a room. If you wanted to know what other things are in the bag with the coin, that's just all_inventory(environment(coin)).

As for hidden objects, the simplest way would probably be to have a variable called "hidden" which holds a list of places in which the object is hidden. Adjust your look/get/etc to filter out things which have a non-empty hidden value unless the user is using the right phrase. So, if hidden = [ 'under bed' ], you wouldn't find it unless you specified "look under bed" or "get cat toy under bed".

You could implement both active and passive search functionality, so if you "search", you have a chance to be told about hidden things, and likewise if your race/class/skills give you the option to find hidden things easier, it might print such messages in a normal look.
23 Jul, 2014, jakesmokes wrote in the 5th comment:
Votes: 0
quixadhal said:
LPMUD treats all objects alike, in terms of containment. All items, npcs, rooms, etc have both an inventory and an environment. Usually, rooms have an empty environment, since they aren't normally contained by other things. Likewise, solid objects like swords usually have empty inventories.

This makes it very easy to walk around the relationship. If you have a coin inside a bag inside your backpack on your person, and you want to know the room the coin is in, you can just call environment() until the thing you get back is a room. If you wanted to know what other things are in the bag with the coin, that's just all_inventory(environment(coin)).


I've thought about doing that too. Everything has an inventory list. Rather than make the possession of an inventory be dependent on the class of the item (i.e container). Its just an extra pointer, I realize. I guess the thing that stopped me from doing that was dealing with attributes of containers. Containers can be closed and locked. So taking an item from a container would need to respect that so then every object has to have an inventory list and couple of booleans gating access to the items within.

It also doesn't solve the problem of tracking relationships of items. Under, on, behind, etc.

On the "environment" call you were describing above. That's just a reference to the object's container right? So every item is in an inventory list and it also has a pointer to its parent?

Thanks,

David
23 Jul, 2014, quixadhal wrote in the 6th comment:
Votes: 0
openable or lockable are just behaviors. In an LPMUD, you'd inherit them. In a hard-coded environment, you'd just make them properties. Dikurivatives have a lot of artifical seperation of objects that actually share more things in common than they have different. If you don't want the "open" command to work on players or rooms, just code it to only work on objects. If you don't want to special-case for different kinds of objects (IE: containers vs. normal), just set an "openable" flag to true or false.

It takes work, but one of the projects I'm working on now is gradually moving in that direction. Converting ROM to Python, we're finding that as we refactor things for simplicity, a lot of those divisions are collapsing, and carrying a few extra (sometimes) unused variables around is a small price to pay for having things be easily interchangable.
24 Jul, 2014, Idealiad wrote in the 7th comment:
Votes: 0
@jakesmokes, regarding your OP I think #2 is the wisest choice. Redesign containment so that it (x is in y) is simply a relation of y encloses x. Then you can define other relations, like x is on y, or even x is near y, or what have you.

Otherwise you run into some silly constraints. For example the Inform text adventure language has containers, and supporters, but an object (like a desk) can't be both. You have to model the desk as parts, so the desk has drawers which are containers, and a top which is a surface. Or consider an open bookshelf, which could be considered both a container and a supporter – put the book in the bookshelf, put the book on the shelf. Both would make sense.
24 Jul, 2014, plamzi wrote in the 8th comment:
Votes: 0
A tag on each entry in the inventory list holding a preposition is clearly the better approach.

Regarding closeable and lockable, these can be nested properties inside the "container" property. For maximum flexibility, each container item can have its own set of container rules and each rule/tag can have its own closeable and lockable flags. That way, you can have:

item1 = {
name: 'chest'
contains: [
"inside": [ 'closeable', 'lockable', 'locked' ],
"behind": [ 'searchable' ],
"on top of": []
]
}

item2 = {
name: 'curtain'
contains: [
"behind": [ 'searchable', 'closeable', 'closed' ]
]
}


Based on this info, you would be checking for lock state when the player attempts to get an item which is inside a chest, but if it's behind a chest, you can run some visibility checks instead. And if it's "on top of", no checks will be performed. Further, players would not be able to close the 'behind' location of a chest, but they will be able to close the "behind" of a curtain.
24 Jul, 2014, jakesmokes wrote in the 9th comment:
Votes: 0
quixadhal said:
openable or lockable are just behaviors. In an LPMUD, you'd inherit them. In a hard-coded environment, you'd just make them properties. Dikurivatives have a lot of artifical seperation of objects that actually share more things in common than they have different. If you don't want the "open" command to work on players or rooms, just code it to only work on objects. If you don't want to special-case for different kinds of objects (IE: containers vs. normal), just set an "openable" flag to true or false.

It takes work, but one of the projects I'm working on now is gradually moving in that direction. Converting ROM to Python, we're finding that as we refactor things for simplicity, a lot of those divisions are collapsing, and carrying a few extra (sometimes) unused variables around is a small price to pay for having things be easily interchangable.


Agree on the fact that open / lockable are behaviors. That's how I currently have it set up. Anything can be anything if it has the right behaviors. A sword could be a door if it responds to the right action (by adding a behavior to it). Objects themselves are very small and don't have a lot of data other than things that are specific to "thing": location, description, etc. My question relating to your response was just that if I break that and put containment as a field for every object then the code would still have to ask an object's behavior if it were a container before it would be allowed to use it as one. So, in that case, why not just make the inventory a list a behavior too. I do have a "protocol" for asking every object about certain aspects of what it can do and those protocol methods ask their behavior lists and respond. For example: the parsers asks the object if it responds to "takable" before it tries to take it.


Idealiad said:
@jakesmokes, regarding your OP I think #2 is the wisest choice. Redesign containment so that it (x is in y) is simply a relation of y encloses x. Then you can define other relations, like x is on y, or even x is near y, or what have you.


I am leaning in this direction. But its different than how I am used to thinking about things so I am trying to think it through before I make the change. I tend to think about things in terms of the parser that I have written. When someone types "Put the ruby ring in the leather pack on the wooden table" I get a noun and two noun modifiers (ring, [in]pack, [on]table). So its really easy to create states / behaviors that map to semantic tokens that I get from the parse: on / in.

So I guess the question is (and its just an implementation detail which I should be figuring out myself, probably rather than bugging you all).. would it be better to

1. Have one container list with an embedded type of containment? So table has: ring:on top, box:underneath, wand:on top.

2. Create behaviors for: inside:of, on top:of, underneath. Such that.. if a user "places the ring under the bed" it would go into the "under" inventory list. A bag would have an "inside" inventory list. "take from" would consider all inventory lists (coalesced).

#2 feels nice and ties into the parser really well. If the user asks to take the ring under the table, he'll get an error saying that its actually on top of the table. But performance might be of concern. Maybe. In some sense it does less work. "Take everything under the table" would completely ignore the "on:top" inventory list. But there would be some iterating / dispatching to get to the right list.

#1 is probably more performant even though I would have to dereference a structure to get to an object in an inventory list. So instead of a straight list it would be a list of blobs that have a type "under, on, in, beside" and then the actual object pointer. Or I could even just add a prepositional locative behavior to objects and just ask it for that as I am considering the inventory list rather than dereferencing. That might be the solution.

I can't believe all of thousands of lines of code I have written so far, and I am getting hung up on this silly problem.

Thanks to everyone for the time,

David
24 Jul, 2014, plamzi wrote in the 10th comment:
Votes: 0
jakesmokes said:
If the user asks to take the ring under the table, he'll get an error saying that its actually on top of the table.


II don't see why you wouldn't be able to return this error message in the case where you have one inventory and each item has an "inventory position" tag.

If anything, your example highlights why the single list option is better. You will only need to traverse one list, and when you find the ring, you check if the position the user requested matches the position of the ring. If it doesn't match, you tell them where it actually is.

If you have a separate list for each containment space within the table, you would have to traverse them all to find a ring. The only thing that is made *slightly* easier by having separate lists is matching object handles within a specific containment space. As in, you want "take 2.ring inside" to match the second ring in the "inside" list. But even then, you can easily get that behavior out of the single list implementation by just adding a position check during your one loop.

The real benefit to having separate lists is a slight speed boost *if* you never bother to traverse all lists belonging to an object. In my mind, that is a very tiny gain, completely overshadowed by the amount of red tape you'll be adding.

I believe we've already touched upon this subject in an earlier thread, but as a general rule, spawning very similar but slightly different lists should be done for very good reasons. Otherwise, you are always better off having to touch fewer places when a state changes.
24 Jul, 2014, quixadhal wrote in the 11th comment:
Votes: 0
Especially since almost every place you use the word "list", you can easily substitute "hash table" or "binary tree". A raw list is really a horrible data structure unless the order matters, in which case in any modern language I'd use an array, but in C I might still use lists to avoid the annoyance of having to do all the realloc()/memcpy() nonsense, just to remove an item from the middle.
24 Jul, 2014, jakesmokes wrote in the 12th comment:
Votes: 0
plamzi said:
A tag on each entry in the inventory list holding a preposition is clearly the better approach.
.


Thanks PlaMzi :-). That's basically what I am doing now except I am not nesting them. Every object has attributes / behaviors / actions that can be attached to them and are checked before certain actions are allowed. I was just unnecessarily concerned about keeping state about the inventory in the object and the lockable/locked/open/openable stuff in the behavior chain.

Ultimately, I suppose the implementation detail isn't super important. So, in about 5 minutes I reorganized the object hierarchy so that everything is an object (before locations weren't) and moved inventory higher up in the inheritance (based on a suggestion from quixadhal). I've left the inventory state in the object for now. I may add it as a behavior later if I feel the urge. I am also probably going to add the location relationships as states as well. So far nothing has been anything short of blindingly fast.. but its early yet in the process :-).


quixadhal said:
Especially since almost every place you use the word "list", you can easily substitute "hash table" or "binary tree". A raw list is really a horrible data structure unless the order matters, in which case in any modern language I'd use an array, but in C I might still use lists to avoid the annoyance of having to do all the realloc()/memcpy() nonsense, just to remove an item from the middle.


I totally agree. After reading your comments, smoking a cigar, and reflecting.. I have come to the conclusion I was overthinking. Your suggestion of which alternative to use definitely feels cleaner.

David
24 Jul, 2014, jakesmokes wrote in the 13th comment:
Votes: 0
quixadhal said:
Especially since almost every place you use the word "list", you can easily substitute "hash table" or "binary tree". A raw list is really a horrible data structure unless the order matters, in which case in any modern language I'd use an array, but in C I might still use lists to avoid the annoyance of having to do all the realloc()/memcpy() nonsense, just to remove an item from the middle.


I see your point, I believe. Are you drawing a distinction between a list an array here when you say the above? I do tend to lean towards hash tables when it seems appropriate. But there are, of course, many times when you need to iterate all of the contents of a structure. In those cases a list / array is more efficient since you aren't performing a look up, right? Sometimes I find myself groping a NSDictionary for its keys and then iterating over them and fetching the value for each key which just feels dirty. But there is no other way (of which I am aware) to iterate over the contents of a dictionary. You can use an enumerator to iterate over the keys of an NSDictionary but you still have to fetch the value.

Thanks for the good discussion. It helps to talk about this stuff. And there are precious few who you can talk to about things mud related :)

David
24 Jul, 2014, quixadhal wrote in the 14th comment:
Votes: 0
Not sure what language you're using, but in perl and python, the cost of iterating over a dictionary/hash isn't too bad.

Essentially, if you do for k,v in dict_object, you're plucking the key and value out at the same time, which is optimized in that you don't have to do a seperate lookup for the value. Or course that only works well if you don't care about the order items are retrieved in.

If order matters, a list (array) is the way to go.

I was mostly referring to the fact that in C, an array is a block of memory that's indexed by fixed-size steps, whereas a linked list is a structure that has next and (sometimes) previous pointers in each node. A linked list is much easier to work with when you need to insert or delete items frequently, as the array has to be (basically) copied over into a new memory region to be changed in any way that will change its size.
25 Jul, 2014, Rarva.Riendf wrote in the 15th comment:
Votes: 0
quixadhal said:
If order matters, a list (array) is the way to go.


SortedMap (in Java at least and probably in other langages)
Array is really when you will always parse the whole list in all situationq other than that, as you say, you are better off with something else…
12 Aug, 2014, jakesmokes wrote in the 16th comment:
Votes: 0
quixadhal said:
Not sure what language you're using, but in perl and python, the cost of iterating over a dictionary/hash isn't too bad.

Essentially, if you do for k,v in dict_object, you're plucking the key and value out at the same time, which is optimized in that you don't have to do a seperate lookup for the value. Or course that only works well if you don't care about the order items are retrieved in.

If order matters, a list (array) is the way to go.

I was mostly referring to the fact that in C, an array is a block of memory that's indexed by fixed-size steps, whereas a linked list is a structure that has next and (sometimes) previous pointers in each node. A linked list is much easier to work with when you need to insert or delete items frequently, as the array has to be (basically) copied over into a new memory region to be changed in any way that will change its size.


Well, in my experience, iterating over a dict in ObjC requires that you iterate over the keys and still do a lookup. Which can still be pretty fast depending on what you are using for a key.

David
0.0/16