Click here to Skip to main content
13,356,787 members (83,023 online)
Click here to Skip to main content
Add your own
alternative version


30 bookmarked
Posted 4 Jul 2012

Metro: Shuffle

, 11 Jul 2012
Rate this:
Please Sign up or sign in to vote.
A Metro tile puzzle game



Shuffle is a tile puzzle game where the objective is to arrange the numbered tiles in numerical order. Since this is a Metro application there's a huge focus on touch interaction so as to emulate a real tile puzzle game. The user has to slide the tiles into place and moving one tile can cause another tile to move e.g. in the screenshot above moving tile 3 downwards will also resort in tile 2 moving downwards.


Shuffle contains top and bottom app bars with buttons for carrying out various tasks.


The bottom app bar contains a single button which the user can tap/click to shuffle the tiles.


Shuffle button in bottom app bar

The top app bar contains buttons for changing the application's look.


Theme buttons in top app bar

Wallpaper buttons in top app bar

The colored buttons on the left of the top app bar can be used to change the application's theme e.g. clicking/tapping on the green button changes the application's theme to green.

The buttons on the right of the top app bar can be used to set or remove the application's wallpaper. Clicking/tapping on the Set Wallpaper button displays the file picker which enables the user to select an image file to use as the app's wallpaper.

Selecting an image using the file picker

Shuffle with wallpaper

Setting the wallpaper does not override the app's theme. When the user sets the theme or wallpaper it will be loaded the next time the user opens the app.


Each tile is a UserControl and contains a Write Only property for setting the Text property of a TextBlock that displays a tile's number.

Public WriteOnly Property TileNumber As Double
    Set(value As Double)
        NumberTextBlock.Text = value
    End Set
End Property
The UserControl also contains a Thumb control whose DragDelta event handler deals with the movement of the tile.
Private Sub GameTileThumb_DragDelta(ByVal sender As Object, ByVal e As DragDeltaEventArgs) _
    Handles GameTileThumb.DragDelta

    vChange = e.VerticalChange
    hChange = e.HorizontalChange

    absVChange = Math.Abs(e.VerticalChange)
    absHChange = Math.Abs(e.HorizontalChange)

    ' Vertical movement
    If (absVChange > absHChange) Then
        ' Down
        If (vChange > 0) Then
            drctn = Direction.Down
        Else ' Up
            drctn = Direction.Up
        End If
        ' Horizontal movement
    ElseIf (absHChange > absVChange) Then
        ' Right
        If (hChange > 0) Then
            drctn = Direction.Right
            ' Left
            drctn = Direction.Left
        End If
    End If
End Sub

The MoveTileDownwards() method does as its name suggests,

Private Sub MoveTileDownwards(ByVal e As DragDeltaEventArgs)
    y = Canvas.GetTop(Me)
    x = Canvas.GetLeft(Me)

    If (x = LOWER_BOUND) OrElse (x = 130) OrElse (x = UPPER_BOUND) Then
        Canvas.SetTop(Me, (y + SHIFT))
    End If
End Sub

MoveTileBelow() moves any tile that is positioned directly below the tile being shifted,

Private Sub MoveTileBelow(ByVal e As DragDeltaEventArgs)
    For Each tile As GameTile In parentCanvas.Children
        tl_X = Canvas.GetLeft(tile)
        tl_Y = Canvas.GetTop(tile)

        If (tl_X = x) And (tl_Y = (y + TILE_HEIGHT)) Then
            tile.GameTileThumb_DragDelta(Nothing, e)
            Exit Sub
        End If
End Sub

When a tile is moving it checks whether there's a tile blocking its path. PartialTileBlock() checks whether there is a tile that is partially in its path e.g. in the image below tile 7 will prevent tile 3 from being move upwards.

Private Sub PartialTileBlock()
    Select Case drctn
        Case Direction.Up
            For Each tile As GameTile In parentCanvas.Children
                tX = Canvas.GetLeft(tile)
                tY = Canvas.GetTop(tile)
                If (tY < y) AndAlso (tY = (y - TILE_HEIGHT)) AndAlso (tX > x) AndAlso (tX < (x + TILE_WIDTH)) Then
                    Canvas.SetTop(Me, tY + TILE_HEIGHT)
                    Exit Sub
                ElseIf (tY < y) AndAlso (tY = (y - TILE_HEIGHT)) AndAlso (tX < x) AndAlso ((tX + TILE_WIDTH) > x) Then
                    Canvas.SetTop(Me, tY + TILE_HEIGHT)
                    Exit Sub
                End If
        Case Direction.Down
            For Each tile As GameTile In parentCanvas.Children
                tX = Canvas.GetLeft(tile)
                tY = Canvas.GetTop(tile)
                If (tY > y) AndAlso (tY = (y + TILE_HEIGHT)) AndAlso (tX > x) AndAlso (tX < (x + TILE_WIDTH)) Then
                    Canvas.SetTop(Me, tY - TILE_HEIGHT)
                    Exit Sub
                ElseIf (tY > y) AndAlso (tY = (y + TILE_HEIGHT)) AndAlso (tX < x) AndAlso ((tX + TILE_WIDTH) > x) Then
                    Canvas.SetTop(Me, tY - TILE_HEIGHT)
                    Exit Sub
                End If
        Case Direction.Left
            For Each tile As GameTile In parentCanvas.Children
                tX = Canvas.GetLeft(tile)
                tY = Canvas.GetTop(tile)
                If (tX < x) AndAlso (tX = (x - TILE_HEIGHT)) AndAlso (tY > y) AndAlso (tY < (y + TILE_WIDTH)) Then
                    Canvas.SetLeft(Me, tX + TILE_HEIGHT)
                    Exit Sub
                ElseIf (tX < x) AndAlso (tX = (x - TILE_HEIGHT)) AndAlso (tY < y) AndAlso ((tY + TILE_WIDTH) > y) Then
                    Canvas.SetLeft(Me, tX + TILE_HEIGHT)
                    Exit Sub
                End If
        Case Direction.Right
            For Each tile As GameTile In parentCanvas.Children
                tX = Canvas.GetLeft(tile)
                tY = Canvas.GetTop(tile)
                If (tX > x) AndAlso (tX = (x + TILE_HEIGHT)) AndAlso (tY > y) AndAlso (tY < (y + TILE_WIDTH)) Then
                    Canvas.SetLeft(Me, tX - TILE_HEIGHT)
                    Exit Sub
                ElseIf (tX > x) AndAlso (tX = (x + TILE_HEIGHT)) AndAlso (tY < y) AndAlso ((tY + TILE_WIDTH) > y) Then
                    Canvas.SetLeft(Me, tX - TILE_HEIGHT)
                    Exit Sub
                End If
    End Select
End Sub


Metro apps make use of Pages, which are the equivalent of Windows in desktop applications. Shuffle contains only one page, MainPage. MainPage contains a two-dimensional array with the 9 possible initial coordinates of the 8 tiles that are displayed.

Private TileCoords()() As Double = New Double(8)() { _
    New Double() {0, 0}, New Double() {130, 0}, New Double() {260, 0}, _
    New Double() {0, 130}, New Double() {130, 130}, New Double() {260, 130}, _
    New Double() {0, 260}, New Double() {130, 260}, New Double() {260, 260}}

The LoadTiles() method is called when MainPage is loaded and populates a layout container, of type Canvas, with 8 tiles.

Private Sub LoadTiles()
    Dim rnd As New Random
    Dim n As Integer = 1

        num = rnd.Next(0, 9)

        If (rndList.Contains(num) <> True) Then
        End If
    Loop Until rndList.Count = 8

    For Each i As Integer In rndList
        x = TileCoords(i)(0)
        y = TileCoords(i)(1)

        gameTile = New GameTile
        gameTile.TileNumber = n

        Canvas.SetLeft(gameTile, x)
        Canvas.SetTop(gameTile, y)
        n += 1
End Sub


The AppBar is a toolbar for displaying application-specific commands and tools. It is hidden by default, and is shown or dismissed when the user swipes from the edge of the screen. An app bar can appear at the top or bottom of the page, or both. It is assigned to a Page's TopAppBar or BottomAppBar property.

Shuffle's AppBars are defined as follows in MainPage's XAML markup,

    <AppBar x:Name="tpAppBar" Padding="10,0,10,0" Height="76">
            <StackPanel Orientation="Horizontal" HorizontalAlignment="Left">
                <Button x:Name="GreenButton" Background="#FF03A018" Margin="20,0,20,0" Width="80" Height="40"/>
                <Button x:Name="RedButton" Background="#FFBF0303" Margin="0,0,20,0" Width="80" Height="40"/>
                <Button x:Name="BlueButton" Background="#FF0C84E8" Margin="0,0,20,0" Width="80" Height="40"/>
            <StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
                <Button x:Name="SetWallpaperButton" Content="Set Wallpaper" Margin="0,0,20,0"/>
                <Button x:Name="RemoveWallpaperButton" Content="Remove Wallpaper" Margin="0,0,15,0"/>                                 
    <AppBar x:Name="btmAppBar" Padding="10,0,10,0" Height="76">
            <Button x:Name="ShuffleButton" Content="Shuffle" HorizontalAlignment="Center"/>


Clicking/tapping the Shuffle button calls the ShuffleTiles() method,

Private Sub ShuffleTiles()
End Sub

Setting the Theme

Unfortunately you can't refer to a DynamicResource in Metro. This creates a rather tricky scenario when it comes to skinning/theming a Metro app. In Shuffle I change the value of the Source property of an Image that forms the background of the app and make use of some custom Storyboards to change the Fill property of two Paths.

    <Image x:Name="BackgroundImage" Source="Images/BackgroundBlue.png" Stretch="UniformToFill" Margin="0"/>
    <Image x:Name="WallpaperImage" Stretch="UniformToFill" Margin="0"/>
    <Grid Name="GameGrid">
        <Path x:Name="OuterEdge" Fill="{StaticResource OuterEdgeBrush}" 

              Stretch="Fill" Width="396" Height="396" Data="..."/>                
        <Path x:Name="InnerSurface" Fill="#FF005170" 

              Stretch="Fill" StrokeLineJoin="Round" Stroke="{x:Null}" Data="..." 

              Height="390" Width="390"/>            

The Storyboards that are used to change the theme are defined in the Resources section of the Page.

    <!-- Storyboards for themes -->
    <Storyboard x:Name="BlueThemeStoryboard">
        <ColorAnimation Storyboard.TargetName="OuterEdge" 


                    To="#FF001F2E" Duration="00:00:00.1" EnableDependentAnimation="True"/>
        <ColorAnimation Storyboard.TargetName="OuterEdge" 


                    To="#FF003953" Duration="00:00:00.1" EnableDependentAnimation="True"/>
        <ColorAnimation Storyboard.TargetName="OuterEdge" 


                    To="#FF0075AC" Duration="00:00:00.1" EnableDependentAnimation="True"/>

        <ColorAnimation Storyboard.TargetName="InnerSurface" 


                    To="#FF005170" Duration="00:00:00.1"/>

    <Storyboard x:Name="GreenThemeStoryboard">
        <ColorAnimation Storyboard.TargetName="OuterEdge" 


                    To="#FF042E00" Duration="00:00:00.1" EnableDependentAnimation="True"/>
        <ColorAnimation Storyboard.TargetName="OuterEdge" 


                    To="#FF085300" Duration="00:00:00.1" EnableDependentAnimation="True"/>
        <ColorAnimation Storyboard.TargetName="OuterEdge" 


                    To="#FF10AC00" Duration="00:00:00.1" EnableDependentAnimation="True"/>

        <ColorAnimation Storyboard.TargetName="InnerSurface" 


                    To="#FF0A7000" Duration="00:00:00.1"/>

    <Storyboard x:Name="RedThemeStoryboard">
        <ColorAnimation Storyboard.TargetName="OuterEdge" 


                    To="#FF2E0000" Duration="00:00:00.1" EnableDependentAnimation="True"/>
        <ColorAnimation Storyboard.TargetName="OuterEdge" 


                    To="#FF530000" Duration="00:00:00.1" EnableDependentAnimation="True"/>
        <ColorAnimation Storyboard.TargetName="OuterEdge" 


                    To="#FFAC0000" Duration="00:00:00.1" EnableDependentAnimation="True"/>

        <ColorAnimation Storyboard.TargetName="InnerSurface" 


                    To="#FF700000" Duration="00:00:00.1"/>

In Metro not all custom animations run by default. Animations that can have a performance impact must be enabled before they run. These types of animation are referred to as dependent animations. To enable them their EnableDependentAnimation property must be set to True. Notice that some of the animation objects defined in the XAML markup above have this property set to True, specifically those targeting the Path with a GradientBrush.

The theme can now be set by calling the SetTheme() method,

''' <summary>
''' Sets the user's preferred theme and writes the user's preference to
''' the app's settings.
''' </summary>
''' <param name="theme">User's preferred theme</param>
Private Sub SetTheme(ByVal theme As String)
    Dim localSettings As ApplicationDataContainer = ApplicationData.Current.LocalSettings
    localSettings.CreateContainer("ThemeContainer", ApplicationDataCreateDisposition.Always)

    Dim bmp As New BitmapImage

    Select Case theme
        Case "Blue"
            bmp.UriSource = New Uri(Me.BaseUri, "/Images/BackgroundBlue.png")
        Case "Green"
            bmp.UriSource = New Uri(Me.BaseUri, "/Images/BackgroundGreen.png")
        Case "Red"
            bmp.UriSource = New Uri(Me.BaseUri, "/Images/BackgroundRed.png")
    End Select
    ' Write setting
    localSettings.Containers("ThemeContainer").Values("ThemeSetting") = theme
    BackgroundImage.Source = bmp
End Sub

Notice that in the method above I'm creating an application setting called ThemeSetting that is held in a container named ThemeContainer. Once this setting is written to it is used to load the user's preferred theme the next time the application is launched.

''' <summary>
''' Checks whether the user has set a preferred theme and sets
''' it when the app is loaded.
''' </summary>
Private Sub LoadTheme()
    Dim localSettings As ApplicationDataContainer = ApplicationData.Current.LocalSettings

    If (localSettings.Containers.ContainsKey("ThemeContainer")) Then
        ' Read setting
        Dim theme As String = CStr(localSettings.Containers("ThemeContainer").Values("ThemeSetting"))
    End If
End Sub

Setting the Wallpaper

Setting the wallpaper is done by calling the SetWallpaper() method.

''' <summary>
''' Sets the app's wallpaper when the user selects an image file.
''' </summary>
Private Async Sub SetWallpaper()
    Dim openPicker As New FileOpenPicker
    With openPicker
        .ViewMode = PickerViewMode.Thumbnail
        .SuggestedStartLocation = PickerLocationId.PicturesLibrary
    End With

    Dim file As StorageFile = Await openPicker.PickSingleFileAsync()

    If (file IsNot Nothing) Then
        Dim stream As IRandomAccessStream = Await file.OpenAsync(FileAccessMode.Read)
        Dim bmp As New BitmapImage
        WallpaperImage.Source = bmp

        ' Store the file
        Dim token As String = StorageApplicationPermissions.FutureAccessList.Add(file)
    End If
End Sub

The method above launches the file picker. The file picker is an interface that lets the user pick one or more files for an app to open. Since here the file picker is used to display pictures, that the user can set as the wallpaper, the ViewMode of the FileOpenPicker object is set to PickerViewMode.Thumbnail. The SuggestedStartLocation property specifies that the Pictures library is the first place the file picker should check for pictures, when it is launched for the first time. (On subsequent occassions when the user launches the file picker it will start with the last directory the user checked). I also specify the file types the file picker should deal with by adding them to the list returned by the FileTypeFilter property.

When all the necessary file picker properties have been set the file picker is displayed by calling Await FileOpenPicker.PickSingleFileAsync().

Once a file has been opened I need to keep track of the file so that it can be used to display the wallpaper the next time the app is launched. To do this I add the file to the FutureAccessList. Adding a file to the FutureAccessList returns a token, which is a string value that uniqely identifies the file in the list. To access the file on the next app launch I could store the token in the app's settings but there is another option, which I'll explain in the next section.

Loading the Wallpaper

When the application is loaded it checks whether the user had specified a wallpaper image. This is done by calling the LoadWallpaper() method.

''' <summary>
''' Sets the app's wallpaper when the application is loaded.
''' </summary>
Private Async Sub LoadWallpaper()
    Dim n As Integer = StorageApplicationPermissions.FutureAccessList.Entries.Count

    If (n > 0) Then
        Dim firstToken As String = StorageApplicationPermissions.FutureAccessList.Entries.First.Token
        Dim retrievedFile As StorageFile = Await StorageApplicationPermissions.FutureAccessList.GetFileAsync(firstToken)
        Dim stream As IRandomAccessStream = Await retrievedFile.OpenAsync(FileAccessMode.Read)
        Dim bmp As New BitmapImage
        WallpaperImage.Source = bmp
    End If
End Sub

To set the wallpaper I retrieve the file that was added to the FutureAccessList. This is done by using the token of the first entry in the list. This application's FutureAccessList will have only one entry if a file was added to it.


In the application I've made use of several Metro features whose guidelines have been defined by Microsoft. The following is a list containing links to guidelines that relate to some of the utilized features;


  • 4th, July 2012: Initial post
  • 9th, July 2012: Added theme and wallpaper feature


This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


About the Author

Meshack Musundi
Software Developer
Kenya Kenya
Meshack is a software developer with a passion for WPF.


  • CodeProject MVP 2013
  • CodeProject MVP 2012

You may also be interested in...

Comments and Discussions

GeneralMy vote of 5 Pin
Member 1020119010-Aug-13 3:23
memberMember 1020119010-Aug-13 3:23 
QuestionHow to improve it Pin
brant_chen7-Nov-12 0:34
memberbrant_chen7-Nov-12 0:34 
AnswerRe: How to improve it Pin
Meshack Musundi7-Nov-12 1:44
mvpMeshack Musundi7-Nov-12 1:44 
Hi Brant, I haven't experienced jaggies but I'll look into it.
"As beings of finite lifespan, our contributions to the sum of human knowledge is one of the greatest endeavors we can undertake and one of the defining characteristics of humanity itself"

GeneralMy vote of 5 Pin
Prabhakaran Soundarapandian26-Aug-12 21:39
memberPrabhakaran Soundarapandian26-Aug-12 21:39 
GeneralRe: My vote of 5 Pin
Meshack Musundi27-Aug-12 2:54
mvpMeshack Musundi27-Aug-12 2:54 
QuestionGreat work Pin
Kenneth Haugland8-Aug-12 12:47
memberKenneth Haugland8-Aug-12 12:47 
GeneralRe: Great work Pin
Meshack Musundi8-Aug-12 21:09
mvpMeshack Musundi8-Aug-12 21:09 
QuestionMy Vote of 5 Pin
A_K_11-Jul-12 0:59
memberA_K_11-Jul-12 0:59 
GeneralRe: My Vote of 5 Pin
Meshack Musundi11-Jul-12 3:18
mvpMeshack Musundi11-Jul-12 3:18 
QuestionNice example :-) Pin
nick_journals9-Jul-12 0:51
membernick_journals9-Jul-12 0:51 
AnswerRe: Nice example :-) Pin
Meshack Musundi9-Jul-12 1:08
mvpMeshack Musundi9-Jul-12 1:08 
QuestionERROR Pin
afroDeluXe4-Jul-12 23:19
memberafroDeluXe4-Jul-12 23:19 
AnswerRe: ERROR Pin
Meshack Musundi5-Jul-12 4:18
mvpMeshack Musundi5-Jul-12 4:18 
AnswerRe: ERROR Pin
Meshack Musundi10-Jul-12 22:23
mvpMeshack Musundi10-Jul-12 22:23 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.180111.1 | Last Updated 12 Jul 2012
Article Copyright 2012 by Meshack Musundi
Everything else Copyright © CodeProject, 1999-2018
Layout: fixed | fluid