Learn Tcl by writing a simple game

Explore the basic language constructs of Tcl, which include user input, output, variables, conditional evaluation, looping, and simple functions.
3 readers like this.
Person standing in front of a giant computer screen with numbers, data


My path to Tcl started with a recent need to automate a difficult Java-based command-line configuration utility. I do a bit of automation programming using Ansible, and I occasionally use the expect module. Frankly, I find this module has limited utility for a number of reasons including: difficulty with sequencing identical prompts, capturing values for use in additional steps, limited flexibility with control logic, and so on. Sometimes you can get away with using the shell module instead. But sometimes you hit that ill-behaving and overly complicated command-line interface that seems impossible to automate.

In my case, I was automating the installation of one of my company's programs. The last configuration step could only be done through the command-line, through several ill-formed, repeating prompts and data output that needed capturing. The good old traditional Expect was the only answer. A deep understanding of Tcl is not necessary to use the basics of Expect, but the more you know, the more power you can get from it. This is a topic for a follow-up article. For now, I explore the basic language constructs of Tcl, which include user input, output, variables, conditional evaluation, looping, and simple functions.

Install Tcl

On a Linux system, I use this:

# dnf install tcl
# which tclsh

On macOS, you can use Homebrew to install the latest Tcl:

$ brew install tcl-tk
$ which tclsh

Guess the number in Tcl

Start by creating the basic executable script numgame.tcl:

$ touch numgame.tcl
$ chmod 755 numgame.tcl

And then start coding in your file headed up by the usual shebang script header:


Here are a few quick words about artifacts of Tcl to track along with this article.

The first point is that all of Tcl is considered a series of strings. Variables are generally treated as strings but can switch types and internal representations automatically (something you generally have no visibility into). Functions may interpret their string arguments as numbers ( expr) and are only passed in by value. Strings are usually delineated using double quotes or curly braces. Double quotes allow for variable expansion and escape sequences, and curly braces impose no expansion at all.

The next point is that Tcl statements can be separated by semicolons but usually are not. Statement lines can be split using the backslash character. However, it's typical to enclose multiline statements within curly braces to avoid needing this. Curly braces are just simpler, and the code formatting below reflects this. Curly braces allow for deferred evaluation of strings. A value is passed to a function before Tcl does variable substitution.

Finally, Tcl uses square brackets for command substitution. Anything between the square brackets is sent to a new recursive invocation of the Tcl interpreter for evaluation. This is handy for calling functions in the middle of expressions or for generating parameters for functions.


Although not necessary for this game, I start with an example of defining a function in Tcl that you can use later:

proc used_time {start} {
	return [expr [clock seconds] - $start]

Using proc sets this up to be a function (or procedure) definition. Next comes the name of the function. This is then followed by a list containing the parameters; in this case 1 parameter {start} and then followed by the function body. Note that the body curly brace starts on this line, it cannot be on the following line. The function returns a value. The returned value is a compound evaluation (square braces) that starts by reading the system clock [clock seconds] and does the math to subtract out the $start parameter.

Setup, logic, and finish

You can add more details to the rest of this game with some initial setup, iterating over the player's guesses, and then printing results when completed:

set num [expr round(rand()*100)]
set starttime [clock seconds]
set guess -1
set count 0

puts "Guess a number between 1 and 100"

while { $guess != $num } {
	incr count
	puts -nonewline "==> "
	flush stdout
	gets stdin guess

	if { $guess < $num } {
		puts "Too small, try again"
	} elseif { $guess > $num } {
		puts "Too large, try again"
	} else {
		puts "That's right!"

set used [used_time $starttime]

puts "You guessed value $num after $count tries and $used elapsed seconds"

The first set statements establish variables. The first two evaluate expressions to discern a random number between 1 and 100, and the next one saves the system clock start time.

The puts and gets command are used for output to and input from the player. The puts I've used imply standard out for output. The gets needs the input channel to be defined, so this code specifies stdin as the source for terminal input from the user.

The flush stdout command is needed when puts omits the end-of-line termination because Tcl buffers output and it might not get displayed before the next I/O is needed.

From there the while statement illustrates the looping control structure and conditional logic needed to give the player feedback and eventually end the loop.

The final set command calls our function to calculate elapsed seconds for gameplay, followed by the collected stats to end the game.

Play it!

$ ./numgame.tcl
Guess a number between 1 and 100
==> 100
Too large, try again
==> 50
Too large, try again
==> 25
Too large, try again
==> 12
Too large, try again
==> 6
Too large, try again
==> 3
That's right!
You guessed value 3 after 6 tries and 20 elapsed seconds

Continue learning

When I started this exercise, I doubted just how useful going back to a late 1990s fad language would be to me. Along the way, I found a few things about Tcl that I really enjoyed — my favorite being the square bracket command evaluation. It just seems so much easier to read and use than many other languages that overuse complicated closure structures. What I thought was a dead language was actually still thriving and supported on several platforms. I learned a few new skills and grew an appreciation for this venerable language.

Check out the official site over at https://www.tcl-lang.org. You can find references to the latest source, binary distributions, forums, docs, and information on conferences that are still ongoing.

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.