REALbasic University Resources:

RBU: Glossary Defines common REALbasic programming terms
  Archives Previously published columns
Translations: Dutch Courtesy of Floris van Sandwijk
  Japanese Courtesy of Kazuo Ishizuka
  Chinese Courtesy of Dong Li
  RBU Translation Guide Information on Translating RBU into other languages
Books: Matt's Book (2nd Edition!) Ideal for experienced programmers
  Erick's Book Best for beginning programmers
Websites: Mother Ship The publisher of REALbasic
  RB Webring Links to hundreds of REALbasic websites
  RESExcellence Another REALbasic programming column
  REALbasic Developer Magazine The premiere source for REALbasic instruction.

REALbasic University is Sponsored by

Make your Mac do what YOU want it to. Create games, utilities, cool Mac OS X tricks. Download REALbasic now and create your own software.


Print This Article

REALbasic University: Column 045

RBU Pyramid XVIII: Polishing Part II

Have you ever heard the comment that "ten percent of a program takes 90 percent of the work?" I've found that to be the case. Getting a program 90% of the way done is fairly easy: it's that last ten percent that's the killer.

Polishing a program to final form is a large part of that final ten percent. Last week we began the process, and this week we continue. I'd hoped to accomplish this in just two lessons, but it seems there's more to do than I remembered. We'll finish this up in next week's column.

Adding Keyboard Deck Advance

One feature I've been missing in our version of Pyramid is a way to quickly draw cards from the Deck. What's easier than pressing the space bar?

Open gameWindow's Code Editor and go to the KeyDown event. Put in this code:

  
if key = " " then
if (uBound(gTheDeck) > 0 or gFirstTimeThrough) then
deckAdvance
end if
end if

Here we check to see if the user pressed the space bar: if so, we check to see if the Deck has cards left (there are items within our gTheDeck array), or it's the user's first time through (in which case a Deck advance is still valid). If either of those conditions is true, we call our deckAdvance routine. Simple!

Adding Safe Shuffle

One of the original reasons for my writing RBU Pyramid was to stop the annoying tendency of most Pyramid games to deal "unwinnable" hands.

My solution was to write a "safe shuffle" routine, whereby RBU Pyramid would check a shuffle to make sure it was winnable before showing the shuffle to the player. Somehow in the course of this tutorial, we never wrote that routine. So let's add it now.

Open globalsModule and add a new method (Edit menu, "New Method"). Name it safeGame and give it a return type of Boolean:

Now we must figure out the algorithm we're going to use to solve our problem. We could have our routine actually virtually "play" a game of Pyramid, attempting to solve the puzzle with the current deal of cards. If the game turns out to be unbeatable, we know we've got a bad deal, so we reshuffle the cards and try again.

There are two problems with that, however. One, programming a routine to actually play Pyramid is complicated: we're getting into a lot of sophisticated decision-making. Second, playing a whole game, even virtually, takes time. Even if it only takes half a second on a fast computer, keep in mind that "unwinnable" hands are fairly common: our safeGame routine might be called several times in succession before a winnable hand is dealt. The player might have to wait several seconds between deals. We don't want that.

Our solution is a common compromise in programming. We don't need to guarantee that an unwinnable hand is never dealt: we just want to greatly lesson the chances of such a thing happening. If we can stop 99% of the bad hands from being seen, it will eliminate most of the frustration a player is likely to encounter.

So what can we do to check for unwinnable hands without actually checking every possible move to make sure the game is winnable?

The answer to this took me a little time: I first analyzed a number of unwinnable deals I received (yes, playing Pyramid was research) to figure out what happens in an unwinnable hand.

I discovered an unwinnable pyramid is when you get blocked: the card you need to win is either behind the front cards of the pyramid (covered and not choosable) or past you in the discard pile and you're on the last redeal of the Deck.

The latter can happen when you don't play the game well: it's quite possible to turn a winnable game into an unwinnable game by not using your resources well. For instance, let's assume you've got a pyramid with three Twos and one Jack in it. That tells you there are three Jacks in the Deck. But if you advance through the Deck and don't use the Jacks, on your second and final time through the Deck, you must use those Jacks to clear at least two of those Twos or you're toast. Once the Jacks you need are in the Discard pile, you can't get back to them (at least not without a huge amount of luck).

At least that type of unwinnable hand can be solved by planning ahead: that's part of the game of Pyramid. But sometimes you'll be dealt pyramids like this one (from RBU Pyramid I):

This is an example of an impossible shuffle: the two 2's near the bottom of the pyramid each require a Jack to be removed, yet there are three Jacks near the top of the pyramid, meaning that a maximum of one exists within the Deck. Even worse, the top card is a 2, requiring a Jack to match. What a mess! It's a Catch-22: the game cannot be solved.

That's impossible right out of the box! You might as well quit now, because there's no way to solve this shuffle.

What causes this kind of impossible situation? The key problem with unwinnable hands comes from having too many of the same cards in the pyramid.

Think about it: the three Jacks in the pyramid makes this tough to solve in the first place. Having a couple Twos in the front makes it impossible.

So now we've got something: we know that too many of the same card within the pyramid makes for a possibly unplayable game.

But there's another conclusion we can draw from this analysis. Having the three Jacks in the pyramid by themselves isn't the end of the world, but it's the three Jacks in combination with several Twos that make it impossible. That tells us that too many valid pairs in the pyramid could cause a problem.

So now we've got our algorithm! All we need to do is count the frequency of each card in the pyramid, see how many pairs we've got, and if the numbers are too high, don't use that shuffle! That should be very fast as well, since we don't have to calculate a huge number of possibilities, just count a few items.

However, there is a flaw in this idea. In an actual pyramid game, the order of the cards is critical: it is entirely possible to have three Jacks and three Twos in the pyramid and have a winnable game. So our algorithm, to be accurate in predicting bad hands, should check the order of the cards in pyramid.

But just because it's possible doesn't mean it's likely. The chances are much greater than the game is unwinnable if there are a lot of the same card in the pyramid.

Remember the compromise I spoke about earlier? This is where it comes into play. Since we're not concerned with absolutes, our algorithm should work for us. Yes, we might eliminate a few winnable deals, and yes, we might miss a few unwinnable hands, but this algorithm should work to catch 99% of the bad shuffles.

So, how do we implement our algorithm? Let's look at the code I came up with:

  
dim freq(13) as integer
dim i, j as integer

const max = 5

// Count how many of each card in pyramid
for i = 1 to 28
j = gCards(gTheDeck(i)).number
freq(j) = freq(j) + 1
next

// if there are 3+ of any one kind, reshuffle
for i = 1 to 12 // (We can ignore 13, Kings)
if freq(i) > 3 then
return false
end if
next

// Now check for excessive pairs
if freq(1) + freq(12) > max then
return false
end if

if freq(2) + freq(11) > max then
return false
end if

if freq(3) + freq(10) > max then
return false
end if

if freq(4) + freq(9) > max then
return false
end if

if freq(5) + freq(8) > max then
return false
end if

if freq(6) + freq(7) > max then
return false
end if

return true

What does this do? First, it defines an integer array, freq, which has 13 elements (not counting the zeroth, which we don't use). Then we count through the 28 cards in the pyramid. For each card we get the number value (1 to 13, Ace through King) and use that as the index for freq, and we increment that element by one.

The result is that when we're done examining the pyramid cards, we have an array in which each element contains the number of cards that appear in the pyramid. The type of card corresponds to the array's index. So if there are three Aces and two Tens in the pyramid, freq(1) would contain 3 and freq(10) a 2.

The next thing we do is check to see if there are four of any single card (we ignore Kings, since they don't require a match to be removed). We simply count through our freq array and if any element has a number higher than three, we return false for our method (meaning the shuffle is bad).

Then what we do is see how many matching pairs of cards there are in the pyramid. We're not just looking for the actual number of matches, but for the total of two kinds of cards. For instance, Aces and Queens are a match, so we add up the number of Aces and the number of Queens. If that's greater than max, our constant, we return false for a bad deal.

We do this for every type of possible match: Jacks and Twos, Tens and Threes, etc.

You'll notice I have used a value of 5 for max. That means if there are more than five matching cards (i.e. at least three of each), we mark the shuffle as bad. So five would be okay: three Aces and two Queens would be an okay deal.

How did I come up with this value for max? Through calculation and a little trial and error. You are free to do your own testing and modify max if you think a different number is better. For example, a value of four would reject the three Ace/two Queen deal above, which you might think is too difficult a shuffle. Basically, the lower the value the easier the game.

Which brings us to a very important point: what we are doing in our safeGame method is essentially cheating. We're taking some of the random chance out of the game. When I first started playing pyramid, I found that games were easier if there was a King at the top of the pyramid. So I used to hit the "Deal" button until I got a "good" shuffle. Obviously, that only helped me on the first pyramid, and it was no guarantee I'd clear the pyramid, but it did make the game slightly easier.

In the case of RBU Pyramid's safeGame method, we're not cheating to the point of making sure an easy shuffle is dealt, but because our algorithm isn't 100% accurate -- it does eliminate winnable games -- the result is we never see some of the more challenging shuffles. Remember, the toughest Pyramid games are when there are several of the same card in the pyramid and the player has to use their Deck resources wisely. With safeGame active, a player may never encounter one of those tough but winnable games.

Adding Safe Shuffle Menu Option

The solution to our "safeGame equals cheating" problem is to let the player decide. Some players may want it on, others may keep it turned off. So let's add an option to turn this feature on/off via a menu.

Open the menu item within your project window and go to the Options menu. In the blank area at the bottom, type "Safe Shuffling" and press Return. Go back to the blank area and put in a hyphen (that's a minus sign) and press Return. REALbasic will add a separator to the menu. Drag both items to the top so the menu looks like this:

Excellent. Since we're going to need a way to remember the current state of safe shuffling, let's add a global property to reflect this. Open globalsModule and add a new property (Edit menu, "New Property") and name it thusly: gSafeShuffle as boolean.

You can close globalsModule now.

Let's enable the menu. Open app and go to the EnableMenuItems event. Put in this code anywhere you like:

  
OptionsSafeShuffling.enabled = true
OptionsSafeShuffling.checked = gSafeShuffle

This will enable the menu and make sure that its checked state (whether there is a checkmark by the menu item) reflects the current value of gSafeShuffle.

Now that the menu's enabled, we must do something when the user chooses it. Add a menu handler (Edit menu, "New Menu Handler") and choose "OptionsSafeShuffling" in the popup menu that's presented.

Use this for the handler's code:

  
gSafeShuffle = not gSafeShuffle

This just toggles (inverts) the safe shuffle mode. If it's on, it's turned off. If it's off, it's turned on.

Calling SafeGame

It's cool we've got a menu and all, but you may have noticed we have yet to actually call safeGame at any point in our program! If you run RBU Pyramid right now, it won't act any differently than before. The "Safe Shuffling" menu option will be available and will check and uncheck properly, but the game won't actually use any safe shuffling.

Go to our freshDeck routine in globalsModule. Right at the beginning you'll find code that looks like this:

  
// Build a full deck and shuffle it
shuffleDeck

Replace that with this:

  
// Build a full deck and shuffle it
if gSafeShuffle then
do
shuffleDeck
loop until safeGame
else
shuffleDeck
end if

See the difference? Before we just called shuffleDeck once. Now we check to see if safe shuffling is turn on. If it isn't, we just call shuffleDeck as before. But if it is, we run shuffleDeck multiple times until safeGame reports that we've got a good shuffle.

(So you can see by this that if our safeGame routine was too strict or too slow, dealing a new game's cards could take a while.)

That's all we've got time for this week. Your homework assignment is to play RBU Pyramid and test out the differences between shuffles with Safe Shuffle on and off. Enjoy!

If you would like the complete REALbasic project file for this week's tutorial (including resources), you may download it here.

Next Week

In Polishing, Part III, we'll get the game gleamingly clean.

Letters

This week's letter is from David, who sounds like he's having a real problem with REALbasic.

REALBasic community,

I have some questions concerning RB. I bought RB a year ago, plus 3 books, for a total of $200. The book has instructions for creating a demo, an application that claims to be an editor. Along the way, the instructions indicate that you should run this demo in debug, open a file, and attempt to edit it. This never worked!

Initially when you run the demo, a code listing appears with a line highlighter, indicating that it doesn’t recognize the keyword in that piece of code. So you either have to declare all variables in every piece of code, as there is no place to declare global variables.

If you are running RB, building this demo editor, and attempt to open a text file and edit it, you crash the system! Something is missing here. If you are running the compiler or assembler or whatever this is, and attempt to run an editor (like WORD or WORKS or AppleWorks) at the same time, no doubt there will be a conflict.

I have seen a listing containing names of applications presumably created with RB, and the email address of the programmer who invented them. I have sent emails to a few of these people, and to the company. Apparently no one, not any of the people in the company nor any of the programmers found in the listing have ever built this demo file.

I am use to creating a program where code is entered in a text file using an editor, which is then interpreted or compiled. The entire contents of the file can be listed on a terminal or lineprinter. Not so here, as the code is hidden in hundreds of places. How does anyone know what little piece of code is related to what? You can't print it out. Is it possible to locate all data entry, then save a copy of that to a text file, and attempt to create a listing from that? Again there is a problem because we are running this RB assembler/compiler, and a text editor at the same time.

Is it necessary to do screen dumps of every code listing, then print the pictures, and retype in the text from the picture into a text file? Maybe the pictures could be printed and scanned in as a text document, etc. I was hoping that there was something more sane. Got any clues? Have you attempted to do this demo editor application that is listed it the book from RB? Thanks in advance.

David Blish

Sorry to hear you're having such a problem getting going with REALbasic, David. Might I suggest you begin with the first few lessons of REALbasic University, which are designed for the very beginner?

You don't specify which book or demo you were attempting to build, so I can't help you with that exact situation (if you'd like my help with that demo, let me know which one it is). I can, however, attempt to explain a bit about REALbasic's philosophy of programming, which might help settle some of your frustration.

You are correct that code in REALbasic is scattered throughout the program. From some perspectives, that's a disadvantage. For instance, it's almost impossible to have a traditional "code listing" of a REALbasic program. You can "Export Source" via the File menu, but the code is missing many of the property settings and all of the user interface and imported elements, and thus is almost useless for recreating a full project. Newer versions of REALbasic include an option to save your project as XML, which can be a way to edit the code outside of the REALbasic IDE. (But with RB's terrific "autocomplete" feature, why would you want to edit code elsewhere?)

It would certainly be easier for me to teach REALbasic if code was all in one place, but in truth today's high-level languages are all becoming similar to REALbasic in that there's code in multiple places (C has dozens of header files, for instance, almost every modern programmer works in some sort of application framework, such as Codewarrior's PowerPlant).

Instead of fighting REALbasic's "scattered" nature, embrace it. The advantages far outweigh the occasional disadvantage. It's part of Object-Oriented Programming. REALbasic makes that metaphor even easier to absorb by embedding code within actual interface objects. A button, for instance, has the code inside it that gets executed when it is clicked: that makes intuitive sense.

Yes, there are plenty of other places code can hide that aren't as obvious, but the metaphor is still the same, the problem is that it's difficult to create a physical representation for an abstract concept, like the "Open" event, for instance.

I don't pretend that understanding REALbasic's system of programming is child's play or that you'll get it overnight, but it does make sense and once you're used to it, it's a very natural way of working. (It took me less than a week of regular use to become used to it, but then I'd had some experience with HyperCard, which used a similar metaphor. REALbasic is actually much better at displaying your code than HyperCard ever was, however.)

Best of all, once you become competent at programming in REALbasic, you can create your own "reusable objects" -- modules and classes you write once and use in multiple programs. That saves you eons of time and is exactly why object-oriented programming has taken over the software development world.

Finally, you mention REALbasic's "inability" to create global variables. Nonsense: any property added to a module is inherently global. You can add a property (variable) to a window, and that variable's available to all routines in that window, but what if the user closes the window? But modules can't be closed by the user so they're always global.


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
is an author, philosopher, graphic designer, photographer, film director, soccer fanatic, and programmer (among other things). He writes for MacOpinion, runs his own software company, Stone Table Software, which sells the revolutionary Z-Write word processor, and is Publisher and Editor of REALbasic Developer. He lives in Northern California with his cats, Mischief and Mayhem, and is rapidly running out of free time.

See the REALbasic University Archives


REALbasic University contents ©2001-2004 by Marc Zeedar and REALbasic Developer. All Rights Reserved.

.

.