Switching the behavior of an Akka.NET actor

akkadotnet-logoI am quite interested by the actor model pattern and especially by using Akka.NET. So I wondered how I could learn it by practicing, the Petabridge’s boot camp is a very good start but I want to learn more by myself.

When I was a kid, I used to play a lot at Age Of Empires II: The Age of Kings, I played this game for hours and I still know a lot about the rules. This is a strategy game in real-time, where the player manage units to gather resources, expand on a map, build armies to defeat some opponents.

280px-Age_of_Empires_2_The_Age_of_Kings_Logo

When thinking about it I think it could provide a good and fun example to try Akka.NET in depth. There are a lot of different rules in the game and some of them are quite specific, and a unit in the game could be implemented by an actor, imagine all the interactions that can happened between all of these actors.

When writing these lines I am only at the start of this journey and I don’t know if it is a good idea, but still I want to try. I don’t think I will handle the movement of the units and the graphic interface, I will focus on the interactions between the different units, buildings and technologies.

The setup

For this demo I will show you how to switch the behavior of a villager depending on the message (command) he receives. In the game a villager can do a lot of things: gathering resources, constructing buildings, repairing them, even fighting. For the moment I will focus on the resources gathering part.

In order to see the different amounts of resource available to the player, I created an actor which will store them:

public class ResourcesSupervisorActor : TypedActor, IHandle<ResourceRecolted>
{
    public Dictionary<Resource, uint> ResourcesAmounts { get; private set; }
 
    public ResourcesSupervisorActor()
    {
        ResourcesAmounts = new Dictionary<Resource, uint>
        {
            {Resource.Food, 0},
            {Resource.Wood, 0},
            {Resource.Stone, 0},
            {Resource.Gold, 0}
        };
    }
 
    public void Handle(ResourceRecolted message)
    {
        ResourcesAmounts[message.ResourceType] += message.Quantity;
    }
}

You can notice that unlike in previous posts about Akka.NET, I used a new kind of actor: the TypedActor and I implemented an interface to specify the type of message this actor can handle. Now it is time to have a villager that will gather resources to increase these amounts.

The tests

I am using Specflow to write the acceptance tests I want to implement for the project and here are the firsts for the villager:

Feature: Villager Professions

Scenario: A villager recolts food when ordered to gather fruits
	Given I have a villager
	When he becomes a gatherer
	Then he recolts food

Scenario: A villager recolts food when ordered to becoma a shepherd
	Given I have a villager
	When he becomes a shepherd
	Then he recolts food

And below you will find the implementation of these steps.

[Binding]
public sealed class VillagerSteps : TestKit
{
    [AfterScenario]
    public void AfterScenario()
    {
        Shutdown();
    }
 
    [BeforeScenario]
    public void BeforeScenario()
    {
        _resourcesSupervisor = ActorOfAsTestActorRef<ResourcesSupervisorActor>();
    }
 
    private TestActorRef<VillagerActor> _villagerActor;
    private TestActorRef<ResourcesSupervisorActor> _resourcesSupervisor;
 
    [Given(@"I have a villager")]
    public void GivenIHaveAVillager()
    {
        var props = Props.Create<VillagerActor>();
        _villagerActor = ActorOfAsTestActorRef<VillagerActor>(props);
    }
 
    [When(@"he becomes a gatherer")]
    public void WhenHeBecomesAGatherer()
    {
        _villagerActor.Tell(new GatherFruits());
    }
 
    [When(@"he becomes a shepherd")]
    public void WhenHeBecomesAShepherd()
    {
        _villagerActor.Tell(new ShepherdFlock());
    }
 
    [Then(@"he recolts food")]
    public void ThenHeWillRecoltFood()
    {
        _villagerActor.UnderlyingActor.ResourceToRecolt.ShouldBe(Resource.Food);
    }
}

I am using Akka.TestKit and the shouldly library for the tests. With these scenarios we can understand that a villager can have several roles, even when gathering a single type of resource. I only put two professions in this example to shorten the code.

The villager implementation

Now I will show you the implementation of the villager actor to show how a behavior switching can be made when using Akka.NET.

public class VillagerActor : ReceiveActor
{
    private readonly IActorRef _resourcesSupervisor;
 
    public VillagerActor(IActorRef resourcesSupervisor)
    {
        _resourcesSupervisor = resourcesSupervisor;
        Profession = Profession.Idle;
    }
 
    public Profession Profession { get; private set; }
    public Resource ResourceToRecolt { get; private set; }
 
    protected override void PreStart()
    {
        base.PreStart();
        Become(Idle);
    }
 
    private void Idle()
    {
        CommandsHandler();
    }
 
    private void Gatherer()
    {
        Profession = Profession.Gatherer;
        ResourceToRecolt = Resource.Food;
        // repeat until new order or lack of bushes
        _resourcesSupervisor.Tell(new ResourceRecolted { ResourceType = ResourceToRecolt, Quantity = 10 });
 
        CommandsHandler();
    }
 
    private void Shepherd()
    {
        Profession = Profession.Shepherd;
        ResourceToRecolt = Resource.Food;
        // repeat until new order or lack of sheeps
        _resourcesSupervisor.Tell(new ResourceRecolted { ResourceType = ResourceToRecolt, Quantity = 10 });
 
        CommandsHandler();
    }
 
    private void CommandsHandler()
    {
        Receive<GatherFruits>(m => Become(Gatherer));
        Receive<ShepherdFlock>(m => Become(Shepherd));
    }
}

Like in my introduction to Akka.NET I used the ReceiveActor which allows me to treat several types of message and to do different actions depending on the context.

At the first the villager is idle and just wait to receive an order to get to work (just like in the game). At the moment he can only be ordered to gather food from bushes or from sheep. When one of these two order arrives the “profession” of this villager change and he begins his work, gathering food in these cases (message sent to the supervisor actor to increment the food amount).

Yet a gatherer can at anytime receive the order to become a shepherd and this is done by using the Become method available for this type of actor (I extract this behavior in a separate method to avoid duplication).

I have written the following test to check that a villager can change its behavior:

[Fact(DisplayName = "VillagerActor Should Be Able To Change Profession")]
public void Be_Able_To_Change_Profession()
{
    var villager = ActorOfAsTestActorRef<VillagerActor>(Props.Create<VillagerActor>(TestActor));
    var shepherdCommand = new ShepherdFlock();
    villager.Tell(shepherdCommand);
 
    var gatherCommand = new GatherFruits();
 
    villager.Tell(gatherCommand);
 
    villager.UnderlyingActor.Profession.ShouldBe(Profession.Gatherer);
}

Using this Become method is very helpful when designing Finite-State-Machine with Akka.NET.

A final word

This example is just an introduction regarding the behavior switching with the actor model, the example is quite simple at the moment. But I intent to make updates on the project to implement the rest of the possible gathering methods for a villager. There are a lot more rules to implement even in the area I just showed you.

I put the code on my GitHub account to allow you to browse the whole code and to see the improvements I will add over time. If you would like to see a particular set of rules implemented, let me know, I will continue to post articles about this project and the use of Akka.NET for it. And do not hesitate to tell me if you think something should be made differently, I am open to feedback.

See you next time!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s