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 038

RBU Pyramid XI

Lately we've been concentrating on adding some "whiz-bang" features to RBU Pyramid, such as last week's ability to change the game window's background graphic. For the next few lessons, we're going to concentrate on finishing up the core of the game, to make it more playable. Some of this will be ugly but important stuff, like adding an undo system, and some will be more graphical, such as the help system and a high score mechanism.

Before we begin, however, some of you may have noticed that the original RBU Pyramid beta application posted in column I expired at the end of November. My original intention was to have the final version released before that date, but several things conspired against that, including a bout of the flu I'm currently enduring (if part of this column sounds irrational, it's probably fever talking ;-). But I'll have a new final version posted as soon as it's finished (in the next week or so).

Adding Available Moves Detection

This week we're going to add an incredibly useful feature to RBU Pyramid: move detection. Move detection just means our program can tell what valid moves are available. That sounds simple, but just getting a computer program to understand the rules of a game is tough.

From a programming perspective, move detection must analyze every possible card match and see if the two cards are a valid match (the card values add up to 13).

From the player's perspective, move detection works like this: holding down the Shift key at any time during the game will highlight any available card matches.

There's another key benefit to the move detection feature: it will tell us when there are no available moves, meaning the game is over. That's helpful: is there anything more annoying than playing a game where you've reached a dead end and yet the computer doesn't tell you that?

So the first thing we're going to do today is to add a global "game over" property. Open up globalsModule and add this new property (Edit menu, "New Property"): gGameOver as boolean.

Now find your updateCards method inside gameWindow. Scroll through it until you see the comment reading "// See if pyramid has been won". In the blank space above that line, we're going to insert a few new lines of code. (Make sure you add this before the comment but after the "next // row" which should be the previous line.)

Here's the new block of code to insert:

  
// See if there are any moves left
if not gFirstTimeThrough and uBound(gTheDeck) = 0 then
findAvailableMoves
if gGameOver then
endOfGame
end if
end if

What this does is check to see if the deck is empty (no more cards to draw) and that we've already gone through the deck once. If so, we scan for available moves. If the game's over (no available moves), we call an end of game routine. The effect of this is that the game will stop when there are no more moves left.

Since we're referring to the endOfGame method, let's add it. Go to the Edit menu and choose the "New Method" command. Name it endOfGame. For today, we're just going to leave it blank. (But we need to add the method, otherwise REALbasic wouldn't let your program compile since it wouldn't know what endOfGame meant.)

Do that same thing again, and this time name the method findAvailableMoves. While we're at it, go ahead and add a showAvailableMoves method as well.

Since the showAvailableMoves is simpler, we'll add its code first:

  
dim i as integer

findAvailableMoves

if showingMoves then

for i = 1 to 31
if cardCanvas(i).highlight then
cardCanvas(i).refresh
end if
next

else

for i = 1 to 31
if cardCanvas(i).highlight then
cardCanvas(i).highlight = false
cardCanvas(i).refresh
end if
next

end if

This method will be called when the user holds down the Shift key: it will redraw the pyramid with playable cards highlighted. Since the findAvailableMoves method sets the highlight property of each card, all we have to do is check that property and force the card to redraw if it's true.

If showingMoves is false (meaning the Shift key is not down), we turn off the highlight setting of each card and force the card to redraw non-highlighted.

That's pretty straightforward. But showingMoves is a new property: we must add it to gameWindow. Go to the Edit menu and choose "New Property" and type in showingMoves as boolean.

Finding Valid Moves

Now we come to the complicated part: the findAvailableMoves method is where all the key work is done to calculate if a move is valid or not. Here's the code -- I'll explain it in a minute:

  
dim i, j, n as integer

for i = 1 to 31
if cardCanvas(i).highlight then
cardCanvas(i).highlight = false
end if
next

for i = 1 to 28
if cardCanvas(i).topCard and cardCanvas(i).visible then
// is a King?
if gCards(cardCanvas(i).card).number = 13 then
cardCanvas(i).highlight = true
end if

// temp discard
if cardCanvas(30).clickable then
if gCards(cardCanvas(i).card).number + gCards(cardCanvas(30).card).number = 13 then
cardCanvas(i).highlight = true
cardCanvas(30).highlight = true
elseif gCards(cardCanvas(30).card).number = 13 then
cardCanvas(30).highlight = true
end if
end if // cardCanvas(30).clickable

// main discard
if cardCanvas(31).clickable then
if gCards(cardCanvas(i).card).number + gCards(cardCanvas(31).card).number = 13 then
cardCanvas(i).highlight = true
cardCanvas(31).highlight = true
elseif gCards(cardCanvas(31).card).number = 13 then
cardCanvas(31).highlight = true
end if
end if // cardCanvas(31).clickable

for j = 1 to 28
if j <> i and (cardCanvas(j).topCard and cardCanvas(j).visible) then
if gCards(cardCanvas(i).card).number + gCards(cardCanvas(j).card).number = 13 then
cardCanvas(i).highlight = true
cardCanvas(j).highlight = true
end if // total = 13
end if // j = i
next // j
end if // cardCanvas(i).topCard

// Check for overlapping match
if cardCanvas(i).row < 7 and (not cardCanvas(i).topCard) and cardCanvas(i).clickable and cardCanvas(i).visible then
n = i + cardCanvas(i).row
if cardCanvas(n).clickable then
cardCanvas(n).highlight = true
cardCanvas(i).highlight = true
end if
n = i + cardCanvas(i).row + 1
if cardCanvas(n).clickable then
cardCanvas(n).highlight = true
cardCanvas(i).highlight = true
end if
end if // is clickable but not on top
next // i

// Check if discard cards match
if cardCanvas(30).card > 0 and cardCanvas(31).card > 0 then
if gCards(cardCanvas(30).card).number + gCards(cardCanvas(31).card).number = 13 then
cardCanvas(30).highlight = true
cardCanvas(31).highlight = true
end if
end if

// Check for end of game
gGameOver = false
if not gFirstTimeThrough and uBound(gTheDeck) = 0 then
gGameOver = true // assume no moves left
for i = 1 to 31
// if we find a highlight, that means there's a move left
if cardCanvas(i).highlight then
gGameOver = false
end if
next
end if

Yes, that's a lot of code for something that seems like it should be simple! But it's not as bad as it seems once you understand what's going on. Let's step through it and see.

The first loop simply sets the highlight property to false for every card. That's simple enough.

Then we start a for-next loop through the first 28 cards (the pyramid, not counting the discard piles). We check for the simplest thing first: a king (removable by itself). If the card is a topCard (not covered by another card) and it's a king (number = 13) then we set its highlight to true (meaning it's a valid play).

Next, we want to see if a playable card in the pyramid can combine with a discard card to be a match. So first we only examine playable pyramid cards by restricting our examination to topCards that are visible (therefore playable).

We add the value of card i of the pyramid with the card on top of the temporary discard pile. If the two added together equal 13, we've got a match! If they don't, we do a quick check to see if the temp discard card is a king, and if it is, we set its highlight to true. Note that there may not be a card on temporary discard pile: that's why we first check to see if that card is clickable, meaning that it's a playable card.

We do the same thing again with the main discard pile: simple.

But we still haven't checked for all possibilities yet: we haven't compared cards in the pyramid to other cards in the pyramid. That gets a little more complicated.

To do that, we start a new for-next loop with a variable j, and loop through every card in the pyramid. For each card, we first make sure that i and j aren't the same (otherwise we'd be comparing the same card to itself), that it's a topCard, and that it is visible. That ensures that we're only dealing with playable cards.

Since we already know that card i is playable, now that we know j is playable, we just have to see if the two are a match (they add up to 13). If so, we set the highlight of both cards to true.

We're getting closer, but we're still not finished. Remember, if two cards overlap each other and add up to 13, they're removable, so we must check for overlapping cards. This is more difficult since the underneath card is not a topCard and yet it must be directly underneath the overlapping card for the match to be valid.

Fortunately, we've already done most of the ugly math for this calculation in our updateCards routine. Remember how it calculates if a card underneath another is a match and sets its clickable property to true? (See RBU Column 031 for more on that.)

So we can cheat a little and use that previous calculation. All we do is find a playable card that is not a topCard -- if it's clickable, then it must be part of a match! Then we just find which of the two cards on top of it is also clickable, and we've got the two cards we need to highlight.

The formulas n = i + cardCanvas(i).row and n = i + cardCanvas(i).row + 1 set n to the number of the card on top of card i, so we can simply see if cardCanvas(n).clickable is true or not to tell if we've got a valid match. Simple!

But we're still not quite finished. There's one more possibility of a match: the two discard piles could match each other! So we add a few more lines of code to see if the two added together total 13, and we set their highlight property to true if so.

Finally, we could be at the end of the game: what if there are no more cards to draw and no moves available? We check for this by making sure there are no cards left to draw and that we've already gone through the deck once. If that's the case, we simply look for the any highlighted cards: if there is even one, there's a valid move so the game's not over. If there are none, however, gGameOver = true and the game will end.

Whew! That was a lot of technical stuff, so I hope you followed it well (and I hope I explained it clearly). This does get complicated, but one of the things I've noticed is that REALbasic's object-oriented design greatly reduces the complicity. For instance, with the above, we're referring to cards as though they are real objects, completely ignoring the fact that they are nothing more than collections of data in a computer's memory!

That's what happens as you work with data objects more and more: instead of rationally thinking "Ah, x a property of object y," you begin to think of your object's properties as settings and characteristics of your objects.

Checking for the Shift Key

Your program should run now, but the key new feature -- move detection -- won't actually work until we do one more thing. We must add a way to detect when the user presses down the Shift key.

That gets a little tricky, because pressing the Shift key, while it is a key on the keyboard, does not activate gameWindow's keyDown event. We can manually check to see if the Shift key is down using the keyboard.asyncShiftKey function, but that only is checked when we call it. We'd have to call it constantly for it to do what we want. So how can we do that?

The only alternative is to set up a timer object: each time the timer activates, it checks to see the state of the Shift key. If it's down, it highlights the cards. If it's up, it draws the cards normally. We can set the timer to be called as frequently as we need: ten times a second is good. (It's fast enough the user won't notice a delay and yet not so frequent as to slow down the game.)

Drag a timer object to gameWindow and give it the following settings:

Good. Now in moveTimer's Action event, put the following:

  
dim original as boolean

original = showingMoves
if keyboard.asyncShiftKey then
showingMoves = true
else
showingMoves = false
end if

if original <> showingMoves then
showAvailableMoves
end if

Since moveTimer's code is executed ten times every second (remember, we set its period property to 100 milliseconds), it will be executed multiple times while the Shift key is being held down. That means we don't want just a simple toggle: that would mean the slow and complicated showAvailableMoves and findAvailableMoves routines would get executed ten times a second!

So what we do is create a new variable, original, and store in it the state of showingMoves before we check the keyboard. Then if keyboard state changes showingMoves, the two won't match, meaning we've had a change in state. If that's the case, we redraw the cards by calling showAvailableMoves. That way showAvailableMoves is only called once per Shift key down (instead of dozens or hundreds of times). Slick, eh?

Well, that should do it for this week. The game should run and show you available moves when you hold down the shift key. If you would like the complete REALbasic project file for this week's tutorial (including resources), you may download it here.

Organizing Your Project Window

Oh, one more thing. I've noticed that our project window is getting rather crowded. Let's use a cool feature of REALbasic to organize our project's elements into folder. Go to the File menu and choose "New Folder" three times. Rename the folders like in the picture below, and put the appropriate elements into each folder:

Isn't that much better?

Next Week

We'll continue by adding in a system to remember players' high scores.

Letters

This week we hear from another Marc, Marc Gyarmaty, who writes with a question regarding arrays:

Hi Marc!

I´ve got a little problem with arrays in RealBasic.

example dim a(10) as string a(0)="this is a string standing in position 0"

but now the complete string stands in the index 0. If I display it (e.g editfield1.text=a(0)), the whole string is displayed. I just want the first character (or if a(10), the tenth char).

In C/C++ just 1 character of the string would stand in it. And thats exactly what i want - i just don't know how. I could not find any docs or samples. Do you know how thats works?

thanks in advance,

marc

PS: there is a type 'array', but the compile won´t accept it e.g. dim a(10) as array

I can see how this would confuse you, Marc, if you're familiar with other programming languages such as Pascal or C which don't have a string datatype. In those languages, a "string" of letters is always stored in an array, meaning that the first character in the string is at array index 1, the second at array index 2, etc. While that can be handy for accessing the individual letters of a string, it's a pain because the size of your string is usually fixed (i.e. you can set the maximum length of the array/number of letters in the string, but you can't change that while the programming is running).

The solution to your problem is simple, however. Since a(0) in your example is a string, you just use the mid() function to grab a single letter inside that string. The mid() function is flexible and powerful: you can use it to retrieve a single letter or a series. For instance, the following are valid uses of the mid() function (stick this code in a pushButton if you want to try it).

  
dim s as string

s = "The quick brown fox ran fast."

msgBox mid(s, 1, 1) // first letter
msgBox mid(s, 1, 3) // first three letters

msgBox mid(s, len(s), 1) // last letter
msgBox mid(s, 10, 1) // tenth letter
msgBox mid(s, 10) // everything after the 10th letter

The mid() function takes three parameters: the first is the name of the string you are examining, the second is the position you want to examine, and the third is the number of letters to extract. The third parameter is optional: if you don't include it, it will return all the letters in the string past (and including) the position point. (So in the above, the final line returns the phrase " brown fox ran fast.")

Just remember, REALbasic doesn't use arrays for strings, and thus strings can be any length, up to the available memory (to a maximum of two gigabytes, I believe).


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
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.

.

.