Exercise 2: Creating the Puzzle Board in Visual Studio

In this task, you will create a page to display the puzzle board and play the game. The page displays an image that when clicked, is broken down into pieces and then rearranged on the board in random fashion.

The logic of the game is contained in the PuzzleGame class, which you add to the project from the assets provided with this lab. During the exercise, you create the layout of the controls on the page and then add the application logic necessary to initialize the board and respond to user interface events. Next, you add multi-touch support to allow users to use their fingers to drag the pieces of the puzzle on the board and rearrange them. As a last step, you create an animation storyboard that creates an attractive visual effect when you succeed in solving the puzzle.

Task 1 – Creating the User Interface

In this task, you add a new page to the application to display a puzzle board and add the user interface elements necessary to display the puzzle image as well as a status panel with the total number of moves performed during a game. In the page, you also include a button that, when pressed, will solve the puzzle for you.

  1. If you completed the steps in the previous exercise, you may continue with the solution that you created for that exercise; otherwise, open Begin.sln from Ex2-CreatingThePuzzleView\Begin in the Source folder of the lab.
  2. Add a new page to the project. In Solution Explorer, right-click the WindowsPhonePuzzle project node, point to Add and select New Item. In the Add New Item dialog, select Windows Phone Portrait page from the list of templates, set the name to PuzzlePage.xaml and then click Add.
  3. In the XAML view of the new page, locate the RowDefinitions section of the LayoutRoot element and set the value of the Height property for the first row to 0.2*.

    Figure 13

    Configuring the layout of the container grid

    Note:
    Specifying the height as star (*) ensures that the row stretches to fill the available space in the layout grid after all other rows are allocated. When the star-sized height also includes a multiplier, as is the case for the first row in the definition above, the unused space is proportionally allocated among all “star-sized” rows based on the value of the multiplier—a multiplier of 1 is assumed when left unspecified. Thus, in the definition above, the first row occupies 1/6th of the available space.

  4. Inside the LayoutRoot grid, delete the existing TitlePanel and ContentPanel elements that are included as part of the default page template—this includes their children.
  5. Immediately below the RowDefinitions section, insert two StackPanel (and its relevant children) container elements as shown (highlighted) below, one for each row in the layout.

    XAML

    <Grid x:Name="LayoutRoot" Background="Transparent"> <Grid.RowDefinitions> <RowDefinition Height="0.2*"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <StackPanel Orientation="Vertical" VerticalAlignment="Stretch"> <Button x:Name="SolveButton" Content="SOLVE" Margin="10" HorizontalAlignment="Center" Click="SolveButton_Click" /> <StackPanel x:Name="StatusPanel" Orientation="Horizontal" HorizontalAlignment="Center" Visibility="Collapsed"> <TextBlock HorizontalAlignment="Center" Text="Your Moves: " TextWrapping="Wrap" Foreground="#FFD0D0D0" FontSize="17.333"/> <TextBlock x:Name="TotalMovesTextBlock" HorizontalAlignment="Center" Text="N" TextWrapping="Wrap" Foreground="#FFFFB000" FontSize="17.333"/> </StackPanel> </StackPanel> <StackPanel Orientation="Vertical" VerticalAlignment="Top" Grid.Row="1"> <Border x:Name="CongratsBorder" Height="30" Background="#FFF10DA2" HorizontalAlignment="Center" Width="443" RenderTransformOrigin="0.5,0.5" UseLayoutRounding="False" Opacity="0"> <Border.RenderTransform> <TransformGroup> <ScaleTransform/> <SkewTransform/> <RotateTransform/> <TranslateTransform/> </TransformGroup> </Border.RenderTransform> <TextBlock HorizontalAlignment="Center" Text="CONGRATULATIONS!" TextWrapping="Wrap" Foreground="White" FontSize="17.333" VerticalAlignment="Center" FontWeight="Bold"/> </Border> </StackPanel> </Grid>

    Note:
    This Border element (inside the second StackPanel) contains a message that is displayed when the puzzle is solved. Note that the border includes a RenderTransform. This transformation is required by an animation storyboard that you will create later in the lab to provide a visual effect. The storyboard cannot animate the element unless the transformation is present.

  6. To complete the contents of the lower StackPanel, insert the following (highlighted) XAML immediately below the markup inserted in the previous step.

    XAML

    ... <Grid x:Name="LayoutRoot" Background="Transparent"> ... <StackPanel Orientation="Vertical" VerticalAlignment="Top" Grid.Row="1"> <Border x:Name="CongratsBorder"...> </Border> <Border x:Name="border" BorderThickness="3" Background="#FF262626" HorizontalAlignment="Center" VerticalAlignment="Center" Padding="1" RenderTransformOrigin="0.5,0.5"> <Border.RenderTransform> <TransformGroup> <ScaleTransform/> <SkewTransform/> <RotateTransform/> <TranslateTransform/> </TransformGroup> </Border.RenderTransform> <Border.BorderBrush> <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0"> <GradientStop Color="#FFF10DA2" Offset="0"/> <GradientStop Color="#FFEE7923" Offset="1"/> </LinearGradientBrush> </Border.BorderBrush> <Canvas Height="435" Width="435"> <Image x:Name="PreviewImage" Height="435" Width="435" Opacity="0.2" /> <Canvas x:Name="GameContainer" Width="435" Height="435" /> </Canvas> </Border> <TextBlock x:Name="TapToContinueTextBlock" HorizontalAlignment="Center" Text="Tap the picture to start the puzzle" TextWrapping="Wrap" Foreground="#FFD0D0D0" FontSize="17.333"/> </StackPanel> </Grid>

    Note:
    The XAML markup above defines a Border element drawn with a linear gradient brush that uses multiple colors that blend into each other. This element also specifies a RenderTransform that will be necessary when you create an animation storyboard to provide a visual effect.

    Inside the Border element is a single Canvas element. This container element is used to explicitly position other child elements using coordinates relative to its area. Among its children is an Image element that shows a watermark with a preview of the puzzle’s solution (PreviewImage) and an inner Canvas that holds the pieces of the puzzle (GameContainer).

    The markup also includes a TextBlock element (TapToContinueTextBlock) that displays instructions on how to start the game. This element is hidden while a game is in progress.

  7. Switch to Design view to examine the layout of the page. To select the view and maximize the viewing area, double-click the Design tab () on the right edge of the designer window. If you have trouble identifying the correct tab, position the mouse cursor over each tab to display a tooltip that identifies it. The page should appear as shown in the figure below.

    Figure 14

    Windows Phone Puzzle application user interface

Task 2 – Programming the Application Logic

During this task, you program the application logic. This includes initializing the puzzle board, reading the image from the application’s resources, and creating handlers for events from the user interface.

  1. Add the class that contains the game’s logic to the project. In Solution Explorer, right-click the WindowsPhonePuzzle project node, point to Add and select Existing Item. In the Add Existing Item dialog, browse to Assets in the Source folder of the lab, select PuzzleGame.cs and click Add.

    Note:
    The logic of the game is contained in the PuzzleGame class, which you add to the project from the assets included with this lab. This class provides methods that include starting a new game, moving the puzzle pieces, and saving and restoring the game’s state. You are free to examine the source code for this class but it is beyond the scope of the lab to analyze the code in detail.

  2. Open the code-behind file for the puzzle page that you created in the previous task. To do this, right-click the PuzzlePage.xaml file in Solution Explorer and select View Code.
  3. Insert the following namespace declarations into the PuzzlePage.xaml.cs code-behind file.

    C#

    using System.IO; using System.Windows.Media.Imaging; using System.Windows.Resources;

  4. In the PuzzlePage class, insert the following member variable declarations as shown (highlighted) below.

    C#

    public partial class PuzzlePage : PhoneApplicationPage { private const double DoubleTapSpeed = 500; private const int ImageSize = 435; private PuzzleGame game; private Canvas[] puzzlePieces; private Stream imageStream; public PuzzlePage() { InitializeComponent(); } }

  5. Now, add an ImageStream property, as shown (highlighted) in the following code snippet.

    C#

    public partial class PuzzlePage : PhoneApplicationPage { ... public Stream ImageStream { get { return this.imageStream; } set { this.imageStream = value; BitmapImage bitmap = new BitmapImage(); bitmap.SetSource(value); this.PreviewImage.Source = bitmap; int i = 0; int pieceSize = ImageSize / this.game.ColsAndRows; for (int ix = 0; ix < this.game.ColsAndRows; ix++) { for (int iy = 0; iy < this.game.ColsAndRows; iy++) { Image pieceImage = this.puzzlePieces[i].Children[0] as Image; pieceImage.Source = bitmap; i++; } } } } public PuzzlePage() { InitializeComponent(); } }

    Note:
    The ImageStream property returns the stream to use for the puzzle image. It automatically refreshes the background image and each of the puzzle pieces. You can replace the puzzle image by setting the property to a new Stream that contains any valid bitmap, for example, an image originating from the phone’s camera. For this hands-on lab, you use an image embedded as a resource in the application package.

  6. Update the constructor for the PuzzlePage class as shown (highlighted) in the following code snippet.

    C#

    public partial class PuzzlePage : PhoneApplicationPage { ... public PuzzlePage() { InitializeComponent(); SupportedOrientations = SupportedPageOrientation.Portrait | SupportedPageOrientation.Landscape; // Puzzle Game this.game = new PuzzleGame(3); this.game.GameStarted += delegate { this.StatusPanel.Visibility = Visibility.Visible; this.TapToContinueTextBlock.Opacity = 0; this.TotalMovesTextBlock.Text = this.game.TotalMoves.ToString(); }; this.game.GameOver += delegate { this.TapToContinueTextBlock.Opacity = 1; this.StatusPanel.Visibility = Visibility.Visible; this.TotalMovesTextBlock.Text = this.game.TotalMoves.ToString(); }; this.game.PieceUpdated += delegate(object sender, PieceUpdatedEventArgs args) { int pieceSize = ImageSize / this.game.ColsAndRows; this.AnimatePiece(this.puzzlePieces[args.PieceId], Canvas.LeftProperty, (int)args.NewPosition.X * pieceSize); this.AnimatePiece(this.puzzlePieces[args.PieceId], Canvas.TopProperty, (int)args.NewPosition.Y * pieceSize); this.TotalMovesTextBlock.Text = this.game.TotalMoves.ToString(); }; this.InitBoard(); } }

    Note:
    The constructor instantiates the game logic—encapsulated by the PuzzleGame class—and then binds its events. The PuzzleGame class defines the following events:

    GameStarted: This event is raised when a new game begins. The handler for this event shows the panel with the number of pieces moved, hides the legend with instructions on how to start the game and then resets the number of moved pieces.

    GameOver: This event occurs when the puzzle is solved. The handler for the event shows the legend with instructions on how to start the game and then updates the move count.

    PieceUpdated: This event is triggered whenever a piece is moved. The handler for the event animates the moved piece and then updates the move count.

    Finally, after subscribing the event handlers, the constructor calls the InitBoard method to initialize the game board.

  7. Next, in the PuzzlePage class, define the InitBoard method to initialize the game board.

    C#

    private void InitBoard() { int totalPieces = this.game.ColsAndRows * this.game.ColsAndRows; int pieceSize = ImageSize / this.game.ColsAndRows; this.puzzlePieces = new Canvas[totalPieces]; int nx = 0; for (int ix = 0; ix < this.game.ColsAndRows; ix++) { for (int iy = 0; iy < this.game.ColsAndRows; iy++) { nx = (ix * this.game.ColsAndRows) + iy; Image image = new Image(); image.SetValue(FrameworkElement.NameProperty, "PuzzleImage_" + nx); image.Height = ImageSize; image.Width = ImageSize; image.Stretch = Stretch.UniformToFill; RectangleGeometry r = new RectangleGeometry(); r.Rect = new Rect((ix * pieceSize), (iy * pieceSize), pieceSize, pieceSize); image.Clip = r; image.SetValue(Canvas.TopProperty, Convert.ToDouble(iy * pieceSize * -1)); image.SetValue(Canvas.LeftProperty, Convert.ToDouble(ix * pieceSize * -1)); this.puzzlePieces[nx] = new Canvas(); this.puzzlePieces[nx].SetValue(FrameworkElement.NameProperty, "PuzzlePiece_" + nx); this.puzzlePieces[nx].Width = pieceSize; this.puzzlePieces[nx].Height = pieceSize; this.puzzlePieces[nx].Children.Add(image); this.puzzlePieces[nx].MouseLeftButtonDown += this.PuzzlePiece_MouseLeftButtonDown; if (nx < totalPieces - 1) { this.GameContainer.Children.Add(this.puzzlePieces[nx]); } } } // Retrieve image StreamResourceInfo imageResource = Application.GetResourceStream(new Uri("WindowsPhonePuzzle;component/Assets/Puzzle.jpg", UriKind.Relative)); this.ImageStream = imageResource.Stream; this.game.Reset(); }

    Note:
    The InitBoard method creates the Canvas controls that contain the pieces of the puzzle, one for every piece. Each piece is a clipped portion of the whole image used in the puzzle.

    The method also retrieves the puzzle image from the application resources and initializes the ImageStream property with the resulting stream.

  8. Insert the AnimatePiece into the PuzzlePage class.

    C#

    private void AnimatePiece(DependencyObject piece, DependencyProperty dp, double newValue) { Storyboard storyBoard = new Storyboard(); Storyboard.SetTarget(storyBoard, piece); Storyboard.SetTargetProperty(storyBoard, new PropertyPath(dp)); storyBoard.Children.Add(new DoubleAnimation { Duration = new Duration(TimeSpan.FromMilliseconds(200)), From = Convert.ToInt32(piece.GetValue(dp)), To = Convert.ToDouble(newValue), EasingFunction = new SineEase() }); storyBoard.Begin(); }

    Note:
    AnimatePiece is a helper method for animating puzzle pieces in the UI. It creates a storyboard using code and can be used to update any DependencyProperty in a control. For this application, the method is only used to update the Top and Left properties of a puzzle piece to control its position.

  9. Add a handler for the MouseLeftButtonDown event. To do this, insert the following (highlighted) code into the PuzzlePage class.

    C#

    private void PuzzlePiece_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { if (!this.game.IsPlaying) { this.game.NewGame(); } }

    Note:
    This event handler is triggered by clicking the board surface and causes the start of a new game, unless one is already in progress.

  10. Now, add the handler for the Click event of the “Solve” button.

    C#

    private void SolveButton_Click(object sender, RoutedEventArgs e) { this.game.Reset(); this.game.CheckWinner(); }

    Note:
    The event handler for the Solve button forces the game to reset—to display the original image and solve the puzzle—and then calls PuzzleGame.CheckWinner in the PuzzleGame class. This method validates that every piece is in the correct place and then raises the GameOver event, which causes the UI to display a congratulatory message.

  11. Press F5 to build and deploy the application to the Windows Phone Emulator. Wait for the splash screen to appear and then click START.
  12. In the emulator window, tap the image to start the puzzle. Notice that the image is broken down into pieces and then rearranged on the board in random fashion. Moreover, observe that the pieces do not instantly snap into place but instead smoothly slide into position as the result of the animation storyboard applied by the AnimatePiece method. This method animates the left and top coordinates of each puzzle piece to produce the observed transition.

    Figure 15

    Puzzle pieces randomly rearranged on the board after the game starts

  13. Click a piece to select it and then attempt to drag it. Notice that currently this has no effect. In the next task, you will add support for multi-touch, which will allow you to use your fingers to drag the pieces on the board to rearrange them.
  14. Now, click the SOLVE button and watch as the pieces on the board reassemble to display the original image.
  15. In Visual Studio, press SHIFT + F5 to stop the debugging session. Do not close the emulator window.

Task 3 – Adding Support for Multi-Touch

Multi-touch input allows users to apply multiple finger gestures simultaneously and have them interpreted as a unit to provide complex commands to an application and simulate directly manipulating an element on the page, for example, to pan and zoom at the same time. In this task, you update the Windows Phone Puzzle game to receive multi-touch input and enable users to move the pieces of the puzzle on the board by tapping and then dragging them to an empty slot.

  1. In Solution Explorer, right-click PuzzlePage.xaml and select View Code to open the code-behind file for this page in the designer.
  2. In the PuzzlePage class, insert the following declarations below the existing member variables, as shown (highlighted) below.

    C#

    public partial class PuzzlePage : PhoneApplicationPage { private const double DoubleTapSpeed = 500; private const int ImageSize = 435; private PuzzleGame game; private Canvas[] puzzlePieces; private Stream imageStream; private long lastTapTicks; private int movingPieceId = -1; private int movingPieceDirection; private double movingPieceStartingPosition; public PuzzlePage() { InitializeComponent(); ... }

  3. Now, right-click the editor window and select View Designer () to switch to Design view.
  4. On the designer surface, click the blank area that surrounds the emulator image to select the PhoneApplicationPage element and press F4 to open its Properties window. In the Properties window, click the Events tab to display a list of available events.

    Note:
    Be careful not to select another UI element on the designer surface, otherwise the properties window displays events for that element instead.

    Figure 16

    Creating event handlers in Microsoft Visual Studio 2010 Express for Windows Phone

  5. Define an event handler for the ManipulationStarted event. To do this, double-click the corresponding item in the events list to subscribe to this event. When you do this, Visual Studio creates an event handler and opens the code-behind file to display the method stub that it generated. Insert the following (highlighted) code inside the body of the PhoneApplicationPage_ManipulationStarted method.

    C#

    private void PhoneApplicationPage_ManipulationStarted(object sender, ManipulationStartedEventArgs e) { if (this.game.IsPlaying && e.ManipulationContainer is Image && e.ManipulationContainer.GetValue(FrameworkElement.NameProperty).ToString().StartsWith("PuzzleImage_")) { int pieceIx = Convert.ToInt32(e.ManipulationContainer.GetValue(FrameworkElement.NameProperty).ToString().Substring(12)); Canvas piece = this.FindName("PuzzlePiece_" + pieceIx) as Canvas; if (piece != null) { int totalPieces = this.game.ColsAndRows * this.game.ColsAndRows; for (int i = 0; i < totalPieces; i++) { if (piece == this.puzzlePieces[i] && this.game.CanMovePiece(i) > 0) { int direction = this.game.CanMovePiece(i); DependencyProperty axisProperty = (direction % 2 == 0) ? Canvas.LeftProperty : Canvas.TopProperty; this.movingPieceDirection = direction; this.movingPieceStartingPosition = Convert.ToDouble(piece.GetValue(axisProperty)); this.movingPieceId = i; break; } } } } }

    Note:
    The ManipulationStarted event is raised when the user touches (or clicks with the mouse) any UIElement to manipulate it and is similar to a MouseDown event. The handler for the event verifies if a game is currently in progress and that one of the image elements that represents the puzzle pieces raised the event, otherwise it ignores the event. It locates the corresponding Canvas element for this piece and then calls the game’s logic to determine whether it can be moved—for this to happen, there must be an empty slot adjacent to the piece. It then stores the selected piece and then records the axis / direction where it can move.

  6. Next, create a handler for the ManipulationDelta event. Once again, right-click the code editor window and select View Designer to switch to Design view. Then, select the PhoneApplicationPage element on the designer surface, press F4 to open its Properties window and then select the Events tab. Next, locate the ManipulationDelta event in the list of events and double-click the corresponding item to subscribe to this event and generate a method stub. In the code-behind file, insert the following (highlighted) code inside the body of the PhoneApplicationPage_ManipulationDelta method.

    C#

    private void PhoneApplicationPage_ManipulationDelta(object sender, ManipulationDeltaEventArgs e) { if (this.movingPieceId > -1) { int pieceSize = ImageSize / this.game.ColsAndRows; Canvas movingPiece = this.puzzlePieces[this.movingPieceId]; // validate direction DependencyProperty axisProperty; double normalizedValue; if (this.movingPieceDirection % 2 == 0) { axisProperty = Canvas.LeftProperty; normalizedValue = e.CumulativeManipulation.Translation.X; } else { axisProperty = Canvas.TopProperty; normalizedValue = e.CumulativeManipulation.Translation.Y; } // enforce drag constraints // (top or left) if (this.movingPieceDirection == 1 || this.movingPieceDirection == 4) { if (normalizedValue < -pieceSize) { normalizedValue = -pieceSize; } else if (normalizedValue > 0) { normalizedValue = 0; } } // (bottom or right) else if (this.movingPieceDirection == 3 || this.movingPieceDirection == 2) { if (normalizedValue > pieceSize) { normalizedValue = pieceSize; } else if (normalizedValue < 0) { normalizedValue = 0; } } // set position movingPiece.SetValue(axisProperty, normalizedValue + this.movingPieceStartingPosition); } }

    Note:
    The ManipulationDelta event is raised when the user moves the finger (or the mouse cursor) while manipulating a UIElement. The handler for this event checks if there is a piece currently being moved. If so, it captures the delta value from the only possible axis / direction. To do so, the code needs to enforce drag constraints to validate that the movement is within the available boundaries—a piece cannot overlap other pieces. The handler then updates the position for the piece by setting the property for the appropriate axis.

  7. Finally, repeat the previous step only this time, choose the ManipulationCompleted event instead. In the code-behind file, insert the following (highlighted) code into the body of the PhoneApplicationPage_ManipulationCompleted method.

    C#

    private void PhoneApplicationPage_ManipulationCompleted(object sender, ManipulationCompletedEventArgs e) { if (this.movingPieceId > -1) { int pieceSize = ImageSize / this.game.ColsAndRows; Canvas piece = this.puzzlePieces[this.movingPieceId]; // check for double tapping if (TimeSpan.FromTicks(DateTime.Now.Ticks - this.lastTapTicks).TotalMilliseconds < DoubleTapSpeed) { // force move this.game.MovePiece(this.movingPieceId); this.lastTapTicks = int.MinValue; } else { // calculate moved distance DependencyProperty axisProperty = (this.movingPieceDirection % 2 == 0) ? Canvas.LeftProperty : Canvas.TopProperty; double minRequiredDisplacement = pieceSize / 3; double diff = Math.Abs(Convert.ToDouble(piece.GetValue(axisProperty)) - this.movingPieceStartingPosition); // did it get halfway across? if (diff > minRequiredDisplacement) { // move piece this.game.MovePiece(this.movingPieceId); } else { // restore piece this.AnimatePiece(piece, axisProperty, this.movingPieceStartingPosition); } } this.movingPieceId = -1; this.movingPieceStartingPosition = 0; this.movingPieceDirection = 0; this.lastTapTicks = DateTime.Now.Ticks; } }

    Note:
    The ManipulationCompleted event is raised when the user lifts up the finger from the screen (or releases the mouse button) after manipulating a UIElement and is similar to a MouseUp event. The handler for this event checks if there is a puzzle piece currently being moved. If so, it determines the time when the last ManipulationCompleted was fired and, provided the time interval is lower than the value specified by the DoubleTapSpeed, it interprets this event as a Double Tap event (or double-click) and moves the piece to the adjacent empty slot. Otherwise, it calculates the offset distance of the piece into its target position and, if the piece has shifted at least one third of its size into the adjacent slot, it completes the move of the piece into that slot; if not, it pulls the piece back to its original position.

  8. After completing the previous steps, open the Properties window for the page to verify that you have created the event handlers correctly. If you inspect the XAML markup for the page, you will see the corresponding attributes defined on the PhoneApplicationPage element.

    Figure 17

    Properties window showing the manipulation event handlers

    Figure 18

    XAML markup for the page showing the event subscriptions

  9. To test the added support for multi-touch, press F5 to build and deploy the application to the emulator. Wait for the splash screen to be displayed and then click START to begin a new game.
  10. Drag the pieces on the board to solve the puzzle. To move a piece, click to select it and then drag it to its destination. Alternatively, you may double-click a piece to move it to the next available slot. Notice that each move increments the counter above the puzzle board.

    Figure 19

    Solving the puzzle

  11. Continue moving pieces until you get a sense of what the experience might be like when the application is running on a real device with a touch screen. When you are ready, switch back to Visual Studio and press SHIFT + F5 to end the debugging session.

Task 4 – Creating Animation Effects

In Silverlight, a storyboard is an animation. Each storyboard defines a timeline that contains key frames. Each key frame can independently redefine controls properties such as position, size, rotation, opacity and even foreground or background color. Using this approach, users are not required to create animations by defining every single frame and instead, only need to provide selected points on the timeline to mark significant property changes. Silverlight interpolates the value of the animated properties between two consecutive key frames to generate intervening frames and provide a smooth transition.

Each storyboard is an object with methods and events that you can use from the page's code-behind. This includes methods to Begin, Stop, and Pause the animation and a single event Completed, which occurs when the animation has completed playing.

Storyboards can be written as XAML code and are easily created using Expression Blend.

In this task, you create a storyboard to play when the user successfully solves the puzzle. The animation creates a visual effect that rotates the puzzle image about its middle axis and displays a message that gradually fades into view.

  1. First, insert a new Resources section into the page as shown (highlighted) below.

    XAML

    <phone:PhoneApplicationPage x:Class="WindowsPhonePuzzle.PuzzlePage" ...> <phone:PhoneApplicationPage.Resources> </phone:PhoneApplicationPage.Resources> ... <Grid x:Name="LayoutRoot" Background="Transparent"> </Grid> </navigation:PhoneApplicationPage>

    Note:
    Resources provide a simple way to reuse commonly defined objects and values. You can create definitions for common items including control templates, styles, brushes, colors, and animation storyboards and store them in resource dictionaries. A resource dictionary is a keyed dictionary of objects that you can use both in XAML and in code. You can create resource dictionaries at different scopes in your application structure, allowing you to define resources at the page level or as application resources.

    In this task, you use the page’s resources to store the definition of the animation storyboard.

  2. Next, inside the Resources section, insert the animation storyboard for the transition that occurs when the user solves the puzzle. To do this, insert the following (highlighted) XAML markup that defines the WinTransition storyboard.

    XAML

    ... <phone:PhoneApplicationPage.Resources> <Storyboard x:Name="WinTransition"> <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="PreviewImage" Storyboard.TargetProperty="(UIElement.Opacity)"> <EasingDoubleKeyFrame KeyTime="00:00:00" Value="0.2"/> <EasingDoubleKeyFrame KeyTime="00:00:00.7000000" Value="1"/> </DoubleAnimationUsingKeyFrames> <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="border" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)"> <EasingDoubleKeyFrame KeyTime="00:00:00" Value="1"/> <EasingDoubleKeyFrame KeyTime="00:00:00.7000000" Value="-1"> <EasingDoubleKeyFrame.EasingFunction> <CubicEase EasingMode="EaseInOut"/> </EasingDoubleKeyFrame.EasingFunction> </EasingDoubleKeyFrame> <EasingDoubleKeyFrame KeyTime="00:00:01.7000000" Value="1"> <EasingDoubleKeyFrame.EasingFunction> <CubicEase EasingMode="EaseInOut"/> </EasingDoubleKeyFrame.EasingFunction> </EasingDoubleKeyFrame> </DoubleAnimationUsingKeyFrames> <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="CongratsBorder" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)"> <EasingDoubleKeyFrame KeyTime="00:00:00" Value="0"/> <EasingDoubleKeyFrame KeyTime="00:00:00.7000000" Value="-1"> <EasingDoubleKeyFrame.EasingFunction> <CubicEase EasingMode="EaseInOut"/> </EasingDoubleKeyFrame.EasingFunction> </EasingDoubleKeyFrame> <EasingDoubleKeyFrame KeyTime="00:00:01.7000000" Value="1"> <EasingDoubleKeyFrame.EasingFunction> <CubicEase EasingMode="EaseInOut"/> </EasingDoubleKeyFrame.EasingFunction> </EasingDoubleKeyFrame> </DoubleAnimationUsingKeyFrames> <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="CongratsBorder" Storyboard.TargetProperty="(UIElement.Opacity)"> <EasingDoubleKeyFrame KeyTime="00:00:01.2000000" Value="0"/> <EasingDoubleKeyFrame KeyTime="00:00:01.3000000" Value="0"/> <EasingDoubleKeyFrame KeyTime="00:00:01.4000000" Value="1"/> </DoubleAnimationUsingKeyFrames> </Storyboard> </phone:PhoneApplicationPage.Resources>

    Note:
    The WinTransition storyboard plays when the user wins the game. The animation rotates the image that contains the solution for the puzzle about its middle axis and shows a frame around the image with a congratulatory message that gradually fades into view.

    You can use tools such as Expression Blend to visually animate properties in the designer and generate the XAML markup required to define a storyboard. For an introduction on how to create animations using Expression Blend, see the Hello Phone hands-on lab.

  3. Finally, insert the storyboard for the transition that occurs whenever the user restarts the game.

    XAML

    ... <phone:PhoneApplicationPage.Resources> <Storyboard x:Name="WinTransition"> ... </Storyboard> <Storyboard x:Name="ResetWinTransition"> <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="CongratsBorder" Storyboard.TargetProperty="(UIElement.Opacity)" Duration="00:00:00.0010000"> <SplineDoubleKeyFrame KeyTime="00:00:00" Value="0"/> </DoubleAnimationUsingKeyFrames> <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="PreviewImage" Storyboard.TargetProperty="(UIElement.Opacity)" Duration="00:00:00.0010000"> <SplineDoubleKeyFrame KeyTime="00:00:00" Value="0.20000000298023224"/> </DoubleAnimationUsingKeyFrames> <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000" Storyboard.TargetName="GameContainer" Storyboard.TargetProperty="(UIElement.Opacity)"> <EasingDoubleKeyFrame KeyTime="00:00:00" Value="1"/> </DoubleAnimationUsingKeyFrames> </Storyboard> </phone:PhoneApplicationPage.Resources> ...

    Note:
    The ResetWinTransition storyboard hides the congratulatory message and restores the puzzle image opacity to its original value.

  4. In Solution Explorer, right-click PuzzlePage.xaml and select View Code () to open the code-behind file for this page.
  5. In the constructor for the PuzzlePage class, locate the anonymous method that handles the GameOver event and insert the following (highlighted) line of code at the start of the method.

    C#

    public PuzzlePage() { ... this.game.GameOver += delegate { this.WinTransition.Begin(); this.TapToContinueTextBlock.Opacity = 1; this.StatusPanel.Visibility = Visibility.Visible; this.TotalMovesTextBlock.Text = this.game.TotalMoves.ToString(); }; ... }

    Note:
    When the game is over, the inserted code plays a storyboard to provide a visual effect for the completed puzzle image and the congratulatory message.

  6. While still in the PuzzlePage class constructor, locate the handler for the GameStarted event and add the following (highlighted) line of code.

    C#

    public PuzzlePage() { ... this.game.GameStarted += delegate { this.ResetWinTransition.Begin(); this.StatusPanel.Visibility = Visibility.Visible; this.TapToContinueTextBlock.Opacity = 0; this.TotalMovesTextBlock.Text = this.game.TotalMoves.ToString(); }; ... }

    Note:
    The inserted code starts the ResetWinTransition animation storyboard when the game starts. This storyboard resets the state of the background image and hides the congratulatory message.

Task 5 – Verifying

  1. To test the latest changes, press F5 to build and deploy the application to the emulator once again.
  2. Once the splash screen is shown, click START and then tap the screen to begin a new game.
  3. At this point, if you wish, you may attempt to solve the puzzle by dragging the pieces of the puzzle on the board; otherwise, press the SOLVE button above the game board to solve the puzzle and end the game. Notice that when the game is over, it triggers the animation and the board rotates about its middle axis while a legend with the text “CONGRATULATIONS” located above the board gradually fades into view.

    Figure 20

    Solved puzzle showing the congratulatory message

  4. When you complete your verification, switch back to Visual Studio press SHIFT + F5 to end the debugging session.