开发者

Coding interactions in a text adventure

开发者 https://www.devze.com 2023-01-18 06:23 出处:网络
EDIT: If you can\'t be bothered to read this mammoth question, I\'ve put a summary at the bottom. I\'m currently working on a sort of \"framework\" for a text adventure I am going to make in C#, as a

EDIT: If you can't be bothered to read this mammoth question, I've put a summary at the bottom.

I'm currently working on a sort of "framework" for a text adventure I am going to make in C#, as a coding excercise. In this framework, possible actions are defined by an "Interaction" class.

The potential "Actionable" objects are Inventory Items (stick, gun, sword), Environmental Items (wall, door, window) and Characters (people, animals). Each of these has a property which is a List of Interactions. At the moment, an Interaction is basically an "action/response" name value pair. When you type "smash window", it looks through all possible actionable items that the Player has available and matches the subject (in this case, "Window"). It then works out that the action is "Smash" and looks up in the List of Interactions on the Window (Environmental Item) to get a response for the Smash action and then writes it to the console.

That is all done, but here is the point that I am stuck:

An action has any number of potential consequences, which differs by each potential interaction. These are:

- Returns a response describing the result of the action by looking it up on the interaction, possibly with a second subject

EITHER - The subject of the action (inventory item, environmental item or character) changes it's description EG. "punch wall" could change the wall's description to describe a dent in the wall OR - The subject of the action is replaced by another item EG. "smash bottle" results in "bottle" changing to "broken bottle" or "kill John" results in the character John being replaced by environmental item "John's corpse".

- Returns a response describing the preceeding change EG. "The broken pieces of the bottle are scattered across the floor."

- An area's description is changed. EG. "smash lightbulb" results in the room's description changing to describe a pitch black room

- Items are added/removed from the inventory or the environment EG. "pick up bottle". You now have a bottle in your inventory, and the bottle is removed from the environment.

- The directions available for movement and the areas which they lead to are changed EG. "unlock door with key" allows you to move East into another room

- The player is moved to a new area EG. "go north" takes you to another area.

I need to somehow determine in a generic way which of these consequences a particular Interaction should invoke, and invoke them. An action could potentially use a number of these consequences, or just one.

For example, if the item is a Bottle:

"fill bottle with water" would first return a response describing that you have filled the bottle with water. It would then replace the "bottle" item with a "bottle of water" item. That is two consequences, returning a response and replacing an item.

Say you were then to do "throw bottle of water at window". This is more complex. It would first return a response describing the events that take place, the bottle and the window would both smash and water would go everywhere. The bottle would be removed from the Player's inventory. Next, the "bottle of water" would be replaced by the "broken bottle" and the "Window" would be replaced with "Broken window". The area description would also change to reflect this. That is five consequences, returning a response, removing an item from inventory, replacing two items and updating the description of the current area.

As you can see, I need a generic way of being able to define on a per "Interaction" basis, what the consequences of that action will be and update other objects such as Item, Player (for inventory) and Area appropriately.

I'm sorry if this is unclear, and I will do my best to clarify if anyone has any questions.

EDIT: Is there a way for me to define a method on an Interaction that I can pass a number of methods to call (and their parameters) into? The initial response returned would be the default, mandatory consequence, and then there could be extra ones if spec开发者_Python百科ified.

For example, in the examples above, for the first interaction, "fill with water", I would tell it to return a response ("You have filled the bottle with water") and also to call a ReplaceItem method that would replace the "bottle" subject with a "bottle of water".

For the second interaction I would tell it to return a response ("The bottle hurtles through the air into..."), call RemoveFromInventory on the subject of the action, call UpdateStatus on the bottle ("the bottle is smashed") and the window ("the window is smashed") and call UpdateAreaDescription to change the current area's description ("You are standing in a room with a single window, the glass smashed to pieces").

Does that sound feasible? I'm trying to keep this as generic as possible, for the sake of all the different possible interactions.

EDIT 2: To clarify further, and to attempt to summarize the problem:

In my game, there are Actionable objects (a bottle, a wall, John). Each Actionable object has a list of Interaction objects which describe how a player can interact with them. At the moment, an Interaction has a "Name" property ("throw", "hit", "break") and returns a Response ("You throw the ").

The issue that I am trying to resolve is that an Interaction also needs to do a number of other things, varying by each particular Interaction. Let's take the example of a glass bottle.

"throw glass bottle"

- A response is returned ("You threw the glass bottle.")

- The "Bottle", is removed from the Player's inventory.

- The is replaced with a new to reflect the change. ("Bottle" replaced with "Broken bottle").

- A second response is returned ("The pieces of the glass bottle are scattered on the floor").

"throw glass bottle at window"

- A response is returned ("You threw the glass bottle at the window.")

- The object "Bottle", is removed from the Player's inventory.

- The object is replaced with a new object to reflect the change. ("Bottle" replaced with "Broken bottle").

- A second, optional object is replaced with a new to reflect the change. ("Window" replaced with "Broken window").

- The "Description" property of the current Area is updated. ("You are standing in a room, with a single broken window.").

When I create the Interactions, how can I vary the additional actions that they perform, such as status changes to the subject, or changes to the current Area's description?

If you need more examples of actions as above, let me know and I'll do a few more.


I think you should decide on a set number of verbs you will recognize, and then for each object decide which of those verbs it is capable of responding to.

Lock Object Recognized Verbs

  • Look
  • UseItemOn(Key001, LockPicks, Sledgehammer, ...)
  • Punch

That way you can generically handle verbs it doesn't recognize with a response like "You can't <verb> the <object>, and handle verbs it does recognize with events or whatever.

Edit

As per your comment I obviously just scanned your question (too long for me). Still, I don't see the difference, really. The point is, an object participates in an event. From the Bottle's perspective, it gets hit by a wall. From the Wall's perspective, it gets hit by a Bottle. Both objects will have a list of verbs to which they will respond in a certain way.

So if you plan for the wall to be responsive to ANY thrown object, then you'll need to add a Collide verb to its list. You'll want to specify which objects it should care about colliding with, and maybe for each of those, how it should respond to particular magnitudes of force, etc.

But the principle is the same. For any event there are a number of participants, and each participant will have certain stimuli it cares about, and for those stimuli it will have certain stimulus origin objects it cares about. If it's a verb it cares about but its origin is not an object it cares about, then it will effectively ignore it - or respond in some vanilla fashion.

The Bottle participates in a Collision with the Wall. The Bottle has in its Verbs list the Collide interaction type. It may have a single object with which it cares about colliding, or it may have a value of Any, or AnySolid, or whatever. There's a million ways to architect that. In any case, the Wall also participates and may also have in its Verbs list the Collide interaction type. But it only cares about colliding with the Sledgehammer object - or maybe AnySolid that has a Mass of 10 or greater...

You could also do this with interfaces. You can have an LootableObject that implements ICollidible interface, or whatever. When any ICollidible (say, a bottle) executes its Collide method it will need certain parameters: how fragile it is, how much force it received, whether the colliding object is something it cares about, etc.

It may be full of liquid so it would implement an IContainer interface which has a Spill method, and also an IConsumeable interface which has a Drink method. It may be a lock which implements an ILockable interface which has an Unlock(obj Key) method and a Pick(int PickSkill) method. Each of these methods can produce certain changes in state to the object and the other particpant(s) in the interaction. You can do this with Events if you like.

Basically you need to decide what level of (un)predictability you want and then compose a matrix of interactions (not necessarily physics, but any kind of interaction you plan to operate on - a lockpicking event, a collision event, a drinking event) that involve certain predictable properties.


All actions you have described consist of the following:

  • A verb (for example "throw")
  • an object (for example "bottle")
  • an optional additional parameter describing the action further (for example "at window")

How about modelling each actionable object as a class derived from a common ancestor and have that class handle the action itself. Something like

public interface IObjectBase
{
   bool HandleAction(string verb,string [] params)
}

public class Bottle: IObjectBase
{
   bool HandleAction(string verb,string [] params)
   {
     //analyze verb and params to look for appropriate actions
     //handle action and return true if a match has been found
   }
}


You've got two things: the player and the environment (you might also have other players).

Pass them both to each interaction:

interaction.ActOn(environment, player);

//eg:
smash.ActOn(currentRoom, hero);

Then let each interaction work out what to do:

environment.ReplaceObject("window", new Window("This window is broken. Watch out for the glass!");
player.Inventory.RemoveObject("bottle");
player.Hears("The window smashes. There is glass all over the floor! If only John McLane were here...").

With the usual checks to make sure that the environment actually has a window, the player has the bottle, etc.

player.Inventory.ReplaceObject("bottle", new BottleOfWater());

Interaction then becomes a common interface which you can attach to anything in the system, be it an environment, player, bottle, etc. You can probably work out some particular types of interaction which you can use to remove duplication, but I'd start simple and go from there.

See also Double Dispatch.


Hah, I'm working on something similar too. I'm wondering if your framework ends up becoming a text-adventure creator which is what my project is.

My approach is to have a sort of API that consists of methods that represent all the most basic actions in the game. Then use 'scripts', which are basically methods containing a combination of these basic actions. These basic actions may involve:

  • Print a message
  • Change an object's/room's description
  • "Lock" or "unlock" an object. This means that "examine belt" will say "You don't seen any belt here" UNTIL "examine corpse" has been performed to learn that "The corpse has a shiny belt around its waist".
  • Lock or unlock exits from a room
  • Move the player to some room
  • Add/Remove something from the player's inventory
  • Set/Change some game variable eg. "movedGlowingRock = true" or "numBedroomVisits = 13" etc.

and so on... This is what I currently have in mind. These are all methods in maybe an API class and take various parameters as necessary.

Now, there are rooms. Rooms have objects. Certain commands are valid for each object. One simple way is to have each room object hold a Dictionary of allowed commands. Script is a delegate that points to your action script. Ponder this:

delegate void Script();

class GameObject
{
    public Dictionary<string, Script> Scripts {get; set;}
    public string Name {get; set;}

    //etc...
}

And your scripts, stored in the relevant Room instance:

    //In my project, I plan to have such an abstract class, and since it is a game _creator_, the app will generate a C# file that contains derived types containing info that users will specify using a GUI Editor.
abstract class Room
{
    protected Dictionary<string, GameObject> objects;

    public GameObject GetObject(string objName) {...//get relevant object from dictionary}
}


class FrontYard : Room
{

    public FrontYard()
    {
        GameObject bottle;
        bottle.Name = "Bottle";
        bottle.Scripts["FillWithWater"] = Room1_Fill_Bottle_With_Water;
        bottle.Scripts["ThrowAtWindow"] = Room1_Throw_Bottle_At_Window;
        //etc...
    }

    void void Room1_Fill_Bottle_With_Water()
    {
         API.Print("You fill the bottle with water from the pond");
         API.SetVar("bottleFull", "true");         
    }

    void Room1_Throw_Bottle_At_Window()
    {
         API.Print("With all your might, you hurl the bottle at the house's window");
         API.RemoveFromInventory("bottle");
         API.UnlockExit("north");
         API.SetVar("windowBroken", "true");    
         //etc...     
    }    
}

All this is sort of a skeleton view of what I have in mind (there are many subtleties I have noted, but this is good enough for an example). Sadly I haven't even coded a single word for my project, hehe. Everything on paper.

So...all this might give you some ideas to tinker with for your own project. If something is unclear, ask. Hope I haven't strayed from your question or something.

I think I spent too much time typing all this >_>

EDIT: PS: My skeleton example doesn't exactly show how to manage commands involving multiple game objects (This is just one of the many subtleties I hinted at). For stuff like "throw bottle at window", you need to think up how to manage such syntax, eg. a taste of my solution to this is to parse and discover what command is being issued... "throw GO at GO". Find out what the game objects are, then see if the current room has them. etc etc.

More importantly, this also prevents you from holding scripts inside a game object instance, since one command involves more than one game object. Probably better to store the Dictionary in the Room instance now. (This is sort of where I am with my project.)

Pardon my ramblings... >_>


It seems like your issue is managing propagation of events. Microsoft handles this issue (for less colorful purposes) using the Observer pattern/events.

I think the combining the Observer and Mediator design patterns from Gamma,etc.'s book "Design Patterns" would be very helpful for you. The book has a sample ChangeManager class that might be helpful, but I have attached some other links that should serve you well.

One implementation suggestion I have would be to use a static or singleton class that acts as the mediator and also stores references to all of the actionable objects in active memory as well as all of the invoked actions. This class can process the algorithms to determine all of the responses and the chronological order of the responses from a given action. (If you consider that the collateral-effects of a primary action, A, can affect the consequences of that action, A, prior to the action completing, it would become evident that the proper chronological sequence is necessary, and must update before invoking each collateral action.)

Microsoft's article on the observer pattern: http://msdn.microsoft.com/en-us/library/ee817669.aspx

DoFactory on Mediator pattern (with UML diagrams): http://www.dofactory.com/Patterns/PatternMediator.aspx

DoFactory on Observer pattern (with UML diagrams): http://www.dofactory.com/Patterns/PatternObserver.aspx

IObserver interface documentation in .Net 4, http://msdn.microsoft.com/en-us/library/dd783449.aspx

another article on the observer pattern. http://www.devx.com/cplus/Article/28013/1954


Interaction can be defined as "Verb + {List of Filters} + {List of Responses}"

For your "fill bottle with water" example, the Interaction would be:

  • Verb: Fill({"fill", "pour"})
  • List of Filters: Have(player, "bottle"), Have(currentRoom, "water tap")
  • List of Responses: Print("You filled the bottle with water"), Remove(player, "bottle"), Add(player, "bottle of water")
  • alternatively, List of Responses can be: SetAttribute(player.findInventory("bottle"), "fill", "water")

Then if you need to "throw bottle of water at windows":

  • Verb: Throw({"throw", "smash"})
  • List of Filters: Have(player, "bottle of water"), Have(currentRoom, "windows")
  • List of Responses: Print("The bottle smashed with the windows, and both of them are broken"), Remove(player, "bottle of water"), Add(curentRoom, "broken bottle"), Remove(currentRoom, "window"), Add(currentRoom, "broken window"), SetAttribute(currentRoom, "description", "There is water on the floor")

Upon entering a Room, the Framework will query all objects in the room for a list of valid Verbs, and enumerate them. When the player enters a command, the framework searches for a Verb that matches the command; then it will check the list of Filters, and if all of them is True, then iterate through the List of Responses to execute them in order.

The Responses would be a function object which implements the IResponse interface which has some constructors, and a IResponse.do() method. The Filters will be function object which implements the IFilter interface, again with some constructors, and IFilter.check() method returning a boolean. You can even have And(), Or(), and Not() filter to make more complex queries.

You can make things even more readable by having some convenience methods, a Player could have Player.have(Actionable) convenience method so you can write player.have("bottle of water"), which returns not the bottle object itself, but an IFilter object that will check whether the player have "bottle of water" when its .check() method is called. Basically, make the objects lazy.


Alright guys, here's how I handled it. It was the most generic way I could think of and I think it suits what I am trying to achieve.

I added an "Invoke()" method to the Interaction class, a new Interface called IActionResult which defined an "Initiate()" method, and a number of different ActionResult types for each possible consequence. I also added a List of ActionResults to an Interaction. The Invoke method would simply loop through all of the IActionResult objects and call the Initiate() method.

When you define an Interaction on an item, you would pass in a list of verbs for that Interaction and then add a number of ActionResult objects depending on the consequences of that Interaction.

I also added a GlobalActionReference, which would be updated each time an action is performed, and an ActionResult would have appropriate access to the objects it needs to update through this.

I really appreciate all of your suggestions, and I'm sorry if I wasn't clear with my question or my comments (or even this answer). Thanks for your help.

0

精彩评论

暂无评论...
验证码 换一张
取 消