<?xml version="1.0" encoding="windows-1252"?>
<node id="166379" title="Tk cryptogram (no, not cryptography)" created="2002-05-14 01:25:22" updated="2005-08-09 22:15:13">
<type id="1042">
CUFP</type>
<author id="44715">
graff</author>
<data>
<field name="doctext">
This was a little "game" project I cooked up a few years
back as one of my early forays into Perl/Tk.  It proved
to be interesting (and successful) enough that I find myself
referring back to it over the years when I need to remember
how to get a particular behavior with Tk (or when I feel
like doing a mindless word puzzle...)
&lt;P&gt;
Many newspapers provide a "cryptogram" puzzle on the comics
page or next to the crossword, where a clever saying is
"encrypted" by doing regular substitutions of letters (e.g.
all "A"s in the "clear text" have been turned into "M"s, etc).
They're kinda fun to solve (if you like that sort of thing),
but doing it on paper is &lt;em&gt;such a drag&lt;/em&gt;, especially
when you have to erase things.
Perl to the rescue!  Once you get past the unavoidable kluge
of having to type in the "cipher text", solving is a snap.
&lt;P&gt;
Okay, I admit this probably doesn't qualify as a "cool" use
of Perl, but I think it is a nice demo of some useful GUI
techniques, including special event bindings to optimize the
task, a pop-up for helpful information, and overall ease and
transparency of use -- man page/pod not included, because (I think)
none is needed.  (oh,
no exit button either, but who needs that, really?)
&lt;code&gt;
#!/usr/bin/perl -w

use strict;
use Tk;

my ($puzStr, %ent, %lbl);
my $tfont = "Courier 14";
my $lfont = "Courier 12";
my @letters = qw/a b c d e f g h i j k l m n o p q r s t u v w x y z/;

my $main = MainWindow-&gt;new;
my $bg = $main-&gt;cget('-background');
my $fg = $main-&gt;cget('-foreground');

$main-&gt;Label(-text =&gt; "Type the cryptogram puzzle in this box and hit the \"Enter\" key:",
             -font =&gt; $lfont
             )-&gt;pack(-side =&gt; 'top');
my $puzzle = $main-&gt;Text(-relief =&gt; 'sunken',
                         -width =&gt; 80, -height =&gt; 3,
                         -font =&gt; $tfont,
                         -wrap =&gt; 'word',
                         -spacing1 =&gt; '5p', -spacing2 =&gt; '5p', -spacing3 =&gt; '5p'
                         )-&gt;pack(-side =&gt; 'top');

my $nxt = $main-&gt;Label(-text =&gt; "Now type a letter in each of these boxes to decode it:",
                       -font =&gt; $lfont,
                       -foreground =&gt; $bg
                       )-&gt;pack(-side =&gt; 'top');
my $frm = $main-&gt;Frame()-&gt;pack(-side =&gt; 'top', -pady =&gt; 2);

my $row = my $col = 1;
foreach my $c (@letters) 
{
    $lbl{$c} = $frm-&gt;Label(-text =&gt; " $c&gt;",
                           -font =&gt; $tfont,
                           -foreground =&gt; $bg
                           )-&gt;grid(-row =&gt; $row,
                                   -col =&gt; $col++);
    $ent{$c} = $frm-&gt;Entry(-width =&gt; 1,
                           -font =&gt; $tfont,
                           -borderwidth =&gt; 1,
                           -relief =&gt; 'flat'
                           )-&gt;grid(-row =&gt; $row,
                                   -col =&gt; $col++ );
    if ( $c eq 'n' ) {
        $row = 2, $col = 1;
    }
}
my $btn = $frm-&gt;Button(-text =&gt; "Show Counts",
             -command =&gt; \&amp;scSub,
             -state =&gt; 'disabled',
             )-&gt;grid(-row =&gt; $row,
                     -col =&gt; $col,
                     -columnspan =&gt; 5 );

my $lst = $main-&gt;Label(-text =&gt; "The solution will appear below:", 
                       -font =&gt; $lfont,
                       -foreground =&gt; $bg
                       )-&gt;pack(-side =&gt; 'top');
my $solutn = $main-&gt;Text(-relief =&gt; 'raised',
                         -width =&gt; 80, -height =&gt; 3,
                         -font =&gt; $tfont,
                         -wrap =&gt; 'word',
                         -spacing1 =&gt; '5p', -spacing2 =&gt; '5p', -spacing3 =&gt; '5p'
                         )-&gt;pack(-side =&gt; 'top');

$main-&gt;bind( 'Tk::Entry', '&lt;KeyRelease&gt;', [\&amp;krSub, Ev('K')] );
$main-&gt;bind( 'Tk::Entry', '&lt;Enter&gt;', \&amp;enSub );
$puzzle-&gt;bind( "&lt;Return&gt;" =&gt; sub { 
    $puzStr = lc $puzzle-&gt;get('1.0','end');
    $puzStr =~ s/\s+$//;
    $puzzle-&gt;delete('1.0','end'), $puzzle-&gt;insert('1.0',$puzStr);
    foreach my $c (@letters) {
        $ent{$c}-&gt;delete(0,'end');
        if ( $puzStr =~ /$c/ ) {
            $ent{$c}-&gt;configure(-state =&gt; 'normal', -relief =&gt; 'sunken');
            $ent{$c}-&gt;insert(0,"_");
            $lbl{$c}-&gt;configure(-foreground =&gt; $fg);
        } else {
            $ent{$c}-&gt;configure(-state =&gt; 'disabled', -relief =&gt; 'flat');
            $lbl{$c}-&gt;configure(-foreground =&gt; $bg);
        }
    }
    $nxt-&gt;configure(-foreground =&gt; $fg);
    $lst-&gt;configure(-foreground =&gt; $fg);
    $btn-&gt;configure(-state =&gt; 'normal');
    &amp;updateSlvd; 
});

MainLoop;

sub krSub {
    my ( $w, $c ) = @_;
    if ( $c !~ /^[a-z]$/i ) {
        $w-&gt;delete(0,'end'), $w-&gt;insert(0,'_');
    } else {
        foreach my $l (@letters) {
            next if ( $w eq $ent{$l} );
            if ( $c eq $ent{$l}-&gt;get ) {
                $ent{$l}-&gt;delete(0,'end'), $ent{$l}-&gt;insert(0,'_');
            }
        }
    }
    &amp;updateSlvd;
    $w-&gt;selectionRange(0,'end');
}

sub enSub {
    my $w = $_[0];
    $w-&gt;selectionRange(0,'end');
    $w-&gt;focus;
}

sub scSub {
    my %cnt = ();
    my ( $n, $chk );
    my $maxn = my $maxw = 0;
    foreach my $c (@letters) {
        $chk = $puzStr;
        $chk =~ s/[^$c]//g;
        $n = length( $chk );
        $cnt{$n} .= " $c";
        $maxn = $n if ( $maxn &lt; $n );
        $n = length( $cnt{$n} );
        $maxw = $n if ( $maxw &lt; $n );
    }
    my $top = $main-&gt;Toplevel(-title =&gt; 'Crypto-count');
    $n = scalar( keys( %cnt ));
    $maxw += 5;
    my $txt = $top-&gt;Text(width =&gt; $maxw,
                         height =&gt; $n,
                         font =&gt; $lfont
                         )-&gt;pack;
    $top-&gt;Button(-text =&gt; "Dismiss",
                 -command =&gt; sub { $top-&gt;destroy }
                 )-&gt;pack;
    for ( $n=$maxn; $n&gt;0; $n-- ) {
        if ( exists( $cnt{$n} )) {
            $txt-&gt;insert( 'end', sprintf( "%3d %s\n", $n, $cnt{$n} ));
        }
    }
}

sub updateSlvd
{
    local $_ = $puzStr;
    my $trStr1 = my $trStr2 = "";
    foreach my $c (@letters) {
        my $t = uc $ent{$c}-&gt;get;
        if ( $t ne "" ) {
            $trStr1 .= $c;
            $trStr2 .= $t;
        }
    }
    eval "tr/$trStr1/$trStr2/";
    $solutn-&gt;delete('1.0','end'), $solutn-&gt;insert('1.0',$_);
}
&lt;/code&gt;</field>
</data>
</node>
