HiLo2

Summary

HJG HiLo is a simple number-guessing game: for each guess, you get the answer "too high" or "too low", until you guess right.

Code


#!/bin/sh
# Restart with tcl: -*- mode: tcl; tab-width: 4; -*- \
exec wish $0 ${1+"$@"}

#: HiLo2.tcl - HaJo Gurt - 2005-06-21
#: Simple number-guessing-game with GUI
#
# 2005-07-03 Text-highlights
# 2016-01-30 Text-width

#########1#########2#########3#########4#########5#########6#########7#####

package require Tk

proc random { {range 100} } {
#: Return a number in the range 0 .. $range-1
    return [expr {int(rand()*$range)}]
}

proc Disp { {w .res1} {str ""} {tag ""} } {
#: Output string to one of the text-widgets
    $w insert end "\n"
    $w insert end $str $tag
    $w see end             ;# scroll to bottom
}

proc Disable {} {
#: Disable entry-widget, highlight frame around Newgame-Button
    .f2a  config -bg limegreen
    .f2b  config -bg grey
    .inp1 config -state readonly
}

proc NewGame {} {
#: Start a new game, put GUI in start-position
    set ::nGuess  0        ;# :: prefix for global vars.
   #set ::Secret 13
    set ::Secret [expr {[random $::Max] +1 }]

    .f2a  config -bg grey
    .f2b  config -bg limegreen
    .inp1 config -state normal

    .res1 delete 0.0 end
    .res2 delete 0.0 end
    Disp .res1 "  Guess my number:\n" Hi
    Disp .res2 "   (1 .. $::Max)  \n" Hi
}

proc TestNum {str} {
#: Validate input into entry-widget: Check if input is numeric
    set ok [string is integer $str]  
    if {$ok==0} {bell}     ;# alert: invalid input
    return $ok
}

proc Update {} {
#: Process input, update display
    global Secret Guess nGuess

# Check input:
    if {$Guess eq ""} {
        bell               ;# alert: empty input
    } else {
        incr nGuess
        if {$Guess <  $Secret} { Disp .res1 "$Guess is too low"  }
        if {$Guess >  $Secret} { Disp .res2 "$Guess is too high" }
        if {$Guess == $Secret} { Disable
                                 Disp .res1 " $Guess is correct. " Ok
                                 Disp .res1 "You needed $nGuess guesses." 
                               }
    }
    set Guess ""           ;# clear input-field
}

proc Init {} {
#: Initialize values, build GUI
    global Max Secret Guess nGuess
    set Max 100

    frame  .f1                                        ;# Frame for text-output
    frame  .f2  -relief ridge -borderwidth 3 -padx 2  ;# Frame for controls
    pack   .f1  .f2  -padx 2 -pady 2

    frame  .f2a -borderwidth 3   ;# Frame for "new game"-controls
    frame  .f2x                  ;# Spacer
    frame  .f2b -borderwidth 3   ;# Frame for number-entry
    pack   .f2a .f2x .f2b  -in .f2  -side left  -padx 2 -pady 2 -fill y

    text   .res1 -width 22 -height 10
    text   .res2 -width 22 -height 10
    pack   .res1 .res2  -in .f1  -side left
    # Define styles for text-highlights:
    .res1 tag configure "Hi"  -background lightblue -underline 1
    .res2 tag configure "Hi"  -background lightblue -underline 0
    .res1 tag configure "Ok"  -background red       -foreground white

    tk_optionMenu .opt1 Max 16 32 64 100 128 256 512 1000 1024 2048 4096 8192
    button        .but1 -text "New game" -command {NewGame}
    label         .lx   -text ""         -anchor c -padx 4 -bg grey          ;# Spacer
    label         .lab1 -text "Guess:"
    entry         .inp1 -width 5         -textvariable Guess -validate key -validatecommand {TestNum %P}
    pack          .opt1 .but1     -in .f2a  -side left  -padx 2 -pady 2
    pack          .lx             -in .f2x
    pack          .lab1 .inp1     -in .f2b  -side left  -padx 2 -pady 2

    bind          .inp1 <Return> {Update}

    wm title . "HiLo 2"
    focus -force .inp1
}

Init
NewGame

Comments

The GUI is done with frames, pack, entry, tk_optionMenu and two text-areas for output.
These are used to separate the "too low" and "too high"-results.

A textmode-version is at HiLo, and an international version (using msgcat) at HiLo-international.

HJG The options -relief and -validatecommand did not work as expected, but otherwise everything looks fine.

MG The problem with your -validatecommand there is that you're checking the -textvariable for the widget, to see if the new text is valid. The way the validation works, though, is that you check before the value actually changes (and thus before the -textvariable is altered), to decide whether the change can happen or not. If you change it to use

  entry .inp1 -width 5 -textvariable Guess -validatecommand {TestNum %P} -validate key

then TestNum will be called with one arg - the %P will be substituted with the value of the entry widget if the change goes ahead. (There are several other validations available - check the 'Validation' section of the entry widget manpage.) In this particular instance, you'd want to check that the string entered is either a number, or blank - which your TestNum already does. So, all you need to do is change it to take an argument, and check that instead of $::Guess

proc TestNum {value} {
# Test if input is numeric
    set ok [string is integer $value]
    if {$ok==0} {bell}
    return $ok
}

And then it should work OK. One other update you might want to add: In your Update proc, you use [string is integer $::Guess] to see if the update should go ahead. The problem with that is that the empty string passes the test. If you change that one to be [string is integer -strict $::Guess] it will stop people being able to guess "", and actually work on integers alone.

MG adds: the problem you're having with the -relief on the frame .f2 is that it's -borderwidth is set to 0. If you use

frame .f2 -bg blue -relief ridge -borderwidth 3

the ridge will show up.

HJG Thanks for the advice! Now TestNum() works, but it does not allow using backspace in the entry widget. That is not exactly userfriendly, so I decided that the check in Update() has to do all the work. MG Only use the -strict in Update, and not in TestNum, and that'll be fixed. HJG Done.

Although the GUI now looks nice enough, I find it somewhat tedious to adjust all the parts of the GUI by hand and trial&error, because the packer keeps surprising me... What tools do you recommand for GUI-design ?

Brian Theado - I found the packer demo at [L1 ] instructive (or [L2 ] if you have the plugin installed) for learning. See also pack.

HJG Looks like the demo is out of order. Man-pages, examples and demos are fine (and I think I understand them quite well), but I'm looking more for a real GUI-Builder.