A Functional Impass - Advise me on how to proceed? (C#)

Started by
21 comments, last by duke_meister 5 years, 2 months ago

ECS is a solution direction if inheritance or interfaces fail. From your post I gathered you don't use those much yet, so ECS is a possible solution 2 steps away from your problem for you. I'd suggest you get a good understanding about inheritance and interfaces first (in particular when not to use them) before you venture into ECS.

I think my Behavior object is at least in the direction of ECS, but I am not sure. I am more of a pragmatic programmer picking the ideas that fix the problem, rather than knowing exactly what is "proper ECS" or "proper OOP" or "proper Foo-Pattern" or "proper <whatever>".

Nice to see you already moved into reading data from file. As to how to write behavior in a file, whatever you do, it must be text, since that's the only thing you can write in a text file. Other adventure authoring systems tend to allow writing source code in some form which they load and interpret, but that's likely too complicated for you at this time (in the general case you end up in scanner and parser code generators like yacc or bison (both are C-based, no idea what exists in C# but likely it's similar). A much simpler form is to give each behavior a name, and then loading behavior into an Item is just a list of such names.

 

A switch statement is one option for converting a name to an object in the program. An other option is to use a Dictionary, which is quite simple if none of the the behavior objects has state. (That is, they don't have any variables inside that are different between behavior objects in different Items.) In that case, you can make a "Dictionary<string, Behavior> behaviors;" dictionary, where Behavior is the base class (or interface) of all behaviors. Getting the behavior is then a simple dictionary lookup, something like "behaviors.get(loaded_name)" or "behaviors[loaded_name]". (Not being a C# programmer, I don't know exactly how to do that, but https://stackoverflow.com/questions/12169443/get-dictionary-value-by-key seems to point to a solution.)

If the behavior objects do have state (and I can imagine that being useful), you need to construct a new Behavior object for each Item. The usual solution for that is the Factory pattern. Instead of a dictionary from name to behavior, you have a dictionary of name to behavior-builder. Getting a behavior is then a 2-step process, first get the builder (the factory) from the dictionary, then ask the builder for a fresh behavior object. I can imagine that this is too complicated for now, and a switch is the better solution to you at this point.

 

How to find and perform a behavior at runtime is indeed another puzzle. I would try to avoid splitting the actions as much as possible. The advantage of that is less cases to deal with, and (probably more for the future), simpler expansion of the set of actions. If you want to add "boinc" as new action (no idea what it would do, just an example), you don't want to have to write a new derived BoincAction, extend all existing behavior code for this new Boinc action, etc.

So instead, why not let the command processor ask the Item for a Behavior object matching the given action string (or action ID). The Item then asks all its behaviors whether they understand that action, and if one does, it is returned. The command processor thus gets (or doesn't get) a behavior object, which it then executes ("behavior, please do your stuff"). Nothing in this setup knows what action is performed exactly, except the behavior object itself, but the latter is supposed to know eh? :) The command processor and the Item don't know if you typed "eat", "open", "rub", or (in the future) "boinc".

Advertisement

So I have been playing a little bit with ideas form this thread... I have a lot of life stuff going on so not putting in as much time as I hope. When I finish moving house I'll be able to devote more to this project....
But.. the idea for some kind of dynamic way to give "unique" behaviours to the various objects and areas sounds great.. I am just having some difficulties working out how this can be achieved in C#. I have tried a variety of ways.. but they all end up requiring at some point a hard coded bit that I can not do dynamically.

What exactly am I trying to do?

What I am trying to do is make a system that can take in a "string" and use that string to somehow assign unique code (a behaviour) to a class.

The way I was planning to do this was to have a series of generic "functions" in a class... like say open, take, move etc etc and then assign to those functions unique methods in the form a class...

So.. for example... when building my class I was trying to do something like this (this could be a bad way of doing it)

  • I define in the DataFile for the item an part that defines specific behaviours to get read and added to the item class in the game.
  • The data reader reads a behaviour called something like "take-TriggerAvalanche". This unique "take" behaviour has all this unique code in it I want to run. In this case triggering an avalanche, adding and removing items, and flicking switches in other rooms. This all occurs when the player tries to "take" this item.
  • This behaviour is added to a "behaviour" variable that is in the Item Class. This is a Dictionary <string,Behaviours>. So this "take" behaviour is added kinda like this... behaviours.Add("take", take-TriggerAvalancheClass). This is the bit that is not working (see next sections below)

So now I have a item.behaviours[action] that is a class with whatever methods I like..... I already have a command processor that boils down user inputs into "action" and "target".... so "retrieve the ball next to the table" becomes "take" and "ball".

  • So all I need to do is search the area's item list for "ball", and if that is found.. then search the ball's behaviour dictionary for the key that matches the "action" sent by the command processor... and if it find it.. then activate the unique method in that behaviour.
  • 
    if (behaviour.ContainsKey(action))
        behaviour[action].runAction();

     

So this is basically what I would like to happen.

Example of trying to use inheritance.


class ClassName
{
    public string name { get; }
    public ClassName()
    {
        name = "name";
    }
}

class Unique : ClassName
{
    public string uName { get; }
    public Unique()
    {
        uName = "UniqueName?";
    }
}

class YAunique : ClassName
{
    public string yaName { get; }
    public YAunique()
    {
        yaName = "YetAnotherName";
    }
}

class Program
{
    static void Main(string[] args)
    {
        Dictionary<string, ClassName> doesThisWork = new Dictionary<string, ClassName>();
        doesThisWork.Add("test", new Unique());
        doesThisWork.Add("test2", new YAunique());

        Console.WriteLine(doesThisWork["test"].name);
        Console.WriteLine(doesThisWork["test"].uName); //dose not work
        Console.WriteLine(doesThisWork["test2"].name);
        Console.WriteLine(doesThisWork["test2"].yaName); //does not work

        Unique example = new Unique();
        Console.WriteLine(example.name);
        Console.WriteLine(example.uName);

        YAunique example2 = new YAunique();
        Console.WriteLine(example2.name);
        Console.WriteLine(example2.yaName);

        // Pauses the console window
        Pause4Input();
    }

Here is a simple example of inheritance being added to a Dictionary class. The problem here is that while I can add any inherited child class to the Dictionary... I can only access the methods and variables of the base class.

The only way I can work out how to get this to work is to "cast" the class to the correct child class. Like this...


            var casted = (Unique)doesThisWork["test"];
            Console.WriteLine(casted.name);
            Console.WriteLine(casted.uName); //This Now Works

The problem here is that casting to the unique class form the base class, basically means that it is no longer "dynamic". I need to physically type in the casting every time and be different for each type.. so it removes the entire purpose of the idea, as in have a way to add and call unique code from simple "string".

Basically I am trying to find a way to do this but that actually works


    class ClassName
    {
        public string name { get; }
        public ClassName() {name = "defaultName"; }
        public void RunAction()
        {
            Console.WriteLine("Default");
        }
    }

    class Unique : ClassName
    {
        public string uName { get; }
        public Unique() { uName = "UniqueName"; }
        public new void RunAction()
        {
            Console.WriteLine("Unique");
        }
    }

    class YAunique : ClassName
    {
        public string yaName { get; }
        public YAunique() { yaName = "YetAnotherName"; }
        public new void RunAction()
        {
            Console.WriteLine("Yet Another Unique");
        }
    }

    class TestingGround
    {
        static void Main(string[] args)
        {
            //Dictionary<string, ClassName> doesThisWork = new Dictionary<string, ClassName>();
            Dictionary<string, ClassName> doesThisWork = new Dictionary<string, ClassName>();
            doesThisWork.Add("test", new Unique());
            doesThisWork.Add("test2", new YAunique());

            doesThisWork["test"].RunAction();

            Console.ReadKey();
        }
    }

 

s

 

 

: GameDev.Net Blog: Open Blog; Read Blog : My uTube Chans : TragicTableTopTragicDeskTop

I have hacked together a kind of "conversion" method. That can take the raw class data and cast it correctly. This is just a rough test but I think it could be made to work properly.


    class ClassName
    {
        public string name { get; }
        public ClassName() {name = "defaultName"; }
        public void RunAction()
        {
            Console.WriteLine("Default");
        }
    }

    class Unique : ClassName
    {
        public string uName { get; }
        public Unique() { uName = "UniqueName"; }
        public new void RunAction()
        {
            Console.WriteLine("Unique");
        }
    }

    class YAunique : ClassName
    {
        public string yaName { get; }
        public YAunique() { yaName = "YetAnotherName"; }
        public new void RunAction()
        {
            Console.WriteLine("Yet Another Unique");
        }
    }

    class TestingGround
    {
        static void Main(string[] args)
        {
            //Dictionary<string, ClassName> doesThisWork = new Dictionary<string, ClassName>();
            Dictionary<string, ClassName> doesThisWork = new Dictionary<string, ClassName>();
            doesThisWork.Add("test", new Unique());
            doesThisWork.Add("test2", new YAunique());

            //doesThisWork["test"].RunAction();
            RunAction(doesThisWork["test"]);
            RunAction(doesThisWork["test2"]);

            Console.ReadKey();
        }

        static void RunAction(ClassName behaviour)
        {
            //var instance1 = (YAunique)behaviour;
            //var myObject = behaviour as YAunique;
            if (behaviour as Unique != null)
            {
                var myObject = behaviour as Unique;
                myObject.RunAction();
            }
            else if (behaviour as YAunique != null)
            {
                var myObject = behaviour as YAunique;
                myObject.RunAction();
            }
            else
            {
                Console.WriteLine("**************** FAILED CAST ****************"); //I should never see this!
            }
        }
    }

What this dose is test "doesThisWork[action]" against all the available behaviours in the game. If one is a match, then it executes the code. It dosen't seem very efficient but it looks like I can make this work maybe. I can reduce the gigantic IF statment by using the "action" string itself to limit the searchable behaviours. So something like.... RunAction(doesThisWork, action)... then read "action" and for example, send it to a code block that only tests TAKE actions... and then use that same "action" string as the dictionary string and then run the correct unqiue behaviour.

Again we come back to the same kind of "bookkeeping" problem. A gigantic If Statement is a pain in the arse but I can not use a for(loop) on a list that has every unique behaviour class, as we run into the same problem.. you can not have different classes in a collection.

So yeah.. thoughts on this and my "hacked" solution? I would love a better way if you know one.

 

 

: GameDev.Net Blog: Open Blog; Read Blog : My uTube Chans : TragicTableTopTragicDeskTop

Perhaps, I'm jumping to conclusions but it seems from your last two posts that you don't understand how to override virtual functions in C#. You need to make the functions in the base class virtual, or better yet define the functions you need in an interface, in order to override them in the derived class. Then when you call runAction it will get handled by the version in the derived class. I can't remember whether you can override properties in C#, so you might need an interface composed only of functions. Googling 'overriding in C#' should get you the information you need.

I'm posting from my phone right now, but if this isn't clear I'll write a more detailed post when I get home tonight.

Before I start, a quick disclaimer: I don't work much in C# and it's been quite a while since I did any at all, so apologies if I get some of the syntax a touch off, or if my methods of doing things aren't up to the latest C# approaches.

Okay, so firstly, you want your base behaviour definition to be an interface rather than a class, this means that rather defining actual methods, etc. it simply declares an interface of expected methods, etc. that need to be implemented by the implementing class. You can refer to objects by their interface just fine, so something like this:


    // Interface for the behaviour
    interface IBehaviour
    {
        // Simplest possible RunAction
        // You may actually want to pass the full command in,
        // or the parameters to it, or some game objects to work with
        void RunAction();
    }

This is obviously the minimum set of actions for Behaviour interface, you might want to add additional methods or properties to the interface, to give it more functionality. Note that in this approach, behaviours don't know what keywords are used to trigger them ('take', 'open', etc.), this is by design to make them more flexible, more on that below.

(Btw, alternatively you could declare RunAction to be a virtual method in your base class and then override them in your derived classes, but interfaces are usually a better way of doing this in languages that support them, such as C#).

You can then implement an action in your implementing class. Note that because you are using an interface, your behaviour implementations can have completely separate design rather than being part of a class hierarchy, this makes them more lightweight and flexible. As a simplistic example, you could do do something like this:


	class HelloWorld : IBehaviour
	{
		void IBehaviour.RunAction()
		{
			Console.WriteLine("Hello World!");
		}
	}

Which you can then use in your main code, with something like this:


// You can create any behaviour you like here
IBehaviour behaviour = new HelloWorld();

// And this call will always invoke the right action
// In this case printing "hello world"
behaviour.RunAction();

As in your code, you will actually want to put your behaviours into a Dictionary or similar that lets them be looked up by keyword and the right behaviour invoked. I'm not sure how you are deciding which behaviour classes to instance for your keywords when defining them in your textfile, but if you're not already using one, you probably want to look into implementing the Factory Pattern to do this. Using a factory pattern will help keep your code nice and clean and separate the range of available behaviours from the code that creates them. Allowing your keywords to be specified separately from your behaviours allows the same behaviour to be used for multiple keywords, which seems like a likely occurrence to me (e.g. you might want to 'enter' a portal but 'climb' a ladder, but both actions will take you to a new room).

I hope that helps, let me know if anything isn't clear.

The magic words for running a different function implementation in a derived class are "C# method overriding". If you search for that, you should find plenty of examples of how to make a method in a derived class do something else.

 

Hey guys... just want to let you know I am still on this.. I have not had a chance to try any of the new information in this thread.. I am moving house and my spare time is currently painting and ripping up carpet and putting down floors.. I hope to get back into my hobby in a few more weeks. 

: GameDev.Net Blog: Open Blog; Read Blog : My uTube Chans : TragicTableTopTragicDeskTop

Hi, I think the solution you are looking for is scripting. Each object in a room has an associated script, which is just a plain text file. The behaviour for every item in the game can be unique and as simple or complex as you like. Back in the day, a lot of text adventures were written in BASIC, so you're trying to do is probably a lot harder. (some of them are even open source https://github.com/SinclairQL/akalabeth )

So you could use BASIC, Lua or Python but you will get annoyed trying to create bindings, so if you're feeling ambitious you can create your own scripting language. Here is an example of a script for the box with a push button


@Push:
    // used the Push command
    GetFlag FOUND_BUTTON End		// if flag FOUND_BUTTON is 0 goto label End
    Print "You pushed the button."
    Wait 3.0
    SoundEffect BUZZSAW_1
    Print "A mechanical arm bursts out of the box and tries to decapitate you!"
    Goto End
    
@Turn:
    Print "You turned over the box..."
    Wait 0.5
    SoundEffect MYSTERY_1
    Print "You were surprised to find a big red push button underneath!"
    SetFlag FOUND_BUTTON
    Goto End
    
@Inspect:
    Print "It's a small wooden box, varnished mahogany."
    Goto End
    
@Shake:
    SoundEffect RATTLE_1
    Print "It feels weighty. It seems to rattle when you shake it, as if it contains moving mechanical parts."
    Goto End
    
End:
    // finish executing the script

I've not written a text adventure game before but I think this might be a good way to do it. The way it would work is, when the user enters a command it would look for the label in the script and execute that bit of code. I put '@' before the command labels to distinguish them from normal labels, like the End label. When you compile the script you would have to export a table which maps the command names to line numbers so that when the user enters a command you can jump in at the right place. Anyway, I'm sure you could come up with a neater scripting syntax (I don't like how every command has to end in 'Goto End'...). The point is you can come up with your own design that is specialised for doing exactly what you want.

The cool thing about scripting is the script could define it's own commands, so you could get really imaginative with how the user can interact with something. By using flags (like the FOUND_BUTTON flag) you can affect the global state of the game, you should be able to read these same flags from other scripts so that they can 'communicate' with each other. You can also see how I've added timing cues and sound effects to the script, this is how you add life to your game and make it entertaining. If you've ever played a JRPG you can get an idea of the surprising level of detail you can go into with just text.

If you want your text adventure to be super optimised here is a helpful article about using writing a bytecode interpreter for games http://gameprogrammingpatterns.com/bytecode.html That article in particular dives into making a tree parser which you don't really need to do, all you need is a few commands which can accept float, int or string literal parameters.

While buildng your own programming language on top of your existing one is fun, I have a lot of doubts that making a language for it is very helpful in getting forward in the project.

Do you really want to reinvent this wheel? Fine if you do :)

But first have a look at TADS 3

Developer since 1994. Programming since '80. Still like it.

This topic is closed to new replies.

Advertisement