| |||||||||||||||||||||||||||
|
| |||||||||||||||||||||||||||
Print This Article REALbasic University: Column 031
RBU Pyramid IVLast week we got our game to draw some cards, but they weren't shuffled and the player can't select cards. We'll get that working today.
Shuffling and DealingLet's start by creating a newGame routine which will initialize the variables needed for a new game. Open globalsModule (double-click on it) and add a new method (Edit menu, "New Method"). Call it newGame and put in the following code:
Okay, yeah, nothing much is going on here... yet. We'll be adding to this later. For instance, we'll eventually need a variable to keep track of the player's score, and that would be initialized to zero here. For now, though, we just want to get a bare bones version of the game working. The key routine we need now is the freshDeck routine, which generates a new, shuffled deck of cards. Let's create it. Add in another method and call it freshDeck. Here's the code:
If you read through this code you can see it does more than just shuffle the cards: it shuffles them, deals out 28 cards to the pyramid, builds the Deck and initializes the discard piles, and finally sets several custom properties of the cards such as whether or not the cards are visible, selected, etc. Overall it's fairly routine stuff, but very important. All this stuff must be set up exactly like this at the beginning of any game, or things won't work correctly. Once we've done the above, we're almost done, except we must call our new newGame method. Go to the Open event of the app class we added last week and make it look like this:
Excellent. You should be able to run the program now and if you've done everything correctly, you'll see a screen full of shuffled cards this time: ![]()
Selecting and DeselectingThat's great, but we still can't select any of the cards. How do we get that working? Well, the raw selecting and deselecting is easy: we actually did most of the work last week in the cardCanvas' Paint event. That's where we check to see if a card is selected or not (by examining our .selected custom property) and draw it shaded if it's selected. In fact, we could just add these two lines of code to cardCanvas's MouseDown event to enable selection/deselection:
Run the program and you'll see that yes, you can indeed select and deselect cards. However, the process isn't intelligent, and it not only lets you select multiple cards, it lets you select cards that shouldn't be allowed according to the rules of pyramid! ![]() We've got to make the selection process more intelligent, and here things start to get a little complicated. Before we begin, let's do a little forethinking about what we need to happen. I've already written the code, of course, but this will give you an idea of the thinking process that I went through to generate that code: it didn't just magically appear. Visualize the game of pyramid. How does the selection process work? For starters, we know that only the top-most cards are selectable. Next, we know that no more than one card can be selected at a time: if a second card is selected it is either a match to the first and both are removed, or it is an invalid match and the first card is deselected and the second card remains selected (effectively switching our selection). If it's a match, we've got to deal with removing those cards. Then we've also got our Deck, which deals out a new card when clicked, as well as the discard piles. Let's think about these issues from a programming standpoint. The first problem, that of only allowing the selection of the top-most cards, is fairly straightforward. We already have a boolean (true/false) property for each of our cardCanvas objects which tells us if a card is clickable or not. All we need is a routine that will scan through the cards and set the appropriate cards to clickable or not. To solve the second part of the problem, that of multiple selection, we'll need some way to save the current (previous) selection. How else would we know that a card has been selected or which card it was? This is easily solved by adding a global property which contains the currently selected card. Open globalsModule and add this property (Edit menu, "New Property"): gSelection as integer We'll also need routines that handle card matches, so let's add them now. Go back to the gameWindow Code Editor and add three methods: cardMatch, kingMatch, and discardMatch. For each method, include this parameter: index as integer. For now, we'll pretty much leave these routines empty, but they give us a way to include calls as appropriate within our selection routine. Just so we can see what's happening, let's add msgBox "Match!" to the cardMatch method. We're now ready to write our main selection routine. All this code goes into cardCanvas' MouseDown event (replace the two lines of temporary code we used earlier):
This may look complicated and intimidating, but let's take it step-by-step. First, we simply check to see if the card clicked on is clickable -- if it is, we continue to analyze the selection to see what needs to be done. We also check at this point to make sure that the user didn't click on the Deck -- that's a completely different problem we'll deal with later. Once we've got a valid click, we toggle the state of the current card (select it if it's unselected, deselect it if it's already selected). Next, we do a quick check to make sure that our saved selection variable, gSelection, contains a valid selection and is not the same as the current card. Finally, we make sure that both our previous selection and the current card are selected, and if so, we check to see if there's a match. Finding out if there's a match is easy: we simply add the numbers of the two selected cards to see if they total 13. Once we know there's a match, we check to see if one of the matched cards is on a discard pile. If it is, we handle that in our special discardMatch routine. Otherwise we pass the match to our cardMatch method. If the match isn't valid, we simply deselect the previous card. Finally, we check to see if the player kicked on a King, and if so, we handle removing it. Our final step is to set gSelection to the current card (if it's selected -- keep in mind it could have been toggled off). If you run the program now, you may be disappointed. Nothing much will happen: none of the cards are clickable, and even though you put in code to handle matches, it doesn't seem to do anything. That's because none of the cards are clickable. Remember how we initialized all the cards' clickable property to false in the freshDeck method? Since we've never done anything to make any of the clickable, we now can't click on any of them! The solution is we need to write a routine that will update the state of all the cards, determining which are clickable. Add a new method (to gameWindow) and call it updateCards. Here's the code (it's a little complicated):
Yes, we are now getting into some complicated stuff! The first parts of this routine are not too difficult: we first set all cards to unclickable, then turn on the top cards of the pyramid. Then we start getting messy. We have two checks we need to do. First, we need to find the top-most cards and make them clickable. Remember, for RBU Pyramid, we never actually delete a cardCanvas object: all we do is make it invisible. So we can check to see if a card is top-most or not by looking at the cards above it and seeing if they're visible or not. If they're invisible, then the card underneath is clickable. To actually figure out which cards on are top requires a little math. To figure out the formula, it's good to remember how our pyramid is built. Our pyramid structure is made of cardCanvas objects which are numbered 1 to 28, like this:
If you study the above, you may see there's a relationship between the row number and the card numbers on different rows. For example, the first row is row 1. If you add 1 to the card number, you end up with the card on the next row (card 2: 1 + 1 = 2). This also works on the second row: 2 + 2 = 4, and 4 is first card on the third row. Let's try it on the sixth row. The first card is 16. If we add 6 we get 22, which is the first card of the seventh row. It works! This gives us a formula we can use to figure out which card is above the current card. If we step through the cards in a loop, we can look at the cards on top and see if they are invisible or not (that's the above not cardCanvas(index + row).visible [the left card on top] and not cardCanvas(index + row + 1).visible [the right card on top]). See? It's not so bad! The second check is for the situation of where a matching pair overlap. Here the check is similar, but we can't have more than one card overlapping and the pair must be a match (total 13). That should be about it, except that we need to call updateCards at the beginning of the game. Go back to freshDeck and add these lines to the very bottom:
There, that calls updateCards so the state of selectable cards get established correctly. You should now be able to select cards and almost play the game. As you can see, we've accomplished a great deal in the few lessons so far, but by now you should begin to see the scope of what we are doing. There's still tons to do, of course: we haven't done anything with the card matching routines, so nothing much happens when there's a match. We also have been neglecting to do anything with the Deck and discard piles. That gives us something to do for next week. If you would like the REALbasic project file for this week's tutorial, you may download it here.
Next WeekWe continue with RBU Pyramid, making it so card matches can be removed.
LettersOur first letter this week is from across the Atlantic:
In your example, Paul, I'd use a simple array structure as well: you are correct that it's much easier. The advantage of using a custom class is when you have multiple pieces of information that benefit from being encapsulated into an object. For example, which is easier to use:
As you can see, the two programming methods are similar, except the first uses multiple arrays while the second uses a single array of personClass objects. For some tasks, such as adding a new record, the process is similar though a couple steps more complicated for the custom class method. However, for other tasks, such as deleting a record, the custom class method is far simpler requiring a single line of code. This becomes even more of an issue the more complicated your data structure. With the first method, it could be easy to lose track of an array's index: for example, if you forgot to delete a record from the zipArray field, suddenly your zipArray isn't the same size as the other fields and your data doesn't line up (i.e. the zip code for record 10 now lines up with record 11 of the other fields). That can be hairy to debug! If your data structure is at all complicated, the custom class method will soon prove its value. For instance, in the above two examples, which would be easier to add another element to the client data structure (such as home telephone or id number)? And what happens if your data's sub-structure is dynamic? Let's say this database structure we've created was a list of your salespeople and each had their own list of sales. Since the selling data is different for each person, you can't use a static array like your example. With the custom class solution you'd simple define a subfield, salesClass, like this:
Then you'd simply add that to your original personClass definition:
Now there's a separate array of sales for each person. You can't even do this with your method. (About the best you could do would be to use a multi-dimensional array with the array's second dimension being the sales data, but that would require that everyone have the same number of sales as the person with the most sales.) And I don't even want to think about how complicated this could get if you had multiple dynamic data to keep track of per person! In conclusion, you do pay a slight price in complication using a custom class, but the benefits can be considerable if your data structure is complicated. If it's simple, don't use a custom class. Keep those letters coming! I may not answer your question immediately, but I'll hopefully get around to it in a future column. (If you don't want your correspondence published, just be sure to indicate that when you write. Otherwise I'll assume it's fair game.) About the Column REALbasic University is a weekly instructional column on programming with REALbasic and is brought to you by REALbasic Developer, the magazine for REALbasic programmers. Each week we answer select reader questions, and we're always open to ideas for future columns. Send your questions to . (Keep your questions simple and specific. General queries like "How do I write my own web browser?" will be neglected.) Your question won't be answered immediately, but will be answered in a future column. (If you don't want your correspondence published, just be sure to indicate that when you write. Otherwise it's fair game.) About the Author See the REALbasic University Archives
REALbasic University contents ©2001-2004 by Marc Zeedar and REALbasic Developer. All Rights Reserved.
| |||||||||||||||||||||||||||