Tcl/Tk占いの館
 どうです、ひとつ、Tcl/Tkでいんちき…いやいや、何かいいましたか? 高邁な精神の結晶として、占いのプログラムを作ろうかと。 まずは基本ですから、姓名判断をやりましょう。姓名判断。

wm title . "Uranai House Tcl/Tk"
label .la -text "名前を英文字で入力して下さい" -anchor w
entry .e  -textvariable Name
button .cmda -text OK -command judge_name
button .cmde -text 終了 -command exit
text   .ta -state disabled

place .la -relx 0.0 -rely 0.0 -relw 1.0 -relh 0.15
place .e  -relx 0.1 -rely 0.15 -relw 0.6 -relh 0.15
place .cmda -relx 0.75 -rely 0.15 -relw 0.1 -relh 0.15
place .cmde -relx 0.90 -rely 0.15 -relw 0.1 -relh 0.15
place .ta -relx 0.0 -rely 0.3 -relw 1.0 -relh 0.7

. configure -width 300 -height 100

proc nameis {pattern name} {
    if {[regexp -nocase "^(\\w+\\s+)?${pattern}(\\s+\\w+)?\$" \
         [string trim $name] all p1 p2]} {
        if {"$p1" != "" || "$p2" != ""} { return 1}
        return 0
    } else { return 0 }
}

proc judge_name {} {
    global Name
    .ta configure -state normal
    .ta delete 1.0 end

    if {[nameis suzuki $Name]} {
	.ta insert end すずきさんは、今日はラッキーです
    } elseif {[nameis tanaka $Name]} {
	.ta insert end たなかさんは、今日はラッキーです
    } else {
	.ta insert end 今日は気の集まりが悪く、占えません
    }
    .ta configure -state disabled
}
   うーん。というかなんですかこれは。 要するに「すずき」さんと「たなか」さんを検出しているだけなんですね。 わはは(帰れ) さてその検出なんですが、 Tcl/Tkは他のawkやPerl、PythonなどのUNIX起源のスクリプト言語と同様、 正規表現という文字列のパターンマッチに使う言語集合をサポートしているので、 C言語などでは簡単に書けないチェックを行うことができます。 言語集合というとなにやらふがふがですが、 ある文字列の中に、決まったパターンが含まれるか、 というのを調べるための簡単なコマンドで、 たとえば、「Tanaka」さんは該当するけど、「Hatanaka」さんは該当しない、 というようなことを正規表現のパターンで調べることができます。 Tcl/Tkの正規表現はPerlのそれに非常によく似ています。 正規表現はUNIXになじみがないと難しいこともあり、 マニュアルではかなりの分量をさいて解説が加えられています。 日本語のリソースが必要なら、PerlやPythonの書籍にも載っているはずです。
    if {[regexp -nocase "^(\\w+\\s+)?${pattern}(\\s+\\w+)?\$" \
         [string trim $name] all p1 p2]} {
        if {"$p1" != "" || "$p2" != ""} { return 1}
        return 0
    } else { return 0 }


   えー、けむにまいてしまいましたが、 このページの本来の趣旨は正規表現などというつまらないものではなく(+_+;)、 あくまで高邁な精神で占いをですね(帰れ)

…気を取りなおして、誕生日占いをやりましょう。誕生日占い。 月と日を入力してもらいましょう。

wm title . "Uranai House Tcl/Tk"
label .la -text "誕生日を入力してね" -font 8x16
frame .f -rel groove -borderwidth 3
entry .f.ea -width 5
label .f.la -text 月
entry .f.eb -width 5
label .f.lb -text 日
button .f.cmda -text 占う   -command {uranai [.f.ea get] [.f.eb get] .ta}
button .f.cmde -text やめる -command exit
pack .f.ea .f.la .f.eb .f.lb .f.cmda .f.cmde -side left
text .ta -height 5 -state disabled -font {Helvetica 12 normal} -width 30
pack .la .f .ta -side top -fill x

proc uranai {month day ta} {
    array set days {
	1 31 2 29 3 31 4 30 5 31 6 30 7 31 8 31 9 30 10 31 11 30 12 31
    }
    set messages {
	{ちょっとした発言が、思わぬ誤解につながることも。}
	{金運なし。友人に借りたCDは返すな。}
	{張り込みには富士銀行の脇が吉。}
	{言わないことは伝わらない。はっきり意見を言おう。}
	{ラッキー食品はてりやきチキン。}
	{緑の濃い野菜はスタミナの源です。}
	{部屋を掃除するといいことがあります。}
    }
    if {$month >= 1 && $month <= 12 && $day >= 1 && $day <= $days($month)} {
	set n [expr ($month+$day) % [llength $messages]]
	$ta configure -state normal
	$ta delete 1.0 end
	$ta insert end [lindex $messages $n]
	$ta configure -state disabled
    } else {
	tk_messageBox -message "月日が不正でしゅ。"
    }
}
 

 今度は誕生日を入力してもらうと、 上のように見事、最近はやりの無責任な占いが出てきます。 占いじゃなく、単なる健康のアドバイスじゃねーか、というのもありますが。

 Tcl/Tkの配列は、大きくリストと配列(ほんとの配列)の2種類があります。 Tcl/Tkのリストは、こんな形が代表的ですが、

    set messages {
	{ちょっとした発言が、思わぬ誤解につながることも。}
	{金運なし。友人に借りたCDは返すな。}
	{張り込みには富士銀行の脇が吉。}
	{言わないことは伝わらない。はっきり意見を言おう。}
	{ラッキー食品はてりやきチキン。}
	{緑の濃い野菜はスタミナの源です。}
	{部屋を掃除するといいことがあります。}
    }
いつも上のような形とは限りません。 たとえば、"This is the joy in the world." これもリストです。また、 {This is the joy in the world} これもリストです。また、
{"joy in the world" "joy on your mind" "joy of your control"}
これは一見、3つの文字列からなる単なるリストですが、 同時にそれぞれが4要素の文字列からなる3つのリスト、 つまり「リストのリスト」でもあります。はにゃ?これならわかるでしょうか。
{{A B C D} {E F G H} {I J K L}}
というわけで、Tclのリストは要するに文字列とほぼ等価に変換が行えるというのが特徴で、リストがそのままTclコマンドとして実行できたり、 英単語の羅列の文字列のうち何番目かの単語がリストを扱うコマンドで抽出できたりという柔軟な処理ができます。 これ、慣れるまでかなり違和感があるので、 このあたりがTclを嫌う人が出てくる要因でもあるのですが。

 一方配列ですが、これは

set days(10) 31
set days(11) 30
set days(12) 31
こんな感じで括弧の中の文字列(10,11,12)と値(31,30,31)を結びつけるのに使います。 ここでは array set コマンドを使って一気にセットしています。
    array set days {
	1 31 2 29 3 31 4 30 5 31 6 30 7 31 8 31 9 30 10 31 11 30 12 31
    }

 上のサンプル、じゃなかった占い(あくまで占い)プログラムでは、 リストや配列に関するコマンドがいくつか出てきていますが、 詳しいところはマニュアルを当たってみてください。

 


タネがばれては…
 さて、上のプログラム、どうやって「占っているか」というと、 え、わかりますか?なに? 「誕生日の月と日を足して候補の数で割った余りでメッセージを選んでるだけじゃねーか!」って。 どうしてわかったのでしょう。 それは Tcl/Tk というかスクリプト言語で書かれたプログラムの本質的な宿命でありまして、 つまりプログラマ以外の人でも、メモ帳で開けばプログラムがそのまま見れるわけで、 インチキのタネがこのようにすぐにばれてしまうわけです。

 困った。

 では、どうしましょう。普通の答えは「C言語で書く」でしょう。 C言語のプログラムなら、機械コードにコンパイルしてしまいますから、 普通の人様には読めません。 なるほど名案ですね。しかし、それだと、せっかく「簡単にウィンドウアプリケーションを作れる」というTcl/Tkの利点がなくなってしまいます。 またあの枕のようなWindows APIやXウィンドウシステムの分厚いマニュアルに戻らないといけないのでしょうか。 いやいや、そこがTcl/Tkだとうまく切り抜ける方法がいくつかあるのです。

 まず、ウィンドウアプリではなく普通のプログラム (コンソールアプリケーション)としてC言語で「占い」プログラムを作りましょう。 そのさい、 コマンドラインで誕生日でも姓名でもいいので渡せるようにするのがポイントです。 C処理系はUNIXならccかgccコンパイラがついてきますし、 MS-WindowsならC++ BuilderなりVisual C++なりでビルドしましょう。

 

   こんな感じに動くことを確認します。

 それでは、先ほどのTcl/Tkプログラムを改造して、 このCプログラムを呼び出して結果を受けとって表示するようにします。

 

wm title . "Uranai House Tcl/Tk"
label .la -text "誕生日を入力してね" -font 8x16
frame .f -rel groove -borderwidth 3
entry .f.ea -width 5
label .f.la -text 月
entry .f.eb -width 5
label .f.lb -text 日
button .f.cmda -text 占う   -command {uranai [.f.ea get] [.f.eb get] .ta}
button .f.cmde -text やめる -command exit
pack .f.ea .f.la .f.eb .f.lb .f.cmda .f.cmde -side left
text .ta -height 5 -state disabled -font {Helvetica 12 normal} -width 30
pack .la .f .ta -side top -fill x

proc uranai {month day ta} {
    array set days {
	1 31 2 29 3 31 4 30 5 31 6 30 7 31 8 31 9 30 10 31 11 30 12 31
    }
    if {$month >= 1 && $month <= 12 && $day >= 1 && $day <= $days($month)} {
	set command "/the/path/of/uranai.exe $month $day"
	set result [eval exec $command]
	$ta configure -state normal
	$ta delete 1.0 end
	$ta insert end $result
	$ta configure -state disabled
    } else {
	tk_messageBox -message "月日が不正でしゅ。"
    }
}
 

まず、例のインチキメッセージのリストが消え、インチキだとばれなくなりました。 そして

   set command "/the/path/of/uranai.exe $month $day"
   set result [eval exec $command]
このように、execというコマンドで、 UNIX上でもWindows上でも、他のプログラムを実行し、そのプログラムが標準出力 (stdout) に書き出したメッセージを取得することができます。 よかった。これで真のTcl/Tk占いの館が完成したのです。

本当によかったのか?

 


Tcl/Tkを拡張する?
 ところで、上のように他のプログラムを呼び出すほかに、 もっとすごい解決方法もあります。 それはTcl/Tk処理系を拡張して、
   set message [uranai 1 15]
こんな機能をもつ「uranai」というTclコマンドをTcl/Tkに追加してしまおうというものです。 これは、別にTcl/Tk処理系のソースプログラムを解析して書き換えよう、 というのではありません。 Tcl/Tk処理系と一緒にもれなくついてくる「Tclライブラリ」「Tkライブラリ」 と我々の、例のC言語で書いたインチキ占いプログラムをリンクして、 共有ライブラリ(Windows上ではダイナミックリンクライブラリ(DLL)) を作れば Tcl/Tk に新しい Tclコマンドを追加することは簡単にできます。

 または逆の考え方もあります。 そのC言語で書いたインチキ占いのプログラムと「Tclライブラリ」「Tkライブラリ」 をリンクすると、今度は占いプログラムの中からウィンドウを出したり、 ボタンに反応したり、 ということが簡単に出来るようになります。

 これはどちらの方法も、たいへん有効で、興味深い方法です。 このサイトでは、この方法のためになもなも全体をさいて紹介していますが、 まずは簡単なとっつきだけ次のページに簡単にまとめたいと思います。

やねうらの top
(first uploaded 2000/03/24 last updated (not ever) , Urano398 - K.Ushirodani )