moving to a robocode world state

I've been sharing my experiences in driving my robocode implementation through specification and in my last few posts showed how my thinking has evolved and how I wanted to have some bounded contexts between decision making, calculation and manipulation.

I've got my robot to a stage where it scans once, then does not fire and to make it do more things I need to publish some more state to observables. My existing code needs a refactor to any new shape I derive but I have acceptance tests that assert against that development so I'm free to change it with some degree of confidence.

So I go back to an older specification for the SlayerRobot itself - when the turn status is updated and mark this to be removed at some stage. Here's what it currently looks like:

    public class when_robocode_updates_the_turn_status_on_a_robot_TO_BE_REMOVED 
                                                                      : with_robot_set_up
    {
        Establish context = () =>                   
        {
            robot.StartOfEveryTurn += (s, e) =>  statusAtStartOfTheTurn = e;

            turnNumber = 34;
            robotStatus = RobocodeSpy.CreateARobotStatus(turnNumber);
            statusEvent = new StatusEvent(robotStatus);
        };

        Because of = () => robot.OnStatus(statusEvent);

        It should_update_the_start_of_every_turn_event_with_the_turn_number = () =>
            statusAtStartOfTheTurn.TurnNumber.ShouldEqual(turnNumber);

        static StatusEvent statusEvent;
        static RobotStatus robotStatus;
        static long turnNumber;
        static StatusAtTheStartOfATurn statusAtStartOfTheTurn;
    }

I want a new way of updating the state, so create a new specification:

    public class when_robocode_updates_the_turn_status_on_a_robot : with_robot_set_up
    {
        Establish context = () =>
        {            
            robotStatus = RobocodeSpy.CreateARobotStatus(0, 2.3);
            statusEvent = new StatusEvent(robotStatus);

            robotEngine = MockRepository.GenerateStub<ImARobotEngine>();

            serviceLocator.Stub(x => x.ProvideRobotEngine()).Return(robotEngine);
            robot.Run();
        };

        Because of = () => robot.OnStatus(statusEvent);

        It should_tell_the_robot_engine_to_publish_to_the_world_state = () =>
            robotEngine.AssertWasCalled(x => 
                      x.PublishToTheWorldState(Arg<Action<IWorldState>>.Is.NotNull));

        static StatusEvent statusEvent;
        static RobotStatus robotStatus;
        static ImARobotEngine robotEngine;
    }

I've first expanded my RobocodeSpy class to allow the gun heat to be injected when creating a RobotStatus - here I'm setting it to 2.3. I then provide a stubbed RobotEngine by using the service locator and run the robot to begin that initialisation.

When the event is called, I now want the robot to publish to the world state through the robot engine - it can provide an Action<IWorldState> that the robot engine will take care of.
The specification fails, so I implement like this (only relevant part of the robot code is shown):

    public class SlayerRobot : Robot, ImARobot
    {
        public override void OnStatus(StatusEvent e)
        {
            _robotEngine.PublishToTheWorldState(w => { });
        }
     }

I'm publishing to the world state and my specification passes. I now want to verify that the turn number and the gun heat are updated on the world state so I add some more assertions:

    public class when_robocode_updates_the_turn_status_on_a_robot : with_robot_set_up
    {
        Establish context = () =>
        {
            expectedTurnNumber = 32;
            expectedGunHeat = 2.3;
            robotStatus = RobocodeSpy.CreateARobotStatus(expectedTurnNumber, expectedGunHeat);
            statusEvent = new StatusEvent(robotStatus);

            robotEngine = MockRepository.GenerateStub<ImARobotEngine>();
            robotEngine.Stub(x => x.PublishToTheWorldState(null))
                       .IgnoreArguments().Do(RecordPublicationToTheWorldState);

            serviceLocator.Stub(x => x.ProvideRobotEngine()).Return(robotEngine);
            robot.ServiceLocator = serviceLocator;
            robot.Run();
        };

        Because of = () => robot.OnStatus(statusEvent);

        It should_tell_the_robot_engine_to_publish_to_the_world_state = () =>
            robotEngine.AssertWasCalled(x => 
                     x.PublishToTheWorldState(Arg<Action<IWorldState>>.Is.NotNull));

        It should_affect_the_turn_number_on_the_world_state = () =>
            TheWorldState.CurrentTurnNumber.ShouldEqual(expectedTurnNumber);

        It should_affect_the_gun_temperature_on_the_world_state = () =>
            TheWorldState.GunTemperature.ShouldEqual(expectedGunHeat);

        static Action<Action<IWorldState>> RecordPublicationToTheWorldState = 
                                                      a => updateTheWorldState = a;

        protected static IWorldState TheWorldState
        {
            get
            {
                var worldState = MockRepository.GenerateStub<IWorldState>();
                updateTheWorldState(worldState);
                return worldState;
            }
        }

        static StatusEvent statusEvent;
        static RobotStatus robotStatus;
        static ImARobotEngine robotEngine;
        static Action<IWorldState> updateTheWorldState;
        static long expectedTurnNumber;
        static double expectedGunHeat;
    }

There are quite a few changes here - first I'm stubbing the robot engine publishing to world state method and am recording the action that is sent in with my RecordPublicationToTheWorldState static field.
I have two new observations that check the world state - each time the property is accessed I create a new stub and apply the recorded action's changes to it and return it.
I can then assert that the robot is indeed suggesting an update to the right areas of the world state.

Here's the implementation to make this pass:

    public class SlayerRobot : Robot, ImARobot
    {
        public override void OnStatus(StatusEvent e)
        {
            _robotEngine.PublishToTheWorldState(w =>
                        {
                            w.CurrentTurnNumber = e.Status.Time;
                            w.GunTemperature = e.Status.GunHeat;
                        });
        }
    }

In the next robocode post I'll move onto the robot engine and ensuring the world state updates occur.



0 comments: