通过“猜数字”游戏学习 Tcl
Tcl
![](/data/attachment/album/202302/23/140000ilg3oa8khgrhr1r3.jpg)



> 
> 探索 Tcl 的基本语言结构，包括用户输入、输出、变量、条件评估、循环和简单函数。
> 
> 
> 


我的 Tcl 之旅始于最近需要将一个困难的基于 Java 的命令行配置工具自动化。我使用 Ansible 做了一些自动化编程，偶尔也会使用 `expect` 模块。坦率地说，我发现这个模块的作用有限，原因包括：难以对相同的提示进行排序，难以捕捉到额外步骤的值，控制逻辑的灵活性有限，等等。有时你可以用 `shell` 模块来代替。但有时你会遇到那种特立独行、过于复杂的命令行程序，似乎无法实现自动化。


就我而言，我正在自动安装我公司的一个程序。最后的配置步骤只能通过命令行来完成，通过几个不规范的、重复的提示和需要捕捉的数据输出。好在传统的 Expect 是唯一的答案。要使用 Expect 的基本功能，并不需要对 Tcl 有很深的了解，但你了解的越多，你就能从它那里得到更多的力量。这是后续文章的话题。现在，我探讨一下 Tcl 的基本语言结构，包括用户输入、输出、变量、条件判断、循环和简单函数。


### 安装 Tcl


在 Linux 系统上，我使用这个：



```
# dnf install tcl
# which tclsh
/bin/tclsh

```

在 macOS 上，你可以使用 [Homebrew](https://opensource.com/article/20/6/homebrew-mac) 来安装最新的 Tcl：



```
$ brew install tcl-tk
$ which tclsh
/usr/local/bin/tclsh

```

### 在 Tcl 中猜数字


从创建基本的可执行脚本 `numgame.tcl` 开始：



```
$ touch numgame.tcl
$ chmod 755 numgame.tcl

```

接着在你的文件中开始编码，标题是通常的 #!：



```
#!/usr/bin/tclsh

```

这里有一些关于 Tcl 的简单介绍，以便与本文一起追踪。


第一点是，Tcl 处理的都是字符串。变量通常被当作字符串处理，但可以自动切换类型和内部表示（这一点你通常无法看到）。函数可以把它们的字符串参数解释为数字（`expr`），并且只通过值传递。字符串通常使用双引号或大括号来划分。双引号允许变量扩展和转义序列，而大括号则完全没有扩展。


第二点是 Tcl 语句可以用分号隔开，但通常不这样。语句行可以用反斜杠字符来分割，然而，典型的做法是将多行语句放在大括号内，以避免需要这样做。大括号只是更简单，下面的代码格式也反映了这一点。大括号允许对字符串进行延迟求值。在 Tcl 进行变量替换之前，值被传递给函数。


最后，Tcl 使用方括号进行命令替换。方括号之间的任何东西都会被送到 Tcl 解释器的一个新的递归调用中进行求值。这对于在表达式中间调用函数或为函数生成参数是很方便的。


### 过程


虽然在这个游戏中没有必要，但我先举一个在 Tcl 中定义函数的例子，你可以在以后使用：



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

```

使用 `proc` 将其设定为一个函数（或过程）定义。接下来是函数的名称。然后是一个包含参数的列表；在本例中是一个参数 `{start}` ，然后是函数主体。注意，主体的大括号在这一行开始，它不能在下面一行。该函数返回一个值。返回值是一个复合求值（方括号），它从读取系统时钟 `[clock seconds]` 开始，并进行数学运算以减去 `$start` 参数。


### 设置、逻辑和完成


你可以在这个游戏的其余部分增加更多的细节，进行一些初始设置，对玩家的猜测进行迭代，然后在完成后打印结果：



```
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"

```

前面的 `set` 语句建立变量。前两个求值表达式用于识别 1 到 100 之间的随机数，下一个保存系统时钟启动时间。


`puts` 和 `gets` 命令用于来自玩家的输出和输入。我使用的 `puts` 暗示输出是标准输出。`gets` 需要定义输入通道，所以这段代码指定 `stdin` 作为用户的终端输入源。


当 `puts` 省略行末终止符时，需要 `flush stdout` 命令，因为 Tcl 缓冲了输出，在需要下一个 I/O 之前可能不会被显示。


从这里开始，`while` 语句说明了循环控制结构和条件逻辑，需要给玩家反馈并最终结束循环。


最后的 `set` 命令调用我们的函数来计算游戏的耗时秒数，接着是收集到的统计数字来结束游戏。


### 玩吧!



```
$ ./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

```

### 继续学习


当我开始这个练习时，我怀疑回到 90 年代末的流行语言对我有多大的帮助。一路走来，我发现 Tcl 有几处让我非常喜欢的地方，我最喜欢的是方括号内的命令求值。与其他许多过度使用复杂闭包结构的语言相比，它似乎更容易阅读和使用。我以为它是一种 [已消亡的语言](https://opensource.com/article/19/6/favorite-dead-language)，但实际上它仍在蓬勃发展，并在多个平台上得到支持。我学到了一些新的技能，并对这种古老的语言有了新的认识。


在 <https://www.tcl-lang.org> 上查看官方网站。你可以找到最新的源代码、二进制发行版、论坛、文档，以及仍在进行的会议信息的参考。




---


via: <https://opensource.com/article/23/2/learn-tcl-writing-simple-game>


作者：[James Farrell](https://opensource.com/users/jamesf) 选题：[lkxed](https://github.com/lkxed/) 译者：[geekpi](https://github.com/geekpi) 校对：[wxy](https://github.com/wxy)


本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


OK
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
/bin/tclsh
```


On macOS, you can use [Homebrew ](https://opensource.com/article/20/6/homebrew-mac)to install the latest Tcl:

```
``````
$ brew install tcl-tk
$ which tclsh
/usr/local/bin/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:

```
````#!/usr/bin/tclsh`


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.

## Procedures

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](https://opensource.com/article/19/6/favorite-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](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.

## Comments are closed.