the robocode decision context

In my robocode series I've been exploring separation of concerns and domain driven design I'm ready to write the first of my bounded contexts that I described in an earlier post. This context will be responsible for making decisions about enemies on the battlefield and as such the dialect will hopefully reflect this.

I'm going to start with the decision state keeper in the decision namespace and context:

    [Subject(typeof(DecisionStateKeeper))]
    public class when_the_decision_state_keeper_is_created
    {
        Because of = () => decisionStateKeeper = new DecisionStateKeeper();

        It should_ensure_that_we_have_something_to_observe_for_decision_making = () =>
            decisionStateKeeper.Situation.ShouldNotBeNull();

        static IKeepDecisionState decisionStateKeeper;
    }

I want to ensure that an observable is ready for use once the class is constructed:

    public class DecisionStateKeeper : IKeepDecisionState
    {
        public DecisionStateKeeper()
        {
            CreateObservableForDecisionState();
        }

        public IObservable<IEvent<DecisionState>> Situation { get; private set; }

        event EventHandler<DecisionState> DecisionStateHasUpdated;

        void CreateObservableForDecisionState()
        {
            Situation = Observable.FromEvent<DecisionState>(
                                             ev => this.DecisionStateHasUpdated += ev,
                                             ev => this.DecisionStateHasUpdated -= ev);
        }
    }

Next, I want any observers to subscribe to the observable when it has been created:

    [Subject(typeof(DecisionStateKeeper))]
    public class when_the_decision_state_keeper_is_created
    {
        Establish context = () =>
        {
          firstDecisionMakingObserver = MockRepository.GenerateStub<IObserveToMakeDecisions>();
          secondDecisionMakingObserver = MockRepository.GenerateStub<IObserveToMakeDecisions>();
          decisionObservers = new List<IObserveToMakeDecisions>
                                    {firstDecisionMakingObserver, secondDecisionMakingObserver};
        };

        Because of = () => decisionStateKeeper = new DecisionStateKeeper(decisionObservers);

        It should_ensure_that_we_have_something_to_observe_for_decision_making = () =>
            decisionStateKeeper.Situation.ShouldNotBeNull();

        It should_tell_the_first_observer_to_make_its_observations = () =>
            firstDecisionMakingObserver.AssertWasCalled(o => o.Observe(decisionStateKeeper));

        It should_tell_the_second_observer_to_make_its_observations = () =>
            secondDecisionMakingObserver.AssertWasCalled(o => o.Observe(decisionStateKeeper));

        static IKeepDecisionState decisionStateKeeper;
        static IEnumerable<IObserveToMakeDecisions> decisionObservers;
        static IObserveToMakeDecisions firstDecisionMakingObserver;
        static IObserveToMakeDecisions secondDecisionMakingObserver;
    }

The implementation is:

    public class DecisionStateKeeper : IKeepDecisionState
    {
        public DecisionStateKeeper(IEnumerable<IObserveToMakeDecisions> decisionMakingObservers)
        {
            CreateObservableForDecisionState();

            foreach(var observer in decisionMakingObservers)
            {
                observer.Observe(this);
            }
        }

        public IObservable<IEvent<DecisionState>> Situation { get; private set; }

        event EventHandler<DecisionState> DecisionStateHasUpdated;

        void CreateObservableForDecisionState()
        {
            Situation = Observable.FromEvent<DecisionState>(
                                             ev => this.DecisionStateHasUpdated += ev,
                                             ev => this.DecisionStateHasUpdated -= ev);
        }
    }

I'm now ready to implement my original observer I had that wrote statistics out when turn 50 was reached. So I rework that original set of specifications (the originals can be seen in iobservable<t> with robocode):

    [Subject(typeof(BattleReachedAReasonableTurnObserver))]
    public class when_at_the_start_of_turn_fifty : with_an_observer
    {
        Establish context = () => WithADecisionStateAtTurnNumber(50);

        It should_subscribe_to_the_observable_and_recognise_turn_fifty = () =>
            observer.ReachedTurnFifty.ShouldBeTrue();

        It should_ask_the_statistics_manager_to_write_the_statistics = () =>
            statisticsManager.AssertWasCalled(m => m.WriteStatistics(true));
    }

    [Subject(typeof(BattleReachedAReasonableTurnObserver))]
    public class when_at_the_start_of_turn_fourty_nine : with_an_observer
    {
        Establish context = () => WithADecisionStateAtTurnNumber(49);

        It should_subscribe_to_the_observable_but_not_have_recognised_turn_fifty = () =>
            observer.ReachedTurnFifty.ShouldBeFalse();
    }

    [Subject(typeof(BattleReachedAReasonableTurnObserver))]
    public class when_starting_at_turn_fifty_one_and_fifty_never_happened : with_an_observer
    {
        Establish context = () => WithADecisionStateAtTurnNumber(51);

        It should_subscribe_to_the_observable_and_not_have_recognised_turn_fifty = () =>
            observer.ReachedTurnFifty.ShouldBeFalse();
    }

    public abstract class with_an_observer
    {
        Establish context = () =>
        {
            decisionStateEvent = MockRepository.GenerateStub<IEvent<DecisionState>>();
            decisionStateKeeper = MockRepository.GenerateStub<IKeepDecisionState>();
            statisticsManager = MockRepository.GenerateStub<IManageStatistics>();
            observer = new BattleReachedAReasonableTurnObserver(statisticsManager);
        };

        Because of = () => observer.Observe(decisionStateKeeper);

        protected static IEvent<DecisionState> decisionStateEvent;
        protected static IObservable<IEvent<DecisionState>> decisionStateObservable;
        protected static IKeepDecisionState decisionStateKeeper;
        protected static BattleReachedAReasonableTurnObserver observer;
        protected static IManageStatistics statisticsManager;
        protected static DecisionState decisionState;

        protected static void WithADecisionStateAtTurnNumber(int turnNumber)
        {
            decisionState = new DecisionState() { TheNumberOfTurnsSoFar = turnNumber };
            decisionStateEvent.Stub(s => s.EventArgs).Return(decisionState);
            situation = Observable.Return(decisionStateEvent);
            decisionStateKeeper.Stub(d => d.Situation)
                               .Return(situation);
        }
    }

I've cleaned the specification up a bit, but the main changes are simply moving from the old observable to to the new one in the new context. The observer now implements the new interface and thus I can pass in the decision state keeper.
Here is the revised implementation:

    public class BattleReachedAReasonableTurnObserver : IObserveToMakeDecisions
    {
        readonly IManageStatistics statisticsManager;

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

        public void Observe(IKeepDecisionState decisionStateKeeper)
        {
            decisionStateKeeper.Situation
                               .Where(d => d.EventArgs.TheNumberOfTurnsSoFar == 50)
                               .Subscribe(s =>
                               {
                                   ReachedTurnFifty = true;
                                   statisticsManager.WriteStatistics(ReachedTurnFifty);
                               });
        }

        public bool ReachedTurnFifty { get; private set; }
    }

This will ensure I pass my original acceptance test that the robot should not be disabled at the start of a battle - once I hook up the translator to this, it should all be ready to go.

I also have a StartOfTheBattleObserver which detects the first turn number and issues instructions to the robot to begin scanning. Issuing instructions is outside the responsibilities of this context, so I'm not going to port that across to the context just yet and will review this in good time.

The next post will cover the context translator that will hopefully pull it all together to pass my acceptance test once again.



0 comments: