Published: 08 Feb 2010
By: Xianzhong Zhu
Download Sample Code

In this second part of the series, you will learn the crucial programming skills of the solitaire game. In detail, we are going to discuss about the three mouse events, i.e. MouseLeftButtonDown, MouseMove, and MouseLeftButtonUp, related event handler programming.

 You can view the live demo here.

Contents [hide]

Cloning Windows Solitaire Game in Silverlight 3 Series

  • Part 1 In the two parts of this series, you will learn to write a Silverlight 3 based solitaire game like the one shipped with various Windows versions. The main purpose to write this game is to explore the mouse drag and drop related programming in Siverlight 3.
  • Part 2 In this second part of the series, you will learn the crucial programming skills of the solitaire game. In detail, we are going to discuss about the three mouse events, i.e. MouseLeftButtonDown, MouseMove, and MouseLeftButtonUp, related event handler programming.
  • Introduction

    In the first article, we've examined several possible mouse drag and drop solutions in Silverlight based application development and finally brought forward the fundamental techniques used to develop our own Silverlight solitaire game. Now that we have learned all the play rules and techniques required to develop our application, in this part, we are going to discuss the focus of the game, i.e. the three mouse events (MouseLeftButtonDown, MouseMove, and MouseLeftButtonUp) related programming.

    The MouseLeftButtonDown Event Handler

    According to the playing rules of the solitaire application, there are several problems to be tackled in the MouseLeftButtonDown event handler, as listed below:

    • Tell apart whether the current two continuous clicks is a left button double click.
    • Under the double click case, judge whether the current card is at the deck or row stack area and further decide whether to move it to the suit stack or not. If the current card satisfies the conditions to move, move the card to the suit stack area.
    • Under the single click case, according to the face and position of the current card decide whether to flip the facedown card, to reset and continue to turn cards, to prepare for mouse capture, or just to return.

    With these questions in mind, let's delve into them one by one.

    Case 1: Double Click Programming

    According to the play rule, there are only two valid cases for the double click. One is when the current card is at the deck (turn) area; the other is when the current card is at the bottom row stack area. Under the two cases, if the info of the current card matches the info at the upper right suit stack area, then the current card can automatically fly onto the top of the target card (possibly a 'placeholder' card as stressed in the method GenerateAndDealCards in the first article) at the suit stack area; or else, we should keep the current double-clicked card rest.

    OK, now let's detail into the double click case related code.

    Listing 1: Code for the mouse double click case

    As is shown above, after we identifies that the current mouse operation is a double click, we should first manage to get the current card. Note the subsequent discussions are on the bases that the current card is not null and the face of current card is face up. If these premises are established, there are totally two small cases valid. One is the double-clicked card is at the deck area, the other is at the row stack area.

    When the current card is at the deck area and at the top, we can traverse the suit stacks to find the matched stack. If matched, we adopt the commonly-used algorithm as mentioned previously to perform the moving action; or else, nothing will happen.

    As for the small case of moving from bottom to upper right, things are also simple. You may find that the key lies in the helper method called MoveFromBottomToTopRight. Listing 2 indicates the related code for it.

    Listing 2: Code for the method MoveFromBottomToTopRight

    In this method, the premise is the topmost card of RowStack[i] is the current card. Afterwards, we just use a for loop to iterate through the suit stacks to find whether there is a matched card there. If matched, we just finish the movement and set up proper zIndex property value and return; or else, nothing will take place.

    On the whole, the double click case is not difficult to grasp, but the result is not very satisfactory. Here, again, I wish cute readers to continue to improve the double click solution. Next, let's turn to research into the single click case.

    Case 2: Single Click Programming

    As far as the left mouse button related single click is concerned, there are several small cases needed us to further examine. Anyway, let's look at the whole related code first.

    Listing 3: C code for the single click case

    Here, we use a switch-case sentence to divide all the face states of current card into four cases, i.e. facedown, faceup, empty, and placeholder. As has already been pointed out, the empty case relates to the special card at the upper left board area, while the placeholder the four special cards at the upper right suit stack area. Let's next look into these small cases one by one.

    As for the empty case, i.e. when we click the special empty card, we need to re-shuffle the left cards at the deck area by calling the method ResetAndContinueToTurn. Note in our game we do not really re-shuffle but just to turn over all the left cards at the board. For brevity, we no more discuss the method ResetAndContinueToTurn, but you'd better study the array index related operations in it.

    Obviously, when the player clicks any of the four special placeholder cards at the upper right area, we just need to return.

    Next, when the player clicks a face-back card whether it's at the deck or row stack, we just need to flip it. Nevertheless, taking into account the complexity of turning cards, we use a special method named TurnCards to deal with the upper left card flipping operation. As for the bottom face-back card flipping, we just need to call the method SetToFaceUp to change the card image.

    Now, let's say a few more words about the method TurnCards.

    Listing 4: Code for turning cards at the upper left area

    Why is the seemingly-simply flipping related code so long? In fact, things are not as easy as supposed beforehand. In dissecting the above code, you should notice the following points:

    • The initial number of cards at the board is 24 (=3 x 8).
    • The global variable choiceNumber may be 1 (the default value) or 3, which represents the number of cards to flip each time.
    • There is a special empty card at the upper left board area, which in some degree leads to the more complexity.
    • The local variable iLeft is used to remember the number of cards left at the board, which, according to our design, may be 2, 3 or greater.

    I believe, with the four points above in mind, it's no more difficult for you to understand the above method TurnCards. One more word, in the above code, the C# generic class List<T> and its plentiful methods help a lot. Here, we are still going to leave off the detailed discussion.

    Now, the last case is when the current card face is face up. In this case, we just call the method PrepareForMouseCapture to make preparation for the mouse capture.

    Listing 5: The PrepareForMouseCapture method

    First, please note that the control we are to set mouse capture is not the container control but the current card. Second, we set up the initial values of several global variables. With the explanations in the first article, you are sure to know the meanings of these variables. Let's next turn to explorer another important event handler- MouseMove.

    The MouseMove Event Handler

    Compared with the other two mouse event handlers, MouseLeftButtonDown and MouseLeftButtonUp, the MouseMove event handler is the easiest to understand and implement. As analyzed before, if the mouse is not captured, then nothing will happen. However, if the mouse is being captured, then we should consider how to move the cards. And, of course, the player may be moving just one card, or a stack of cards.

    Now, let's look more closely at the MouseMove event related code. First, consider moving the current card, as shown in Listing 6 below.

    Listing 6: Code for moving the current card

    In the above code, we need to calculate the offset values of x-direction and y-direction. Note if the two offset values of the two directions are both 0, then we can conclude that the mouse is not moving. In this case, we should call the method ResetMouseCapture to reset all mouse capture related statuses and return. If the mouse is moving (then the current card is being dragged), then we invoke the static method SetZIndex of control Canvas to increase the zIndex property value to let the current card move over all cards on the screen. And also, by calling the other two static methods, SetLeft and SetTop of the control Canvas, we changed the physical positions of the current card.

    Till now, if you have full caught on how the current card is moved, then moving the other several possible cards above the current card becomes a piece of cake. Listing 7 illustrates the related code to achieve this goal.

    Listing 7: Code for moving the other several possible cards above the current card

    Apparently, the key part of the above code is calling the utility method GetFrontCardsFromBottom. As the name hints, the method GetFrontCardsFromBottom is responsible for returning the other several possible cards above the current card in the bottom row stack area. If the above cards exist, then by iterating through the list collection holding all these cards and using the same methods as listed above, we can easily move the rest face-up cards in front of the current card. Lastly, we used a global variable lastMousePosition to remember the current mouse position to facilitate the calculation of the next mouse moving offsets.

    For more details about the method GetFrontCardsFromBottom, you can further research into the source code. Let's next shift our mind to examine another important function, the MouseLeftButtonUp event handler.

    The MouseLeftButtonUp Event Handler

    Compared with the previous MouseMove event handler, the MouseLeftButtonUp event handler is a bit complex. In spite of this, as you may have known in Windows built-in solitaire game, the main task in this handler is to judge whether to put the moving cards at the target area or restore them to their original positions, as well as to finish the corresponding move.

    The first thing to notice is the prerequisite of all above code-- the mouse has been captured and the current card must be face up. For other cases, nothing will happen in the MouseLeftButtonUp event handler. So, you will see the following code outline.

    Listing 8: All the things should happen under the mouse captured condition

    Next, we are going to discuss three cases according to the original position of current card. Also, in terms of the game rules, the original positions of current card may be:

    • Case 1: at upper left (the deck area).
    • Case 2: at upper right (the suit stack area).
    • Case 3: at bottom (the row stacks area).

    Next, let's delve into the above three cases.

    Case 1: Original Position of Current Card at the Deck

    When the original position of the current card is at the deck area, there are two small cases needed to be considered, possibly moving from the deck to the suit stack and from the deck to the row stack, as shown in Figure 1 and 2, respectively.

    Figure 1: Moving valid card from the deck to the suit stack

    Moving valid card from the deck to the suit stack

    Figure 2: Moving valid card from the deck to the row stack

    Moving valid card from the deck to the row stack

    Let's first consider the small case of moving cards from upper left to upper right area. Listing 9 below gives the related pieces of code.

    Listing 9: Moving cards from upper left to upper right area

    By calling the utility method bTurnTargetAtTopRight, we can decide whether the moving target is at the top left area (the suit stack) or not. If so, the out typed parameter iPos will return the index of the suit stack suit stack for current card to move to. As for the concrete moving algorithm, we don't need to waste space since you have skimmed the above paragraphs.

    As a supplement, let's explain the method bTurnTargetAtTopRight for you.

    Listing 10: Code for the method bTurnTargetAtTopRight

    Here, by invoking another helper method UserControlBounds, we get the Rect structure info of the current card. Then, by using the Intersect method of the Rect class, we test whether the current card related rectangle intersects one of the upper right suit stack related four rectangles. If so, we further take two cases into account: one is for the special ace card under which case there should be an empty position for it (we use a special card to represent the empty placeholder as explained in the method GenerateAndDealCards), and the other is for general status. If the moving card matches the target, then we use the variable iPos to remember the index and return true to indicate the match test is OK. For all the rest cases, we simply assign -1 to the variable iPos and return false.

    Let's continue to consider the small case of moving cards from upper left to bottom area. Listing 11 below gives the related pieces of code.

    Listing 11: Moving cards from upper left to bottom area

    Here, by invoking the helper method bTurnTargetAtBottom, we know if the current moving is from the upper left to bottom and whether there is a matched target place for the moving card. Since the implementation of the method bTurnTargetAtBottom quite resembles the above method bTurnTargetAtTopRight, we still select to elide the related discussion to save space.

    As you see, there are still two small cases here. When the current moving card has a rank of King, then only under the situation there is an empty place at the bottom area can the king card be moved to the row stack area. As the general cases, we utilize the similar algorithm as mentioned previously to move the card from the deck area to the bottom row stack. What's more, since the value of the zIndex property has been changed in the MouseMove handler, we cannot modify the value here again.

    Case 2: Original Position of Current Card at the Suit Stack

    When the original position of the current card is already at the suit stack area, there are only one case to be considered, moving one card from the upper right suit stack to the bottom row stack, as shown in the moving direction in Figure 3.

    Figure 3: Moving valid card from the suit stack to the bottom row stack

    Moving valid card from the suit stack to the bottom row stack

    For this case, the related code is also short. Listing 12 below shows how to achieve this movement.

    Listing 12: Moving cards from upper right to bottom area

    Let's summarize the above logic. First, judge whether the original position of the current moved card is at the suit stack area. If so, we continue to invoke the helper method bTopRightTargetAtBottom to see whether the dragging direction is at the bottom. And if so, we remember the upper right index using the variable iTopRightPos and utilize the similar algorithm as mentioned previously to move the card from the suit stack area to the bottom row stack. The last thing to take notice of is the upper right card cannot be dragged to the blank area at the bottom row stack area.

    Case 3: Original Position of Current Card at the Row Stack

    When the original position of the current card is at the bottom row stack, there are two cases to be considered, i.e. moving one card from the bottom row stack to the upper right suit stack, and possibly moving a stack of cards (one card or more) from the bottom row stack 1 to row stack 2, as shown in the moving directions in Figure 4 and 5 respectively.

    Figure 4: Moving a valid card from the bottom row stack to the suit stack

    Moving a valid card from the bottom row stack to the suit stack

    Figure 5: Moving two valid cards from the bottom row stack 1 to the bottom row stack 2

    Moving two valid cards from the bottom row stack 1 to the bottom row stack 2

    For the case of moving cards from bottom to upper right areas, the related code is shown in Listing 13 below.

    Listing 13: Moving cards from bottom to upper right area

    In the above code, first by calling the helper method bBottomTargetAtTopRight, we can have the idea of whether the target card for the current bottom card is at top right area. And if so, get the associated index of the target suit stack using the out typed variable iPos. Next, by using a for loop to iterate through the seven bottom rectangle areas we get the index of the row stack of the current card located at. At last, with the similar algorithm used above we accomplish the task of move the current face-up card to the upper right suit stack area.

    As for the small case of moving cards from bottom area 1 to bottom area 2, things become a bit complicated, but still not too difficult to comprehend. The related code is shown in Listing 14 below.

    Listing 14: Moving cards from bottom area 1 to bottom area 2

    Here, also, we first use a helper method bBottom1TargetAtBottom2 to get the idea of whether the target card is at some row stack at the bottom. And, if so, we also retrieve the target row stack related index.

    Next, also, by using a for loop to iterate through the seven bottom rectangle areas we get the index of the row stack of the current card located at.

    And then, we set up a Rect typed variable lastRect to remember the approximate target rectangle that the current dragged cards will be put down at. Note here only when the current face up card has the rank 13 (the king) can the current card and the possible related stack of cards be dropped at any of the empty row stacks. For this, you should notice the target rectangle size.

    Later on, by invoking the utility method GetFrontCardsIncludeMeFromBottom, we get the pile of dragged cards (including the current card).

    Next, by using the sentence if (RowStack[iPos].Count == 0) we can deduce whether we are going to drag a stack of cards onto an empty position. Finally, by using a simple foreach iteration sentence we achieved the task of putting the dragged cards onto the target place.

    Putting Dragged Card(s) Back

    Till now, we have given a thorough study of all the three cases, the original position of moved card(s) at the upper left deck area, at the upper right suit stack area, and at the bottom row stacks area. So, if none of all the above conditions are met, then we have to put the dragged card(s) back to their original positions. This is done through the method PutBackMovingCards, as shown in Listing 15.

    Listing 15: Moving cards back to their original positions

    The first natural point coming into our mind is to restore the current card to its original position. However, there is a special and important tip deserved to be taken care of: when the mouse does not move, i.e. the horizontal and vertical offsets are all zeroes, we should just return.

    Then, next, we restore the value of the zIndex property of current card, all of which owes to the two methods SetZIndex and GetZIndex of control Canvas. Afterwards, by using the methods GetLeft, GetTop, SetLeft, and SetTop, in combination with the above calculated horizontal and vertical offsets, we move the current card back to its original position.

    Another point should be paid attention to here is, if the moving card(s) is at the deck or suit stack area, there will be only one card be moved back to its original position if the moving condition is not met. Therefore, in the above code, the invocation of the method GetFrontCardsFromBottom is just to get possible cards in front of current card at the bottom area. And so, if the variable preElementList is null, then we just return; or else, it shows that the player is moving a stack of cards at the bottom. With this in mind and with the help of the above discussion, you can easily put the other moving cards back to their original positions using a simple foreach iteration sentence.

    Summary

    In the two series of articles, we have accomplished the target of developing a solitaire game similar to that shipped with Windows using Silverlight 3 techniques. Due to various reasons, what I brought to you is only a rough product. There are still many aspects deserved to be improved. For example, I have not given the game score support, I have not given an algorithm to prejudge whether the current game is solvable or not, and so on. Anyway, the solitaire game is interesting and learning Silverlight itself is interesting, too. So, all the true game just begins...

     You can view the live demo here.

    Cloning Windows Solitaire Game in Silverlight 3 Series

  • Part 1 In the two parts of this series, you will learn to write a Silverlight 3 based solitaire game like the one shipped with various Windows versions. The main purpose to write this game is to explore the mouse drag and drop related programming in Siverlight 3.
  • Part 2 In this second part of the series, you will learn the crucial programming skills of the solitaire game. In detail, we are going to discuss about the three mouse events, i.e. MouseLeftButtonDown, MouseMove, and MouseLeftButtonUp, related event handler programming.
  • 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 6 articles on DotNetSlackers. View other articles or the complete profile here.

    Other articles in this category


    WPF Tutorial
    With all the new technology that Microsoft is releasing, it's hard to keep up. WPF is one such techn...
    Silverlight "WPF/E" first steps: Getting started with simple analog clock
    This article is an introductory article on how to build a WPF/E simple web application that represen...
    Introduction to XAML Part 1
    You are coding in .NET and have basic knowledge of XML. You heard about that Windows Presentation F...
    Hello Silverlight : Start your Silverlight journey today!
    Start your Silverlight development journey today with this step-by-step article.
    RIA Services With Silverlight 3 - Part 1
    What RIA services actually are and how to use them with Silverlight.

    You might also be interested in the following related blog posts


    Tech Ed 2006 Summary read more
    Apple Safari for Windows and Microsoft Silverlight read more
    Moonlight 1.0 Released, Silverlight script updated – and a Chrome hack read more
    Coding a Euchre Game, Part 7: Total Logic (Matt Gertz) read more
    Silverlight Controls read more
    Using XML as a resource in your code one more series on gamewriting (Matt Gertz) read more
    Telerik Announces Support for Microsoft Silverlight 3 read more
    Some Silverlight ecosystem updates read more
    Why Embedded Silverlight Makes Sense read more
    Some Silverlight ecosystem updates read more
    Top
     
     
     

    Please login to rate or to leave a comment.

    Product Spotlight