The speed system


My speed system will have the following characteristics:

  • All actors (who are able) should have at least one turn per game turn.
  • Faster actors should take their turns before slower actors.
  • If Bob is twice as fast as Fred then Bob should have two turns per Fred’s one turn.
  • The game turn ends when all actors have had at least one turn.

To implement the above features I came up with the following unit tests. For all the unit tests I create a GameTurn object that is initialized with two identical actors. Each of these actors have a speed of 1 be default.

The GameTurn class is responsible for implementing the speed system features. In order for me to test the functionality I let the ProcessTurn method return a list of GameEvents completed during the turn. The GameEvent class holds information such as the actor, the action and the time interval at which a turn took place.

   50         // Every actor should have at least one turn per game turn

   51         [Test]

   52         public void TestEachActorHadLeastOneTurn()

   53         {

   54             var actions = gameTurn.ProcessTurn();

   55 

   56             Assert.IsTrue(actions.Any(x => x.Actor == actor1));

   57             Assert.IsTrue(actions.Any(x => x.Actor == actor2));

   58         }

In the above test I process the game turn and check that each actor has performed at least one action. The implementation for the above test is fairly trivial. We just loop through each of the actors and add their next action to a list and return it when we’re done.

   60         // Actors take their turns in sequence based on their speed

   61         [Test]

   62         public void TestEachActorTakesHisTurnInOrder()

   63         {

   64             actor1.Speed = 2;

   65             actor2.Speed = 3;

   66 

   67             var actions = gameTurn.ProcessTurn();

   68 

   69             Assert.IsTrue(actions[0].Actor == actor1);

   70             Assert.IsTrue(actions[1].Actor == actor2);

   71         }

In the above test I make sure that each actor is processed in the correct sequence. The sequence is determined by the actor speed. Lower speed values are processed before higher speed values. The implementation for the above test makes use of a priority queue that is keyed to the actor speed. The ProcessTurn method now asks the priority queue for the next actor until all the actors have had their turn.

   73         // The slowest actor should only act once per game turn

   74         [Test]

   75         public void TestSlowestActorHadOneTurn()

   76         {

   77             actor2.Speed = 2;

   78 

   79             var actions = gameTurn.ProcessTurn();

   80 

   81             Assert.AreEqual(1, actions.Count(x => x.Actor == actor2));

   82         }

In the above test we make sure that the slowest actor has only one turn. The game turn must end when the slowest actor has completed its turn. To implement this feature I evaluate the list of actions that have already been completed before processing the next actor. I stop processing actors when all the actors have had their turn.

   84         // The actor with half the speed should act twice per turn

   85         [Test]

   86         public void TestHalfSpeedActorHadTwoTurns()

   87         {

   88             actor2.Speed = 2;

   89 

   90             var actions = gameTurn.ProcessTurn();

   91 

   92             Assert.AreEqual(2, actions.Count(x => x.Actor == actor1));

   93             Assert.AreEqual(1, actions.Count(x => x.Actor == actor2));

   94         }

In the above test we make sure that actor1 had two turns while actor2 only had one turn. My first pass of the test failed with unexpected results. Each actor only completed one turn even though actor1 with twice the speed of actor2 was in the priority queue. The reason for the failure was that check that everyone had completed their turn was true even though actor1 had not completed it’s second turn. To solve this I added an additional check that the game turn would only end if the last actor had completed it’s turn and all other actors to act at the same time had completed theirs.

The final ProcessTurn method is implemented as follows.

   19         public IList<GameEvent> ProcessTurn()

   20         {

   21             var completedActions = new List<GameEvent>();

   22 

   23             InitializeCommandQueue();

   24 

   25             var lastTimeSlice = -1;

   26 

   27             // We process the command queue while there are actors to process

   28             while (commandQueue.Count > 0)

   29             {

   30                 // Get the next actor to process

   31                 var nextTimeInterval = commandQueue.Peek();

   32 

   33                 // stop processing if everyone has had a turn and we’re on a new time slice

   34                 if ((AllActorsHaveActed(completedActions))

   35                     && (lastTimeSlice != nextTimeInterval.Key))

   36                     break;

   37 

   38                 // Update the last time slice to the current time slice

   39                 lastTimeSlice = nextTimeInterval.Key;

   40 

   41                 // Remove dead actors from the queue

   42                 if (!nextTimeInterval.Value.IsAlive)

   43                 {

   44                     commandQueue.Dequeue();

   45                     continue;

   46                 }

   47 

   48                 // Get the next action from the actor

   49                 ICommand nextAction = nextTimeInterval.Value.GetNextAction();

   50                 if ((nextAction != null) && (nextAction.Execute()))

   51                 {

   52                     // Remove the actor from the queue if the action was successful

   53                     commandQueue.Dequeue();

   54                     // Add the successful command to the list of completed actions

   55                     completedActions.Add(new GameEvent

   56                         {   Actor = nextTimeInterval.Value,

   57                             Command = nextAction,

   58                             TimeSlice = nextTimeInterval.Key

   59                         });

   60                     // Add the actor to the queue incrementing the priority with the speed

   61                     commandQueue.Enqueue(

   62                         nextTimeInterval.Key + nextTimeInterval.Value.Speed,

   63                         nextTimeInterval.Value);

   64                 }

   65                 else

   66                     // We stop processing actions for this game loop cycle as

   67                     // the last action was unsuccessful

   68                     break;

   69             }

   70 

   71             return completedActions;

   72         }

Advertisements

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 )

Twitter picture

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

Facebook photo

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

Google+ photo

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

Connecting to %s