Previous |
| 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:
|
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:
|
Or we can use a loop like this:
|
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
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:
|
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:
|
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.
|
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:
|
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:
|
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.
|
That's all the procedures in this program. The complete code looks like this:
|
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.
if
command can have an else
clause that
contains commands to run if the test is not true.
-padx
and -pady
options
that you can use to make a prettier GUI.
-background
command
that will change the normal background to a different color.
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 |