Version 4 of CryptoAid

Updated 2009-10-13 22:43:43 by kpv

Keith Vetter 2009-10-10 -- several years ago I was an avid cryptogram solver. I used a program called CryptoAid that helped in solving the puzzle--not by any fancy solving logic but simply by doing the tedious grunt work enabling you to try different possible solutions.

Recently I wanted to try my hand again at cryptograms but when I dug out CryptoAid it wouldn't run anymore (missing some VB libraries). A cursory Google check lead only to dead links. So I thought it would be fun to spend a few hours and rewrite it in tcl. (A later, deeper check reveals that the program is still maintained and available at http://www.rrufer.com .)

You can type in a cryptogram puzzle from, say, the local paper or can use one of the 500 built-in puzzles. There's also an option to scrap the day's puzzle from my cryptogram website, Cryptogram Corner [L1 ].


WikiDbImage cryptoAid_screen.png


##+##########################################################################
#
# CryptoAid -- tcl port of VB CryptoAid program
# by Keith Vetter, Oct 2009
#

package require Tcl 8.5
package require Tk
package require textutil
package require http

set C(title) CryptoAid
set C(height) 300
set C(undo) {}
set C(CipherLines) 4

set AB {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}
if {"myFont" in [font names]} { font delete myFont }
font create myFont -family Courier -size 12 -weight bold

proc DoDisplay {} {
    global C

    wm title . $C(title)
    . config -padx .1i -pady .1i
    ::ttk::frame .txt
    canvas .c -width 0 -height $C(height) -highlightthickness 0
    ::ttk::frame .ctrl
    DoCanvas

    pack .txt -side top -fill both
    pack .c -side left -fill both
    pack .ctrl -side bottom -padx {.1i 0} -fill y -expand 1

    # Control panel
    ::ttk::label .ctrl.info -textvariable ::C(sample,info)
    ::ttk::button .ctrl.retype -text Edit -command GetCipherText
    ::ttk::button .ctrl.help -text Help -command Help
    ::ttk::button .ctrl.erase -text Erase -command Erase
    ::ttk::label .ctrl.l1 -text "Type-In\nFields" -font myFont
    entry .ctrl.key1 -width 2 -font myFont -textvariable C(key1) -justify center
    entry .ctrl.key0 -width 2 -font myFont -textvariable C(key0) -justify center
    focus .ctrl.key1
    foreach var {key0 key1} {
        foreach tr [trace vinfo C($var)] {
            eval trace vdelete C($var) $tr
        }
        trace variable C($var) w KeyTrace
    }

    pack .ctrl.key0 -side bottom
    pack .ctrl.key1 -side bottom -pady {0 5}

    set pady .01i
    pack .ctrl.l1 -side bottom -pady {.1i 0}
    pack .ctrl.erase -side bottom -pady $pady
    pack .ctrl.help -side bottom -pady $pady
    pack .ctrl.retype -side bottom -pady $pady
    pack .ctrl.info -side top

    # Solution window
    set opts "-font myFont"
    append opts " -relief sunken -bd 1"
    append opts " -width 0"
    append opts " -state disabled"

    for {set row 0} {$row < $C(CipherLines)} {incr row} {
        set w .txt.f$row
        text ${w}plain {*}$opts -height 1 -foreground red
        text ${w}cipher {*}$opts -height 1
        ${w}cipher tag config highlightLetter -background cyan
        pack ${w}plain -side top -fill x
        pack ${w}cipher -side top -fill x -pady {0 .05i}
    }

    bind all <Key-F1> Help
    bind all <Key-F2> {console show}
    bind all <Control-h> Hint
    bind all <Control-s> Solution
    bind all <Control-c> CheatSheet
    bind all <Control-z> Undo
}
##+##########################################################################
#
# DoCanvas -- draws histogram and cipher/deciphered letters chart
#
proc DoCanvas {} {
    global C XY

    entry .c.c -width 2 -relief sunken -justify center -font myFont
    set eWidth [winfo reqwidth .c.c]
    set eHeight [winfo reqheight .c.c]
    set opts "-width 2 -justify center -state disabled -font myFont"
    append opts " -disabledbackground [.c.c cget -background]"
    append opts " -disabledforeground [.c.c cget -foreground]"
    destroy .c.c

    set x [expr {$eWidth/2}]
    set y0 $C(height)
    set y1 [expr {$y0 - $eHeight - 5}]
    set y2 [expr {$y1 - $eHeight - 5}]
    set XY(bottom) $y2

    foreach ch $::AB {
        set XY($ch) $x
        entry .c.c$ch {*}$opts -textvariable C(key,$ch)
        set C(key,$ch) ""
        .c create window $x $y0 -window .c.c$ch -anchor s

        entry .c.p$ch -width 2 -font myFont -justify center -relief flat \
            -disabledforeground blue -disabledbackground [.c cget -bg]
        .c.p$ch insert end $ch
        .c.p$ch config -state disabled
        bind .c.p$ch <1> [list FauxKey $ch]
        .c create window $x $y1 -window .c.p$ch -anchor s
        incr x $eWidth

    }
    .c config -width $x
}
##+##########################################################################
#
# KeyTrace -- Handles key presses in type-in fields
#
proc KeyTrace {var1 var2 op} {
    global C ch

    set ch [string toupper $C($var2)]
    if {! [string is alpha $ch]} { set ch "" }
    set C($var2) $ch

    if {$var2 eq "key1"} {                      ;# In top, focus to bottom
        if {$ch ne ""} {tk::TabToWindow .ctrl.key0}
        HighlightCipherLetter $C(key1)
        return
    }

    if {$var2 eq "key0" && $C(key1) eq ""} {    ;# In bottom, empty top : refocus
        set C(key0) ""
        tk::TabToWindow .ctrl.key1
        return
    }

    DecipherLetter $C(key1) $C(key0)
    set C(key0) ""
    set C(key1) ""
    Decipher
    tk::TabToWindow .ctrl.key1
    HighlightCipherLetter $C(key1)
    IsSolved
}
##+##########################################################################
#
# DecipherLetter -- substitutes deciphered letter for the ciphered letter
#
proc DecipherLetter {ciphered deciphered {addUndo 1}} {
    global C

    if {$C(key,$ciphered) eq $deciphered} return

    set undo {}

    foreach arr [array names C key,*] { ;# Check for already in use
        if {$C($arr) eq $deciphered && $deciphered ne ""} {
            set txt "The letter '$deciphered' has already been choosen -- Replace?"
            set n [tk_messageBox -icon question -message $txt -type yesno \
                       -title $C(title) -parent .]
            if {$n eq "no"} return

            set ch [lindex [split $arr ","] 1]
            lappend undo $ch $deciphered
            set C($arr) ""
        }
    }

    if {$addUndo} {
        set undo [list $ciphered $C(key,$ciphered) {*}$undo]
        set C(undo) [concat [list $undo] $C(undo)]
    }
    set C(key,$ciphered) $deciphered
}
##+##########################################################################
#
# Undo -- Undoes the last change
#
proc Undo {} {
    global C

    set C(undo) [lassign $C(undo) this]
    foreach {ciphered deciphered} $this {
        DecipherLetter $ciphered $deciphered 0
    }
    Decipher
    set C(key0) ""
    set C(key1) ""
    tk::TabToWindow .ctrl.key1
}
##+##########################################################################
#
# FauxKey -- Produce fake key presses based on mouse clicks
#
proc FauxKey {ltr} {
    global C
    set w [focus]
    if {$w eq ".ctrl.key1"} {
        set C(key1) $ltr
    } elseif {$w eq ".ctrl.key0"} {
        set C(key0) $ltr
    }
}
##+##########################################################################
#
# PrepCipherText -- Turns cipher text into workable format
#
proc PrepCipherText {cipher {plain ""} {hint ""}} {
    global C

    set C(solved) 0
    set C(undo) ""
    set C(cipher) [string trim [string toupper $cipher]]
    regsub -all {\s+} $C(cipher) " " C(cipher)
    set C(plain) [string trim [string toupper $plain]]
    regsub -all {\s+} $C(plain) " " C(plain)
    set C(hint) $hint

    Highlight 0
    set txt [::textutil::adjust $C(cipher) -length 60 -strictlength true]
    set lines [split $txt \n]

    for {set i 0} {$i < $C(CipherLines)} {incr i} {
        set C($i,cipher) [lindex $lines $i]
        regsub -all {[A-Z]} $C($i,cipher) " " C($i,plain)
        foreach who {plain cipher} {
            set w .txt.f${i}$who
            $w config -state normal
            $w delete 1.0 end
            $w insert end $C($i,$who)
            $w config -state disabled
        }
    }
    Histogram
}
##+##########################################################################
#
# Histogram -- Draws histogram of letter usage
#
proc Histogram {} {
    global C XY cnt

    unset -nocomplain cnt

    regsub -all {[^A-Z]} $C(cipher) "" txt
    set max 0
    foreach ch [split $txt ""] {
        incr cnt($ch)
        if {$cnt($ch) > $max} { set max $cnt($ch) }
    }

    set sc [expr {($XY(bottom)-5) / double($max)}]
    .c delete histogram
    foreach ch [array names cnt] {
        set y [expr {$XY(bottom) - $cnt($ch) * $sc}]
        .c create line $XY($ch) $XY(bottom) $XY($ch) $y -tag histogram \
            -width 5 -fill magenta -capstyle round
    }
}
##+##########################################################################
#
# Decipher -- Applies current cipher/deciphered pairings
#
proc Decipher {} {
    global C

    set mapping {}
    foreach ltr $::AB {
        set ltr2 [expr {$C(key,$ltr) eq "" ? " " : $C(key,$ltr)}]
        lappend mapping $ltr $ltr2
    }
    for {set i 0} {$i < $C(CipherLines)} {incr i} {
        if {! [info exists C($i,cipher)]} break
        set C($i,plain) [string map $mapping $C($i,cipher)]
        .txt.f${i}plain config -state normal
        .txt.f${i}plain delete 1.0 end
        .txt.f${i}plain insert end $C($i,plain)
        .txt.f${i}plain config -state disabled
    }
    InUse
}
##+##########################################################################
#
# InUse -- Marks all letters currently used
#
proc InUse {} {
    global C

    array set RC [lreverse [array get C key,*]]
    foreach ch $::AB {
        set clr [expr {[info exists RC($ch)] ? "green" : [.c cget -bg]}]
        .c.p$ch config -disabledbackground $clr
    }
}
##+##########################################################################
#
# Hint -- Shows hint if we have one
#
proc Hint {} {
    global C
    set msg $C(hint)
    if {$C(hint) eq ""} {
        set msg "No hint available"
    }
    tk_messageBox -icon info -message $msg -title "$C(title) Hint" -parent .
}
##+##########################################################################
#
# Solution -- Shows solution if we have one
#
proc Solution {} {
    global C
    set msg [::textutil::adjust $C(plain) -length 60 -strictlength true]
    if {$C(plain) eq ""} {
        set msg "No solution available"
    }
    tk_messageBox -icon info -message $msg -title "$C(title) Solution" -parent .
}
##+##########################################################################
#
# Erase -- Erase current cipher/deciphered letter pairings
#
proc Erase {} {
    global C

    set undo {}
    foreach ch $::AB {
        if {$C(key,$ch) ne ""} {
            lappend undo $ch $C(key,$ch)
            set C(key,$ch) ""
        }
    }
    if {$undo eq {}} {                          ;# Double erase clears undo
        set C(undo) {}
    } else {
        set C(undo) [concat [list $undo] $C(undo)]
    }
    Decipher
    focus .ctrl.key1
}
proc Shuffle {llist} {
    set len [llength $llist]
    set len2 $len
    for {set i 0} {$i < $len-1} {incr i} {
        set n [expr {int($i + $len2 * rand())}]
        incr len2 -1

        # Swap elements at i & n
        set temp [lindex $llist $i]
        lset llist $i [lindex $llist $n]
        lset llist $n $temp
    }
    return $llist
}
##+##########################################################################
#
# GetCipherText -- Puts up dialog for getting cipher text
#
proc GetCipherText {} {
    set W .get
    destroy $W
    toplevel $W -padx .1i -pady .1i
    wm title $W "Enter Cryptogram"
    catch {wm attribute $W -toolwindow 1}
    wm protocol $W WM_DELETE_WINDOW DoneCipherText
    wm transient $W .
    wm withdraw $W

    ::ttk::label $W.l -text "Enter cryptogram here" -font {Times 14 bold}
    text $W.t -wrap word -height 13

    ::ttk::button $W.sample -text Sample -command GrabSample
    ::ttk::button $W.solve -text Solve -command DoneCipherText
    ::ttk::button $W.cryptocorner -text "Cryptogram\nCorner" \
        -command GetTodaysCryptogramCorner

    grid $W.l -columnspan 2 -sticky w
    grid $W.t -columnspan 2
    grid $W.sample $W.solve -pady .1i
    grid $W.cryptocorner

    ::ttk::label $W.lsample -textvariable ::C(sample,info)
    place $W.lsample -in $W.sample -relx 1 -y 0 -anchor nw

    update idletasks
    scan [wm geom .] "%dx%d+%d+%d" . . x y
    wm geom $W +[incr x 20]+[incr y 20]
    #RightWindow $W .
    wm deiconify $W

    grab $W
    $W.t insert end $::C(cipher)
    focus $W.t
}
##+##########################################################################
#
# DoneCipherText -- Exits GetCipherText dialog
#
proc DoneCipherText {} {
    global C

    set txt [string trim [.get.t get 1.0 "end - 1 char"]]
    if {$txt eq ""} return
    destroy .get

    set plain [set hint ""]
    if {[info exists C(sample,cipher)] && $txt eq $C(sample,cipher)} {
        set plain $C(sample,plain)
        set hint [GetHint $txt $plain]
    } else {
        set C(sample,info) ""
    }
    PrepCipherText $txt $plain $hint
    Decipher
    raise .
    focus .ctrl.key1
}
proc GetRandom {{idx ""}} {
    if {$idx eq ""} {
        set idx [expr {int(rand() * [llength $::samples])}]
    }
    set plain [lindex $::samples $idx]
    set cipher [Encrypt $plain]
    set hint [GetHint $cipher $plain]

    set ::C(sample,idx) $idx
    set ::C(sample,cipher) $cipher
    set ::C(sample,plain) $plain
    set ::C(sample,info) "#$idx of [llength $::samples]"

    PrepCipherText $cipher $plain $hint
    Decipher
    raise .
    focus .ctrl.key1
}
##+##########################################################################
#
# GrabSample -- Gets a sample from our built in list
#
proc GrabSample {} {
    set idx [expr {int(rand() * [llength $::samples])}]
    set plain [lindex $::samples $idx]
    set cipher [Encrypt $plain]

    .get.t delete 1.0 end
    .get.t insert end $cipher

    set ::C(sample,idx) $idx
    set ::C(sample,cipher) $cipher
    set ::C(sample,plain) $plain
    set ::C(sample,info) "#$idx of [llength $::samples]"
}
##+##########################################################################
#
# Encrypt -- Encrypts plain text with a random substitution key
#
proc Encrypt {plain} {
    while {1} {
        set key [Shuffle $::AB]
        set ok 1
        set mapping {}
        foreach a $::AB b $key {
            lappend mapping $a $b
            if {$a == $b} {                     ;# Letter can't map to itself
                set ok 0
                break
            }
        }
        if {$ok} break
    }
    set cipher [string map $mapping $plain]
    return $cipher
}
##+##########################################################################
#
# GetHint -- Grabs a random hint from plain/cipher text
#
proc GetHint {cipher plain} {
    regsub -all {[^A-Z]} $cipher "" cipher
    regsub -all {[^A-Z]} $plain "" plain
    set idx [expr {int(rand() * [string length $cipher])}]
    set hint "[string index $cipher $idx]=[string index $plain $idx]"
    return $hint
}
##+##########################################################################
#
# GetTodaysCryptogramCorner -- Scraps todays cryptogram from
# Cryptogram Corner web site.
#
proc GetTodaysCryptogramCorner {} {
    destroy .get

    set url http://www.geocities.com/cryptogramcorner/
    set html [DownloadPage $url]
    set month [ScrapCryptogramCorner $html]

    set thisDay [clock format [clock seconds] -format %d]
    set thisDay [expr {$thisDay > 0 ? $thisDay-1 : $thisDay}]
    lassign [lindex $month $thisDay] cipher plain hint
    PrepCipherText $cipher $plain $hint
    set ::C(sample,info) ""
    set ::C(sample,index) ""
}
proc GetYearOfCryptogramCorner {year} {
    if {$year < 2000} { error "year must be 2000 or greater" }

    set samples {}
    foreach month {jan feb mar apr may jun jul aug sep oct nov dec} {
        set date "$month[string range $year end-1 end]"
        puts "fetching $date"
        set url "http://www.geocities.com/cryptogramcorner/${date}.htm"
        if {[catch {set html [DownloadPage $url]}]} {
            puts "  error: skipping $date"
            continue
        }
        set all [ScrapCryptogramCorner $html]
        set samples [concat $samples $all]
    }
    set plainSamples ""
    set cnt 0
    foreach sample $samples {
        set plain [lindex $sample 1]
        if {$plain ne ""} {
            append plainSamples "    {$plain}\n"
            incr cnt;
        }
    }

    puts "total: $cnt"
    return $plainSamples
}
##+##########################################################################
#
# ScrapCryptogramCorner -- Scraps all cryptograms puzzle from the
# html for Cryptogram Corner.
#
proc ScrapCryptogramCorner {html} {
    global SAMPLES

    set re "if\\s*?\\(\\s*?thisday\\s*?==\\s*?\\d+?.*?\}"

    foreach match [regexp -all -inline $re $html] {
        set n [regexp {thisday\s*==\s*(\d+)} $match . thisDay]
        if {! $n} { error "can't extract thisDay" }

        set all [regexp -all -inline {VAR[123]="(.*?)"} $match]
        if {$all ne ""} {
            lassign $all . var1 . var2 . var3
            set SAMPLES(cipher,$thisDay) "$var1 $var2 $var3"
            set n [regexp {msg\s*?=\s*?"(.=.)"} $match . SAMPLES(hint,$thisDay)]
            if {! $n} { error "can't extract hint for $thisDay" }
        } else {
            regsub -all {"<.*?>"} $match "" match
            regsub -all {"Keyword:.*?"} $match "" match
            set txt [join [regexp -inline -all {".*?"} $match]]
            set txt [string map {\" ""} $txt]
            set SAMPLES(plain,[expr {$thisDay-1}]) $txt
        }
    }
    set all {}
    foreach arr [lsort -dictionary [array names SAMPLES cipher,*]] {
        set thisDay [lindex [split $arr ","] 1]
        set plain [expr {[info exists SAMPLES(plain,$thisDay)] \
                             ? $SAMPLES(plain,$thisDay) : ""}]
        set hint [expr {[info exists SAMPLES(hint,$thisDay)] \
                            ? $SAMPLES(hint,$thisDay) : ""}]
        lappend all [list $SAMPLES($arr) $plain $hint]
    }
    return $all
}
proc ValidateSample {thisDay cipher plain hint} {
    if {$plain eq ""} {
        if {$thisDay != 31} {
            puts "  no plain text: $thisDay"
        }
        return 0
    }
    regsub -all {[^A-Z ]} $cipher "" cipher
    regsub -all {[A-Z]} $cipher "@" cipher
    regsub -all {\s+} $cipher " " cipher
    regsub -all {[^A-Z ]} $plain "" plain
    regsub -all {[A-Z]} $plain "@" plain
    regsub -all {\s+} $plain " " plain
    if {$cipher ne $plain} {
        puts "  bad text: $thisDay"
        return 0
    }
    if {$hint eq ""} {
        puts "  no hint: $thisDay"
        return 0
    }
    return 1
}
##+##########################################################################
#
# DownloadPage -- fetches a web page
#
proc DownloadPage {url} {
    set token [::http::geturl $url]
    ::http::wait $token
    if {[::http::ncode $token] != 200} {
        error "cannot download $url: [::http::ncode $token]"
    }
    set html [::http::data $token]
    ::http::cleanup $token
    return $html
}
##+##########################################################################
#
# IsSolved -- Determines if we've solved the cryptogram
#
proc IsSolved {} {
    global C

    if {$C(solved)} return
    if {$C(plain) eq ""} return

    set plain [string trim "$C(0,plain) $C(1,plain) $C(2,plain) $C(3,plain)"]
    if {$plain ne $C(plain)} return
    set C(solved) 1
    Highlight 1
    catch {
        set fname [file join [pwd] [file dirname $::argv0] solved.txt]
        set fout [open $fname a]
        puts $fout "$C(sample,idx) [clock format [clock seconds]]"
        close $fout
    }
}
##+##########################################################################
#
# Highlight -- Highlights display for victory
#
proc Highlight {onOff} {
    set clr [expr {$onOff ? "yellow" : "white"}]
    foreach w [winfo child .txt] {
        $w config -background $clr
    }
}
##+##########################################################################
#
# HighlightCipherLetter -- Highlights a letter in the cryptogram
#
proc HighlightCipherLetter {ltr} {
    global C

    for {set row 0} {$row < $C(CipherLines)} {incr row} {
        set w .txt.f${row}cipher
        ${w} config -state normal
        $w tag remove highlightLetter 1.0 end
        if {$ltr ne ""} {
            foreach idxs [regexp -all -inline -indices $ltr $C($row,cipher)] {
                set idx [lindex $idxs 0]
                $w tag add highlightLetter 1.$idx 1.[expr {$idx+1}]
            }
        }
        ${w} config -state disabled
    }
}
##+##########################################################################
#
# RightWindow -- Positions window on right edge of the main window
#
proc RightWindow {w {W .}} {
    update idletasks

    set W [winfo toplevel $W]
    set y [expr {3 + [winfo y $W]}]              ;# Top of main window
    set x [expr {15 + [winfo x $W] + [winfo width $W]}] ;# Right side

    set right [expr {$x + [winfo reqwidth $w]}]
    if {$right > [winfo screenwidth $W] + 20} {
        wm geom $w -0+$y
    } else {
        wm geom $w +$x+$y
    }
}
set samples {
    {TO PERSONS STANDING ALONE ON A HILL DURING A CLEAR MIDNIGHT SUCH AS THIS, THE ROLL OF THE WORLD EASTWARD IS ALMOST A PALPABLE MOVEMENT. THOMAS HARDY}
    {TO TRAVEL HOPEFULLY IS A BETTER THING THAN TO ARRIVE. ROBERT LOUIS STEVENSON}
    {LOVE HAS ITS OWN INSTINCT, FINDING THE WAY TO THE HEART, AS THE FEEBLEST INSECT FINDS THE WAY TO ITS FLOWER, WITH A WILL WHICH NOTHING CAN DISMAY NOR TURN ASIDE. HONORE DE BALZAC}
    {BATON TECHNIQUE IS TO A CONDUCTOR WHAT FINGERS ARE TO A PIANIST. IGOR MARKEVITCH}
    {I ONCE WANTED TO BECOME AN ATHEIST, BUT I GAVE UP ... THEY HAVE NO HOLIDAYS. HENNY YOUNGMAN}
    {IT IS BECAUSE HUMANITY HAS NEVER KNOWN WHERE IT WAS GOING THAT IT HAS BEEN ABLE TO FIND ITS WAY. OSCAR WILDE}
    {THE ARTIST MUST BE AN EGOTIST BECAUSE, LIKE THE SPIDER, HE DRAWS ALL HIS BUILDING MATERIAL FROM HIS OWN BREAST. RHETA CHILDE DORR}
    {THE FINAL TEST OF A LEADER IS THAT HE LEAVES BEHIND HIM IN OTHER MEN THE CONVICTION AND THE WILL TO CARRY ON. WALTER LIPPMANN}
    {TOTAL PHYSICAL AND MENTAL INERTIA ARE HIGHLY AGREEABLE, MUCH MORE SO THAN WE ALLOW OURSELVES TO IMAGINE JOHN KENNETH GALBRAITH}
    {THE SKYSCRAPER ESTABLISHES THE BLOCK, THE BLOCK CREATES THE STREET, THE STREET OFFERS ITSELF TO MAN. ROLAND BARTHES}
    {IT IS REMARKABLE HOW EASILY AND INSENSIBLY WE FALL INTO A PARTICULAR ROUTE, AND MAKE A BEATEN TRACK FOR OURSELVES. HENRY DAVID THOREAU}
    {I'M NOTORIOUS FOR GIVING A BAD INTERVIEW. I'M AN ACTOR AND I CAN'T HELP BUT FEEL I'M BORING WHEN I'M ON AS MYSELF. ROCK HUDSON}
    {TONGUES WRANGLED DARK AT A MAN .. HE BUTTONED HIS OVERCOAT AND STOOD ALONE .. IN A SNOWSTORM, RED HOLLYBERRIES, THOUGHTS, HE STOOD ALONE. CARL SANDBURG}
    {A POLICY IS A TEMPORARY CREED LIABLE TO BE CHANGED, BUT WHILE IT HOLDS GOOD IT HAS GOT TO BE PURSUED WITH APOSTOLIC ZEAL. MOHANDAS GANDHI}
    {IT IS TO BE REGRETTED THAT THE RICH AND POWERFUL TOO OFTEN BEND THE ACTS OF GOVERNMENT TO THEIR SELFISH PURPOSES. ANDREW JACKSON}
    {GOODNESS AND EVIL NEVER SHARE THE SAME ROAD, JUST AS ICE AND CHARCOAL NEVER SHARE THE SAME CONTAINER. CHINESE PROVERB}
    {A FOOL BOLTS PLEASURE, THEN COMPLAINS OF MORAL INDIGESTION. MINNA ANTRIM}
    {I LIKE TO COLLECT EXPERIENCES THE WAY OTHER PEOPLE LIKE TO COLLECT COINS AND STAMPS. MICHAEL MCGUIRE}
    {TRUTH IS TOUGH. IT WILL NOT BREAK, LIKE A BUBBLE, AT A TOUCH. NAY, YOU MAY KICK IT ABOUT ALL DAY LIKE A FOOTBALL, AND IT WILL BE ROUND AND FULL AT EVENING. OLIVER WENDELL HOLMES, SR}
    {I WAS THROWN OUT OF NYU MY FRESHMAN YEAR ... FOR CHEATING ON MY METAPHYSICS FINAL. YOU KNOW, I LOOKED WITHIN THE SOUL OF THE BOY SITTING NEXT TO ME. WOODY ALLEN}
    {PEOPLE WHOSE SENSIBILITY IS DESTROYED BY MUSIC IN TRAINS, AIRPORTS, LIFTS, CANNOT CONCENTRATE ON A BEETHOVEN QUARTET. WITOLD LUTOSLAWSKI}
    {IN PRISON, THOSE THINGS WITHHELD FROM AND DENIED TO THE PRISONER BECOME PRECISELY WHAT HE WANTS MOST OF ALL. ELDRIDGE CLEAVER}
    {HAS ANYBODY EVER SEEN A DRAMATIC CRITIC IN THE DAYTIME? OF COURSE NOT. THEY COME OUT AFTER DARK, UP TO NO GOOD. P. G. WODEHOUSE}
    {INGRATITUDE IS ALWAYS A SIGN OF WEAKNESS. I HAVE NEVER OBSERVED THAT ACCOMPLISHED PEOPLE WERE UNGRATEFUL. JOHANN WOLFGANG VON GOETHE}
    {PRO FOOTBALL IS LIKE NUCLEAR WARFARE. THERE ARE NO WINNERS, ONLY SURVIVORS. FRANK GIFFORD}
    {WHAT'S GREAT ABOUT THIS COUNTRY IS THAT AMERICA STARTED THE TRADITION WHERE THE RICHEST CONSUMERS BUY ESSENTIALLY THE SAME THINGS AS THE POOREST. ANDY WARHOL}
    {IMPORTANT PRINCIPLES MAY, AND MUST, BE INFLEXIBLE. ABRAHAM LINCOLN}
    {THE RIVER KNOWS THE WAY TO THE SEA ... WITHOUT A PILOT IT RUNS AND FALLS ... BLESSING ALL LANDS WITH ITS CHARITY. RALPH WALDO EMERSON}
    {IT IS A CONSOLATION TO THE WRETCHED TO HAVE COMPANIONS IN MISERY. PUBLIUS SYRUS}
    {THE GAP BETWEEN THE COMMITTED AND THE INDIFFERENT IS A SAHARA WHOSE FAINT TRAILS, FOLLOWED BY THE MIND'S EYE ONLY, FADE OUT IN SAND. NADINE GORDIMER}
    {EARLY AND PROVIDENT FEAR IS THE MOTHER OF SAFETY. EDMUND BURKE}
    {WE MAKE GUILTY OF OUR DISASTERS THE SUN, THE MOON, AND STARS, AS IF WE WERE VILLAINS ON NECESSITY, FOOLS BY HEAVENLY COMPULSION. WILLIAM SHAKESPEARE}
    {THE PRESENCE OF HUMANS, IN A SYSTEM CONTAINING HIGH SPEED ELECTRONIC COMPUTERS AND HIGH SPEED, ACCURATE COMMUNICATIONS, IS QUITE INHIBITING. STUART LUMAN SEATON}
    {ONCE MY HEART WAS CAPTURED, REASON WAS SHOWN THE DOOR, DELIBERATELY AND WITH A SORT OF FRANTIC JOY. GEORGE SAND}
    {I DON'T WANT TO READ WHAT IS GOING TO SLIDE DOWN EASILY. THERE HAS TO BE SOME CRUNCH, A CERTAIN AMOUNT OF RESILIENCE. JOHN ASHBERY}
    {ALL OF OUR LIVES IS A REBUS ... OF LITTLE WOODEN ANIMALS PAINTED SHY ... TERRIFIC COLORS, MAGNIFICENT AND HORRIBLE ... CLOSE TOGETHER. JOHN ASHBERY}
    {IT IS WISE TO APPLY THE OIL OF REFINED POLITENESS TO THE MECHANISM OF FRIENDSHIP. COLETTE}
    {I RECOGNIZE NO RIGHTS BUT HUMAN RIGHTS ... I KNOW NOTHING OF MEN'S RIGHTS AND WOMEN'S RIGHTS ... ANGELINA GRIMKE}
    {HE USES STATISTICS AS A DRUNKEN MAN USES LAMP POSTS ... FOR SUPPORT RATHER THAN ILLUMINATION. ANDREW LANG}
    {THE SEARCH FOR CONSPIRACY ONLY INCREASES THE ELEMENTS OF MORBIDITY AND PARANOIA AND FANTASY IN THIS COUNTRY. ANTHONY LEWIS}
    {LIFE IS HARDLY MORE THAN A FRACTION OF A SECOND. SUCH A LITTLE TIME TO PREPARE ONESELF FOR ETERNITY!!! PAUL GAUGUIN}
    {IT AIN'T NO SIN IF YOU CRACK A FEW LAWS NOW AND THEN, JUST SO LONG AS YOU DON'T BREAK ANY. MAE WEST}
    {I PREFER DRAWING TO TALKING. DRAWING IS FASTER, AND LEAVES LESS ROOM FOR LIES. LE CORBUSIER}
    {WE SAY THAT THE HOUR OF DEATH CANNOT BE FORECAST, BUT WHEN WE SAY THIS WE IMAGINE THAT HOUR AS PLACED IN AN OBSCURE AND DISTANT FUTURE. MARCEL PROUST}
    {OUR FATHERS HAD THEIR DREAMS. WE HAVE OURS. THE GENERATION THAT FOLLOWS WILL HAVE ITS OWN. WITHOUT DREAMS AND PHANTOMS MAN CANNOT EXIST. OLIVE SCHREINER}
    {I DISCOVERED EARLY IN MY MOVIE WORK THAT A MOVIE IS NEVER ANY BETTER THAN THE STUPIDEST MAN CONNECTED WITH IT. BEN HECHT}
    {WHEN THE SNOW LAY DEEPEST NO WANDERER VENTURED NEAR MY HOUSE FOR A WEEK OR FORTNIGHT AT A TIME, BUT THERE I LIVED AS SNUG AS A MEADOW MOUSE. HENRY DAVID THOREAU}
    {IN THE DIME STORES AND BUS STATIONS, ... PEOPLE TALK OF SITUATIONS, ... READ BOOKS, REPEAT QUOTATIONS, ... DRAW CONCLUSIONS ON THE WALL. BOB DYLAN}
    {EXERCISE AND APPLICATION PRODUCE ORDER IN OUR AFFAIRS, HEALTH OF BODY, CHEERFULNESS OF MIND, AND THESE MAKE US PRECIOUS TO OUR FRIENDS. THOMAS JEFFERSON}
    {EACH CITY IS AN ARCHETYPE RATHER THAN A PROTOTYPE, AN EXAGGERATED EXAMPLE FROM WHICH TO DERIVE LESSONS FOR THE TYPICAL. ROBERT VENTURI}
    {WHEN CHILDHOOD DIES, ITS CORPSES ARE CALLED ADULTS. BRIAN ALDISS}
    {THERE IS NOTHING EXEMPT FROM THE PERIL OF MUTATION. THE EARTH, HEAVENS, AND WHOLE WORLD IS THEREUNTO SUBJECT. SIR WALTER RALEIGH}
    {I JUST PUT ON WHAT THE LADY SAYS. I'VE BEEN MARRIED THREE TIMES, SO I'VE HAD LOTS OF SUPERVISION. UPTON SINCLAIR}
    {OF SOME THINGS WE FEEL QUITE CERTAIN. INSIDE THERE IS A CLICK, A KIND OF BELL THAT STRIKES, WHEN THE HANDS OF OUR DESTINY MEET AT THE MERIDIAN HOUR. AMELIA BARR}
    {MY BONES DENOUNCE THE BUCKBOARD BOUNCE AND THE CACTUS HURTS MY TOES. RAY EVANS}
    {IT IS A CONSOLATION TO THE WRETCHED TO HAVE COMPANIONS IN MISERY. PUBLIUS SYRUS}
    {THE GAP BETWEEN THE COMMITTED AND THE INDIFFERENT IS A SAHARA WHOSE FAINT TRAILS, FOLLOWED BY THE MIND'S EYE ONLY, FADE OUT IN SAND. NADINE GORDIMER}
    {HE MUST HAVE A TRULY ROMANTIC NATURE, FOR HE WEEPS WHEN THERE IS NOTHING AT ALL TO WEEP ABOUT. OSCAR WILDE}
    {WHEN YOUNG ONE IS CONFIDENT TO BE ABLE TO BUILD PALACES FOR MANKIND, BUT WHEN THE TIME COMES ONE HAS ONE'S HANDS FULL JUST TO BE ABLE TO REMOVE THEIR TRASH. JOHANN WOLFGANG VON GOETHE}
    {SOUP IS A LOT LIKE A FAMILY. EACH INGREDIENT ENHANCES THE OTHERS. EACH BATCH HAS ITS OWN CHARACTERISTICS. AND IT NEEDS TIME TO SIMMER TO REACH FULL FLAVOR. MARGE KENNEDY}
    {MEN ARE LIKE PLANTS. THE GOODNESS AND FLAVOR OF THE FRUIT PROCEEDS FROM THE PECULIAR SOIL AND EXPOSITION IN WHICH THEY GROW. MICHEL GUILLAUME JEAN DE CREVECOEUR}
    {WE CONSTRUCT A NARRATIVE FOR OURSELVES, AND THAT'S THE THREAD THAT WE FOLLOW FROM ONE DAY TO THE NEXT. PAUL AUSTER}
    {THE SHORTEST ROUTE IS NOT THE MOST DIRECT ONE, BUT RATHER THE ONE WHERE THE MOST FAVORABLE WINDS SWELL OUR SAILS. FRIEDRICH NIETZSCHE}
    {LET US NOT BANKRUPT OUR TODAYS BY PAYING INTEREST ON THE REGRETS OF YESTERDAY AND BY BORROWING IN ADVANCE THE TROUBLES OF TOMORROW. RALPH W. SOCKMAN}
    {THE FUTURE IS BUILT ON BRAINS, NOT PROM COURT, AS MOST PEOPLE CAN TELL YOU AFTER ATTENDING THEIR HIGH SCHOOL REUNION. ANNA QUINDLEN}
    {ONE WHO RESTRAINS HIS APPETITES AVOIDS DEBT. CHINESE PROVERB}
    {INNOVATION IS THE SPECIFIC INSTRUMENT OF ENTREPRENEURSHIP ... THE ACT THAT ENDOWS RESOURCES WITH A NEW CAPACITY TO CREATE WEALTH. PETER DRUCKER}
    {THE MOST SUCCESSFUL COLUMN IS ONE THAT CAUSES THE READER TO THROW DOWN THE PAPER IN A PEAK OF FIT. WILLIAM SAFIRE}
    {POWER IS NOT ONLY WHAT YOU HAVE BUT WHAT THE ENEMY THINKS YOU HAVE. SAUL ALINSKY}
    {SCENES MUST BE BEAUTIFUL, WHICH, DAILY VIEWED, ... PLEASE DAILY, AND WHOSE NOVELTY SURVIVES ... LONG KNOWLEDGE AND THE SCRUTINY OF YEARS WILLIAM COWPER}
    {GRANDDADDY USED TO HANDLE SNAKES IN CHURCH. GRANNY DRANK STRYCHNINE. I GUESS YOU COULD SAY I HAD A LEG UP, GENETICALLY SPEAKING. WESLEY STRICK}
    {NO OTHER MAN MADE DEVICE SINCE THE SHIELDS AND LANCES OF THE ANCIENT KNIGHTS FULFILLS A MAN'S EGO LIKE AN AUTOMOBILE. SIR WILLIAM ROOTES}
    {YOU MAY BE A PRINCESS OR THE RICHEST WOMAN IN THE WORLD, BUT YOU CANNOT BE MORE THAN A LADY. JENNIE JEROME CHURCHILL}
    {BLUNDERS ARE AN INESCAPABLE FEATURE OF WAR, BECAUSE CHOICE IN MILITARY AFFAIRS LIES GENERALLY BETWEEN THE BAD AND THE WORSE. ALLAN MASSIE}
    {IN THE COURSE OF TWENTY CROWDED YEARS ONE PARTS WITH MANY ILLUSIONS. I DID NOT WISH TO LOSE THE EARLY ONES. WILLA CATHER}
    {THE GREAT EVENTS OF LIFE OFTEN LEAVE ONE UNMOVED. THEY PASS OUT OF CONSCIOUSNESS, AND, WHEN ONE THINKS OF THEM, BECOME UNREAL. OSCAR WILDE}
    {TO BE A TOURIST IS TO ESCAPE ACCOUNTABILITY. ERRORS AND FAILINGS DON'T CLING TO YOU THE WAY THEY DO BACK HOME. DON DELILLO}
    {DREAMS COME TRUE. WITHOUT THAT POSSIBILITY, NATURE WOULD NOT INCITE US TO HAVE THEM. JOHN UPDIKE}
    {OUR CAPACITY TO RETALIATE MUST BE, AND IS, MASSIVE IN ORDER TO DETER ALL FORMS OF AGGRESSION. JOHN FOSTER DULLES}
    {TRUE CIVILIZATION DOES NOT LIE IN GAS, NOR IN STEAM, NOR IN TURN TABLES. IT LIES IN THE REDUCTION OF THE TRACES OF ORIGINAL SIN. CHARLES BAUDELAIRE}
    {IN SPRING MORE MORTAL SINGERS THAN BELONG ... TO ANY ONE PLACE COVER US WITH SONG... THRUSH, BLUEBIRD, BLACKBIRD, SPARROW, AND ROBIN THRONG.... ROBERT FROST}
    {THE GOOD MOTHER KNOWS THAT FRUSTRATION TEACHES TOLERANCE AND THAT INSTANT GRATIFICATION IS NOT ALWAYS BEST. ELYSE KARLIN}
    {AS ONE LAMP LIGHTS ANOTHER, NOR GROWS LESS, SO NOBLENESS ENKINDLETH NOBLENESS. JAMES RUSSELL LOWELL}
    {WITH A SMILE OF CHRISTIAN CHARITY GREAT CASEY'S VISAGE SHONE ... HE STILLED THE RISING TUMULT, HE MADE THE GAME GO ON ... ERNEST LAWRENCE THAYER}
    {IF YOU LOOK INTO YOUR OWN MIND, WHICH ARE YOU, DON QUIXOTE OR SANCHO PANZA? ALMOST CERTAINLY YOU ARE BOTH. GEORGE ORWELL}
    {WAR AND PEACE MUST WAIT FOR THE LEISURE OF RETIREMENT, WHICH NEVER REALLY COMES: MEANWHILE IT HELPS TO FURNISH THE LIVING ROOM. ANTHONY BURGESS}
    {I'D TAKE THE BUS DOWNTOWN WITH MY MOTHER, AND THE BIG THING WAS TO SIT AT THE COUNTER AND GET AN ORANGE DRINK AND A TUNA SANDWICH ON TOAST. CONNIE PORTER}
    {A SMALL GARDEN, FIGS, A LITTLE CHEESE, AND, ALONG WITH THIS, THREE OR FOUR GOOD FRIENDS ... SUCH WAS LUXURY TO EPICURUS. FRIEDRICH NIETZSCHE}
    {THERE ARE TIMES WHEN EVEN THE MOST POTENT GOVERNOR MUST WINK AT TRANSGRESSION, IN ORDER TO PRESERVE THE LAWS INVIOLATE FOR THE FUTURE. HERMAN MELVILLE}
    {CHESS, LIKE MATHEMATICS AND MUSIC, IS A NURSERY FOR CHILD PRODIGIES. JAMIE MURPHY}
    {BE STUDIOUS IN YOUR PROFESSION, AND YOU WILL BE LEARNED. BE INDUSTRIOUS AND FRUGAL, AND YOU WILL BE RICH. BE SOBER AND TEMPERATE, AND YOU WILL BE HEALTHY. BENJAMIN FRANKLIN}
    {THE ELEPHANT SNEEZED ... AND FELL ON HIS KNEES ... AND THAT WAS THE END OF THE MONK ... THE MONK, THE MONK. ANIMAL FAIR}
    {CLEANING YOUR HOUSE WHILE YOUR KIDS ARE STILL GROWING IS LIKE SHOVELING THE WALK BEFORE IT STOPS SNOWING. PHYLLIS DILLER}
    {DEFEAT DOESN'T FINISH A MAN ... QUIT DOES. A MAN IS NOT FINISHED WHEN HE'S DEFEATED. HE'S FINISHED WHEN HE QUITS. RICHARD M. NIXON}
    {THE BRAIN MAY BE REGARDED AS A KIND OF PARASITE OF THE ORGANISM, A PENSIONER, AS IT WERE, WHO DWELLS WITH THE BODY. ARTHUR SCHOPENHAUER}
    {TODAY CONVENIENCE IS THE SUCCESS FACTOR OF JUST ABOUT EVERY TYPE OF PRODUCT AND SERVICE THAT IS SHOWING STEADY GROWTH. CHARLES G. MORTIMER}
    {ONE MUST CREDIT AN HYPOTHESIS WITH ALL THAT HAS HAD TO BE DISCOVERED IN ORDER TO DEMOLISH IT. JEAN ROSTAND}
    {WHEN LOVE'S DELIRIUM HAUNTS THE GLOWING MIND, LIMPING DECORUM LINGERS FAR BEHIND. GEORGE GORDON NOEL BYRON}
    {EATING WELL GIVES A SPECTACULAR JOY TO LIFE AND CONTRIBUTES IMMENSELY TO GOODWILL AND HAPPY COMPANIONSHIP. ELSA SCHIAPARELLI}
    {THE GREATER PART OF HUMANITY IS TOO MUCH HARASSED AND FATIGUED BY THE STRUGGLE WITH WANT, TO RALLY ITSELF FOR A NEW AND STERNER STRUGGLE WITH ERROR. FRIEDRICH VON SCHILLER}
    {IT OFFERED BRIGHT LIGHTS AND HEALING ESSENCES ... MEDICINE, COSMETICS, COFFEE, GOSSIP AND CONTEMPLATION OF CHROME AND MARBLE, FIZZ AND SPLIT BANANAS. NEW YORKER}
    {NEVER RELINQUISH CLOTHING TO A HOTEL VALET WITHOUT FIRST SPECIFICALLY TELLING HIM THAT YOU WANT IT BACK. FRAN LEBOWITZ}
    {RIVERS ARE ROADS WHICH MOVE, AND WHICH CARRY US WHITHER WE DESIRE TO GO. BLAISE PASCAL}
    {DUTIES ARE NOT PERFORMED FOR DUTY'S SAKE, BUT BECAUSE THEIR NEGLECT WOULD MAKE THE MAN UNCOMFORTABLE. MARK TWAIN}
    {A CORPSE IS MEAT GONE BAD. WELL AND WHAT'S CHEESE? CORPSE OF MILK. JAMES JOYCE}
    {WITH BOYS YOU ALWAYS KNOW WHERE YOU STAND. RIGHT IN THE PATH OF A HURRICANE. ERMA BOMBECK}
    {I WANDERED LONELY AS A CLOUD .. THAT FLOATS ON HIGH O'ER VALES AND HILLS .. WHEN ALL AT ONCE I SAW A CROWD .. A HOST, OF GOLDEN DAFFODILS. WILLIAM WORDSWORTH}

}
##+##########################################################################
#
# CheatSheet -- Some hints I've collected over time
#
proc CheatSheet {} {
    set data {LETTER FREQUENCY
    ETAOIN SHRDL CUMWF GYPBV KJZQZ

COMMON UNIQUE LETTER COMBINATIONS
    AB/AC       : it/is  be/by
    AB/BC       : no/of
    AB/BA       : no/on
    AB/CB       : to do   to go  in an
    AB/CA       : o[fnr]/[dgnst]o my/am me/am no/anan
                  no/in so/[aiu]s to/at to/it

    ABC/AB      : not/no  and/an  its/it  one/no
    ABC/AC      : two/to  its/is  own/on
    ABC/BA      : not/on  for/of  one/no
    ABC/BC      : the/he  has/as  his/is  man/an
    ABC/CA      : out/to
    ABC/CB      : got/to  not/to
    ABC/BCA     : who/how
    ABC/ABCD'EC : the/they're
    AB'C/ADC    : he'd/his
    AB/ACD'B    : it/isn't
    ABCDC/ABC   : there/the
    ABCD/CDB    : know/own
    ABCD/ABC    : the[nmy]/the your/you

    ABCA        : that  died  else high
    ABAC        : ever  even  away
    ABCDC       : there
    ABCADC      : people
    ABCCAD      : little
    ABth        : with

TWO LETTER WORDS        CONTRACTIONS
    am     be          I'd       I've
    an     he          I'm       o'er
    as     me          he'd      he'll
    at     we          he's      we'll
    be     if          it's      we're
    by     of          we'd      we've
    do     am          can't     she'll
    go     an          don't     you'll
    he     in          isn't     you're
    if     on          one's     you've
    in     do          she'd     they're
    is     go          you'd     could've
    it     no          won't     might've
    me     so          aren't    would've
    my     to          didn't
    no     up          hasn't
    of     or          wasn't
    on     as          doesn't
    or     is          haven't
    so     us          couldn't
    to     at          wouldn't
    up     it
    us     by
    we     my

MOST COMMON THREE LETTER WORDS
    the and for not you but his say
    her she one all out who get man
}

    destroy .cheat
    toplevel .cheat
    wm title .cheat "$::C(title) Cheat Sheet"
    wm transient .cheat .
    wm withdraw .cheat
    text .cheat.t -width 60 -relief raised -wrap word \
        -padx 10 -pady 10 -cursor {} -yscrollcommand {.cheat.sb set}
    ::ttk::scrollbar .cheat.sb -orient vertical -command [list .cheat.t yview]
    ::ttk::button .cheat.dismiss -text Dismiss -command {destroy .cheat}
    pack .cheat.dismiss -side bottom -pady 10
    pack .cheat.sb -side right -fill y
    pack .cheat.t -side top -expand 1 -fill both

    .cheat.t insert end $data
    .cheat.t config -state disabled
    RightWindow .cheat .
    wm deiconify .cheat

    focus .ctrl.key1
}

##+##########################################################################
#
# Help -- a simple help screen
#
proc Help {} {
    catch {destroy .help}
    toplevel .help
    wm title .help "$::C(title) Help"
    wm transient .help .
    wm withdraw .help

    set t .help.t
    text $t -relief raised -wrap word -width 70 -height 33 \
        -padx 10 -pady 10 -cursor {} -yscrollcommand {.help.sb set}
    ::ttk::scrollbar .help.sb -orient vertical -command [list $t yview]
    ::ttk::button .help.dismiss -text Dismiss -command {destroy .help}
    pack .help.dismiss -side bottom -pady 10
    pack .help.sb -side right -fill y
    pack $t -side top -expand 1 -fill both

    set bold "[font actual [$t cget -font]] -weight bold"
    set italic "[font actual [$t cget -font]] -slant italic"
    $t tag config title -justify center -foregr red -font "Times 20 bold"
    $t tag configure title2 -justify center -font "Times 12 bold"
    $t tag configure header -font $bold -spacing3 5
    $t tag configure bold -font $bold
    $t tag configure italic -font $italic
    $t tag configure n -lmargin1 10 -lmargin2 10
    $t tag configure bullet -lmargin1 20 -lmargin2 35 -tabs 35

    $t delete 1.0 end
    $t insert end "$::C(title)\n" title "by Keith Vetter, Oct. 2009\n\n" title2

    set txt "$::C(title) is a tool to aid in solving cryptograms. "
    append txt "It does not solve the cipher, but "
    append txt "instead it has the computer do all the dirty work thus "
    append txt "allowing you to concentrate on solving the puzzle. "
    append txt "It is based "
    append txt "on a similarly named program by Mike Kaplan and "
    append txt "Dick Rufer (see www.rrufer.com).\n\n"
    $t insert end "Introduction\n" header $txt

    set txt "The first step in using $::C(title) is to get a "
    append txt "cryptogram to solve. You can either type in one "
    append txt "your own, use one of the 300 plus built it puzzles, "
    append txt "or extract today's puzzle from the web site "
    append txt "Cryptogram Corner "
    append txt "(http://www.geocities.com/cryptogramcorner/)\n\n"
    $t insert end "Getting Started\n" header $txt

    set txt "After selecting the puzzle, $::C(title) displays the cryptogram "
    append txt "in four rows with a blank line above each row for the solution. "
    append txt "Also displayed is a histogram of letter usage. "
    append txt "As you solve the puzzle, the letters you've already solved are "
    append txt "marked.\n\n"
    $t insert end $txt

    set txt "Using $::C(title)\n"
    $t insert end $txt header
    set txt "Enter ciphered letter in top 'Type-In Field'\n"
    $t insert end \u25cf\t$txt bullet
    set txt "Enter deciphered letter in bottom 'Type-In Field'\n"
    $t insert end \u25cf\t$txt bullet
    set txt "Press Ctrl-Z for undo\n\n"
    $t insert end \u25cf\t$txt bullet

    set txt "How to Cheat\n"
    $t insert end $txt header
    set txt "Press Ctrl-H for hint (if known)\n"
    $t insert end \u25cf\t$txt bullet
    set txt "Press Ctrl-S for solution (if known)\n"
    $t insert end \u25cf\t$txt bullet
    set txt "Press Ctrl-C for a helpful cheat sheet\n"
    $t insert end \u25cf\t$txt bullet

    $t config -state disabled
    RightWindow .help .
    wm deiconify .help

    focus .ctrl.key1
}

################################################################

DoDisplay
GetRandom
return