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.
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 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)
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.
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.
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:
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.
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:
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!
Upon first starting the game, the user gets to choose a difficulty setting using a Slider. Difficulty affects the game in the following ways…
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.
| You must Sign In to use this message board. | ||||||
|
||||||
|
||||||
|
||||||