Porting - microsoft/sudokumaster-wp GitHub Wiki

Porting the Qt Sudokumaster to Windows Phone 7 Silverlight

UI Overview

The application contains two pages, the main page which contains the game board, and the high scores page. Main page is similar to Qt application’s main view;

WP main page  Qt main view

Graphics from the Qt application were used as-is. The Qt application is designed for a bit lower resolution (640x360), but the existing images scaled nicely on the 800x480 screen, though the cell background was resized to improve performance. The statistics and buttons on the bottom of the screen differ a bit. The Silverlight application is not a full screen application, and vertically stacked statistics didn’t fit nicely on the page when the system tray on top of the page and the application bar buttons are visible, so the statistics are stacked horizontally side-by-side. Silverlight application has no Options or Exit buttons. Exit button is not really needed at all, since the device buttons (Back, Start and Search) must exist on all WP 7 devices. Only Metro UI style new game and high scores buttons exist in application bar menu. Qt application used a separate options menu view for these;

Qt options

The high scores are listed on a separate page while Qt's listview pops up on top of the main view;

WP highscores  Qt highscores

Silverlight application contains two additions; scrollable Top 20 list, and the number of moves made to solve the puzzle is included.

When starting the application a splashscreen is displayed. With Qt the splashscreen was implemented by changing the main qml file from SplashScreen.qml to main.qml at application startup with a timer.

With Silverlight this is easy and no coding is needed. When you create a new Silverlight project, a splashscreenimage.jpg file is added to the project, and when the application is launched, the splash screen is displayed and will remain displayed until the navigation to the first page is complete. Just replace the splashscreen.jpg with suitable one.

WP splash screen

Generating new puzzles

The algorithm for generating new puzzles is rather slow. It takes 2-3 seconds to generate a puzzle on the device, and a wait note (or progress bar) was needed. Silverlight's standard ProgressBar control was not used. The Qt application used a spinning image (circle) animation, and a similar animation was implemented for Silverlight application. The WaitNote is a UserControl containing a rectangle filled with semi-transparent image. The animation rotates the rectangle forever, 360 degrees every 1.5 seconds. On silverlight we cut down the time to do full 360 rotation to 0.5 second which gives the impression of being blazing fast.

	<Rectangle.Resources>
		<Storyboard x:Name="spinAnimation">
			<DoubleAnimation
				Storyboard.TargetName="Transform" 
				Storyboard.TargetProperty="Angle"
				By="360" 
				Duration="0:0:0.5"  
				AutoReverse="False" 
				RepeatBehavior="Forever" />
		</Storyboard>
	</Rectangle.Resources>

The animation is defined very similarly in WaitNote.qml;

	NumberAnimation on rotation {
		id: animation

		loops: Animation.Infinite
		from: 0
		to: 360
		duration: 1500
	}

The animations look exactly the same on both applications, but on device the Silverlight animation ran much smoother;

WP wait note  Qt wait note

The WaitNote is created in xaml but is hidden. The generation of a puzzle would block the UI thread and the animation would not show up at all, so a new thread is created for the puzzle generation;

	private void NewGame()
	{// Display wait note (spinning circle)
		waitIndicator.Visibility = System.Windows.Visibility.Visible;
		waitIndicator.StartSpin();// Disable databinding while generating puzzle
		DataContext = null;

		// Puzzle generation takes couple of seconds, do it in another thread
		ThreadPool.QueueUserWorkItem(dummy =>
				{
					// generating puzzle doesn't touch UI so it can run on another thread
					game.GeneratePuzzle();

					// switching to UI thread to modify UI components
					Deployment.Current.Dispatcher.BeginInvoke(() =>
					{
						DataContext = game.Model; // let's turn on databinding again
						gameTimer.Start();
						gameStartTime = DateTime.Now;
						gameState = GameState.Ongoing;
						UpdateStatus();
						waitIndicator.Visibility = System.Windows.Visibility.Collapsed;
						waitIndicator.StopSpin();
					});
				});

		...
	}

The same problem was solved with a WorkerScript on Qt application;

	WorkerScript {
		id: puzzleWorker
		source: "gameLogic.js"
		onMessage: {
			Functions.numbers = messageObject.board;
			empties = messageObject.boardEmpties;
			boardChanged();
			viewLoader.close();
			gameOn = true;
			mainBoard.focus = true;
			mainTimer.start();
		}
	}
	…
	// gamelogic.js
	WorkerScript.onMessage = function(message) {
		generatePuzzle(message.rands);
		WorkerScript.sendMessage( {board: numbers, boardEmpties: empties} );
	}

Game logic

The gameLogic.js in Qt application contains the code for puzzle generation, and the code needed to check player’s actions (placing a number on the board). The JavaScript code was easy to port to C#, since it contains just elemental arithmetic and logic with integer arrays. For example;

JavaScript:

	/*
	 * Creates an array with numbers 1-9 in it, ordered randomly.
	 */
	function fillRandOrder()
	{
		randOrder = new Array();
		var isSet = 0;
		var rand;
		for (var i = 0; i < DIM; i++) {
			while (!isSet) {
				rand = Math.floor(Math.random()*DIM)+1;
				for (var j = 0; j < DIM; j++)
					if (randOrder[j] && (rand ## randOrder[j]))
						break;
				if (j ## DIM) {
					randOrder[i] = rand;
					isSet = 1;
				}
			}
			isSet = 0;
		}
	}

C#:

	/// <summary>
	/// Creates an array with numbers 1-9 in it, ordered randomly.
	/// </summary>
	private void FillRandOrder()
	{
		randOrder = new int[rowLength];
		bool isSet = false;
		int rand, j;
		for (int i = 0; i < rowLength; i++)
		{
			while (!isSet)
			{
				rand = randGen.Next(columnLength) + 1;
				for (j = 0; j < columnLength; j++)
					if (rand ## randOrder[j])
						break;
				if (j ## columnLength)
				{
					randOrder[i] = rand;
					isSet = true;
				}
			}
			isSet = false;
		}
	}

So, what was needed to port the JavaScipt to C#:

  • Put the functions in a class (GameLogic)
  • Replace Arrays() with int[]
  • Replace vars with int and bool
  • Use Random instead of Math.random
  • Check the scope of variables

This is enough to get the JavaScript to compile, but some additions were made to the GameLogic to bind it with the UI.

Game board

MainPage.xaml contains a grid, BoardGrid, of 9 rows and 9 columns. The grid is initially empty, and is filled in CreateGrid method with instances of Cells. Each Cell is a UserControl containing a TextBlock for the cell’s value. Button -control was not used, since the cell needed to be quite small (~50 pix), and displaying the number on a button so small proved to be quite impossible.

Couple of animations was needed for the cell. The cell turns to red when touched, and goes back to original when released. This is achieved by putting a red element on top of the cell, and changing its opacity;

	<!-- Animations -->
		<Grid.Resources>
		<!-- Makes the cell go gradually from white/black to red -->
		<Storyboard x:Name="fadeInAnimation">
			<DoubleAnimation Storyboard.TargetName="Foreground"
				Storyboard.TargetProperty="Opacity" From="0.0"
				To="1.0" Duration="0:0:1" />
		</Storyboard>
		<!-- Makes the cell go gradually from red to white/black -->
		<Storyboard x:Name="fadeOutAnimation">
			<DoubleAnimation Storyboard.TargetName="Foreground"
				Storyboard.TargetProperty="Opacity" From="1.0"
				To="0.0" Duration="0:0:1" />
		</Storyboard>
		<!-- Blinks the cell twice between white and red -->
		<Storyboard x:Name="blinkAnimation">
			<DoubleAnimation Storyboard.TargetName="Foreground"
				Storyboard.TargetProperty="Opacity" From="0.0"
				To="1.0" AutoReverse="True" RepeatBehavior="2x"
				Duration="0:0:0.5" />
		</Storyboard>
	</Grid.Resources>

When the cell is touched, an instance of NumberSelection control is created and displayed.

Number selection

The number pad on Qt application is made of a GridView containing Rectangles with Text on them. The same approach works with Silverlight, Grid with Rectangles and TextBlocks on them;

WP number pad  Qt number pad

Qt application used pop-up transitions in all views, while the Silverlight application uses fade in/out animations. It is possible to do a similar pop-up animation with Silverlight by using a transform animation, but it was a bit jerky, fade animation was better looking. The visual difference is minor, since the animation needed to be short (~0.3s) with the number selection dialog.

	Transition {
		to: "open"
		PropertyAnimation {
			properties: "scale, x, y"
			duration: animSpeed
			easing.type: Easing.OutBack
			easing.overshoot: 1.8
		}
	}
	<Storyboard x:Name="fadeInAnimation">
		<DoubleAnimation
			Storyboard.TargetName="LayoutRoot" 
			Storyboard.TargetProperty="Opacity"
			From="0.0" To="0.8" Duration="0:0:0.3" 
			/>
	</Storyboard>

Highscores

Highscores page is simple one; background, some text and a listbox. Highscores are listed on a ListBox, where each item contains multiple TextBlocks. This is achieved by defining ItemTemplate for the ListBox;

        <!-- And the listbox containing the scores sbelow the text. -->
        <ListBox Name="HighscoreList" FontSize="28" Margin="0,160,0,0"
                 Foreground="White" HorizontalAlignment="Stretch">
            
            <!-- Define item template. Each item in the listbox is constructed
            from four textblocks; position, name, time and moves.-->
            <ListBox.ItemContainerStyle>
                <Style TargetType="ListBoxItem">
                    <Setter Property="HorizontalContentAlignment" Value="Stretch" />
                </Style>
            </ListBox.ItemContainerStyle>
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <Grid Name="ListBoxItemGrid" HorizontalAlignment="Stretch">
                        
                        <!-- Columns for the textblocks -->
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="10*" />
                            <ColumnDefinition Width="55*" />
                            <ColumnDefinition Width="25*" />
                            <ColumnDefinition Width="10*" />
                        </Grid.ColumnDefinitions>
                        
                        <!-- Note the data binding ('{Binding *}'). The list box 
                        is populated by binding the list of scores with
                        "HighscoreList.ItemsSource = scores;" in the constructor
                        of HighscoresPage. -->
                        <TextBlock Grid.Column="0" Text="{Binding Index}" />
                        <TextBlock Grid.Column="1" Text="{Binding Name}" />
                        <TextBlock Grid.Column="2" Text="{Binding Time}" />
                        <TextBlock Grid.Column="3" Text="{Binding Moves}" />
                    </Grid>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>

Items in the ListBox are bind to the highscore List by setting the ItemsSource of the listbox;

	public static List<HighscoreItem> scores;
	...

	public Highscores()
	{
		InitializeComponent();
		HighscoreList.ItemsSource = scores;
	}

In the ItemTemplate we defined data bindings for every TextBlock, Text="{Binding Index}", Text="{Binding Name}", etc, and the HighscoreItem has matching public properties Index, Name, Time and Moves. When manipulating the HighscoreItems in the scores –list, the changes are updated to the UI automatically.

The scores are stored into a XML file using XmlSerializer, while Qt application used sqlite database (dbHandling.js).

Sound effects

Qt application doesn't have any sound effects, so this is a new feature. Initially the goal was to use only Silverlight's components, in this case the MediaElement. The MediaElement has several limitations, for example, only one MediaElement can be used at a time, and it stops all other media playback on the phone - which actually prevents you from getting an application certified (if not used properly). Decision was made to use XNA's SoundEffect instead.

Sound effects are played when;

  • user clicks on a cell
  • user clicks a number on the number selection dialog
  • user has made an invalid move
  • the game ends

A simple PlaySound method was enough, since the soundscape is so small;

	static public void PlaySound(string soundFile)
	{
		using (var stream = TitleContainer.OpenStream(soundFile))
		{
			var effect = SoundEffect.FromStream(stream);
			FrameworkDispatcher.Update();
			effect.Play();
		}
	}

Orientation

The application supports both, portrait and landscape modes. This was implemented by splitting the MainPage into a grid with multiple columns and rows in xaml, and when the orientation changes, the UI elements in the grid are assigned to different rows and columns. This required suprisingly lot of code, since the layout differs a lot between the two orientations - especially in the statistics grid, which is almost rebuilt in the code. This could also be achieved with VisualStateManger, but that would require a bit of a different approach.

	private void PhoneApplicationPage_OrientationChanged(object sender,OrientationChangedEventArgs e)
	{
		if (e.Orientation ## PageOrientation.Landscape ||
		e.Orientation ## PageOrientation.LandscapeLeft ||
		e.Orientation ## PageOrientation.LandscapeRight)
		{
			Logo.SetValue(Grid.RowProperty, 1);
			Logo.SetValue(Grid.ColumnSpanProperty, 1);

			BoardGrid.SetValue(Grid.RowProperty, 0);
			BoardGrid.SetValue(Grid.ColumnProperty, 1);
			BoardGrid.SetValue(Grid.RowSpanProperty, 3);
			BoardGrid.SetValue(Grid.ColumnSpanProperty, 2);
			...
		}
		else
		{
			Logo.SetValue(Grid.RowProperty, 0);
			Logo.SetValue(Grid.ColumnSpanProperty, 2);

			BoardGrid.SetValue(Grid.RowProperty, 1);
			BoardGrid.SetValue(Grid.ColumnProperty, 0);
			BoardGrid.SetValue(Grid.RowSpanProperty, 1);
			BoardGrid.SetValue(Grid.ColumnSpanProperty, 2);
			...
		}
	}

WP landscape  Qt landscape

The layout of the HighScores page was so simple, that it didn't need a handler for the orientation events - the layout defined in xaml worked ok in both orientations.

With Qt the orientation was handled by setting the margins of UI elements in qml depending on the orientation. For example, the statistics image on main view;

    Image {
        id: statics

        property int imageSize: 20

        source: "gfx/statistic.png"
        width: 150
        height: 130
        anchors {
            bottom: parent.bottom
            bottomMargin: portrait ? 5 : 100
            left: parent.left
            leftMargin: portrait ? parent.width/2-width/2 : 10
        }
	...

Tombstoning

With WP 7 the application is basically terminated when user does something that puts the app in the background, such as pressing the Start -button. Only the current page and page history of the application are stored automatically. The game state needs to be stored by the application, when it is about to be deactivated.

MainPage listens for the Deactivated –event, and stores the game state (contents of the cells, moves, time, etc) into a file (gamestate.dat) when the application is deactivated. Upon startup the application restores the state, if the gamestate.dat file exists. MainPage cannot listen for Activated events, since it is not instantiated when the event occurs, only the main Application, App class can receive these events.

	public MainPage()
	{PhoneApplicationService.Current.Deactivated += new EventHandler<DeactivatedEventArgs>(App_Deactivated);
		RestoreState();
	}

The App.cs has pre-created handlers for application lifetime events, as defined in App.xaml;

	<Application.ApplicationLifetimeObjects>
		<!--Required object that handles lifetime events for the application-->
		<shell:PhoneApplicationService 
			Launching="Application_Launching" Closing="Application_Closing" 
			Activated="Application_Activated" Deactivated="Application_Deactivated"/>
	</Application.ApplicationLifetimeObjects>

The high score list is loaded in App.cs's Application_Launching and Application_Activated event handlers, but the game state is not. The reason for this is that if you use App.cs's handlers, you’ll need to have static members, or maintain the game state in App, since the pages, MainPage and HighscoresPage, do not exist when the event is received. High score list can easily be static, it is just one List, but the game state is spread to various classes and members. Didn’t want to make major changes to the application since the game itself was already working, and restoring the state in constructor worked ok.

⚠️ **GitHub.com Fallback** ⚠️