I fired up an old laptop last week and I found Panzer General installed on it. Bring back fond memories of wasted hours, I thought how cool it would be to port this game to Windows Phone. Ambitious? Absolutely. Realistic? Not even close. Undaunted, I thought a bit about how a game board are might be modeled. You can see from this screen shot that the game board is a series of Hexes – like so:

Dialing up the MVVM pattern, I thought the basic unit of code is the Hex. I looked at this article about building a MVVM Pattern for a Sudoku found in MSDN here but quickly threw it away. I think that Miller’s implementation is a bit mangled and all of the code in the View smells to me like an incorrect design. I realized that I need a HexModel, and HexView, and a HexViewModel. To start, I put all of the properties that I care about into the Model like so:
public class HexModel
{
public double Width { get; set; }
public double Height { get; set; }
public double CanvasTop { get; set; }
public double CanvasLeft { get; set; }
public SolidColorBrush Fill { get; set; }
public int XCoordinate { get; set; }
public int YCoordinate { get; set; }
}
}
I then put all of the implementation of INotifiedPropertyChanged in the ViewModel. It makes alot of sense to me that the View is a passive implementation of the underlying data structures (EF classes, POCOs, TakeYourPickDataStructure) and the ViewModel where all of the heavy lifting occurs (For example, your LINQ for the CRUD if this was working with a permanent data store) and that is where you notify the consuming layers that properties changed. Following this mental model, I first implemented an abstract class to handle INotifiedPropertyChanged:
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(String propertyName)
{
if (string.IsNullOrEmpty(propertyName))
throw new ArgumentException("Argument 'propertyName' cannot be null or empty.");
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
I then implemented the HexViewModel like this (partial snip):
public class HexViewModel: ViewModelBase
{
HexModel _hexModel;
public HexViewModel()
{
_hexModel = new HexModel();
Width = 100;
Height = 100;
CanvasTop = 0.0;
CanvasLeft = 0.0;
Fill = new SolidColorBrush(Colors.Gray);
XCoordinate = 0;
YCoordinate = 0;
}
public double Width
{
get
{
return _hexModel.Width;
}
set
{
_hexModel.Width = value;
NotifyPropertyChanged("Width");
}
}
With this view model done, I then was onto the View itself. A hex shape is a bit more tricky than a square for a game board – heck you can use a grid if you just want to build a game board of squares. I found a pretty good implementation of a WPF hex on Stack Overflow here. Since the Panzer General Hex is flat on the top (versus the flat sides in the Stack Overflow question), I reversed the path coordinates and threw it into a User Control:
<UserControl.DataContext>
<vm:HexViewModel />
</UserControl.DataContext>
<Canvas x:Name="CanvasRoot"
Height="100"
Width="100">
<Path x:Name="PathRoot"
IsHitTestVisible="True"
Width="100"
Height="100"
d:LayoutOverrides="None"
d:LastTangent="0,0"
Stroke="Black"
Fill="{Binding Path=Fill}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Margin="0"
Stretch="Fill"
Data="M0,8.660254 L5,17.320508 15,17.320508 20,8.660254 15,0 5,0 0,8.660254 z"
Canvas.Left="{Binding Path=CanvasLeft}"
Canvas.Top="{Binding Path=CanvasTop}">
</Path>
<TextBox x:Name="LocationTextBox"
IsHitTestVisible="False"
Canvas.Left="21"
Canvas.Top="22"
FontSize="12"
Background="{Binding Path=Fill}"
HorizontalAlignment="Center"
BorderThickness="0"
Text="{Binding Path=Coordinates}">
</TextBox>
</Canvas>
</UserControl>
Sure enough, I rendered great in VS2010 at design time because of the magic of data binding and INotifyPropertyChanged:

I then though about how I would get a series of hexes as the game board using the same MVVM pattern. I set up a BoardModel like so:
public class BoardModel
{
public int NumberOfRows { get; set; }
public int NumberOfColumns { get; set; }
public int ZoomFactor { get; set; }
}
I then implemented the model and added a collection to the ViewModel to have a collection of HexModels (screen shot, not code):

I then put together a BoardView that goes through the collection and renders out the HexViews in the constructor:
private void BindCanvas()
{
MainCanvas.Height = (_boardViewModel.NumberOfRows * 100);
MainCanvas.Width = (_boardViewModel.NumberOfColumns * 100);
foreach (HexViewModel hexViewModel in _boardViewModel.HexViewModels)
{
this.MainCanvas.Children.Add(CreateHexView(hexViewModel));
}
}
Because this is not data bound, I don’t see the HexViews at design time. However, when I slap the BoardView into the MainPage at design time, I get a nice playboard:

Also, I used the Windows Phone Control Toolkit’s Gesture Listen class and I can intercept touches and active and deactivates hexes on a user’s touch (I love the SOC of the IPropertyNotifiedChanged):
private void GestureListener_Tap(object sender, Microsoft.Phone.Controls.GestureEventArgs e)
{
Path path = (Path)e.OriginalSource;
Canvas canvas = (Canvas)path.Parent;
HexView hexView = (HexView)canvas.Parent;
if (currentHexViewModel != null)
currentHexViewModel.Fill = new SolidColorBrush(Colors.Gray);
currentHexViewModel = (HexViewModel)hexView.DataContext;
currentHexViewModel.Fill = new SolidColorBrush(Colors.Red);
e.Handled = true;
}
Notice how I am changing the property of the ViewModel – not of the UI control.
I am not sure how to make the BoardVewModel can be data bound like the HexViewModel. I was thinking that the Board should expose a collection of HexViews (versus ViewModel) but that does seem right. Also, I am not sure how to code up design time data binding to that collection – I would think I would need a container control. After futzing around with Rob Seder and not getting anywhere, I gave up and went with the code-behind implementation. I am sure I will revisit, but I am jazzed just to get the board working. Up next, I want to see if I can put in a picture into the Hex.