Previous

Using loops to make a number guessing game.

Next

The most important thing about computers is that they do things quickly.

In particular, they do things over and over quickly, but just a little differently each time.

If Professor Umbridge had let Harry Potter use a computer, instead of the magic pen, to write "I must not tell lies", Harry would have finished in plenty of time for Quidditch practice.

The Tcl/Tk command to tell the computer program to do something a given number of times is the for command.

The for command is a combination of the set command, the if command and a do-it-again command.

The command looks like this:

for {initial conditions} {test} {changer} {commands}

The initial conditions uses the set command to define how the loop will start. Usually this is setting the value of a variable to be the value we want to start our loop. It will look something like this:

set counter 0

The test part is the same as the test used with the if command. The computer uses this test to figure out when it's finished the loop. As long as the test is TRUE, the computer stays in the loop, when the test is FALSE, the computer goes on to the next line in the program.

A test will look something like this:

$counter < 10

The changer is some Tcl commands to change the value of the variable we use in the test. It will look something like this:

set counter [expr $counter + 1]

Finally, the commands are what we want to do each time we go through the loop.

For instance, Harry Potter, could have done something like this:


for {set counter 0} {$counter < 1000} {set counter [expr $counter + 1]} {
  tk_messageBox -type ok -message "I will not tell lies"
}

This code will start out by setting a variable named counter to the value 1. Then it tests to see if the value of the variable counter is less than 1000. The value is less than 1000, because we just set it to 1, but the computer doesn't know that.

Next the computer will evaluate the commands - in this case that's showing the message box and waiting for the user to click the Ok button.

After running the commands the computer will run the command we put in the changer part of the for loop. In this case, that changes the value in counter from 1 to 2.

Finally the computer does the test again, and if it's TRUE, does the body again, then the changer, then the test, until finally the test is FALSE.

Professor Umbridge would need to click the Ok button 1000 times in order to see if Harry had finished his lines. (That would serve her right!)

Lets use a for loop to make a new version of the number game that goes from 1 to 10, and will tell you if you guess too high or too low.

This program puts the buttons in a single column from top to bottom, instead of on a single row from left to right. We'll look at ways to make it look better with grid after we look at this program.

The program will look like this when it's running.

Just below here is the code that makes this program. You should notice a few things in this code.

The commands part of the for loop has an open curly brace at the end of the first line. After that the code is indented until the final closing curly brace at the end.

The first curly brace needs to be at the end of the line, and it needs a space in front of it, just like the if command does. The indentation is a common practice that makes programs easier to read. You don't need to do it, but your life will be simpler if you do.

The button commands use $counter as part of their name. This is an easy way to name widgets that you create in a loop. The .b$counter tells Tcl to get the value from the variable counter and append that to the .b to make a variable name. The names of the buttons will be .b0, .b1, .b2, etc.

The button commands are kind of long. In Tcl/Tk, we put a command on a single line if we can, but if the command is too long, we can break it up by putting a backwards slash as the last character on the line. That tells Tcl/Tk that this command continues on the next line until there is a line that doesn't end with a backward slash.


# Show two labels to explain the game.
label .info1 -text "I've got a secret number."
label .info2 -text "Can you guess it?"
grid .info1 .info2

# Calculate a secret number between 1 and 10
set secret [expr 1 + int(rand() * 10)]

# Build 10 buttons and grid them.

for {set counter 1} {$counter <= 10} {set counter [expr $counter + 1]} {

  # We'll execute this code 10 times.
  # The value of counter will either be 
  #   larger, smaller or equal to the secret.
  # On each pass, only one of the if statements will be TRUE.
  #   so on each pass, we'll create one button.

  if {$counter < $secret} {
    button .b$counter -text "$counter" \
        -command {tk_messageBox -type ok -message "Too Low"}
  }

  if {$counter > $secret} {
    button .b$counter -text "$counter" \
        -command {tk_messageBox -type ok -message "Too High"}
  }

  if {$counter == $secret} {
    button .b$counter -text "$counter" \
        -command {tk_messageBox -type ok -message "You Win"}
  }
  
  # We've created a button, so we should display it.
  grid .b$counter
}

This game may not be as exciting as Worlds of Warcraft, but it's a real game now.

Take a break and try putting this into Komodo Edit and play with it a little. You might try making it just run to 5 or up to 15. You can even try making it use 50 or 100 if you like, just to see what will happen.

We can make it look better with some new arguments to the grid command. Something like this is easier to read:

You can do a lot with the grid command to make a program look better. We've used the grid command in a pretty simple way so far. Now, it's time to do something more complex.

The grid command thinks of the GUI like a big checker board with rows and columns where it can put a widget. The rows are lines. The first line (0) is at the top and they count down as you use grid to place widgets in higher numbered rows.

The columns go across. They start with 0 being the leftmost column, and the column number gets larger as you move to the right. You can tell the grid command what position to put a widget in just like you'd put a checker on a square in a checkerboard by saying "two rows down and 3 columns over".

The grid command supports argument pairs the same way that the button and label commands do. These arguments tell the grid command more details about what we want to do.

The -row argument lets you tell grid what row to put a widget on, and the -column argument tells grid which column to use.

You can also tell the grid command that you want a widget to stretch across several rows or columns. Kind of like if you laid a pencil across 4 squares on a checkerboard. You do this with the -columnspan or -rowspan options.

We use 3 of the options to the grid command in the next game.
-row number Display the widget in row number number
-column number Display the widget in column number number
-columnspan number Display the widget across number columns

Here's some sample code and the widget layout it creates:


label .l1 -text "I'm Row 1, Column 1"
grid .l1 -row 1 -column 1
label .l2 -text "I'm Row 2, Column 2"
grid .l2 -row 2 -column 2
label .l3 -text "I'm Row 3, Column 1"
grid .l3 -row 3 -column 1
label .l4 -text "I spread across both columns in row 4"
grid .l4 -row 4 -column 1 -columnspan 2

Here's the description of how the grid command works:

Syntax: grid .widgetName -option value
Places a Tk widget on the screen in the described row/column
-row rowNum Display the widget on a given row
-column colNum Display the widget on a given column
-columnspan count Span across count columns
-rowspan count Span across count rows
-sticky nsew Make one or more edges of the widget stick to that boundary of the cell that contains the widget

Here's the program that uses these fancier grid commands to make this good looking game.


# Create a label
label .info -text "I've got a secret number. Can you guess it?"

# Grid this label so that it goes across 10 columns 
#   one for each of the 10 buttons.
grid .info -row 1 -columnspan 10

# Calculate a secret number between 1 and 10
set secret [expr 1 + int(rand() * 10)]

# Build 10 buttons and grid them.

# Go through this loop 10 times.

for {set counter 1} {$counter <= 10} {set counter [expr $counter + 1]} {
  # Each pass through the loop, only one of these if tests will be
  #  true, so only one button will be created.

  if {$counter < $secret} {
    button .b$counter -text "$counter" \
        -command {tk_messageBox -type ok -message "Too Low"}
  }

  if {$counter > $secret} {
    button .b$counter -text "$counter" \
        -command {tk_messageBox -type ok -message "Too High"}
  }

  if {$counter == $secret} {
    button .b$counter -text "$counter" \
        -command {tk_messageBox -type ok -message "You Win"}
  }
  
  # Grid the button we just created.
  #   Notice that we are using counter to see if the button matches
  #   the secret, as the text in the button, and as the column to
  #   grid this button on.

  grid .b$counter -row 2 -column $counter
}

Notice that we've only got one label now, instead of two, and that we use the -columnspan argument to grid to make it spread across all the numbers.

Try typing this program into Komodo Edit to see how it works. Try changing the number of secret numbers to 15. You'll need to change 3 places in the code to make this work and look nice.

We can make this code a little bit simpler by changing the code in the action part of the if command. The example above repeats the button command 3 times (too high, too low, and you win). The only difference between these commands is the message.

Why not just change the message, and have a single button command instead? We can assign the message we want to display to a variable inside the if command action, and then build a button with the chosen message.

That version of the program looks like this. It's just a little simpler.

There's a tricky part in this that I'll describe after you look at the program. In particular, look at the button command.


# Create a label
label .info -text "I've got a secret number. Can you guess it?"

# Grid this label so that it goes across 10 columns 
#   one for each of the 10 buttons.
grid .info -row 1 -columnspan 10

# Calculate a secret number between 1 and 10
set secret [expr 1 + int(rand() * 10)]

# Build 10 buttons and grid them.

# Go through this loop 10 times.

for {set counter 1} {$counter <= 10} {set counter [expr $counter + 1]} {
  # Each pass through the loop, one of these if tests will be true, 
  #  and message will be assigned a new value

  if {$counter < $secret} {
    set message "Too Low"
  }

  if {$counter > $secret} {
    set message "Too High"
  }

  if {$counter == $secret} {
    set message "You Win"
  }
  
  button .b$counter -text "$counter" \
        -command "tk_messageBox -type ok -message \"$message\""
  # Grid the button we just created.
  #   Notice that we are using counter to see if the button matches
  #   the secret, as the text in the button, and as the column to
  #   grid this button on.

  grid .b$counter -row 2 -column $counter
}

You may be wondering why I put the backslash (\) in front of the quotes for the message.

Here's the reasons:


Here's one for you to think about - change this game so that it uses the numbers from 1 to 100, and put each 10 numbers on a separate row. You can do this with another variable to hold the value for the row, or using the expr command to calculate a row based on the $counter number. You'll need to use the % operator in expr to calculate the column.


Here's the important points in this lesson:


In the next lesson, we'll make the computer talk to us, instead of using the popup tk_messageBox commands.

Have fun.



Previous

Next


Copyright 2007 Clif Flynt