Use Windows Store app features in your REVERSI game

6 out of 7 rated this helpful - Rate this topic

The Reversi sample uses several common features of Windows Store apps using XAML and C#. This topic describes how the sample uses some of these features, and provides links to key feature topics.

This topic does not require you to understand the whole sample, but it does assume that you already know XAML and C#, and already understand the basics of each feature, or are willing to learn by reading the linked topics. For info on app development fundamentals, see Create your first Windows Store app using C# or Visual Basic.

For a general introduction to the sample, see Developing Reversi. To understand how the various features work together as a whole, see Reversi app structure.

Download the Reversi sample app or browse the source code.

Tile and splash screen

The app tile and splash screen are the first things that a user of your app sees. You can use them to provide a compelling entry point and to display your branding. The basics are trivial, but you can also do some more complex things, as described in the documentation.

Key resources:

Reversi provides only basic tile and splash-screen support. This includes two tile sizes and a splash screen, shown here at reduced size.

Reversi tiles and splash screen

The image file names are set in the Package.appxmanifest file. No other implementation is required for this simple usage.

App bar

App bars provide a standard place to put app commands. By default, users can show or hide an app bar as needed, making it a good place for commands used less frequently. This helps keep your main UI focused on direct interactions with your content.

Key resources:

Reversi includes a few secondary commands that are well suited to the app bar: the ability to pause the clock and to undo or redo moves. During normal game play the app bar is hidden, but the user can swipe from the top or bottom of the screen to display or hide it.

Reversi app bar

This code, from GamePage.xaml, shows the app bar definition. Although the background and border are transparent, the Background property is set to {x:Null} to prevent the invisible app bar from blocking taps and clicks. This setting is necessary because the app bar extends across the whole screen and overlaps the bottom row of the game board.


  <Page.BottomAppBar>
    <AppBar x:Name="GamePageAppBar" IsSticky="True"
      Background="{x:Null}" BorderBrush="Transparent">
      <StackPanel Orientation="Horizontal">
        <Button Style="{StaticResource PauseAppBarButtonStyle}"
          Command="{Binding Clock.PauseCommand}"
          Visibility="{Binding Clock.IsPauseButtonVisible, 
            Converter={StaticResource BooleanToVisibilityConverter}}"/>
        <Button Style="{StaticResource PlayAppBarButtonStyle}"
          Command="{Binding Clock.PlayCommand}"          
          Visibility="{Binding Clock.IsPauseButtonVisible,
            Converter={StaticResource BooleanToVisibilityConverter}, 
            ConverterParameter=Reverse}"/>
        <Button Style="{StaticResource UndoAppBarButtonStyle}"
          Command="{Binding UndoCommand}"/>
        <Button Style="{StaticResource RedoAppBarButtonStyle}"
          Command="{Binding RedoCommand}"/>
      </StackPanel>
    </AppBar>
  </Page.BottomAppBar>


Reversi uses app bar button styles defined in the StandardStyles.xaml file, which is generated by the Microsoft Visual Studio 2012 app templates. These standard styles provide the circle, icon, caption, and disabled view for each button. The button behavior and its enabled state are provided by view-model commands bound to the button Command properties, as described in the Commands section.

The Play and Pause buttons work like a single toggle button, but instead of using an actual ToggleButton control, Reversi uses standard buttons with Visibility properties bound to the same view-model property. These bindings use a BooleanToVisibilityConverter, but one of them also has a ConverterParameter property setting that reverses the effect of the binding. That way, each button is visible only when the other one isn't. For more info, see the Data binding section.

Toast notifications

Toast notifications alert your users when an important event occurs in your app, even if another app is currently active.

Key resources:

In Reversi, the computer may take a while to make its move. If you switch to another app while you wait, a toast notification will alert you when it's your turn.

Reversi toast notification

Reversi uses the minimum code required for toast notifications, and sets the Toast capable field to Yes in the Package.appxmanifest designer. The toast code is easily reusable, so it's in a helper class in the Common folder.

In GameViewModel.cs:


if (!Windows.UI.Xaml.Window.Current.Visible && !IsCurrentPlayerAi)
{
    Toast.Show("It's your turn!");
}


In Toast.cs:


public static void Show(string text)
{
    const string template = "<toast duration='short'><visual>" +
        "<binding template='ToastText01'><text id='1'>{0}</text>" +
        "</binding></visual></toast>";
    var toastXml = new XmlDocument();
    toastXml.LoadXml(String.Format(template, text));
    var toast = new ToastNotification(toastXml);
    ToastNotificationManager.CreateToastNotifier().Show(toast);
}


Settings flyouts

The Settings charm provides standardized access to app settings.

Key resources:

Reversi has two settings flyouts, one for display options, and one for new-game options.

Reversi settings flyouts

This code from App.xaml.cs shows how Reversi handles the SettingsPane.CommandsRequested event to create SettingsCommand objects. These commands reference methods that create and open the flyouts.


SettingsPane.GetForCurrentView().CommandsRequested += OnCommandsRequested;



private void OnCommandsRequested(SettingsPane sender,
    SettingsPaneCommandsRequestedEventArgs args)
{
    args.Request.ApplicationCommands.Add(new SettingsCommand("Display", 
        "Display", _ => ShowDisplaySettingsFlyout()));
    args.Request.ApplicationCommands.Add(new SettingsCommand("NewGame", 
        "New game options", _ => ShowNewGameSettingsFlyout()));
}


Sharing content

The share contract lets your app share data with another app.

Key resources:

Reversi shares a description of the current game state and an image of the game board. There is currently no easy way to render XAML as a bitmap, so Reversi generates the board image by assembling board-space images from a complete set stored in ShareImages.png. In order to support more share targets, the data is shared using the HTML, file, and bitmap data formats.

Reversi sharing data with the Mail app

This code from App.xaml.cs shows how Reversi handles the DataTransferManager.DataRequested event:


DataTransferManager.GetForCurrentView().DataRequested += OnDataRequested;



private void OnDataRequested(DataTransferManager sender, 
    DataRequestedEventArgs e)
{
    ShareHelper.Share(e.Request, SettingsViewModel.GameViewModel);
}


This code from ShareHelper.cs shows how Reversi fulfills the share request by calling the SetDataProvider method for each supported format. The OnHtmlRequested method shows one of the data providers. See the ShareHelper.cs file for the other providers and the image generation code.


public static void Share(DataRequest request, 
    GameViewModel gameViewModel)
{
    if (gameViewModel == null) return;

    var dataRequestDeferral = request.GetDeferral();
    try
    {
        request.Data.Properties.Title = 
            "Reversi game " + DateTime.Now.ToString("g");
        request.Data.Properties.Description = 
            GetDescription(gameViewModel);

        // Generating the image is slightly time intensive, 
        // so it requires deferred loading.
        request.Data.SetDataProvider(StandardDataFormats.Html, 
            providerRequest => OnHtmlRequested(
                providerRequest, request.Data, gameViewModel));
        request.Data.SetDataProvider(StandardDataFormats.Bitmap,
            providerRequest => OnBitmapRequested(
                providerRequest, gameViewModel));

        // To use the StorageItems format, it must 
        // be added to the FileTypes collection.
        request.Data.Properties.FileTypes.Add(
            StandardDataFormats.StorageItems);
        request.Data.SetDataProvider(StandardDataFormats.StorageItems,
            providerRequest => OnFileRequested(
                providerRequest, gameViewModel));
    }
    finally
    {
        dataRequestDeferral.Complete();
    }
}



private async static void OnHtmlRequested(DataProviderRequest request, 
    DataPackage requestData, GameViewModel gameViewModel)
{
    var dataProviderDeferral = request.GetDeferral();
    try
    {
        string html = String.Format(
            "<p>{0}</p><p><img src='ReversiGameComponent.png'/></p>", 
            GetDescription(gameViewModel));
        request.SetData(HtmlFormatHelper.CreateHtmlFormat(html));
        requestData.ResourceMap["ReversiGameComponent.png"] = 
            await GetImageStreamRef(gameViewModel, DefaultImageSize);
    }
    finally
    {
        dataProviderDeferral.Complete();
    }
}


Data binding

Data binding lets you connect UI controls to the data they display so that changes in one will update the other. Data binding is common for data-entry forms, but you can also use it to drive your entire UI and to keep your UI separate from your app logic.

Key resources:

Reversi uses data bindings to connect its UI (or "view" layer) to its app logic (or "view model" layer). This layering helps separate the UI from other code, and is known as the Model-View-ViewModel (MVVM) pattern. For info about how Reversi uses this pattern, see Reversi app structure. For a short intro to MVVM, see Using the Model-View-ViewModel pattern.

Most of the bindings in Reversi are defined in XAML by means of the Binding markup extension, although code-behind is used in a few cases (for example, in the Board.xaml.cs file). Each page sets its DataContext property, which all the elements on the page use as the data source for their bindings.

UI updates

The data bindings drive the UI in Reversi. UI interactions cause changes to the data source properties, and the data bindings respond to these changes by updating the UI.

These updates work because the Reversi view-model classes inherit the BindableBase class generated by the Visual Studio 2012 app and item templates (not including the blank templates). BindableBase provides a standard INotifyPropertyChanged implementation and a few support methods. The SetProperty method updates a property's backing value and also any bound UI by using a single method call. The OnPropertyChanged method updates the UI that is bound to specified properties. This is useful to control the timing of the updates, and for properties that get their values from other properties.

This code from GameViewModel.cs shows the basic usage of both SetProperty and OnPropertyChanged.


public State CurrentPlayer
{
    get { return _currentPlayer; }
    set
    {
        SetProperty(ref _currentPlayer, value);
        OnPropertyChanged("IsCurrentPlayerAi");
        OnPropertyChanged("IsPlayerOneAi");
        OnPropertyChanged("IsPlayerTwoAi");
        OnPropertyChanged("CurrentPlayerAiSearchDepth");
    }
}


Value conversion

You can convert any property values to a form more suitable for binding by creating calculated properties, which are properties that get their values from other properties.

This code from GameViewModel.cs shows a simple calculated property. UI that is bound to this property is updated by the matching OnPropertyChanged call from the previous example.


public bool IsPlayerOneAi { get { return (int)PlayerOne > 0; } }


Calculated properties are easy to create for any kind of conversion you might need, but they tend to clutter your code. For common conversions, it is better to put the conversion code into a reusable IValueConverter implementation. Reversi uses the NullStateToVisibilityConverter and BooleanToVisibilityConverter classes in the Common/Converters folder for bindings that show and hide various UI elements.

This binding from StartPage.xaml shows or hides a panel depending on whether a property has a value.


<StackPanel Visibility="{Binding Path=GameViewModel, 
  Converter={StaticResource NullStateToVisibilityConverter}}">


This binding, from NewGameSettings.xaml, shows or hides a panel depending on the state of a ToggleSwitch control.


<StackPanel Orientation="Horizontal" 
  Visibility="{Binding IsOn, ElementName=PlayerOneSwitch, 
    Converter={StaticResource BooleanToVisibilityConverter}}">


For more examples, see App bar.

Commands

Button behaviors are often implemented with Click event handlers in code-behind files. Reversi does this for navigation buttons, but for other buttons, it separates the button UI from the non-UI code that the button invokes. To do this, the Button.Command properties are bound to view-model properties that return ICommand implementations.

Reversi command properties are of type DelegateCommand or DelegateCommand<T>. These classes are in the Common/DelegateCommand.cs file, and they provide standard, reusable ICommand implementations. You can use these classes to simplify the creation of single-use commands and to keep the necessary code confined to single property implementations.

This code from GameViewModel.cs shows the move command used by the board spaces, which are custom buttons. The ?? or "null-coalescing" operator means that the field value is returned only if it isn't null; otherwise, the field is set and the new value is returned. This means that a single command object is created the first time a property is accessed, and the same object is reused for all future accesses. The command object is initialized by setting its CanExecute and Execute properties to lambda expressions that mostly call other methods.


public DelegateCommand<Space> MoveCommand
{
    get
    {
        return _moveCommand ?? (_moveCommand = new DelegateCommand<Space>
        {
            CanExecute = space => IsValidMove(space) && !IsCurrentPlayerAi,
            Execute = async space => await MoveAsync(space)
        });
    }
}


The CanExecute method is called by the data binding to update the enabled state of the button. However, command bindings rely on change notification similar to that of other bindings (discussed in UI updates). This code from GameViewModel.cs shows how the NextMove method synchronizes the view-model state with the model state and then calls OnCanExecuteChanged for each command before continuing with the next move.


private async Task NextMoveAsync()
{
    IsGameOver = Game.IsGameOver();
    CurrentPlayer = IsGameOver ? Winner : Game.CurrentPlayer;
    Score = Game.GetScore();
    UpdateBoard();
    UndoCommand.OnCanExecuteChanged();
    RedoCommand.OnCanExecuteChanged();
    MoveCommand.OnCanExecuteChanged();
    if ((IsGameAiVersusAi && Clock.IsPaused) || IsGameOver) return;
    if (IsValidMove(null)) await PassAsync();
    else if (IsCurrentPlayerAi) await AiMoveAsync();
}


Custom dependency properties

Reversi uses custom dependency properties in its custom controls so that it can use data binding updates to drive visual state changes. Visual states and animated transitions are defined in XAML by using the VisualStateManager class. However, there is no way to bind a visual state directly to a view-model property. Custom dependency properties provide targets for binding to view-model properties. The dependency properties include property-changed callbacks that make the necessary VisualStateManager.GoToState method calls.

This code shows how the PlayerStatus control uses code-behind to bind its custom dependency properties to view-model properties. Only one of the dependency properties is shown here, including its property-changed callback method. The callback and the OnApplyTemplate method override both call the update method. However, the OnApplyTemplate call initializes the control for its first appearance on screen, so it does not use animated transitions.


public PlayerStatus()
{
    DefaultStyleKey = typeof(PlayerStatus);
    SetBinding(CurrentPlayerProperty,
        new Binding { Path = new PropertyPath("CurrentPlayer") });
    SetBinding(IsClockShowingProperty,
        new Binding { Path = new PropertyPath("Settings.IsClockShowing") });
    SetBinding(IsGameOverProperty,
        new Binding { Path = new PropertyPath("IsGameOver") });
    SetBinding(WinnerProperty,
        new Binding { Path = new PropertyPath("Winner") });
}

protected override void OnApplyTemplate()
{
    base.OnApplyTemplate();
    UpdatePlayerState(false);
    UpdateClockState(false);
    UpdateGameOverState(false);
}

public bool IsClockShowing
{
    get { return (bool)GetValue(IsClockShowingProperty); }
    set { SetValue(IsClockShowingProperty, value); }
}

public static readonly DependencyProperty IsClockShowingProperty =
    DependencyProperty.Register(
        "IsClockShowing", typeof(bool), typeof(PlayerStatus), 
        new PropertyMetadata(true, IsClockShowingChanged));

private static void IsClockShowingChanged(DependencyObject d,
    DependencyPropertyChangedEventArgs e)
{
    (d as PlayerStatus).UpdateClockState(true);
}

private void UpdateClockState(bool useTransitions)
{
    GoToState(IsClockShowing ? "ClockShowing" : "ClockHidden",
        useTransitions);
}

private void GoToState(string state, bool useTransitions)
{
    VisualStateManager.GoToState(this, state, useTransitions);
}


Asynchronous code

Asynchronous code helps your UI stay responsive while your app is busy with time-consuming operations.

Key resources:

Reversi uses asynchronous code to perform moves in a game. Each move takes at least a second to complete, including the move animation, and AI moves can take much longer. However, the UI stays responsive at all times, and user commands (such as undo) will cancel a move in progress.

This code from GameViewModel.cs shows how Reversi uses the async and await keywords, the Task class, and cancellation tokens. Note the use of AsTask to integrate with the Windows Runtime asynchronous code in the Game class. (For more info, see the next section.)


private async Task MoveAsync(Space move)
{
    var cancellationToken = GetNewCancellationToken();
    LastMoveAffectedSpaces = 
        await Game.MoveAsync(move).AsTask(cancellationToken);
    if (cancellationToken.IsCancellationRequested) return;
    await OnMoveCompletedAsync(cancellationToken);
}



private async Task AiMoveAsync()
{
    var cancellationToken = GetNewCancellationToken();

    // Unlike the MoveAsync method, the AiMoveAsync method requires a 
    // try/catch block for cancellation. This is because the AI search 
    // checks for cancellation deep within a recursive, iterative search 
    // process that is easiest to halt by throwing an exception. 
    try
    {
        // The WhenAll method call enables the delay and the AI search to 
        // occur concurrently. However, in order to retrieve the return 
        // value of the first task, both tasks must have the same signature,
        // thus requiring the delay task to have a (meaningless) return value.  
        var results = await Task.WhenAll(
            Game.GetBestMoveAsync(CurrentPlayerAiSearchDepth)
                .AsTask(cancellationToken),
            Task.Run(async () => {
                await Task.Delay(MinimumTurnLength, cancellationToken);
                return (Space)null; 
            })
        );

        LastMoveAffectedSpaces = 
            await Game.MoveAsync(results[0]).AsTask(cancellationToken);
        if (cancellationToken.IsCancellationRequested) return;
        await OnMoveCompletedAsync(cancellationToken);
    }
    catch (OperationCanceledException) 
    {
        System.Diagnostics.Debug.WriteLine("cancelled with exception");
    }
}


Using a Windows Runtime Component

Implementing some of your code as a Windows Runtime Component enables you to reuse that code in different apps, on different platforms, or with different languages. You can also more easily replace the component with an alternate implementation in another language. For example, you can use a C++ Windows Runtime Component for specific functions in order to improve performance.

Key resource:

Reversi implements its core game logic as a Windows Runtime Component in order to fully decouple it from the app. This supports future extensibility and code reuse.

This code from Game.cs shows how Reversi uses Task-based asynchronous code (including the async and await keywords) but exposes the results through Windows Runtime asynchronous interfaces. It also shows how the cancellation token from the GameViewModel code is consumed by the Game class.

The first and third methods in the example code call the AsyncInfo.Run method to return an IAsyncOperation<T>. This wraps the return value of the task and enables cancellation. The second example calls the WindowsRuntimeSystemExtensions.AsAsyncAction method to return an IAsyncAction. This is useful for tasks that don't have return values and don't need cancellation.


public IAsyncOperation<IList<Space>> MoveAsync(Space move)
{
    // Use a lock to prevent the ResetAsync method from modifying the game 
    // state at the same time that a different thread is in this method.
    lock (_lockObject)
    {
        return AsyncInfo.Run(cancellationToken => Task.Run(() =>
        {
            if (cancellationToken.IsCancellationRequested) return null;
            var changedSpaces = Move(Board, move, CurrentPlayer);
            Moves.Add(move);

            // If the move diverges from the move stack, synchronize
            // the Moves and MoveStack lists.  
            if (Moves.Count > MoveStack.Count) MoveStack.Add(move);
            else if (!MoveStack[Moves.Count - 1].Equals(move))
            {
                MoveStack = new List<Space>(Moves);
            }

            return changedSpaces;
        }));
    }
}



public IAsyncAction AiMoveAsync(int searchDepth)
{
    return Task.Run(async () => 
    {
        await MoveAsync(await GetBestMoveAsync(searchDepth));
    }).AsAsyncAction();
}

public IAsyncOperation<Space> GetBestMoveAsync(int searchDepth)
{
    if (searchDepth < 1) throw new ArgumentException(
        "must be 1 or greater.", "searchDepth");

    return AsyncInfo.Run(cancellationToken => Task.Run(() => 
    {
        // If it is the AI's turn and we're not at the end of the move stack,
        // just use the next move in the stack. This is necessary to preserve
        // the forward stack, but it also prevents the AI from having to search again. 
        if (Moves.Count < MoveStack.Count) return MoveStack[Moves.Count];

        return GetBestMove(Board, CurrentPlayer, searchDepth, cancellationToken);
    }));
}


Related topics

Developing games
Reversi sample app
Developing Reversi
Reversi app structure
Create your first Windows Store app using C# or Visual Basic
Roadmap for Windows Store apps using C# or Visual Basic
Designing great games for Windows

 

 

Build date: 3/22/2013

Did you find this helpful?
(1500 characters remaining)
© 2013 Microsoft. All rights reserved.