Learn Tcl/Tk and Wish with this simple game

Here's an easy coding project to get you started with Tcl/Tk.
1 reader likes this.
Programming keyboard.

Opensource.com

Explore the basic language constructs of Tcl/Tk, which include user input, output, variables, conditional evaluation, simple functions, and basic event driven programming.

My path to writing this article started with a desire to make advanced use of Expect which is based on Tcl. Those efforts resulted in these two articles: Learn Tcl by writing a simple game and Learn Expect by writing a simple game.

I do a bit of Ansible automation and, over time have collected a number of local scripts. Some of them I use often enough that it becomes annoying to go through the cycle of:

  1. Open terminal
  2. Use cd to get to the right place
  3. Type a long command with options to start the desired automation

I use macOS on a daily basis. What I really wanted was a menu item or an icon to bring up a simple UI to accept parameters and run the thing I wanted to do, like in KDE on Linux.

The classic Tcl books include documentation on the popular Tk extensions. Since I was already deep into researching this topic, I gave programming it (that is wish) a try. 

I've never been a GUI or front-end developer, but I found the Tcl/Tk methods of script writing fairly straight forward. I was pleased to revisit such a venerable stalwart of UNIX history, something still available and useful on modern platforms.

Install Tcl/Tk

On a Linux system, you can use this:

$ sudo dnf install tcl
$ which wish
/bin/wish

On macOS, use Homebrew to install the latest Tcl/Tk:

$ brew install tcl-tk
$ which wish
/usr/local/bin/wish

Programming concepts

Most game-writing articles cover the typical programming language constructs such as loops, conditionals, variables, functions and procedures, and so on.

In this article, I introduce event-driven programming. With event-driven programming, your executable enters into a special built-in loop as it waits for something specific to happen. When the specification is reached, the code is triggered to produce a certain outcome.

These events can consist of things like keyboard input, mouse movement, button clicks, timing triggers, or nearly anything your computer hardware can recognize (perhaps even from special-purpose devices). The code in your program sets the stage from what it presents to the end user, what kinds of inputs to listen for, how to behave when these inputs are received, and then invokes the event loop waiting for input.

The concept for this article is not far off from my other Tcl articles. The big difference here is the replacement of looping constructs with GUI setup and an event loop used to process the user input. The other differences are the various aspects of GUI development needed to make a workable user interface. With Tk GUI development, you need to look at two fundamental constructs called widgets and geometry managers.

Widgets are UI elements that make up the visual elements you see and interact with. These include buttons, text areas, labels, and entry fields. Widgets also offer several flavors of option selections like menus, check boxes, radio buttons, and so on. Finally, widgets include other visual elements like borders and line separators.

Geometry managers play a critical role in laying out where your widgets sit in the displayed window. There are a few different kinds of geometry managers you can use. In this article, I mainly use grid geometry to lay widgets out in neat rows. I explain some of the geometry manager differences at the end of this article.

Guess the number using wish

This example game code is different from the examples in my other articles. I've broken it up into chunks to facilitate the explanation.

Start by creating the basic executable script numgame.wish:

$ touch numgame.wish
$ chmod 755 numgame.wish

Open the file in your favorite text editor. Enter the first section of the code:

#!/usr/bin/env wish
set LOW 1
set HIGH 100
set STATUS ""
set GUESS ""
set num [expr round(rand()*100)]

The first line defines that the script is executable with wish. Then, several global variables are created. I've decided to use all upper-case variables for globals bound to widgets that watch these values (LOW, HIGH and so on).

The num global is the variable set to the random value you want the game player to guess. This uses Tcl's command execution to derive the value saved to the variable:

proc Validate {var} {
    if { [string is integer $var] } {
        return 1
    }
    return 0
}

This is a special function to validate data entered by the user. It accepts integer numbers and rejects everything else:

proc check_guess {guess num} {
    global STATUS LOW HIGH GUESS

    if { $guess < $LOW } {
        set STATUS "What?"
    } elseif { $guess > $HIGH } {
        set STATUS "Huh?"
    } elseif { $guess < $num } {
        set STATUS "Too low!"
        set LOW $guess
    } elseif { $guess > $num } {
        set STATUS "Too high!"
        set HIGH $guess
    } else {
        set LOW $guess
        set HIGH $guess
        set STATUS "That's Right!"
        destroy .guess .entry
        bind all <Return> {.quit invoke}
    }

    set GUESS ""
}

This is the main loop of the value guessing logic. The global statement allows you to modify the global variables created at the beginning of the file (more on this topic later). The conditional looks for input that is out of bounds of 1 through 100 and also outside of values the user has already guessed. Valid guesses are compared against the random value. The LOW and HIGH guesses are tracked as global variables reported in the UI. At each stage, the global STATUS variable is updated. This status message is automatically reported in the UI.

In the case of a correct guess, the destroy statement removes the "Guess" button and the entry widget, and re-binds the Return (or Enter) key to invoke the Quit button.

The last statement set GUESS "" is used to clear the entry widget for the next guess:

label .inst -text "Enter a number between: "
label .low -textvariable LOW
label .dash -text "-"
label .high -textvariable HIGH
label .status -text "Status:"
label .result -textvariable STATUS
button .guess -text "Guess" -command { check_guess $GUESS $num }
entry .entry -width 3 -relief sunken -bd 2 -textvariable GUESS -validate all \
    -validatecommand { Validate %P }
focus .entry
button .quit -text "Quit" -command { exit }
bind all <Return> {.guess invoke}

This is the section where the user interface is set up.  The first six label statements create various bits of text that display on your UI. The option -textvariable watches the given variable and updates the label's value automatically. This displays the bindings to global variables LOW, HIGH, and STATUS.

The button lines set up the Guess and Quit buttons, with the -command option specifying what to do when the button is pressed. The Guess button invokes the check_guess procedure logic above to check the users entered value.

The entry widget gets more interesting. It sets up a three-character wide input field, and binds its input to GUESS global. It also configures validation with the -validatecommand option. This prevents the entry widget from accepting anything other than numbers.

The focus command is a UI polish that starts the program with the entry widget active for input. Without this, you need to click into the entry widget before you can type.

The bind command is an additional UI polish that automatically clicks the Guess button when the Return key is pressed. If you remember from above in check_guess, guessing the correct value re-binds Return to the "Quit" button.

Finally, this section defines the GUI layout:

grid .inst
grid .low .dash .high
grid .status .result
grid .guess .entry
grid .quit

The grid geometry manager is called in a series of steps to incrementally build up the desired user experience. It essentially sets up five rows of widgets. The first three are labels displaying various values, the fourth is the Guess button and entry widget, then finally, the Quit button.

At this point, the program is initialized and the wish shell enters into the event loop. It waits for the user to enter integer values and press buttons. It updates labels based on changes it finds in watched global variables.

Notice that the input cursor starts in the entry field and that pressing Return invokes the appropriate and available button.

This was a simple and basic example. Tcl/Tk has a number of options that can make the spacing, fonts, colors, and other UI aspects much more pleasing than the simple UI demonstrated in this article.

When you launch the application, you may notice that the widgets aren't very fancy or modern. That is because I'm using the original classic widget set, reminiscent of the X Windows Motif days. There are default widget extensions, called themed widgets, which can give your application a more modern and polished look and feel.

Play the game!

After saving the file, run it in the terminal:

$ ./numgame.wish

In this case, I can't give console output, so here's an animated GIF to demonstrate how the game is played:

A guessing game written in Wish

(James Farrell, CC BY-SA 4.0)

More about Tcl

Tcl supports the notion of namespaces, so the variables used here need not be global. You can organize your bound widget variables into alternate namespaces. For simple programs like this, it's probably not worth it. For much larger projects, you might want to consider this approach.

The proc check_guess body contains a global line I didn't explain. All variables in Tcl are passed by value, and variables referenced within the body are in a local scope. In this case, I wanted to modify the global variable, not a local scoped version. Tcl has a number of ways of referencing variables and executing code in execution stacks higher in the call chain. In some ways, it makes for complexities (and mistakes) for a simple reference like this. But the call stack manipulation is very powerful and allows for Tcl to implement new forms of conditional and loop constructs that would be cumbersome in other languages.

Finally, in this article, I skipped the topic of geometry managers which are used to take widgets and place them in a specific order. Nothing can be displayed to the screen unless it is managed by some kind of geometry manager. The grid manager is fairly simple. It places widgets in a line, from left to right. I used five grid definitions to create five rows. There are two other geometry managers: place and pack. The pack manager arranges widgets around the edges of the window, and the place manager allows for fixed placement. In addition to these geometry managers, there are special widgets called canvas, text, and panedwindow that can hold and manage other widgets. A full description of all these can be found in the classic Tcl/Tk reference guides, and on the Tk commands documentation page.

Keep learning programming

Tcl and Tk provide a straightforward and effective approach to building graphical user interfaces and event-driven applications. This simple guessing game is just the beginning when it comes to what you can accomplish with these tools. By continuing to learn and explore Tcl and Tk, you can unlock a world of possibilities for building powerful, user-friendly applications. Keep experimenting, keep learning, and see where your newfound Tcl and Tk skills can take you.

James Farrell
I am a long time UNIX system administrator and open source advocate. In recent years my primary focus as been on Linux & FreeBSD systems administration, networking, telecom, and SAN/storage management. I love building infrastructure, tying systems together, creating processes, and bringing people together in support of their technical efforts.

Comments are closed.

Creative Commons LicenseThis work is licensed under a Creative Commons Attribution-Share Alike 4.0 International License.