2

I have a requirement to implement a type of AI for a project, the issue is that I am having trouble visualizing how the AI should actually be implemented.

Essentially, I want the AI to be as dynamic as possible. That is, if I decide in the future to add another type of AI Action (as we'll call it), I don't need to rewrite the class using the AI or AI Action.

What I have now, is a very rough idea, but it should help clarify.

public interface IAiAction
{
    List<IAiProperty> Properties { get; }

    DateTime LastUpdate { get; }

    bool Update(Entity e);
}

public interface IAiProperty
{

}

Then I have something like:

public class WanderAction : IAiAction
{
    private List<IAiProperty> _Properties = new List<IAiProperty>();
    public List<IAiProperty> Properties { get { return _Properties; } }

    private DateTime _LastUpdate;
    public DateTime LastUpdate { get { return _LastUpdate; } }

    private Point _WanderTo;

    public bool Update(Entity e)
    {
        // Do stuff here

        return true;
    }
}

public struct WanderDistance : IAiProperty
{
    private Point _Home;
    private float _MaxDistance;

    public Point Home { get { return _Home; } private set { _Home = value; } }
    public float MaxDistance { get { return _MaxDistance; } private set { _MaxDistance = value; } }

    public WanderDistance(Point h, float m)
    {
        _Home = h;
        _MaxDistance = m;
    }
}

Then, in the class with the actions:

private List<IAiAction> _AiActions = new List<IAiAction>();
public List<IAiAction> AiActions { get { return _AiActions; } }

public override void Update(Vector2F force)
{
    foreach (IAiAction action in AiActions)
        action.Update(this);

    base.Update(force);
}

The problem is that this does not extend well. I'm not even sure how I got to this stage, as I've never had to do something like this before.

This code is by no means a requirement. If anyone has any other ideas for how it can be done (even if they are far separate from what this indicates) I am up for it.

For example, if I had an IAiAction that was to determine what the Entity was going to say randomly, this leaves very poor room to do that. I also, for the WanderAction as an example, need to return a Vector2F to the calling class, to add it to the force parameter of the Update(Vector2F) method.

If any clarification beyond this is necessary, please let me know. This isn't holding up the project, but it is causing me issue.

The only other method I can think of is to implement a sort of state that is carried through the programme. This would technically satisfy my requirements, but I'm curious if there is a better way.

2
  • Not an answer, but as a note I'd look into autoproperties,to cut down on your boilerplate code. Jul 5, 2015 at 2:05
  • Yeah, there's some cleaning to be done. I'm a bit weary of doing too much cleaning until I have a solid founding on how to implement this portion of the project. Jul 5, 2015 at 2:09

1 Answer 1

2

If I'm understanding correctly, you have an Entity, which can do things (like walk, talk, and so on), and want to use classes to be able to provide this entity with AI behaviours. These will be triggered in some loop (or by schedule, or whatever) and will then cause some behaviour in the entity (walking, talking, etc.).

There's a few you're going to need two things:

  • An interface for AI actions, allowing them to be triggered
  • A way for the AI action to actually effect the change in behaviour

For the first one, you have your interface IAiAction. Let's look at how it's used:

foreach (IAiAction action in AiActions)
    action.Update(this);

So from this, it seems like actually, the only member on IAiAction should be:

void Update(Entity entity);

This also relieves you of needing IAiProperty at all. WanderDistance, for example, can just be a normal, private member on WanderAction.

Looking at this interface, there also doesn't seem to be anything AI-specific about it. It could work just as well for user-controlled actions, or actions so dumb they can't really be called AI (like waiting in place forever). Why not just IAction, or IInstructor or something?

It may be that as you implement more of these AI classes, you do find common structure. You could pull that out into a dependency or base class. But there's no need to pre-emptively impose structure, and certainly no need to expose it publicly. Just expose what consumers actually need. As an example, maybe you were considering using LastUpdated so that the Entity could only call actions that have been updated more than a second ago, or whatever. But why not keep LastUpdated private, and let the actions can make that decision themselves?


As for the second bullet point, it'd be tough to try to have some general way for the AI action to return an instruction for the Entity to understand. Instead, Entity should expose what it can do through methods on an interface:

void SetDestination(Vector2D destination){ /*...*/ }
void Say(string utterance) { /*...*/ }

Now an AI action can call methods, instead of having to return something and assume the Entity will have the logic to handle it. You could, for example easily add a ReactToDangerAction which both yells and runs away from the danger, given the above two methods.


This is a starting point, but one design improvement which may go a long way is to not pass the Entity in to the Update method, but instead pass it in through the constructor. After all, it doesn't make sense for your example WanderAction to be shared by two different entities, as they'd probably have different homes. So it might as well be tied to one particular entity permanently, rather than accepting one each time the method is called and hoping it always gets the same one. This would probably require a little boilerplate (factory classes, possibly), but would enable some design improvements.

For example, you could adhere better to the Interface Segregation Principle by splitting the Entity's interface into several (IDirectable, IVocal, etc.). Or, if you don't like the idea of Entity exposing methods allowing it to be ordered around by all and sundry, you could even make the SetDestination and Say methods private and pass them into the actions' constructors as delegates. It would also give flexibility if in the future you found that some actions shouldn't be tied to particular entities, or should be tied to multiple.

5
  • I'm going to re-review this in the morning, but I very much like the points you made. I didn't think to act as if this were not the only thing that could be implemented in this pattern. It always helps to have a second pair of eyes on it. You also hit the hammer on the head with the first paragraph. That is entirely my intention. Jul 5, 2015 at 2:52
  • Ben, this is a brilliant answer. I just re-read it and looked at what I have, and I can see what I've had so much trouble. I really appreciate this breakdown, it's helped me visualize what I was trying to do. I think the only change I'm going to make from this pattern, is to pass a State to each AI (or action) method, and use that state to carry the changes. (Some AI Actions will affect the operation of others in the future.) Though, the interface segregation won't really help here, as Entity is an abstract class and all entities have these properties. I wish I could up-vote this again. Jul 5, 2015 at 12:38
  • @EBrown Glad it could help! Jul 5, 2015 at 20:14
  • @EBrown By the way, maybe I'm misunderstanding the last thing you said about interface segregation, but the idea is to present as small an interface as possible to the consuming code. So that would mean Entity (or State, under your version) could implement multiple interfaces, but the consumers of Entity (in this case the AI Action) would accept arguments of the interface type(s) they needed, rather than of type Entity. Jul 5, 2015 at 22:46
  • I don't know why, but I read your last statement wrong when I posted the original comment - that was my mistake. Interface segregation won't really help here - I don't think. All Entity objects should be affected by any IInstruction, as they stand. Entity is an abstract class as Entity performs some of the updating for all objects inheriting it, though after reviewing the code I have I may be doing it the wrong way. (I may post more questions of it in the near future.) This commenting does bring some other design issues to light though. Jul 6, 2015 at 18:41

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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