Learning Unit Testing IX – Moving On With Movement

Having gotten those knotty problems with world management, distances, directions, etc all out of the way I can finally move off of page 10. So what’s next? Page 11! Hooray!

It begins with a description of a charge move. A charge works like a run, at double-move rate, with the model (hopefully) ending up in contact with an enemy, or with a low wall that is also in contact with the enemy. The movement side of things should be fairly easy, but describing ‘base contact’ might be slightly more difficult.

In the real game, a model’s base is usually a circle 25mm in diameter. This is close enough to one inch to be treated as one of the arbitrary units I’m using for movement and measurement, so a model can be described as having a width of 1. If I choose the location to describe the centre-point of a model’s base, then ‘base contact’ would be a point equal to half the charger’s width plus half the target’s width, to allow for future models that may be bigger or smaller than a normal one (I’m fondly thinking of Ogryns!)

I started skirting around the issue slightly in the last post, but I need to seriously consider scenery and models as things with width and height, more than points on a virtual graph. The scenery will also need a different method of determining base contact, since it won’t often have a circular base.

It’s taking me a while to work out exactly which direction (no pun intended) to go with this. A charge sounds exactly like a regular run move, except you can (and are expected to!) get within 8” of an enemy model without stopping, and it declares that the model “Can do nothing for the rest of the turn.” To that end, I believe that only three new tests are needed, on a ‘charge’ method – one that makes sure a charging model can approach within 8” of an enemy, one that checks that an ‘isCharging’ flag is set so that the model can’t take any more actions this turn, and one that checks that that flag is removed at the beginning of a turn.

The first test – that the model doesn’t stop moving close to an enemy – is almost identical to the one that checks that it does stop moving close to an enemy, but with a different expected final position and no exception thrown.

[TestMethod]
public void Charge_CanGetWithin8InchesOfEnemy()
{
Mock<IGameManager> mockGameManager = new Mock<IGameManager>();
Mock<IObjectManager> mockObjectManager = new Mock<IObjectManager>();
Model enemyModel = new Model(1, mockGameManager.Object, mockObjectManager.Object);
enemyModel.Location.X = 9;
enemyModel.Location.Y = 0;
enemyModel.Location.Z = 0;
mockGameManager.Setup(item => item.Models).Returns(new List<IModel>() { enemyModel });
Model testModel = new Model(2, mockGameManager.Object, mockObjectManager.Object);
testModel.Movement = 4;
testModel.Location.X = 0;
testModel.Location.Y = 0;
testModel.Location.Z = 0;
bool exceptionThrown = false;
try
{
mockObjectManager.Setup(item => item.GetDistanceBetween(testModel.Location, It.IsAny<float>(), It.IsAny<float>(), It.IsAny<float>())).Returns(8);
mockObjectManager.Setup(item => item.GetPointOfIntersection(testModel.Location, It.IsAny<LocationPoint>(), enemyModel.Location, 8)).Returns(new LocationPoint(1, 0, 0));
mockObjectManager.Setup(item => item.GetLineOfSight(testModel, enemyModel)).Returns(1);
testModel.Charge(8, 0, 0);
}
catch (MovementException ex)
{
exceptionThrown = true;
}
Assert.IsFalse(exceptionThrown);
Assert.AreEqual(8, testModel.Location.X);
Assert.AreEqual(0, testModel.Location.Y);
Assert.AreEqual(0, testModel.Location.Z);
}

The code to pass this test is extremely simple:

private bool _isCharging = false;
public void Charge(float positionX, float positionY, float positionZ)
{
_isCharging = true;
MoveModel(positionX, positionY, positionZ);
}

And a quick change to the ValidateMove method to check if the model is charging before validating the ‘within 8” of an enemy’ rule:

if (this.TotalDistanceMoved + distanceToPoint > this.Movement && _isCharging == false)

I almost feel silly for thinking it would be any harder than that, but then I was originally worrying about the base sizes, low wall restriction, etc, that is all part of ‘being in base contact’ – as far as Movement is concerned, it doesn’t matter if the models are in base contact after a charge, only that a charge was attempted. The next test, then, checks that the flag is set:

[TestMethod]
public void Charge_SetsIsChargingFlag()
{
Mock<IGameManager> mockGameManager = new Mock<IGameManager>();
Mock<IObjectManager> mockObjectManager = new Mock<IObjectManager>();
Model testModel = new Model(1, mockGameManager.Object, mockObjectManager.Object);
testModel.Movement = 4;
testModel.Location.X = 0;
testModel.Location.Y = 0;
testModel.Location.Z = 0;
mockObjectManager.Setup(item => item.GetDistanceBetween(testModel.Location, It.IsAny<float>(), It.IsAny<float>(), It.IsAny<float>())).Returns(8);
testModel.Charge(8, 0, 0);
Assert.IsTrue(testModel.IsCharging);
}

The code only needed to change the private _isCharging to a public IsCharging property, and again – straight into the pass. To make sure the NewTurn method resets the charging flag:

[TestMethod]
public void NewTurn_SetsIsChargingToFalse()
{
Mock<IGameManager> mockGameManager = new Mock<IGameManager>();
Mock<IObjectManager> mockObjectManager = new Mock<IObjectManager>();
Model testModel = new Model(1, mockGameManager.Object, mockObjectManager.Object);
testModel.Movement = 4;
testModel.Charge(8, 0, 0);
Assert.IsTrue(testModel.IsCharging);
testModel.NewTurn();
Assert.IsFalse(testModel.IsCharging);
}

Code to pass that test is just insultingly simple. At this time, I realised that the IsRunning flag might not have been set back to false at the beginning of a new turn – there’s no test for it, so I’ll add that in now.

[TestMethod]
public void NewTurn_SetsIsRunningToFalse()
{
Mock<IGameManager> mockGameManager = new Mock<IGameManager>();
mockGameManager.Setup(item => item.Models).Returns(new List<IModel>());
Mock<IObjectManager> mockObjectManager = new Mock<IObjectManager>();
Model testModel = new Model(1, mockGameManager.Object, mockObjectManager.Object);
testModel.Movement = 4;
testModel.MoveModel(8, 0, 0);
Assert.IsTrue(testModel.IsRunning);
testModel.NewTurn();
Assert.IsFalse(testModel.IsRunning);
}

And as I predicted – it fails! The passing code should, again, be fairly easy to figure out. So I now have all the basics of charging, without the ‘is in base contact’ checks which will mostly be mathematical functions, and abstracted out without really implementing them (as I decided earlier).

The next challenge is Hiding, then we can move away from page 11 – much faster than getting away from page 10 was, at any rate! As I see it, the following things are necessary to check for hiding:

  • A model can choose to hide if it ends it’s turn in ‘reasonable’ cover
  • A model can move about and stay hidden, as long as it doesn’t leave the ‘reasonable’ cover.
  • A model cannot run or charge and hide in the same turn.
  • If an enemy model moves to a position it can see the hiding model, it ceases to be hidden.
  • A model ceases to be hidden if an enemy is within it’s Initiative value in inches of the model.

I will define ‘reasonable’ cover as being in at least 40% cover – this value could easily be tweaked later. I had decided while brainstorming before that line-of-sight shouldn’t return a boolean value, rather a percentage of the target that is visible. In systems that can determine only ‘visible’ and ‘not visible’, the IObjectManager would obviously return 0 and 1 and nothing in between. More subtle systems would give a range of values, meaning that cover saves (when I get to shooting) will take account of partial cover, heavy cover, etc. The first test then will check line of sight from every enemy, to make sure that it is not more than 60% visible to any of them.

[TestMethod]
public void Hide_SetsFlagIfInReasonableCover()
{
Mock<IGameManager> mockGameManager = new Mock<IGameManager>();
Mock<IObjectManager> mockObjectManager = new Mock<IObjectManager>();
Model enemyModel = new Model(1, mockGameManager.Object, mockObjectManager.Object);
mockGameManager.Setup(item => item.Models).Returns(new List<IModel>() { enemyModel });
Model testModel = new Model(2, mockGameManager.Object, mockObjectManager.Object);
bool exceptionThrown = false;
try
{
mockObjectManager.Setup(item => item.GetLineOfSight(It.IsAny<IModel>(), It.IsAny<IModel>())).Returns(0.5f);
testModel.Hide();
}
catch (HidingException ex)
{
exceptionThrown = true;
}
Assert.IsFalse(exceptionThrown);
Assert.IsTrue(testModel.IsHiding); }

Code to pass this test:

public void Hide()
{
List<IModel> enemyModels = (from models in this._gameManager.Models
where models.Player != this.Player
&& this._objectManager.GetLineOfSight(models, this) > 0.6
select models).ToList();
if (enemyModels.Count > 0)
{
HidingException ex = new HidingException();
ex.EnemyModels = enemyModels;
throw ex;
}
else
{
this.IsHiding = true;
}
}

Using some lovely LINQy goodness! The next test:

[TestMethod]
public void Hide_ThrowsExceptionIfInSight()
{
Mock<IGameManager> mockGameManager = new Mock<IGameManager>();
Mock<IObjectManager> mockObjectManager = new Mock<IObjectManager>();
Model enemyModel = new Model(1, mockGameManager.Object, mockObjectManager.Object);
mockGameManager.Setup(item => item.Models).Returns(new List<IModel>() { enemyModel });
Model testModel = new Model(2, mockGameManager.Object, mockObjectManager.Object);
bool exceptionThrown = false;
try
{
mockObjectManager.Setup(item => item.GetLineOfSight(It.IsAny<IModel>(), It.IsAny<IModel>())).Returns(1);
testModel.Hide();
}
catch (HidingException ex)
{
exceptionThrown = true;
Assert.AreEqual(enemyModel, ex.EnemyModels[0]);
}
Assert.IsTrue(exceptionThrown);
}

Needs no modification. Maybe I went a bit too far with the code writing, since I knew what the test would be in advance? I played with the figures to make sure that the test fails when it should.

The following tests should prevent the Hiding flag staying set when running or charging:

[TestMethod]
public void Hide_RemoveHidingFlagWhenRunning()
{
Mock<IGameManager> mockGameManager = new Mock<IGameManager>();
Mock<IObjectManager> mockObjectManager = new Mock<IObjectManager>();
mockGameManager.Setup(item => item.Models).Returns(new List<IModel>());
Model testModel = new Model(1, mockGameManager.Object, mockObjectManager.Object);
testModel.IsHiding = true;
testModel.Movement = 4;
testModel.MoveModel(6, 0, 0);
Assert.IsFalse(testModel.IsHiding);
}
[TestMethod]
public void Hide_RemoveHidingFlagWhenCharging()
{
Mock<IGameManager> mockGameManager = new Mock<IGameManager>();
Mock<IObjectManager> mockObjectManager = new Mock<IObjectManager>();
mockGameManager.Setup(item => item.Models).Returns(new List<IModel>());
Model testModel = new Model(1, mockGameManager.Object, mockObjectManager.Object);
testModel.IsHiding = true;
testModel.Movement = 4;
testModel.Charge(6, 0, 0);
Assert.IsFalse(testModel.IsHiding);
}

The code for both these tests is simple – change MoveModel and Charge to look like:

public void MoveModel(float positionX, float positionY, float positionZ)
{
try
{
ValidateMove(ref positionX, ref positionY, ref positionZ);
}
catch (Exception ex)
{
throw;
}
finally
{
this.TotalDistanceMoved += GetDistanceFrom(positionX, positionY, positionZ);
this.Location.X = positionX;
this.Location.Y = positionY;
this.Location.Z = positionZ;
if (this.TotalDistanceMoved > this.Movement)
{
this.IsHiding = false;
this.IsRunning = true;
}
}
}
public void Charge(float positionX, float positionY, float positionZ)
{
IsHiding = false;
IsCharging = true;
MoveModel(positionX, positionY, positionZ);
}

The next two tests check that a model can’t hide if it charged or ran this turn:

[TestMethod]
public void Hide_CannotHideIfRanInSameTurn()
{
Mock<IGameManager> mockGameManager = new Mock<IGameManager>();
Mock<IObjectManager> mockObjectManager = new Mock<IObjectManager>();
mockGameManager.Setup(item => item.Models).Returns(new List<IModel>());
Model testModel = new Model(1, mockGameManager.Object, mockObjectManager.Object);
testModel.IsRunning = true;
bool correctExceptionThrown = false;
try
{
testModel.Hide();
}
catch (HidingException ex)
{
correctExceptionThrown = true;
}
Assert.IsFalse(testModel.IsHiding);
Assert.IsTrue(correctExceptionThrown);
}
[TestMethod]
public void Hide_CannotHideIfChargedInSameTurn()
{
Mock<IGameManager> mockGameManager = new Mock<IGameManager>();
Mock<IObjectManager> mockObjectManager = new Mock<IObjectManager>();
mockGameManager.Setup(item => item.Models).Returns(new List<IModel>());
Model testModel = new Model(1, mockGameManager.Object, mockObjectManager.Object);
testModel.IsCharging = true;
bool correctExceptionThrown = false;
try
{
testModel.Hide();
}
catch (HidingException ex)
{
correctExceptionThrown = true;
}
Assert.IsFalse(testModel.IsHiding);
Assert.IsTrue(correctExceptionThrown);
}

I’m really liking how simple these tests are to pass at the moment. The code to solve both of those tests goes at the top of the Hide method, and is just a quick check:

if (this.IsCharging || this.IsRunning)
{
HidingException ex = new HidingException("Cannot hide if charged or ran this turn.");
throw ex;
}

This is getting a bit too long (I’m thinking that 2000 words, including code, should be a maximum limit on these). I’ll continue more Hiding tests next time!

Leave a comment

Your email address will not be published. Required fields are marked *