Merge pull request #30059 from wxy/20230424.3-️-Learn-TclTk-and-Wish-with-this-simple-game

ATRP:published/20230424.3 ️ Learn TclTk and Wish with this simple game.md
This commit is contained in:
Xingyu.Wang 2023-09-07 23:26:03 +08:00 committed by GitHub
commit 6088e165ee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 226 additions and 219 deletions

View File

@ -0,0 +1,226 @@
[#]: subject: "Learn Tcl/Tk and Wish with this simple game"
[#]: via: "https://opensource.com/article/23/4/learn-tcltk-wish-simple-game"
[#]: author: "James Farrell https://opensource.com/users/jamesf"
[#]: collector: "lkxed"
[#]: translator: "ChatGPT"
[#]: reviewer: "wxy"
[#]: publisher: "wxy"
[#]: url: "https://linux.cn/article-16170-1.html"
通过这个简单的游戏学习 Tcl/Tk 和 Wish
======
![][0]
> 以下是一个简单的编程项目,能够帮助你开始学习 Tcl/Tk。
探索 Tcl/Tk 的基础构造,包括用户输入、输出、变量、条件评估、简单函数和基础事件驱动编程。
我写这篇文章的初衷源于我想更深入地利用基于 Tcl 的 Expect。这让我写下了以下两篇文章[通过编写一个简单的游戏学习 Tcl][1] 和 [通过编写一个简单的游戏学习 Expect][2]。
我进行了一些 [Ansible][3] 自动化工作,逐渐积累了一些本地脚本。有些脚本我频繁使用,以至于以下循环操作变得有些烦人:
- 打开终端
- 使用 `cd` 命令跳转至合适的目录
- 输入一条带有若干选项的长命令启动所需的自动化流程
我日常使用的是 macOS。实际上我更希望有一个菜单项或者一个图标能够弹出一个简单的界面接受参数并执行我需要的操作[这就像在 Linux 的 KDE 中一样][4]。
经典的 Tcl 类书籍都包含了关于流行的 Tk 扩展的文档。既然我已经深入研究了这个主题,我尝试着对其(即 `wish`)进行编程。
虽然我并非一名 GUI 或者前端开发者,但我发现 Tcl/Tk 脚本编写的方式相当直接易懂。我很高兴能重新审视这个 UNIX 历史的古老且稳定的部分,这种技术在现代平台上依然有用且可用。
### 安装 Tcl/Tk
对于 Linux 系统,你可以按照下面的方式安装:
```
$ sudo dnf install tcl
$ which wish
/bin/wish
```
而在 macOS 上,你可以通过 [Homebrew][5] 来安装最新版的 Tcl/Tk
```
$ brew install tcl-tk
$ which wish
/usr/local/bin/wish
```
### 编程理念
许多编写游戏的教程都会介绍到典型的编程语言结构,如循环、条件判断、变量、函数和过程等等。
在此篇文章中,我想要介绍的是 [事件驱动编程][6]。当你的程序使用事件驱动编程,它会进入一个特殊的内置循环,等待特定的事件发生。当这个特定的事件发生时,相应的代码就会被触发,产生预期的结果。
这些事件可以包括键盘输入、鼠标移动、点击按钮、定时器触发,甚至是任何你的电脑硬件能够识别的事件(可能来自特殊的设备)。你的程序中的代码决定了用户看到了什么,以及程序需要监听什么输入,当这些输入被接收后程序会怎么做,然后进入事件循环等待输入。
这篇文章的理念并没有脱离我之前的 Tcl 文章太远。这里最大的不同在于用 GUI 设置和用于处理用户输入的事件循环替代了循环结构。其他的不同则是 GUI 开发需要采取的各种方式来制作一个可用的用户界面。在采用 Tk GUI 开发的时候,你需要了解两个基础的概念:<ruby>部件<rt>widget</rt></ruby><ruby>几何管理器<rt>geometry manager</rt></ruby>
部件是构成可视化元素的 UI 元素,通过这些元素用户可以与程序进行交互。这其中包括了按钮、文本区域、标签和文本输入框。部件还包括了一些选项选择,如菜单、复选框、单选按钮等。最后,部件也包括了其他的可视化元素,如边框和线性分隔符。
几何管理器在放置部件在显示窗口中的位置上扮演着至关重要的角色。有一些不同的几何管理器可以供你使用。在这篇文章中,我主要使用了 `grid` 几何来让部件在整齐的行中进行布局。我会在这篇文章的结尾地方解释一些几何管理器的不同之处。
### 用 wish 进行猜数字游戏
这个示例游戏代码与我其他文章中的示例有所不同,我将它分解为若干部分以方便解释。
首先创建一个基本的可执行脚本 `numgame.wish`
```
$ touch numgame.wish
$ chmod 755 numgame.wish
```
使用你喜欢的文本编辑器打开此文件,输入下列代码的第一部分:
```
#!/usr/bin/env wish
set LOW 1
set HIGH 100
set STATUS ""
set GUESS ""
set num [expr round(rand()*100)]
```
第一行定义了该脚本将通过 `wish` 执行。接下来,创建了几个全局变量。这里我使用全部大写字母定义全局变量,这些变量将绑定到跟踪这些值的窗口小部件(`LOW`、`HIGH`等等)。
全局变量 `num` 是游戏玩家要猜测的随机数值,这个值是通过 Tcl 的命令执行得到并保存到变量中的:
```
proc Validate {var} {
if { [string is integer $var] } {
return 1
}
return 0
}
```
这是一个验证用户输入的特殊函数,它只接受整数并拒绝其他所有类型的输入:
```
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 ""
}
```
这是主要的猜数逻辑循环。`global` 语句让我们能够修改在文件开头创建的全局变量(关于此主题后面将会有更多解释)。这个条件判断寻找入力范围在 1 至 100 之外以及已经被用户猜过的值。有效的猜测和随机值进行比较。`LOW` 和 `HIGH` 的猜测会被追踪,作为 UI 中的全局变量进行报告。在每一步,全局 `STATUS` 变量都会被更新,这个状态信息会自动在 UI 中显示。
对于正确的猜测,`destroy` 语句会移除 “Guess” 按钮以及输入窗口,并重新绑定回车键,以激活 “Quit” 按钮。
最后的语句 `set 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}
```
这是设置用户界面的部分。前六个标签语句在你的 UI 上创建了不同的文本展示元素,`-textvariable` 选项监控给定的变量,并自动更新标签的值,这展示了全局变量 `LOW`、`HIGH`、`STATUS` 的绑定。
`button` 行创建了 “Guess” 和 “Quit” 按钮, `-command` 选项设定了当按钮被按下时要执行的操作。按下 “Guess” 按钮执行了上面的 `check_guess` 函数以检查用户输入的值。
`entry` 部件更有趣。它创建了一个三字符宽的输入框,并将输入绑定到 `GUESS` 全局变量。它还通过 `-validatecommand` 选项设置了验证,阻止输入部件接收除数字以外的任何内容。
`focus` 命令是用户界面的一项改进,使程序启动时输入部件处于激活状态。没有此命令,你需要先点击输入部件才可以输入。
`bind` 命令允许你在按下回车键时自动点击 “Guess” 按钮。如果你记得 `check_guess` 中的内容,猜测正确之后会重新绑定回车键到 “Quit” 按钮。
最后,这部分设定了图形用户界面的布局:
```
grid .inst
grid .low .dash .high
grid .status .result
grid .guess .entry
grid .quit
```
`grid` 几何管理器被逐步调用,以逐渐构建出预期的用户体验。它主要设置了五行部件。前三行是显示不同值的标签,第四行是 “Guess” 按钮和 `entry` 部件,最后是 “Quit” 按钮。
程序到此已经初始化完毕,`wish` shell 进入事件循环,等待用户输入整数并按下按钮。基于其在被监视的全局变量中找到的变化,它会更新标签。
注意,输入光标开始就在输入框中,而且按下回车键将调用适当且可用的按钮。
这只是一个初级的例子Tcl/Tk 有许多可以让间隔、字体、颜色和其他用户界面方面更具有吸引力的选项,这超出了本文中简单 UI 的示例。
运行这个应用,你可能会注意到这些部件看起来并不很精致或现代。这是因为我正在使用原始的经典部件集,它们让人回忆起 X Windows Motif 的时代。不过,还有一些默认的部件扩展,被称为主题部件,它们可以让你的应用程序有更现代、更精致的外观和感觉。
### 启动游戏!
保存文件之后,在终端中运行它:
```
$ ./numgame.wish
```
在这种情况下,我无法给出控制台的输出,因此这里有一个动画 GIF 来展示如何玩这个游戏:
![用 Wish 编写的猜数游戏][7]
### 进一步了解 Tcl
Tcl 支持命名空间的概念,所以在这里使用的变量并不必须是全局的。你可以把绑定的部件变量组织进不同的命名空间。对于像这样的简单程序,可能并不太需要这么做。但对于更大规模的项目,你可能会考虑这种方法。
`proc check_guess` 函数体内有一行 `global` 代码我之前没有解释。在 Tcl 中所有变量都按值传递函数体内引用的变量的范围是局部的。在这个情况下我希望修改的是全局变量而不是局部范围的版本。Tcl 提供了许多方法来引用变量,在执行堆栈的更高级别执行代码。在一些情况下,像这样的简单引用可能带来一些复杂性和错误,但是调用堆栈的操作非常有力,允许 Tcl 实现那些在其他语言中实现起来可能较为复杂的新的条件和循环结构。
最后在这篇文章中我没有提到几何管理器它们用于以特定的顺序展示部件。只有被某种几何管理器管理的部件才能显示在屏幕上。grid 管理器相当简洁,它按照从左到右的方式放置部件。我使用了五个 grid 定义来创建了五行。另外还有两个几何管理器place 和 pack。pack 管理器将部件围绕窗口边缘排列,而 place 管理器允许固定部件的位置。除这些几何管理器外,还有一些特殊的部件,如 `canvas` `text` 和 `panedwindow`,它们可以容纳并管理其他部件。你可以在经典的 Tcl/Tk 参考指南,以及 [Tk 命令][8] 文档页上找到这些部件的全面描述。
### 继续学习编程
Tcl 和 Tk 提供了一个简单有效的方法来构建图形用户界面和事件驱动应用程序。这个简单的猜数游戏只是你能用这些工具做到的事情的起点。通过继续学习和探索 Tcl 和 Tk你可以打开构建强大且用户友好的应用程序的无数可能性。继续尝试继续学习看看你新习得的 Tcl 和 Tk 技能能带你到哪里。
*题图MJ/40621c50-6577-4033-9f3c-8013bd0286f1*
--------------------------------------------------------------------------------
via: https://opensource.com/article/23/4/learn-tcltk-wish-simple-game
作者:[James Farrell][a]
选题:[lkxed][b]
译者ChatGPT
校对:[wxy](https://github.com/wxy)
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
[a]: https://opensource.com/users/jamesf
[b]: https://github.com/lkxed/
[1]: https://opensource.com/article/23/2/learn-tcl-writing-simple-game
[2]: https://opensource.com/article/23/2/learn-expect-automate-simple-game
[3]: https://www.redhat.com/en/technologies/management/ansible/what-is-ansible?intcmp=7013a000002qLH8AAM
[4]: https://opensource.com/article/23/2/linux-kde-desktop-ansible
[5]: https://opensource.com/article/20/6/homebrew-mac
[6]: https://developers.redhat.com/topics/event-driven/all?intcmp=7013a000002qLH8AAM
[7]: https://opensource.com/sites/default/files/2023-03/numgame-wish.gif
[8]: https://tcl.tk/man/tcl8.7/TkCmd/index.html
[0]: https://img.linux.net.cn/data/attachment/album/202309/07/231710i7u72ttuzlt4thhr.jpg

View File

@ -1,219 +0,0 @@
[#]: subject: "Learn Tcl/Tk and Wish with this simple game"
[#]: via: "https://opensource.com/article/23/4/learn-tcltk-wish-simple-game"
[#]: author: "James Farrell https://opensource.com/users/jamesf"
[#]: collector: "lkxed"
[#]: translator: " "
[#]: reviewer: " "
[#]: publisher: " "
[#]: url: " "
Learn Tcl/Tk and Wish with this simple game
======
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][1] and [Learn Expect by writing a simple game][2].
I do a bit of [Ansible][3] 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:
- Open terminal
- Use `cd` to get to the right place
- 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][4].
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][5] 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][6]. 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][7]
### 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][8] 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.
--------------------------------------------------------------------------------
via: https://opensource.com/article/23/4/learn-tcltk-wish-simple-game
作者:[James Farrell][a]
选题:[lkxed][b]
译者:[译者ID](https://github.com/译者ID)
校对:[校对者ID](https://github.com/校对者ID)
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
[a]: https://opensource.com/users/jamesf
[b]: https://github.com/lkxed/
[1]: https://opensource.com/article/23/2/learn-tcl-writing-simple-game
[2]: https://opensource.com/article/23/2/learn-expect-automate-simple-game
[3]: https://www.redhat.com/en/technologies/management/ansible/what-is-ansible?intcmp=7013a000002qLH8AAM
[4]: https://opensource.com/article/23/2/linux-kde-desktop-ansible
[5]: https://opensource.com/article/20/6/homebrew-mac
[6]: https://developers.redhat.com/topics/event-driven/all?intcmp=7013a000002qLH8AAM
[7]: https://opensource.com/sites/default/files/2023-03/numgame-wish.gif
[8]: https://tcl.tk/man/tcl8.7/TkCmd/index.html