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 039

RBU Pyramid XII

This week we're going to do something that sounds simple, but isn't: add a high score list to our game. This isn't brain surgery, but as you will see, it effects many areas of our game, making the implementation complicated. In fact, we won't even finish this today -- this will be a two-part lesson. Be prepared as we jump around the code a lot!

The techniques used for adding this high score list are simple and should be reusable in other games.

Adding a High Scores Window

The first thing we'll need is a special "high scores" window where we'll display the list of top scores. This could be a simple dialog box, but that's not as convenient for the player since the scores can only be seen when the game's over. Instead we'll go the extra mile and make our window a floating window -- that's a special kind of window that floats above document windows and is typically used for tool palettes. That way the scores can always be visible (or not, as the player prefers).

With your RBU Pyramid project, add a new window (File menu, "New Window"). Give it the following settings:

(Note that the full title property reads, "RBU Pyramid All-Time High Scores".)

Now open HighScoreDialog (double-click on it) and drag on a listBox control. Give it these settings:

The contents of the initialValue field aren't displayed, so here's what you put there: "Rank Name Score Date". Please note that those aren't spaces separating each item -- those are tab characters.

In Detail

If your HighScoreDialog comes up with incorrect column names, you didn't put in the tabs correctly. There must exactly one between each name. REALbasic will assign each name to a column based on them being divided by tabs.

You could do this manually, within the listbox's Open event, with code like this:

  
scoreList.heading(0) = "Rank"
scoreList.heading(1) = "Name"
scoreList.heading(2) = "Score"
scoreList.heading(3) = "Date"

If you don't do it via code, however, REALbasic will assume the first line of the initialValue property to contain the headings.

If you don't tell RB how to name the headings anywhere, it will just use the default "Column 0," "Column 1," "Column 2," etc.

Now that you've done that, let's initialize our listBox. Open scoreList in the Code Editor (double-click on it). Go to the Open event and put this:

  
// Center rank, score, and date columns
me.columnAlignment(0) = 2
me.columnAlignment(2) = 2
me.columnAlignment(3) = 2

All this does is set the appropriate columns to center alignment. We don't want column 1 to be centered, since that's the player's name and it looks best flushed left (the default).

Next, go to sortColumn of scoreList and put in these two lines:

  
me.headingIndex = -1
return true

That just prevents the user from sorting the listBox. By default, any multi-column listBox in REALbasic will automatically sort itself when the user clicks on the various headings. In our case, we don't want that -- high scores should always be displayed best-to-worst. So we intercept the sort event and tell RB that the headingIndex is really -1, meaning that the user hasn't clicked on a specific column (and therefore there's no need to sort anything).

Remembering a Window's Location

As an extremely particular and organized Mac user, one of my pet peeves is programs that don't remember where I put a window. If I resize a window and put it someplace, it should be in that same place the next time I run that program. This applies especially to floating palettes such as the high scores list we're working on.

There are countless ways you could use to save a window's location info, but we'll take a simple direct route. It's not necessarily any better or worse than any other, though it does integrate well with my preference system.

What we'll do is save our window positioning info in a global property called gScorePos. It's of string type, so open globalsModule and add a new property (Edit menu, "New Property"). Type in this gScorePos as string in the dialog that pops up.

Let's also add another global property: gScoresShowing as boolean.

Good. Now go back to HighScoreDialog and let's add a new method (Edit menu, "New Method"). Name it saveWindowInfo and put this code into it:

  
gScorePos = str(self.left) + comma
gScorePos = gScorePos + str(self.top) + comma
gScorePos = gScorePos + str(self.width) + comma
gScorePos = gScorePos + str(self.height)

All this does is save the window size and location, separated by commas. Simple! (Remember, the special self function refers to the window owning the routine. In this case, that's HighScoreDialog [saveWindowInfo is inside it], so self.left is exactly the same as HighScoreDialog.left.)

But wait a second -- what's a comma? Does REALbasic know what that is? No, it does not: go back to globalsModule and let's add a constant (Edit menu, "New Constant"). Name it comma and give it a value of "," (no quotes).

I warned you we'd be jumping around today! (And we've just gotten started.)

Now we run into a tricky little situation. Basically, what we want to have happen is that when the user resizes or moves the HighScoreDialog, we save that info. So it seems like all we need to do is put in a call to saveWindowInfo within each of the Resized and Moved events, right?

Not so fast. What happens is that when the window is initially opened (opened for the first time), it is resized and moved to the correct position -- and doing that causes REALbasic to fire the Resized and Moved events! If those events save the window position, they overwrite (erase) the original info that we haven't used yet. (If that doesn't make your brain spin, I don't know what will!)

This bug took me a while to find when I wrote the first draft of RBU Pyramid: everything looked fine, but the program wasn't saving the window position info. I finally had to step through the program step-by-step to figure out what was going wrong.

The solution, however, is simple: we just need a flag to tell us when the window is initially opening. So let's add a property to HighScoreDialog (Edit menu, "New Property"): opening as boolean.

Now go to Resized and Moved events of HighScoreDialog. Put in this same code for both:

  
if not opening then
saveWindowInfo
end if

See how that works? If the window's initially opening, it won't call saveWindowInfo. In other words, it will only save the new window details if the user's changed the window's position or size.

But for this to work, we must do some initialization stuff when the window is opened. So go to the window's Open event and put in all this stuff:

  
gScoresShowing = true
opening = true
if countFields(gScorePos, comma) = 4 then
highScoreDialog.left = val(nthField(gScorePos, comma, 1))
highScoreDialog.top = val(nthField(gScorePos, comma, 2))
highScoreDialog.width = val(nthField(gScorePos, comma, 3))
highScoreDialog.height = val(nthField(gScorePos, comma, 4))
end if // countFields = 4
opening = false

See how we've framed the window resizing stuff with opening = true and opening = false statements? That way when we're programmatically adjusting the window it won't save that location info.

As you can see, it's easy to extract the window info from gScorePos. Since each field of info is separated by a comma, we use that as our divider and tell nthField() to grab each field one by one. Since nthField() returns a string and we need a number, we use val() to convert the string type to a number type, and then we assign that number to the appropriate window property. About the only way to screw this up is to do things in a different order: make sure that if your window data is saved in left, top, width, height order that you retrieve it in the same order!

When HighScoreDialog is closed, we'll need to save the current window position and set the gScoresShowing property. This goes in the Close event:

  
gScoresShowing = false
saveWindowInfo

Guess what? We're done with HighScoreDialog. You can run the program and it will work, but of course we haven't done anything with HighScoreDialog yet, so it won't seem any different.

Adding a Menu

The player can't see the high scores window because we haven't displayed it, or given the player any way to display it!

So let's add a menu command to display the window.

Within your RBU Pyramid project, double-click on the Menu object. You should see a menubar within a little window. To the right of the Edit menu within this window, there's a gray rectangle. Click on it to highlight it. Like this:

Now type in "Options" (no quotes) and press return. That should have added an Options menu to your program. On the blank line underneath that, select the blank area and type in "Show High Scores..." (no quotes).

You can give the menu item a Command-key shortcut by typing "H" (no quotes) on the CommandKey property field.

Now all we've done is tell REALbasic we need this menu item -- we haven't told it do anything with it yet. Since the OptionsShowHighScores option is a command that is application-wide (not restricted to gameWindow), we want to add a handler to this command within our app class.

So open App class and add a Menu Handler (Edit menu, "New Menu Handler") -- OptionsShowHighScores should be available on the popup list. Select it and click OK. REALbasic should have added the menu handler underneath the existing AppleAbout handler. If you click on it, we should be able to add code to it on the right side:

  
if gScoresShowing then
highScoreDialog.close
else
gameWindow.showScores
end if

All this does is if the window is showing, it closes it, or if it's closed, it displays it. Simple!

But if you run the program now, you'll find that our new menu command is grayed out! What's up with that?

By default, REALbasic keeps menu items inactive. You must specifically tell RB that the command is enabled for it to be available for the user.

Go to the EnableMenuItems event of app. There should already be a line of code there. Underneath that, add this (separated by a blank line or two, if you want):

  
if gScoresShowing then
OptionsShowHighScores.text = "Hide High Scores"
else
OptionsShowHighScores.text = "Show High Scores"
end if

OptionsShowHighScores.enabled = true

See how clever we're getting? We not only enable the menu item, but we change the text of it so when the window is visible it says, "Hide" and when it's hidden it says "Show." Very slick!

But when you try running the program now, it gives you an error, doesn't it? It says "unknown identifier: gameWindow.showScores". That's because we never created a showScores method within gameWindow!

Open gameWindow and add a new method (Edit menu, "New Method"). Name it showScores. For now, put it highScoreDialog.show for the code -- next week we'll fill in the rest.

That should give you a program that's runable. No actual high scores are displayed (yet), but you can move and resize the window and show/hide it at will. (Of course, the details of the resize are not saved in the preference file yet -- we'll add that capability next week.)

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

Next Week

We add high score loading and saving routines.

Letters

This week we hear from Paul Thompson, who writes with a common REALbasic situation:

Dear Marc

Here's a puzzle.

I know how to select the contents of an editField as you might do on opening a window:

  
namefield.setfocus
namefield.SelStart=0
namefield.SelLength=Len(namefield.Text)

The problem is that you have to put the name of the editField in the method code. What I am trying to do is create a method which could apply to any field. You would call the method with the name of the editField as a parameter, eg:

  
selectText(editField1)

where the method would be:

  
sub selectText (fieldName as ???)
fieldName.setfocus
fieldName.SelStart=0
fieldName.SelLength=Len(fieldName.Text)
end sub

As you can see from my ???, what data type do I send to the method? If I use a string, it comes up with the error "Not an Object".

I suspect there is no way of calling a method with a control as parameter but I hope you know better.

You're so close, Paul. The answer's simple... an editField is a class, a class of "editField". (If you select an editField and look at the "super" property on the Property list it will say "editField".)

So the solution is simple. "EditField" is name of the class! This works:

  
sub selectText (fieldName as editField)
fieldName.setfocus
fieldName.SelStart=0
fieldName.SelLength=Len(fieldName.Text)
end sub

I do this all the time -- as you've figured out, it's a valuable technique!

A variation of this is that you can store an editField's reference inside a variable. For example, you could create a property "cf as editField" and then within an editField's GotFocus handle put in "cf = me".

What good is that? Well, let's say you have a window with multiple editFields. Let's say you have a "Select All" command (like you do above). You want the command to work in all fields, but to do that, it must know which is the current field. By storing the current field in the cf variable, your Select All routine can just be:

  
if cf <> nil then
cf.selStart = 0
cf.selLength = len(cf.text)
end if

You'd probably also want to put a "cf = nil" in each editField's LostFocus event. That way when the user moves out of an editField, cf is set to nil, and when they move into a new editField, cf is set to that editField. And if you set up your editFields as a control array, you only have to put in this code in once no matter how many editFields you have!

Hope this helps.

Next, we hear from David J. Gauthier, who has a question about the REALbasic documentation:

Hi Marc: I did not know where else to ask you this, I hope you don't mind. I was hoping you could tell me if the printed documentation for Real Basic is worth getting.

Thanks,

Dave

That's an interesting question, Dave. Unfortunately I don't know the answer as I never bought it. I think it might be the same as the downloadable PDFs, but simply printed, but I'm not 100% sure on that one.

Either way, I suspect the docs are decent, but not outstanding -- you'd be better off with one of the REAlbasic books out there (either "RB for Dummies" or Matt Neuburg's more advanced book -- see links at the top of this page). You might want to join one of the REALbasic mailing lists and post this one to the crowd there -- I'm sure a few have bought the printed docs and could help you.

If any RBU readers have a comment on this, feel free to let me know your thoughts on the subject.

Our final letter this week, comes from Michael Krzyzek, who has a real doozy of a question:

I've been following the Pyramid game and decided to port a Risk type game that I had helped write for the Newton and later implimented in a primitive way on the Mac.

I found I could draw images into a canvas and apply a mask. This allows me to display irregular areas like countries and continents. What I can't figure out is how to limit the clickable areas of an irregular shape. I thought that it might happen automatically since it seems that your cards are not clickable on the corners, but in my canvas the whole rectangle is clickable.

In my other implimentations I could check whether a specific set of coordinates was in a region or even over a black pixel in a bitmap, but have not found similar functionality in RB.

A corollary question is there an equivelant of a region in RB. A region is basically an area but does not have to be contiguous. Multiple shapes, regular or irregular, can be in the same region.

Thank you in advance for any help,

Michael Krzyzek

Tactile Systems, Inc.

Excellent question, Michael!

Actually, the corners of my cards in RBU Pyramid are active: it's just the corner area is so small it's rare you're able to click within the few pixels between the rectangular canvas edge and the curved corner.

First, REALbasic does not support a region-type graphic. Yes, that's a bummer, since the Mac OS has support for regions and cool routines like "is this x,y point within region y?" making what you want to do easy. It's harder in REALbasic.

Now you could figure out the mathematics and do all the calculations for this kind of thing manually within REALbasic, but it would complicated and (probably) slow.

A simpler method, which is what I would use, has some disadvantages, but should work in most cases.

You mention you have a mask that's the shape of a particular continent. A mask (in REALbasic) has white areas transparent and black areas solid. Since the mask is just another picture, you can examine the clicked point inside the mask and see if the point is black or not. If it's black, we're inside the mask area!

I've written a tiny program demonstrating this: click on North America and it beeps:

But if you click within the clear area of Gulf of Mexico or the Great Lakes, it doesn't beep.

Hopefully this will get you started. For anyone who'd like this code, here's a project file to download.


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.

Email This Article - Comment On This Article

.

Reader Specials

Server Racks Online:
Apple Xserve CompatibleServer Racks and Universal Network Racks
42U KVM Switch Solutions:
High-End Mac and Multi-Platform KVM Matrix switching solutions!
Digital Camera Online:
Great prices on Digital Cameras and accessories!
KVM Switches Online:
Great prices on Mac KVM Switches from the leading manufacturers!
LCD Monitors Online:
Great prices on LCD Monitors from the leading manufacturers!
LCD Projectors Online:
Shop online for LCD Projectors from the leading manufacturers!
USB 2.0 Online:
Great prices on USB 2.0 products from the leading manufacturers

Serious Business Software:
Accounting, Sales, Inventory, CRM, Shipping, Payroll & more!

KVM Switch solutions for MACs:
DAXTEN is a KVM switch, KVM extender and monitor splitter specialist for PC, SUN and MAC applications from name brand manufacturers - offices worldwide.

The "Think Different Store: The iPod Accessories Store - iPod cases, iPod mini, iPod photo, speakers, itrip, inMotion, Soundstage and all other iPod accessories

Earn Cash with the ThinkDifferent Store Affiliates Program

Need A Web Site?
Applelinks Web Hosting Starting at 19.95 a Month

iTunes_RGB_9mm

.

iTunes_RGB_9mm

Cool Mac Gear


iPod 1G-2G
iPod 3G
iPod 4G
iPod Mini
PowerBook-iBook
Keyboard Skins
Garageband