Panzer General Portable: RTM!

I finished the last couple of things on the game I have been developing over the last 6 months and submitted it to App Hub certification.   Since I went through the process before, this submission process was a breeze.  Here is a snapshot of the images I submitted:

 

image

 

Hopefully, it gets certified soon and it will be available for download.  The help page for the app is found here.

Curse you copy/paste gremlin!

I was on to deserializing my Windows Phone 7 game data when I ran across this oh-so helpful exception:

System.InvalidOperationException was unhandled Message=There is an error in XML document (0, 0).

And the cause?  I copy and pasted the code for both the turn class and the tiles list .  However, I didn’t update the stream name (turnStream).

IsolatedStorageFileisolatedStorageFile=IsolatedStorageFile.GetUserStoreForApplication();
//Turn
IsolatedStorageFileStreamturnStream=isolatedStorageFile.OpenFile("turn",FileMode.Open);
XmlSerializerturnSerializer=newXmlSerializer(typeof(List<Turn>));
Game.Turns=(List<Turn>)turnSerializer.Deserialize(turnStream);
 
//Tile
IsolatedStorageFileStreamtileStream=isolatedStorageFile.OpenFile("tiles",FileMode.Open);
XmlSerializertileSerializer=newXmlSerializer(typeof(List<Tile>));
Game.BoardFactory.Tiles=(List<Tile>)turnSerializer.Deserialize(turnStream);

This is another good example of why you should break out 1 task into its own function – I would have gotten a compile exception right away and wouldn’t have spent a hour chasing down ghosts in my object model and other such red herrings…

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.

WP7 Phone Capabilities

Note to self.  When adding a Web Browser to a Windows Phone 7 page like this:

<phone:WebBrowser x:Name="HelpBrowser"
                    Grid.Row="1"
                    HorizontalAlignment="Stretch"
                    VerticalAlignment="Stretch"
                    />

and code up a navigation like this:

public GameInformation()
        {
            InitializeComponent();
            this.HelpBrowser.Loaded += new RoutedEventHandler(HelpBrowser_Loaded);
        }

        void HelpBrowser_Loaded(object sender, RoutedEventArgs e)
        {
            this.HelpBrowser.Navigate(new Uri(Constants.HelpDocumentsLocation, UriKind.Absolute));
        }

and you get an exception like this:

image

You are forgetting the capabilities section of the phone app for web browsers.  The inner exception about privileges gives it away (short of actually telling you what happened).  Just add this to the WMAPPManifest.xml file:

<Capabilities>
      <Capability Name="ID_CAP_NETWORKING" />
      <Capability Name="ID_CAP_WEBBROWSERCOMPONENT" />

 

 

 

 

 

Faking Synch WCF calls in WP7

So you can’t do synchronous WCF calls in Windows Phone 7. I have a use case where I do a WCF call to get all of the data to set up a game board of the phone game I am writing. I don’t want the application to progress until the board is set up, so I need it as a blocking call. In addition, the WCF call is 5-6 layers deep in my class hierarchy.  My first idea was just to use a non-thread-safe variable from the Main UI thread and set the flag when the asynch call/thread returned:

        private List<ScenarioTile> GetScenarioTilesFromWebService(int scenarioId)
        {
            CallbackComplete = false;
            PanzerProxy.PanzerClient panzerClient = new PanzerProxy.PanzerClient();
            panzerClient.GetScenarioTilesCompleted += new EventHandler<PanzerProxy.GetScenarioTilesCompletedEventArgs>(panzerClient_GetScenarioTilesCompleted);
            panzerClient.GetScenarioTilesAsync(scenarioId);
            while (CallbackComplete == false)
            {
                Thread.Sleep(1000);
            }
            return ScenarioTiles;
        }

        void panzerClient_GetScenarioTilesCompleted(object sender, PanzerProxy.GetScenarioTilesCompletedEventArgs e)
        {
            ScenarioTiles = new List<ScenarioTile>();
            List<PanzerProxy.ScenarioTile> proxyTiles = e.Result as List<PanzerProxy.ScenarioTile>;
            foreach (PanzerProxy.ScenarioTile proxyScenarioTile in proxyTiles)
            {
                ScenarioTiles.Add(ConvertProxyScenarioTileToScenarioTile(proxyScenarioTile));
            }
            CallbackComplete = true;
        }

The problem is that, well, it doesn’t work. The app just hangs. I then thought of using a ManualResetEvent or AutoResetEvent class to trigger like this:

        ManualResetEvent manualResetEvent = new ManualResetEvent(false);

        private List<ScenarioTile> GetScenarioTilesFromWebService(int scenarioId)
        {
            PanzerProxy.PanzerClient panzerClient = new PanzerProxy.PanzerClient();
            panzerClient.GetScenarioTilesCompleted += new EventHandler<PanzerProxy.GetScenarioTilesCompletedEventArgs>(panzerClient_GetScenarioTilesCompleted);
            panzerClient.GetScenarioTilesAsync(scenarioId);
            manualResetEvent.WaitOne();
            return ScenarioTiles;
        }

        void panzerClient_GetScenarioTilesCompleted(object sender, PanzerProxy.GetScenarioTilesCompletedEventArgs e)
        {
            ScenarioTiles = new List<ScenarioTile>();
            List<PanzerProxy.ScenarioTile> proxyTiles = e.Result as List<PanzerProxy.ScenarioTile>;
            foreach (PanzerProxy.ScenarioTile proxyScenarioTile in proxyTiles)
            {
                ScenarioTiles.Add(ConvertProxyScenarioTileToScenarioTile(proxyScenarioTile));
            }
            manualResetEvent.Set();
        }

Still no luck.  I then ran across this thread – basically you can’t do anything to block the Main UI thread.  Chong’s solution was to launch a 3rd thread from the secondary thread.  That seemed to me as overly complex and smelled of brittle code.  I then took a step back and realized I just had to architect my solution to get the same effect.  Basically, once the user hits the LOAD button, the MAINUI thread is done.  However, the page also subscribes to an event that the business logic raises.  Once the event fires, then the app progresses to the next page.

So instead of this:

private void SetupScenario(int scenarioId)
{
    Game.CurrentScenarioId = scenarioId;
    Game.BoardFactory.PopulateBoard(scenarioId);
    Game.CurrentBoard = Game.BoardFactory.Board;
    Game.Turns = Game.TurnFactory.GetTurnsForAScenario(scenarioId);
    this.NavigationService.Navigate(new Uri(@"/Briefing.xaml", UriKind.Relative));
}

I have this:

private void SetupScenario(int scenarioId)
{
    Game.CurrentScenarioId = scenarioId;
    Game.BoardFactory.BoardLoaded += new EventHandler<EventArgs>(BoardFactory_BoardLoaded);
    Game.BoardFactory.PopulateBoard(scenarioId);
    Game.CurrentBoard = Game.BoardFactory.Board;
    Game.Turns = Game.TurnFactory.GetTurnsForAScenario(scenarioId);

}

void BoardFactory_BoardLoaded(object sender, EventArgs e)
{
    this.NavigationService.Navigate(new Uri(@"/Briefing.xaml", UriKind.Relative));
}

The event is defined as simple as it comes:

public event EventHandler<EventArgs> BoardLoaded;

and

void TileFactory_TilesLoaded(object sender, EventArgs e)
{
    Tiles = Game.TileFactory.Tiles;
    Hexes = Game.HexFactory.GetHexes(Tiles);
    foreach (Hex hex in Hexes)
    {
        Board.MainCanvas.Children.Add(hex);
    }
    SetBoardDimensions(Board, Tiles);
    BoardLoaded(null, null);
}

Basically I just bubbled up the WCF asych event up from the lowest level to the UI page.