Previous

Keeping track of the score

Next

There are lots of games that can always be won, and the challenge is to win them more quickly.

The last few games are ones that you can always win. The computer will tell you where to guess next, and if you follow the instructions, you'll eventually find the secret square.

We can make the game a little more interesting by keeping track of the player's best score. Now the game is a challenge to see if you can beat your best score. (Of course, the best score will be "1", but that's just a lucky guess.)

If you wrote and played the 10-button guessing game from lesson 12, you probably noticed that sometimes you got the right answer in a couple guesses, and sometimes it took more.

You could keep track of your best score with a piece of paper while you play the game, but that seems kind of silly. Why can't we make the computer remember the score and show us what the best score was.

The answer, of course, is "What a coincidence, that's what this lesson is about."

Keeping track of the score can be done with two global variables - one to track the current score, and one to save the best previous score.

Using the global variables for the current score the best score might be obvious, but we also need to show those score to the user.

There's a Tcl/Tk widget that will do this for us - it's the label widget that we used back in lesson 6 and lesson 8. Labels are like buttons, except that they don't use a -command argument.

You create a label just like you create a button. The first word is the Tcl/Tk command to create a label (label), next comes the name of this label - a unique word that starts with a period and a lower case letter, and then a set bunch of option/value pairs.

Here's some code to make and display a label the way we did it in lessons 6 and 8.


label .l -text "This is my first label"
grid .l

Every graphic widget in Tcl/Tk has a slightly different set of options that it supports. For instance, buttons have a -command option, but labels don't have a -command option.

We've used the label's -text option. The labels also have a special -textvariable option. When we use this option, we tell Tcl/Tk the name of a variable, and this label will always show the value of that variable. When we use the set command to change the variable, Tcl/Tk will show the new value.

Try typing this code into Komodo to see what it does. When you click the button, it changes the value of the myVariable variable, and that changes what's displayed in the label.


set myVariable "Click the button to change this message"
label .l -textvariable myVariable
grid .l
button .b -text "Change" -command "set myVariable {This is a new message}"
grid .b

Lets make a new game. We'll start out fairly simple, and then add a couple bells and whistles to make it better.

The game looks like this:

You play it by clicking buttons. Each button has a number associated with it. Each time you click a button, that number is added to your total score. You get to click 10 buttons, and the goal is to get the highest total score.

Here's the global scope code that sets things up. It sets the best score variable to 0, creates pairs of labels to tell you what the best score, current turn and current score are, and finally makes up some buttons.

Take a close look at the button creation command. We'll look at that line right after the code.


# This is global scope code that sets up the game.
set best  0

# Build and grid the labels
label .l_bestLabel  -text "Best"
label .l_bestValue  -textvariable best

label .l_turnsLabel -text "Turn"
label .l_turnsValue -textvariable turns

label .l_scoreLabel -text "Score"
label .l_scoreValue -textvariable score

grid .l_bestLabel   -row 1 -column 0
grid .l_bestValue   -row 2 -column 0

grid .l_scoreLabel  -row 4 -column 0
grid .l_scoreValue  -row 5 -column 0

grid .l_turnsLabel  -row 7 -column 0
grid .l_turnsValue  -row 8 -column 0

# build and grid the buttons
set buttonNum 0
for {set x 1} {$x <= 10} {incr x} {
  for {set y 1} {$y <= 10} {incr y} {
    button .b$buttonNum -text ?
    grid .b$buttonNum -row $y -column $x
    incr buttonNum
  }
}  
   
# Start the game
startGame

You probably noticed that there is no -command for the buttons in this code. That's unusual, isn't it?

Yeah, it's little unusual, but not completely strange.

When we create a graphic widget like a button or label in Tcl/Tk, we're really creating an object, like the sound objects we made with snack.

In computer terms, an object is a command and some data all wrapped into a single unit. For instance, the sounds we've used have a command (like backgroundMusic) and a set of data to send to the speakers when the program says backgroundMusic play.

The Tcl/Tk widgets are the same sort of thing. We create a new button named .b, and there is a bunch of information associated with the new button (like the text to display and command to run when it's clicked). Tcl/Tk also creates a command named .b, and we can use that new command to change the information stored in the button .b

We do this by calling the .b command with special command options, just like we play a sound by calling the sound command with the command option play.

One of the command arguments that works on all widgets is configure. What the configure argument does is to tell a button to change the value of an option/value pair.

For instance, we can tell Tcl/Tk what command to use on a button when we create it, or we can configure the -command option later like this:


button .b -text "show messageBox"
grid .b
.b configure -command "tk_messageBox -type ok -message {Here's my message}"

We can use the configure command to change what the buttons do after we've made them.

Here's the startGame procedure that sets the score and turns variables and then configures the buttons with a command.

The -command option runs another procedures called playTurn. The playTurn procedure takes a single argument - a number to add to the score.


################################################################
# proc  startGame {}--
#    Initialize the game
# Arguments
#   NONE   
#   
# Results
#   global variables score and turns are modified.
#   Buttons are configured with new values
#   
proc  startGame {} {
  global score
  global turns
  
  set score 0
  set turns 1
  
  set buttonNum 0
  for {set x 1} {$x < 10} {incr x} {
    for {set y 1} {$y < 10} {incr y} {
       set number [expr int(rand() * 100)]
      .b$buttonNum configure -command "playTurn $number"
      incr buttonNum
    }
  }  
}    

The playTurn procedure adds the number associated with a button to your total score. If that's more than the previous best score, it updates the best variable. It also counts the number of turns you've taken and stops the game when you've made 10 guesses.

Here's what the code looks like. Take a look at the lines around the the tk_messageBox. We'll discuss that right after this code.


################################################################
# proc playTurn {val}--
#    process a turn
# Arguments  
#   val         The value associated with this button
#
# Results
#   Global variables score, turns are modified.
#   Global variable best may be modified.   
#   If end of game, the player is prompted to restart.
#
proc playTurn {val} {
  global score
  global turns
  global best

  #update the score
  incr score $val
        
  # If this is a new high score, update best
  if {$score > $best} {
    set best $score
  }
        
  # Increment the number of turns taken.
  # If more than 10 turns, we're at the end, ask player if they want to replay.
  if {[incr turns] > 10} {
    set turns DONE
    set reply [tk_messageBox -type yesno \
        -message "That's all your guesses.\n Play again?"]
    if {$reply eq "no"} {
      exit
    }
    startGame
  }
}

The tk_messageBox command will return the value of the button that the user clicks. When we use a yesno type of message box, it will return a yes or no. The code above is putting up the tk_messageBox, waiting for the user to click a button, and then returning the yes or no depending on the button they clicked.

The if command is interesting. We looked at this in lesson 7 and used the if command to compare numeric values with code like
if {$guess == $secret}.

We can also use the if command to compare strings.

Instead of the number comparison operators like ==, < and >, we use short strings to tell the if command how to compare the two strings.

eqTrue if the strings are the same (EQual).
neTrue if the strings are not the same (Not Equal).

So, the tk_messageBox code in the example says to display the message box, and return the word in the button the user clicks. Then compare that word to "no". If the word was "no", exit, otherwise, we keep playing.

This game is just a guessing game. Sometimes you guess a better score than other times. A good game requires skill as well as luck.

This would be a better game if we could show the values for each button for a few seconds so the player can find the big numbers and then change all the numbers to question marks before the players click.

We can do this with the configure command. Just like we can configure the -command option, we can also configure the -text option.

But we need two more commands to make this work right.

The first command we need is after. This command makes Tcl/Tk pause for a given number of milliseconds (there are 1000 milliseconds in a second). When we use the after command the program stops and the user can look at the screen.

The other command is update idle. This command tells Tcl/Tk to update the display now, instead of waiting until it's not doing anything else. Normally, Tcl/Tk only updates the display when it's not busy calculating numbers, building buttons, or something like that.

This code will make some labels. It pauses for 1 second before making the next label. Try typing it into Komodo to see how it works.


for {set i 0} {$i < 10} {incr i} {
  label .l$i -text "$i"
  grid .l$i
  update idle
  after 1000
}

Now we can make a game that requires some skill as well as luck.

To make the game look and play a bit better, I used a couple new options to the button command.

-state Makes the button normal or disabled. A disabled button doesn't respond to clicks.

-disabbledforeground Defines the color to use for the text in a button when it is disabled. By default a disabled button is gray.

Here's the code for a the full game:


################################################################
# proc playTurn {val}--
#    process a turn
# Arguments
#   buttonName  The name of the button that was clicked.
#   val		The value associated with this button
# 
# Results
#   Global variables score, turns are modified.
#   Global variable best may be modified.
#   If end of game, the player is prompted to restart.
# 
proc playTurn {buttonName val} {
  global score
  global turns
  global best

  # Update the score
  incr score $val
  
  # If this is a new high score, update best
  if {$score > $best} {
    set best $score
  }

  # Show the text for this button and disable it so the 
  # player can't click a big button over and over.

  $buttonName configure -text $val -state disabled
  
  # Increment the number of turns taken.  
  # If more than 10 turns, we're at the end, ask player if they want to replay.

  if {[incr turns] > 10} {
    set turns END
    set reply [tk_messageBox -type yesno \
        -message "That's all your guesses.\n Play again?"]
    if {$reply eq "no"} {
      exit
    }
    startGame
  }
}

################################################################
# proc  startGame {}--
#    Initialize the game
# Arguments
#   NONE
# 
# Results
#   global variables score and turns are modified.
#   Buttons are configured with new values
# 
proc  startGame {} {
  global score
  global turns
  
  # Initialize the score and turns variables
  set score 0
  set turns 1
  
  # Show the buttons and the value associated with them.

  set buttonNum 0
  for {set x 1} {$x < 10} {incr x} {
    for {set y 1} {$y < 10} {incr y} {
      set number [expr int(rand() * 100)]
      .b$buttonNum configure  -state disabled -text $number \
          -command "playTurn .b$buttonNum $number" 
      incr buttonNum
    }
  }
  
  # Update the display
  # raise moves the main window to the top.  Some operating
  # systems are slow about removing the dialog box
  raise .

  update idle
  
  # pause for 5 seconds
  after 5000

  # Now reset all the buttons to show a question mark.
  set buttonNum 0
  for {set x 1} {$x < 10} {incr x} {
    for {set y 1} {$y < 10} {incr y} {
      set num [expr int(rand() * 100)]"
      .b$buttonNum configure -text ?  -state normal
      incr buttonNum
    }
  }

}

# This is global scope code that sets up the game.
set best  0

# Build and grid the labels
label .l_bestLabel  -text "Best"
label .l_bestValue  -textvariable best

label .l_turnsLabel -text "Turn"
label .l_turnsValue -textvariable turns

label .l_scoreLabel -text "Score"
label .l_scoreValue -textvariable score

grid .l_bestLabel   -row 1 -column 0
grid .l_bestValue   -row 2 -column 0

grid .l_scoreLabel  -row 4 -column 0
grid .l_scoreValue  -row 5 -column 0

grid .l_turnsLabel  -row 7 -column 0
grid .l_turnsValue  -row 8 -column 0

# build and grid the buttons
set buttonNum 0
for {set x 1} {$x < 10} {incr x} {
  for {set y 1} {$y < 10} {incr y} {
    button .b$buttonNum -text ? -disabledforeground black -width 3
    grid .b$buttonNum -row $y -column $x
    incr buttonNum
  }
}

# Start the game
startGame


A better game would tell you when you've managed to make a new best score. This should happen after you've done your last turn. Add another global variable named newHighScore that gets initialized to zero in startGame. Then modify the playTurn procedure to set thie variable to 1 when the best variable is changed. Finally, change the message in the reply box to tell you that you got a new high score (if you did) before asking if you want to play another game.

You might also try adding some sounds like background music, or cheers when a player gets a new high score.


Here's the important things in this lesson:


Simple games like these can store all the data they need in simple variables, as part of a command, or in the graphic widgets. More complex (ie, fun) games need more complex data, and better ways to save it.

The next lesson will start looking at bette ways to keep track of the data in your computer programs.



Previous

Next


Copyright 2007 Clif Flynt