Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WPF

The Never Ending Memory Game

4.67/5 (4 votes)
29 Apr 2010CPOL8 min read 38K   1.4K  
Learn how to write a small game using an XBap
Image 1

Introduction

Earlier in 2009, Microsoft held a programming competition to get folks interested in WPF/XAML technology. The challenge was simple: create something mind-blowing with just 10k of code. The program had to utilize WPF in one of the following formats: ClickOnce, Silverlight or XBap. I'd been reading up on XBaps and thought the paradigm sounded pretty cool, so I chose to attempt to create a small XBap app for the contest.

Background

If the reader is unaware, XBap stands for XAML Browser Application. It’s the next great leap in ClickOnce deployment; the user simply clicks on a hyperlink hosted in a web page to launch the application. This link points to a .xbap that’s been deployed to the web server that hosts the application. This is exactly how ClickOnce deployments occur, but with XBaps, the client browser downloads the app and launches it within the browser space.

Silverlight apps are also hosted within the browser. While both XBaps and Silverlight apps run in a sandbox, XBaps provide the full WPF programming model while Silverlight only can use a subset of the .NET Framework. Here’s a brief table to compare the two:

XBap Silverlight
Programming Full .NET/WPF Framework Subset of framework and WPF
Client machine Requires full .NET Framework No framework installation required, only need the Silverlight client (~5MB download)
Platform Client machine must be Windows machine or emulated Windows Can run on any platform that supports the Silverlight client (Win, MacOS, soon Linux)
Browser Can run in Internet Explorer or Firefox Any browser that can support the Silverlight client
Client Software required .NET 2.0/3.5 Redistributable Package (~30 MB) Silverlight Runtime (~5 MB)
Sandbox Both run in a sandbox; i.e. protect the client machine from local disk access, registry access, etc. Both can also consume and utilize services (XBaps must use server of origin)

The Game

The idea I came up with for the contest was to create a memory matching game. It’s a simple game where the user “flips” a card on the board and after flipping a second card, checks to see if the user chose a matching card. If not, the cards are flipped again to look for another turn. The trick is to memorize the missed matches and find the matching card elsewhere if the user can remember where the original match is.

The fun twist to this game is that it’s theoretically never-ending. The original board starts with 4 cards that require the user to find 2 matches. Then the user moves on to the next level making 4 matches, then 8, then 12, so on and so forth until the squares get too small to click on! ;o)

Using the Code

There are two source code zip files: one is the original submission for the contest and another “uncompressed” version. The uncompressed version has been reformatted and has had some of its object and variable names changed to try to bring clarity to the code. Keep in mind this was a 10k challenge, so every bit of whitespace counted against the total number of bytes. If you look at the compressed version (the original version submitted for the contest) you'll see that I was very careful to utilize every byte I could! From this point forward, I will make reference to variable and method names that are included in the uncompressed version of the source.

Board Layout and Card Construction

The entire game is created using a Grid that has dynamically generated rows and columns based on the current level. If a Grid contains ColumnDefinitions and RowDefinitions have no height or width assigned to them, WPF’s layout engine will automatically space them out evenly. Discovering this little tid-bit was what got me started on this entire program.

The main level variable (_currentLevel) keeps track of the user’s progress through the game and is used as the main driving value for each board. The board itself is constructed using the BuildGrid() method which adds the rows and columns to the Grid.

The next step in the process is to create the “cards” using the BuildCards() method. There is a custom object called PlayerCard that holds various pieces of information that we'll cover a bit later in this article.

C#
class PlayerCard
{
    public int myXCoordinate { get; set; }
    public int myYCoordinate { get; set; }
    public int partnerXCoordinate { get; set; }
    public int partnerYCoordinate { get; set; }
    public bool firstGuess { get; set; }
    public bool secondGuess { get; set; }
}

The BuildCards() follows this basic algorithm:

  1. Calculate the number of cards needed to fill the current level
    1. Every other level has an odd number of spaces, so those levels get a blank space
  2. Create a card
    1. Randomly pick a square on the grid
    2. If this square is not already populated by a card, populate it with the current card
    3. If this square is populated, randomly pick another square in the grid until an empty square is found
      1. This may not seem all that optimized. Well, it isn't. The focus of this code was minimization. But I am surprised at how well C# does handle this though… during development I could watch it build level 34, 35…. 65, 66… without slowing down too much! ;o)
  3. Create the next card (this will be the match of the first card)
    1. Follow the same algorithm as the first card
    2. Once a square on the grid is found and populated with this card, assign this card the first card’s grid coordinates
    3. Go back to the first card created and assign it the 2nd card’s coordinates
      1. This is what the PlayerCard’s …Coordinate variables are for
  4. Repeat until all cards are created

Each “card” is really just a Border object. After all the Borders are created, each pair gets assigned a randomized background using a RadialGradientBrush and the BackerGradient() method. This method scrambles the colors and offsets of GradientStop objects to create a different pattern for each pair. Being a “never-ending” memory game, as the user gets higher and higher in the levels, this has less and less of a differentiating effect. But that’s just the challenge of the game!

The algorithm finishes off by then creating a Button object for each card. Each button is the exact same size as the Border and lies on top of the Border. These buttons all get assigned the CardClickCheckForMatch(…) to handle the click and the CardMouseMoveAnimation(…) to handle the nice mouse-over fade effect. So the “card flipping” action is achieved by effectively animating the buttons Opacity to nothing when a card gets clicked. Then if a match is not found, both cards get “flipped” back by re-animating the button’s Opacity back to 100.

Match Detection and Scoring

The match detection is achieved by using a matching state variable (_turnCount) and the CardClickCheckForMatch(…) method. If the current click is the first card clicked in a matching check, the method finds the clicked card in the card collection (_PlayerCards) based on matching grid coordinates and marks its firstGuess member as true and returns. If this is the 2nd click in the turn, then the 2nd card is found in the grid and its secondGuess is set to true. Then the MatchCheck(…) method is called.

The MatchCheck(…) method determines if a match is found by first using LINQ to Objects to pull out the cards that have firstGuess and secondGuess marked. If the 1st card’s myXCoordinate / myYCoordinate and the 2nd card’s partnerXCoordinate / partnerYCoordinate are equal, then we have found a match!

Scoring is then calculated using the ReportScore(…) method. The point value is determined by a number of factors:

  • The current level number
  • How much time has elapsed since the previous match or the level started
    • The quicker the match, the higher the point value
  • Combo sequence
    • Each consecutive match without a miss results in a multiple of 10 applied to the score
  • The Difficulty setting chosen at the start of the game

If this is a miss, then points are also deducted in a similar fashion based on current level number and Difficulty setting. Regardless of the point value calculated, it’s added to the overall game score then drawn on the screen over the 2nd card clicked with a nice fade and TranslateTransform to give the game some “pop” appeal.

The ScorePoint(…) which houses the call to ReportScore(…) also determines if this is the final match in the game and rewards the user with statistics of their performance. The user then is given the opportunity to move on to the next level and continue the fun!

The Difficulty Setting

Upon first starting the game, the user gets to choose a difficulty setting using a Slider. Difficulty affects the game in the following ways…

  • Toward Easy <--
    • Missed matches flip slower allowing for more time to memorize the missed card pattern
    • Less points are awarded in found match calculation
    • Less points are deducted during a miss
  • Toward Difficult -->
    • Card flip faster during a miss
    • More points are awarded during a match
    • More points are deducted during a miss

Points of Interest

This was a fun adventure in learning a bit about game programming and WPF/XAML.  I picked up a bunch of new techniques in WPF animation.

History

  • 30th April, 2009: Initial post

License

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