Click here to Skip to main content
15,867,453 members
Articles / Desktop Programming / WPF

Solitaire and Spider Solitaire for WPF

Rate me:
Please Sign up or sign in to vote.
4.97/5 (89 votes)
26 Feb 2013CPOL24 min read 170.9K   15.4K   160   70
Create Solitaire and Spider Solitaire for WPF, step by step.

Contents

  1. Introduction 
  2. Background
  3. Step-by-Step 
  4. Solitaire and Augmented Reality 
  5. Final Thoughts  

Introduction 

In this article, I will show you how to create the classic Solitaire and Spider Solitaire games using WPF. I have based these games closely around the versions found in Windows Vista onwards. First, some screenshots:

Klondike Solitaire

solitaire/Klondike.jpg

Spider Solitaire

solitaire/Spider.jpg

The Casino (Home Page)

solitaire/Casino.jpg

Background

I've been meaning to write this up for a while but it has turned into one of those 'never quite done' projects. There are a couple more things I would have loved to get in but I think the time has come to draw a line under the project - I'm leaving a list of nice-to-have features at the end of the article so if anyone fancies contributing, then go right ahead!

Step-By-Step

I'm going to go through the whole project step-by-step, so I'm building a brand new project from scratch and taking you through it. However, some bits that might be repetitive or are generic will be brushed over - if anyone feels anything is missing, then please comment and I'll elaborate.

Step 1: Build the Projects

Create a new WPF application named Solitaire (I've targeted .NET 4, I recommend you do the same so the code I've written will work for you).

Immediately add a new WPF User Control Library to the solution named 'SolitaireGames'. This is where we'll stick the solitaire game code and the control that hosts it, we'll keep it in a separate library in case we ever want to add it to another project. In the project 'Solitaire', add a reference to the project 'SolitaireGames'. In SolitaireGames, delete 'UserControl1.xaml', we don't need it.

We'll be using the MVVM design pattern in this project, I am using my own lightweight library Apex. I have included the distributable Apex.dll at the top of the article, both of these projects will need to have it as a dependency. And now we're good to go.

Step 2: Create Core Classes

Well, we're going to need classes and enumerations to represent playing cards. Let's add them one-by-one to the SolitaireGames project. First, create a file called CardColor.cs:

C#
namespace SolitaireGames
{
    /// <summary>
    /// Represents a card colour.
    /// </summary>
    public enum CardColour
    {
        /// <summary>
        /// Black cards (spades or clubs).
        /// </summary>
        Black,

        /// <summary>
        /// Red cards (hearts or diamonds). 
        /// </summary>
        Red
    }
}

Not even a using directive, nice and easy.

Now create CardSuit.cs:

C#
namespace SolitaireGames
{
    /// <summary>
    /// Represents a card's suit.
    /// </summary>
    public enum CardSuit
    {
        /// <summary>
        /// Hearts. 
        /// </summary>
        Hearts,

        /// <summary>
        /// Diamonds.
        /// </summary>
        Diamonds,

        /// <summary>
        /// Clubs.
        /// </summary>
        Clubs,

        /// <summary>
        /// Spades.
        /// </summary>
        Spades
    }
}

So far so good, finally we need the most important enumeration - the CardType enum. Add CardType.cs:

C#
namespace SolitaireGames
{
    /// <summary>
    /// The Card Types.
    /// </summary>
    public enum CardType
    {
        //  Hearts
        HA,
        H2,
        H3,
        H4,
        H5,
        H6,
        H7,
        H8,
        H9,
        H10,
        HJ,
        HQ,
        HK,

        //  Diamonds
        DA,
        D2,
        D3,
        D4,
        D5,
        D6,
        D7,
        D8,
        D9,
        D10,
        DJ,
        DQ,
        DK,

        //  Clubs
        CA,
        C2,
        C3,
        C4,
        C5,
        C6,
        C7,
        C8,
        C9,
        C10,
        CJ,
        CQ,
        CK,

        //  Spades
        SA,
        S2,
        S3,
        S4,
        S5,
        S6,
        S7,
        S8,
        S9,
        S10,
        SJ,
        SQ,
        SK
    }
}

OK, with this one, I skipped the 'comment every enum member' rule, we're really going wild now.

The next file we're going to add is PlayingCard.cs, and with this one, we'll take it bit by bit.

C#
using Apex.MVVM;

namespace SolitaireGames
{
    /// <summary>
    /// The Playing Card represents a Card played in a game - so as
    /// well as the card type it also has the face down property etc.
    /// </summary>
    public class PlayingCard : ViewModel
    {
        /// <summary>
        /// Gets the card suit.
        /// </summary> 
        /// <value>The card suit.</value>
        public CardSuit Suit
        {
            get
            {
                //  The suit can be worked out from
                //  the numeric value of the CardType enum.
                int enumVal = (int)CardType;
                if (enumVal < 13)
                    return CardSuit.Hearts;
                if (enumVal < 26)
                    return CardSuit.Diamonds;
                if(enumVal < 39)
                    return CardSuit.Clubs;
                return CardSuit.Spades;
            }
        }

The PlayingCard class is a ViewModel, as described in the Apex article. All that this does is give us access to the NotifyingProperty construct, which handles all the INotifyPropertyChanged stuff we get in a ViewModel class; we'll see more of this in a bit.

What is a playing card? Well, in this context, a playing card is more than just the face value, it is a card that is played, i.e., we know not just its value but also whether it is face down and so on. The first property is just a little helper property that gets the suit - based on the numeric value of the card type (which is a property defined later!).

C#
/// <summary>
/// Gets the card value.
/// </summary>
/// <value>The card value.</value>
public int Value
{
    get 
    {
        //  The CardType enum has 13 cards in each suit.
        return ((int)CardType) % 13;
    }
}

/// <summary>
/// Gets the card colour.
/// </summary>
/// <value>The card colour.</value>
public CardColour Colour
{
    get 
    {
        // The first two suits in the CardType
        // enum are red, the last two are black.
        return ((int)CardType) < 26 ? 
                 CardColour.Red : CardColour.Black;
    }
}

The card value is another helper property, useful when we want to see if one card is higher than another and so on. We also have the card colour property - again useful when comparing cards. So far these are all read only properties - they're just helpers and all based on CardType, which comes next.

C#
/// <summary>
/// The card type notifying property.
/// </summary>
private NotifyingProperty CardTypeProperty =
        new NotifyingProperty("CardType", typeof(CardType), CardType.SA);

/// <summary>
/// Gets or sets the type of the card.
/// </summary>
/// <value>The type of the card.</value>
public CardType CardType
{
    get { return (CardType)GetValue(CardTypeProperty); }
    set { SetValue(CardTypeProperty, value); }
}

OK, this might look a bit unfamiliar. With Apex, we define NotifyingPropertys - they are written to look very similar to dependency properties but automatically handle calling NotifyPropertyChanged - so all the important members of this class will be notifying properties, and when their values change, anything bound to them will know.

The next four properties are IsFaceDown (is the card face down in the game), IsPlayable (can this card be moved around by the user), and FaceDownOffset/FaceUpOffset (these will be used occasionally when laying out cards). Here they are and this ends the PlayingCard.cs file:

C#
/// <summary>
/// The IsFaceDown notifying property.
/// </summary>
private NotifyingProperty IsFaceDownProperty =
  new NotifyingProperty("IsFaceDown", typeof(bool), false);

/// <summary>
/// Gets or sets a value indicating whether this instance is face down.
/// </summary>
/// <value>
///     <c>true</c> if this instance is face down;
///     otherwise, <c>false</c>.
/// </value>
public bool IsFaceDown
{
    get { return (bool)GetValue(IsFaceDownProperty); }
    set { SetValue(IsFaceDownProperty, value); }
}

/// <summary>
/// The IsPlayable notifying property.
/// </summary>
private NotifyingProperty IsPlayableProperty =
  new NotifyingProperty("IsPlayable", typeof(bool), false);

/// <summary>
/// Gets or sets a value indicating whether this instance is playable.
/// </summary>
/// <value>
///     <c>true</c> if this instance
///    is playable; otherwise, <c>false</c>.
/// </value>
public bool IsPlayable
{
    get { return (bool)GetValue(IsPlayableProperty); }
    set { SetValue(IsPlayableProperty, value); }
}

/// <summary>
/// The FaceDown offset property.
/// </summary>
private NotifyingProperty FaceDownOffsetProperty =
  new NotifyingProperty("FaceDownOffset", typeof(double), default(double));

/// <summary>
/// Gets or sets the face down offset.
/// </summary>
/// <value>The face down offset.</value>
public double FaceDownOffset
{
    get { return (double)GetValue(FaceDownOffsetProperty); }
    set { SetValue(FaceDownOffsetProperty, value); }
}

/// <summary>
/// The FaceUp offset property.
/// </summary>
private NotifyingProperty FaceUpOffsetProperty =
        new NotifyingProperty("FaceUpOffset", 
        typeof(double), default(double));

/// <summary>
/// Gets or sets the face up offset.
/// </summary>
/// <value>The face up offset.</value>
public double FaceUpOffset
{
    get { return (double)GetValue(FaceUpOffsetProperty); }
    set { SetValue(FaceUpOffsetProperty, value); }
}

A little bit of forward thinking here. If you are familiar with the pivot control for Windows Phone 7, this is roughly how we're going to present this app. There'll be a pivot control with four items - Klondike Solitaire (which is just standard solitaire in Windows), the 'Casino' (where we see the statistics and can go to any other game), Spider Solitaire, and the settings.

As we've got more than one card game, we'll create a common base class for the ViewModel for a card game. Create a file called CardGameViewModel.cs:

C#
using System;
using System.Collections.Generic;
using Apex.MVVM;
using System.Windows.Threading;

namespace SolitaireGames
{
    /// <summary>
    /// Base class for a ViewModel for a card game.
    /// </summary>
    public class CardGameViewModel : ViewModel
    {

I don't normally put properties and members first, but doing it this way will make it easier to go through. For a game, we need:

  • A timer to time how long we've been playing
  • A score
  • An elapsed time
  • A 'moves' counter (number of distinct moves made)
  • A flag to indicate the game is won
  • An event that is fired when the game is won
  • A few Commands - Go to casino, card clicked, and deal a new game

Commands are handled in Apex via the ViewModelCommand class, we'll see more later. Anywhere, here are the properties and members we'll need for a card game:

C#
/// <summary>
/// The timer for recording the time spent in a game.
/// </summary>
private DispatcherTimer timer = new DispatcherTimer();

/// <summary>
/// The time of the last tick.
/// </summary>
private DateTime lastTick;

/// <summary>
/// The score notifying property.
/// </summary>
private NotifyingProperty scoreProperty = 
        new NotifyingProperty("Score", typeof(int), 0);

/// <summary>
/// Gets or sets the score.
/// </summary>
/// <value>The score.</value>
public int Score
{
    get { return (int)GetValue(scoreProperty); }
    set { SetValue(scoreProperty, value); }
}

/// <summary>
/// The elapsed time property.
/// </summary>
private readonly NotifyingProperty elapsedTimeProperty =
    new NotifyingProperty("ElapsedTime", 
    typeof(double), default(double));

/// <summary>
/// Gets or sets the elapsed time.
/// </summary>
/// <value>The elapsed time.</value>
public TimeSpan ElapsedTime
{
    get { return TimeSpan.FromSeconds(
         (double)GetValue(elapsedTimeProperty)); }
    set { SetValue(elapsedTimeProperty, value.TotalSeconds); }
}

/// <summary>
/// The moves notifying property.
/// </summary>
private readonly NotifyingProperty movesProperty =
    new NotifyingProperty("Moves", typeof(int), 0);

/// <summary>
/// Gets or sets the moves.
/// </summary>
/// <value>The moves.</value>
public int Moves
{
    get { return (int)GetValue(movesProperty); }
    set { SetValue(movesProperty, value); }
}

/// <summary>
/// The victory flag.
/// </summary>
private NotifyingProperty isGameWonProperty = 
        new NotifyingProperty("IsGameWon", typeof(bool), false);

/// <summary>
/// Gets or sets a value indicating whether this instance is game won.
/// </summary>
/// <value>
///     <c>true</c> if this instance
///           is game won; otherwise, <c>false</c>.
/// </value>
public bool IsGameWon
{
    get { return (bool)GetValue(isGameWonProperty); }
    set { SetValue(isGameWonProperty, value); }
}

/// <summary>
/// The left click card command.
/// </summary>
private ViewModelCommand leftClickCardCommand;

/// <summary>
/// Gets the left click card command.
/// </summary>
/// <value>The left click card command.</value>
public ViewModelCommand LeftClickCardCommand
{
    get { return leftClickCardCommand; }
}

/// <summary>
/// The right click card command.
/// </summary>
private ViewModelCommand rightClickCardCommand;

/// <summary>
/// Gets the right click card command.
/// </summary>
/// <value>The right click card command.</value>
public ViewModelCommand RightClickCardCommand
{
    get { return rightClickCardCommand; }
}

/// <summary>
/// The command to go to the casino.
/// </summary>
private ViewModelCommand goToCasinoCommand;

/// <summary>
/// Gets the go to casino command.
/// </summary>
/// <value>The go to casino command.</value>
public ViewModelCommand GoToCasinoCommand
{
    get { return goToCasinoCommand; }
}

/// <summary>
/// The command to deal a new game.
/// </summary>
private ViewModelCommand dealNewGameCommand;

/// <summary>
/// Gets the deal new game command.
/// </summary>
/// <value>The deal new game command.</value>
public ViewModelCommand DealNewGameCommand
{
    get { return dealNewGameCommand; }
}

/// <summary>
/// Occurs when the game is won.
/// </summary>
public event Action GameWon;

By now you should be familiar with NotifyingPropertys, and ViewModelCommand is a very standard MVVM command object, more info is on the Apex article.

Right the bulk of this class is what we have above, let's finish it off.

C#
/// <summary>
/// Initializes a new instance of the
/// <see cref="CardGameViewModel"> class./>
/// </summary>
public CardGameViewModel()
{
    //  Set up the timer.
    timer.Interval = TimeSpan.FromMilliseconds(500);
    timer.Tick += new EventHandler(timer_Tick);

    //  Create the commands.
    leftClickCardCommand = new ViewModelCommand(DoLeftClickCard, true);
    rightClickCardCommand = new ViewModelCommand(DoRightClickCard, true);
    dealNewGameCommand = new ViewModelCommand(DoDealNewGame, true);
    goToCasinoCommand = new ViewModelCommand(DoGoToCasino, true);
}

/// <summary>
/// The go to casino command.
/// </summary>
private void DoGoToCasino()
{
}

/// <summary>
/// The left click card command.
/// </summary>
/// <param name="parameter">The parameter.</param>
protected virtual void DoLeftClickCard(object parameter)
{
}

/// <summary>
/// The right click card command.
/// </summary>
/// <param name="parameter">The parameter.</param>
protected virtual void DoRightClickCard(object parameter)
{
}

/// <summary>
/// Deals a new game.
/// </summary>
/// <param name="parameter">The parameter.</param>
protected virtual void DoDealNewGame(object parameter)
{
    //  Stop the timer and reset the game data.
    StopTimer();
    ElapsedTime = TimeSpan.FromSeconds(0);
    Moves = 0;
    Score = 0;
    IsGameWon = false;
}

The constructor sets up the timer and creates the ViewModelCommands. Note that three of the commands do nothing but they must exist - they're protected, and also derived classes may need to do special things with them. The DoDealNewGame is going to get fun later on, but for now, it just resets everything. This class is nearly done - we just offer a way to start and stop the timer and a simple way to fire the GameWon event - remember events cannot be directly invoked from derived classes!

C#
/// <summary>
/// Starts the timer.
/// </summary>
public void StartTimer()
{
    lastTick = DateTime.Now;
    timer.Start();
}

/// <summary>
/// Stops the timer.
/// </summary>
public void StopTimer()
{
    timer.Stop();
}

/// <summary>
/// Handles the Tick event of the timer control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see
///     cref="System.EventArgs"> instance
///     containing the event data.</param>
private void timer_Tick(object sender, EventArgs e)
{
    //  Get the time, update the elapsed time, record the last tick.
    DateTime timeNow = DateTime.Now;
    ElapsedTime += timeNow - lastTick;
    lastTick = timeNow;
}

/// <summary>
/// Fires the game won event.
/// </summary>
protected void FireGameWonEvent()
{
    Action wonEvent = GameWon;
    if (wonEvent != null)
        wonEvent();
}

OK, we've spent quite a bit of time with the core classes, it's time to actually start making the game.

Step 3: Klondike Solitaire - The Logic

Klondike Solitaire is our familiar friend 'Solitaire' in Windows. We'll name it KlondikeSolitaire in this project to distinguish it from Spider Solitaire or any others we have. Add a folder called KlondikeSolitaire to the SolitaireGames project.

Create a class called KlondikeSolitaireViewModel, this is going to hold all the logic for the game.

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Apex.MVVM;
using Apex.Extensions;
using System.Collections.ObjectModel;

namespace SolitaireGames.KlondikeSolitaire
{
    /// <summary>
    /// The DrawMode, i.e. how many cards to draw.
    /// </summary>
    public enum DrawMode
    {
        /// <summary>
        /// Draw one card.
        /// </summary>
        DrawOne = 0,

        /// <summary>
        /// Draw three cards.
        /// </summary>
        DrawThree = 1
    }

    /// <summary>
    /// The Klondike Solitaire View Model.
    /// </summary>
    public class KlondikeSolitaireViewModel : CardGameViewModel
    {

One useful little enum at the top of the file is DrawMode - it defines whether we draw one or three cards. Again, I prefer to have member variables and properties at the end, but I'll write them out in an order here that makes it easier to describe:

C#
//  For ease of access we have arrays of the foundations and tableaus.
List<observablecollection<playingcard>> foundations = 
       new List<observablecollection<playingcard>>();
List<observablecollection<playingcard>> tableaus = 
       new List<observablecollection<playingcard>>();

//  Klondike Solitaire has four foundations.
private ObservableCollection<playingcard> foundation1 = 
        new ObservableCollection<playingcard>();
private ObservableCollection<playingcard> foundation2 = 
        new ObservableCollection<playingcard>();
private ObservableCollection<playingcard> foundation3 = 
        new ObservableCollection<playingcard>();
private ObservableCollection<playingcard> foundation4 = 
        new ObservableCollection<playingcard>();

//  It also has seven tableaus.
private ObservableCollection<playingcard> tableau1 = 
        new ObservableCollection<playingcard>();
private ObservableCollection<playingcard> tableau2 = 
        new ObservableCollection<playingcard>();
private ObservableCollection<playingcard> tableau3 = 
        new ObservableCollection<playingcard>();
private ObservableCollection<playingcard> tableau4 = 
        new ObservableCollection<playingcard>();
private ObservableCollection<playingcard> tableau5 = 
        new ObservableCollection<playingcard>();
private ObservableCollection<playingcard> tableau6 = 
        new ObservableCollection<playingcard>();
private ObservableCollection<playingcard> tableau7 = 
        new ObservableCollection<playingcard>();

//  As in most games there is one stock pile.
private ObservableCollection<playingcard> stock = 
        new ObservableCollection<playingcard>();

//  Also, there is the waste pile...
private ObservableCollection<playingcard> waste = 
        new ObservableCollection<playingcard>();

Some nomenclature: A Foundation is where we build a run of suited cards, the top four piles in Solitaire. We've got four of them and they're observable collections. A Tableau is a set of cards, some face up or face down where we do most of the work, we move cards between tableau according to the rules of the game. Klondike has seven tableaus, with one to seven cards in each to begin with. The Stock is the set of face down cards we can 'turn' into the Waste - the Waste is the small pile of cards we can draw from. We'll also want a list that holds each Tableau and Foundation, this'll come in handy later. Here are the last properties:

C#
/// <summary>
/// The draw mode property.
/// </summary>
private NotifyingProperty DrawModeProperty =
  new NotifyingProperty("DrawMode", typeof(DrawMode), 
                        DrawMode.DrawThree);

/// <summary>
/// Gets or sets the draw mode.
/// </summary>
/// <value>The draw mode.</value>
public DrawMode DrawMode
{
    get { return (DrawMode)GetValue(DrawModeProperty); }
    set { SetValue(DrawModeProperty, value); }
}

//  Accessors for the various card stacks.
public ObservableCollection<playingcard> Foundation1 { get { return foundation1; } }
public ObservableCollection<playingcard> Foundation2 { get { return foundation2; } }
public ObservableCollection<playingcard> Foundation3 { get { return foundation3; } }
public ObservableCollection<playingcard> Foundation4 { get { return foundation4; } }
public ObservableCollection<playingcard> Tableau1 { get { return tableau1; } }
public ObservableCollection<playingcard> Tableau2 { get { return tableau2; } }
public ObservableCollection<playingcard> Tableau3 { get { return tableau3; } }
public ObservableCollection<playingcard> Tableau4 { get { return tableau4; } }
public ObservableCollection<playingcard> Tableau5 { get { return tableau5; } }
public ObservableCollection<playingcard> Tableau6 { get { return tableau6; } }
public ObservableCollection<playingcard> Tableau7 { get { return tableau7; } }
public ObservableCollection<playingcard> Stock { get { return stock; } }
public ObservableCollection<playingcard> Waste { get { return waste; } }

/// <summary>
/// The turn stock command.
/// </summary>
private ViewModelCommand turnStockCommand;

/// <summary>
/// Gets the turn stock command.
/// </summary>
/// <value>The turn stock command.</value>
public ViewModelCommand TurnStockCommand
{
    get { return turnStockCommand; }
}

Now we have the familiar notifying property for the draw mode, a set of accessors for the various card stacks, and finally a command - 'Turn Stock', which will move cards from the Stock to the Waste.

Now we can add the functionality.

C#
/// <summary>
/// Initializes a new instance of the
/// <see cref="KlondikeSolitaireViewModel"> class.
/// </summary>
public KlondikeSolitaireViewModel()
{
    //  Create the quick access arrays.
    foundations.Add(foundation1);
    foundations.Add(foundation2);
    foundations.Add(foundation3);
    foundations.Add(foundation4);
    tableaus.Add(tableau1);
    tableaus.Add(tableau2);
    tableaus.Add(tableau3);
    tableaus.Add(tableau4);
    tableaus.Add(tableau5);
    tableaus.Add(tableau6);
    tableaus.Add(tableau7);

    //  Create the turn stock command.
    turnStockCommand = new ViewModelCommand(DoTurnStock, true);

    //  If we're in the designer deal a game.
    if (Apex.Design.DesignTime.IsDesignTime)
        DoDealNewGame(null);
}


/// <summary>
/// Gets the card collection for the specified card.
/// </summary>
/// <param name="card">The card.</param>
/// <returns>
public IList<playingcard> GetCardCollection(PlayingCard card)
{
    if (stock.Contains(card)) return stock;
    if (waste.Contains(card)) return waste;
    foreach (var foundation in foundations) 
             if (foundation.Contains(card)) return foundation;
    foreach (var tableau in tableaus) 
             if (tableau.Contains(card)) return tableau;

    return null;
}

The constructor adds the Foundations and Tableaus to the master list, wires up the Turn Stock command, and (if we're in the designer) actually deals a game (this'll be useful later, in the design view, we'll have a freshly dealt game to see). We also have a helper function to get the parent collection of any card (the View will need this later).

Now we have the first chunk of serious logic:

C#
/// <summary>
/// Deals a new game.
/// </summary>
/// <param name="parameter">The parameter.</param>
protected override void DoDealNewGame(object parameter)
{
    //  Call the base, which stops the timer, clears
    //  the score etc.
    base.DoDealNewGame(parameter);

    //  Clear everything.
    stock.Clear();
    waste.Clear();
    foreach (var tableau in tableaus)
        tableau.Clear();
    foreach (var foundation in foundations)
        foundation.Clear();

    //  Create a list of card types.
    List<cardtype> eachCardType = new List<cardtype>();
    foreach (CardType cardType in Enum.GetValues(typeof(CardType)))
        eachCardType.Add(cardType);

    //  Create a playing card from each card type.
    List<playingcard> playingCards = new List<playingcard>();
    foreach (var cardType in eachCardType)
        playingCards.Add(new PlayingCard() 
        { CardType = cardType, IsFaceDown = true });

    //  Shuffle the playing cards.
    playingCards.Shuffle();

    //  Now distribute them - do the tableaus first.
    for (int i = 0; i < 7; i++)
    {
        //  We have i face down cards and 1 face up card.
        for (int j = 0; j < i; j++)
        {
            PlayingCard faceDownCard = playingCards.First();
            playingCards.Remove(faceDownCard);
            faceDownCard.IsFaceDown = true;
            tableaus[i].Add(faceDownCard);
        }

        //  Add the face up card.
        PlayingCard faceUpCard = playingCards.First();
        playingCards.Remove(faceUpCard);
        faceUpCard.IsFaceDown = false;
        faceUpCard.IsPlayable = true;
        tableaus[i].Add(faceUpCard);
    }

    //  Finally we add every card that's left over to the stock.
    foreach (var playingCard in playingCards)
    {
        playingCard.IsFaceDown = true;
        playingCard.IsPlayable = false;
        stock.Add(playingCard);
    }
    playingCards.Clear();

    //  And we're done.
    StartTimer();
}

I hope I've commented it well enough for you to follow it. It basically sets up the newly dealt game, arranging the cards. Note that we set IsPlayable when appropriate - this'll be used later to make sure that we can only drag cards that we should be able to drag.

The main 'command' of the View Model logic-wise is the Turn Stock command, which will turn one or three cards from the Stock to the Waste or clear the Waste:

C#
/// <summary>
/// Turns cards from the stock into the waste.
/// </summary>
private void DoTurnStock()
{
    //  If the stock is empty, put every
    //  card from the waste back into the stock.
    if (stock.Count == 0)
    {
        foreach (var card in waste)
        {
            card.IsFaceDown = true;
            card.IsPlayable = false;
            stock.Insert(0, card);
        }
        waste.Clear();
    }
    else
    {
        //  Everything in the waste so far must now have no offset.
        foreach (var wasteCard in waste)
            wasteCard.FaceUpOffset = 0;

        //  Work out how many cards to draw.
        int cardsToDraw = 0;
        switch (DrawMode)
        {
            case DrawMode.DrawOne:
                cardsToDraw = 1;
                break;
            case DrawMode.DrawThree:
                cardsToDraw = 3;
                break;
            default:
                cardsToDraw = 1;
                break;
        }

        //  Put up to three cards in the waste.
        for (int i = 0; i < cardsToDraw; i++)
        {
            if (stock.Count > 0)
            {
                PlayingCard card = stock.Last();
                stock.Remove(card);
                card.IsFaceDown = false;
                card.IsPlayable = false;
                card.FaceUpOffset = 30;
                waste.Add(card);
            }
        }
    }

    //  Everything in the waste must be not playable,
    //  apart from the top card.
    foreach (var wasteCard in waste)
        wasteCard.IsPlayable = wasteCard == waste.Last();
}

In Klondike, we can automatically move a card to the appropriate foundation; we'll need a function to handle this (this happens when we right click on a card).

C#
/// <summary>
/// Tries the move the card to its appropriate foundation.
/// </summary>
/// <param name="card">The card.</param>
/// <returns>True if card moved.</returns>
public bool TryMoveCardToAppropriateFoundation(PlayingCard card)
{
    //  Try the top of the waste first.
    if (waste.LastOrDefault() == card)
        foreach (var foundation in foundations)
            if (MoveCard(waste, foundation, card, false))
                return true;

    //  Is the card in a tableau?
    bool inTableau = false;
    int i = 0;
    for (; i < tableaus.Count && inTableau == false; i++)
        inTableau = tableaus[i].Contains(card);

    //  It's if its not in a tablea and it's not the top
    //  of the waste, we cannot move it.
    if (inTableau == false)
        return false;

    //  Try and move to each foundation.
    foreach (var foundation in foundations)
        if (MoveCard(tableaus[i - 1], foundation, card, false))
            return true;

    //  We couldn't move the card.
    return false;
}

If we right click in some blank space, it'll try and move every card to its appropriate Foundation, so let's put a function together for this:

C#
/// <summary>
/// Tries the move all cards to appropriate foundations.
/// </summary>
public void TryMoveAllCardsToAppropriateFoundations()
{
    //  Go through the top card in each tableau - keeping
    //  track of whether we moved one.
    bool keepTrying = true;
    while (keepTrying)
    {
        bool movedACard = false;
        if (waste.Count > 0)
            if (TryMoveCardToAppropriateFoundation(waste.Last()))
                movedACard = true;
        foreach (var tableau in tableaus)
        {
            if (tableau.Count > 0)
                if (TryMoveCardToAppropriateFoundation(tableau.Last()))
                    movedACard = true;
        }

        //  We'll keep trying if we moved a card.
        keepTrying = movedACard;
    }
}

Perfect. Now we have a function inherited from the base class CardGameViewModel that is called when a card is right clicked, we can use this to call the 'TryMoveCard...' function:

C#
/// <summary>
/// The right click card command.
/// </summary>
/// <param name="parameter">The parameter
/// (should be a playing card).</param>
protected override void DoRightClickCard(object parameter)
{
    base.DoRightClickCard(parameter);

    //  Cast the card.
    PlayingCard card = parameter as PlayingCard;
    if (card == null)
        return;

    //  Try and move it to the appropriate foundation.
    TryMoveCardToAppropriateFoundation(card);
}

Another thing we'll need to know is whether we've won - so let's add a function that can check to see if we've won, and if we have set the IsGameWon flag:

C#
/// <summary>
/// Checks for victory.
/// </summary>
public void CheckForVictory()
{
    //  We've won if every foundation is full.
    foreach (var foundation in foundations)
        if (foundation.Count < 13)
            return;

    //  We've won.
    IsGameWon = true;

    //  Stop the timer.
    StopTimer();

    //  Fire the won event.
    FireGameWonEvent();
}

Later on we're going to wire up the View so that we can drag cards from one stack to another. When this happens, we need to know if the move is valid and if it is actually performing the move and update the score. This is the most complicated function of the class, so look carefully over the comments:

C#
/// <summary>
/// Moves the card.
/// </summary>
/// <param name="from">The set we're moving from.</param>
/// <param name="to">The set we're moving to.</param>
/// <param name="card">The card we're moving.</param>
/// <param name="checkOnly">if set to <c>true</c>
/// we only check if we CAN move, but don't actually move.</param>
/// <returns>True if a card was moved.</returns>
public bool MoveCard(ObservableCollection<playingcard> from,
       ObservableCollection<playingcard> to,
       PlayingCard card, bool checkOnly)
{
    //  The trivial case is where from and to are the same.
    if (from == to)
        return false;

    //  This is the complicated operation.
    int scoreModifier = 0;

    //  Are we moving from the waste?
    if (from == Waste)
    {
        //  Are we moving to a foundation?
        if (foundations.Contains(to))
        {
            //  We can move to a foundation only if:
            //  1. It is empty and we are an ace.
            //  2. It is card SN and we are suit S and Number N+1
            if ((to.Count == 0 && card.Value == 0) ||
                (to.Count > 0 && to.Last().Suit == 
                 card.Suit && (to.Last()).Value == (card.Value - 1)))
            {
                //  Move from waste to foundation.
                scoreModifier = 10;
            }
            else
                return false;
        }
        //  Are we moving to a tableau?
        else if (tableaus.Contains(to))
        {
            //  We can move to a tableau only if:
            //  1. It is empty and we are a king.
            //  2. It is card CN and we are color !C and Number N-1
            if ((to.Count == 0 && card.Value == 12) ||
                (to.Count > 0 && to.Last().Colour != card.Colour 
                  && (to.Last()).Value == (card.Value + 1)))
            {
                //  Move from waste to tableau.
                scoreModifier = 5;
            }
            else
                return false;
        }
        //  Any other move from the waste is wrong.
        else
            return false;
    }
    //  Are we moving from a tableau?
    else if (tableaus.Contains(from))
    {
        //  Are we moving to a foundation?
        if (foundations.Contains(to))
        {
            //  We can move to a foundation only if:
            //  1. It is empty and we are an ace.
            //  2. It is card SN and we are suit S and Number N+1
            if ((to.Count == 0 && card.Value == 0) ||
                (to.Count > 0 && to.Last().Suit == card.Suit 
                  && (to.Last()).Value == (card.Value - 1)))
            {
                //  Move from tableau to foundation.
                scoreModifier = 10;
            }
            else
                return false;
        }
        //  Are we moving to another tableau?
        else if (tableaus.Contains(to))
        {
            //  We can move to a tableau only if:
            //  1. It is empty and we are a king.
            //  2. It is card CN and we are color !C and Number N-1
            if ((to.Count == 0 && card.Value == 12) ||
                (to.Count > 0 && to.Last().Colour != card.Colour 
                  && (to.Last()).Value == (card.Value + 1)))
            {
                //  Move from tableau to tableau.
                scoreModifier = 0;
            }
            else
                return false;
        }
        //  Any other move from a tableau is wrong.
        else
            return false;
    }
    //  Are we moving from a foundation?
    else if (foundations.Contains(from))
    {
        //  Are we moving to a tableau?
        if (tableaus.Contains(to))
        {
            //  We can move to a tableau only if:
            //  1. It is empty and we are a king.
            //  2. It is card CN and we are color !C and Number N-1
            if ((to.Count == 0 && card.Value == 12) ||
                (to.Count > 0 && to.Last().Colour != card.Colour 
                  && (to.Last()).Value == (card.Value + 1)))
            {
                //  Move from foundation to tableau.
                scoreModifier = -15;
            }
            else
                return false;
        }
        //  Are we moving to another foundation?
        else if (foundations.Contains(to))
        {
            //  We can move from a foundation to a foundation only 
            //  if the source foundation has one card (the ace) and the
            //  destination foundation has no cards).
            if (from.Count == 1 && to.Count == 0)
            {
                //  The move is valid, but has no impact on the score.
                scoreModifier = 0;
            }
            else
                return false;
        }
        //  Any other move from a foundation is wrong.
        else
            return false;
    }
    else
        return false;

    //  If we were just checking, we're done.
    if (checkOnly)
        return true;

    //  If we've got here we've passed all tests
    //  and move the card and update the score.
    DoMoveCard(from, to, card);
    Score += scoreModifier;
    Moves++;

    //  If we have moved from the waste, we must 
    //  make sure that the top of the waste is playable.
    if (from == Waste && Waste.Count > 0)
        Waste.Last().IsPlayable = true;

    //  Check for victory.
    CheckForVictory();

    return true;
}

You may have noticed that there is a DoMoveCard function called near the end; this actually moves the card from one place to another and is a bit more straightforward than the last function:

C#
/// <summary>
/// Actually moves the card.
/// </summary>
/// <param name="from">The stack to move from.</param>
/// <param name="to">The stack to move to.</param>
/// <param name="card">The card.</param>
private void DoMoveCard(ObservableCollection<playingcard> from,
    ObservableCollection<playingcard> to,
    PlayingCard card)
{
    //  Indentify the run of cards we're moving.
    List<playingcard> run = new List<playingcard>();
    for (int i = from.IndexOf(card); i < from.Count; i++)
        run.Add(from[i]);

    //  This function will move the card, as well as setting the 
    //  playable properties of the cards revealed.
    foreach(var runCard in run)
        from.Remove(runCard);
    foreach(var runCard in run)
        to.Add(runCard);

    //  Are there any cards left in the from pile?
    if (from.Count > 0)
    {
        //  Reveal the top card and make it playable.
        PlayingCard topCard = from.Last();

        topCard.IsFaceDown = false;
        topCard.IsPlayable = true;
    }
}

That's it - the class is done. Let's move onto the visuals.

Step 3: Klondike Solitaire - The View

We've got a solid ViewModel, Notifying Properties, and Observable Collections as well as Commands, so creating the View should be a breeze.

Add a new UserControl called KlondikeSolitaireView to the KlondikeSolitaire folder. Let's put the XAML together.

XML
<UserControl x:Class="SolitaireGames.KlondikeSolitaire.KlondikeSolitaireView"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         xmlns:local="clr-namespace:SolitaireGames.KlondikeSolitaire"
         xmlns:solitaireGames="clr-namespace:SolitaireGames"
         xmlns:apexControls="clr-namespace:Apex.Controls;assembly=Apex"
         xmlns:apexCommands="clr-namespace:Apex.Commands;assembly=Apex"
         xmlns:apexDragAndDrop="clr-namespace:Apex.DragAndDrop;assembly=Apex"
         mc:Ignorable="d" 
         x:Name="klondikeSolitaireView"
         d:DesignHeight="300" d:DesignWidth="300">
    
<!-- Point to the main resource dictionary for the assembly. -->
<UserControl.Resources>
    <ResourceDictionary 
      Source="/SolitaireGames;component/Resources/
              SolitaireGamesResourceDictionary.xaml" />
</UserControl.Resources>

The user control is going to use a few different namespaces which we defined early on. We point to a single resource dictionary for the assembly - but we'll go through this afterwards.

XML
<!-- The main grid - the game is at the top and the commands are at the bottom. -->
<apexControls:ApexGrid 
           Rows="*,Auto"
           DataContext="{Binding ViewModel, ElementName=klondikeSolitaireView}">

<!-- The cards etc are all in a Viewbox so that they resize in a sensible
           way when we resize the window. -->
<Viewbox Grid.Row="0" Margin="10">
<!-- A DragAndDropHost allows us to perform more complicated logic when
           we drag and drop. -->
<apexDragAndDrop:DragAndDropHost 
       x:Name="dragAndDropHost" 
       MouseRightButtonDown="dragAndDropHost_MouseRightButtonDown"
       MinimumHorizontalDragDistance="0.0" 
       MinimumVerticalDragDistance="0.0">

The whole control is wrapped in a grid (actually an ApexGrid; see this article for details: apexgrid.aspx) with the game at the top and a small row of buttons and info at the bottom. The grid has its data context set to the ViewModel property we'll see later.

The game is in a ViewBox which is great because it'll mean it looks consistent at different sizes. The DragAndDrop host is a class provided by Apex that provides efficient and easy drag and drop functionality for cases where we're trying to move elements, not necessarily items from a tree to a list or whatever. The DragAndDrop host is a monster to explain in detail, so it is detailed in Appendix 2. Anything within DragAndDropHost will be eligible for our simplified drag and drop functionality.

OK, time to lay out the Tableaus, Foundations, and so on, binding each one to the appropriate View Model member.

XML
 <!-- This is the layout grid for the tableaus, foundations etc. -->
<apexControls:ApexGrid Width="1200" Height="840" Columns="*,*,*,*,*,*,*" Rows="240,600">
XML
<!-- The drag stack, cards are stored here temporarily while they are being dragged. -->
<solitaireGames:CardStackControl 
       x:Name="dragStack" Grid.Row="0" 
       Grid.Column="0" Margin="0,-2000,0,0"
       Orientation="Vertical" 
       FaceDownOffset="10" FaceUpOffset="30"
       apexDragAndDrop:DragAndDrop.IsDragSource="False"/>
<!-- The stock. -->
<Border 
       Grid.Row="0" Grid.Column="0" 
       Style="{StaticResource StackMarker}" />
<solitaireGames:CardStackControl 
       Grid.Row="0" Grid.Column="0" 
       ItemsSource="{Binding Stock}" Cursor="Hand"
       Orientation="Horizontal" FaceDownOffset="0" 
       FaceUpOffset="0" 
       MouseLeftButtonUp="CardStackControl_MouseLeftButtonUp" />

<!-- The waste. -->
<Border 
       Grid.Row="0" Grid.Column="1" 
       Style="{StaticResource StackMarker}" />
<solitaireGames:CardStackControl 
       x:Name="wasteStack"
       Grid.Row="0" Grid.Column="1" 
       Grid.ColumnSpan="2" ItemsSource="{Binding Waste}"
       Orientation="Horizontal" OffsetMode="UseCardValues"  />

<!-- The foundations. -->
<Border 
       Grid.Row="0" Grid.Column="3" 
       Style="{StaticResource StackMarker}" />
<solitaireGames:CardStackControl 
       Grid.Row="0" Grid.Column="3" 
       ItemsSource="{Binding Foundation1}"
       Orientation="Horizontal" FaceDownOffset="0" 
       FaceUpOffset="0" />
<Border 
       Grid.Row="0" Grid.Column="4" 
       Style="{StaticResource StackMarker}" />
<solitaireGames:CardStackControl 
       Grid.Row="0" Grid.Column="4" 
       ItemsSource="{Binding Foundation2}"
       Orientation="Horizontal" 
       FaceDownOffset="0" FaceUpOffset="0" />
<Border 
       Grid.Row="0" Grid.Column="5" 
       Style="{StaticResource StackMarker}" />
<solitaireGames:CardStackControl 
       Grid.Row="0" Grid.Column="5" 
       ItemsSource="{Binding Foundation3}"
       Orientation="Horizontal" 
       FaceDownOffset="0" FaceUpOffset="0" />
<Border 
        Grid.Row="0" Grid.Column="6" 
        Style="{StaticResource StackMarker}" />
<solitaireGames:CardStackControl 
       Grid.Row="0" Grid.Column="6" 
       ItemsSource="{Binding Foundation4}"
       Orientation="Horizontal" FaceDownOffset="0" 
       FaceUpOffset="0" />


<!-- The tableaus. -->
<Border 
       Grid.Row="1" Grid.Column="0" 
       Style="{StaticResource RunMarker}" />
<solitaireGames:CardStackControl 
       Grid.Row="1" Grid.Column="0" 
       ItemsSource="{Binding Tableau1}"
       Orientation="Vertical" 
       FaceDownOffset="10" FaceUpOffset="30" />
<Border 
       Grid.Row="1" Grid.Column="1" 
       Style="{StaticResource RunMarker}" />
<solitaireGames:CardStackControl
       Grid.Row="1" Grid.Column="1" 
       ItemsSource="{Binding Tableau2}"
       Orientation="Vertical" 
       FaceDownOffset="10" FaceUpOffset="30" />
<Border 
       Grid.Row="1" Grid.Column="2" 
       Style="{StaticResource RunMarker}" />
<solitaireGames:CardStackControl 
       Grid.Row="1" Grid.Column="2" 
       ItemsSource="{Binding Tableau3}"
       Orientation="Vertical" 
       FaceDownOffset="10" FaceUpOffset="30" />
<Border 
       Grid.Row="1" Grid.Column="3" 
       Style="{StaticResource RunMarker}" />
<solitaireGames:CardStackControl 
       Grid.Row="1" Grid.Column="3" 
       ItemsSource="{Binding Tableau4}"
       Orientation="Vertical" 
       FaceDownOffset="10" FaceUpOffset="30" />
<Border 
       Grid.Row="1" Grid.Column="4" 
       Style="{StaticResource RunMarker}" />
<solitaireGames:CardStackControl 
       Grid.Row="1" Grid.Column="4" 
       ItemsSource="{Binding Tableau5}"
       Orientation="Vertical" 
       FaceDownOffset="10" FaceUpOffset="30" />
<Border 
       Grid.Row="1" Grid.Column="5" 
       Style="{StaticResource RunMarker}" />
<solitaireGames:CardStackControl 
       Grid.Row="1" Grid.Column="5" 
       ItemsSource="{Binding Tableau6}"
       Orientation="Vertical" FaceDownOffset="10" 
       FaceUpOffset="30" />
<Border 
       Grid.Row="1" Grid.Column="6" 
       Style="{StaticResource RunMarker}" />
<solitaireGames:CardStackControl 
       Grid.Row="1" Grid.Column="6" 
       ItemsSource="{Binding Tableau7}"
       Orientation="Vertical" FaceDownOffset="10" 
       FaceUpOffset="30" />
</apexControls:ApexGrid>
</apexDragAndDrop:DragAndDropHost>
</Viewbox>

We're referring to some things here that'll be detailed later, but in a nutshell, we have:

  • dragStack: A 'hidden' stack that holds the stack of cards being dragged.
  • CardStackControl: An items control that can lay its children out in stacks, with various different ways of offsetting each item. See Appendix 1.
  • StackMarker: A resource used to draw a faint white border for a stack.
  • RunMarker: A resource used to draw a fading white set of lines for a run.

Ignoring the resources here, what we're really just doing is putting together a few ItemsControls and binding them to our ViewModel.

Below the actual game, we put some commands and some info:

XML
<!-- A padded grid is used to layout commands 
     and info at the bottom of the screen. -->
<apexControls:PaddedGrid 
 Grid.Row="1" Padding="4" 
 Columns="Auto,*,Auto,Auto,Auto,*,Auto">
<Button 
 Style="{StaticResource CasinoButtonStyle}"
 Grid.Column="0" Width="120" Content="Deal New Game"
 Command="{Binding DealNewGameCommand}" />
<TextBlock Grid.Column="2" 
   Style="{StaticResource CasinoTextStyle}" 
   VerticalAlignment="Center">
<Run Text="Score:" />
<Run Text="{Binding Score}" />
</TextBlock>
<TextBlock Grid.Column="3" 
  Style="{StaticResource CasinoTextStyle}" 
  VerticalAlignment="Center">
<Run Text="Moves:" />
<Run Text="{Binding Moves}" />
</TextBlock>
<TextBlock Grid.Column="4" 
  Style="{StaticResource CasinoTextStyle}" 
  VerticalAlignment="Center">
<Run Text="Time:" />
<Run Text="{Binding ElapsedTime, 
            Converter={StaticResource 
            TimeSpanToShortStringConverter}}" />
</TextBlock>
<Button 
 Style="{StaticResource CasinoButtonStyle}"
 Grid.Column="6" Width="120" Content="Go to Casino"
 Command="{Binding GoToCasinoCommand}" />
</apexControls:PaddedGrid>

We use a padded grid to lay things out nicely (wpfpaddedgrid.aspx). This is straightforward - we have a deal new game button, a go to casino button, the score, time, and moves. Again, the resources are described later.

When the game is won, we just overlay the XAML below - this is why we have the IsGameWon property.

XML
<!-- The win overlay. --> 
<apexControls:ApexGrid 
 Rows="*,Auto,Auto,Auto,*" Grid.RowSpan="2" Background="#00FFFFFF"
 Visibility="{Binding IsGameWon, 
              Converter={StaticResource 
              BooleanToVisibilityConverter}}">


<TextBlock 
   Grid.Row="1" FontSize="34" 
   FontWeight="SemiBold" Foreground="#99FFFFFF"
   HorizontalAlignment="Center" Text="You Win!" />
<TextBlock
   Grid.Row="2" FontSize="18" Foreground="#99FFFFFF"
   HorizontalAlignment="Center" TextWrapping="Wrap">
<Run Text="You scored" />
<Run Text="{Binding Score}" />
<Run Text="in" />
<Run Text="{Binding Moves}" />
<Run Text="moves!" />
</TextBlock>
<StackPanel 
   Grid.Row="3" Orientation="Horizontal" 
   HorizontalAlignment="Center">
<Button 
   Style="{StaticResource CasinoButtonStyle}" Width="120" 
   Margin="4" Content="Play Again" 
   Command="{Binding DealNewGameCommand}" />
<Button 
   Style="{StaticResource CasinoButtonStyle}" Width="120" 
   Margin="4" Content="Back to Casino" 
   Command="{Binding GoToCasinoCommand}"  />
</StackPanel>
</apexControls:ApexGrid>
</apexControls:ApexGrid>
</UserControl>

That's it - that's the View for Klondike! The eagle-eyed among you may have noticed we have a few code-behind functions, let's add them.

The constructor will wire in the drag and drop host:

C#
/// <summary>
/// Initializes a new instance of the
/// <see cref="KlondikeSolitaireView"/> class.
/// </summary>
public KlondikeSolitaireView()
{
    InitializeComponent();

    //  Wire up the drag and drop host.
    dragAndDropHost.DragAndDropStart += 
             new DragAndDropDelegate(Instance_DragAndDropStart);
    dragAndDropHost.DragAndDropContinue += 
             new DragAndDropDelegate(Instance_DragAndDropContinue);
    dragAndDropHost.DragAndDropEnd += 
             new DragAndDropDelegate(Instance_DragAndDropEnd);
}

I'm not going to go into a huge amount of detail about drag and drop right now, but here's the skinny:

  • DragAndDropStart: Is called when any UIElement that has DragAndDrop.IsDraggable set to true is dragged.
  • DragAndDropContinue: Is called when a dragged element is moved over an element with DragAndDrop.IsDropTarget set to true.
  • DragAndDropEnd: Is called when a dragged element is released over a drop target.

Why write my own? Well, the plan is that it'll work in Silverlight as well - but this is a topic for another day.

When drag and drop starts, we check the card is OK, and put it and the cards below it into the invisible drag stack - this will be what we draw as the drag adorner.

C#
void Instance_DragAndDropStart(object sender, DragAndDropEventArgs args)
{
    //  The data should be a playing card.
    PlayingCard card = args.DragData as PlayingCard;
    if (card == null || card.IsPlayable == false)
    {
        args.Allow = false;
        return;
    }
    args.Allow = true;

    //  If the card is draggable, we're going to want to drag the whole
    //  stack.
    IList<playingcard> cards = ViewModel.GetCardCollection(card);
    draggingCards = new List<playingcard>();
    int start = cards.IndexOf(card);
    for (int i = start; i < cards.Count; i++)
        draggingCards.Add(cards[i]);

    //  Clear the drag stack.
    dragStack.ItemsSource = draggingCards;
    dragStack.UpdateLayout();
    args.DragAdorner = new Apex.Adorners.VisualAdorner(dragStack);

    //  Hide each dragging card.
    ItemsControl sourceStack = args.DragSource as ItemsControl;
    foreach (var dragCard in draggingCards)
        ((ObservableCollection<playingcard>)
          sourceStack.ItemsSource).Remove(dragCard);
}

Why the custom adorner? Silverlight is the short answer. When we drag a card, if it is draggable, we drag it and all the cards below it. We hide them in the source stack by putting them in the drag stack, which is what's drawn by the adorner.

C#
void Instance_DragAndDropContinue(object sender, DragAndDropEventArgs args)
{
    args.Allow = true;
}

The continue function is trivial in this case, we're always going to let the operation continue; when we get to DragAndDropEnd, that's when we'll check all is OK.

Drag and drop end moves cards from the temporary drag stack back to the source and then just lets the View Model take over.

C#
void Instance_DragAndDropEnd(object sender, DragAndDropEventArgs args)
{
    //  We've put cards temporarily in the drag stack, put them in the 
    //  source stack again.
    ItemsControl sourceStack = args.DragSource as ItemsControl;
    foreach (var dragCard in draggingCards)
        ((ObservableCollection<playingcard>)
        ((ItemsControl)args.DragSource).ItemsSource).Add(dragCard);

    //  If we have a drop target, move the card.
    if (args.DropTarget != null)
    {
        //  Move the card.
        ViewModel.MoveCard(
            (ObservableCollection<playingcard>)((ItemsControl)args.DragSource).ItemsSource,
            (ObservableCollection<playingcard>)((ItemsControl)args.DropTarget).ItemsSource,
            (PlayingCard)args.DragData, false);
    }
}

We have a ViewModel dependency property as well, we'll see why this is important when we build the Casino.

C#
/// <summary>
/// The ViewModel dependency property.
/// </summary>
private static readonly DependencyProperty ViewModelProperty =
  DependencyProperty.Register("ViewModel", 
  typeof(KlondikeSolitaireViewModel), typeof(KlondikeSolitaireView),
  new PropertyMetadata(new KlondikeSolitaireViewModel()));

/// <summary>
/// Gets or sets the view model.
/// </summary>
/// <value>The view model.</value>
public KlondikeSolitaireViewModel ViewModel
{
    get { return (KlondikeSolitaireViewModel)GetValue(ViewModelProperty); }
    set { SetValue(ViewModelProperty, value); }
}

The last thing in the View code-behind is the temporary storage for the dragged cards, we let a right click that's not on a card call the TryMoveAllCards... function, and a left click on the stock stack (i.e., only handled when the stack is empty) call the Turn Stock command (so we can click on the empty stock to turn cards from the waste back over).

C#
/// <summary>
/// Handles the MouseRightButtonDown event of the dragAndDropHost control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see
// cref="System.Windows.Input.MouseButtonEventArgs">
// instance containing the event data.</param>
private void dragAndDropHost_MouseRightButtonDown(object sender, 
             MouseButtonEventArgs e)
{
    ViewModel.TryMoveAllCardsToAppropriateFoundations();
}

/// <summary>
/// Temporary storage for cards being dragged.
/// </summary>
private List<playingcard> draggingCards;

/// <summary>
/// Handles the MouseLeftButtonUp event of the CardStackControl control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see
/// cref="System.Windows.Input.MouseButtonEventArgs">
/// instance containing the event data.</param>
private void CardStackControl_MouseLeftButtonUp(object sender, 
             MouseButtonEventArgs e)
{
    ViewModel.TurnStockCommand.DoExecute(null);
}

This is the View done.

Step 4: Resources

In the View created previously, we referenced a resource dictionary, let's now add that dictionary.

Create a folder called Resources in SolitaireGames, add a ResourceDictionary named SolitiareGamesResourceDictionary.xaml.

XML
<ResourceDictionary 
 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 xmlns:sys="clr-namespace:System;assembly=mscorlib"
 xmlns:apexMVVM="clr-namespace:Apex.MVVM;assembly=Apex"
 xmlns:apexCommands="clr-namespace:Apex.Commands;assembly=Apex"
 xmlns:apexDragAndDrop="clr-namespace:Apex.DragAndDrop;assembly=Apex"
 xmlns:local="clr-namespace:SolitaireGames"
 xmlns:apexConverters="clr-namespace:Apex.Converters;assembly=Apex"
 xmlns:solitaireGames="clr-namespace:SolitaireGames"
 xmlns:klondike="clr-namespace:SolitaireGames.KlondikeSolitaire"
 xmlns:spider="clr-namespace:SolitaireGames.SpiderSolitaire">

<!-- Converters.-->
<apexConverters:BooleanToVisibilityConverter 
        x:Key="BooleanToVisibilityConverter" />
<solitaireGames:TimeSpanToShortStringConverter 
        x:Key="TimeSpanToShortStringConverter" />

First we have the usual raft of namespace declarations. Then a couple of converters. Apex.Converters.BooleanToVisibilityConverter is just like the standard one - except that you can pass Invert as its parameter and it'll invert the result.

In the Klondike View, we show a time span, but we want it in a particular format, and for that, we have a converter. It's such a simple class, I'll just drop it in below (it is called TimeSpanToShortStringConverter).

C#
using System;
using System.Windows.Data;
namespace SolitaireGames
{
    /// <summary>
    /// A converter that turns a time span into a small string, only 
    /// suitable for up to 24 hours.
    /// </summary>
    class TimeSpanToShortStringConverter : IValueConverter
    {
        /// <summary>
        /// Converts a value.
        /// </summary>
        /// <param name="value">The value produced by the binding source.</param>
        /// <param name="targetType">The type of the binding target property.</param>
        /// <param name="parameter">The converter parameter to use.</param>
        /// <param name="culture">The culture to use in the converter.</param>
        /// <returns>
        /// A converted value. If the method returns null,
        /// the valid null value is used.
        /// </returns>
        public object Convert(object value, Type targetType, 
               object parameter, System.Globalization.CultureInfo culture)
        {
            TimeSpan timeSpan = (TimeSpan)value;
            if(timeSpan.Hours > 0)
                return string.Format("{0:D2}:{1:D2}:{2:D2}",
                        timeSpan.Hours,
                        timeSpan.Minutes,
                        timeSpan.Seconds);
            else
                return string.Format("{0:D2}:{1:D2}",
                        timeSpan.Minutes,
                        timeSpan.Seconds);
        }

        /// <summary>
        /// Converts a value.
        /// </summary>
        /// <param name="value">The value that
        /// is produced by the binding target.</param>
        /// <param name="targetType">The type to convert to.</param>
        /// <param name="parameter">The converter parameter to use.</param>
        /// <param name="culture">The culture to use in the converter.</param>
        /// <returns>
        /// A converted value. If the method returns null, the valid null value is used.
        /// </returns>
        public object ConvertBack(object value, Type targetType, 
               object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}

The next converter we have is a playing card to brush converter, this will take a PlayingCard object and return the appropriate brush. If you look in the sample code, you'll see that Resources contains a folder called Decks, this contains four deck folders, Classic, Hearts, Large Print, and Seasons. Each of these folders contains an image with each card type as a name (e.g., S2.png is the 2 of Spades, HA is the ace of Hearts), and a Back.png as a card background image.

These resources were extracted from the Windows 7 Solitaire games and tidied, cropped, etc. in Photoshop (600 odd files, it took a while, thank you PhotoShop batch processing). The PlayingCardToBrushConverter will allow us to turn a PlayingCard into the appropriate brush for it - with the brushes stored in a dictionary so we only create them as needed. The additional decks functionality was added later so is a bit kludgy, but it works!

Again, it's a simple converter so I'm not going to go into the details:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Data;
using System.Globalization;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace SolitaireGames
{
    /// <summary>
    /// Converter to get the brush for a playing card.
    /// </summary>
    public class PlayingCardToBrushConverter : IMultiValueConverter
    {
        /// <summary>
        /// Sets the deck folder.
        /// </summary>
        /// <param name="folderName">Name of the folder.</param>
        public static void SetDeckFolder(string folderName)
        {
            //  Clear the dictionary so we recreate card brushes.
            brushes.Clear();

            //  Set the deck folder.
            deckFolder = folderName;
        }

        /// <summary>
        /// The default deck folder.
        /// </summary>
        static string deckFolder = "Classic";

        /// <summary>
        /// A dictionary of brushes for card types.
        /// </summary>
        static Dictionary<string,> brushes = new Dictionary<string,>();

        /// <summary>
        /// Converts source values to a value for the binding target.
        /// The data binding engine calls this method when it propagates
        /// the values from source bindings to the binding target.
        /// </summary>
        /// <param name="values">The array of values that
        /// the source bindings in the <see cref="T:System.Windows.Data.MultiBinding">
        /// produces. The value <see cref="F:System.Windows.DependencyProperty.UnsetValue">
        /// indicates that the source binding has
        /// no value to provide for conversion.</param>
        /// <param name="targetType">The type of the binding target property.</param>
        /// <param name="parameter">The converter parameter to use.</param>
        /// <param name="culture">The culture to use in the converter.</param>
        /// <returns>
        /// A converted value.If the method returns null, the valid null value is used.
        /// A return value of <see cref="T:System.Windows.DependencyProperty">.
        /// <see cref="F:System.Windows.DependencyProperty.UnsetValue"> indicates
        /// that the converter did not produce a value, and that the binding will use the
        /// <see cref="P:System.Windows.Data.BindingBase.FallbackValue">
        /// if it is available, or else will use the default value.A return value of
        /// <see cref="T:System.Windows.Data.Binding">.<see
        /// cref="F:System.Windows.Data.Binding.DoNothing"> indicates that
        /// the binding does not transfer the value or use the
        /// <see cref="P:System.Windows.Data.BindingBase.FallbackValue">
        /// or the default value.
        /// </see></see></see></see></see></see></returns>
        public object Convert(object[] values, Type targetType, 
               object parameter, CultureInfo culture)
        {
            //  Cast the data.
            if (values == null || values.Count() != 2)
                return null;

            //  Cast the values.
            CardType cardType = (CardType)values[0];
            bool faceDown = (bool)values[1];

            //  We're going to create an image source.
            string imageSource = string.Empty;

            //  If the card is face down, we're using the 'Rear' image.
            //  Otherwise it's just the enum value (e.g. C3, SA).
            if (faceDown)
                imageSource = "Back";
            else
                imageSource = cardType.ToString();

            //  Turn this string into a proper path.
            imageSource = 
              "pack://application:,,,/SolitaireGames;component/Resources/Decks/" + 
              deckFolder + "/" + imageSource + ".png";

            //  Do we need to add this brush to the static dictionary?
            if (brushes.ContainsKey(imageSource) == false)
                brushes.Add(imageSource, new ImageBrush(
                new BitmapImage(new Uri(imageSource))));

            //  Return the brush.
            return brushes[imageSource];
        }

        /// <summary>
        /// Converts a binding target value to the source binding values.
        /// </summary>
        /// <param name="value">The value that the binding target produces.</param>
        /// <param name="targetTypes">The array of types to convert to.
        /// The array length indicates the number and types of values
        /// that are suggested for the method to return.</param>
        /// <param name="parameter">The converter parameter to use.</param>
        /// <param name="culture">The culture to use in the converter.</param>
        /// <returns>
        /// An array of values that have been converted from
        /// the target value back to the source values.
        /// </returns>
        public object[] ConvertBack(object value, Type[] targetTypes, 
               object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}

In essence, we write the enum value as a string and build a path from some hard coded values.

Back to the dictionary XAML, the most important data template comes next:

XML
<!-- The playing card data template. -->
<DataTemplate DataType="{x:Type solitaireGames:PlayingCard}">
<Border
 Width="140" Height="190" Cursor="Hand"
 BorderThickness="1" CornerRadius="6"
 apexDragAndDrop:DragAndDrop.IsDraggable="True"
 apexCommands:ExtendedCommands.RightClickCommand="{Binding RelativeSource={RelativeSource 
 FindAncestor, AncestorType={x:Type UserControl}}, Path=ViewModel.RightClickCardCommand}"
 apexCommands:ExtendedCommands.RightClickCommandParameter="{Binding }"
>
<apexCommands:EventBindings.EventBindings>
    <apexCommands:EventBindingCollection>
        <apexCommands:EventBinding 
            EventName="MouseLeftButtonUp"
            Command="{Binding RelativeSource={RelativeSource FindAncestor, 
              AncestorType={x:Type UserControl}}, 
              Path=ViewModel.LeftClickCardCommand}" 
            CommandParameter="{Binding}" />
    </apexCommands:EventBindingCollection>
</apexCommands:EventBindings.EventBindings>
<Border.Background>
    <MultiBinding Converter="{StaticResource PlayingCardToBrushConverter}">
        <Binding Path="CardType" />
        <Binding Path="IsFaceDown" />
    </MultiBinding>
</Border.Background>

Essentially, a playing card is drawn as a border with a brush provided by the converter we just saw. The DragAndDrop.IsDraggable rears its ugly head here, making sure that cards can be dragged. The RightClickCommand is going to assume that somewhere up the visual tree, we have a ViewModel that derives from CardGameViewModel. We next have an event binding for the left mouse click.

Event Bindings and Mouse Up

Why not just use apexCommands:ExtendedCommands.LeftClickCommand rather than the more wordy Apex Event Binding? Well, in this case, we want left clicks of cards to be registered only when the mouse is released, otherwise it is going to play merry hell with the events used in drag and drop. EventBinding is funky, it'll route any event to a command, handling datacontexts, etc.

Finally, for the card, we have a kludge - the face images are too white, we need a border, but the back images are fine - so we only draw the border if we're face up.

XML
<Border.Style>
<Style TargetType="Border">
<Style.Triggers>
<DataTrigger Binding="{Binding IsFaceDown}" Value="True">
<Setter Property="BorderBrush" Value="#00ffffff" />
</DataTrigger>
<DataTrigger Binding="{Binding IsFaceDown}" Value="False">
<Setter Property="BorderBrush" Value="#ff666666" />
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
</Border>
</DataTemplate>

The next part of the resource dictionary defines how a CardStackControl is styled. This is a bit odd as it is essentially passing properties from itself to its layout panel, which is a CardStackPanel. CardStacks are a bit fruity and as they're not critical in understanding how the app works, they are detailed in Appendix 1. However, here's the XAML:

XML
<!-- The style for the card stack control. -->
<Style TargetType="{x:Type solitaireGames:CardStackControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type solitaireGames:CardStackControl}">
<Border Background="{TemplateBinding Background}"
 BorderBrush="{TemplateBinding BorderBrush}"
 BorderThickness="{TemplateBinding BorderThickness}">
<ItemsControl ItemsSource="{TemplateBinding ItemsSource}"
 apexDragAndDrop:DragAndDrop.IsDragSource="True"
 apexDragAndDrop:DragAndDrop.IsDropTarget="True"
 Background="Transparent">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<solitaireGames:CardStackPanel 
 FaceDownOffset="{Binding FaceDownOffset, RelativeSource=
   {RelativeSource AncestorType={x:Type solitaireGames:CardStackControl}}}"
 FaceUpOffset="{Binding FaceUpOffset, RelativeSource=
   {RelativeSource AncestorType={x:Type solitaireGames:CardStackControl}}}"
 OffsetMode="{Binding OffsetMode, RelativeSource=
   {RelativeSource AncestorType={x:Type solitaireGames:CardStackControl}}}"
 NValue="{Binding NValue, RelativeSource=
   {RelativeSource AncestorType={x:Type solitaireGames:CardStackControl}}}"
 Orientation="{Binding Orientation, RelativeSource=
   {RelativeSource AncestorType={x:Type solitaireGames:CardStackControl}}}" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

The next four styles are for consistent looking text, curved borders for stacks and runs, and rounded buttons that look nice on a green baize background.

XML
<!--The style for text etc in a game. -->
<Style x:Key="CasinoTextStyle" TargetType="TextBlock">
<Setter Property="Foreground" Value="#99FFFFFF" />
<Setter Property="FontSize" Value="16" />
</Style>

<!-- The style for a stack marker. -->
<Style x:Key="StackMarker" TargetType="Border">
<Setter Property="Padding" Value="10" />
<Setter Property="BorderThickness" Value="6" />
<Setter Property="CornerRadius" Value="15" />
<Setter Property="BorderBrush" Value="#33FFFFFF" />
<Setter Property="Margin" Value="8,10,40,60" />
</Style>
<!-- Style for a run marker. -->
<Style x:Key="RunMarker" TargetType="Border">
<Setter Property="Padding" Value="10" />
<Setter Property="BorderThickness" Value="6" />
<Setter Property="CornerRadius" Value="15" />
<Setter Property="BorderBrush">
<Setter.Value>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<GradientStop Color="#33FFFFFF" Offset="0" />
<GradientStop Color="#00FFFFFF" Offset="0.8" />
</LinearGradientBrush>
</Setter.Value>
</Setter>
<Setter Property="Margin" Value="8,10,40,40" />
</Style>
<!-- A nice clean style for a button. -->
<Style x:Key="CasinoButtonStyle" TargetType="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border 
   Padding="4" BorderThickness="2" 
   CornerRadius="15" BorderBrush="#66FFFFFF"
   Background="#11FFFFFF"
   Cursor="Hand">
<ContentPresenter 
   TextBlock.Foreground="#99FFFFFF"
   TextBlock.FontWeight="SemiBold"
   HorizontalAlignment="Center"
   Content="{TemplateBinding Content}"
   />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

We only data bind two enums in the whole app - the draw mode of Klondike and the Difficulty of Spider, so we finish the resource dictionary off with data providers for them:

XML
<!-- Data provider for DrawMode. -->
<ObjectDataProvider 
 MethodName="GetValues" 
 ObjectType="{x:Type sys:Enum}" x:Key="DrawModeValues">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="klondike:DrawMode" />
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>

<!-- Data provider for Difficulty. -->
<ObjectDataProvider 
   MethodName="GetValues" 
   ObjectType="{x:Type sys:Enum}" 
   x:Key="DifficultyValues">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="spider:Difficulty" />
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
</ResourceDictionary>

Step 5: Spider Solitaire

If I detail Spider Solitaire here, the article will be too long. The code is in the sample and is strikingly similar to the Klondike code - we have a View Model and a Ciew, a set of Tableaus, etc. If you can follow the Klondike code, the Spider code is fine. If you are building the project step-by-step with the article, you can add the SpiderSolitiare folder now and drag in the files from the download at the top of the page.

Step 6: The Casino

The Casino is going to be our home page or hub for all of the solitaire action. It'll hold the View Models for the two games and also some statistics. In fact, statistics are the next thing we'll work on. Let's create a View Model for some statistics. Add a file called GameStatistics.cs to SolitaireGames:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Apex.MVVM;

namespace SolitaireGames
{
    /// <summary>
    /// A set of general statistics for a game.
    /// </summary>
    public class GameStatistics : ViewModel
    {
        /// <summary>
        /// The game name property.
        /// </summary>
        private NotifyingProperty GameNameProperty =
          new NotifyingProperty("GameName", 
          typeof(string), default(string));

        /// <summary>
        /// Gets or sets the name of the game.
        /// </summary>
        /// <value>The name of the game.</value>
        public string GameName
        {
            get { return (string)GetValue(GameNameProperty); }
            set { SetValue(GameNameProperty, value); }
        }
        
        /// <summary>
        /// The games played property.
        /// </summary>
        private NotifyingProperty GamesPlayedProperty =
          new NotifyingProperty("GamesPlayed", 
          typeof(int), default(int));

        /// <summary>
        /// Gets or sets the games played.
        /// </summary>
        /// <value>The games played.</value>
        public int GamesPlayed
        {
            get { return (int)GetValue(GamesPlayedProperty); }
            set { SetValue(GamesPlayedProperty, value); }
        }

        /// <summary>
        /// The games won property.
        /// </summary>
        private NotifyingProperty GamesWonProperty =
          new NotifyingProperty("GamesWon", 
          typeof(int), default(int));

        /// <summary>
        /// Gets or sets the games won.
        /// </summary>
        /// <value>The games won.</value>
        public int GamesWon
        {
            get { return (int)GetValue(GamesWonProperty); }
            set { SetValue(GamesWonProperty, value); }
        }

        /// <summary>
        /// The games lost property.
        /// </summary>
        private NotifyingProperty GamesLostProperty =
          new NotifyingProperty("GamesLost", 
          typeof(int), default(int));

        /// <summary>
        /// Gets or sets the games lost.
        /// </summary>
        /// <value>The games lost.</value>
        public int GamesLost
        {
            get { return (int)GetValue(GamesLostProperty); }
            set { SetValue(GamesLostProperty, value); }
        }
        
        /// <summary>
        /// The highest winning streak property.
        /// </summary>
        private NotifyingProperty HighestWinningStreakProperty =
          new NotifyingProperty("HighestWinningStreak", 
          typeof(int), default(int));

        /// <summary>
        /// Gets or sets the highest winning streak.
        /// </summary>
        /// <value>The highest winning streak.</value>
        public int HighestWinningStreak
        {
            get { return (int)GetValue(HighestWinningStreakProperty); }
            set { SetValue(HighestWinningStreakProperty, value); }
        }

        /// <summary>
        /// The highest losing streak.
        /// </summary>
        private NotifyingProperty HighestLosingStreakProperty =
          new NotifyingProperty("HighestLosingStreak", 
          typeof(int), default(int));

        /// <summary>
        /// Gets or sets the highest losing streak.
        /// </summary>
        /// <value>The highest losing streak.</value>
        public int HighestLosingStreak
        {
            get { return (int)GetValue(HighestLosingStreakProperty); }
            set { SetValue(HighestLosingStreakProperty, value); }
        }
        
        /// <summary>
        /// The current streak.
        /// </summary>
        private NotifyingProperty CurrentStreakProperty =
          new NotifyingProperty("CurrentStreak", 
          typeof(int), default(int));

        /// <summary>
        /// Gets or sets the current streak.
        /// </summary>
        /// <value>The current streak.</value>
        public int CurrentStreak
        {
            get { return (int)GetValue(CurrentStreakProperty); }
            set { SetValue(CurrentStreakProperty, value); }
        }

        /// <summary>
        /// The cumulative score.
        /// </summary>
        private NotifyingProperty CumulativeScoreProperty =
          new NotifyingProperty("CumulativeScore", 
          typeof(int), default(int));

        /// <summary>
        /// Gets or sets the cumulative score.
        /// </summary>
        /// <value>The cumulative score.</value>
        public int CumulativeScore
        {
            get { return (int)GetValue(CumulativeScoreProperty); }
            set { SetValue(CumulativeScoreProperty, value); }
        }
        
        /// <summary>
        /// The highest score.
        /// </summary>
        private NotifyingProperty HighestScoreProperty =
          new NotifyingProperty("HighestScore", 
          typeof(int), default(int));

        /// <summary>
        /// Gets or sets the highest score.
        /// </summary>
        /// <value>The highest score.</value>
        public int HighestScore
        {
            get { return (int)GetValue(HighestScoreProperty); }
            set { SetValue(HighestScoreProperty, value); }
        }

        /// <summary>
        /// The average score.
        /// </summary>
        private NotifyingProperty AverageScoreProperty =
          new NotifyingProperty("AverageScore", 
          typeof(double), default(double));

        /// <summary>
        /// Gets or sets the average score.
        /// </summary>
        /// <value>The average score.</value>
        public double AverageScore
        {
            get { return (double)GetValue(AverageScoreProperty); }
            set { SetValue(AverageScoreProperty, value); }
        }

        /// <summary>
        /// The cumulative game time.
        /// </summary>
        private NotifyingProperty CumulativeGameTimeProperty =
          new NotifyingProperty("CumulativeGameTime", 
          typeof(double), default(double));

        /// <summary>
        /// Gets or sets the cumulative game time.
        /// </summary>
        /// <value>The cumulative game time.</value>
        public TimeSpan CumulativeGameTime
        {
            get { return TimeSpan.FromSeconds(
                   (double)GetValue(CumulativeGameTimeProperty)); }
            set { SetValue(CumulativeGameTimeProperty, value.TotalSeconds); }
        }
        
        /// <summary>
        /// The average game time.
        /// </summary>
        private NotifyingProperty AverageGameTimeProperty =
          new NotifyingProperty("AverageGameTime", 
          typeof(double), default(double));

        /// <summary>
        /// Gets or sets the average game time.
        /// </summary>
        /// <value>The average game time.</value>
        public TimeSpan AverageGameTime
        {
            get { return TimeSpan.FromSeconds(
                    (double)GetValue(AverageGameTimeProperty)); }
            set { SetValue(AverageGameTimeProperty, value.TotalSeconds); }
        }

        /// <summary>
        /// The reset command.
        /// </summary>
        private ViewModelCommand resetCommand;

        /// <summary>
        /// Gets the reset command.
        /// </summary>
        /// <value>The reset command.</value>
        public ViewModelCommand ResetCommand
        {
            get { return resetCommand; }
        }

The statistics View Model is mostly just a set of properties - but we also need a constructor to wire in the View Model command and the reset command.

Serializing TimeSpans

You may have noticed that although the notifying properties AverageGameTime and CumulativeGameTime are exposed as TimeSpans, they're stored and defined as doubles. This is because we will want to serialize this whole object, and TimeSpan objects do not serialize with XmlSerializer!

Below is the constructor and reset command:

C#
/// <summary>
/// Initializes a new instance of the <see cref="GameStatistics"> class.
/// </summary>
public GameStatistics()
{
    //  Create the reset command.
    resetCommand = new ViewModelCommand(DoReset, true);
}

/// <summary>
/// Resets the statistics.
/// </summary>
private void DoReset()
{
    GamesPlayed = 0;
    GamesWon = 0; 
    GamesLost = 0; 
    HighestWinningStreak = 0; 
    HighestLosingStreak = 0;
    CurrentStreak = 0; 
    CumulativeScore = 0; 
    HighestScore = 0; 
    AverageScore = 0; 
    CumulativeGameTime = TimeSpan.FromSeconds(0); 
    AverageGameTime = TimeSpan.FromSeconds(0);
}

The final and most important function is the UpdateStatistics function - this will update the statistics from a CardGameViewModel object:

C#
/// <summary>
/// Updates the statistics based on a won game.
/// </summary>
/// <param name="cardGame">The card game.</param>
public void UpdateStatistics(CardGameViewModel cardGame)
{
    //  Update the games won or lost.
    GamesPlayed++;
    if (cardGame.IsGameWon)
        GamesWon++;
    else
        GamesLost++;

    //  Update the current streak.
    if (cardGame.IsGameWon)
        CurrentStreak = CurrentStreak < 0 ? 1 : CurrentStreak + 1;
    else
        CurrentStreak = CurrentStreak > 0 ? -1 : CurrentStreak - 1;

    //  Update the highest streaks.
    if (CurrentStreak > HighestWinningStreak)
        HighestWinningStreak = CurrentStreak;
    else if (Math.Abs(CurrentStreak) > HighestLosingStreak)
        HighestLosingStreak = Math.Abs(CurrentStreak);

    //  Update the highest score.
    if (cardGame.Score > HighestScore)
        HighestScore = cardGame.Score;

    //  Update the average score. Only won games
    //  contribute to the running average.
    if (cardGame.IsGameWon)
    {
        CumulativeScore += cardGame.Score;
        AverageScore = CumulativeScore / GamesWon;
    }

    //  Update the average game time.
    CumulativeGameTime += cardGame.ElapsedTime;
    AverageGameTime = 
      TimeSpan.FromTicks(CumulativeGameTime.Ticks / (GamesWon + GamesLost));
}

That's the GameStatistics done. Now let's build the Casino View Model - this is going to be the big one that holds everything else. Add a folder to SolitaireGames called Casino. Now add a class called CasinoViewModel:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Apex.MVVM;
using SolitaireGames.KlondikeSolitaire;
using System.IO.IsolatedStorage;
using System.IO;
using System.Xml.Serialization;
using SolitaireGames.SpiderSolitaire;

namespace SolitaireGames.Casino
{
    /// <summary>
    /// The casino view model.
    /// </summary>
    public class CasinoViewModel : ViewModel
    {
        /// <summary>
        /// Initializes a new instance of the
        /// <see cref="CasinoViewModel"> class.
        /// </summary>
        public CasinoViewModel()
        {
            //  Create the commands.
            goToCasinoCommand = new ViewModelCommand(DoGoToCasino, true);
            goToKlondikeSolitaireCommand = 
              new ViewModelCommand(DoGoToKlondikeSolitaire, true);
            goToSpiderSolitaireCommand =
              new ViewModelCommand(DoGoToSpiderSolitaire, true);
            settingsCommand = new ViewModelCommand(DoSettingsCommand, true);
        }

        /// <summary>
        /// The go to casino command.
        /// </summary>
        private ViewModelCommand goToCasinoCommand;

        /// <summary>
        /// The go to klondike command.
        /// </summary>
        private ViewModelCommand goToKlondikeSolitaireCommand;

        /// <summary>
        /// The spider command.
        /// </summary>
        private ViewModelCommand goToSpiderSolitaireCommand;

        /// <summary>
        /// The settings command.
        /// </summary>
        private ViewModelCommand settingsCommand;

        /// <summary>
        /// Gets the go to casino command.
        /// </summary>
        /// <value>The go to casino command.</value>
        public ViewModelCommand GoToCasinoCommand
        {
            get { return goToCasinoCommand; }
        }

        /// <summary>
        /// Gets the go to klondike solitaire command.
        /// </summary>
        /// <value>The go to klondike solitaire command.</value>
        public ViewModelCommand GoToKlondikeSolitaireCommand
        {
            get { return goToKlondikeSolitaireCommand; }
        }

        /// <summary>
        /// Gets the go to spider solitaire command.
        /// </summary>
        /// <value>The go to spider solitaire command.</value>
        public ViewModelCommand GoToSpiderSolitaireCommand
        {
            get { return goToSpiderSolitaireCommand; }
        }

        /// <summary>
        /// Gets the settings command.
        /// </summary>
        /// <value>The settings command.</value>
        public ViewModelCommand SettingsCommand
        {
            get { return settingsCommand; }
        }
        
        /// <summary>
        /// Goes to the casino.
        /// </summary>
        private void DoGoToCasino()
        {
            KlondikeSolitaireViewModel.StopTimer();
            SpiderSolitaireViewModel.StopTimer();
        }

        /// <summary>
        /// Goes to spider.
        /// </summary>
        private void DoGoToSpiderSolitaire()
        {
            if(SpiderSolitaireViewModel.Moves > 0)
                SpiderSolitaireViewModel.StartTimer();
        }

        /// <summary>
        /// Goes to Klondike.
        /// </summary>
        private void DoGoToKlondikeSolitaire()
        {
            if(KlondikeSolitaireViewModel.Moves > 0)
                KlondikeSolitaireViewModel.StartTimer();
        }

        /// <summary>
        /// The settings command.
        /// </summary>
        private void DoSettingsCommand()
        {
        }

(Again, I'm describing this class in an order that makes it easier to break down, not in the order it is defined in the actual file). The first thing we have is four commands - one for each of the places we can navigate to.

When we move from place to place, we start or stop the appropriate game timers, but how do we actually move? Well, as the current screen is a View concern, we let the View listen out for the commands and handle them as it determines is appropriate - the ViewModel just does the data and logic side of things. So for example, the DoSettingsCommand actually does nothing - however, later on, we'll have a View listen for this command and change the current screen when it fires.

Next we have the Klondike and Spider View Models and statistics:

C#
/// <summary>
/// The Klondike stats.
/// </summary>
private NotifyingProperty KlondikeSolitaireStatisticsProperty =
  new NotifyingProperty("KlondikeSolitaireStatistics", typeof(GameStatistics),
      new GameStatistics() { GameName = "Klondike Solitaire" });

/// <summary>
/// Gets or sets the klondike solitaire statistics.
/// </summary>
/// <value>The klondike solitaire statistics.</value>
public GameStatistics KlondikeSolitaireStatistics
{
    get { return (GameStatistics)GetValue(KlondikeSolitaireStatisticsProperty); }
    set { SetValue(KlondikeSolitaireStatisticsProperty, value); }
}

/// <summary>
/// The spider stats.
/// </summary>
private NotifyingProperty SpiderSolitaireStatisticsProperty =
  new NotifyingProperty("SpiderSolitaireStatistics", typeof(GameStatistics),
      new GameStatistics() { GameName = "Spider Solitaire" });

/// <summary>
/// Gets or sets the spider solitaire statistics.
/// </summary>
/// <value>The spider solitaire statistics.</value>
public GameStatistics SpiderSolitaireStatistics
{
    get { return (GameStatistics)GetValue(SpiderSolitaireStatisticsProperty); }
    set { SetValue(SpiderSolitaireStatisticsProperty, value); }
}

/// <summary>
/// The Klondike view model.
/// </summary>
private NotifyingProperty KlondikeSolitaireViewModelProperty =
  new NotifyingProperty("KlondikeSolitaireViewModel", 
      typeof(KlondikeSolitaireViewModel), 
      new KlondikeSolitaireViewModel());

/// <summary>
/// Gets or sets the klondike solitaire view model.
/// </summary>
/// <value>The klondike solitaire view model.</value>
public KlondikeSolitaireViewModel KlondikeSolitaireViewModel
{
    get { return (KlondikeSolitaireViewModel)GetValue(
                  KlondikeSolitaireViewModelProperty); }
    set { SetValue(KlondikeSolitaireViewModelProperty, value); }
}

/// <summary>
/// The spider solitaire view model.
/// </summary>
private NotifyingProperty SpiderSolitaireViewModelProperty =
  new NotifyingProperty("SpiderSolitaireViewModel", 
  typeof(SpiderSolitaireViewModel), 
  new SpiderSolitaireViewModel());

/// <summary>
/// Gets or sets the spider solitaire view model.
/// </summary>
/// <value>The spider solitaire view model.</value>
public SpiderSolitaireViewModel SpiderSolitaireViewModel
{
    get { return (SpiderSolitaireViewModel)GetValue(
                  SpiderSolitaireViewModelProperty); }
    set { SetValue(SpiderSolitaireViewModelProperty, value); }
}

As we can see, CasinoViewModel holds the View Models for the games.

Nested ViewModels

In terms of Design Patterns, there are various opinions on how things like nested View Models should work. In an extensible design, we might have a list of games in the casino and use the Managed Extensible Framework to allow us to add games arbitrarily. However, in this case, we're just having the child View Models as properties - it's getting the job done in this case, but it might be a practice to look over carefully before using it in other projects.

We're going to allow the deck to be chosen via a combo box. You may recall that decks were added at the last minute, so this isn't too clean, but again, it works. I could have tidied it up, but I wanted to finally get this published:

C#
/// <summary>
/// The selected deck folder.
/// </summary>
private NotifyingProperty DeckFolderProperty =
  new NotifyingProperty("DeckFolder", typeof(string), "Classic");

/// <summary>
/// Gets or sets the deck folder.
/// </summary>
/// <value>The deck folder.</value>
public string DeckFolder
{
    get { return (string)GetValue(DeckFolderProperty); }
    set 
    { 
        SetValue(DeckFolderProperty, value);
        PlayingCardToBrushConverter.SetDeckFolder(value);
    }
}

/// <summary>
/// The set of available deck folders.
/// </summary>
private List<string> deckFolders = 
  new List<string>() { "Classic", "Hearts", "Seasons", "Large Print" };

/// <summary>
/// Gets the deck folders.
/// </summary>
/// <value>The deck folders.</value>
[XmlIgnore]
public List<string> DeckFolders
{
    get { return deckFolders; }
}

Now for MVVM experts, you may have not been too comfortable with nested View Models - the next section is even more cheeky:

C#
/// <summary>
/// Saves this instance.
/// </summary>
public void Save()
{
    // Get a new isolated store for this user, domain, and assembly.
    IsolatedStorageFile isoStore = 
        IsolatedStorageFile.GetStore(IsolatedStorageScope.User |
        IsolatedStorageScope.Domain | IsolatedStorageScope.Assembly, 
        null, null);

    //  Create data stream.
    using (IsolatedStorageFileStream isoStream =
        new IsolatedStorageFileStream("Casino.xml", 
        FileMode.Create, isoStore))
    {
        XmlSerializer casinoSerializer = 
          new XmlSerializer(typeof(CasinoViewModel));
        casinoSerializer.Serialize(isoStream, this);
    }
}

/// <summary>
/// Loads this instance.
/// </summary>
/// <returns>
public static CasinoViewModel Load()
{
    // Get a new isolated store for this user, domain, and assembly.
    IsolatedStorageFile isoStore = 
        IsolatedStorageFile.GetStore(IsolatedStorageScope.User |
        IsolatedStorageScope.Domain | IsolatedStorageScope.Assembly, 
        null, null);

    //  Create data stream.
    try
    {
        //  Save the casino.
        using (IsolatedStorageFileStream isoStream =
            new IsolatedStorageFileStream("Casino.xml", 
            FileMode.Open, isoStore))
        {
            XmlSerializer casinoSerializer = 
              new XmlSerializer(typeof(CasinoViewModel));
            return (CasinoViewModel)casinoSerializer.Deserialize(isoStream);
        }
    }
    catch
    {
    }

    return new CasinoViewModel();
}

Two functions - Save and Load. They allow us to persist the whole Casino.

Serialzing ViewModels

MVVM-wise, this shouldn't be done. A View Model is presentation logic - it is the bridge between the View and the Model - it is the Model that should be used to persist data. However, this gets the job done - it is a small application and we can adapt the pattern to our needs.

Understanding the rules of patterns such as MVVM is very important if you are going to break them like this - you must understand what limitations you are building in. In this case, the application isn't going to be built on over years with a high cost for customer change requests, etc., so we can use the pattern in the way it works for us - it doesn't need to be extendible with regards to its data storage mechanism.

As in the note before, you should think very carefully about doing something like this in a 'serious' application because it will cause problems if the structure of the View Model changes.

Once a Casino has been loaded or created, we need to wire in some events and so on, so let's give it an Initialise function to do one-off initialization:

C#
/// <summary>
/// Initialises this instance.
/// </summary>
public void Initialise()
{
    //  We're going to listen out for certain commands in the game
    //  so that we can keep track of scores etc.
    KlondikeSolitaireViewModel.DealNewGameCommand.Executed += 
      new CommandEventHandler(KlondikeDealNewGameCommand_Executed);
    KlondikeSolitaireViewModel.GameWon += 
      new Action(KlondikeSolitaireViewModel_GameWon);
    KlondikeSolitaireViewModel.GoToCasinoCommand.Executed += 
      new CommandEventHandler(GoToCasinoCommand_Executed);
    SpiderSolitaireViewModel.DealNewGameCommand.Executed += 
      new CommandEventHandler(SpiderDealNewGameCommand_Executed);
    SpiderSolitaireViewModel.GameWon += 
      new Action(SpiderSolitaireViewModel_GameWon);
    SpiderSolitaireViewModel.GoToCasinoCommand.Executed +=
      new CommandEventHandler(GoToCasinoCommand_Executed);

    //  Set the deck we're using for brushes.
    PlayingCardToBrushConverter.SetDeckFolder(DeckFolder);
}

What's going on here? Well, we're listening out for certain commands that are fired by the child View Models.

Apex Commands

An Apex Command fires two events - Executing before the command is executed, which allows the command to be cancelled, and Executed after the command is executed. We can use these events in Views or other View Models to know when ViewModelCommands have been fired.

What do we want to listen to these commands for? Well, really just to save (in case the game ends unexpectedly) and update stats:

C#
/// <summary>
/// Called when Klondike is won.
/// </summary>
void KlondikeSolitaireViewModel_GameWon()
{
    //  The game was won, update the stats.
    KlondikeSolitaireStatistics.UpdateStatistics(KlondikeSolitaireViewModel);
    Save();
}

/// <summary>
/// Called when Spider is won.
/// </summary>
void SpiderSolitaireViewModel_GameWon()
{
    //  The game was won, update the stats.
    SpiderSolitaireStatistics.UpdateStatistics(KlondikeSolitaireViewModel);
    Save();
}

/// <summary>
/// Handles the Executing event of the DealNewGameCommand control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="args">The <see
/// cref="Apex.MVVM.CommandEventArgs"> instance
/// containing the event data.</param>
void KlondikeDealNewGameCommand_Executed(object sender, CommandEventArgs args)
{
    //  If we've made any moves, update the stats.
    if (KlondikeSolitaireViewModel.Moves > 0)
        KlondikeSolitaireStatistics.UpdateStatistics(KlondikeSolitaireViewModel);
    Save();
}

/// <summary>
/// Handles the Executed event of the DealNewGameCommand control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="args">The <see
/// cref="Apex.MVVM.CommandEventArgs">
/// instance containing the event data.</param>
void SpiderDealNewGameCommand_Executed(object sender, CommandEventArgs args)
{
    //  If we've made any moves, update the stats.
    if (SpiderSolitaireViewModel.Moves > 0)
        SpiderSolitaireStatistics.UpdateStatistics(SpiderSolitaireViewModel);
    Save();
}

/// <summary>
/// Handles the Executed event of the GoToCasinoCommand control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="args">The <see
/// cref="Apex.MVVM.CommandEventArgs"> instance
/// containing the event data.</param>
void GoToCasinoCommand_Executed(object sender, CommandEventArgs args)
{
    GoToCasinoCommand.DoExecute(null);
}

That's it - the Casino View Model is done. We can now do the casino View. Add a UserControl called CasinoView to SolitaireGames:

XML
<UserControl x:Class="SolitaireGames.Casino.CasinoView"
 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
 xmlns:solitaireGames="clr-namespace:SolitaireGames"
 xmlns:local="clr-namespace:SolitaireGames.Casino"
 xmlns:apexControls="clr-namespace:Apex.Controls;assembly=Apex"
 xmlns:klondikeSolitaire="clr-namespace:SolitaireGames.KlondikeSolitaire"
 xmlns:spiderSolitaire="clr-namespace:SolitaireGames.SpiderSolitaire"
 xmlns:apexCommands="clr-namespace:Apex.Commands;assembly=Apex"
 mc:Ignorable="d" 
 x:Name="casinoViewModel"
 d:DesignHeight="300" d:DesignWidth="300"
 DataContext="{Binding CasinoViewModel, ElementName=casinoViewModel}">

<!-- The main resource dictionary. -->
<UserControl.Resources>
<ResourceDictionary 
  Source="/SolitaireGames;component/Resources/
          SolitaireGamesResourceDictionary.xaml" />
</UserControl.Resources>

So far so good, just the namespaces, datacontext, and dictionary.

XML
<!-- Main container. -->
<Grid>

<!-- Background image (the baize). -->
<Image Source="/SolitaireGames;component/Resources/Backgrounds/Background.jpg" 
          Stretch="Fill" />

<!-- The main pivot control. -->
<apexControls:PivotControl x:Name="mainPivot" ShowPivotHeadings="False">

Now we have a grid to hold everything, a baize image (which you can get from the sample project), and a pivot control which will hold the games, casino, and settings.

The Pivot Control

Apex contains a control called PivotControl which can hold a set of PivotItems. This is very similar to the pivot control seen in WP7 applications. If ShowPivotHeadings is set to true, we see the headings at the top of the control allowing us to move between items; however, in this app, we use buttons in other places to move around.

If anyone thinks the PivotControl is useful, then I will write it up in detail in a separate article.

Now for the first pivot item:

XML
<!-- Klondike Solitaire is the first pivot item. -->
<apexControls:PivotItem Title="Klondike">

<klondikeSolitaire:KlondikeSolitaireView 
 x:Name="klondikeSolitaireView"
 ViewModel="{Binding KlondikeSolitaireViewModel}" />

</apexControls:PivotItem>

The first pivot item is a KlondikeSolitaireView bound to the casino's Klondike Solitaire ViewModel.

XML
<!-- The casino is the next pivot item. -->
<apexControls:PivotItem Title="Casino" IsInitialPage="True" >
<!-- The main container for the casino. -->
<apexControls:ApexGrid Rows="Auto,Auto,*,Auto">
<!-- The title of the game. -->
<TextBlock 
 Grid.Row="0" FontSize="34" HorizontalAlignment="Center" 
 Foreground="#99FFFFFF" Text="Solitaire" />
<!-- The container for the statistics. -->
<apexControls:ApexGrid Grid.Row="1" 
      Rows="Auto" Columns="*,Auto,Auto,*">
<!-- Klondike/Spider Solitaire Statistics. -->
<local:StatisticsView
 Width="300"
 Grid.Column="1" Grid.Row="0" 
 GameStatistics="{Binding KlondikeSolitaireStatistics}"
 apexCommands:ExtendedCommands.LeftClickCommand=
   "{Binding GoToKlondikeSolitaireCommand}"
 Cursor="Hand" />
<local:StatisticsView
 Width="300"
 Grid.Column="2" Grid.Row="0" 
 GameStatistics="{Binding SpiderSolitaireStatistics}" 
 apexCommands:ExtendedCommands.LeftClickCommand=
   "{Binding GoToSpiderSolitaireCommand}"
 Cursor="Hand" />

</apexControls:ApexGrid>
<!-- The casino commands. -->
<apexControls:ApexGrid
   Grid.Row="4" Columns="*,Auto,Auto,Auto,*">

<Button 
   Grid.Column="1" 
   Style="{StaticResource CasinoButtonStyle}" Width="120" 
   Margin="4" Content="Play Klondike" 
   Command="{Binding GoToKlondikeSolitaireCommand}"  />
<Button 
   Grid.Column="2" Style="{StaticResource CasinoButtonStyle}" 
   Width="120" Margin="4" Content="Settings" 
   Command="{Binding SettingsCommand}"  />
<Button 
   Grid.Column="3" Style="{StaticResource CasinoButtonStyle}" 
   Width="120" Margin="4" Content="Play Spider" 
   Command="{Binding GoToSpiderSolitaireCommand}"  />
</apexControls:ApexGrid>

</apexControls:ApexGrid>
</apexControls:PivotItem>

The next pivot item is the casino itself. It shows a title, two StatisticsViews (which we'll see later), and some commands to go from one page to another. Now we have Spider Solitaire:

XML
<!-- The spider solitaire pivot item. -->
<apexControls:PivotItem Title="Spider">
<spiderSolitaire:SpiderSolitaireView
x:Name="spiderSolitaireView"
ViewModel="{Binding SpiderSolitaireViewModel}" /> 
</apexControls:PivotItem>

Again fairly straightforward. The last pivot item is a settings page, where we bind to things like the Klondike draw mode or casino deck style:

XML
<!-- The settings pivot item.  -->
<apexControls:PivotItem Title="Settings">

<!-- The main container for the settings. -->
<apexControls:ApexGrid Rows="Auto,Auto,*,Auto">

<!-- The title. -->
<TextBlock 
   Grid.Row="0" FontSize="34" HorizontalAlignment="Center" 
   Foreground="#99FFFFFF" Text="Settings" />

<!-- The container for the statistics. -->
<apexControls:PaddedGrid Grid.Row="1" Columns="*,*" 
   Width="500" Padding="4"
   Rows="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto">
<TextBlock 
   Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2"
   Text="Solitaire" FontWeight="Bold" 
   HorizontalAlignment="Center" 
   Style="{StaticResource CasinoTextStyle}" />

<TextBlock 
   Grid.Row="1" Grid.Column="0" Text="Deck Style"
   HorizontalAlignment="Right" 
   Style="{StaticResource CasinoTextStyle}" />

<ComboBox
   Grid.Row="1" Grid.Column="1" 
   SelectedItem="{Binding DeckFolder}"
   ItemsSource="{Binding DeckFolders}" />

<TextBlock
   Grid.Row="2" Grid.Column="1" 
   Style="{StaticResource CasinoTextStyle}"
   FontSize="12" Text="Requires Restart" />

<TextBlock 
   Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="2"
   Text="Klondike Solitaire" FontWeight="Bold" 
   HorizontalAlignment="Center" 
   Style="{StaticResource CasinoTextStyle}" />

<TextBlock 
   Grid.Row="4" Grid.Column="0" Text="Draw Mode"
   HorizontalAlignment="Right" 
   Style="{StaticResource CasinoTextStyle}" />

<ComboBox
   Grid.Row="4" Grid.Column="1" 
   SelectedItem="{Binding KlondikeSolitaireViewModel.DrawMode}"
   ItemsSource="{Binding Source={StaticResource DrawModeValues}}" />

<TextBlock 
   Grid.Row="5" Grid.Column="0" Text="Statistics"
   HorizontalAlignment="Right" 
   Style="{StaticResource CasinoTextStyle}" />

<Button
   Grid.Row="5" Grid.Column="1" 
   Content="Reset" HorizontalAlignment="Left"
   Width="80" Style="{StaticResource CasinoButtonStyle}" 
   Command="{Binding KlondikeSolitaireStatistics.ResetCommand}" />

<TextBlock 
   Grid.Row="6" Grid.Column="0" Grid.ColumnSpan="2"
   Text="Spider Solitaire" FontWeight="Bold" 
   HorizontalAlignment="Center" 
   Style="{StaticResource CasinoTextStyle}" />

<TextBlock 
   Grid.Row="7" Grid.Column="0" Text="Difficulty"
   HorizontalAlignment="Right" 
   Style="{StaticResource CasinoTextStyle}" />

<ComboBox
   Grid.Row="7" Grid.Column="1" 
   SelectedItem="{Binding SpiderSolitaireViewModel.Difficulty}"
   ItemsSource="{Binding Source={StaticResource DifficultyValues}}" />

<TextBlock 
   Grid.Row="8" Grid.Column="0" Text="Statistics"
   HorizontalAlignment="Right" 
   Style="{StaticResource CasinoTextStyle}" />

<Button
   Grid.Row="8" Grid.Column="1" 
   Content="Reset" HorizontalAlignment="Left"
   Width="80" Style="{StaticResource CasinoButtonStyle}" 
   Command="{Binding SpiderSolitaireStatistics.ResetCommand}" />

</apexControls:PaddedGrid>

<Button 
   Grid.Row="3" Style="{StaticResource CasinoButtonStyle}" 
   Width="120" Margin="4" Content="Casino" 
   Command="{Binding GoToCasinoCommand}"  />

</apexControls:ApexGrid>
</apexControls:PivotItem>
</apexControls:PivotControl>
</Grid>
</UserControl>

And that's it for the View. Now for the code-behind:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace SolitaireGames.Casino
{
    /// <summary>
    /// Interaction logic for CasinoView.xaml
    /// </summary>
    public partial class CasinoView : UserControl
    {
        /// <summary>
        /// Initializes a new instance of the CasinoView class.
        /// </summary>
        public CasinoView()
        {
            InitializeComponent();
        }

        /// <summary>
        /// Handles the Executed event
        /// of the GoToKlondikeSolitaireCommand control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="args">The Apex.MVVM.CommandEventArgs
        /// instance containing the event data.</param>
        void GoToKlondikeSolitaireCommand_Executed(object sender, 
             Apex.MVVM.CommandEventArgs args)
        {
            mainPivot.SelectedPivotItem = mainPivot.PivotItems[0];
        }

        /// <summary>
        /// Handles the Executed event
        /// of the GoToSpiderSolitaireCommsand control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="args">The Apex.MVVM.CommandEventArgs
        ///    instance containing the event data.</param>
        void GoToSpiderSolitaireCommsand_Executed(object sender, 
             Apex.MVVM.CommandEventArgs args)
        {
            mainPivot.SelectedPivotItem = mainPivot.PivotItems[2];
        }

        /// <summary>
        /// Handles the Executed event of the GoToCasinoCommand control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="args">The <see
        ///   cref="Apex.MVVM.CommandEventArgs">
        ///   instance containing the event data.</param>
        void GoToCasinoCommand_Executed(object sender, 
             Apex.MVVM.CommandEventArgs args)
        {
            mainPivot.SelectedPivotItem = mainPivot.PivotItems[1];
        }

        /// <summary>
        /// Handles the Executed event of the SettingsCommand control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="args">The <see
        ///    cref="Apex.MVVM.CommandEventArgs">
        ///    instance containing the event data.</param>
        private void SettingsCommand_Executed(object sender, 
                     Apex.MVVM.CommandEventArgs args)
        {
            mainPivot.SelectedPivotItem = mainPivot.PivotItems[3];
        }

        /// <summary>
        /// CasinoViewModel dependency property.
        /// </summary>
        private static readonly DependencyProperty CasinoViewModelProperty =
          DependencyProperty.Register("CasinoViewModel", 
          typeof(CasinoViewModel), typeof(CasinoView),
          new PropertyMetadata(null, 
          new PropertyChangedCallback(OnCasinoViewModelChanged)));

        /// <summary>
        /// Gets or sets the casino view model.
        /// </summary>
        /// <value>The casino view model.</value>
        public CasinoViewModel CasinoViewModel
        {
            get { return (CasinoViewModel)GetValue(CasinoViewModelProperty); }
            set { SetValue(CasinoViewModelProperty, value); }
        }

        /// <summary>
        /// Called when casino view model changed.
        /// </summary>
        /// <param name="o">The o.</param>
        /// <param name="args">The
        /// System.Windows.DependencyPropertyChangedEventArgs
        /// instance containing the event data.</param>
        private static void OnCasinoViewModelChanged(DependencyObject o, 
                DependencyPropertyChangedEventArgs args)
        {
            CasinoView me = o as CasinoView;

            //  Listen for events.
            me.CasinoViewModel.GoToCasinoCommand.Executed += 
              new Apex.MVVM.CommandEventHandler(me.GoToCasinoCommand_Executed);
            me.CasinoViewModel.GoToSpiderSolitaireCommand.Executed += 
              new Apex.MVVM.CommandEventHandler(
              me.GoToSpiderSolitaireCommsand_Executed);
            me.CasinoViewModel.GoToKlondikeSolitaireCommand.Executed += 
              new Apex.MVVM.CommandEventHandler(
              me.GoToKlondikeSolitaireCommand_Executed);
            me.CasinoViewModel.SettingsCommand.Executed += 
              new Apex.MVVM.CommandEventHandler(me.SettingsCommand_Executed);
        }                
    }
}

There's very little to this - the CasinoViewModel is a dependency property. When it is set, we listen for the 'GoTo...' commands, and when they're fired, just move the pivot control selection to the correct item.

The last thing for the casino is the StatisticsView, which is little usercontrol that shows the details of a StatisticsViewModel. Add a UserControl called StatisticsView to the Casino folder, here's the code-behind:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace SolitaireGames.Casino
{
    /// <summary>
    /// Interaction logic for StatisticsView.xaml
    /// </summary>
    public partial class StatisticsView : UserControl
    {
        /// <summary>
        /// Initializes a new instance of the StatisticsView class.
        /// </summary>
        public StatisticsView()
        {
            InitializeComponent();
        }
        
        /// <summary>
        /// The statistics view model.
        /// </summary>
        private static readonly DependencyProperty GameStatisticsProperty =
          DependencyProperty.Register("GameStatistics", 
          typeof(GameStatistics), typeof(StatisticsView),
          new PropertyMetadata(null));

        /// <summary>
        /// Gets or sets the game statistics.
        /// </summary>
        /// <value>The game statistics.</value>
        public GameStatistics GameStatistics
        {
            get { return (GameStatistics)GetValue(GameStatisticsProperty); }
            set { SetValue(GameStatisticsProperty, value); }
        }    
    }
}

Just one dependency property - the statistics View Model (which allows us to bind the ViewModel to the CasinoViewModel child items). And finally the XAML:

XML
<UserControl x:Class="SolitaireGames.Casino.StatisticsView"
 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
 xmlns:apexControls="clr-namespace:Apex.Controls;assembly=Apex"
 mc:Ignorable="d" 
 d:DesignHeight="300" d:DesignWidth="300"
 x:Name="statisticsView">

<!-- Point to the main resource dictionary for the assembly. -->
<UserControl.Resources>
<ResourceDictionary 
  Source="/SolitaireGames;component/Resources/
          SolitaireGamesResourceDictionary.xaml" />
</UserControl.Resources>

<Border Padding="10" Margin="10" 
 BorderBrush="#99FFFFFF" BorderThickness="6" CornerRadius="15"
 DataContext="{Binding GameStatistics, ElementName=statisticsView}">

<apexControls:PaddedGrid 
   Rows="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto" 
   Columns="3*,*" 
   TextBlock.Foreground="#99FFFFFF" 
   TextBlock.FontSize="13">

<!-- The Game Title. -->
<TextBlock 
   Grid.Row="0" Grid.Column="0" 
   Grid.ColumnSpan="2" HorizontalAlignment="Center"
   Text="{Binding GameName}" FontSize="24" />

<!-- The game stats. -->
<TextBlock Grid.Row="1" Grid.Column="0" Text="Games Played" />
<TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding GamesPlayed}" />
<TextBlock Grid.Row="2" Grid.Column="0" Text="Games Won" />
<TextBlock Grid.Row="2" Grid.Column="1" Text="{Binding GamesWon}" />
<TextBlock Grid.Row="3" Grid.Column="0" Text="Games Lost" />
<TextBlock Grid.Row="3" Grid.Column="1" Text="{Binding GamesLost}" />
<TextBlock Grid.Row="4" Grid.Column="0" Text="Current Streak" />
<TextBlock Grid.Row="4" Grid.Column="1" Text="{Binding CurrentStreak}" />
<TextBlock Grid.Row="5" Grid.Column="0" Text="Highest Winning Streak" />
<TextBlock Grid.Row="5" Grid.Column="1" Text="{Binding HighestWinningStreak}" />
<TextBlock Grid.Row="6" Grid.Column="0" Text="Highest Losing Streak" />
<TextBlock Grid.Row="6" Grid.Column="1" Text="{Binding HighestLosingStreak}" />
<TextBlock Grid.Row="7" Grid.Column="0" Text="Highest Score" />
<TextBlock Grid.Row="7" Grid.Column="1" Text="{Binding HighestScore}" />
<TextBlock Grid.Row="8" Grid.Column="0" Text="Cumulative Score" />
<TextBlock Grid.Row="8" Grid.Column="1" Text="{Binding CumulativeScore}" />
<TextBlock Grid.Row="9" Grid.Column="0" Text="Average Score" />
<TextBlock Grid.Row="9" Grid.Column="1" Text="{Binding AverageScore}" />
<TextBlock Grid.Row="10" Grid.Column="0" Text="Cumulative Time" />
<TextBlock Grid.Row="10" Grid.Column="1" 
  Text="{Binding CumulativeGameTime, 
        Converter={StaticResource TimeSpanToShortStringConverter}}" />
<TextBlock Grid.Row="11" Grid.Column="0" Text="Average Time" />
<TextBlock Grid.Row="11" Grid.Column="1" 
   Text="{Binding AverageGameTime, Converter={StaticResource 
         TimeSpanToShortStringConverter}}" />
</apexControls:PaddedGrid>
</Border> 
</UserControl>

As we can see, this control just shows game stats in a rounded border.

Step 7: The Solitaire Application

Now go to the Solitaire project and open MainWindow.xaml, make sure you have a reference to Apex and SolitaireGames, and add a CasinoView as below:

XML
<Window x:Class="Solitaire.MainWindow"
 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 xmlns:casino="clr-namespace:SolitaireGames.Casino;assembly=SolitaireGames"
 Title="Solitaire" Height="400" Width="650" 
 Icon="/Solitaire;component/Solitaire.ico">

<!-- All we need is a casino view. -->
<casino:CasinoView x:Name="casinoView" />

</Window>

And the code-behind just loads the ViewModel (and saves it when we close):

C#
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        casinoView.CasinoViewModel = CasinoViewModel.Load();
        casinoView.CasinoViewModel.Initialise();

        Closing += 
         new System.ComponentModel.CancelEventHandler(MainWindow_Closing);
    }

    void MainWindow_Closing(object sender, 
         System.ComponentModel.CancelEventArgs e)
    {
        casinoView.CasinoViewModel.Save();
    }
}

The final thing we've added is an icon called Solitaire.ico, which is provided in the download.

Hit F5 and enjoy!

Appendix 1: The Card Stack Control

The card stack control is a rather complicated beast so I've included it as an appendix. It is an items control that has a set of properties that are passed to a card stack panel for layout. Here is the card stack panel:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Controls;
using System.Windows;

namespace SolitaireGames
{
    /// <summary>
    /// The offset mode - how we offset individual cards in a stack.
    /// </summary>
    public enum OffsetMode
    {
        /// <summary>
        /// Offset every card.
        /// </summary>
        EveryCard,

        /// <summary>
        /// Offset every Nth card.
        /// </summary>
        EveryNthCard,

        /// <summary>
        /// Offset only the top N cards.
        /// </summary>
        TopNCards,

        /// <summary>
        /// Offset only the bottom N cards.
        /// </summary>
        BottomNCards,

        /// <summary>
        /// Use the offset values specified in the playing card class (see
        /// PlayingCard.FaceDownOffset and PlayingCard.FaceUpOffset).
        /// </summary>
        UseCardValues
    }

    /// <summary>
    /// A panel for laying out cards.
    /// </summary>
    public class CardStackPanel : StackPanel
    {
        /// <summary>
        /// Infinite size, useful later.
        /// </summary>
        private readonly Size infiniteSpace = 
                new Size(Double.MaxValue, Double.MaxValue);

        /// <summary>
        /// Measures the child elements
        /// of a System.Windows.Controls.StackPanel
        /// in anticipation of arranging them during the
        /// System.Windows.Controls.StackPanel.
        //     ArrangeOverride(System.Windows.Size pass.
        /// </summary>
        /// <param name="constraint">An upper limit
        /// System.Windows.Size that should not be exceeded.</param>
        /// <returns>
        /// The System.Windows.Size that represents
        /// the desired size of the element.
        /// </returns>
        protected override Size MeasureOverride(Size constraint)
        {
            //  Keep track of the overall size required.
            Size resultSize = new Size(0, 0);

            //  Get the offsets that each element will need.
            List<size> offsets = CalculateOffsets();

            //  Calculate the total.
            double totalX = (from o in offsets select o.Width).Sum();
            double totalY = (from o in offsets select o.Height).Sum();

            //  Measure each child (always needed, even if we don't use
            //  the measurement!)
            foreach(UIElement child in Children)
            {
                //  Measure the child against infinite space.
                child.Measure(infiniteSpace);
            }

            //  Add the size of the last element.
            if (LastChild != null)
            {
                //  Add the size.
                totalX += LastChild.DesiredSize.Width;
                totalY += LastChild.DesiredSize.Height;
            }
                        
            return new Size(totalX, totalY);
        }

        /// <summary>
        /// When overridden in a derived class, positions child
        /// elements and determines a size for
        /// a System.Windows.FrameworkElement derived class.
        /// </summary>
        /// <param name="finalSize">The final
        /// area within the parent that this element should
        /// use to arrange itself and its children.</param>
        /// <returns>The actual size used.</returns>
        protected override Size ArrangeOverride(Size finalSize)
        {
            double x = 0, y = 0;
            int n = 0;
            int total = Children.Count;

            //  Get the offsets that each element will need.
            List<size> offsets = CalculateOffsets();
            
            //  If we're going to pass the bounds, deal with it.
            if ((ActualWidth > 0 && finalSize.Width > ActualWidth) || 
                (ActualHeight > 0 && finalSize.Height > ActualHeight))
            {
                //  Work out the amount we have to remove from the offsets.
                double overrunX = finalSize.Width - ActualWidth;
                double overrunY = finalSize.Height - ActualHeight;

                //  Now as a per-offset.
                double dx = overrunX / offsets.Count;
                double dy = overrunY / offsets.Count;

                //  Now nudge each offset.
                for (int i = 0; i < offsets.Count; i++)
                {
                    offsets[i] = new Size(Math.Max(0, offsets[i].Width - dx), 
                                 Math.Max(0, offsets[i].Height - dy));
                }

                //  Make sure the final size isn't increased past what we can handle.
                finalSize.Width -= overrunX;
                finalSize.Height -= overrunY;
            }

            //  Arrange each child.
            foreach (UIElement child in Children)
            {
                //  Get the card. If we don't have one, skip.
                PlayingCard card = ((FrameworkElement)child).DataContext as PlayingCard;
                if (card == null)
                    continue;

                //  Arrange the child at x,y (the first will be at 0,0)
                child.Arrange(new Rect(x, y, child.DesiredSize.Width, 
                              child.DesiredSize.Height));

                //  Update the offset.
                x += offsets[n].Width;
                y += offsets[n].Height;

                //  Increment.
                n++;
            }

            return finalSize;
        }

        /// <summary>
        /// Calculates the offsets.
        /// </summary>
        /// <returns>
        private List<size> CalculateOffsets()
        {
            //  Calculate the offsets on a card by card basis.
            List<size> offsets = new List<size>();

            int n = 0;
            int total = Children.Count;

            //  Go through each card.
            foreach (UIElement child in Children)
            {
                //  Get the card. If we don't have one, skip.
                PlayingCard card = ((FrameworkElement)child).DataContext as PlayingCard;
                if (card == null)
                    continue;

                //  The amount we'll offset by.
                double faceDownOffset = 0;
                double faceUpOffset = 0;

                //  We are now going to offset only if the offset mode is appropriate.
                switch (OffsetMode)
                {
                    case OffsetMode.EveryCard:
                        //  Offset every card.
                        faceDownOffset = FaceDownOffset;
                        faceUpOffset = FaceUpOffset;
                        break;
                    case OffsetMode.EveryNthCard:
                        //  Offset only if n Mod N is zero.
                        if (((n + 1) % NValue) == 0)
                        {
                            faceDownOffset = FaceDownOffset;
                            faceUpOffset = FaceUpOffset;
                        }
                        break;
                    case OffsetMode.TopNCards:
                        //  Offset only if (Total - N) <= n < Total
                        if ((total - NValue) <= n && n < total)
                        {
                            faceDownOffset = FaceDownOffset;
                            faceUpOffset = FaceUpOffset;
                        }
                        break;
                    case OffsetMode.BottomNCards:
                        //  Offset only if 0 < n < N
                        if (n < NValue)
                        {
                            faceDownOffset = FaceDownOffset;
                            faceUpOffset = FaceUpOffset;
                        }
                        break;
                    case SolitaireGames.OffsetMode.UseCardValues:
                        //  Offset each time by the amount specified in the card object.
                        faceDownOffset = card.FaceDownOffset;
                        faceUpOffset = card.FaceUpOffset;
                        break;
                    default:
                        break;
                }

                n++;

                //  Create the offset as a size.
                Size offset = new Size(0, 0);
                
                //  Offset.
                switch (Orientation)
                {
                    case Orientation.Horizontal:
                        offset.Width = card.IsFaceDown ? faceDownOffset : faceUpOffset;
                        break;
                    case Orientation.Vertical:
                        offset.Height = card.IsFaceDown ? faceDownOffset : faceUpOffset;
                        break;
                    default:
                        break;
                }

                //  Add to the list.
                offsets.Add(offset);
            }

            return offsets;
        }

        /// <summary>
        /// Gets the last child.
        /// </summary>
        /// <value>The last child.</value>
        private UIElement LastChild
        {
            get { return Children.Count > 0 ? Children[Children.Count - 1] : null; }
        }

        /// <summary>
        /// Face down offset.
        /// </summary>
        private static readonly DependencyProperty FaceDownOffsetProperty =
          DependencyProperty.Register("FaceDownOffset", 
          typeof(double), typeof(CardStackPanel),
          new FrameworkPropertyMetadata(5.0, 
          FrameworkPropertyMetadataOptions.AffectsMeasure | 
          FrameworkPropertyMetadataOptions.AffectsArrange));

        /// <summary>
        /// Gets or sets the face down offset.
        /// </summary>
        /// <value>The face down offset.</value>
        public double FaceDownOffset
        {
            get { return (double)GetValue(FaceDownOffsetProperty); }
            set { SetValue(FaceDownOffsetProperty, value); }
        }

        /// <summary>
        /// Face up offset.
        /// </summary>
        private static readonly DependencyProperty FaceUpOffsetProperty =
          DependencyProperty.Register("FaceUpOffset", 
          typeof(double), typeof(CardStackPanel),
          new FrameworkPropertyMetadata(5.0, 
          FrameworkPropertyMetadataOptions.AffectsMeasure | 
          FrameworkPropertyMetadataOptions.AffectsArrange));

        /// <summary>
        /// Gets or sets the face up offset.
        /// </summary>
        /// <value>The face up offset.</value>
        public double FaceUpOffset
        {
            get { return (double)GetValue(FaceUpOffsetProperty); }
            set { SetValue(FaceUpOffsetProperty, value); }
        }

        /// <summary>
        /// The offset mode.
        /// </summary>
        private static readonly DependencyProperty OffsetModeProperty =
          DependencyProperty.Register("OffsetMode", 
          typeof(OffsetMode), typeof(CardStackPanel),
          new PropertyMetadata(OffsetMode.EveryCard));

        /// <summary>
        /// Gets or sets the offset mode.
        /// </summary>
        /// <value>The offset mode.</value>
        public OffsetMode OffsetMode
        {
            get { return (OffsetMode)GetValue(OffsetModeProperty); }
            set { SetValue(OffsetModeProperty, value); }
        }

        /// <summary>
        /// The NValue, used for some modes.
        /// </summary>
        private static readonly DependencyProperty NValueProperty =
          DependencyProperty.Register("NValue", 
          typeof(int), typeof(CardStackPanel),
          new PropertyMetadata(1));

        /// <summary>
        /// Gets or sets the N value.
        /// </summary>
        /// <value>The N value.</value>
        public int NValue
        {
            get { return (int)GetValue(NValueProperty); }
            set { SetValue(NValueProperty, value); }
        }
    }
}

The CardStackControl simply duplicates the key properties and passes them to its child CardStackPanel via the template in the SolitaireGamesResourcesDictionary.xaml file. The idea behind the card stack panel is that it can do just about anything we need for laying out cards. It also will make sure stacks are 'squeezed' if needs be into the available space.

Appendix 2: Drag and Drop

Drag and drop in Apex is a full topic on its own; if anyone feels the approach used would be useful in other applications, then please let me know and I'll write it up as a full article.

Solitaire and Augmented Reality

I got a very interesting message from Rupam Das (http://www.codeproject.com/script/Membership/View.aspx?mid=8114613), who has made an augmented reality version of the project! In his application, you use your webcam to play the game physically by picking up cards with gestures. Other gestures, like thumbs up and thumbs down are bound to commands in the game – here’s a screenshot:

Image 4 

The project is called GesCard. Check out the YouTube video with the link here https://www.youtube.com/watch?v=wCOjuPdBooI. Thanks to rupam for getting in touch and sharing this very cool code!

Final Thoughts

There are many things that could be done to improve this application:

  • Animation 
  • Sounds
  • Different scoring models

These are just a few, but it came to the point where it was getting too big to write up as a sensible article so I upload it now; feel free to play with it, change it, and suggest improvements.

There may well be bugs as I've transcribed the whole project from one solution to another in the order I've written the article to make sure everything is detailed properly, please let me know if you find any!

I've written this entire article with a broken ulna, so apologies for spelling mistakes or other oddities!

Apex

Apex in its most basic form is written up at apex.aspx but the latest version is available at: http://apex.codeplex.com/.

License

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


Written By
Software Developer
United Kingdom United Kingdom
Follow my blog at www.dwmkerr.com and find out about my charity at www.childrenshomesnepal.org.

Comments and Discussions

 
AnswerRe: Selecting a card with click instead of drag? Pin
Dave Kerr26-Feb-13 20:34
mentorDave Kerr26-Feb-13 20:34 
GeneralMy vote of 5 Pin
Member 37773275-Dec-12 6:17
Member 37773275-Dec-12 6:17 
GeneralRe: My vote of 5 Pin
Dave Kerr10-Dec-12 11:37
mentorDave Kerr10-Dec-12 11:37 
QuestionCards blur when you start to drag them and remain blurry until dropped Pin
Member 37773275-Dec-12 6:15
Member 37773275-Dec-12 6:15 
AnswerRe: Cards blur when you start to drag them and remain blurry until dropped Pin
Dave Kerr10-Dec-12 11:54
mentorDave Kerr10-Dec-12 11:54 
GeneralRe: Cards blur when you start to drag them and remain blurry until dropped Pin
Member 377732710-Dec-12 12:25
Member 377732710-Dec-12 12:25 
GeneralRe: Cards blur when you start to drag them and remain blurry until dropped Pin
Dave Kerr10-Dec-12 23:50
mentorDave Kerr10-Dec-12 23:50 
GeneralMy vote of 5 Pin
Sperneder Patrick22-Oct-12 23:00
professionalSperneder Patrick22-Oct-12 23:00 
awesome!
GeneralRe: My vote of 5 Pin
Dave Kerr3-Nov-12 23:04
mentorDave Kerr3-Nov-12 23:04 
Questionwell done Pin
vasim sajad22-Aug-12 2:41
vasim sajad22-Aug-12 2:41 
AnswerRe: well done Pin
Dave Kerr22-Aug-12 2:53
mentorDave Kerr22-Aug-12 2:53 
GeneralMy vote of 5 Pin
Member 92297999-Jul-12 5:29
Member 92297999-Jul-12 5:29 
GeneralRe: My vote of 5 Pin
Dave Kerr9-Jul-12 6:44
mentorDave Kerr9-Jul-12 6:44 
QuestionAbout the executable Pin
Member 92297999-Jul-12 5:27
Member 92297999-Jul-12 5:27 
AnswerRe: About the executable Pin
Dave Kerr9-Jul-12 6:43
mentorDave Kerr9-Jul-12 6:43 
GeneralRe: About the executable Pin
Member 92297999-Jul-12 20:17
Member 92297999-Jul-12 20:17 
QuestionQuestion about game Pin
koraVoda13-May-12 8:48
koraVoda13-May-12 8:48 
AnswerRe: Question about game Pin
Dave Kerr16-May-12 3:09
mentorDave Kerr16-May-12 3:09 
GeneralMy vote of 5 Pin
Gabriel RB20-Oct-11 6:13
Gabriel RB20-Oct-11 6:13 
GeneralRe: My vote of 5 Pin
Dave Kerr23-Feb-12 1:04
mentorDave Kerr23-Feb-12 1:04 
GeneralMy vote of 5 Pin
Mohammad A Rahman14-Oct-11 16:43
Mohammad A Rahman14-Oct-11 16:43 
GeneralRe: My vote of 5 Pin
Dave Kerr23-Feb-12 1:03
mentorDave Kerr23-Feb-12 1:03 
GeneralMy vote of 5 Pin
linuxjr14-Oct-11 5:01
professionallinuxjr14-Oct-11 5:01 
GeneralRe: My vote of 5 Pin
Dave Kerr23-Feb-12 1:03
mentorDave Kerr23-Feb-12 1:03 
GeneralMy vote of 5 Pin
mbcrump6-Oct-11 15:42
mentormbcrump6-Oct-11 15:42 

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.