TK/TCL, trouble understanding this tic tac toe cod

2019-09-06 12:17发布

问题:

Can anyone help explain to me what the snippet below does. This code snippet is taken from http://wiki.tcl.tk/12374. It is meant to create a tic tac toe game. There are not many resources out there for understanding Tk/Tcl so this is giving me significant difficulty.

proc DrawBoard {{redraw 0}} { 
    global S B GAME C 

    if {$redraw} {                              ;# Must redraw everything
        .c delete all
        set w2 [expr {$B(w2) - 15}]             ;# Make a little margins
        set h2 [expr {$B(h2) - 15}]
        set hbar [expr {$h2 / 3.0}]   
        set vbar [expr {$w2 / 3.0}]

        set B(0) [list -$w2   -$h2   -$vbar -$hbar] ;# All 9 cells
        set B(1) [list -$vbar -$h2    $vbar -$hbar]
        set B(2) [list  $vbar -$h2    $w2   -$hbar]
        set B(3) [list -$w2   -$hbar -$vbar  $hbar]
        set B(4) [list -$vbar -$hbar  $vbar  $hbar]
        set B(5) [list  $vbar -$hbar  $w2    $hbar]
        set B(6) [list -$w2    $hbar -$vbar  $h2]
        set B(7) [list -$vbar  $hbar  $vbar  $h2]
        set B(8) [list  $vbar  $hbar  $w2    $h2]

        for {set i 0} {$i < 9} {incr i} {       ;# Rectangle for each cell
            .c create rect $B($i) -tag b$i -fill {} -outline {}
            .c bind b$i <Button-1> [list DoClick $i]
            set B($i) [ShrinkBox $B($i) 25]
        }
        .c create line -$w2 $hbar $w2 $hbar -tag bar ;# Draw the cross bars
        .c create line -$w2 -$hbar $w2 -$hbar -tag bar
        .c create line $vbar -$h2 $vbar $h2 -tag bar
        .c create line -$vbar -$h2 -$vbar $h2 -tag bar
        .c itemconfig bar -width 20 -fill $::C(bars) -capstyle round
    }
    .new config -state [expr {$GAME(tcnt) == 0 ? "disabled" : "normal"}]

    for {set i 0} {$i < 9} {incr i} {
        .c itemconfig b$i -fill {}              ;# Erase any win lines
        DrawXO $GAME(board,$i) $i
    }
    foreach i $GAME(win) {                      ;# Do we have a winner???
        .c itemconfig b$i -fill $C(win)
    }
}

Ok, the most significant question I have regards w2, h2, hbar, vbar variables. Particularly with how they are declared. For instance, set w2 [expr {$B(w2) - 15}]. How can w2 be defined referring to itself??? The author uses these variables to draw the tic tac toe lines, but I don't even know how what these variables mean. Does these variables specify some dimension of the canvas so that the author can use it to bind particular regions to click activities?

If I understand these four variables, everything else will make sense!

Here is the image of what the board looks like:

回答1:

Variables in Tcl (Tk is just a window drawing toolkit that lives on top of Tcl) are defined when they are written to; there's usually no explicit declaration. The only exception to this is with variables directly in a namespace, where it is best practice to use the variable command to declare them before first use, like this:

namespace eval exampleNamespace {
    variable xmpl1 "abc def"

    # Or equivalently...

    variable xmpl2
    set xmpl2 "abc def"

    # You have to use the second style with arrays...

    variable arrayXmpl
    set arrayXmpl(1) "pqr stu"
    set arrayXmpl(2) "qwerty uiop"
}

Local variables in procedures don't need declaring, though if you want to access a variable that isn't local you have to use a command (often global or upvar) to bring it into scope.

proc variableExample {formalArgument1 formalArgument2} {
    set localVar1 "abc"
    set localVar2 "def"
    global thisOtherVar
    append thisOtherVar "ghi" $formalArgument1
    puts "Currently, got $localVar1 $localVar2 and '$thisOtherVar'"
}

It's pretty conventional to put the global at the top of the procedure, but it's totally not necessary. It's effect persists from where you do it until the end of the procedure call. Tcl's semantics are strictly operational with extremely tightly defined evaluation order (it's left-to-right, always).

Now, arrays are aggregate variables. Each element of an array is a variable itself. They're distinct from normal simple variables, though the name of the overall array is in the same naming scheme as the simple variables. You can't have a simple foo and a foo(bar) in the same scope (without unsetting one first, which removes the variable). The keys into the elements are strings — the implementation behind the scenes is a high-performance hash table — that are entirely not variables and this means that the B(w2) and w2 variables are entirely distinct; they're not the same thing at all. However, we can use variables (and other Tcl substitutions) in computing the string to use as a key, so we can do this:

set name "w2"
set B($name) "example of "
append B(w2) "array key handling"
puts "this is an $B($name)"

Let's look at the example that you were puzzling over:

set w2 [expr {$B(w2) - 15}]

Breaking it into pieces:

set w2 […]

It's going to write to the variable w2. That's what the set command does with two arguments. The thing that is going to be written in is the result of evaluating another command. We need to look deeper.

expr {$B(w2) - 15}

Breaking it into pieces:

expr {…}

That yields the result of evaluating the expression in the braces. It's strongly recommended that you put braces around all your expressions; it's safer and much faster. What's the expression? (Note that expressions use a different syntax to the rest of Tcl.)

$B(w2) - 15

OK, that's subtracting 15 (the number) from the value read (because of the $) from the w2 element of the array B. The w2 here is just a string. That there's a variable elsewhere with the same name is coincidence.

And that's it. Reassembling the pieces, we see that:

set w2 [expr {$B(w2) - 15}]

Assigns the result of subtracting 15 from the contents of B(w2) to the variable w2. That's all it does. (The array B is a global array; see the global at the top of the procedure.)


The lines:

    set w2 [expr {$B(w2) - 15}]             ;# Make a little margins
    set h2 [expr {$B(h2) - 15}]
    set hbar [expr {$h2 / 3.0}]   
    set vbar [expr {$w2 / 3.0}]

These get the half height and width of the canvas from the global array B, remove 15 pixels for a margin, and then set hbar/vbar to a third of that value so that the coordinates for drawing are easier. It helps once you realize that the canvas has had its drawing origin moved (similar to in scrolling) to the center of its window. And be aware that doing -$hbar is actually being a bit naughty, though cute; it's using string concatenation to negate a value, which is OK when the value is positive and doesn't have an explicit sign, but would be fragile if that ever changed. And it's slow by comparison with doing [expr {-$hbar}], though that's longer; the cost of computation isn't exactly matched with the length of the command.



标签: tcl tk