Previous

Tchuka Ruma (gesundheit)

Next

In India and Africa they play several games with some beans and a board with small bins in it. The games all involve moving beans one at a time from one bin to another. The games are called Awari, Mancala, and several other names.

Most of these are games that two people play, like we'd play checkers, chess, Chinese checkers or go.

There's a solitaire puzzle named Tchuka Ruma that we can make a program to play.

When we start the game, a Tchuka Ruma board looks like this:

The way the game works is that on each turn, you take all the beans out of one bin and drop them one at a time into the bins to the right. When you reach the end of the board, you loop back and put a bean into the left most bin, and keep going until you run out of beans.

After one play, taking the beans from the rightmost small bin, the game looks like this:

The larger bin on the far right is the goal. Each time we get a bean into that bin, we get a point, and that bean is out of play. We can take beans out of the left 4 circular bins, but not out of the right-most oval bin.

If the last bean you play goes into an empty, round bin, you lose.

If you can get all the beans in the big bin on the right, you win.

This looks like a pretty easy game to program - and it is.

We can use a button for each of the round bins, and a label to show how many beans are in the right-most bin. That will let us click a bin to move the beans to the right, but we won't be able to click the rightmost bin

The game would look like this when we start:

And like this after one turn:

In the games we've made so far, we store all the information that the game is using inside the graphic widgets. For instance, in the previous number guessing game, we configured the buttons like this:


  .b$buttonNum configure  -state disabled -text $number \
     -command "playTurn .b$buttonNum $number" 

The text to display is the number, and the command associated with the button has that number in it.

This works for simple games, but for more complex (more fun) games, we need to keep the information the game uses and the part the player sees separate.

For Tchuka Ruma, we need to have 5 variables - each variable will contain the count of beans in a bin.

We could do something like naming the variables bin1, bin2, bin3, etc. That would work OK with a game like this where we've only got 5 bins.

But remember that we've got to list every global variable in our procedures. We can put 5 global commands at the start of each procedure (we only need one for this game), but in games like Alien Defense, where there are 50 alien ships at the start of the game, it gets to be a lot of typing.

The solution to this problem is a thing called the array. An array is a special type of variable that holds more variables. We call the whole variable by a name, and then add a second name to specify one of the variables that is being held inside the main variable. We call the parts of an array elements and we call the second name that identifies each of these elements an index.

In this instance, we'll use an array variable named board. The board variable has 5 elements - bin 0, bin 1, bin 2, bin 3 and bin 4. To be simpler, we can just use 0, 1, 2, 3 and 4, and leave out the word bin.

In Tcl/Tk we specify the elements of an array variable by putting the index inside parentheses. When we specify an array variable with the array name and the index, we can treat it just like the variables we've been using so far.

In our program, bins in our board would be board(0), board(1), board(2), board(3), and board(4).

We can use a variable to hold the index. In that case, Tcl/Tk takes the value of the variable holding the index, and uses that to look up the index in the array.

This sounds complex, but it's really not. For instance, to set up a game of Tchuka Ruma, we can use 4 lines like this:


set board(0) 2
set board(1) 2
set board(2) 2
set board(3) 2

Or we can use a loop like this:


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

The advantage of using a loop and a variable to hold the index is that it's easy to expand this to an array with 100 indexes, like we might have for the number guessing game in lesson 13

When it comes time to look at a member of an array, we treat the array variable and index just like a normal variable. We put the dollar sign in front of arrayName(index), and Tcl/Tk knows we want to know what's in that variable.

We could check the contents of the bins with code like this:


for {set i 0} {$i < 4} {incr i} {
  tk_messageBox -type ok -message "There are $board($i) beans in bin $i"
}

One last thing about the arrays (at least, for now). The index is a word - the word can be a number like 0 or a real word like goal.

In this game, we'll use numbers 0, 1, 2 and 3 to index the first 4 bins, and the word goal to index the last bin.

So, after all this, the procedure to initialize the game sounds pretty simple. It would look like this:


################################################################
# 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
}

The next trick will be building the board.

We're going to add some new wrinkles to making buttons and labels to make the display a little nicer than it would normally appear.

By default, the grid command puts the widgets right next to each other. That's usually what we want. But, for this game, it looks a little more like a real board if we puts space between the buttons. There's an option we can use with the grid command to add some spaces.

The -padx tells the grid command to put padding between the widgets in the x dimension. As you might guess, there is also a -pady option.

To make the label for the final bin show up better, we can use the -background option to tell Tcl/Tk to make the label white instead of grey.

The code to build the buttons and the label 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 -text $board($i) -command "moveBeans $i"
    grid .b_$i -row 1 -column $i -padx 3
  }

  label .goal -textvariable board(goal) -width 7 -background white
  grid  .goal -row 1 -column 4 -sticky news -padx 10
}

That leaves just one procedure to write - the one to process a players move.

This is where the rules for the game get implemented. The number guessing games have had very simple rules. The rules for this game are simple, but not very simple.

The rules are:

Here's the code that plays by those rules. Take a look at the if statement where we check to see if the bin number is 4. There's a new thing in that command. We'll look at the else thing just after we look at the code.


################################################################
# 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
}

In the previous games we've used multiple if commands to check if a number was greater than, equal to, or less than the player's guess.

In this game we need to know if the bin is the last one, so we can take that bean out of play and see if the player won, or we need to put the bean in a round bin.

To check which bin the bean is going into, we could have a bunch of lines like this:


if {$binNumber == 0} {
  # do stuff for bin 4
}
if {$binNumber == 1} {
  # do stuff for bin 4
}
if {$binNumber == 2} {
  # do stuff for bin 4
}
# and so forth

But, we can treat all the round bins the same. We really have two conditions - is this the goal bin, or not.

The else is a part of the if command. It tells the if command to do a different set of code if the test is false.

A simplified if/else command looks like this:


if {$binNumber == 4} {
  # put bean in the goal bin
} else {
  # put bean in a normal bin
}

You might also notice that we've got a lot of code, including more if statements inside the if statements.

Each set of code inside curlies is indented further than the previous set of code. This makes it easier to see how the loops and if commands are grouped.

The last thing that the moveBeans procedure does is to call the showBeans procedure. When you write a program using this style - the data is contained in variables, not coded into the GUI - you should write the procedures so that they either change the GUI, or change the data, but never both. This makes it easier to change the way the program looks later without needing to change the code that controls how it plays.

The showBeans procedure looks like this. It loops through the bins and uses the configure command to make the buttons show how many beans are in their bin. We don't need to do anything for the goal bin because we used the -textvariable option with the label, and Tcl/Tk takes care of updating it for us.


################################################################
# 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 -text $board($i)
  }
}  

That's all the procedures in this program. The complete code looks like this:


################################################################
# 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 -text $board($i)
  }
}  

################################################################
# 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 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 -text $board($i) -command "moveBeans $i"
    grid .b_$i -row 1 -column $i -padx 3
  }

  label .goal -textvariable board(goal) -width 7 -background white
  grid  .goal -row 1 -column 4 -sticky news -padx 10
}

initializeGame
buildBoard


Copy/Paste that code into komodo and play with it. There is only one way to win the game. There really is a way to win. Honest.

You'll get tired of restarting the game each time you want to play again - Try adding some commands to the you lose part of the moveBeans procedure ask if you want to play again. If you click yes it should call the initializeGame procedure to reset the game and run it again.

You might also try playing with the -background and -forground options when the buttons and labels are created to make the game look more interesting.


Important points are:

The buttons with a number to show how many beans are in a bin works. But, it looks pretty kludgy.

Next lesson will look at making the game look cooler with pictures of beans in the bins.



Previous

Next


Copyright 2007 Clif Flynt