Whoop! My Windows Phone App Store Check Came Today!

WP_000462

 

I know it is hard to read  – I just wanted to show the full effect of $3.46…

Serialization in an WP7 project

So if you want to do some basic serialization in your Windows Phone 7 app, (cerealization to my kids) image 

you most likely will start here.  However, if you copy/paste the code an out of the box project, you get the dreaded wiggly red line:

image

What is going on?

image

Oh crud.  The default for MSDN is .NET Framework 4.  When you switch it to Silverlight, you see that you need a reference to System.Xml.Serialization.

image

Out of the box, WP7 projects don’t have a reference to System.Xml.Serialization – only System.Xml.  Once I add that assembly to the project, we get the happy blue:

image

This is a bit confusing, to say the least.

Optimize Searching Using Value Types

As mentioned in my blog post here, I need to write a searching algorithm of hexes adjacent to the active unit to display movable/visible/attackable hexes.  The original code was a bit of a beast across 3 functions:

 

1 public List<Hex> GetTotalAdjacentHexes(Hex currentHex, Int32 maxDistance) 2 { 3 List<Hex> hexes = new List<Hex>(); 4 List<Hex> tempList = GetAdjacentHexes(currentHex); 5 foreach (Hex hex in tempList) 6 { 7 if (!InHexList(hexes,hex)) 8 GetAdjacentHexes(currentHex, hexes, hex, tempList, 0, maxDistance); 9 } 10 return hexes; 11 } 12 13 public void GetAdjacentHexes(Hex currentHex, List<Hex> hexes, Hex hex, List<Hex> currentList, int distanceCounter, int maxDistance) 14 { 15 distanceCounter++; 16 { 17 foreach (Hex tempHex in currentList) 18 { 19 hexes.Add(tempHex); 20 if (distanceCounter < maxDistance) 21 { 22 List<Hex> tempList = GetAdjacentHexes(tempHex); 23 GetAdjacentHexes(currentHex, hexes, hex, tempList, distanceCounter, maxDistance); 24 } 25 } 26 } 27 } 28 29 public List<Hex> GetAdjacentHexes(Hex currentHex) 30 { 31 32 List<Hex> adjacentHexes = new List<Hex>(); 33 Hex targetHex = null; 34 //Above 35 int targetColumnNumber = currentHex.ColumnNumber; 36 int targetRowNumber = currentHex.RowNumber - 1; 37 targetHex = GetHex(targetColumnNumber, targetRowNumber); 38 if (targetHex != null) 39 adjacentHexes.Add(targetHex); 40 //Top Left 41 targetColumnNumber = currentHex.ColumnNumber - 1; 42 if (currentHex.ColumnNumber % 2 == 0) 43 targetRowNumber = currentHex.RowNumber - 1; 44 else 45 targetRowNumber = currentHex.RowNumber; 46 targetHex = GetHex(targetColumnNumber, targetRowNumber); 47 if (targetHex != null) 48 adjacentHexes.Add(targetHex); 49 //Bottom Left 50 targetColumnNumber = currentHex.ColumnNumber - 1; 51 if (currentHex.ColumnNumber % 2 == 0) 52 targetRowNumber = currentHex.RowNumber; 53 else 54 targetRowNumber = currentHex.RowNumber + 1; 55 targetHex = GetHex(targetColumnNumber, targetRowNumber); 56 if (targetHex != null) 57 adjacentHexes.Add(targetHex); 58 //Below 59 targetColumnNumber = currentHex.ColumnNumber; 60 targetRowNumber = currentHex.RowNumber + 1; 61 targetHex = GetHex(targetColumnNumber, targetRowNumber); 62 if (targetHex != null) 63 adjacentHexes.Add(targetHex); 64 //Bottom Right 65 targetColumnNumber = currentHex.ColumnNumber + 1; 66 if (currentHex.ColumnNumber % 2 == 0) 67 targetRowNumber = currentHex.RowNumber; 68 else 69 targetRowNumber = currentHex.RowNumber + 1; 70 targetHex = GetHex(targetColumnNumber, targetRowNumber); 71 if (targetHex != null) 72 adjacentHexes.Add(targetHex); 73 //Top Right 74 targetColumnNumber = currentHex.ColumnNumber + 1; 75 if (currentHex.ColumnNumber % 2 == 0) 76 targetRowNumber = currentHex.RowNumber - 1; 77 else 78 targetRowNumber = currentHex.RowNumber; 79 targetHex = GetHex(targetColumnNumber, targetRowNumber); 80 if (targetHex != null) 81 adjacentHexes.Add(targetHex); 82 83 return adjacentHexes; 84 }

I recently had a duh moment when I tried to optimize the code for speed (replacing hexes with tiles) – each hex has an unique row/column value that I could use to calculate distance.  In addition, instead of looping and collecting heavy reference types, I am using 2 integers in 1 loop, so the performance is dramatically improved.  Finally, since I drop from 84 lines of code to 54, I am making my code more maintainable.

 

1 public List<Tile> GetTotalAdjacentTiles(Tile startTile, Int32 maxDistance) 2 { 3 List<Tile> tiles = new List<Tile>(); 4 int startingColumn = startTile.ColumnNumber - maxDistance; 5 if(startingColumn < 0) 6 { 7 startingColumn = 0; 8 } 9 int endingColumn = startTile.ColumnNumber + maxDistance; 10 if (endingColumn > Game.CurrentBoard.NumberOfColumns) 11 { 12 endingColumn = Game.CurrentBoard.NumberOfColumns; 13 } 14 15 int startingRow = startTile.RowNumber - maxDistance; 16 if (startingRow < 0) 17 { 18 startingRow = 0; 19 } 20 int endingRow = startTile.RowNumber + maxDistance; 21 if (endingRow > Game.CurrentBoard.NumberOfRows) 22 { 23 endingRow = Game.CurrentBoard.NumberOfRows; 24 } 25 26 Tile tile = null; 27 int combinedDistance = 0; 28 for (int i = startingColumn; i <= endingColumn; i++) 29 { 30 for (int j = startingRow; j <= endingRow; j++) 31 { 32 tile = GetTile(i, j); 33 if (!tiles.Contains(tile)) 34 { 35 combinedDistance = GetDistanceBetweenTwoTiles(startTile,tile); 36 if (combinedDistance <= maxDistance) 37 { 38 tiles.Add(tile); 39 } 40 } 41 } 42 } 43 44 //Remove Starting Tile 45 tiles.Remove(startTile); 46 47 return tiles; 48 } 49 50 public List<Tile> GetAdjacentTiles(Tile currentTile) 51 { 52 return GetTotalAdjacentTiles(currentTile, 1); 53 }

Windows Phone 7– Duh Moment Of The Day

So I wrote this code block in a Windows Phone 7 application:

1 private void CreateNewLogFile() 2 { 3 string logFileLocation = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); 4 if (File.Exists(logFileLocation)) 5 File.Delete(logFileLocation); 6 7 } 8

And I got this exception:

image

And it hit me – can’t write to the local file system of the emulator using Windows Desktop/Server constructs. To write in a Windows Phone 7 application, I need to write to the local storage.  However, since I can’t read from local storage easily (no WindowsExplorer access) – I would need to write a program to get the data out.  This seems more trouble than it is worth.  I’ll just write to the Debug window….

Windows Phone 7–Threading Issues With the Stopwatch Class

I am writing a profiling application to help me with some performance problems that I am having with the Windows Phone 7 game I am writing.  Here is a snippet where I track the time it takes to load in the game data for a given scenario:

1 private void LoadTile() 2 { 3 Stopwatch stopWatch = new Stopwatch(); 4 ScenarioTileFactory scenarioTileFactory = new ScenarioTileFactory(); 5 List<ScenarioTile> scenarioTiles = null; 6 stopWatch.Start(); 7 scenarioTiles = scenarioTileFactory.GetScenarioTiles(20); 8 stopWatch.Stop(); 9 this.TilesListBox.Items.Add(String.Format("Scenario {0} has {1} tiles and it took {2}.", 10 "20", scenarioTiles.Count.ToString(), stopWatch.ElapsedMilliseconds.ToString())); 11 12 } 13

When I run it, I get this (which is correct):

image

I then wanted time how long each scenario takes to load and update the UI after each load.  I immediately thought of using a second thread – 1 to load the data and the main UI thread to update the screen.  I update my load function for multi-threading like so:

1 Stopwatch stopWatch = new Stopwatch(); 2 ScenarioTileFactory scenarioTileFactory = new ScenarioTileFactory(); 3 List<ScenarioTile> scenarioTiles = null; 4 stopWatch.Start(); 5 scenarioTiles = scenarioTileFactory.GetScenarioTiles(20); 6 stopWatch.Stop(); 7 this.TilesListBox.Dispatcher.BeginInvoke(() => 8 { 9 this.TilesListBox.Items.Add(String.Format("Scenario {0} has {1} tiles and it took {2}.", 10 "20", scenarioTiles.Count.ToString(), stopWatch.ElapsedMilliseconds.ToString())); 11 }); 12 stopWatch.Reset(); 13 stopWatch.Start(); 14 scenarioTiles = scenarioTileFactory.GetScenarioTiles(21); 15 stopWatch.Stop(); 16 this.TilesListBox.Dispatcher.BeginInvoke(() => 17 { 18 this.TilesListBox.Items.Add(String.Format("Scenario {0} has {1} tiles and it took {2}.", 19 "21", scenarioTiles.Count.ToString(), stopWatch.ElapsedMilliseconds.ToString())); 20 }); 21

The results started getting screwy:

image

 

It sure looks like the Stopwatch.Reset is not thread-aware? Nope, hoping the thread back calls an auto refresh. For example, I put a break:

image

 

But when I hop the thread and go back to the UI thread:

image

So the stopwatch class is not thread-safe. Good to know! To get around this problem, I created a local holding variable and passed that value over to the UI thread:

1 private void LoadTiles() 2 { 3 Stopwatch stopWatch = new Stopwatch(); 4 ScenarioTileFactory scenarioTileFactory = new ScenarioTileFactory(); 5 List<ScenarioTile> scenarioTiles = null; 6 long elapsedTime = 0; 7 for (int i = 0; i < 30; i++) 8 { 9 stopWatch.Reset(); 10 stopWatch.Start(); 11 scenarioTiles = scenarioTileFactory.GetScenarioTiles(i); 12 stopWatch.Stop(); 13 elapsedTime = stopWatch.ElapsedMilliseconds; 14 15 this.TilesListBox.Dispatcher.BeginInvoke(() => 16 { 17 this.TilesListBox.Items.Add(String.Format("Scenario {0} has {1} tiles and it took {2}.", 18 i.ToString(), scenarioTiles.Count.ToString(), elapsedTime.ToString())); 19 }); 20 } 21 } 22

 

Now I have accurate diagnostic information for my analysis:

 

image

Windows Phone Application Bar Icons: Gotcha

I wanted to toggle my Panzer General game between air and ground mode. To do so, I created an Icon Button like so:

1 <shell:ApplicationBarIconButton x:Name="airSurface" Text="air/surface" IconUri="Images/appbar.upload.rest.png" Click="airSurface_Click" />

I then wired up the code-behind like so:

1 if (Game.InGroundMode) 2 { 3 this.airSurface.IconUri = new Uri(@"/Images/appbar.upload.rest.png", UriKind.Relative); 4 Game.InGroundMode = false; 5 } 6 else 7 { 8 this.airSurface.IconUri = new Uri(@"/Images/appbar.download.rest.png", UriKind.Relative); 9 Game.InGroundMode = true; 10 } 11

When I ran it, I got this:

image

I first thought – I must have the url wrong. I double checked the properties of the icon images – they are content. After screwing around with url and paths for a bit, it dawned on me that perhaps the uri was fine. Sure enough, when I separated the uri creation into its own line, the error was on the IconUri assignment – the button is null! For someone used to WinForms/WebForm programming, this was unexpected.

image

Jumping back to Nathan’s excellent book, I see that he has a different way of referring to IconButtons in code. I updated my code to this:

1 IApplicationBarIconButton button = sender as IApplicationBarIconButton; 2 3 if (Game.InGroundMode) 4 { 5 Uri uri = new Uri(@"/Images/appbar.upload.rest.png", UriKind.Relative); 6 button.IconUri = uri; 7 Game.InGroundMode = false; 8 } 9 else 10 { 11 button.IconUri = new Uri(@"/Images/appbar.download.rest.png", UriKind.Relative); 12 Game.InGroundMode = true; 13 } 14

And it now toggles as expected

So the two lessons are:

1) When 1 line of code does the work on two, you are setting yourself up for debugging headaches. Separate 1st, then consolidate

2) Just b/c it looks like a duck, walks like a duck, and quacks like a duck; it doesn’t mean that it follows the other APIs that you are used to….

A curious property about dependency properties

For my Panzer General game, I have a collection of hexes that make up the main board. Before every turn, I update the main canvas with this collection. I wrote some code like this:

this.MainCanvas.Children.Clear(); foreach (Hex hex in Game.CurrentBoard) { hex.HexInfoTextBlock.Text = string.Empty; this.MainCanvas.Children.Add(hex); }

However, when I run it, I get the following exception:

 

System.InvalidOperationException was unhandled

Message=Element is already the child of another element.

 

My first thought was to create a Clone for each Hex – remove the old Hex and then add the new one to the collection. However, I still got the same error.

After mucking around Bing for a bit (and not getting any closer to the answer), I decided to back into the parent and try and clear the collection that way:

Canvas currentCanvas = Game.CurrentBoard[0].Parent as Canvas; if (currentCanvas != null) { currentCanvas.Children.Clear(); }

Surprise! Surprise! It worked. I am guessing that the dependency property of the children still hold onto their parent even after the clear until the page goes out of scope.

What a drag

I am getting more into the details of the User Interface of the WinPhone7 game I am writing. I am using the ScrollViewer and the WindowPhone Toolkit.

Using the ScrollViewer_ManipulationStarted event handler, I check to see if there is a hex active. If it is, I call the e.complete() method so that we don’t scroll off the screen where the active hex is.

Using that model, I wanted to use the Tap and Drag completed events from the Toolkit. If the user taps a hex, that hex becomes active. They tap that active hex again, then the hex becomes inactive:

private void GestureListener_Tap(object sender, Microsoft.Phone.Controls.GestureEventArgs e) { Hex closestHex = GetClosestHex(e.GetPosition(this.MainCanvas)); if (Game.ActiveHex == null) { EnableGameActiveHex(closestHex); } else { if (Game.ActiveHex == closestHex) DisableGameActiveHex(); } }

That works fine:

image

I then wanted to code up the drag event. If there is not an active hex, then dragging should do nothing and the scroll viewer will allow the user to inspeact any part of the board. If there is an active hex and there is not a unit in that hex, then nothing should happen. If there is an active hex and there is an active unit in that hex, then the unit should “move” to the hex where the drag completed (and the hex becomes inactive).

To that end, I coded up a quick event handler:

private void GestureListener_DragCompleted(object sender, Microsoft.Phone.Controls.DragCompletedGestureEventArgs e) { if (Game.ActiveHex != null) { Hex closestHex = GetClosestHex(e.GetPosition(this.MainCanvas)); if (Game.ActiveHex.CurrentUnit != null) { MoveGameActiveUnit(closestHex); } DisableGameActiveHex(); } }

Toolkit does not have an e.complete() method so I couldn’t call that on the drag end – but since I don’t enter the drag unless the hex is active, I should be OK.

Next, I added movement allowances to the active unit based on the movement cost of each hex. Like last week’s post on recursion, I created a monster method and used the VS2010 extract method to make the code, well, suck less. To that end, I created a method that all of the movable hexes for the unit based on their max movement points.

private static List<Hex> GetMovableHexes(Hex startHex) { List<Hex> movableHexes = new List<Hex>(); List<Hex> tempHexes = new List<Hex>(); Unit currentUnit = startHex.CurrentUnit; int totalMovementPoints = currentUnit.BaseMovement; int unitMovementCost = 0; int pathMovementCost = 0; List<Hex> adjacentHexes = GetAdjacentHexes(startHex); startHex.PathMovementCost = pathMovementCost; foreach (Hex currentHex in adjacentHexes) { unitMovementCost = HexFactory.CalculateMovementCost(currentHex, currentUnit); pathMovementCost = pathMovementCost + unitMovementCost; if (pathMovementCost <= totalMovementPoints) { currentHex.PathMovementCost = pathMovementCost; movableHexes.Add(currentHex); } pathMovementCost = 0; } for (int i = 0; i < currentUnit.BaseMovement; i++) { DetermineMovableHexes(movableHexes, tempHexes, currentUnit, totalMovementPoints, ref unitMovementCost, ref pathMovementCost, ref adjacentHexes); } return movableHexes; }

 

I then determined if I could get to the hex from the current location (note the argument overload from VS2010 Extract Method):

private static void DetermineMovableHexes(List<Hex> movableHexes, List<Hex> tempHexes, Unit currentUnit, int totalMovementPoints, ref int unitMovementCost, ref int pathMovementCost, ref List<Hex> adjacentHexes) { foreach (Hex currentHex in movableHexes) { adjacentHexes = GetAdjacentHexes(currentHex); foreach (Hex tempHex in adjacentHexes) { unitMovementCost = HexFactory.CalculateMovementCost(tempHex, currentUnit); if (tempHex.CurrentTerrain.RiverInd == true && tempHex.CurrentTerrain.RoadInd == false) { unitMovementCost = 99; } pathMovementCost = currentHex.PathMovementCost + unitMovementCost; if (pathMovementCost <= totalMovementPoints) { if (currentHex.PathMovementCost < pathMovementCost) tempHex.PathMovementCost = pathMovementCost; tempHexes.Add(tempHex); } pathMovementCost = 0; } } foreach (Hex hex in tempHexes) { var q = (from Hex h in movableHexes where h.HexId == hex.HexId select h).FirstOrDefault(); if (q == null) { movableHexes.Add(hex); } } }

Basically, each hex has 2 movement points – the cost for the hex by itself and then the cumulative cost for the hex as part of a path. If the path is too much, the hex is not selected. By looping though each hex then its adjacent hexes, all possible paths are covered. The results look good:

image

Up next – having the unit attack another unit. Fun!

WP7 and the Phone Controls Toolkit

I started hooking up events to my Panzer General Port over the weekend.  There are some see events I want to capture and react to: a tap to activate a cell, a double tap to display information about the cell, a pinch to make the screen smaller or larger, and a swipe to move to a different location on the screen.  Of these event, the pinch was the easiest to handle:

private void GestureListener_PinchCompleted(object sender, Microsoft.Phone.Controls.PinchGestureEventArgs e) { if (e.DistanceRatio < 1) { if (Game.CurrentViewLevel > 1) { Game.CurrentViewLevel--; UpdateHexes(); } } else { if (Game.CurrentViewLevel < 3) { Game.CurrentViewLevel++; UpdateHexes(); } } }

with the UpdateHexes basically redrawing the hexes that make up the board:

private void UpdateHexes() { this.MainCanvas.Children.Clear(); Game.CurrentBoard = BoardFactory.GetBoard(1); foreach (Hex hex in Game.CurrentBoard) { this.MainCanvas.Children.Add(hex); } this.MainCanvas.Width = BoardFactory.BoardWidth; this.MainCanvas.Height = BoardFactory.BoardHeight; }

It works great – here is the same view with the different zoom levels.

image

One of the more annoying things is that the screen “bounces” when you drag it past its designated screen size.  Apparently, this is by design and no one has an easy way to disable this “feature”.  You can ready my dialog on AppHub here.

The next event was the tap.  This was easy to implement on the hex level, however, it was really hard to implement on the board level.  I need it at the board level because the Hex class is unaware of its surroundings and many of the units actions in the game are dependent on its surroundings – what hexes it can go to, can it attack, etc…  I solved this problem with alot of trial and error and my old friend, the Pythagoriem Theorem:

private static Hex GetClosestHex(Point point) { double testDistance = 0; double finalDistance = 99999; Hex closestHex = null; double a = 0; double b = 0; foreach (Hex hex in Game.CurrentBoard) { a = point.X - hex.CenterPoint.X; b = point.Y - hex.CenterPoint.Y; testDistance = Math.Sqrt(a * a + b * b); if (testDistance < finalDistance) { finalDistance = testDistance; closestHex = hex; } } return closestHex; }

The point is found in EventArgument for both Tap, Dopuble Tap, Drag Complete events:

private void GestureListener_Tap(object sender, Microsoft.Phone.Controls.GestureEventArgs e) { Hex closestHex = GetClosestHex(e.GetPosition(this.MainCanvas)); if (closestHex.CurrentUnit != null) UpdateHexes(closestHex); }

I finally added in a Kludge to determine the adjacent hexes for the selected hexes like so:

private static List<Hex> GetAdjacentHexes(Hex currentHex) { List<Hex> adjacentHexes = new List<Hex>(); //Above int targetColumnNumber = currentHex.ColumnNumber; int targetRowNumber = currentHex.RowNumber - 1; adjacentHexes.Add(GetHex(targetColumnNumber, targetRowNumber)); //Below targetColumnNumber = currentHex.ColumnNumber; targetRowNumber = currentHex.RowNumber +1 ; adjacentHexes.Add(GetHex(targetColumnNumber, targetRowNumber)); //Top Left targetColumnNumber = currentHex.ColumnNumber -1 ; if (currentHex.ColumnNumber % 2 == 0) targetRowNumber = currentHex.RowNumber - 1; else targetRowNumber = currentHex.RowNumber; adjacentHexes.Add(GetHex(targetColumnNumber, targetRowNumber)); //Bottom Left targetColumnNumber = currentHex.ColumnNumber - 1; if (currentHex.ColumnNumber % 2 == 0) targetRowNumber = currentHex.RowNumber; else targetRowNumber = currentHex.RowNumber +1; adjacentHexes.Add(GetHex(targetColumnNumber, targetRowNumber)); //Top Right targetColumnNumber = currentHex.ColumnNumber + 1; if (currentHex.ColumnNumber % 2 == 0) targetRowNumber = currentHex.RowNumber - 1; else targetRowNumber = currentHex.RowNumber; adjacentHexes.Add(GetHex(targetColumnNumber, targetRowNumber)); //Bottom Right targetColumnNumber = currentHex.ColumnNumber + 1; if (currentHex.ColumnNumber % 2 == 0) targetRowNumber = currentHex.RowNumber; else targetRowNumber = currentHex.RowNumber +1; adjacentHexes.Add(GetHex(targetColumnNumber, targetRowNumber)); return adjacentHexes; } private static Hex GetHex(int targetColumnNumber, int targetRowNumber) { Hex targetHex = (from hex in Game.CurrentBoard where hex.ColumnNumber == targetColumnNumber && hex.RowNumber == targetRowNumber select hex).First(); if (targetHex != null) return targetHex; else return null; }

 

Note how the LINQ makes for search for the correct hex a snap.  I know have a way of detecting the selected hexs, surrounding hexes for units, and moving units to an end of a swipe:

image

 

This is all very cool (and very easy in Silverlight).  Up next is how to handle 2 unit interaction….

MVVM and Game Design

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:

image

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:

image

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):

image

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:

image

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.