Previous

Make a game look like a game

Next

The Tchuka Ruma game with numbers works, but it's kind of kludgey.

It would be neater if we had pictures of the bins with beans in them, instead of numbers in the buttons.

We can change just a few lines of code to convert this:

To this:

Believe it or not, those are just buttons. Tcl/Tk can put text, or images or even both in a button. We just need to use some different options when we create the button.

But, in order to make a button with an image in it, we need an image. Or maybe even 9 images.

We can load images into our Tcl/Tk program in a lot of ways. The easiest is to have the image in a file on our disk and tell Tcl/Tk to load it.

Image files come in lots of formats. In the early days of computers, every time someone wrote a program to create images on a computer, they came up with a new way to describe it. There were dozens of different ways to define and image, and nobody could understand anyone else's format.

In the early 1980's a company named Compuserve developed a way to describe an image that they called the Graphic Interchange Format. It was better than a lot of the existing formats, and people started using the GIF format instead of other formats, and it became an industry standard.

Nowadays, there are 3 main image formats.

The GIF style images can be made smallest, so we mostly use these in games.

To load an image into your Tcl/Tk program, you use the image command. The image command has a few optional commands - the most important one is create. The create command can create a couple types of images, but the only one we'll use is the photo command. We can tell Tcl/Tk what to name the image, or we can take a name that Tcl/Tk makes up on its own.

If we've got an image in a file named 0bean.gif and we want to create an image named nobeans, we'd use a command like this:


image create photo nobeans -file "0bean.gif"

We can put that image onto a button just like we put text onto a button, but instead of a -text option, we use a -image option.


button .b -image nobeans \
    -command "tk_messageBox -type ok -message {No Beans}"
grid .b

You can also use a -image option with the label command.


label .l -image nobeans 
grid .l

You can make your own images for Tchuka Ruma bins with beans in them or click here to download a zip file with the images of bins with beans in them.

Download the zip file and unpack it onto your system. Try making some images and then put the images onto buttons and labels. Don't forget to grid the new widgets you make.

So, how do we go about changing our Tchuka Ruma game to use images instead of numbers.

We really don't need to change very much.

The first thing we'll need to do is create the images and save them someplace where we can find them later. We can make a new procedure to load the images, or we can merge the image loading code into one of the procedures we've already written.

Most programs have a bunch of stuff that they need to do once (when the program starts), other stuff that gets done at the start of a game, stuff that gets done when the player takes a turn, and finally, stuff that's done when the when the game ends.

You don't want to do things like loading all the images every time a player takes a turn, and probably not even every time they start a new game.

We could load the images as part of the buildBoard procedure, but it's not a really good way to design the program. A program is easier to modify if a procedure just does one sort of thing (like load images, or draw the board). This keeps procedures small and simple enough that we can understand them next year when we decide to change the program to do something new.

So, we'll add a new procedure to load the images.

The next trick, is where do we store the names of the images?

There are a couple of good solutions to that problem, and both of them involve using an array.

We can make a new array variable (maybe called images and index it with the number of beans shown in the image. Code to create these images and save the image names would look like this.


set image(0) [image create photo -file 0bean.gif]
set image(1) [image create photo -file 1bean.gif]
set image(2) [image create photo -file 2bean.gif]
# ...

Each image create command returns the name of the image it created. The names are something like image1, image2, etc. We don't care what the names are, as long as we've saved that name in a variable that makes sense to us.

Having a new array is a good solution to the problem, but, then we'll need to add another global command to all the procedures that need access to the images, but we won't need to add that line to procedures that only need to access the number of beans. This is a little confusing.

For big programs, where you might have thousands of different types of data, using more arrays is a good idea. It helps to organize the data you're working with. For a program as small as Tchuka Ruma it's better to stick with just one global array and use a different index to reference the image names.

What will happen if we do something like the next code? Remember that we use board(0) to hold the number of beans in the leftmost bin, and board(1) to hold the number of beans in the next bin, etc.


set board(0) [image create photo -file 0bean.gif]
set board(1) [image create photo -file 1bean.gif]
set board(2) [image create photo -file 2bean.gif]
# ...

Ooops. When we put 2 beans into the leftmost bin (0), we'll overwrite the name of the image of a bin with no beans with the number 2.

That won't be good.

So, we'll have to use to use something other than the number of beans in the image as the array index. We could use words like joe, sam, dog, cat and so forth, but that would make it really hard to remember that 0 beans is joe, 2 beans is dog, etc. We need to come up with some index that makes sense.

We can use any word as the index for an array. In fact, we can use any string of letters, numbers, or punctuation marks (except spaces or parentheses) as an array index.

An array that's indexed with words instead of numbers is called an associative array. Mathematicians use arrays that are indexed with numbers (not words). Sometimes mathematicians need arrays that are indexed with sets of numbers.

For instance, a mathematician might describe a button in the 100 button number game by its row and column position. So the button in row 2, column 3 would be button(2,3).

We can do the same things with the Tcl/Tk arrays. We can use two (or more) words, and separate them with commas, periods, dashes, or whatever.

Using commas to separate parts of an array index is usually the best way to make a complex index out of simple parts. That's not a hard-and-fast rule, but if you don't have a good reason to use something else: use a comma.

We can distinguish the array index for our images from the index that references the number of beans in a bin by adding image, to the index like this:


  set board(image,0) [image create photo -file 0bean.gif]
  set board(image,1) [image create photo -file 1bean.gif]
  set board(image,2) [image create photo -file 2bean.gif]
  # ...

The whole procedure looks like this:


################################################################
# proc loadImages {}--
#    Load the button images
# Arguments
#   NONE
#
# Results
#   New array elements are created in the global board array.
#  
proc loadImages {} {
  global board
  
  set board(image,0) [image create photo -file 0bean.gif]
  set board(image,1) [image create photo -file 1bean.gif]
  set board(image,2) [image create photo -file 2bean.gif]
  set board(image,3) [image create photo -file 3bean.gif]
  set board(image,4) [image create photo -file 4bean.gif]
  set board(image,5) [image create photo -file 5bean.gif]
  set board(image,6) [image create photo -file 6bean.gif]
  set board(image,7) [image create photo -file 7bean.gif]
  set board(image,8) [image create photo -file 8bean.gif]
}

The next thing to do is to change the buildBoard procedure.

This is pretty easy. First, we change the button commands to not have a -text option. Then we change the label command to not have any options at all. Our program will use the showBeans procedure to configure the image for the buttons and label after we build the board.

The new buildBoard procedure looks like this:


################################################################
# proc buildBoard --
#    Creates the GUI
# Arguments
#   NONE  
#
# Results 
#   Modifies the screen.  Creates widgets
#
proc buildBoard {} {
  global board
  
  # Build the buttons.
  
  for {set i 0} {$i < 4} {incr i} {
    button .b_$i -command "moveBeans $i"
    grid .b_$i -row 1 -column $i -padx 3
  }
  
  label .goal
  grid  .goal -row 1 -column 4 -sticky news -padx 10
}

We also need to change the showBeans procedure to configure the buttons and labels with an image instead of putting the number in the button. Take a hard look at how the image is selected in this code, then read the discussion just after the code.


################################################################
# proc showBeans {}--
#    Make the GUI reflect the contents of the board array
# Arguments
#   None
#    
# Results   
#   Updates the GUI
#
proc showBeans {} {
  global board
   
  # Update all the buttons to reflact the number of beans
  # in their bin.
  
  for {set i 0} {$i < 4} {incr i} {
      .b_$i configure -image $board(image,$board($i))
  }
  .goal configure -image $board(image,$board(goal))
}

The line to configure button .b_$i looks a lot like the code that would configure the button to show a number. The code that made the button display a number looks like this:


.b_$i configure -text $board($i)

The number of beans in the bins is saved in the board array, indexed by the position of the bin. The variable i holds the bin position we're looking at right now. So, if i has a 0 in it, $board($i) says to get me the number of beans in the leftmost bin (the bin referenced by $i).

Now lets look at the line that configures the button to show an image:


.b_$i configure -image $board(image,$board($i))

Up to the -image part of the line, it looks pretty simple. Then we hit the $board(image,$board($i)). This is actually pretty simple, too. It's just 3 simple things combined into something a bit less simple.

Tcl/Tk will look at the array index from the innermost set of parentheses out. So it will go through these steps:

... That lived in the house that Jack built...

OK, we'll skip the nursery rhymes, but it's the same idea of all the parts referencing another part and getting closer to the part you're interested in. It's not at all uncommon in computer programs to have one variable hold the name or a a value that references another variable.

This is like an index or table of contents in a book - you've got something here, where you can find it easily that tells you where to go next to find something you're interested in.

We don't need to touch the moveBeans procedure. This is the reason that many games have a set of data and procedures to modify the data, and another set of procedures to look at the data and display it for the player.

The biggest and most complex part of the program is usually the code that knows how to play the game. If we have separate procedures for changing the data and displayig the data, we can change the way a game looks without touching the largest part of the code.

Here's this complete program.


################################################################
# proc moveBeans {binNumber}--
#    moves beans from one bin to successive bins.
#    If there are N beans in a bin, One bean will be placed
#    into each of N bins after the start bin.
#    Each time a bean goes into bin 4, it goes out of play.
#    If there are no beans in play (8 beans are in bin 4), 
#    the player wins.
#    If the last bean goes into an empty bin, the player loses.
# Arguments
#   binNumber	The number of the bin to take beans from.
# 
# Results
#   The global variable bins is modified.  
#   The buttons -text option is configured to reflect the number of
#   beans in the bins.

proc moveBeans {binNumber} {
  global board
  
  # Save the number of beans we'll be moving.
  set beanCount $board($binNumber)

  # Empty this bin
  set board($binNumber) 0

  # Put the beans into the bins after this bin.

  for {set i $beanCount} {$i > 0} {incr i -1} {
    incr binNumber 
    
    # If we've reached the end of the board, 
    # go back to the beginning.
    if {$binNumber > 4} {
      set binNumber 0
    }

    # If this is the last bin, update the "goal" bin
    #  Check to see if the player has won.

    if {$binNumber == 4} {
      incr board(goal)
      if {$board(goal) == 8} {
        tk_messageBox -type ok -message "You just Won!"
	exit
      }
    } else {
      # Last bean can't go into an empty bin
      if {($i == 1) && ($board($binNumber) == 0)} {
        tk_messageBox -type ok -message "You just lost"
	exit
      }
      # Put this bean in a bin.
      incr board($binNumber)
    }
  }
  showBeans
}
  
################################################################
# proc showBeans {}--
#    Make the GUI reflect the contents of the board array
# Arguments
#   None
# 
# Results
#   Updates the GUI
# 
proc showBeans {} {
  global board

  # Update all the buttons to reflact the number of beans
  # in their bin.

  for {set i 0} {$i < 4} {incr i} {
      .b_$i configure -image $board(image,$board($i))
  }
  .goal configure -image $board(image,$board(goal))
}

################################################################
# proc initializeGame {}--
#    initializes the game variables.
# Arguments
#   NONE
# 
# Results
#   Modifies the global variable "board"
# 
proc initializeGame {} {
  global board
  # Put beans into the first 4 bins.

  for {set i 0} {$i < 4} {incr i} {
    set board($i) 2
  }

  # Make sure there are no beans in the last bin
  set board(goal) 0
}

################################################################
# proc loadImages {}--
#    Load the button images
# Arguments
#   NONE
# 
# Results
#   New array elements are created in the global board array.
# 
proc loadImages {} {
  global board

  set board(image,0) [image create photo -file 0bean.gif]
  set board(image,1) [image create photo -file 1bean.gif]
  set board(image,2) [image create photo -file 2bean.gif]
  set board(image,3) [image create photo -file 3bean.gif]
  set board(image,4) [image create photo -file 4bean.gif]
  set board(image,5) [image create photo -file 5bean.gif]
  set board(image,6) [image create photo -file 6bean.gif]
  set board(image,7) [image create photo -file 7bean.gif]
  set board(image,8) [image create photo -file 8bean.gif]
}

################################################################
# proc buildBoard --
#    Creates the GUI
# Arguments
#   NONE
# 
# Results
#   Modifies the screen.  Creates widgets
# 
proc buildBoard {} {
  global board

  # Build the buttons.

  for {set i 0} {$i < 4} {incr i} {
    button .b_$i -command "moveBeans $i"
    grid .b_$i -row 1 -column $i -padx 3
  }

  label .goal
  grid  .goal -row 1 -column 4 -sticky news -padx 10
}

loadImages
initializeGame
buildBoard
showBeans


Try typing that code into Komodo Edit, or cut/paste it over and try playing it a few times. If you changed the program in lesson 14 to be replayable, merge your changes into this program.

The real Tchuka Ruma boards have a final bin that's twice as wide as the other bins. You can create your own images using WinPaint, xpaint, Corel Draw or some other program. Try making your some new images for the goal that have larger bins. Be sure to save the image in GIF format.

You'll need to make 9 images. Change the loadImages procedure to load your goal images You'll probably want to use a different word to distinguish the goal images from bin images in the board index. (Maybe set board(goal,0)... instead of set board(image,0).... Finally change showBeans procedure to use the new images.


The important points in this lesson are:

This is getting to be a decent looking game. But, we had to sit down and make up all the images for the buttons. This isn't too bad for 8 beans, but what would you do if there were 48 beans (that's what a 2 person Mancala game needs).

Next lesson, we'll look at letting the computer draw the bins and beans for us.



Previous

Next


Copyright 2007 Clif Flynt