5
\$\begingroup\$

I've been reading up on Actions and delegates for the last couple of days, and I think I'm finally starting to understand, but I wanted to check that I was using them correctly, or at least using them in a way that isn't totally insane.

I'm using a text-based game (as such) as a platform for learning about what I suspect is called event-oriented programming. For a really terrible example, imagine the player has a torch that can set things on fire. I don't want the torch to do target.ChangeState(States.OnFire), that's giving the torch way too much power over other objects - nothing is preventing an action like nonFlammableRock.ChangeState(States.OnFire) I COULD have the torch tell the game manager entity that it wants to set something on fire, and then let the game manager decide if it's allowed to do so... but instead, I came up with this:

TestObject

internal class TestObject
{
    private string instanceVar;
    private Dictionary<string, Action> eventHandlers;

    public TestObject(string newInstanceVar)
    {
        this.instanceVar = newInstanceVar;
        eventHandlers = new Dictionary<string, Action>();
        eventHandlers.Add("event0", PrintInstanceVar);
        eventHandlers.Add("event1", InstanceVarToUpper);
        eventHandlers.Add("event2", ChangeOwnInstanceVar);
    }

    public void PrintInstanceVar()
    {
        Console.WriteLine(instanceVar);
    }

    public void InstanceVarToUpper()
    {
        instanceVar = instanceVar.ToUpper();
    }

    public void Event(string eventName)
    {
        eventHandlers[eventName].Invoke();
    }

    public void ChangeOwnInstanceVar()
    {
        instanceVar = "Object has changed its instance variable";
    }
}

Program

private static void Main(string[] args)
    {
        var test1 = new TestObject("First test obejct");

        test1.Event("event2");
        test1.Event("event0");
        test1.Event("event1");
        test1.Event("event0");

        Console.ReadKey();
    }

In short, I want to tell an object what has happened to it, and then let the object itself decide what to do. In this way, a torch can send a message "ignite" to any object, and the target object itself looks up "ignite" in its dictionary of behaviours, and reacts accordingly. So for a Rock, eventHandlers["ignite"] = () => Console.WriteLine("The rock isn't flammable, the torch had no effect"), whereas for HayBale, eventHandlers["ignite"] = () => Console.WriteLine("The bale of hay catches alight").

Is this a sane approach? Are there any traps I've missed that will come back to haunt me later? Is there a better way of doing this? I know that C# Events exist, but I don't fully understand them yet, and I don't know how to use them to achieve what I'm trying to do here - but if using Events would be better, how would I change this?

Note:

I know individual entities shouldn't be responsible for printing to console - if I made this whole thing, they'd be sending updates as to their state, strings containing action text etc to the game manager, which would be responsible for displaying messages for the player.

\$\endgroup\$
  • \$\begingroup\$ Read about State Machines. A state machine (in this case, an object) can have several states. Its state changes in response to an external event that it detects as a message sent to it (in OOP, a method call). How the new state is achieved and what happens in that new state are completely up to the object itself. So, you are correct to have methods represent the messages that the object can handle and the implementations of these methods as the behaviour that transition the object into the new state. \$\endgroup\$ – RWRkeSBZ May 17 '16 at 11:56
  • \$\begingroup\$ To follow up on my comment, the process is: identify states that your object can have, identify the events that transition the object into the different states, then identify the behaviour that occurs in each state. When you are done, your events become your methods and the behaviours become the method implementations. \$\endgroup\$ – RWRkeSBZ May 17 '16 at 12:00
  • \$\begingroup\$ I considered making each object purely a state machine, but from my (limited) understanding that would involve making every single property of the object part of a state, which is updated on every operation - and the object would become little more than a container for the state. I guess if I went the way you're suggesting, the "eventHandler" table could be held as part of a state, and thus change (e.g. so you can't ignite a burnt piece of wood). But then that train of thought led to the idea of objects having multiple simultaneous states (more like 'effects') - is that something that's done? \$\endgroup\$ – Toadfish May 17 '16 at 12:26
  • \$\begingroup\$ "State" in state machines has a broader meaning than what you are saying. It means the condition that causes your object to behave in a certain way -- which could be a change to the properties of the object or just a change in behaviour. If you use this broader definition of "state", your design process will be easier. \$\endgroup\$ – RWRkeSBZ May 17 '16 at 14:11
  • \$\begingroup\$ Right, I've only implemented the pattern once before and it was an extremely rigid version of the model for a pretty elementary course, so I think I had an overly simplistic view regarding what the pattern was capable of \$\endgroup\$ – Toadfish May 17 '16 at 14:17
2
\$\begingroup\$

This looks ok to me on first glance.

I can only suggest the use of an Enum to strongly type the events that you are registering.

This allows you to search the code base quickly and avoids any typo's you may make:

    private string instanceVar;
    private Dictionary<string, Action> eventHandlers;

    public TestObject(string newInstanceVar)
    {
        this.instanceVar = newInstanceVar;
        eventHandlers = new Dictionary<Events, Action>();
        eventHandlers.Add(Events.event0, PrintInstanceVar);
        eventHandlers.Add(Events.event1, InstanceVarToUpper);
        eventHandlers.Add(Events.event2, ChangeOwnInstanceVar);
    }

    public enum Events
    {
        event0,
        event1,
        event2
    }
\$\endgroup\$
  • \$\begingroup\$ I was going to use an Enum, but then it occurred to me - since I'm generalising everything here, I suppose it's more of a game engine than a game - and a game engine with possible actions hard-coded into the source doesn't seem like the ideal. If we pretend there's a "map editor" as well as a game, would there be any way for a user to add an action to an Enum? If not while the code was running, would it be possible to deserialize an enum out of an XML file? Am I right in thinking that changing an Enum requires recompiling? \$\endgroup\$ – Toadfish May 17 '16 at 12:34
1
\$\begingroup\$

Your event names have no relation to what it is they're representing. This may be because your example isn't real. Make the event names mean something, if it's an event for Igniting, call it that.

At the moment, all of your events work with no arguments. This works as it is, however think about what you're going to do if you need to output 'The bale of hay catches alight from the torch'. There's a good chance that you're going to want to pass at least some information along with the event. It could be the caller, invoking object, trigger etc. This might vary between events, or you might want to have some standard argument but there's a good chance that you'll need something.

\$\endgroup\$

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.