This article is compatible with the latest version of Silverlight.
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
My 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).