5

I'm designing a multiplayer game server where the players are in rooms. Right now I have classes to take care of the client, networking, etc. I'm struggling however to find a design for this Room class. For example, when I want to change a client's room, should I call client.changeRoom(newRoom) or newRoom.addClient(client)? What about when leaving a room, should a client detach itself from the room or the room detach the client? I'm very confuse here, I just don't want to end up with a design I will regret in the future.

  • I'm interested in the ObjectA.function(ObjectB) vs ObjectB.function(ObjectA) concept, but can't think of anything intelligent to advise on that point at the moment. But I would note that, as with programmers.stackexchange.com/questions/156221/…, what makes sense for object oriented enterprise software might not necessarily make sense for game server code design implementation. – BrianH Jan 2 '14 at 18:31
  • In these cases answering 2 questions will usually provide the answer. Does the client need to talk to the room? Does the room need to talk to the client? Usually 1 or the other makes better sense. In your case, I would suspect (but don't know your use cases) that the client doesn't particularly care that it is contained in a room and needs to interact with the room. The client probably needs to interact with other clients in the room instead. In which case, the clients shouldn't know anything about a room class. The room class should provide the connections between clients. – Dunk Jan 2 '14 at 19:31
7

With flashbacks to my LPmud days, the answer is the client (player, object, ...) is moved from one room to another and notifies the previous room (and everything else in the room) of this fact.

Upon entering a new room, the client again notifies all things in the room of its entry.

Not all rooms (or things in rooms) care about this fact and can quietly ignore this message.

The way the LPmud design works is that the room is "just" a special type of object with connivence functions for linking it to other rooms.

The design of the room doing the moving of a player from one location to another means quite a few more lookups.

Note: I'm using getRoom() here as an example function. The method might have been getEnvironment() which returns the container an object is in... and all objects had a container (an coin in a bag in a player inventory coin-> getEnvironment() would get the bag and coin-> getEnvironment()-> getEnvironment() would get the player, and coin-> getEnvironment()-> getEnvironment()-> getEnvironment() would fetch the room...)

The player would need to do a me->getRoom()->moveMeTo(newRoom); to move around. This gets a bit cumbersome and sometimes, when things go wrong, you don't have room that you are in which would make it impossible to move to another room.

The thing with the 'intelligence', the thing doing the acting is the player, and thus that should be the thing doing the acting in the room.

me->getRoom()->notify_all(me,leaving);
me->moveRoom(newRoom);
me->getRoom()->notify_all(me,arriving);

Before you point out that I said me->getRoom()->moveMeTo(newRoom); was bad above and yet use me->getRoom()->notify_all(...) above, the difference is that in the case that the client is not anywhere, the getRoom() would return a null object that ignores all messages and so nothing goes wrong there. However, in the moveMeTo approach, the null object would ignore all messages, and you wouldn't get anywhere.

As an aside, you might want to download an LP mudlib to look at it and consider how the various components are architected. A lot of design thoughts have been put into it over the years that probably represent many programmer-decades of refinement.

The locations of hooks, connivence functions, and structure, when looked at can make some of your own decisions easier to come by, understand, and reconsider.

  • I think the answer is simple: the client notifies rooms of its actions - as MichaelT said. The reason I think it's simple is because you have an abstraction of the real world. When I leave a room I, as the actor or agent, and doing something. The room isn't doing anything. Therefore the method, which is really just a strangely-named verb, should be attached to me. – Seth Jan 2 '14 at 19:31
5

When you look at it at the top level, it could go either way, with neither solution theoretically better than the other. You already know that, or you wouldn't ask the question. This dilemma is fairly common in object-oriented programming. The trick is to look one level of abstraction deeper. The implementation is almost always much cleaner one way compared to the other. When in doubt, try implementing it both ways and see which one works out better.

For example, say you choose client.changeRoom(newRoom). If that's the wrong choice, the method will end up consisting mainly of calls on the newRoom object, like:

newRoom.populateFurniture()
newRoom.populateNPCs()
newRoom.attachChat()
newRoom.notifyPlayers()
...

This often results in wanting to make too many Room methods public, which increases coupling. Not being familiar with your architecture, and never having created something similar, I don't know which way it will go for you, although adds generally end up working better on the container object. However, in my experience, the choice has always been blindingly obvious after trying it both ways.

Also, don't discount the possibility that the best method may be a combination of both, like:

Client::changeRoom(Room newRoom) {
    room.removeClient(this)
    newRoom.addClient(this)
    room = newRoom
}
  • +1 for "When in doubt, try implementing it both ways and see which one works out better." As I read your answer, it prompted the thought of abstracting the presence of a person in the room. A Presence object, with presence.setRoom() and presence.addPlayer(). Then Player has no notion of a room, nor room of a player. – Don Branson Jan 2 '14 at 19:31

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.