To show some of what can be done with Tcl/Tk, we will show an example of part of a GUI for an 8-queens program. Most people will be familiar with the 8-queens problem: how to place 8 queens on a chess board such that they don't attack each other according to the normal rules of chess.
Our example will not be a program to solve the 8-queens problem
(that will come later in the tutorial) but just the Tcl/Tk part for
displaying a solution. The code can be found in
library('tcltk/examples/ex18.tcl')
.
The way an 8-queens solution is normally presented is as a list of
numbers. The position of a number in the list indicates the column the
queens is placed at and the number itself indicates the row. For
example, the Prolog list [8, 7, 6, 5, 4, 3, 2, 1]
would indicate
8 queens along the diagonal starting a column 1, row 8 and finishing at
column 8 row 1.
The problem then becomes, given this list of numbers as a solution, how to display the solution using Tcl/Tk. This can be divided into two parts: how to display the initial empty chess board, and how to display a queen in one of the squares.
Here is our code for setting up the chess board:
% ex18.pl#! /usr/bin/wish proc setup_board { } { # create container for the board frame .queens # loop of rows and columns for {set row 1} {$row <= 8} {incr row} { for {set column 1} {$column <= 8} {incr column} { # create label with a queen displayed in it label .queens.$column-$row -bitmap @bitmaps/q64s.bm -relief flat # choose a background color depending on the position of the # square; make the queen invisible by setting the foreground # to the same color as the background if { [expr ($column + $row) % 2] } { .queens.$column-$row config -background #ffff99 .queens.$column-$row config -foreground #ffff99 } else { .queens.$column-$row config -background #66ff99 .queens.$column-$row config -foreground #66ff99 } # place the square in a chess board grid grid .queens.$column-$row -row $row -column $column -padx 1 -pady 1 } } pack .queens } setup_board
The first thing that happens is that a frame widget is created to contain the board. Then there are two nested loops that loop over the rows and columns of the chess board. Inside the loop, the first thing that happens is that a label widget is created. It is named using the row and column variables so that it can be easily referenced later. The label will not be used to display text but to display an image, a bitmap of a queen. The label creation command therefore has the special argument -bitmap @q64s.bm, which says that the label will display the bitmap loaded from the file q64s.bm.
The label with the queen displayed in it has now been created. The next thing that happens is that the background color of the label (square) is chosen. Depending on the position of the square it becomes either a “black” or a “white” square. At the same time, the foreground color is set to the background color. This is so that the queen (displayed in the foreground color) will be invisible, at least when the board is first displayed.
The final action in the loop is to place the label (square) in relation
to all the other squares for display. A chess board is a simple grid of
squares, and so this is most easily done through the grid
geometry
manager.
After the board has been set up square-by-square it still needs to be displayed,
which is done by pack
-ing the outermost frame widget.
To create and display a chess board widget, all that is needed is to call the procedure
setup_board
which creates the chess board widget.
Once the chess board has been displayed, we need to be able to take a solution, a list of rows ordered by column, and place queens in the positions indicated.
Taking a topdown approach, our procedure for taking a solution and displaying is as follows:
proc show_solution { solution } { clear_board set column 1 foreach row $solution { place_queen $column $row incr column } }
This takes a solution in solution
, clears the board of all queens, and
then places each queen from the solution on the board.
Next we will handle clearing the board:
proc clear_board { } { for { set column 1 } {$column <= 8} {incr column} { reset_column $column } } proc reset_column { column } { for {set row 1 } { $row <= 8 } {incr row} { set_queens $column $row off } } proc set_queens { column row state } { if { $state == "on" } { .queens.$column-$row config -foreground black } else { .queens.$column-$row config -foreground [.queens.$column-$row cget -background] } }
The procedure clear_board
clears the board of queens by calling
the procedure reset_column
for each of the 8 columns on a board.
reset_column
goes through each square of a column and sets the
square to off
through set_queens
. In turn, set_queens
sets the foreground color of a square to black if the square is turned
on
, thus revealing the queen bitmap, or sets the foreground color of a
square to its background color, thus making the queens invisible, if it
is called with something other than on
.
That handles clearing the board, clearing a column or turning a queen on or off on a particular square.
The final part is place_queen
:
proc place_queen { column row } { reset_column $column set_queens $column $row on }
This resets a column so that all queens on it are invisible and then
sets the square with coordinates given in row
and column
to on.
A typical call would be:
show_solution "1 2 3 4 5 6 7 6 8"
which would display queens along a diagonal. (This is of course not a solution to the 8-queens problem. This Tcl/Tk code only displays possible queens solutions; it doesn't check if the solution is valid. Later we will combine this Tcl/Tk display code with Prolog code for generating solutions to the 8-queens problem.)