Drag and Drop in Unity Part Two

After getting a basic system of moving Items between different Containers, I needed to extend this slightly. First of all, I need some feedback of which container an item is going to, and also of restricting items to only some containers. In most games, only some containers are valid locations to drop an item.

Feedback

What I want is for the containers to highlight when an item is hovering over them and they can be dropped. To detect when an item is hovering, I gave all the Containers a BoxCollider2D component and all of the Items both a RigidBody2D component and a BoxCollider2D. Now apparently, the collider should size its mesh to the shape of the GameObject but since I am only using Panels, not sprites, this doesn’t seem to be the case. It’s not a problem, since the collider mesh can be edited manually.

The RigidBody2D components need to be set to Kinematic for the Body Type, so that the Unity engine doesn’t try to push them with gravity all over the place.

The BoxCollider2D elements on the Containers need to have Is Trigger set to true, so that the trigger methods fire.

Then on the DropHandler script I added the following methods (which are overrides of methods on the MonoBehavior base object so they don’t need any more interfaces):

private Color? _startingColour;
 
private void OnTriggerEnter2D(Collider2D collision)
{     var image = GetComponent<Image>();     _startingColour = _startingColour ?? image.color;     if (collision.transform.parent.gameObject != gameObject)     {         image.color = Color.red;     }
}
 
private void OnTriggerExit2D(Collider2D collision)
{     if (collision.transform.parent.gameObject != gameObject)         GetComponent<Image>().color = _startingColour.Value;
}

This will make the Container red when something is hovering, and white otherwise, but only if the container is not it’s original parent container. The _startingColour field is to be able to return to whatever it was before. The last part of the puzzle is to add the following to the OnDrop method in the DropHandler script:

GetComponent<Image>().color =

_startingColour ?? GetComponent<Image>().color;

So that after an item has been dropped, the container will return to it’s original colour.

Restrictions

Restrictions were surprisingly easy. First of all, everything got renamed either ContainerAA, ContainerBB, ItemAA and ItemBB so I could check conditionals without having to go through added a custom editor pane. Then, I added the CanDrop method to the DropHandler script:

public bool CanDrop()
{     return (DragHandler.ItemBeingDragged.name.Contains("AA") && gameObject.name.Contains("AA"))         || (DragHandler.ItemBeingDragged.name.Contains("BB") && gameObject.name.Contains("BB"));
}

And called it as part of the conditional checks on OnDrop, OnTriggerEnter2D and OnTriggerExit2D.

Et voila! Only some targets highlighted on hover, and those were the only ones that would accept the drop. Different items had different targets.

In further developing this, I can see that CanDrop will really be looking for a specific MonoBehavior type (for example, by calling ItemBeingDragged.GetComponent<Card>()) and then interrogating the properties against specific logic – different for each container. So in Solitaire, the CanDrop method would get the Card component out of the item being dragged and then check it’s suit and value against the current suit and value on it’s own stack to determine if the card can be dropped or not.

Drag and Drop in Unity Part One

I’m starting to experiment with Unity for a hobby project involving a card game.

In almost any card game, there are certain areas of the table for a collection of cards. Whether this is the vertical stack in Solitaire, the lands in Magic: The Gathering or a hidden hand of cards that aren’t ‘on’ the table, there are different areas that contain groups of cards. These cards will usually need to move between the card containers, so I figured the first thing to do would be to implement dragging and dropping. After watching a few video tutorials (the most useful was this one), I was ready to try some things out.

I started with a 2D game in Unity – card games are famously 2-dimensional. To this, I added a Panel and named it “Container”. Then I added a Panel inside the Container and named it “Item”. I made the Containers a little bigger than the Items so it was easier to see them, and duplicated this until there were 4.

I added a Grid Layout component to all of the “Container” objects and set the Child Alignment to ‘Middle Center’, then added a Constraint of ‘Fixed Row Count’ and the Constraint Count to 1 so that each container would only ever have one row of items.

Each “Item” needed a script – named DragHandler – to handle the dragging behaviour. The script describes how the object should behave when being dragged around, including how to begin dragging it in the first place. To do this it needs to reference the UnityEngine.EventSystems namespace, and implement the three interfaces IBeginDragHandler, IDragHandler and IEndDragHandler.

First of all, it needs some properties and fields set up.

public static GameObject ItemBeingDragged;
private Vector2 _startPosition;
private Transform _startParent;

This is almost verbatim from the tutorial video, and I will probably be refactoring that static GameObject field at some point in the future (it ought to be a property if it’s public).

public void OnBeginDrag(PointerEventData eventData)
{     ItemBeingDragged = gameObject;     _startPosition = transform.position;     _startParent = transform.parent;     GetComponent<CanvasGroup>().blocksRaycasts = false;
}

OnBeginDrag starts the process off by setting the ItemBeingDragged to the current GameObject, saving the start position (so it can be returned later if the drop is unsuccessful) and saving the original parent (same reason). Finally it finds the CanvasGroup component and stops blocking raycasts so that events can be fired through the item being dragged (this will be needed to drop it).

public void OnDrag(PointerEventData eventData)
{     transform.position = Input.mousePosition;
}

OnDrag just makes the dragged item follow the mouse cursor until it is dropped.

public void OnEndDrag(PointerEventData eventData)
{     ItemBeingDragged = null;     if (_startParent == transform.parent)     {         transform.position = _startPosition;     }     GetComponent<CanvasGroup>().blocksRaycasts = true; 
}

Finally, when the drag is over, the ItemBeingDragged is forgotten (set to null), and if the parent item at the end is the same as the beginning it is returned to it’s starting position. Finally, raycast blocking is restored so it can pick up further events.

The Container objects need a script too, which will also need to reference UnityEngine.EventSystems and implement an interface. This time, IDropHandler so we can drop items there.

public void OnDrop(PointerEventData eventData)
{     DragHandler.ItemBeingDragged.transform.SetParent(transform);
}

This is a simple script. It references the static ItemBeingDragged property from the DragHandler script, and sets its parent to whatever the drop target is.

With all this set up, I can drag Item panels from one Container to another. Or move a card from one location to another, eventually.

SQLite in ASP.NET Core WITHOUT Entity Framework

Try and search for ASP.NET Core and SQLite and you’ll get a dozen guides to using it with Entity Framework.

First of all, we’ll need an ASP.NET Core website and install the Nuget packages Microsoft.Data.Sqlite and System.Data.Sqlite.

I prefer to use Dapper for my database access (hence not wanting to use Entity Framework), so go ahead and install Dapper too.

Opening any connection in SQLite by default automatically creates the database file. Run this code:

var connectionStringBuilder = new SqliteConnectionStringBuilder();
connectionStringBuilder.DataSource = "MyNewDatabase.db";
using (var connection = new SqliteConnection(connectionStringBuilder.ConnectionString))
{     connection.Open();     var result = connection.Query<int>("SELECT @number;", new { number = 789 });
}

And you’ll see in the /bin/debug folder there is a new (empty) file called MyNewDatabase.db. Setting a breakpoint and inspecting the result variable will also reveal that the database is responding nicely to queries. It should have a list of a single item, and that item is the number 789.

Running basic queries for fun isn’t very exciting though. We need to be able to connect to the database, create tables and interrogate them. The way to do this with SQLite is through the command line. Since I’ve been playing with Linux, I loaded up Bash for Windows 10 and gave it a go.

To do it like that, run sudo apt-get install sqlite3 in your Bash shell to get the client application. Then navigate to the database file location (you can get to the main drives with cd /mnt/c, where c is your drive letter). Then hit sqlite3 MyNewDatabase.db, and you’re into command line SQL mode! All commands must be terminated with a semi-colon, but otherwise it’s easy to add tables, set them up, etc. I will be saving all the commands in a separate file so the scripts can be re-run if necessary, or if I choose to create a new database on the live environment and deploy to it separately.

A few hints from this site on using SQLite:

To list all tables, type .tables and to exit the command line type .quit.

Hobby Projects, and Why I Missed An Update

Whoops, missed a scheduled post back there. I was expecting to be able to add a write-up about the MWWS charity tournament in Builth Wells, but unfortunately was struck down ill that weekend and was unable to attend. I was extra disappointed because I was supposed to be driving two other players to the tournament, and we were unable to organise alternative transport.

I’m trying to work on building up a buffer of posts for the site to ensure I always have something ready to go ahead of time, but I’ve been working on a few hobby projects instead. One of these is a revival of my Gorkamorka/Necromunda skirmish engine in WPF, and is coming along nicely for the moment. It’s nowhere near finished, and it’s never going to have pretty graphics, but I’m practicing my architecture design on it (as a large, complicated project it will be ideal for that). The other is an actual public website: www.dreadballhub.com.

Right now, it’s a small website that only has two features. The first is an Xtreme team manager (this will need to be completely rewritten in a month or two when the Player Handbook comes out), that calculates an Xtreme team from the two sponsor options in the book. You can even download the finished roster as a PDF. It’s not entirely pretty, but it works.

The second feature is a relatively new one: a game tracker. Someone made one previously (back when DreadBall was barely out). They closed it down last June as they walked away from the game, citing irreconcilable balance issues and a difference between the support requested and the support given. A number of people have requested that the database be reopened.

I did consider building it earlier this year, but I had heard that Mantic were going to have an all-singing all-dancing ranking website to track players in tournaments, record games, and all of that jazz. Since it hasn’t appeared (and I’m still waiting on Windows Phone versions of the existing Mantic Digital apps) I thought “well blow it, I can write code” and dived into the project.

There’s more things that I want to add, but I figured it would be best to get the site out there and collect data. Even if it’s not publicly visible yet, it’s there and working behind the scenes.

One thing that I want to add (that will be difficult without an internet connection on my lunch/code breaks) is a way to login to the site (with Google/Twitter/etc) so that individual players can be identified as being great players. Identifying the opponent that you played against, however, will be much harder and I’ll need to think about the process before I do any work. Possibly each player will need a unique name of some kind after they’ve logged in to be identified by their opponents? I can also see the possibility of abuse, if people record massive wins against you that never happened, and so a system of verifying games becomes necessary as well as a way to record games against unregistered players.

So that’s what I’ve been up to instead of going to tournaments and writing blog posts. I’m still aiming to be at the Wales and South West Regional tournament in two and a half weeks (yikes, best get painting) and trying to decide if it’s worth trekking across the country for the South-East and Southern regionals too.

That address to remember is www.dreadballhub.com and feel free to suggest new features! I’m looking for new ways to expand it and make a useful community resource.

My thoughts on HTML5 at Microsoft TechDays 2011

I’m beginning to neglect this blog as much as the Year of Frugal Gaming that I also write for (check it out, our glorious leader Frugal Dave recently picked up a stylish blogger award! I’m thrilled to be in such company!) and I’ve written a post over there on what I’ve been up to gaming wise.

I’ve been lucky enough to go to TechDays in London again this year – I’m definitely the “community” guy in our team, seeking out user groups, conferences, blogs and podcasts. I’m of the mind that you need to be plugged into all these things to know what’s available. When all you have is a hammer everything looks like a nail, as the old saying goes, and I want to have as many different types of hammer as possible. It does come at a slightly bad time, however, as my wife is expecting to drop another baby in just over two weeks (and last night was a sleepless one of false alarms, before a 4:15 alarm in order to head to London for TechDays…) and I’m planning on staying in the capital tonight rather than travel all the way back to Bristol, and return to London again tomorrow. If I get “that call” I’ll have to race for the tube station and get back in time!

Anyway, I’ve already been to the Monday event on “The Web” (a discussion of HTML5, CSS3, IE9 and other sequels). I’m very impressed, but also a little melancholy. What I took away from there is that I can now create great looking sites without having an abundance of <div> elements and interlocking images to create a box with rounded corners, I can just do it in CSS. Except, for almost anything, our designers want their design to appear in every browser. This means that we can’t use the new hotness because it won’t show up in IE8 and below, which still have a considerable market share. We’re only now convincing people to let us build sites that degrade “gracefully” in IE6, since it takes a disproportionate amount of time to make a site look like it should in IE6. Now we’ve got the luxury of saying “That’s a bit too hard, I’m just going to make it square.” We can’t use the same excuses for IE8, which is the default browser for Windows 7 – only a couple of years old.

On a personal site, or if we had trendier clients – the kind who care more about seeming new and cutting edge, with things floating all over the page, the sort of thing that grinds to a halt on a slow PC – there would be plenty of scope for this new technology. But as it stands, HTML5 compliance is low enough that we won’t be able to pick it up just yet.

Microsoft did put on a great show though, getting in experts on CSS and HTML from outside their own company (heck, they had a speaker from Opera – probably because Mozilla or Google turned them down!) and giving a little poke at themselves about IE6. Oh, IE6.

I’m keen to get to use HTML5, but the day job isn’t going to be the place to do that. I’m still working on an intranet app that breaks – actual functionality breaks, not just styles – if it’s not used in IE7 (compatibility mode in 8 and 9 are fine), and our external websites need to look the same across browsers. We can’t just pretend that all IE users are on version 9, that’s not realistic and won’t make customers very happy. It doesn’t matter if it degrades gracefully, the design decisions would be so different in most cases (three <div> tags with separate background images for a repeated news item to give it rounded corners, or one <div> and no images to do it all with fancy CSS3?) that it can’t look “correct” without major javascript hacks which probably involve inserting all the HTML that we were going to in the first place, and still work in the newer HTML 5 browsers. The only difference is that it won’t have such pleasant markup for the developers (or the geeks who view source on websites they visit) to read.

This problem isn’t going to go away – Vista shipped with IE7, so that’s going to be supported for a fair while yet. IE6 shipped with XP, and since XP is still supported until about 2014 at last count. That means that IE6 is still officially supported by Microsoft until 2014. Vista won’t roll off until 2019 (at a guess), and Windows 7 (which contains IE8) will probably be into 2022 or so. For another five years (being optimistic), we have to assume that non-technical people (the majority of those that visit our company’s sites) will be using the browser they installed with their operating system and never upgrading, and the majority of market share will be held by non-HTML5 compliant browsers. Hopefully, Microsoft will make more websites like www.theie6countdown.com, which tells us when we can officially ditch IE6 country by country. It won’t be long, and we can assume that anyone hitting our sites from IE6 will be Chinese and therefore unlikely to buy our products!

Learning Unit Testing XI – Difficult Terrain Is Difficult

As the title clearly states, difficult terrain is going to be hard. It will involve a replacement of the ‘get distance’ code that takes into account that strict distances are not the same as movement distances – the idea that four inches of distance may cost five, six or more inches of movement (which is what counts for running, charging, and moving in general).

Because of this difficulty, my current inability to devise a solution, and my need to get moving on other things immediately (time on this project is limited, and I would rather move on than get stuck in design hell that maybe I should have sorted out sooner) I will be abandoning the Movement phase for the infinitely more interesting Shooting phase. It actually looks slightly easier, but I’m sure that’ll be remedied when I get to the rules for weapons and skills.

Unlike movement, which started to bog down the Model class, I’m going to try and keep all shooting related code in the shooting manager. Again, having an inkling of what the skills and weapons rules are going to do later, I believe this will make things a lot easier.

The first thing written down is defining who can shoot – Any model can shoot as long as it’s armed with a missile weapon, is not in hand-to-hand combat, can see a target, didn’t run or charge and is not hiding. This seems like a sensible thing to write a checking method for, and a little raft of tests. Time for a new test class, and also (finally) a test helper.

class TestHelper
{
public static IGameManager GetMockGameManager(IModel testModel, IObjectManager objectManager, int numberOfEnemies, int numberOfAllies)
{
Mock<IGameManager> mockGameManager = new Mock<IGameManager>();
List<IModel> modelList = new List<IModel>();
modelList.Add(testModel);
for (int i = 0; i < numberOfAllies; i++)
{
modelList.Add(new Model(testModel.Player, mockGameManager.Object, objectManager));
}
for (int i = 0; i < numberOfEnemies; i++)
{
modelList.Add(new Model(testModel.Player + 1, mockGameManager.Object, objectManager));
}
mockGameManager.Setup(item => item.Models).Returns(modelList);
return mockGameManager.Object;
}
}
[TestClass]
public class ShootingManagerTests
{
[TestMethod]
public void CanShoot_ReturnsTrue_IfEverythingCorrect()
{
Model model = new Model(1, null, null);
model.Weapons.Add(new MissileWeapon());
TestHelper.GetMockGameManager(model, null, 1, 0);
model.Location = new LocationPoint(0, 1, 0);
model.Direction = 180;
model.IsCharging = false;
model.IsHiding = false;
model.IsRunning = false;
ShootingManager shootingTest = new ShootingManager();
Assert.IsTrue(shootingTest.CanShoot(model));
}
}

Right, a lot of ‘generate class’ and ‘generate stub’ later (the ctrl+. shortcut is my friend) and the library compiles again. I intend for all missile weapons (that need special functionality not covered by the basic rules) to inherit from MissileWeapon, which ought to cover the majority of things. Weapons is a list of IWeapon (implemented by MissileWeapon), Direction is just an integer describing the angle the model faces (assuming a top-down view), and ShootingManager is quite an empty class at the moment. Following TDD pretty strictly, to pass this test it becomes:

public class ShootingManager
{
public bool CanShoot(Model model)
{
bool canShoot = true;
return canShoot;
}
}

But of course, that’s not quite right. Here’s a few more tests.

[TestMethod]
public void CanShoot_ReturnsFalse_IfRunning()
{
Model model = new Model(1, null, null);
model.Weapons.Add(new MissileWeapon());
TestHelper.GetMockGameManager(model, null, 1, 0);
model.Location = new LocationPoint(0, 1, 0);
model.Direction = 180;
model.IsCharging = false;
model.IsHiding = false;
model.IsRunning = true;
ShootingManager shootingTest = new ShootingManager();
Assert.IsFalse(shootingTest.CanShoot(model));
}
[TestMethod]
public void CanShoot_ReturnsFalse_IfCharging()
{
Model model = new Model(1, null, null);
model.Weapons.Add(new MissileWeapon());
TestHelper.GetMockGameManager(model, null, 1, 0);
model.Location = new LocationPoint(0, 1, 0);
model.Direction = 180;
model.IsCharging = true;
model.IsHiding = false;
model.IsRunning = false;
ShootingManager shootingTest = new ShootingManager();
Assert.IsFalse(shootingTest.CanShoot(model));
}
[TestMethod]
public void CanShoot_ReturnsFalse_IfHiding()
{
Model model = new Model(1, null, null);
model.Weapons.Add(new MissileWeapon());
TestHelper.GetMockGameManager(model, null, 1, 0);
model.Location = new LocationPoint(0, 1, 0);
model.Direction = 180;
model.IsCharging = false;
model.IsHiding = true;
model.IsRunning = false;
ShootingManager shootingTest = new ShootingManager();
Assert.IsFalse(shootingTest.CanShoot(model));
}

And what do all those lines of code need to make them pass?

public bool CanShoot(Model model)
{
bool canShoot = true;
if (model.IsRunning == true || model.IsHiding == true || model.IsCharging == true)
{
canShoot = false;
}
return canShoot;
}

It’s almost insulting. The next one should be a bit more of a challenge:

[TestMethod]
public void CanShoot_ReturnsFalse_IfNoMissileWeapon()
{
Model model = new Model(1, null, null);
TestHelper.GetMockGameManager(model, null, 1, 0);
model.Location = new LocationPoint(0, 1, 0);
model.Direction = 180;
model.IsCharging = false;
model.IsHiding = false;
model.IsRunning = false;
ShootingManager shootingTest = new ShootingManager();
Assert.IsFalse(shootingTest.CanShoot(model));
}
[TestMethod]
public void CanShoot_ReturnsFalse_IfOnlyMeleeWeapons()
{
Model model = new Model(1, null, null);
model.Weapons.Add(new MeleeWeapon());
TestHelper.GetMockGameManager(model, null, 1, 0);
model.Location = new LocationPoint(0, 1, 0);
model.Direction = 180;
model.IsCharging = false;
model.IsHiding = false;
model.IsRunning = false;
ShootingManager shootingTest = new ShootingManager();
Assert.IsFalse(shootingTest.CanShoot(model));
}

And these are solved by:

public bool CanShoot(Model model)
{
bool canShoot = true;
if (model.IsRunning == true || model.IsHiding == true || model.IsCharging == true)
{
canShoot = false;
}
if (model.Weapons.Where(item => (item as MissileWeapon) != null).Count() == 0)
{
canShoot = false;
}
return canShoot;
}

Linq makes things a little bit too easy these days… The next test checks if the model can see an opponent:

[TestMethod]
public void CanShoot_ReturnsFalse_IfNoEnemyInSight()
{
Model model = new Model(1, null, null);
model.Weapons.Add(new MissileWeapon());
TestHelper.GetMockGameManager(model, null, 1, 0);
model.Location = new LocationPoint(0, 1, 0);
model.Direction = 0;
model.IsCharging = false;
model.IsHiding = false;
model.IsRunning = false;
ShootingManager shootingTest = new ShootingManager();
Assert.IsFalse(shootingTest.CanShoot(model));
}

As expected, it fails. The only difference to the original ‘everything is fine’ test case is the direction that the model is facing – a model can only see in a 90 degree arc to it’s front, so by turning it completely around it should be unable to see the enemy properly. The calculations on the abstract ObjectManager should reflect this, so an extra method in ObjectManager that is exposed (slightly) through Model will help out.

First off – a lot of tests break. This is because the models have been set up with null object managers, which obviously throw exceptions when the call to check them for models in sight occurs. In refactoring to get the first test (pass if all clear) passing, references and parameters were shifted from Model to IModel, things were added to IModel,and in general a bit of housekeeping took place. Here is the new ‘all clear’ test:

[TestMethod]
public void CanShoot_ReturnsTrue_IfEverythingCorrect()
{
IModel model = new Model(0, null, null);
IGameManager gameManager = TestHelper.GetMockGameManager(model, null, 1, 0);
Mock<IObjectManager> mockObjectManager = new Mock<IObjectManager>();
mockObjectManager.Setup(item => item.GetModelsInSight(It.IsAny<IModel>(), gameManager.Models, 90)).Returns(gameManager.Models);
model = new Model(1, gameManager, mockObjectManager.Object);
model.Weapons.Add(new MissileWeapon());
model.Location = new LocationPoint(0, 1, 0);
model.Direction = 180;
model.IsCharging = false;
model.IsHiding = false;
model.IsRunning = false;
ShootingManager shootingTest = new ShootingManager();
Assert.IsTrue(shootingTest.CanShoot(model));
}

With similar ‘setup’ code to the subsequent tests to get them all passing. The improved ‘new’ test:

[TestMethod]
public void CanShoot_ReturnsFalse_IfNoEnemyInSight()
{
IModel model = new Model(0, null, null);
IGameManager gameManager = TestHelper.GetMockGameManager(model, null, 1, 0);
Mock<IObjectManager> mockObjectManager = new Mock<IObjectManager>();
mockObjectManager.Setup(item => item.GetModelsInSight(It.IsAny<IModel>(), gameManager.Models, 90)).Returns(new List<IModel>());
model = new Model(1, gameManager, mockObjectManager.Object);
model.Weapons.Add(new MissileWeapon());
model.Location = new LocationPoint(0, 1, 0);
model.Direction = 0;
model.IsCharging = false;
model.IsHiding = false;
model.IsRunning = false;
ShootingManager shootingTest = new ShootingManager();
Assert.IsFalse(shootingTest.CanShoot(model));
}

And finally, the code to pass it:

public bool CanShoot(IModel model)
{
bool canShoot = true;
if (model.IsRunning == true || model.IsHiding == true || model.IsCharging == true)
{
canShoot = false;
}
if (model.Weapons.Where(item => (item as MissileWeapon) != null).Count() == 0)
{
canShoot = false;
}
if ((from target in model.GetModelsInSight(90)
where target.Player != model.Player
select target).Count() == 0)
{
canShoot = false;
}
return canShoot;
}

With one small addition to the Model class:

public List<IModel> GetModelsInSight(int angleOfSight)
{
List<IModel> modelsInSight = new List<IModel>();
modelsInSight = _objectManager.GetModelsInSight(this, _gameManager.Models, angleOfSight);
return modelsInSight;
}

Finally, one more test and the ‘who can shoot’ question is completely answered. Models cannot shoot if they’re already in hand-to-hand combat. A simple boolean property (the exact circumstances of being able to set it will be complicated, but that’s a problem for later) on the Model and IModel, adding it as an explicit ‘false’ parameter in the other CanShoot tests, and it’s own test case:

[TestMethod]
public void CanShoot_ReturnsFalse_IfModelInHandToHandCombat()
{
IModel model = new Model(0, null, null);
IGameManager gameManager = TestHelper.GetMockGameManager(model, null, 1, 0);
Mock<IObjectManager> mockObjectManager = new Mock<IObjectManager>();
mockObjectManager.Setup(item => item.GetModelsInSight(It.IsAny<IModel>(), gameManager.Models, 90)).Returns(gameManager.Models);
model = new Model(1, gameManager, mockObjectManager.Object);
model.Weapons.Add(new MissileWeapon());
model.Location = new LocationPoint(0, 1, 0);
model.Direction = 180;
model.IsCharging = false;
model.IsHiding = false;
model.IsRunning = false;
model.IsInHandToHandCombat = true;
ShootingManager shootingTest = new ShootingManager();
Assert.IsFalse(shootingTest.CanShoot(model));
}

And the complete CanShoot code to pass it:

public bool CanShoot(IModel model)
{
bool canShoot = true;
if (model.IsRunning == true || model.IsHiding == true || model.IsCharging == true || model.IsInHandToHandCombat == true)
{
canShoot = false;
}
if (model.Weapons.Where(item => (item as MissileWeapon) != null).Count() == 0)
{
canShoot = false;
}
if ((from target in model.GetModelsInSight(90)
where target.Player != model.Player
select target).Count() == 0)
{
canShoot = false;
}
return canShoot;
}

And it leaves me at a good point to finish off for this section. The next section will be the Closest Target rules, which I’ve already given some thought to – but my word count here is already over 1500, so it’s probably best to clock off!

Learning Unit Testing X – Finishing Hiding

Last time, I got the tests for Charging out of the way, and got through a raft of simple checks for Hiding. This time, a few of the more complicated checks.

First off, a model can move around while hiding as long as it remains out of sight. I’m not sure how to be checking every point along the move, but I can check at the end of the movement whether it’s still out of sight.

[TestMethod]
public void Hide_ThrowsExceptionIfInSightAfterMove()
{
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);
testModel.Movement = 4;
bool exceptionThrown = false;
mockObjectManager.Setup(item => item.GetLineOfSight(It.IsAny<IModel>(), It.IsAny<IModel>())).Returns(0);
testModel.Hide();
mockObjectManager.Setup(item => item.GetLineOfSight(It.IsAny<IModel>(), It.IsAny<IModel>())).Returns(1);
Assert.IsTrue(testModel.IsHiding);
try
{
testModel.MoveModel(4, 0, 0);
}
catch (HidingException ex)
{
exceptionThrown = true;
Assert.AreEqual(enemyModel, ex.EnemyModels[0]);
}
Assert.IsTrue(exceptionThrown);
Assert.IsFalse(testModel.IsHiding);
}

The best way to check this is probably to call the Hide method at the end of MoveModel – any change in the model’s visibility will be picked up. Code re-use: excellent!

if (this.IsHiding)
{
this.Hide();
}

But I still don’t get a pass… I also need to set IsHiding to false in the Hide method, when it throws an exception. Then the test goes green.

The next test is that after an enemy model has moved, it might force a model to stop being hidden. I will keep the current action of throwing an exception – this will let the imaginary UI show that hiding has ceased.

[TestMethod]
public void Hide_StopsHidingIfInSightAfterEnemyMove()
{
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);
enemyModel.Movement = 4;
bool exceptionThrown = false;
mockObjectManager.Setup(item => item.GetLineOfSight(It.IsAny<IModel>(), It.IsAny<IModel>())).Returns(0);
testModel.Hide();
mockObjectManager.Setup(item => item.GetLineOfSight(It.IsAny<IModel>(), It.IsAny<IModel>())).Returns(1);
Assert.IsTrue(testModel.IsHiding);
try
{
enemyModel.MoveModel(4, 0, 0);
}
catch (HidingException ex)
{
exceptionThrown = true;
Assert.AreEqual(enemyModel, ex.EnemyModels[0]);
}
Assert.IsTrue(exceptionThrown);
Assert.IsFalse(testModel.IsHiding);
}

The code to pass this test should be just adding the following check to the end of MoveModel (as long as Hide is added to the IModel interface).

foreach (IModel enemyModel in (from models in _gameManager.Models
where models.Player != this.Player
select models))
{
enemyModel.Hide();
}

But this doesn’t work – not only does it not work, but it breaks other tests! Now, Charge_SetsIsChargingFlag, MoveModel_CannotMoveFurtherThanModelsMovement and NewTurn_SetsIsChargingToFalse throw exceptions and this test fails on the penultimate assert that the exception was thrown. I believe the error with this test is that the Mock GameManager I’m using is only returning the enemyModel – not the testModel. When I added the testModel to the setup, this test at least passes. The other tests all throw exceptions. This is probably due to the fact that the GameManager is not being correctly set up in each case, to return an empty List<IModel> – as a mock object, it returns null instead and the above code to check for other models fails when passed a null.

By adding in the required setup line to each of those tests, I get 31 passes again.

Finally, a model is automatically forced out of hiding if an enemy model is within it’s Initiative range of the hiding model. This one needs to be checked when the model chooses to hide, and also whenever it moves or the enemy model moves. Since all three of these cause the Hide method to be called, that should probably be where it sits. The following tests should cover off those three situations:

[TestMethod]
public void Hide_CannotHideIfEnemyInInitiativeRange()
{
Mock<IGameManager> mockGameManager = new Mock<IGameManager>();
Mock<IObjectManager> mockObjectManager = new Mock<IObjectManager>();
Model testModel = new Model(1, mockGameManager.Object, mockObjectManager.Object);
Model enemyModel = new Model(2, mockGameManager.Object, mockObjectManager.Object);
mockObjectManager.Setup(item => item.GetLineOfSight(It.IsAny<IModel>(), It.IsAny<IModel>())).Returns(0);
testModel.Location = new LocationPoint(0, 0, 0);
enemyModel.Location = new LocationPoint(1, 0, 0);
enemyModel.Initiative = 2;
mockGameManager.Setup(item => item.Models).Returns(new List<IModel>() { enemyModel, testModel });
bool correctExceptionThrown = false;
try
{
testModel.Hide();
}
catch (HidingException ex)
{
correctExceptionThrown = true;
Assert.AreEqual(enemyModel, ex.EnemyModels[0]);
}
Assert.IsFalse(testModel.IsHiding);
Assert.IsTrue(correctExceptionThrown);
}
[TestMethod]
public void MoveModel_StopHidingIfModelMovesIntoEnemyInitiativeRange()
{
Mock<IGameManager> mockGameManager = new Mock<IGameManager>();
Mock<IObjectManager> mockObjectManager = new Mock<IObjectManager>();
Model testModel = new Model(1, mockGameManager.Object, mockObjectManager.Object);
Model enemyModel = new Model(2, mockGameManager.Object, mockObjectManager.Object);
mockObjectManager.Setup(item => item.GetLineOfSight(It.IsAny<IModel>(), It.IsAny<IModel>())).Returns(0);
testModel.Location = new LocationPoint(0, 0, 0);
enemyModel.Location = new LocationPoint(5, 0, 0);
enemyModel.Initiative = 3;
mockGameManager.Setup(item => item.Models).Returns(new List<IModel>() { enemyModel, testModel });
testModel.IsHiding = true;
testModel.Movement = 4;
bool correctExceptionThrown = false;
try
{
testModel.MoveModel(4, 0, 0);
}
catch (HidingException ex)
{
correctExceptionThrown = true;
Assert.AreEqual(enemyModel, ex.EnemyModels[0]);
}
Assert.IsFalse(testModel.IsHiding);
Assert.IsTrue(correctExceptionThrown);
}
[TestMethod]
public void MoveModel_StopHidingIfEnemyMovesWithinInitiativeRange()
{
Mock<IGameManager> mockGameManager = new Mock<IGameManager>();
Mock<IObjectManager> mockObjectManager = new Mock<IObjectManager>();
Model testModel = new Model(1, mockGameManager.Object, mockObjectManager.Object);
Model enemyModel = new Model(2, mockGameManager.Object, mockObjectManager.Object);
mockObjectManager.Setup(item => item.GetLineOfSight(It.IsAny<IModel>(), It.IsAny<IModel>())).Returns(0);
testModel.Location = new LocationPoint(5, 0, 0);
enemyModel.Location = new LocationPoint(0, 0, 0);
enemyModel.Initiative = 3;
mockGameManager.Setup(item => item.Models).Returns(new List<IModel>() { enemyModel, testModel });
testModel.IsHiding = true;
enemyModel.Movement = 4;
bool correctExceptionThrown = false;
try
{
enemyModel.MoveModel(4, 0, 0);
}
catch (HidingException ex)
{
correctExceptionThrown = true;
Assert.AreEqual(enemyModel, ex.EnemyModels[0]);
}
Assert.IsFalse(testModel.IsHiding);
Assert.IsTrue(correctExceptionThrown);
}

The passing code sits in Hide, and is very pleasantly simple. Hide becomes very long:

public void Hide()
{
if (this.IsCharging || this.IsRunning)
{
HidingException ex = new HidingException("Cannot hide if charged or ran this turn.");
throw ex;
}
else
{
List<IModel> enemyModels = (from models in this._gameManager.Models
where models.Player != this.Player
&& (this._objectManager.GetLineOfSight(models, this) > 0.6 || this.GetDistanceFrom(models.Location.X, models.Location.Y, models.Location.Z) <= models.Initiative)
select models).ToList();
if (enemyModels.Count > 0)
{
this.IsHiding = false;
HidingException ex = new HidingException("Cannot hide in sight or Initiative range of enemy models.");
ex.EnemyModels = enemyModels;
throw ex;
}
else
{
this.IsHiding = true;
}
}
}

But this breaks several other tests. The reason is that some of the previously written tests are not setting locations for the enemy and test models, and the distance between them (zero) is always equal to or less than the default Initiative value (also zero). Changing the tests to give them a bit of space fixes this problem.

Since a model can remain hidden for several successive turns, it doesn’t need to reset the IsHiding flag – Hiding for all hidden models is checked whenever a model is moved, so it will be taken out of hiding when a player wishes or when it is chosen.

Next time, I move off of page 11, but I’m not sure where to yet…

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!

Learning Unit Testing VIII – Emergent Design

I’ve been bracing myself for this moment since I started the project – I’m coming to believe that I’ve been heading in a slightly wrong direction.

Part of TDD is the idea of ‘emergent design’, and letting the correct design emerge from the process of writing minimal code to pass tests. At the same time, it’s necessary to keep an eye on the bigger picture so you know where exactly the code should be written.

I’ve been making the MoveModel method into a huge monster, that checks all sorts of things, and as I started investigating how to implement line of sight, I realised that this functionality should probably be moved out into some sort of utility class. MoveModel itself is now checking distances between two points (this will be duplicated later), proximity detection (probably to be used later), and as I said – was about to take on line of sight. All these details could be worked out mathematically as I was doing them, but some frameworks (such as XNA) might provide better ways to calculate them, with things such as Rays, or… something else XNAish.

The best way to handle that would be to refactor all the ‘utility’ calculations into a separate class, exposed via an interface so that it can be swapped out. That way, I can abstract out and mock all the calculations in MoveModel to deal more with the situation parameters and appropriate responses and less about the calculations themselves. At the same time, I can write tests for this new utility class so that I can test the purely mathematical aspects of the same calculations. The interactions become easier (true, false, and integer responses rather than the broad spectrum of mathematical output) since I don’t have to test multiple configurations and situation-specific responses, just the two separate things that should be behaving.

The other benefit will be that if I eventually do take this library over to an XNA frontend, I can swap out that mathematical calculator for something more efficient for the framework to use. But the basic mathematical way will still be available if I choose instead to port it to Silverlight.

I also took this opportunity to move the X, Y and Z co-ordinates into a separate class – LocationPoint.

Here’s the set of tests for MoveModel:

[TestClass]
public class ModelTests
{
[TestMethod]
public void NewTurn_SetsDistanceMovedToZero()
{
Mock<IGameManager> mockGameManager = new Mock<IGameManager>();
Mock<IObjectManager> mockObjectManager = new Mock<IObjectManager>();
Model testModel = new Model(1, mockGameManager.Object, mockObjectManager.Object);
testModel.TotalDistanceMoved = 4;
testModel.NewTurn();
Assert.AreEqual(0, testModel.TotalDistanceMoved);
}
[TestMethod]
public void MoveModel_PositionHasChanged()
{
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.Location.X = 0;
testModel.Location.Y = 0;
testModel.Location.Z = 0;
float newX = 2;
float newY = 2;
float newZ = 2;
testModel.MoveModel(newX, newY, newZ);
Assert.AreEqual(2, testModel.Location.X);
Assert.AreEqual(2, testModel.Location.Y);
Assert.AreEqual(2, testModel.Location.Z);
Assert.IsFalse(testModel.IsRunning);
}
[TestMethod]
public void MoveModel_TotalMovementInASingleTurnCannotExceedModelMovement_ThrowsMovementException()
{
bool correctExceptionThrown = false;
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.Location.X = 0;
testModel.Location.Y = 0;
testModel.Location.Z = 0;
float newX = 5;
float newY = 4;
float newZ = 3;
testModel.MoveModel(newX, newY, newZ);
try
{
testModel.MoveModel(0, 0, 0);
}
catch (MovementException ex)
{
correctExceptionThrown = true;
}
Assert.IsTrue(correctExceptionThrown);
}
[TestMethod]
public void MoveModel_MakeTwoSmallMovementsWithoutGoingOverMovementRate()
{
bool correctExceptionThrown = false;
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.Location.X = 0;
testModel.Location.Y = 0;
testModel.Location.Z = 0;
float newX = 1;
float newY = 1;
float newZ = 0;
try
{
testModel.MoveModel(newX, newY, newZ);
testModel.MoveModel(0, 0, 0);
}
catch (MovementException ex)
{
correctExceptionThrown = true;
}
Assert.IsFalse(correctExceptionThrown);
}
[TestMethod]
public void MoveModel_CannotMoveFurtherThanModelsMovement()
{
bool correctExceptionThrown = false;
Mock<IGameManager> mockGameManager = new Mock<IGameManager>();
Mock<IObjectManager> mockObjectManager = new Mock<IObjectManager>();
Model testModel = new Model(1, mockGameManager.Object, mockObjectManager.Object);
testModel.Movement = 4;
float newX = 5;
float newY = 6;
float newZ = 3;
try
{
testModel.MoveModel(newX, newY, newZ);
}
catch (MovementException ex)
{
correctExceptionThrown = true;
}
Assert.IsTrue(correctExceptionThrown);
}
[TestMethod]
public void MoveModel_MovesDistanceOverMovementRateAndSetsIsRunningFlag()
{
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.Location.X = 0;
testModel.Location.Y = 0;
testModel.Location.Z = 0;
float newX = 5;
float newY = 4;
float newZ = 3;
testModel.MoveModel(newX, newY, newZ);
Assert.IsTrue(testModel.IsRunning);
}
[TestMethod]
public void MoveModel_StopsRunningWithin8InchesOfEnemyModel()
{
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 correctExceptionThrown = 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.MoveModel(8, 0, 0);
}
catch (MovementException ex)
{
correctExceptionThrown = true;
Assert.AreEqual(1, ex.FinalPosition.X);
Assert.AreEqual(0, ex.FinalPosition.Y);
Assert.AreEqual(0, ex.FinalPosition.Z);
}
Assert.IsTrue(correctExceptionThrown);
Assert.AreEqual(1, testModel.Location.X);
Assert.AreEqual(0, testModel.Location.Y);
Assert.AreEqual(0, testModel.Location.Z);
}
[TestMethod]
public void MoveModel_ContinuesRunningWithEnemyMoreThan8InchesAway()
{
Mock<IGameManager> mockGameManager = new Mock<IGameManager>();
Mock<IObjectManager> mockObjectManager = new Mock<IObjectManager>();
Model enemyModel = new Model(1, mockGameManager.Object, mockObjectManager.Object);
enemyModel.Location.X = 18;
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 correctExceptionThrown = false;
try
{
testModel.MoveModel(8, 0, 0);
}
catch (MovementException ex)
{
correctExceptionThrown = true;
}
Assert.IsFalse(correctExceptionThrown);
Assert.AreEqual(8, testModel.Location.X);
Assert.AreEqual(0, testModel.Location.Y);
Assert.AreEqual(0, testModel.Location.Z);
}
[TestMethod]
public void MoveModel_ContinuesMovingWithin8InchesOfEnemyModelNotRunning()
{
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 correctExceptionThrown = false;
try
{
testModel.MoveModel(3, 0, 0);
}
catch (MovementException ex)
{
correctExceptionThrown = true;
}
Assert.IsFalse(correctExceptionThrown);
Assert.AreEqual(3, testModel.Location.X);
Assert.AreEqual(0, testModel.Location.Y);
Assert.AreEqual(0, testModel.Location.Z);
}
[TestMethod]
public void MoveModel_ContinuesRunningWithin8InchesOfFriendlyModel()
{
Mock<IGameManager> mockGameManager = new Mock<IGameManager>();
Mock<IObjectManager> mockObjectManager = new Mock<IObjectManager>();
Model friendlyModel = new Model(1, mockGameManager.Object, mockObjectManager.Object);
friendlyModel.Location.X = 9;
friendlyModel.Location.Y = 0;
friendlyModel.Location.Z = 0;
mockGameManager.Setup(item => item.Models).Returns(new List<IModel>() { friendlyModel });
Model testModel = new Model(1, mockGameManager.Object, mockObjectManager.Object);
testModel.Movement = 4;
testModel.Location.X = 0;
testModel.Location.Y = 0;
testModel.Location.Z = 0;
bool correctExceptionThrown = false;
try
{
testModel.MoveModel(8, 0, 0);
}
catch (MovementException ex)
{
correctExceptionThrown = true;
}
Assert.IsFalse(correctExceptionThrown);
Assert.AreEqual(8, testModel.Location.X);
Assert.AreEqual(0, testModel.Location.Y);
Assert.AreEqual(0, testModel.Location.Z);
}
[TestMethod]
public void MoveModel_ContinuesRunningWithin8InchesOfEnemyModelIfOutOfSight()
{
Mock<IGameManager> mockGameManager = new Mock<IGameManager>();
Mock<IObjectManager> mockObjectManager = new Mock<IObjectManager>();
Model enemyModel = new Model(1, mockGameManager.Object, mockObjectManager.Object);
enemyModel.Location.X = 12;
enemyModel.Location.Y = 0;
enemyModel.Location.Z = 0;
mockGameManager.Setup(item => item.Models).Returns(new List<IModel>() { enemyModel });
Mock<IScenery> mockScenery = new Mock<IScenery>();
mockScenery.Setup(item => item.IsBlocking(It.IsAny<IModel>(), It.IsAny<IModel>())).Returns(true);
mockGameManager.Setup(item => item.Models).Returns(new List<IModel>() { enemyModel });
mockGameManager.Setup(item => item.SceneryObjects).Returns(new List<IScenery>() { mockScenery.Object });
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 correctExceptionThrown = false;
try
{
testModel.MoveModel(8, 0, 0);
}
catch (MovementException ex)
{
correctExceptionThrown = true;
}
Assert.IsFalse(correctExceptionThrown);
Assert.AreEqual(8, testModel.Location.X);
Assert.AreEqual(0, testModel.Location.Y);
Assert.AreEqual(0, testModel.Location.Z);
}
}

And the code needed in MoveModel itself:

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.IsRunning = true;
}
}
}
private bool ValidateMove(ref float positionX, ref float positionY, ref float positionZ)
{
double distanceToPoint = GetDistanceFrom(positionX, positionY, positionZ);
if (distanceToPoint + this.TotalDistanceMoved > this.Movement * 2)
{
MovementException ex = new MovementException("The model cannot move further than it's Movement rate.");
ex.FinalPosition.X = this.Location.X;
ex.FinalPosition.Y = this.Location.Y;
ex.FinalPosition.Z = this.Location.Z;
throw ex;
}
if (this.TotalDistanceMoved + distanceToPoint > this.Movement)
{
foreach (IModel enemyModel in _gameManager.Models.Where(item => item.Player != this.Player))
{
LocationPoint intersectionPoint = _objectManager.GetPointOfIntersection(this.Location, new LocationPoint(positionX, positionY, positionZ), enemyModel.Location, 8);
if (intersectionPoint != null)
{
if (_objectManager.GetLineOfSight(this, enemyModel) > 0)
{
MovementException ex = new MovementException("The model cannot run within 8\" of an enemy model.");
ex.FinalPosition = intersectionPoint;
positionX = intersectionPoint.X;
positionY = intersectionPoint.Y;
positionZ = intersectionPoint.Z;
throw ex;
}
}
}
}
return true;
}

The lesson I’ve learned from this as far as emergent design goes is that I should be more willing to abstract something out if it doesn’t look like it belongs, and be prepared to program against interfaces that are not implemented yet. I believe that’s the way TDD works, and will keep code exactly where it needs to be and not spread around (as it was starting to do in the Model class…)