mirror of
https://github.com/LCTT/TranslateProject.git
synced 2024-12-26 21:30:55 +08:00
219 lines
13 KiB
Markdown
219 lines
13 KiB
Markdown
[#]: 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 |