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 032

RBU Pyramid V

In part IV we got RBU Pyramid to let the user select cards and even detect matches, but we didn't do anything with those matches. Let's fix that today.

Removing Card Matches

Open your most recent version of the project and go to the cardMatch method within gameWindow. Delete the temporary code there and replace it with the following:

   // "Delete" the matched cards 
cardCanvas(index).visible = false
cardCanvas(gSelection).visible = false

// Increase the score
gScore = gScore + 2
gSelection = -1
updateCards

Pretty simple, eh? This hides the two selected cards and increases the score by two (one point for each card removed). When we're done we call updateCards to update which cards are clickable.

Note that we have introduced a new global variable here -- gScore -- which represents the player's score. Let's add that to globalsModule before we forget. Open globalsModule (double-click on it) and add a new property (Edit menu, "New Property"): gScore as integer.

Now let's tackle the more complicated routines. Back in gameWindow, go to the discardMatch method. Remember, this is the match routine that's called when one or more of the selected cards is on a discard pile. Why do we need a separate routine for that contingency? Because we don't want to remove (hide) a card on a discard pile.

Here's the code for discardMatch:

  
// It's on one of the discard piles!
//
// Figure out which one is the discard and "delete" the other
// (We don't want to "delete" the discard canvases since they are reused.)

if index <> 30 and index <> 31 then
cardCanvas(index).visible = false
end if

if gSelection <> 30 and gSelection <> 31 then
cardCanvas(gSelection).visible = false
end if

// if it's the temp Discard, we set its contents to zero
if index = 30 or gSelection = 30 then
gTempDiscard.card = 0 // erase card
gTempDiscard.selected = false
gTempDiscard.clickable = false
gTempDiscard.refresh
end if

if index = 31 or gSelection = 31 then
// It's the discard pile
// Delete top card of discard pile
gTheDiscard.remove uBound(gTheDiscard)

if uBound(gTheDiscard) <> 0 then
// Still some left, display top card
gDiscard.card = gTheDiscard(uBound(gTheDiscard))
gDiscard.clickable = true
else
// None left, empty discard pile
gDiscard.card = 0
gDiscard.clickable = false
end if

gDiscard.selected = false
gDiscard.refresh
end if // index = 30

// Increase the score
gScore = gScore + 2
gSelection = -1
updateCards

This routine has to do several things: if first hides the non-discard match (if any), and then it uses the top card on the discard pile. If it's the temp discard pile, which can only hold one card, it removes it and that's it. However, if the match is from the main discard pile, it must remove the top card and set the discard pile to display the next card in the pile. It does this by looking at gTheDiscard to see if uBound is not zero. If it isn't, then it sets gDiscard.card (the actual cardCanvas object of the main discard pile) to the last card in gTheDiscard.

What happens when a player clicks on a king card is very similar but different enough it warrants its own routine. Put this code into the cardKing method:

  
// Is the King on a discard pile?
if index <> 30 and index <> 31 then
// not a on discard pile, so just remove it
cardCanvas(index).visible = false
cardCanvas(index).refresh
else
// It's on one of the discard piles
// Figure out which

if index = 30 then
// Temp discard pile
gTempDiscard.card = 0 // erase card
gTempDiscard.refresh
gTempDiscard.clickable = false
else

// It's on the discard pile!
// Delete top card
gTheDiscard.remove uBound(gTheDiscard)
if uBound(gTheDiscard) <> 0 then
// Still some left, display top card
gDiscard.card = gTheDiscard(uBound(gTheDiscard))
gDiscard.clickable = true
else
// None left, empty discard pile
gDiscard.card = 0
gDiscard.clickable = false
end if // uBound(gTheDiscard) <> 0

gDiscard.selected = false
gDiscard.refresh
end if // index = 30
end if

// Increase score
gSelection = -1
gScore = gScore + 1
updateCards

Note that we first determine if the king match is one of the discards or not. If it isn't, we just remove (hide) it. If it is a discard, we handle it in a similar manner to the way we did in discardMatch.

Good. Try running the program now: it should work and let you remove matches (including kings). However, since we don't have the Deck working, we can't tell if that code works yet since there are no discards. Let's get that Deck working!

Enabling the Deck

Getting the Deck working might not seem complicated, but it is. The Deck affects many other aspects of the program, such as both discard piles. To get it going we'll need to add some code to our existing routines as well as write some new ones. So pay close attention as we'll be jumping around a bit.

First, we need to draw the card deck. Go to our drawCard routine and find the comment: // Deck gets drawn here. Add the following code after that, but before the final end if line.

      
// Empty Deck
if (index = 29 and uBound(gTheDeck) = 0) then
g.foreColor = rgb(0,0,0)
g.drawRoundRect(0,0,w,h,15,15)
g.foreColor = rgb(255, 255, 255)
g.textFont = "Geneva"
g.textSize = 36
g.bold = true
y = cardCanvas(0).height - (g.textHeight \ 2)
if gFirstTimeThrough then
x = (cardCanvas(0).width - g.stringWidth("R")) \ 2
g.drawString "R", x, y
else
x = (cardCanvas(0).width - g.stringWidth("D")) \ 2
g.drawString "D", x, y
end if
else
if reverse then
g.foreColor = rgb(0, 0, 0) // Black
g.fillRoundRect(0,0,w,h,15,15)
g.foreColor = rgb(0,0,0)
g.drawRoundRect(0,0,w,h,15,15)
else
g.foreColor = rgb(100, 100, 100) // Gray
g.fillRoundRect(0,0,w,h,15,15)
g.foreColor = rgb(0,0,0) // Black
g.drawRoundRect(0,0,w,h,15,15)
end if
end if

This drawing routine is simple. We first check to see if gTheDeck has cards left. If it does, we simply draw the back of a card (a gray box). We draw the card back with black if reverse is true (meaning the card is selected -- more on that later).

More complicated is what happens when the deck is empty: here we draw an "R" if the deck is ready to be redealt, or "D" if we've exhausted the deck for the second time (no redeal permitted).

How do we tell if the deck is on its second deal? Ah! Sharp-eyed readers will note that we've introduced a new variable to track this. It's a boolean (either true or false) telling us if we're on the first run-through or not. Open globalsModule and add this property: gFirstTimeThrough as boolean.

That's the property we check in the above to tell if we draw an "R" or a "D".

Our next step in activating the deck is that we need to detect when a person clicks on the deck. Detecting this means we need to modify some code we wrote earlier. Open cardCanvas' mouseDown routine and scroll to the very bottom. Add this code:

    
// Clicked on deck
if index = 29 and (uBound(gTheDeck) > 0 or gFirstTimeThrough) then
deckAdvance
end if // index = 29 (clicked on deck)

This will detect if the user clicks on the deck and the deck has cards left. If so, it calls a method called deckAdvance. Speaking of that, let's add that method now and put this code in it:

    
// Is there an old selection?
if gSelection <> -1 then
// Deselect it!
cardCanvas(gSelection).selected = false
cardCanvas(gSelection).refresh
gSelection = -1
end if

// Are there are cards left?
if uBound(gTheDeck) > 0 then
gSelection = -1

// Is the temp discard empty?
if gTempDiscard.card > 0 then
// Move the temp discard card to main discard pile
gTheDiscard.append gTempDiscard.card
gTempDiscard.card = 0
end if

// Add one to temp discard
gTempDiscard.card = gTheDeck(uBound(gTheDeck))

// Remove last item from deck
gTheDeck.remove uBound(gTheDeck)

gTempDiscard.clickable = true
gTempDiscard.selected = false
gTempDiscard.refresh

// Redraw discard card
gDiscard.card = gTheDiscard(uBound(gTheDiscard))
gDiscard.selected = false
gDiscard.clickable = true
gDiscard.refresh

drawCard(29, true)
if uBound(gTheDeck) > 0 then
drawCard(29, false)
end if
else
// No cards left -- reset the deck if this is first time through
if gFirstTimeThrough then
resetDeck
gFirstTimeThrough = false
end if
end if // uBound(gTheDeck) > 0 (there are cards left in the deck)

updateCards

What does all this do? Well, first it deselects any existing card selection. Next, we check to see if there are cards left in the discard pile. If there aren't, we reset the deck (moving the discard cards back into the deck). If there are cards left, we deal out the top card to the temp discard pile. If the temp discard pile already has a card on it, we first move it to the main discard pile.

Finally, we draw the deck twice: once as selected (reverse mode), and once not. That will give the player visual feedback for when they click on the deck.

As part of this code we are calling a routine named resetDeck. We won't add the code for that until next week, but just so we can get this to compile, add a new method to gameWindow called resetDeck (leave the contents blank).

If you run RBU Pyramid right now, it should work. You should be able to remove matching cards, either from the discards and/or the pyramid, and the deck should advance when you click on it. However, you'll note that the deck won't reset: we'll fix that next week.

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

Next Week

We'll get the deck to redeal, and we'll update the scoring system so that our pyramid game is playable.

News

Matt Neuburg, author of REALbasic: The Definitive Guide, has written an interesting article for O'Reilly on REALbasic for HyperCard users.

In other news, volunteers are hard at work translating REALbasic University into other languages. Floris van Sandwijk has already translated the first few columns into Dutch, and Kazuo Ishizuka is working on a Japanese version.

I'd like to publicly thank these two for their incredible work: translating technical material like programming tutorials is tough. Please let them know you appreciate their efforts! Also, if you notice errors, contact them directly rather than notifying me.

(If you are interested in translating RBU columns into another language, please contact me for permission and instructions.)

Letters

Hi Marc!

I just went through your GenderChanger tutorial and thought it was one of the gretatest tutorials ever. It was challenging but for me was easy to follow along. It taught alot of the hidden things - like seting up your program- that most books don't do.

For example, you set an array's data type to a class - something that I wouldn't know you could do from reading the manual and Matt Neuburg's book.

Anyhow, I decided to take this step a bit further with somwthing I'm working on (it's a calcluator for a page imposition program(Preps) for prepress)

I have a global array using a data type based on a class I made. The array gets appended here and there in the program. The array does seem to increase when I append it ( i test it to see if the values are being sent - and they do get sent). Anyhow. I keep getting NilException errors saying that an item in the array doesn't exists, when I know that isn't so. (again, I test and the program does have values stored in the array but when I try to use them elsewhere in code, the nasty NilException pops up.)

To be clearer, i got a gGlobalArray(i).sSheetSize array (gGlobalArray() as cSheet -- .sSheetSize is a property of class cSheet) I just finished appending the array then get the value of gGlobalArray.sSheetSize. Everything is fine until I save it into a string like so:

  
dim theLine as string
theLine = gGlobalArray(i).sSheetSize

That's where the error happens. Do you have any suggestions? I'm completely stumped. I'm using RB 3.5. Thanks for any suggestions!

Mike

PS. I really enjoyed your PageMaker scripting site. That too was a great site I LOVED to go to. PageMaker is fun to script. I tried my hand at Applescripting Quark but for me it's next to impossible. I don't know why. The handbook Quark has for it is awesome, and the object model is easy to navigate around in, but the measuremnt system is bizarre. For example, for a .5 in wide box the .5 isn't an integer or double. You have to go gymnastics to convert it into a useable number then convert it back to a thing Quark understands.

Thanks for the letter, Mike. I haven't had much experience scripting Quark XPress -- AppleScript's syntax is, frankly, bizarre. The language changes with each app you attempt to script! Debugging is worse than frustrating: it's impossible. PageMaker uses a proprietary language but I still find it easier than using AppleScript to automate InDesign or Quark.

As to your REALbasic problem, I'm not positive without seeing more of your code, but I suspect your problem comes from failing to instantiate your cSheet object. Remember, you not only need to allocate space in the array for the object, but you must use the new command to generate a new instance of the cSheet object.

For example:

  
dim n as integer
dim theLine as string

// Allocates more space in the array
n = uBound(gGlobalArray) + 1
redim gGlobalArray(n)

// Instantiates a new instance of a cSheet object
gGlobalArray(n) = new cSheet

// Now you can work with gGlobalArray(n) safely

You only need to new an object once, but since an array is made up of multiple objects, you must do it for each object in the array. The rule of thumb: any class object must be instantiated with the new command before you do anything with it.

If that's not your problem, there are some other possibilities:

  • You don't reveal above what data type .sSheetSize is, but if it's a class, it needs to be instantiated with the new command.
  • Your index for the array is out of range (i.e. in your code snippet, i is not defined so I don't know if it's valid or not).

It's an interesting problem: let me know if you figure it out. (By the way, since I work in the prepress industry, I'd be curious to see your finished app if you're game. I don't use Preps now, though I did at one time.)


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.

.

.