(X) Hide this Watch the recording of the webinar 'XNA for Windows Phone 7' delivered on June 30th by Peter Kuhn. In-deep walkthrough to XNA for Windows Phone 7 will be available in the 3-day online training on July 13, 14 and 15. Learn more on the training
SilverlightShow most popular article series are now available as offline downloads in our E-book area. More e-books released every other day!
    • Login
    • Join
      • Generate New Image
        By clicking 'Register' you accept the terms of use .
        Login with Facebook

A classic memory game: Part 2 - The game logic, connecting the ViewModel and the View

(6 votes)
Levente Mihály
>
Levente Mihály
Joined Apr 27, 2010
Articles:   7
Comments:   14
More Articles
0 comments   /   posted on Jan 21, 2011

This article is compatible with the latest version of Silverlight.

Don't miss...

        

             Show more books

This article is Part 2 of “A classic memory game”.

The series is about building the following classic memory game in Silverlight, and porting it to Windows Phone 7. In the first article we started a new MVVM Light project, created the controls and designed the game-states. Now it’s time to put some code behind the game, and finish it.

Here’s the game itself and you can download the source code here.

The Game Logic

MVVM Light template

The MVVM Light project template contains a ViewModelLocator and a MainViewModel in the ViewModel folder. The ViewModelLocator’s job is to hold a reference of the ViewModels and to create them when needed. In the App.xaml there’s an instance of the ViewModelLocator, so the Views can bind to the ViewModel via this instance.

App.xaml:

<Application.Resources>
   <ResourceDictionary>
    <vm:MemoryViewModelLocator x:Key="Locator"
        d:IsDataSource="True" />
   </ResourceDictionary>
</Application.Resources>

MainPage.xaml:

<UserControl.DataContext>
   <Binding Path="Main" Source="{StaticResource Locator}"/>
</UserControl.DataContext>

Model

To represent the cards, we have a MemoryCard class with the following properties:

  • the ID specifies which image is on the card
  • the Upside property indicates if a card is showing it’s face or back
  • the Solved property is set true once the card and its pair are found

The MemoryCard class implements the INotifyPropertyChanged interface. Classes that implement this interface must have an event delegate PropertyChangedEventHandler. Raising this event enables the View (that’s the MainPage.xaml) to track changes. So we need to raise this event when setting the Upside and the Solved properties. The ID won’t change in a MemoryCard’s lifetime so we don’t need to do anything there.

Here’s the Upside property and the raisePropertyChanged method.

/// <summary>
/// The <see cref="Upside" /> property's name.
/// </summary>
public const string UpsidePropertyName = "Upside";
 
private bool _upside = false;
 
/// <summary>
/// Gets the Upside property.
/// Changes to that property's value raise the PropertyChanged event. 
/// </summary>
public bool Upside
{
  get
  {
   return _upside;
  }
 
  set
  {
   if (_upside == value)
   {
     return;
   }
  _upside = value;
 
  // Update bindings, no broadcast
  raisePropertyChanged(UpsidePropertyName);
 }
}
public event PropertyChangedEventHandler PropertyChanged;
 
private void raisePropertyChanged(string propertyName)
{
    if (PropertyChanged != null)
    {
        this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}
 

The game logic

The logic is simple. In the MainViewModel’s constructor we’re setting up two timers (DispatcherTimers), one to turn back cards after a while and one for tracking the elapsed time. In the Start method we load the selected number of cards (based on the difficulty) and reset the score and start the elapsed timer.

We have a few properties to store the state of the game. The most important one is the list of the cards (MemoryCardList). The MainViewModel derives from BaseViewModel that means we don’t have to explicitly implement the INotifyPropertyChanged interface because it’s already implemented in the base class as well as the RaisePropertyChanged method.

Here’s the LoadCards method:

public void LoadCards()
{
    int cardCount = 0;
    switch (Difficulty)
    {
        case Difficulties.Easy:
            cardCount = 8;
            CardSize = 100;
            break;
        case Difficulties.Normal:
            cardCount = 10;
            CardSize = 78;
            break;
        case Difficulties.Hard:
            cardCount = 15;
            CardSize = 62;
            break;                
    }            
 
    // clear
    MemoryCardList.Clear();
 
    // add cards
    for (int i = 0; i < cardCount; i++)
    {
        MemoryCardList.Add(new MemoryCard(i)
        {
            Upside = false
        });
        MemoryCardList.Add(new MemoryCard(i)
        {
            Upside = false
        });
    }
 
    // shuffle them
    var rand = new Random();
    for (int i = MemoryCardList.Count - 1; i > 0; i--)
    {
        int n = rand.Next(i+1);
        var temp = MemoryCardList[i];
        MemoryCardList[i] = MemoryCardList[n];
        MemoryCardList[n] = temp;
    }
 
    IsGameEnded = false;
}

Note that we are adding two cards with the same ID at a time in the loop so we can pair them. After adding the cards we shuffle them. One last thing to explain: the CardSize specifies the actual size (width and height) of the cards in the View as we have smaller cards when there more. Keeping the CardSize in the ViewModel is not very elegant, it should belong to the View, I’ll probably refactor it.

The heart of the game is the OnSelectionChanged method. First we count the upside cards, if it’s two (which is actually one plus the newly flipped) we compare the IDs. If there’s a match they are solved. We have to check if there are any unsolved cards there to track the end of the game. If two cards are already upside showing their front, the newly selected would be the third card to show it’s front, so we have to flip back everything. Here’s the code:

public void OnSelectionChanged(MemoryCard lastCard)
{
    int upsideNo = MemoryCardList.Count((m) => m.Upside);            
    if (upsideNo == 1)
    {
        // one is upside + lastCard is 2, check IDs
        MoveCounter++;
        var upsideCard = MemoryCardList.First((m) => m.Upside);
        if (upsideCard.ID == lastCard.ID)
        {
            if (MemoryCardList.Count((m) => !m.Solved) == 2)
            {
                _elapsedTimer.Stop();
                EndGame();
            }   
            upsideCard.SetSolved();
            lastCard.SetSolved();
            PairCounter++;                                     
            return;
        }
 
        if (!_timer.IsEnabled)
        {
            _timer.Start();
        }
    }                
    else if (upsideNo == 2)
    {
        // two is already upside, hide them, lastCard will be the only upside
        _timer.Stop();
        hideCards();
    }            
}

 

Connecting the ViewModel and the View

After building the project the MainViewModel properties should show up in Blend’s databinding windows. We bind the MemoryCardList to the ListBox’s ItemSource. Click on the little square next to the ItemSource, choose DataBinding and select the MemoryCardList in the DataContext tab.

Now the ListBox will always display the cards in the ViewModel’s MemoryCardList property.

Converters

Blend’s databinding window shows only the compatible types by default. That means for example an IsVisible property type of bool won’t show up (by default) when you try to bind it to any element’s Visibility property, because the Visibility is type of a Visibility enum. Here enters the IValueConverter interface. When you build a class implementing this interface, you can pass it to the binding to have the data converted to compatible type.

A classic example is the VisiblityConverter for the Solved property (so it’s working the opposite way), note that only one way is implemented, we won’t need the ConvertBack:

public class VisibilityConverter : IValueConverter
{
 
    #region IValueConverter Members
 
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        if (value == null) return Visibility.Visible;
 
        if ((bool)value) return Visibility.Collapsed;
        else return Visibility.Visible;
    }
 
    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
 
    #endregion
}

After build, you can set it up in Blend for the MemoryCard’s Solved property. Edit the CardList’s ItemTemplate, select the ToggleButton and bind the Visibility property:

Switch to all properties, select the Solved property, open the dropdown at the bottom of the window, and select the VisibilityConverter as Value converter.

You can bind the Checked property to the Upside property without a converter, because both of them are a type of bool.

Sometimes we need the ValueConverter in both ways, and sometimes we need additional parameters. To set the difficulty I needed both. There is a RadioButton for each difficulty, and we have one Difficulty property in the ViewModel type of an enum. So to make it work I needed to pass which RadioButton is the target. Here’s the code for the DifficultyConverter:

public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
    if (System.Convert.ToString(value).Equals(System.Convert.ToString(parameter)))
    {
        return true;
    }
    return false;
}
 
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
    if (System.Convert.ToBoolean(value))
    {
        return Enum.Parse(typeof(Difficulties), System.Convert.ToString(parameter), true);
    }
    return null;
}

And here’s the Binding for the Easy difficulty to the button’s Checked property:

Another converter was needed for displaying the images based on the ID. The ImageConverter takes the ID and converts it to an ImageSource by resolving a naming convention.

Formatting text

Converters are often used to format text, for example converting a number to string in a desired format. Silverlight 4 however introduced the StringFormat extension. Unfortunately I couldn’t find any Blend support here, so we have to dive into code to make it working. The following code formats the move counter to a 2-digit style:

You can find a good summary about StringFormat on DesignerSilverlight.

CallDatacontextMethodAction

We have to connect not just the data in the ViewModel to the View, but user-actions too. The standard way for the View to notify the ViewModel that something is happened is by commands. A simpler and more designer friendly approach is the CallDatacontextMethodAction behavior by András Velvárt. You might have already read the article about it: A Designer-friendly Approach to MVVM.

After adding it to the project and building it we can add it to our Start button to call the Start method on the ViewModel.

   

Delaying an action

For the MemoryCard I had to delay the effect of setting the Solved property to true. What happened is by setting the card to solved it immediately disappeared, even before the control would arrive to the Checked state. This was clearly not okay, the player couldn’t see the card before it was marked as solved and disappeared.  That’s why I introduced the SetSolved method that starts a timer and only after 1.5 seconds sets the Solved property to true thus making the card disappear.

The Messenger class

The ViewModel sends a signal to the View once all of the cards are collected by using MVVM Light Messenger class. The View then moves into the EndState:

Messenger.Default.Register<string>(this, (msg) =>
{
    if (Constants.EndGameMessage == msg)
    {                        
        VisualStateManager.GoToState(this, EndState.Name, true);
    }
});

 

The Messenger is used in the MemoryCard class as well. When the card is flipped it notifies the MainViewModel and that is when the OnSelectionChanged method runs, which is the heart of the game.

Summary

In this article we inspected the development aspects of a game leveraging on MVVM using bindings, visual states, transitions and behaviors. Expression Blend turned out to be a really powerful tool as for the whole game we needed only little coding.

In the final article of the series I will port this game to Windows Phone 7, exploring performance optimizations, phone-specific UI, and tombstoning. Don’t hesitate to drop a comment if you have questions or want to see something specific in the next article.

About the Author

Levente MihalyMy name is Levente Mihaly. I've entered the .NET world in 2006, and after graduating at Budapest University of Technology (Msc in Computer Science) I started working as a .NET software developer. After meeting technologies like WinForms and ASP.NET, Silverlight quickly became my favourite platform from the early Silverlight 2 beta stages. I was always interested in mobile development and now I'm very happy since with Windows Phone 7 I can easily expand my knowledge to the mobile world. Nowadays beside following Silverlight I'm focusing on WP7 in my free time. I'm also the runner-up of the 2010 Silverlight ecoContest. You can reach me on twitter (@leventemihaly).
 


Subscribe

Comments

No comments

Add Comment

Login to comment:
  *      *       
Login with Facebook

From this series