WP7Sudoku

Sudoku

Couple of weeks ago I had created a Sudoku Game in Silverlight and dressed it up in iPhone's theme, I even posted the code on Codeplex. Now, yesterday with some time on my hand and WP7 expectations running high I took the same codebase and by just changing the main view's XAML in Blend, I re-dressed it in WP7's theme. It's not pixel perfect and the animations need work, but I think we have a reasonable WP7 metro-theme inspired implementation in something that took less than 3 hours of work. Moreover, the iPhone version and WP7 version apart for the visuals are absolutely the same, the backing ViewModel code is without a single change and they both offer the exact same functionality. This is a testimony to both the benefits of a MVVM design (even with games) and also to nRoute framework, upon which both these demos were made.

WP7ViewServices
ViewServices 
View the Window Phone 7 Series Version: http://www.orktane.com/nRoute/WP7Sudoku/
View the iPhone Version: http://www.orktane.com/nRoute/Sudoku/

Note: The WP7 Sudoku demo makes use of "Segoe UI Light" font, and it must be installed on your machine else it will default to the Silverlight's portable font.

Finally, please vote below for which ever design you think is better.

Posted by Rishi on 14-Mar-10 12:20 PM, 31 Comments

Tags: , , , ,
Categories: nRoute, Silverlight, UX

Let me expand on the title, with this post we will build an iPhone-styled Sudoku Game application in Silverlight using nRoute Toolkit. In terms of the architecture we will make use of MVVM design techniques in having separate Model, ViewModel, View plus related Services - and we'll piece them together in a loosely-coupled manner. As a starting point, we'll make use of a Sudoku puzzle-generating engine from an existing Siverlight implementation by Lee Saunders, upon this we will build our MVVM friendly layers.

SudokuPreview the Sudoku Application here

Application Model

In Lee Saunders' Sudoku implementation the puzzle-generating engine asks for an expertise level, and in return it hands over two arrays of string type that represented the incomplete and completed puzzle. However for a proper MVVM design that is not adequate, and so we have to encapsulate the puzzle in a proper model that enables both binding and serialization of the puzzle. Post churning, the resulting Application Model, shown below, has two principle concepts - a "Game" which is the puzzle board as such, and the "Box" which is an individual playable square of the board. Each Box type is uniquely indentified using a row and column placement on the Board, and per the Sudoku concept each Box can either be a given or must be user specified - so accordingly we have the IsPredefined property and a SuggestedValue property that holds the user's input. The ExpectedValue property holds the required/correct value, and the possible Suggested and Expected values are constrained using the BoxValue enumeration. The GameLevel enumeration holds the user specified expertise level.

Application Model

 ViewModel (VM)

Given we have the Model part of MVVM, we proceed to the VM - the main thrust of the VM is two fold, one expose the Game data for UI consumption, and two expose the Game-related functionality as UI consumable ICommands. And we do both, as shown below, with our GameViewModel class. Note we are using nRoute Toolkit specific implementations of ICommands.

GameViewModel

In terms of the implementation, the first thing we do is earmark the GameViewModel class as being ViewModel of the GameView UserControl - see the MapViewModel attribute. Also note, the ViewModeBase is an optional helper class that just implements INotifyPropertyChanged and exposes a method to use the same. Next, we expose the Game model via the Game property in the VM - this provides us access to the Boxes that make up the board in the UI. Following that, we have a series of ICommands like NewGame, Reset, Hint, Confirm etc that basically mirror the functionality in the model. And we also enable saving and restoring of a game in the IsolatedStore via the SaveGame and RestoreGame commands. We also have one Reverse Command (which are basically ICommands that execute in the View and are triggered from the VM), the ViewBoardReverseCommand, that basically allows the VM to instruct the View to show the board visuals when required (a sample case is when we restore a Game from the IsolatedStore, the VM asks the View to show the board visuals immediately).

I'll also point out that the distinction between ActionCommand and ActionableCommand - the later allows us to specify a pre-condition to the execution of the ICommand. So for example, the SaveGame command is declared as follows:

   1: SaveGameCommand = new ActionableCommand(
   2:     () => IsolatedStore.SaveDataObject(Game, FILE_NAME), 
   3:     () => Game != null)
   4: .RequeryOnPropertyChanged(this, () => Game);

Line 2 is the execution handler of the ICommand, which in this cases saves the current Game in the IsolatedStore, and in Line 3 we specify a pre-condition to the execution which says that the Game should not be null - this as shown below has repercussion in the UI. And in Line 4 we use an extension method for ICommands to declare that whenever the Game property (of our INotifyPropertyChanged implementing) VM (i.e. this) changes, then re-evaluate the executable state of the command.

SaveGameView

View

For our View we have basically divided the functionality into two screens named Home Screen and Game Screen, these screens are implemented as Visual States of the User Control. Peppered on the screen are various functional elements that trigger various commands we have defined in our VM - for example the "Confirm" element corresponds to ConfirmCommand in the VM, similarly the "Easy" element triggers the NewGameCommand with an "GameLevel.Easy" enumerated parameter. Note, to keep things light and to my preference, all button like elements in UI are actually Border controls rather than Button controls, however you could change that if you like.

Two Screens

Now to automagically inject the appropriate VM we drop the BridgeViewModel behavior onto the UserControl (see below) and optionally, if we wanted, through the same behavior we could also specify some control-lifetime related commands against the VM (eg. command to trigger when the User Control is loaded).

States And BridgeViewModel Behavior

In the Game Screen, the 9x9 board is an ItemsControl bound to the Game.Board property from the VM, and the ItemsControl's ItemsPanelTemplate is a custom Grid control with 9 columns and 9 rows. Further, the ItemsControl's ItemTemplate shows either a Grey'ish Border (i.e. pre-defined value) or an interactive Blue'ish Border (i.e. user-defined value) control. We position each item in the grid using a custom "SetParentGridPos" behavior which binds the grid position to the Col and Row property of the Box Type from our Model. And to enable user specifying the Box value, we rig the Blue'ish Border control to trigger SetBoxValue command in our VM. However to enable the use of the command within the DataTemplate, we need to relay it from our VM - so we first we declare a static RelayCommand, and then bridge the relay from our VM, that is done using the BridgeCommandBehavior (again see the screenshot above).

ViewServices

You can think of ViewServices in nRoute as services that are implemented visually - and here we have two such ViewServices, one that shows messages and another one that get a user's input.

ViewServices

Above are the visual interpretations, but the VM actually uses something like this:

ViewServicesInterfaces

In our app, both these interfaces are implemented in the code-behind by the GameView type UserControl. Now there are many other ways to do this, but I am comfortable with what is essentially a visual implementation to be implemented within the View's code-behind. Also, because the VM doesn't take any hard-coded dependency on the View, you can change the visual implementation at any point as long as the defining contract remains the same.

   1: [MapViewService(typeof(IBoxValueViewService), 
   2:     Lifetime = ViewServiceLifetime.DiscoveredInstance)]
   3: [MapViewService(typeof(IMessageViewService), 
   4:     Lifetime = ViewServiceLifetime.DiscoveredInstance)]
   5: public partial class GameView 
   6:     : UserControl, IBoxValueViewService, IMessageViewService
   7: {
   8:     //  IBoxValueViewService and IMessageViewService implementations..
   9: }

Above we use the MapViewService attribute to earmark the GameView type as the concrete implementation of both ViewServices, also note we are setting the ViewService's Lifetime to be "DiscoveredInstance", which means nRoute will look for the implementation in the VisualTree at runtime. And in terms of using the ViewServices in the VM, we use the ViewServiceLocator static class as such:

   1: var _messageViewService = ViewServiceLocator.GetViewService<IMessageViewService>();
   2: _messageViewService.ShowMessage(message);

Bootstrapping

One last point, to use nRoute you need to bootstrap it by adding it to the application's ApplicationLifetimeObjects collection, so in App.xaml we need to something like this:

   1: <Application  ...
   2:     xmlns:nRoute="clr-namespace:nRoute.ApplicationServices;assembly=nRoute.Toolkit">
   3:     <Application.ApplicationLifetimeObjects>
   4:         <nRoute:nRouteApplicationService />    <!-- BOOTSTRAP nRoute -->
   5:     </Application.ApplicationLifetimeObjects>
   6: </Application>

Summary

So here what we've got is an end-to-end example of using nRoute to develop a MVVM type application, right from the ApplicationModel to the ViewServices. Further, by cleanly delineating the various application parts and using nRoute to bring them together we usefully get a loosely-coupled but cohesive application.

You can view the Sudoku Application here,
and download the source code from Codeplex

Update (24-1-2010):

I updated the application to show a waiting indicator (by Chris Anderson) when creating the puzzle, as it was taking an inordinate amount of time. I also moved the new game generation code into a separate service (see IGenerateGameService), which makes use of a background worker thread so the UI should be a bit more responsive.