Programming uses maths. It’s hard to ignore sometimes, especially when modelling something that is inherently graphical and position based such as a game.
I’ve decided to give the models three different position indicators – PositionX, PositionY and PositionZ. This will describe their exact position anywhere on the board – including on a ladder, a walkway or the ground. When they are given instructions to move, they should change those position variables accordingly – but not past the maximum move distance.
My two options are to either pass in a distance and direction (both X direction and Y direction, like an aircraft would need to navigate) or a new set of position points – in essence, saying precisely ‘go here’. In the first, the new position would need to be calculated, and in the second the distance would need to be calculated. Either way, I’d need to write both sets of code at some point.
I think that it will be easier to back-track the distance and directions from a set of positions than it would be the other way around. Transforming a simple co-ordinate of (X, Y, Z) – where X is East-West, Y is North-South and Z is Up-Down – such as (1, 2, 1) to a new position of (2, 1, 2) tells me that the model has moved up a slope to the North-East for a distance of… actually, that bit gets harder. But it’s at least easy to work out with trigonometry!
On a two-dimensional plane, it is fairly simple to work out – as on the (hastily scribbled, not-to-scale) diagram to the left. This brings back distant memories of Pythagoras – a2 + b2 = c2 – or, the square root of the difference in X + the difference in Y is the distance between the two points.
On a three-dimensional world, this is a bit more complicated – it has to be reduced to two different two-dimensional triangles to calculate the distance. The difference between two points is the ‘difference in X’ value of the second triangle. Another way of putting it would be √ (√ ((X1 – X2)2 + (Y1 – Y2)2) + (Z1 – Z2)2). Excellent.
In my original example then, the answer would be 1.554 arbitrary units. I could work out the directions (angles) by using more of my dusty trigonometric knowledge, but I only need to know the distance at this point. All I need to know are some test values that give a value above a model’s Movement rate and some under, so I can test the outcomes of the MoveModel method. In the case of overshooting a model’s permitted movement, I think I’ll throw a new exception. I can see (a few pages down the line) that the model is going to need to be able to tell it’s distance from other objects anyway, so this could be checked before attempting to move the model.
Time for a test! These values will move the model just a smidgen over 4 units.
[TestMethod]
public void MoveModel_CannotMoveFurtherThanModelsMovement()
{
bool correctExceptionThrown = false;
try
{
Mock<IGameManager> mockGameManager = new Mock<IGameManager>();
MovementManager movementManager = new MovementManager(mockGameManager.Object);
Model testModel = new Model();
testModel.Movement = 4;
decimal newX = 5;
decimal newY = 6;
decimal newZ = 3;
movementManager.MoveModel(testModel, newX, newY, newZ);
}
catch (ArgumentException ex)
{
correctExceptionThrown = true;
}
Assert.IsTrue(correctExceptionThrown);
}
The code to pass this in the MovementManager is:
public void MoveModel(Model testModel, float newX, float newY, float newZ)
{
if (testModel.Movement < testModel.GetDistanceFrom(newX, newY, newZ))
{
throw new ArgumentException("The model cannot move further than it's Movement rate.");
}
}
And in the Model itself is:
public float PositionX { get; set; }
public float PositionY { get; set; }
public float PositionZ { get; set; }
public double GetDistanceFrom(float positionX, float positionY, float positionZ)
{
double distance = 0;
double differenceX = this.PositionX - positionX;
double differenceY = this.PositionY - positionY;
double differenceZ = this.PositionZ - positionZ;
distance = Math.Sqrt(Math.Sqrt((differenceX * differenceX) + (differenceY * differenceY)) + (differenceZ * differenceZ));
return distance;
}
I can’t help but think that I am still over-designing this, but it feels about right to me. Finally, the test to ensure that the model actually does move:
[TestMethod]
public void MoveModel_PositionHasChanged()
{
Mock<IGameManager> mockGameManager = new Mock<IGameManager>();
MovementManager movementManager = new MovementManager(mockGameManager.Object);
Model testModel = new Model();
testModel.Movement = 4;
testModel.PositionX = 0;
testModel.PositionY = 0;
testModel.PositionZ = 0;
float newX = 5;
float newY = 4;
float newZ = 3;
movementManager.MoveModel(testModel, newX, newY, newZ);
Assert.AreEqual(5, testModel.PositionX);
Assert.AreEqual(4, testModel.PositionY);
Assert.AreEqual(3, testModel.PositionZ);
}
Which requires a change to MoveModel –
public void MoveModel(Model testModel, float newX, float newY, float newZ)
{
if (testModel.Movement < testModel.GetDistanceFrom(newX, newY, newZ))
{
throw new ArgumentException("The model cannot move further than it's Movement rate.");
}
else
{
testModel.PositionX = newX;
testModel.PositionY = newY;
testModel.PositionZ = newZ;
}
}
Next, the situation that a model may move in two goes – and cannot go above it’s movement rate in both chunks. For this, we’ll have to record the distance moved.
[TestMethod]
public void MoveModel_TotalMovementInASingleTurnCannotExceedModelMovement_ThrowsArgumentException()
{
bool correctExceptionThrown = false;
try
{
Mock<IGameManager> mockGameManager = new Mock<IGameManager>();
MovementManager movementManager = new MovementManager(mockGameManager.Object);
Model testModel = new Model();
testModel.Movement = 4;
testModel.PositionX = 0;
testModel.PositionY = 0;
testModel.PositionZ = 0;
float newX = 5;
float newY = 4;
float newZ = 3;
movementManager.MoveModel(testModel, newX, newY, newZ);
movementManager.MoveModel(testModel, 0, 0, 0);
}
catch (ArgumentException ex)
{
correctExceptionThrown = true;
}
Assert.IsTrue(correctExceptionThrown);
}
Movement of a model is starting to take on several different, inter-related actions so I’m going to move that stuff into the Model class itself. MoveModel in the MovementManager becomes simpler:
public void MoveModel(Model testModel, float newX, float newY, float newZ)
{
if (testModel.Movement < testModel.GetDistanceFrom(newX, newY, newZ) + testModel.TotalDistanceMoved)
{
throw new ArgumentException("The model cannot move further than it's Movement rate.");
}
else
{
testModel.MoveModel(newX, newY, newZ);
}
}
And the addition to the Model class is:
public double TotalDistanceMoved { get; private set; }
public void MoveModel(float positionX, float positionY, float positionZ)
{
this.TotalDistanceMoved += GetDistanceFrom(positionX, positionY, positionZ);
this.PositionX = positionX;
this.PositionY = positionY;
this.PositionZ = positionZ;
}
Next is to make sure we can make multiple moves without going over the limit and throwing an exception:
[TestMethod]
public void MoveModel_MakeTwoSmallMovementsWithoutGoingOverMovementRate()
{
bool correctExceptionThrown = false;
try
{
Mock<IGameManager> mockGameManager = new Mock<IGameManager>();
MovementManager movementManager = new MovementManager(mockGameManager.Object);
Model testModel = new Model();
testModel.Movement = 4;
testModel.PositionX = 0;
testModel.PositionY = 0;
testModel.PositionZ = 0;
float newX = 1;
float newY = 1;
float newZ = 0;
movementManager.MoveModel(testModel, newX, newY, newZ);
movementManager.MoveModel(testModel, 0, 0, 0);
}
catch (ArgumentException ex)
{
correctExceptionThrown = true;
}
Assert.IsFalse(correctExceptionThrown);
}
This doesn’t need any new code, it just proves that the existing code still works. Finally, there needs to be a check that the TotalDistanceMoved will reset to 0 at the beginning of each turn. What is going to be the trigger for that? Currently, the only place that a new turn is known about is the GameManager, and the IncrementPhase method. The question is, therefore, how should the model be informed of this change? Should the GameManager contain a collection of all the models currently in the game, so the IncrementPhase method can just iterate that collection? Or should models be held elsewhere, and subscribe to a ‘new turn’ event that is triggered? I think it makes most sense for the models to be stored in the GameManager – at least at this time – and for them to expose a ‘new turn’ method.
I have become aware as I am refactoring this code around that I’m starting to break the isolation of the unit tests – some tests are calling methods in classes that are not the class under test. This will obviously need to be refactored, but I’d like to finish the current train of thought first. To do that properly though, I need to add a new test class – ModelTests – and test the NewTurn method on that as well as making sure the GameManager calls NewTurn on each of it’s (mocked) Models. Testing these two things becomes an integration test, and cannot guarantee which class/method is actually responsible for failing a test.
[TestMethod]
public void IncrementTurnPhase_CallsNewTurnOnModels()
{
GameManager manager = new GameManager(2);
Mock<IModel> mockModel = new Mock<IModel>();
manager.Models.Add(mockModel.Object);
manager.IncrementPhase(); // Go to Shooting
manager.IncrementPhase(); // Go to Close Combat
manager.IncrementPhase(); // Go to Recovery
manager.IncrementPhase(); // Go back to Movement
mockModel.Verify(item => item.NewTurn(), Times.Once());
}
In the above test, I am adding a mock model into the GameManager, then verifying that the NewTurn method is called once. I add the Models property, create an IModel interface, change the IncrementPhase code to
public void IncrementPhase()
{
if (CurrentPhase == TurnPhase.Recovery)
{
CurrentPhase = TurnPhase.Movement;
if (CurrentPlayersTurn == NumberOfPlayers)
{
CurrentPlayersTurn = 1;
}
else
{
CurrentPlayersTurn++;
}
foreach (IModel model in this.Models)
{
model.NewTurn();
}
}
else
{
CurrentPhase++;
}
}
And… four tests fail on me!
This is exactly the purpose of unit testing. In this case, I realised that the Models collection (List<IModel>) hadn’t been initialised – these tests suddenly failing are a result of the turn ending and attempting to iterate through a collection that hasn’t been set up yet – a NullReferenceException. One quick tweak to the constructor, and they’re all passing again.
Finally, the test on the Model side to make sure that it sets TotalDistanceMoved back to zero.
[TestClass]
public class ModelTests
{
[TestMethod]
public void NewTurn_SetsDistanceMovedToZero()
{
Model testModel = new Model();
testModel.TotalDistanceMoved = 4;
testModel.NewTurn();
Assert.AreEqual(0, testModel.TotalDistanceMoved);
}
}
And the code in Model being insultingly simple:
public void NewTurn()
{
this.TotalDistanceMoved = 0;
}
Thirteen tests so far, and all passing. A quick lesson in how unit tests can quickly point to something going awry – though I am sure more important lessons will come along as I move through the project – and we’re almost to the end of page 10 in the rulebook. This has been a long section, and it’s a good point to leave it at.
This is a learning project I am documenting in order to teach myself TDD and unit testing – as I publish this, I have already written many parts in advance but I want to know what I’m doing wrong! If you see improvements or corrections, either to the code or the process, please leave a comment and let me know! Introduction to this project can be found here.