Wednesday, 12 August 2009

State Pattern : Part 3

After some feedback on yesterdays post, I realised I had gone about the State Pattern in the wrong manner - I didn't, in fact, need separate states for movement, attack etc. I think my problem stemmed from not understanding double dispatch, nor realising it could be used in this situation!

To that end, we now have


Much cleaner than before.

The state now includes a couple of interesting methods;


And some subclasses of State:


The static constructor for State inspects each class in the current Assembly - if it is a subclass of State, it maps this to a dictionary based on the Parameter type.

When a concrete StateContext is passed in (say AttackStateContext), the Handler(object o) method is called, this looks to see if it knows about the AttackStateContext type, and if so passes it onto the relevant State.

I still have quite a bit of work to do with switching States - what happens if an AttackState is passed in? Just now it will simply hit the Console.WriteLine("Handling object " + o); code which isn't what we want. What I need to implement is functionality so that if an AttackState is passed in when the current State is MoveState, we either Switch or do nothing. Expect a Part 4 and Part 5 and Part ... soon :)

3 comments:

Troy said...

I'm by no means an expert on the State Pattern, which is one of the reasons I'm so interested in this series of blog posts (pssst, commit the changes!). I did want to offer up a (hopefully) helpful tip for getting rid of a few of those if statements you have lingering around to make the code more legible: use LINQ!

foreach(var t in Assembly.GetCallingAssembly().GetTypes().Where(t=> t..IsSubclassOf(typeof(State))))
{
foreach(var mi in t.GetMethods().Where(mi => mi.Name == "Handle" && mi.GetParameters().Length > 0))
{
var code = ...
Dispatch.Add(code, mi);
}
}

Jamie said...

Thanks for that! I've just committed my work so far, I'll add your changes right now though :)

I've moved away from the Attack / AttackRanged states towards an Attack state - not quite figured out the next step, but I think I was overengineering it by having multiple Attack States - in essence, AttackRanged is exactly the same as Attack!

Troy said...

I think something that might be helpful (at least for me) is to step away from the code for a second and talk about how everything ties together via an example and what is doing what. Let me give it a shot and tell me what I've got wrong...

1. User selects a unit (an archer) and then clicks an empty adjacent hex.
2. The client passes a MoveStateContext into the selected Unit's ExecuteTurn method.
3. The unit passes the context to the CurrentState property's Handle method. We want this to be dispatched to the Movement:State's implementation, which means the unit's CurrentState property would need to have been changed at some point.
4. Movement:State uses the data provided by MoveStateContext to complete the move operation.
5. The user selects the unit again and now clicks on an enemy unit 2 hexes away.
6. The attacking unit's state gets changed to Attack:State.
7. The client passes an AttackStateContext to the unit's ExecuteTurn method, which then passes it to the CurrentState property's Handle method.

Is it just me or does that seem a little dirty? Are Attack and Movement really different states of the Unit? I feel like the actions like Attack and Movement should actually follow the command pattern or similar, and the State Pattern should only be used for deciding how to execute the command. Not really sure how best to implement at this time though...

Post a Comment