11
\$\begingroup\$

I've been entertaining myself lately by programming a simple text-based adventure game, and I'm stuck on what seems like a very simple design issue.

To give a brief overview: the game is broken down into Room objects. Each Room has a list of Entity objects that are in that room. Each Entity has an event state, which is a simple string->boolean map, and an action list, which is a string->function map.

User input takes the form [action] [entity]. The Room uses the entity name to return the appropriate Entity object, which then uses the action name to find the correct function, and executes it.

To generate the room description, each Room object displays its own description string, then appends the description strings of every Entity. The Entity description may change based on its state ("The door is open", "The door is closed", "The door is locked", etc).

Here's the problem: using this method, the number of description and action functions I need to implement quickly gets out of hand. My starting room alone has about 20 functions between 5 entities.

I can combine all actions into a single function and if-else/switch through them, but that's still two functions per entity. I can also create specific Entity sub-classes for common/generic objects like doors and keys, but that only gets me so far.

EDIT 1: As requested, pseudo-code examples of these action functions.

string outsideDungeonBushesSearch(currentRoom, thisEntity, player)
    if thisEntity["is_searched"] then
        return "There was nothing more in the bushes."
    else
        thisEntity["is_searched"] := true
        currentRoom.setEntity("dungeonDoorKey")
        return "You found a key in the bushes."
    end if

string dungeonDoorKeyUse(currentRoom, thisEntity, player)
    if getEntity("outsideDungeonDoor")["is_locked"] then
        getEntity("outsideDungeonDoor")["is_locked"] := false
        return "You unlocked the door."
    else
        return "The door is already unlocked."
    end if

Description functions act in pretty much the same way, checking state and returning the appropriate string.

EDIT 2: Revised my question wording. Assume that there may be a significant number of in-game objects that don't share common behavior (state-based responses to specific actions) with other objects. Is there a way I can define these unique behaviors in a cleaner, more maintainable way than writing a custom function for each entity-specific action?

\$\endgroup\$
2
  • 1
    \$\begingroup\$ I think you need to explain what these "action functions" do and maybe post some code, because I'm not sure what you're talking about there. \$\endgroup\$
    – jhocking
    Commented Jan 31, 2012 at 1:51
  • \$\begingroup\$ Added the code. \$\endgroup\$
    – Eric
    Commented Jan 31, 2012 at 2:18

2 Answers 2

5
\$\begingroup\$

Rather than making a separate function for every combination of nouns and verbs, you should setup an architecture where there is one common interface that all the objects in the game implement.

One approach off the top of my head would be to define an Entity object that all the specific objects in your game extend. Each Entity will have a table (whatever data structure your language uses for associative arrays) that associates different actions with different results. The actions in the table will likely be Strings (eg. "open") while the associated result could even be a private function in the object if your language supports first-class functions.

Similarly, the state of the object is stored in various fields of the object. So for example, you can have an array of stuff in a Bush, and then the function associated with "search" will act on that array, either returning the object found or the string "There was nothing more in the bushes."

Meanwhile, one of the public methods is something like Entity.actOn(String action) Then in that method compare the action passed in with the table of actions for that object; if that action is in the table then return the result.

Now all the different functions needed for each object will be contained within the object, making it easy to repeat that object in other rooms (eg. instantiate the Door object in every room that has a door)

Finally, define all the rooms in XML or JSON or whatever so that you can have lots of unique rooms without needing to write separate code for every single room. Load this data file when the game starts, and parse the data to instantiate the objects that populate your game. Something like:

<rooms>
  <room id="room1">
    <description>Outside the dungeon you see some bushes and a heavy door over the entrance.</description>
    <entities>
      <bush>
        <description>The bushes are thick and leafy.</description>
        <contains>
          <key />
        </contains>
      </bush>
      <door connection="room2" isLocked="true">
        <description>It's an oak door with stout iron clasps.</description>
      </door>
    </entities>
  </room>

  <room id="room2">
    etc.

ADDITION: aha, I just read FxIII's answer and this bit near the end jumped out at me:

(no things like <item triggerFlamesOnPicking="true"> that you will use just once)

Although I disagree that a flame trap being triggered is something that would only happen once (I could see this trap being reused for many different objects) I think I finally get what you meant about entities that react uniquely to user input. I would probably address stuff like making one door in your dungeon have a fireball trap by building all my entities with a component architecture (explained in detail elsewhere).

This way each Door entity is constructed as a bundle of components, and I can flexibly mix-and-match components between different entities. For example, the majority of the doors would have configurations something like

<entity name="door">
  <description>It's an oak door with stout iron clasps.</description>
  <components>
    <lock isLocked="true" />
    <portal connection="room2" />
  </components>
</entity>

but the one door with a fireball trap would be

<entity name="door">
  <description>There are strange runes etched into the wood.</description>
  <components>
    <lock isLocked="true" />
    <portal connection="room7" />
    <fireballTrap />
  </components>
</entity>

and then the only unique code I would have to write for that door is the FireballTrap component. It would use the same Lock and Portal components as all the other doors, and if I later decided to use the FireballTrap on a treasure chest or something that's as simple as adding the FireballTrap component to that chest.

Whether or not you define all the components in the compiled code or in a separate scripting language isn't a big distinction in my mind (either way you are gonna be writing the code somewhere) but the important thing is that you can significantly reduce the amount of unique code you need to write. Heck, if you aren't concerned about flexibility for level designers/modders (you are writing this game by yourself after all) you could even make all the entities inherit from Entity and add components in the constructor rather than a config file or script or whatever:

Door extends Entity {
  public Door() {
    addComponent(new LockComponent());
    addComponent(new PortalComponent());
  }
}

TrappedDoor extends Entity {
  public TrappedDoor() {
    addComponent(new LockComponent());
    addComponent(new PortalComponent());
    addComponent(new FireballTrap());
  }
}
\$\endgroup\$
5
  • 1
    \$\begingroup\$ That works for common, repeatable items. But what about entities that respond uniquely to user input? Creating a subclass of Entity just for a single object groups the code together but doesn't reduce the amount of code I have to write. Or is that an unavoidable pitfall in this regard? \$\endgroup\$
    – Eric
    Commented Jan 31, 2012 at 3:09
  • 1
    \$\begingroup\$ I addressed the examples you gave. I can't read your mind; what objects and inputs do you want to have? \$\endgroup\$
    – jhocking
    Commented Jan 31, 2012 at 3:22
  • \$\begingroup\$ Edited my post to better explain my intentions. If I understand your example correctly, it looks like each entity tag corresponds to some subclass of Entity and the attributes define its initial state. I'm guessing child tags of the entity act as parameters to whatever action that tag is associated with, right? \$\endgroup\$
    – Eric
    Commented Jan 31, 2012 at 4:26
  • \$\begingroup\$ yep, that's the idea. \$\endgroup\$
    – jhocking
    Commented Jan 31, 2012 at 12:50
  • \$\begingroup\$ I should have figured that components would have been part of the solution. Thanks for the help. \$\endgroup\$
    – Eric
    Commented Feb 1, 2012 at 23:49
1
\$\begingroup\$

The dimensional problem you address is quite normal and nearly unavoidable. You want to find a way to express your entities that is both coincise and flexible.

A "container" (the bush in the jhocking answer) is a coincise way but you see that is not flexible enough.

I do not suggest you to try to find a generic interface and then use configuration files to specify the behaviours, because you will always have the unpleasant sensation to be between a rock(standard and boring entities, easy to describe) and a hard place (unique fantastic entities but too long to implement).

My suggestion is to use an interpreted language to code behaviours.

Think about the bush example: it is a container but our bush need to have specific items within; the container object may have:

  • a method for the storyteller to add item,
  • a method for the engine to show the item it contains,
  • a method for the player to pick an item.

One of theese items has a rope that trigger a contraption that in turn fires a flame burning the bush... (you see, I can read your mind so I know the stuffs you like).

You can use a script to describe this bush instead of a config file putting the relevant extra code in a hook you execute from your main program each time someone picks an item from a container.

Now you have a lot of architecture choices: you can define behavioral tools as base classes using your code language or the scripting language (things such containers, door-like and so on). The purpose of theese things is to let you to describe the entities easely aggregating simple behaviours and configuring them using bindings on a scripting language.

All the entities should be accessible to the script: you may associate an identifier to each entity and put them in a container that is exported in the extend of the scripting language script.

Using scripting strategies let you to keep your configuration simple (no things like <item triggerFlamesOnPicking="true"> that you will use just once) while letting you to express odd beaviours(the fun ones) adding some line of code

In few words: scripts as config file that can run code.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .