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 033

RBU Pyramid VI

Last week we got the Deck working, allowing the user to click on it to draw out a new card onto the temporary discard pile (moving any existing card on the temp discard to the main discard). But while we laid the groundwork for the Deck to reset when empty, we didn't actually implement the code to do this, so that will be our first task today.

Resetting the Deck

Open your RBU Pyramid project file and go to the resetDeck method inside gameWindow. Copy and paste in this code:

   dim i as integer 

//
// Rebuild the deck
//

redim gTheDeck(0)

// if there's a card on the tempoary discard, add it to the bottom of the deck
if gTempDiscard.card > 0 then
gTheDeck.append gTempDiscard.card
end if

// Move the discard pile back into the deck
for i = uBound(gTheDiscard) downto 1
gTheDeck.append gTheDiscard(i)
next

// Erase the discard
redim gTheDiscard(0)

gTempDiscard.card = 0
gTempDiscard.clickable = false
gTempDiscard.selected = false
gTempDiscard.refresh

gDiscard.card = 0
gDiscard.clickable = false
gDiscard.selected = false
gDiscard.refresh

gDeck.card = 0
gDeck.clickable = false
gDeck.selected = false
gDeck.refresh

The above is straightforward: we rebuild the empty gTheDeck array from the contents of the main and temporary discard piles. Then we erase the gTheDiscard array (since it's now empty) and set the contents of the two cardCanvas objects for the two discard piles to nothing (making them unclickable and non-selected). Note that we refresh those cardCanvas objects to make sure the new state is reflected in the display (i.e. if they were selected, we force a redraw to draw them as not selected).

Simple eh? Run the program and click on the Deck until it runs out of cards and resets. Go ahead, try it. I'll wait.

Uh oh. You encountered a problem, didn't you? The Deck didn't reset! What's going on? Relax, it's just a little oversight. There's nothing wrong with our code. It's just that last week we added a variable (property) that tells us if it's the first time through the Deck or not. Since we never initialized gFirstTimeThrough to true, it defaults to false, and therefore our resetDeck routine never gets called!

The solution is simple: open globalsModule and go to the newGame method. Insert this code (don't erase what's already there):

   // Initialize game variables 
gFirstTimeThrough = true

Now try running the program again: this time it should let you click through the Deck and display an "R" when the Deck is empty. Clicking on it again will refill the Deck from the discards (watch them go blank) and the Deck is now full of cards again. When you click to the bottom this time, however, it will display a "D" and be finished.

Fixing a Redraw Flaw

While testing the Deck just now, you may have noticed that the display of the Deck is flawed. There's little feedback for when you click on the Deck. But wait a second... didn't we include code for highlighting the Deck when it's click on? What happened to that?

Run the program and watch carefully what happens when you click on the Deck. Depending on the speed of your computer, you may notice that the Deck does get highlighted: but the highlight is so quick it's erased almost before it happens!

The solution is a simple delay between the initial highlight and the redraw. REALbasic doesn't have a delay command, so let's add our own. Within gameWindow add a new method (Edit menu, "New Method") like this:

Put in this code:

   dim tt as integer 

tt = ticks
while ticks - tt < t
wend

The ticks command returns the number of ticks -- 60ths of a second -- since your computer was booted. Here we save the initial ticks value into tt and then do a loop that continually checks to see of our passed value of t ticks have passed. By subtracting the current ticks value with tt, we have the difference and that's what we compare to t.

In Detail

Note that this usually isn't the best kind of wait routine since it freezes all action on your program until it is finished. A better way might be to use a timer control set to delay for a specific amount of time. But in this case we want our program to pause, so this is exactly what we need.

Now we just need to add code to call this wait routine between the drawings of the Deck. Go to our deckAdvance method and find the code that begins with drawCard(index, true). After the subsequent if line, put this code: wait(6).

The entire segment should look like this:

     drawCard(29, true) 
if uBound(gTheDeck) > 0 then
wait(6)
drawCard(29, false)
end if

Try the program now: you should see a brief display of a black Deck before it redraws the gray card background. That's enough of feedback to let the user know they clicked on the Deck (and prevent it accidentally being clicked too many times).

In Detail

How did I figure out that 6 ticks was the appropriate amount of delay? Trial and error: I simply experimented with several numbers and tested them. Six seemed like a good amount.

Feel free to try this: one of the best things about REALbasic is the way you can edit your program while it is running. Just change the wait(6) call to wait(10) or wait(1). Hit the run command to go back to the window and click on the Deck a few times, then go back to your code (click on the code window in the background) and change the amount again. You don't have to quit the program to do this. Keep doing that with different values until you've got a setting that you like.

Adding Sound Feedback

Fixing that drawing flaw reminds me of the importance of feedback. One of the best kinds of feedback is audio: wouldn't it be nice if cards made a sound when you clicked on them?

Let's do that. For today we'll just add sound for the clicking on the cards, but we'll set this up so it will be flexible enough to be easily modified for future sounds. Add a new method like this:

Here's the code:

   select case kind 
case "click"
CLICK.Play
case "deck"
CLICK.Play
end select

This simply looks at the string passed and plays the appropriate sound. (I'm using the same sound for both the Deck click and a card click.)

For this to work, you'll need to add a sound called "click" into your project file. I've included this sound within this week's project file, or you can use your own sound. Just drag the sound from the Finder into your project window.

In Detail

Are you wondering why we write a separate playSound routine instead of just using a direct call to click.play?

There are two key reasons. First, this gives us the ability to easily add a check for a user setting that turns all sounds off. If we didn't do it like this, we'd have to add that check every time we attempt to play a sound.

Second, by not tying the sound file directly to each call for a sound to play, we have the ability to easily change the sounds. For instance, I'm using the same click sound for the Deck clicking, but it could easily be changed to use a card shuffling type sound.

We also could, potentially, add a feature that allows the user to pick the sounds that are used from an internal library of sounds. With the above system we'd only need to modify the code in the above routine to do play the sound the user specified.

If you run the program now, of course, it won't make any sounds. That's because we have never called the playSound routine!

Go to the cardCanvas control and find the mouseDown event. After the first if cardCanvas(index).clickable and index <> 29 then line, put this: playSound("click").

In the deckAdvance method, insert playSound("deck") at the top (before any other code).

Now you should have noise!

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

Next Week

We'll do more with sounds (adding a sound off control) and get a basic scoring system working.

Letters

Our first letter is from Christopher Hunter, who has a question about arrays.

I really appreciate your Real Basic university columns, they provide a kind of learning that I have been missing for a while. (Long ago I learned to program in old-fashioned BASIC from magazines with articles and programs like your RBU columns, but those kinds of magazines are long-gone from the store shelves, and I gave up on programming for a while, because while I could write programs in pascal and a little bit in C I could never manage anything with a graphical user interface until RealBasic came along.)

I had started work on a program, with a lot of information to store, and your information about custom classes has been a lifesaver, helping me keep track of lots of variables in an organized way. I was particularly excited to see that I could put a custom class into a custom class (as in your people/sales example). So I started creating the custom classes to hold my data, and found that the program, instead of being able to refer to the data stored in the sub-classes (my mechClass contains another class structureClass) and when my program tries to alter a variable:

  
gMech.structure.endo=false

I recieve an error message that says "Invalid array method."

Do I need to somehow initialize the class (like I do with mech class "gMech = new mechClass") somewhere?

It is declared in mechClass as per your examples "structure(0) as structureClass."

I hope that this question is coherent enough, I find this problem very confusing.

Ah, I see your problem, Chris. It's very simple: the property structure in your code is an array. In your above line of code you aren't giving it an index.

The correct way to do it would be something like this, where i is a valid index:

  
gMech.structure(i).endo = false

That should solve your problem.

Here's a little chart of error messages and what they mean:

Error Message Meaning Solution
Nil Object Exception You're referring to an object that doesn't exist. You must new the object before referring to it
Out of Bounds You are accessing an array with an index out of range. Make sure that the index you use is within the array's range (i.e. not greater than the array's uBound() value).
Invalid Array Method You are attempting to refer to the method of an object within an array when that object doesn't exist (i.e. arrayname.methodname). Make sure you use an index of the array (i.e. arrayname(index).methodname).

Next, Kevin writes with a suggestion:

Hello Marc.

I've just finished browsing your RBU pages and was interested in your 2 articles on in-house programs. The one project that was most interesting for me was the DVD database. This looks like a very cool little application. I'm curious as to how you create and manage the database. You mentioned that you store it in an xml format so I'm wondering, does this mean that you don't use any of REALbasic's built in database support?

I've been looking at doing a similar thing for my DVD and CD collections and I'm not too sure how to go about storing the data. I don't have the Pro version of RB so I wanted to avoid using the limited database support available to me with the standard version. Any tips or hints?

Would you ever consider using your DVD Database program as the subject of one of your columns? Or possible making the project available for study? That would be very helpful especially if it provides examples of data storage that don't depend on the professional version's database features.

Thanks a bunch! I look forward to your future columns.

--
Kevin LaCoste
kevin_lacoste@yahoo.com

Hi Kevin! Glad you like the DVD project: I will consider your suggestion to use it as the subject for a future column. It's a little unpolished, and the XML aspect of it is a hack (it only supports a subset of "real" XML), but it is an interesting example program. I had thought of interrupting the RBU Pyramid tutorial at some point with a one column project: my DVD program might be a good example (though it does have some complicated aspects).

As to your not wanting to use REALbasic's built-in database support, I concur. I've never used it, or had much of a need for it. I believe it's mostly there for connections to high-end database programs (something I have no experience with). For instance, you could write a REALbasic front-end to an Oracle database.

Like you, I always owned the Standard edition of REALbasic (though I recently upgraded to the Professional version as I'm wanting to explore compiling for Windows). Also, a lot of the terminology in the RB help is database jargon I'm not familiar with, making learning how to use the database features difficult.

As to your own database needs, just write your own! A database is nothing but a collection of data in a particular format. If you define the format, you can read and write the data in that format. If you're not planning on sharing the data with other software, the format of the database is entirely up to you.

For instance, you could use a simple text file for your database. Each line of the database could be a record. You could divide the fields with a tab character (ASCII 9) or any other unique character that's not used in the data, so that you can separate the fields. For example:

  
movie title[TAB]director name[TAB]year[TAB]description[TAB]length in minutes[RETURN]
The Abyss[TAB]James Cameron[TAB]1989[TAB]A deep sea oil rig crew
is drafted to rescue a lost nuclear submarine.[TAB]171[RETURN]
Brazil[TAB]Terry Gilliam[TAB][TAB]The best film ever made.[TAB][RETURN]

The above format is easy to decode with the REALbasic nthField function: just pass it the current line, the delimiter you're using (in this case, chr(9)), and the field number you want.

You can, of course, arrange the data in whatever order you want (and include whatever fields you'd like). The key is to be consistent: if the 5th field is the film's length, you cannot haphazardly insert in a new field after the director name. All records must have the same fields in identical order or you have chaos. You must insert blank fields if a particular movie doesn't have a field's information (with Brazil, in the above, I didn't include the year or length but the fields are still there)

That's one of the reasons I chose to use XML for my DVD data. XML has the advantage of being extensible (that's what the X in the name means) because each field is named. With the above system you only know that the fifth field is the film length because it's the fifth field and you initially decided that it was the length. That forces you to use a strict structure. With XML, you can name each field and they can be in any order. Here's an excerpt from some of my data (note how each film does not necessarily have the same fields):

  
<dvd><title>12 Monkeys</title>
<director>Terry Gilliam</director>
<writer>David Webb Peoples</writer>
<year>1995</year>
<genre>Science Fiction</genre>
<marc rating>10</marc rating>
<id>0196</id>
</dvd>

<dvd><title>Brazil</title>
<director>Terry Gilliam</director>
<writer>Terry Gilliam</writer>
<year>1985</year>
<genre>Science Fiction</genre>
<marc rating>11</marc rating>
<id>0026</id>
<edition>Criterion</edition>
</dvd>

<dvd><title>City Lights</title>
<director>Charles Chaplin</director>
<year>1931</year>
<id>0035</id>
</dvd>

XML is more flexible, but it's also more complicated to decode, which takes time, and the data takes more space on disk (a problem for huge databases). For many databases having a fixed structure isn't a big problem -- I only did it this way for my DVD collection because I wasn't sure what fields I would need, and I wanted to play with XML. In many cases XML wouldn't appropriate for your actual database file, but you might include XML import and export routines in your program.

There are a number of XML classes for REALbasic out there: while I haven't used it (it's a commercial product), Theodore H. Smith's XML Engine looks pretty good.

Finally, we hear from Brett Cheng, with feedback from our new CSS format.

Hi!

I just discovered RBU recently and find it to be a very useful resource. I'm still trying to catch up on the series, but I peeked ahead and notice in #25 you introduce a new format using CSS.

I have a comment on the change:

- the highlighting of glossary words makes them very difficult to read! Blue highlighted blue text is how it shows up, using Explorer 5.0 - I'm using default colour settings so I don't think it's anything particular to my setup. In Netscape 4.7, it doesn't appear much better.

also, the same blue highlighted words don't seem to show up as highlighted at all when you print out the document. i.e. the underline is also gone, unlike regular URL links which do print as underlined - so on the printed page you lose any indication those words are in the glossary.

Perhaps you could highlight in a lighter colour (yellow?), and also retain the link underline? Alternatively, how about italicized blue without the highlighting? Just a suggestion

great series otherwise. Thanks!

My goal was to highlight glossary items differently than standard links, but my original attempt (white text on blue background) made the entire item appear as white-on-white in non-CSS browsers (i.e. invisible). I compensated by making the text black, but you are correct that it's difficult to read.

I've now modified it in a way that should work for everyone: yellow highlight with black underlined text. In browsers that don't support CSS at all or don't support the background color option, it appears as a standard underlined link.


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.

.

.