Published: 26 May 2010
By: Xianzhong Zhu

In this second part of the series, we will delve into the crucial part in the Freecell game. In detail, we are going to examine the two mouse events, i.e. MouseLeftButtonDown and MouseLeftButtonUp, related event handler programming.

Contents [hide]

The Developing Freecell Game Using Silverlight 3 Series

  • Developing Freecell Game Using Silverlight 3 Part 1 In the two parts of series, we are going to develop another Silverlight 3 based Freecell game like the one shipped with Microsoft Windows. Our main purpose to write this game is to continue to explore the mouse related operations supported in Silverlight 3. And also, in constructing this application you will learn the Silverlight 3-supported behavior related topics.
  • Developing Freecell Game Using Silverlight 3 Part 2 In this second part of the series, we will delve into the crucial part in the Freecell game. In detail, we are going to examine the two mouse events, i.e. MouseLeftButtonDown and MouseLeftButtonUp, related event handler programming.
  • Introduction

    NOTE

    1. The Freecell game development environments are as follows:

    Windows XP Professional (SP3);

    .NET 3.5 (SP1);

    Visual Studio 2008 Professional (SP1);

    Microsoft Expression Blend 3;

    Microsoft Silverlight Tools for Visual Studio 2008 SP1;

    Part of Windows Presentation Foundation Pixel Shader Effects Library.

    2. You're highly recommended to read the Solitaire game related articles first before reading this series of two articles since we've elided many similar techniques.

    You are sure to remember the main techniques in terms of the mouse drag and drop programming in the Solitaire game. There, the drag source and related drop target determination is difficult. In this case, however, the knot shifts to the inverse-colored cards associated stuffs. To be honest, the difficulty mainly results from the introduction of the InverseColorClickBehavior behavior introduced in the first part of this series. Anyway, since we have selected to use this behavior to serve as a friendly remembrance in playing the game, we must face up to all the possible troubles caused by it.

    Let's next detail into the related programming.

    The MouseLeftButtonDown Event Handler

    As you've realized, there is no drag and drop operation like in the Solitaire game but click here and there in the Freecell game. However, to gain a better user-friendly interface, we'd better provide support for clicked card inverse color resembling the same effect as in the Windows version. With this feature, our application will become more natural and greeted by players.

    According to the playing rules of the Freecell application, there are several points needed to be stressed in designing the MouseLeftButtonDown event handler, as listed below:

    • At any time, ensure a maximum of only one inverse-color card.
    • At the very beginning of this game the current card is certainly located at the bottom area, so it must have an anti-color behavior.
    • If there are other anti-colored cards at the moment you click the mouse, you should temporarily remove the anti-color behavior of them.
    • Cards that have already been moved to the target area (the foundation piles at the upper right corner on the screen) will no longer allow the anti-color feature.

    All in all, we can take the following general steps to author the MouseLeftButtonDown event handler:

    1. Get the current card.
    2. If the current card is not null, then clear all existing inverse-color effects except for the current card. And, at the same time, remember some related info according to the position of the current card.
    3. Move cards if possible.

    Next, let's look into the general code structure of the MouseLeftButtonDown event handler.

    Listing 1: General code structure for the MouseLeftButtonDown event handler

    As is shown above, we first call the method ClearAllFinalEffect to clear all possible existing inverse-color effects at the foundation piles. After that, we manage to get the current card. If the current card is not null, we clear all possible existing inverse-color effects except for the current card. This is a complex yet attractive story; let's dissect it piece by piece.

    If the current card is at the bottom area but not at the foremost, we should, according to the operation of the Windows versioned counterpart, remove the possible inverse color effect. Herein, we call the helper method bCurrentCardAtBottomAndNotForemost to ascertain the inverse color related cases. As guessed, this method is simple, as listed below:

    Listing 2: Code for the method bCurrentCardAtBottomAndNotForemost

    Next, let's focus upon the pretty intricate part in the MouseLeftButtonDown event handler.

    Remembering Info While Clear the Inverse Color Effects

    First of all, let's follow the previous trail to look at a small part of the subsequent code.

    Herein, the first line of code should be the first point attracts your attention. This is very important. As the if condition requires, the following discuss all revolves around the prerequisite CurrentCard.Effect!=null. Obviously, till now we have just cleared the possible inverse color effects at the upper right area and the possible non-forefront inverse color effects at the bottom, while the rest parts related inverse color effects have not been mentioned. In fact, cards at these areas are very likely with colors inversed.

    According to the previous analysis, the above method, bCurrentCardAtBottomAndForemost, is likely to return true. As the names hint, the method bCurrentCardAtBottomAndForemost is used to judge whether the current card is at the front most at the bottom area and not null.

    Next, you should pay much attention to the two global variables InverseColorCard and SecondInverseColorCard. The first variable InverseColorCard is used to remember the current inverse-colored card, while the second variable SecondInverseColorCard is used to remember the most recently inverse-colored card at the bottom area. Therefore, fully understanding the meanings of the two variables is of vast importance.

    Well, let's continue to look into the subsequent code.

    Here, we use a for loop to iterate over the top left cells area. If the current cell is not null and does not equal to the present card marked in the variable CurrentCard, we continue to judge if the card at the current cell matches the current card (which is at the bottom and at the front most position). If so, we further judge whether its effect is null. If not, we should write down the card in the global variable PreviousMatchedCardInCells and its related index position in another global variable iGlobalCellsIII, and finally clear its effect to ensure there is at most one inverse-colored card (at the bottom in this case) on the screen.

    To gain a better understanding with the above explanation, please image the playing case that you first click the card at the cell area and the card turns inverse-colored, and then you click another card at the bottom when the just inversed card restores its previous state and the bottom one gets inverse-colored, and at the same time, the two cards are matched (satisfying the condition for moving). Refer to Figure 1 to gain a more vivid impression.

    Figure 1: Remember the possible matched card at the cells area

    Remember the possible matched card at the cells area

    Continue to Remember Info While Clear the Inverse Color Effects

    Till now, we have not discussed how to clear the possible inverse color effects at the bottom area. Well, let's continue to look into the subsequent code, as shown in Listing 5.

    Obviously, what attracts our attention is the helper method ClearBottomEffectsAndRememberSome. It is just through this method that we achieve the goal to clear the possible inverse color effects at the bottom area and remember necessary related info. Let's delve into this method.

    Listing 3: Code for the method ClearBottomEffectsAndRememberSome

    First of all, please note the prerequisites to execute the method ClearBottomEffectsAndRememberSome are CurrentCard!=null and CurrentCard.Effect!=null.

    Inside this method, we use a for loop to iterate over the tableau piles area. If the card does not equal to the present card marked in the variable CurrentCard, then we have enough excuse to continue to judge if the card at the tableau pile matches the current card. If so, we further judge whether there is a valid series of cards starting with this card. If so, we should write down the valid series of cards and related info. You can refer to Figure 2 to gain a more intuitive understanding.

    Figure 2: Remember the possible valid series of card at the bottom area

    Remember the possible valid series of card at the bottom area

    To find the valid series of cards is achieved through a compound for loop sentence. Note that the number of the series may be 1 or greater than 1. After finding the series, we should of course remember all related info for later use. For example, we use the global List<Card> variable topElementList to hold the series, use another global variable PreviousMatchedCardInSeryAtBottom to mark the first card in the valid series, and a third global variable iGlobalBottomColumn2 to mark the corresponding column index. At last, you should remember to clear the anti-color effect of the topmost card (it's not the currently clicked card). And also, you have to clear any other possible anti-color effects using the last line of code.

    Till now, if you have understood what I expained above, then you have laid a good foundation to grasp what I'll discuss later on.

    Moving Cards

    Now that nearly every thing gets ready, let's take a look at how to move cards from one place to another. As is indicated before, nearly the whole moving actions are done in the MouseLeftButtonDown event handler.

    Let's follow the above code to see the other related code that performs the task of moving cards.

    Listing 4: Code for moving cards

    As you've seen, all the moving actions will be finished through the three methods, MoveOneCardC2BForNonEmptyCase, MoveCardsFromBottom1ToNonEmptyBottom2, and MoveCardsUnderOtherCases. In our case, we divide the moving operations into three categories. Let's next dissect them one by one.

    Moving One Card from Cells to Bottom for Non-empty Case

    As the name indicates, the method MoveOneCardC2BForNonEmptyCase only deals with the case that we move one card from cells area onto the matched card at the non-empty bottom column. Figure 3 below gives a more intuitive illustration.

    Figure 3: Move one card from cells area onto the matched card at the non-empty bottom column

    Move one card from cells area onto the matched card at the non-empty bottom column

    Next, let's look at the related code.

    Listing 5: Code for the method MoveOneCardC2BForNonEmptyCase

    In the above code, we first judge whether the just-clicked card at the cells area (stored in the variable PreviousMatchedCardInCells) matches the one (stored in the variable SecondInverseColorCard) at the non-empty bottom column. If so, we start the move. As you see, the moving card related code is nearly the same as that in the Solitaire game. So, we are to discard the related discussion.

    After that, we should also notice the ending work - first set the value of the variable PreviousMatchedCardInCells to null, and then clear the anti-color effect of the card SecondInverseColorCard. You are sure to grasp the idea inside the method MoveOneCardC2BForNonEmptyCase if you have already made clear the meanings of the two global variables, PreviousMatchedCardInCells and SecondInverseColorCard.

    Moving Cards from Bottom Source to Non-empty Bottom Target

    From what's mentioned above, we know that if the method MoveOneCardC2BForNonEmptyCase returns true, then the whole card moving process ends. However, if this method returns false, then it means the present condition is not met, and then we need continue to judge the rest cases of card moving. Here, as you see, comes the second method MoveCardsFromBottom1ToNonEmptyBottom2.

    As the names implies, the method MoveCardsFromBottom1ToNonEmptyBottom2 bears the responsibility of moving matched cards from one column to another non-empty column at the bottom area.

    To gain a better understanding with the logic here, let's first take a look at the related snapshot shown in Figure 4 below.

    Figure 4: The snapshot related to from bottom to non-empty bottom case

    The snapshot related to from bottom to non-empty bottom case

    As shown from the figure, things become a bit complex. When we move cards from one column at the bottom to another non-empty column, there are several small cases needed to be considered, which are enumerated below:

    • The valid series of cards may have only one card.
    • The valid series of cards may have more than one card.
    • After the valid series of cards have been moved to the target, the possible existing cards beneath the just moved ones may also match the cards at the top right foundation piles. So, according to the Windows counterpart application, we'd better auto-fly these cards onto the matched foundation piles to save time.

    Now let's further explore the detailed implementation of the method MoveCardsFromBottom1ToNonEmptyBottom2.

    Listing 6: Code for the method MoveCardsFromBottom1ToNonEmptyBottom2

    Here, we no more waste words to explain the meanings of the three global variables topElementList, SecondInverseColorCard, and PreviousMatchedCardInSeryAtBottom. You are sure to have been familiar with them. Next, if the basic condition is met, we will invoke the method GetMaxNumberToMoveCards to get the maximum number of cards to be permitted to move.

    Listing 7: Code for the helper method GetMaxNumberToMoveCards

    As you see, the helper method GetMaxNumberToMoveCards will return the maximum movable cards. Here, the variable nMaxMovingCards is global, with the initial value equal to zero. In this game, as in the Windows counterpart, the number of the maximum movable cards is decided by the number of the empty cells.

    After invoking the method GetMaxNumberToMoveCards, we should deal with the different moving cases according to the returned value and other related data. If the value of the variable nMaxMovingCards equals to 1, then we just need to move one card by calling the helper method MoveOneCardB2BForNonEmptyCase and then return. If the value of the variable nMaxMovingCards is greater than 1 while the total card count of variable topElementList equals to 1, then we also call the same helper method MoveOneCardB2BForNonEmptyCase. For the rest case (nMaxMovingCards > 1 and topElementList.Count> 1), we should call another method MoveMultiCardsForNonEmptyBottomCase to move several cards.

    Let's next look into the simple case --the helper method MoveOneCardB2BForNonEmptyCase.

    The Method MoveOneCardB2BForNonEmptyCase

    First, let's take a look at the related code.

    Listing 8: Code for the helper method MoveOneCardB2BForNonEmptyCase

    As the name hints, the method MoveOneCardB2BForNonEmptyCase is used to move one card from one column at the bottom area to another column in the case that the target column is not empty. With the help of the previously discussed global variables, you can easily seize the idea of the above code. However, there are still two points deserved to be stressed:

    • We use a Rect structure (gotten by calling the method UserControlBounds) to identify the rectangle related to the topmost card at the target column in the tableau piles.
    • We call another helper method AutoFlyPrevCardsIfPossibleForB2BNonEmptyCase to deal with the possible auto-fly case.

    Here, it deserves to say a few more words about the method AutoFlyPrevCardsIfPossibleForB2BNonEmptyCase.

    Listing 9: Code for the method AutoFlyPrevCardsIfPossibleForB2BNonEmptyCase

    If the source card related column is not empty (after the matched card is removed), then we check if the topmost card matches the ones at the foundation piles at the top right area. If so, we move this card using the similar algorithm like that leveraged above and then recursively call the method to do similar checking with the next card, and so on. When the bool-typed variable bMatched equals to false, it means there are no more matched card. Then, the recursive invocation ends.

    Till now, if you have indeed understood the action and running logic of the above method MoveOneCardB2BForNonEmptyCase, then it's no more difficult for you to know the function of another method MoveMultiCardsForNonEmptyBottomCase. This method is called under the case that permits moving multiple cards and exist a series with multiple cards.

    Listing 10: Code for the method MoveMultiCardsForNonEmptyBottomCase

    Different from the method MoveOneCardB2BForNonEmptyCase, this method is able to move multiple cards and possibly results in a series of cards automatically flying to the upper right area. When the condition topElementList.Count <= nMaxMovingCards is met (and of course other prerequisites are met), we rest on a foreach loop to succeed in moving the several cards. After the foreach complex sentence is finished, you should also notice another method called AutoFlyPrevCardsIfPossibleForB2BNonEmptyCase which has been examined previously.

    Up to now, we have finished discussing how to move cards from bottom source to non-empty bottom target. Obviously, there are several other cases to move cards, such as from bottom to cells, from cells to foundation piles, from bottom to foundation piles, and from bottom source to empty bottom target column, etc. which is all fulfilled through another method MoveCardsUnderOtherCases.

    Move Cards under Other Cases

    Till now, there are still several moving cases we have not discussed:

    • Move a card from bottom to cells
    • Move a card from cells to final area (foundation piles)
    • Move a card from bottom to final area (foundation piles)
    • Move cards from one column at the bottom to another empty column at the bottom

    All the above tasks are accomplished in another method MoveCardsUnderOtherCases. Since their implementing mechanisms are nearly the same as the two important cases introduced above we are not going to waste words on them. Please refer to the source code yourselves.

    Let's next shift our mind to examine another mouse button related event handler-- the MouseLeftButtonUp event handler.

    The MouseLeftButtonUp Event Handler

    #Compared with other mouse event handlers, the MouseLeftButtonUp event handler is easier to understand. However, on the whole, there are still the following tasks needed to be figured out.

    • When the cells area is all empty and all the bottom cards are in valid order, we'd better provide support for automatically flying these left cards to the upper right target area.
    • When the foundation piles are all full, it is friendly to show a dialog with the like message 'Game over! Press Start menu to resart'.
    • Stop the timer that controls the game process.

    Let's check over the above points one by one.

    Automatically Flying Cards to Target Detecting

    The method AutoFlyToTargetDetect takes the responsibility of automatically fly cards detecting. Concretely, to make the left cards fly to the top right target piles automatically the following two prerequisites must be established.

    1. All the cells must be empty.
    2. Not all the tableau piles are empty and all the cards in each tableau pile are in order.

    With the above conditions met, we are able to make the cards automatically fly to the top right area to save the player's time. Let's look at the related coding.

    Listing 11: The complete code for the method AutoFlyToTargetDetect

    To gain a more intuitive understanding with the above code, let's first take a look at one of the related snapshots (refer to Figure 5) when the game is about to end.

    Figure 5: The bottom cards are about to automatically fly to the upper right target area

    The bottom cards are about to automatically fly to the upper right target area

    Obviously, when the player drags the card spades 5 onto the bottom card diamonds 6 the two above-mentioned conditions will be met. And in no time, you will see all the bottom cards flying to the top right target corner pile after pile automatically. Afterwards, a dialog is shown on the screen indicating that the game is over.

    With the help of Figure 5, you, I think, are sure to get the central idea of the above code. So we are to discard the related fussy discussion.

    Showing the Game over Dialog

    As soon as the automatic flying ends, we'd better show a related dialog on the screen indicating that the game is over. Figure 6 gives one of the related snapshots.

    Figure 6: When the game is over a friendly dialog is shown

    When the game is over a friendly dialog is shown

    Here we give the complete MouseLeftButtonUp event handler related code.

    Listing 12: The complete code for the MouseLeftButtonUp event handler

    Besides those discussed above what deserves your attention here should be the condition to show the game over dialog. As you see, if the target foundation piles are full of cards (and not empty) we will show the dialog and then end the timer; or else, we do nothing.

    Summary

    In the two series of articles, we have succeeded in developing a Windows like Freecell application using Microsoft Silverlight 3 techniques. Due to various reasons, the current game is still in its infancy. There are still many aspects needed to be improved. For example, we have not given the backend database related score support, each time you play the game we have only given a random cards dealing solution without ready layouts for the players to choose, we have not supported the redo function, and so on. Anyway, we have brought out to you the core part of the Freecell game.

    <<  Previous Article Continue reading and see our next or previous articles Next Article >>

    About Xianzhong Zhu

    I'm a college teacher and also a freelance developer and writer from WeiFang China, with more than fourteen years of experience in design, and development of various kinds of products and applications on Windows platform. My expertise is in Visual C++/Basic/C#, SQL Server 2000/2005/2008, PHP+MyS...

    This author has published 81 articles on DotNetSlackers. View other articles or the complete profile here.

    Other articles in this category


    Develop a Flexible 2.5D Scene Editor Targeting Silverlight RPG Games - Part 1
    Starting from this article, I'm going to introduce to you an excellent 2.5D RPG games scene editor -...
    Displaying Notification Messages in a Silverlight Dashboard Application
    In this article we will see how we could display a notification message and further a list of notifi...
    Develop a Flexible 2.5D Scene Editor Targeting Silverlight RPG Games - Part 2
    In this article, I'm going to introduce to you how to construct such a 2.5D RPG game scene editor th...
    Widget Refresh Timer in MVVM in Silverlight
    In this article we'll see how to refresh and disable widgets using the Model View View-Model pattern...
    Air Space Issue in Web Browser Control in Silverlight
    Air Space issue is a common issue in Web Browser control in Silverlight and WPF. To explain the issu...

    You might also be interested in the following related blog posts


    Introducing SharePoint 2010 Training at U2U read more
    Silverlight MVP read more
    Building a Silverlight 3 based RIA Image Magagement System (1) read more
    Building a Silverlight 3 based RIA Image Management System - 1 read more
    Telerik Launches RadControls for Silverlight 3 for Line-of-Business Application Development read more
    Why Embedded Silverlight Makes Sense read more
    Telerik Announces Support for Microsoft Silverlight 3 read more
    Win a Govie Award Submit an Innovative Gov 2.0 Application read more
    VideoWiki - Step 0 read more
    Moonlight 1.0 Released, Silverlight script updated – and a Chrome hack read more
    Top
     
     
     

    Please login to rate or to leave a comment.

    Free Agile Project Management Tool from Telerik
    TeamPulse Community Edition helps your team effectively capture requirements, manage project plans, assign and track work, and most importantly, be continually connected with each other.