hooking up the engine to my robocode robot

In previous posts I've set up an acceptance test that expects some statistics to be written out at turn fifty to ensure my robot is not disabled at the start of a battle. I now need to hook up my robot engine so that it can detect that turn.

I'm going to be using the Rx framework and plan to utilise the observable FromEvent() extension method, so I want an event to be triggered on my robot when the OnStatus() method is called by robocode - this happens at the start of every turn.
Here's the beginning of my specification:

    public class when_robocode_upates_the_turn_status_on_a_robot : with_robot_set_up
    {
        Establish context = () =>                   
        {    
            robotStatus = ???;
            statusEvent = new StatusEvent(robotStatus);
        };

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

I'm using a previous base class that simply sets up a real SlayerRobot and in my because method I want to call the OnStatus method with a status event instance. This presents me with a problem though - the robocode RobotStatus class is a public sealed class with private constructors used for serializing:

private RobotStatus(double energy, double x, double y, double bodyHeading, double gunHeading,
                    double radarHeading, double velocity, double bodyTurnRemaining,
                    double radarTurnRemaining, double gunTurnRemaining,
                    double distanceRemaining, double gunHeat,
                    int others, int roundNum, int numRounds, long time);

I decide to construct one anyway, so I create a class specifically for dealing with this issue:

    internal static class RobocodeSpy
    {
        public static RobotStatus CreateARobotStatus(long time)
        {
            double d = 0;
            int i = 0;

            Type[] types = new Type[16];
            var doubleTypes = types.Take(12).Select(t => typeof (double));
            var intTypes = types.Skip(12).Take(3).Select(t => typeof (int));
            var longTypes = types.Skip(15).Select(t => typeof (long));
            types = doubleTypes.Concat(intTypes).Concat(longTypes).ToArray();

            object[] parameters = new object[16];
            var doubles = parameters.Take(12).Select<object, object>(p => d);
            var ints = parameters.Skip(12).Take(3).Select<object, object>(p => i);
            var longs = parameters.Skip(15).Select<object, object>(p => time);
            parameters = doubles.Concat(ints).Concat(longs).ToArray();

            ConstructorInfo ci = typeof(RobotStatus).GetConstructor(BindingFlags.NonPublic | 
                              BindingFlags.Instance, null, types, null);
            
            object status = ci.Invoke(parameters);

            return (RobotStatus)status;
        }
    }

I can now construct a RobotStatus class with the right time indicating a turn, so I can complete my specification:

    public class when_robocode_upates_the_turn_status_on_a_robot : 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 define a new event that I want to see on my robot - the StartOfEveryTurn event and I assign an event handler to it to capture the event arguments which I want to be of a new type "StatusAtTheStartOfATurn".
I then construct the objects required for the event and defining a turn and then in the Because action I raise the event.
I expect that the turn number of 34 should make its way into the event arguments.

Here is the relevant part of the robot class that makes this specification pass:

    public class SlayerRobot : Robot, ImARobot
    {
        public event EventHandler<StatusAtTheStartOfATurn> StartOfEveryTurn;

        public override void OnStatus(StatusEvent e)
        {
            StartOfEveryTurn(this, new StatusAtTheStartOfATurn {TurnNumber = e.Status.Time});
        }
    }

    public class StatusAtTheStartOfATurn : EventArgs
    {
        public long TurnNumber { get; set; }
    }

I also don't want this to blow up if no handlers have been assigned to this event, so I write an additional specification:

    public class when_robocode_upates_the_turn_status_on_a_robot_that_has_nothing_listening_
                            to_the_start_of_every_turn : with_robot_set_up
    {
        Establish context = () =>
        {           
            turnNumber = 34;
            robotStatus = RobocodeSpy.CreateARobotStatus(turnNumber);
            statusEvent = new StatusEvent(robotStatus);
        };

        Because of = ()=> caughtException = Catch.Exception(() => robot.OnStatus(statusEvent));

        It should_not_fail = () =>
            caughtException.ShouldBeNull();

        static StatusEvent statusEvent;
        static RobotStatus robotStatus;
        static long turnNumber;
        static Exception caughtException;
    }

This currently fails with a NullReferenceException, so I make this pass via the robot's constructor:

    public class SlayerRobot : Robot, ImARobot
    {
        public event EventHandler<StatusAtTheStartOfATurn> StartOfEveryTurn;

        public SlayerRobot()
        {
            ServiceLocator = new ServiceLocator();
            StartOfEveryTurn += (s,e)=> {};
        }
    }

I've now got my own hook into the start of every turn and I've described this in my own terms, making it more verbose and a bit more intuitive.

I now need to get my own RobotEngine class to transform this into an observable, so again begin with adding to a previous specification for when the robot engine is initialised:

    public class when_initialising_a_robot_engine : with_a_robot_engine
    {
        Because of = () => robotEngine.Initialise(robot);

        It should_tell_the_statistics_writer_to_ensure_we_have_clean_statistics = () =>
            statisticsManager.AssertWasCalled(s => s.EnsureWeStartWithCleanStatistics());

        It should_ensure_that_we_have_something_to_observe_the_start_of_each_turn = () =>
            robotEngine.AtTheStartOfEachTurn.ShouldNotBeNull();    
    }

    public class with_a_robot_engine
    {
        Establish context = () =>
        {
            statisticsManager = MockRepository.GenerateStub<IManageStatistics>();
            robotEngine = new RobotEngine(statisticsManager);
            robot = MockRepository.GenerateStub<ImARobot>();
        };

        protected static ImARobotEngine robotEngine;
        protected static ImARobot robot;
        protected static IManageStatistics statisticsManager;
    }

The following simple code introduced in the Initialise method makes this specification pass:

    public class RobotEngine : ImARobotEngine
    {
        readonly IManageStatistics statisticsManager;

        public RobotEngine(IManageStatistics statisticsManager)
        {
            this.statisticsManager = statisticsManager;
        }

        public void Initialise(ImARobot robot)
        {
            statisticsManager.EnsureWeStartWithCleanStatistics();
            CreateObservableForTheStartOfEachTurnFromTheRobot(robot);
        }

        void CreateObservableForTheStartOfEachTurnFromTheRobot(ImARobot robot)
        {
            AtTheStartOfEachTurn = Observable.Return<IEvent<StatusAtTheStartOfATurn>>(null);
        }

        public IObservable<IEvent<StatusAtTheStartOfATurn>> AtTheStartOfEachTurn 
                                                             { get; private set; }
    }

I have an observable, but it is not tied up to the robot's event, so I need another specification for that:

    public class when_the_start_of_a_turn_occurs : with_a_robot_engine
    {
        Establish context = () => 
        {
            expectedTurnNumber = 784;
            statusAtTheStartOfATurn = new StatusAtTheStartOfATurn 
                                           { TurnNumber = expectedTurnNumber};

            robotEngine.Initialise(robot);
            robotEngine.AtTheStartOfEachTurn.Subscribe(t => 
                observedTurnNumber = (t==null) ? 0 : t.EventArgs.TurnNumber);
        };

        Because of = () => robot.Raise(x => x.StartOfEveryTurn += null, 
                                             robot, statusAtTheStartOfATurn);

        It should_be_able_to_observe_the_start_of_the_turn = () =>
            observedTurnNumber.ShouldEqual(expectedTurnNumber);

        static StatusAtTheStartOfATurn statusAtTheStartOfATurn;
        static long expectedTurnNumber;
        static long observedTurnNumber;
    }

I first initialise the robot engine and then subscribe to the IObservable<IEvent<StatusAtTheStartOfATurn>> where the EventArgs of the object used in the subscription is of type StatusAtTheStartOfATurn.
I then raise an event using Rhino Mocks sending in my constructed status.
I can then assert that the observed turn number is the expected one - this specification fails, so I can now replace the implementation in RobotEngine with the real one:

        void CreateObservableForTheStartOfEachTurnFromTheRobot(ImARobot robot)
        {
            AtTheStartOfEachTurn = Observable.FromEvent<StatusAtTheStartOfATurn>(
                                             ev => robot.StartOfEveryTurn += ev,
                                             ev => robot.StartOfEveryTurn -= ev);            
        }

My specification passes and I now have an observable created from the OnStatus() event of the robocode robot.
In my next post I'll explore how to subscribe to this for turn fifty to pass my original acceptance test.

Here's the RobotEngine code in full:

    public class RobotEngine : ImARobotEngine
    {
        readonly IManageStatistics statisticsManager;

        public RobotEngine(IManageStatistics statisticsManager)
        {
            this.statisticsManager = statisticsManager;
        }

        public void Initialise(ImARobot robot)
        {
            statisticsManager.EnsureWeStartWithCleanStatistics();
            CreateObservableForTheStartOfEachTurnFromTheRobot(robot);
        }

        void CreateObservableForTheStartOfEachTurnFromTheRobot(ImARobot robot)
        {
            AtTheStartOfEachTurn = Observable.FromEvent<StatusAtTheStartOfATurn>(
                                             ev => robot.StartOfEveryTurn += ev,
                                             ev => robot.StartOfEveryTurn -= ev);            
        }

        public IObservable<IEvent<StatusAtTheStartOfATurn>> AtTheStartOfEachTurn 
                                             { get; private set; }
    }



2 comments:

  1. Jason Hyland August 18, 2010 at 9:05 AM

    I bow down to your awesomeness, and whimper at my own inadequacies :)

     
  2. Justin Davies August 19, 2010 at 9:16 AM

    Your inadequacies are more than hard to find gentle sir, but I look forward to their evaluation on the battlefield. Let the battle commence!

    You may also have noticed that this robot doesn't do anything yet!