2011年5月24日火曜日

wxPythonでハングマン

GUIでずーっと苦労してるんですけれども(笑)。


今回、Qtからはじまって、GTK+、wxWidgetsと調べまわっていました。あっちこっちのブログなりWebページなり見て回ってたんですが。


そして非常に意外だったのは、その殆どのケースでは「プログラムを作り上げる」トコまで行ってないんですね(笑)。曲がりなりにもアプリケーションを一本作り上げている、殆ど唯一の例外は我らが@kaorin_linux氏のおっさんでも解るPythonくらいで、他は殆ど、Qt CreatorなりGladeなりwxGladeなりで、ウィンドウを作るだけで終わっちゃってる、つまりGUIのスケルトンを生成する事で終わっちゃってるんです。つまり、本当に知りたい、要するに「動くプログラムを作る」トコまで到達してないんです。


より正確に言うと、アプリケーションを作った例ってのはあります。ただし、それはテキストエディタだったりして。いや、テキストエディタを作ろう、ってのは悪い話じゃないです。フツーに考えれば。ただし、いまどきのGUI Buiderなんかはテキスト入力用ウィジェットくらいデフォルトで備えてるんで、これは実はウィジェットの使い方講座です。従って、ボタン貼っつけたりするのと事実上変わらないんですよ。これじゃああんま実際的なGUIのソフトウェアを書こう、と言う話までは発展しませんよね。しないんです。


今回調べてみて思ったのは、設計思想としてはLinux(あるいはフリーソフトウェア)のGUIの構築法は非常に優秀だと言う事です。まあ、Windowsの方の設計理念はあんま良く知らないんですが、少なくとも、要するにGUI部分はGUIとして独立させ、CLI部分はCLI部分で書けるようにしているって事です。恐らく背景的には、歴史的にはUNIXはCLI文化であり、またCLIのプログラムを書ける人はたくさんいるんです。そこで、そう言う人達のスクリプト的な資産を即GUIアプリに転用出来るように考えてるんでしょうね。この辺、ベースとなる、例えばXや、あるいはその上に被さるQtやGTK+が意識して作られているのかもしれません。


専門的にはメッセージパッシングって呼ぶんですけど、OOPの根幹思考法がこれ程か、って程強調されています。つまり、GUIで組み上げたスケルトンとCLIで書かれたスクリプトが相互にやり取りするモデルとなってる。あるいは通信しあう、って事ですか。これで理論的には至極簡単にGUIアプリが書けるようにはなってるんです。


理論的には、ですよ(笑)。残念ながら、アッチコッチのブログなりWebページなり見て回ったんですが、実はその「通信をどうやるのか」まで行ってないんですよ(笑)。殆ど唯一の例外って言って良いのが、@kaorin_linux氏のページなんです。


何でこんな状況なんですかねえ(苦笑)。一つ言えるのはLinux等のフリーソフトウェア/OSSのGUI Builderがショボ過ぎるって事があるんでしょう。つまりデザイン的に言うと、使ってる人がGUI側のコード構成なんて全く考えなくて良いように作られてない事があるのかもしれません。割に剥き出しです。つまりCLIでGUIのプログラムを書ける知識がいる


もちろん、OSS界隈ではハッカーな人がたくさんいるんでしょうし、CLIでコードを弄る方が大事なんで、中を弄れるようにしておくのが吉だ、と言う考え方もあるでしょう。でも間違ってますよそれ(笑)。次の三点でそれは間違ってる、と断言出来ます。


第一に、そもそもOOPってのは中身を隠蔽出来るってのが前提です。つまりユーザ(この場合プログラマですか)が気にしなくて良い部分は気にせんで良いシステムになってるのが前提なんです。GUIスケルトンがどんなコードで組み立てられてる、なんつーのは全く気にする必要が無い。まずこのOOPの基本原則に反している。第二にGUI Builderが提供するものはあくまでスケルトンであって、実際のトコ、これはいわゆるフロントエンドですよね。ぶっちゃけこの部分はプログラムじゃないって考えて良いんです。プログラム本体はあくまでCLIの部分。つまり、プログラマが全力投球して組み立てなきゃならないのはCLI部分であって、GUI部分なんつーモノに時間割くのは本末転倒なんですよ。一体何の為にGUI部分とCLI本体がメッセージパッシング出来るように設計してあるのか。仮にGUIもCLIもゴッチャになってるVB的なシームレス環境ならいざ知らず、根本的な設計思想にも反してるんです。


そして何より、GUI部分って書くのがつまらないんですよ(笑)。最終的な見栄え調整がGUIの、要するに役割なんですが、やってる事っつったらパーツ並べていくだけのバッチ処理が本質。大して創造性があるわけでもないですし、仮に創造性を下手に発揮したらUIの統一感がグチャグチャです。こんなもんにプログラマの知的な時間を取るべきだ、なんつーのは全くバカげてる。「それでもGUIのコードを見て弄れる自由が欲しい」なんつーのは単なるヘタなこだわりです。味噌汁でツラ洗って出直して来た方が良い(笑)。いや、マジで(笑)。


つまり、Linuxを中心として提供されているGUI関係のツールってのは設計思想に反して出来が悪過ぎるんですよ。本来、GUIスケルトンとCLIのプログラムがそれぞれの役割を全うしながら通信するってモデルなら、GUI Builderは(MicrosoftのVisual Studioより)もっと直感的に使えるツールであり、吐き出されたGUI部分は全く気にせんで良いくらいじゃないといけない。要するに現状見る限り、理論先行の頭でっかちな状況なんです。そう言わざるを得ない。


だから色んなWebページの作者であるとか、あるいはブログの書き手とか、恐らく元々はWinな人たちで、趣味でLinuxもやって、んで「ああ、LinuxでもGUI Builder的なモノもあるのね」と興味を持って触ってみる。で、結局、GUI Builderってのは彼らのアタマの中では当然、「簡単にGUIアプリを構築出来るモノ」じゃなきゃいけない筈なんですが、触ってみるとCLIでのGUIプログラミング知識が必要となる。何でそこまで調べなきゃアカンのだ、ああ、メンド臭くなってきた、ってなるのも分かるんですよ(笑)。だってGUI的な決まりきった定形処理する割には覚えなきゃいけないモノが多すぎるんですから。ホントメンドくせえんだもの(笑)。


加えると、海外のTutorial系も似たり寄ったりなんです。まあ、確かに情報は多い。ただし、記述がGUI寄りなんですよね。分かりますかね、この表現。つまり、GUIで作成されたコードにCLIコードを埋め込みましょう、的な展開になってる。今ここまで読んでくれた方々だった「ああ、なるほどな」って合点が行くかもしれませんが、要するにGUIとCLIを「分離する為のシステム」なのに「埋め込みが生じてる」コード紹介になってて、これはこれでおかしいんですよ(笑)。つまり、恐らくOSS系のGUIアプリのモデルとしてはそんなに規範的ではない、と考えられるのです。つまり、誰も(OSSで恐らく想定している)正しいGUIアプリの書き方を知らない。


いや、考えてみれば酷い状況でしょ(笑)?そうなんですよ。LinuxなんかのGUIアプリのモデルってのは恐らく基本的な設計思想とは別な方向にひん曲がって行ってる気がします。それもこれも提供されているGUI Builderの「やり方」があまりにもお粗末だ、ってのが絶対一要因に違いない。メチャクチャですね。少なくとも完全なRAD(Rapid Application Development)が提供されてたら、もっとLinuxのGUIアプリはWindowsに比べて良く整理されていて(つまりメインテナンスがラクで)、もっと発展してたかもしれません。基本的な設計思想に逆らうからこう言うハメになってるんじゃないかな、って思います。RADなGUI Builderはむしろ思想を実現する肝なんですよ。


とまあ、自分なりの現状分析、っつーか、要するに愚痴なんですけど(笑)。まあ、少なくとも現状に於いてはGUIのコードを書くにもCLIでのコマンドの知識が必要だ、って事で。取り敢えずは取っ付きやすそうなwxPythonを調べていたわけです。


ってなワケで、またもや極私的な備忘録。書いとかないと忘れちゃいそうですからね。上で「GUIとCLI部分の分離が本来のスタイル」って言いながらそこまでまだ出来てないんですが、それは今後の課題と言う事で。取り敢えずGUIアプリをwxPythonを使って「一本仕上げる」事にします。はい。


お題は単純な単語当てゲームです。ハングマンと呼ばれるヤツですね。このコードには元ネタがあって、ピアソンエデュケーションから出版されているPythonで学ぶプログラム作法を元にしています。原著はWebサイトLerning to Programなんですが、本と違って改訂されて来て、現時点ではWeb上で読めるハングマンのコードはありません。残念ですが。


元々書籍の方ではPythonとTcl/tkのインターフェースである組み込みのTkinterモジュールを使ってGUIで書かれていました。それをwxPythonを使って書きなおしてみれば、wxPythonに慣れるだろう、と言うのが目論見です。また、この本では不完全ながらもGUIとCLIのコードを分けていて、言い換えるとやり方によっては基本ロジック(つまりCLI側のコード)を変更する事無く、GUIのフロントエンドを取り替える事が可能だ、と言う事を示唆しています。


でははじめますか。


あ、その前に材料用意しないといけませんね。必要な材料は次の二つです。



  1. 英語の辞書データ

  2. ハングマンの画像7枚(それぞれhm0、hm1、hm2・・・と名付ける。画像形式はお好きなように。サイズは250 x 180〜190辺りが適切。)


辞書データはいいでしょう。英単語当てなんで、まずは英単語辞書が無いとお話になりません。これを別ファイルに用意しておいて、そこからランダムに問題となる単語を選び出すスタイルとします。ご自分で作成してもいいですし、Web上探せばテキトーに使えるモノが引っかかるかもしれません。あるいはPCに詳しい人なら、例えばワープロの辞書なんかから情報抜き出したりも可能でしょう。それで取り敢えず作業フォルダ内に「hangman.words」とでも名づけて保存しておく。


2番の方が厄介ですね。ハングマンと言うゲームは(少なくともここでは)プレイヤーは6回まで間違える事が出来ます。ハングマンと言うのは「絞首刑に合った男」と言う、まあ、如何にもアメリカ的なちょっと笑えないユーモアがあるゲームなんですが(笑)、要するに、単語に何のアルファベットが使われているのか。その予想が当たってたらいいんですけど、予想が外れていた場合、少しづつ「絞首刑に合った男」の画像が明らかになっていきます(通常は単純な線画で、要するに線が描き加えられて行く)。6回間違えると「絞首刑に合った男」の線画が完成し、そしてそこでゲームオーバーとなるわけです。



まあ、そういう絵を用意しとかなきゃなりません。ペインタやGimpでご自分で作成しても良いでしょう。僕は画像作成がメンド臭かったんで、もっとアクロバットなやり方で画像を用意しました(笑)。


ところで、Pythonで学ぶプログラム作法の著者であるAlan Gauld氏は割にオブジェクト指向大好きっ子みたいで(笑)、書籍は23章から成り立ってるんですが、オブジェクト指向が登場する第17章以降、これでもか、と言う程オブジェクト指向コードが蔓延していきます(笑)。そして、その抽象化の方法論に対して一家言持っている模様です。曰く、


このプログラムは、最初からオブジェクトを使って構築することにする。作成するプログラムに必要なクラスの計画を立てるときには、まず、プログラムが実際に行う処理について可能な限り抽象的に考えなければならない。今作成しようとしているのは、コンピュータが生成した何らかの「正解」(target)を当てることを目的として、いくつかの推測(guess)が提示される「ゲーム」(game)である。

まあなんともはや(笑)。ハングマン「如き」のゲームで抽象化、ってのもアレなんですけどね(笑)。まあ、ここではそう言う方針なんで、しょーがないでしょう(笑)。要するに問題の単語を出して、入力を受け取って、判定する、と言う「抽象機構」をここでは最初に作っています。そしてそれがフレームワークだと力説します(笑)。まあ、そうですね、フレームワークってばフレームワークでしょう。極めて限定的で小さい抽象クラスのカタマリですが、プログラミング初心者にOOPのパワーを見せるにはいい方針かもしれません。そうか(笑)?


まあ、ここではちょっと端折りたいので、取り敢えずコードを紹介しましょう。



これをgame.pyとします。


さあて、これ解説必要ですかね(笑)。まあ、単純ってば単純なんですが単純過ぎて・・・(笑)。抽象化に成功してる、って事なんでしょうけれども(笑)。returnばっかやん、とも見える(笑)。あんま単純過ぎても動作が全く想像出来ないっつーか・・・(笑)。


余談なんですが、こないだTwitterで@kaorin_linux氏と面白い話してたんですよ。曰く「ガンダムをOOPでプログラムする場合どうすっか?」みたいな議題でね。果たしてガンダムをプログラムする場合、ルートのスーパークラスを作成して「人型ロボット」と言うクラスを作るべきか否か。OOPの論法から言うと「作るべき」なんですよね。理論的には。


現場主義の@kaorin_linux氏は「必要ない」と言う立場でした。その代わり、Zガンダムとか∀ガンダムとか作る場合は、ガンダムと言う性質を継承して作ればいいじゃないか、と。その通りですよね。現場主義の@kaorin_linux氏的な意見です。


一方、OOPの理論ってのは突き詰めていくとある種「机上の空論」でもあるんですよ。では本当に「人型ロボット」がルートたるべきか、っつーと分かんない。別にロボットに限定する必要もなくって、その上に「人型の何か」と言うもっと抽象的なスーパークラスも考えうる。そうしたら「人型宇宙人」とか「人型へげもげ」とか色んなブツが継承出来る。そして本当は「人型」に限定する必要があるのか、とか。もっと上に「何か」があっても良いのでは・・・・・・突き詰めるとキリがありません(笑)。


意外と、OOPのルートクラス設計的な思想ってのは、それこそJavaのように「豊富なライブラリを提供する前提の」言語とその言語設計者に一番有利なモノかもしれません。彼らがその「言語内のあらゆる事を」決められる立場ですから。Common LispのOOPでもTと言うクラスを頂点にヒエラルキーが構成されています。「見事だ!」なんじゃなくって、最初からそうデザイン出来る人はそう決められる、と言う「だけの」話だと思います。僕ら一般ユーザーはそんなヒエラルキーを考えろ、っつっても、所詮他人の褌で相撲を取るようなもので、初めから限界があるような気がします。


と言うわけで、上のコードも「所与のライブラリ」と考えて単純に使った方が良いのかもしれません。


と言うわけでgame.pyのコード解読は取り敢えずメンド臭いので止めて、CLI版のハングマンのコードを見てみましょうか。これがGUIでのハングマンの「本体」、hangman.pyです。



これどうなんでしょうね〜(笑)。結局Gameクラスとか継承しててもかなりの部分がオーバーライドされてるように見えませんか(笑)?って事は実際は、殆どスクラッチから書き始めるのと変わらないような気がするんですが(笑)。


まあ、こう言うの見ると@kaorin_linux氏が言ってる事の方が現実的には正しいような気がしますね。作らなきゃいけない部分から取り敢えず手をつけてみると。もちろんライブラリ的なモノを作る必要性も出てくる事もあるでしょうけど、少なくとも個人的にはこの例はあまり上手く無い例のような気がします(だから本家サイトからネタが消えたのか・笑)。


さて、本から丸写ししてコメント記述してるんで、これも特に解説は要らないでしょう(こればっか・笑)。ただ、これをPythonインタプリタで走らせてみて、全く画像が無いけど、単語当てゲームとしては機能しているのを確認してください。つまり、ゲームとしてのロジックはほぼ実装し終わっている、と言う事です。これがCLIでのハングマンですが、このファイルに直接手を入れずに次の段階でGUIのゲームへと変身させます。


多重継承は悪か?


さて、いよいよ本題です。CLIのプログラムにGUIフロントエンドを引っ付ける。ここではPythonで学ぶプログラム作法の流儀に従って、GUIフロントエンドは中核機構となるwx.FrameとCLIゲームであるhangmanモジュール内のHangmanクラスを多重継承します。


多重継承って結構嫌われていますよね。当然で、平たく言うと、ヘタな継承の仕方によっては有向循環グラフになる危険がある。グルグルグルグルグールグル、と言うアレです。孫クラスのつもりだったのに曽祖父クラスになってしまっていた。自分で自分を継承してたりして・・・まあ、怖い話ですわな。だからインターフェースだMix-inだ、と言う代替手段が提供されてきたのでしょう。


一方、Pythonは「プログラマの良心に任せて」多重継承機能を提供しています。そしてそれが意味する事は「使いすぎは良くない」でも「使いどころさえ間違えなければ」超強力だ、と言う事です。


そして、CLIプログラムをGUIと統合する「その瞬間」こそが、その使いどきでしょう。全く異質(に見える)のアイディアであるCLIとGUIを自然に貼り付ける「接着剤」の役目としては多重継承がまさしく適していると思われます。


wxPythonの直接手書きは諦める(爆)


ところで、最初は、手書きでwxPythonのコードをゴリゴリゴリゴリ書いてくつもりだったんですよ(笑)。せっかく勉強したんだし。ところが、途中でGive Upしました(爆)。いくつか理由があります。


まずはTcl/tkバインダのTkinterのコードって手書きでも相当短く出来るんですよ。僕はTcl/tk自体は全然分からないんですけど、どうもシンプルな記述で意図的な配置に出来るらしい。正確に言うと、wxWidgetsに比べるともっとバッチ処理に近く、基本的にコードを並べた順に順序良くウィジェットを配置していけるらしい。もちろん、wxWidgetsも基本的にはそう言う部分があるんですが、配置に関して言うと強力なようで同時に弱点も内包してる気がします。平たく言うと配置に使うSizerですね(笑)。こいつは配置をラクにするように設計されてる筈なんですが、全体の画面構成を綺麗にする為にSizerを複数重ねて配置していく必要が出てくるんです。wxGladeなんかのGUI Buiderならこれはラクなんですが、マジでCLIで書くとアタマが痛くなっちゃうんです(笑)。CLI泣かせの機能ですね(笑)。


第二に習慣の問題があります。これはTkinterで書かれたGUIを如何にwxPythonに移植するか、と言う挑戦でもあるんですが、習慣的にTkinterはimport時に



ってやっちゃうんですよ(笑)。これは実は現在のPythonでのライブラリインポートでは薦められた方法じゃない。要するにライブラリ名を(ドット表記での)接頭語にしないようにする「オマジナイ」なんですが、今の流儀では殆ど



と言うようにして、明示的にライブラリ名を接頭語として用い、「ライブラリ同士で名前がぶつかったりしないように」してると思います。ただ、Tkinterの場合は歴史が古いせいか、こう言う書き方が跋扈してるんですね(笑)。


まあ、少なくとも(何故か)Pythonで学ぶプログラム作法では前者の書き方が用いられています。これは移植の時に困るんですよ(笑)。


何が困るか。要するに「見慣れないメソッドが」登場したとき、接頭語が無いお陰で果たしてTkinter内のメソッドなのかどうなのか確認が取りづらい、と言う事です。そうなるとそれを調べながらCLIでコマンド書いてく、ってのは至難の業なんですよね。少なくとも時間がかかりすぎる。


つまり、先にwxGladeとか使って画面が立ち上がるようにしてて、生成されたPythonコードと比較しつつ、対応取って行って調べた方が早いんです。そう言う理由もあります。


最後に、「何がしたいか」で調べるのが難しい。要するに逆引きですよね。wxPythonも例に漏れませんが、ある「既知の」クラスやメソッドに付いては、ある程度調べる事が可能です。逆に「これってどーやるの?」ってなった時、CLIで書いてる場合「アタリを付けて」調べるのが難しい。だって名前をそもそも知らないんですから。


例えば、今回の準備段階として、wxPythonのチュートリアルを2つ程こなしました。ただ両者とも「画像表示に対しては全く触れていない」。つまり、どう言うクラスのどう言うメソッドを使えば良いのかサッパリなんです。今回使用したのはwx.StaticBitmapと言うクラスですが、こんな名前かどうか思いつくのは至難の業です。結局、wxGladeでコードを吐き出させてクラス名を調べてからリファレンスをあたった方が効率的だったんです。


そんなわけで、一旦wxGladeで思ったデザインを作って、そいつをPythonで吐かせて、そのコードを調べながらGUIを作り上げていく、と言う方針を取りました。っつーか取らざるを得なかったのです。


画面イメージ


ボタン配置等の画面イメージは以下のようなカンジです。



GUI版ハングマンのコード


まずは作成したGUI版ハングマンのコードから。



結構wxGladeが吐き出したコードに手を入れています。本当はダメみたいなんですがね(笑)。同時に吐き出すxmlが拗ねちゃう模様で(笑)。


まず、この部分は簡単ですね。



これはお察しの通り、GUI版ハングマンのボタンの配置と対応しています。こいつをクリックして、推測した使われてる文字をGUI版ハングマンに伝えます。ただ、これはボタンそのものではなく、一種テンプレートです。二重のリストになっています。


次はGuessクラスを継承したhmGUIGuessクラスです。



ここも特に解説は要らないでしょう。


次からGUI版ハングマンの心臓部、hmGUIクラスです。こいつがwx.Frameとhangman.Handgmanを多重継承します。まずはここから。



まずは初期化メソッド__init__。ここで分かる通り、二つのクラスを継承しているので両クラスに基づいて初期化を行ってるのが第一点。


imgpathってのは本体フォルダ内の画像フォルダの位置ですね。こいつを使って、相対パスから計算して初期画像を表示する準備をします。ちなみに画像はhm6(この場合は.jpg)から辿ってhm5->hm4->...->hm0と進んで行きます。初期画像は6番ですね。その方が入力指定したアルファベットが間違えた場合の「残りの回答出来るチャンスの数」と対応させやすい。


lettersってのは実は上で見たアルファベットのボタン本体の事です。辞書型を使ってますが、初期状態だとボタンもヘッタクレもないです(笑)。こいつに後でボタンを作成した状態をぶち込んで行きます(笑)。何故辞書型なのか、はその時に。


そして、タイトルバー表示等の調整をする__set_properties()を呼び出し、displayStart()メソッドを呼び出して初期化は終了です。


__set_properties()はwxGladeによって作成されたモノです。手書きでコード書く場合はあんま必要ないです。displayStart()はサイザーが乗ったメソッドです。こっちに関しては後述します。


次は表示に関して扱うdisplay()メソッドです。こいつはCLIのdisplay()をオーバーライドした状態になってます。っつーか事実上殆ど書き換えられてますね。


実はこの辺がOOPの無駄な部分なような気がするのだが・・・。「ちょっとだけ変更する」なら有効だろうが「大幅に変更する」のならあまり意味があるとも思えない。結局コード記述量が増えるわ無駄になるわ、と言う気がしてる。


「表示用メソッド」なんで表示に関する動作が詰め込まれています。まず冒頭で、wx.StaticText(一般にラベルと言われる部分)に何を表示させるか、それが記述されていますね。これはこのメソッドの後半でif〜else文で切り替えられるネタ元です。この辺は難しくないでしょう。


まずは第一のポイントですが、



ここ。このlettersってのはさっき初期化したlettersと同じものです。ただし、このdisplay()メソッド内では「既にボタンとして成り立っている」前提になっています(笑)。あら、いつの間に(笑)。まだボタン化するコード書いてないんですがね(笑)。まあ、そう言う前提として見てください。


ここのポイントはですね。ハングマン、と言うゲームに於いては、もう一度繰り返しますが、推測したアルファベットを与えるわけですよ。んで、それが当たったにせよ、外れたにせよ、その文字は「二度と入力しない」んです。例えば'E'と示してみて、それが正しかったにせよ間違ってたにせよ、二度目の'E'の入力はあり得ない。


と言う事は。ゲームデザインとしては、「同じ文字の二度目の入力があり得ない」って事は、単純に考えると「一度押されたボタンは二度と押せないようにする」のがセオリーですよね。つまり、ここでのlettersってのは実はwx.Buttonと言うクラスのインスタンスになるわけで、Disable()と言うのは「ボタンを押せないようにする」メソッドなんです。これがまた、wxPythonの公式ページにも記述が無い程の(笑)便利機能なんですが(笑)。どないなっとんねん(笑)。


もう一つのポイントは次の二行です。



thefileと言うのは画像(hm*)ですね。ここでoutcome(ゲームの残り回答数)と適合したパスをpythonの相対パスを利用しつつ生成してます。ここはもう良いでしょう。あと5回答えられるならhm5が選ばれるし、あと1回しかなかったらhm1が選ばれる。


次はtheImgです。これも__init__で画像の初期化により初期パスは指定してるんですが、一方画像オブジェクトはいまだ作られてません(笑)。だってコード書いてないもの(笑)。それでもいきなり登場して良いのがOOPです(笑)。self付いた変数ってクラス内大域変数だからあっちゃこっちゃ飛んでてヤダ、ってぇの(笑)。これだから困るんだってばよ(笑)。


与太はさておき、こいつはwx.StaticBitmap()というクラスのインスタンスです。要するにこの時点で(まだコード書いてないけど!)画像オブジェクトだ、と言う事です。


ちょっと余談めいた注釈になりますが、例えばこのGUIハングマンで用いるクラスではwx.StaticBitmap()とかwx.StaticText()とか、良くStatic、と言う形容詞が付いています。他の人がどーだか知らないんですが、Staticって聞くと、僕なんかは「静的な」「変更出来ない」「固定された」って言うような意味に捉えちゃうんですよね。つまり一回表示されたら変更不可みたいな。


だからコンピュータ用語って困るんですよ(笑)。簡単にStaticだ、とかDynamicだ、とか言いますが、実際問題コンテクストによって変わる。そしてコンテクストを決定するのは形容詞の筈なんで、自ずとから矛盾してますよね(笑)。何じゃそりゃ、と。


ですから、僕みたいに直感的に誤解しちゃう人の為に一言。ここで言うStaticとは「ユーザー入力を受け付けない」って事です。つまりプログラム内部から変更する分には全くOKって意味なんです。ああ、紛らわしい。


今、ゲームの進み具合(つまり、あと答えられるのは何回なのか)によって表示すべき画像が変わります。画像オブジェクト、平たく言うとインスタンス化したwx.StaticBitmap()の画像パスを変更するメソッドがSetBitmapなんです。


また良く知らないんですが(笑)、多分いわゆるjpgとかpngとかはそのままじゃビットマップ画像として認識されないのでしょうか。多分変換が必要で、その為の変換クラスがwx.Bitmapなのでしょう。ホント、よう知らんけど(笑)。


wx.Bitmapの第一引数はファイル、第二引数は第一引数で与えられたファイルタイプです。つまり、例えばjpgなら本当はwx.BITMAP_TYPE_JPEGを与えるべきなんでしょうが、メンド臭いんで(笑)、何でもオーケーのwx.BITMAP_TYPE_ANYが与えられているのです。



ここは小粒ですね〜。getTarget()はまあいいですよね。Quit()は、終了ボタンが押されたら反応するメソッドでウィンドウを閉じます。要するにゲームを終了させるメソッドです。


reset()もリセットボタンを押した時にゲームを初期状態に更新するメソッドです。ただ若干複雑なんでコメントが付いててなお重要なところを。まずは



これは先ほどのdisplay()で見た、「ボタンを使用不可にする」事と逆の事を行っています。ゲームに再度挑戦する際に、ボタンが使えないとどうにもこうにもブルドッグ、だからです(謎)。相変わらずlettersはアルファベットのボタンを表しています。つまり、wx.Buttonのインスタンスに於いて、メソッドEnable()とDisable()は真逆の関係だと言う事です。


次はここですね。



一行目はさっきやったのと殆ど同じです。画像を書き換えてる(っつーかより正確に言うと参照すべきパスを変えている)。ここでは__init__()で定義されているself.firstImgを「大域変数よろしく」引っ張ってきています。


二行目は表示されるべきモノですね。実際、self.getResult()で文字情報が入ってる筈なんですが、表示されるのはそれに対応したアンダーバーだ、と言う事になります。


そして三行目。このstatusも実はインスタンスです。まだ定義されてません。クラスはwx.StaticText。これもSetLabel()と言うメソッドで表示を変えます。wx.StaticBitmapのSetBitmap()メソッドの文字版ですね。


__set_properties()はいいでしょう。wxGladeが自動で作ったメソッドですし、ここではWindow上部のタイトルバーにアプリケーション名を表示しているだけ、ですから。


さて、いよいよ最後のメソッド、displayStart()です。



まあ、大体ここまで読んできてくれた人だったら何やってるかおおまかなトコは掴めるでしょう。theImgと言う画像オブジェクトもここで一行目で設定されていますし、同じくラベルオブジェクトであるstatusもここで作成されています。


が、やっぱ見て分かると思いますが、メンド臭いのがSizerですね。全部で5種類程使っています。これ、自分でコード直接書いてたらこんがらがりますよ(爆)。よってSizerに関して言うと、あんま細かい事いいません。ただ、最後のsizer_*.Addの第二引数でSizerの比率を変えたりしています。デフォルトでは一対一に分割、って状態になってますが、それを実際画面見ながらパラメータ微調整する、ってカンジですか。この辺はプログラミングじゃあないですよね、正直なトコ。


そしてsizer_*.Addの第一引数にはそのSizerの上に「何が乗ってるか」を表しています。ボタンが乗ってる場合もあるし、Sizerの上にSizerが乗ってる場合もある。はあ、画面デザインって大変です(爆)。


さて、ここで一番重要な部分は次の部分です。ここで'A'〜'Z'のボタンを生成しています。



実はここがこのプログラム最大の山場で、かつボタンとシグナルを結びつける全テクニックが突っ込まれています。また、プログラミングに対する大変面白い考え方が伺えます。もっとも僕が考えたんじゃないんですけどね(笑)。


実はwxGladeに'A'〜'Z'のボタン生成のコードを吐かせるのも当然可能なんですけれども、それやっちゃうと27個のボタンがソースコード上並びまくる、と言う大変な状態に陥ります。かつ、Bind(あとで後述)がメンド臭くなる。しかもそれをSizer上に置くトコまで指定せねばならない。上のコードはそれをたった9行で行っちゃうスグレモノです。およそコード量が1/3、いや1/6、いや1/9に減った、と言えるでしょう。


まずは冒頭のkeysを使ってrowを、そしてrowの中からchを取り出す二重ループ構造になっていますね。そしてchが空の文字列だった場合、



が実行される。ここで代入されてるgrid_sizer.Addですけど、これはwxGlade上ではSpacerと呼ばれています。隙間を埋める部品、って事なんですけど、実はコード上ではそう言った名前は使われていません。いずれにせよ、""に対応する場所には何も置くな、と言う指示になっているのです(あるいは、30 x 30の小さな「何か」を置け、って事でしょうか)。


では空文字列じゃなかった場合です。まあ、取り敢えず最初のactionは置いておきますか。その次のコードでAlan Gauld氏の手腕が光る。珠玉のコードがたった一行で示されます。



正直な話、最初これ見た時何やってるんだかサッパリだったんですよ。しかし考えてみると非常に良く出来てる。これはこう言う事なんです。最初に__init__でlettersを辞書型にしておいたのがここで効いてくる。


つまりchがlettersと言う辞書型のキーになるんですね。そしてその「値」この場合オブジェクトwxButton()ってのがここで結び付けられるわけです。要するにハッシュであるlettersに適切なキーを渡すと必ず返り値でユニークなボタンオブジェクトが返って来る。ね?メチャクチャクールな方法でしょう(笑)。こりゃすげえや、って思いました。


しかし、ボタンを生成するだけじゃダメです。イベントとして動作と結び付けないといけません。ここで結びつける対象は今後回しにしたactionなんですが、これらを結びつけるメソッドがBindと言います。そして第一引数にはボタン用のイベントだよ、と知らせるwx.EVT_BUTTON、第二引数には結びつける対象の関数(この場合はaction)、そして第三引数にはそのボタン自体が入ります。



これはボタン用のBindの書式です。ですが、例えばプルダウンメニューなんかでも基本変わりません。第一引数がwx.EVT_MENUになったりするだけです(色々あるんで検索するのが大変でしょうが・笑)。第三引数もいいですね。ハッシュ(辞書型)にキーchを与えて返って来るwx.Buttonインスタンスとactionが結びつく。そしてそのactionは最初にラムダ式として定義されてるのです。



この場合、ラムダ式は第一引数にイベントを受け取り、第二引数にchを受け取り、第三引数がself、と言う、ちょっとPythonとしては珍しい記述に落ち着いています(っつーか引数の順番として、はね)。そして、先ほど定義した表示用メソッドdisplay()にchを渡す、と言う作業が閉じ込められています。これで各アルファベットボタン毎に何が表示されるべきか、完全に定義されるわけです。かっこいー!!!!凄いアイディアですね。


そして、最後にボタンをgrid_sizer上に行儀良く並べていけば良いわけです。



たった9行で27個のボタン生成、27個のボタンとメソッドを結びつけ、配置までやってのける、ってなあ凄すぎます。でも良く考えてみると、これって凄いだけじゃあないんです。実はあるアイディアが背後に見え隠れしています。それは一体何でしょう?


Lisp的考え方


僕も最初にお題を見た時、「27個もボタンしこしこ書きたくねえな。何とか自動生成できねえかな」って思いました。ところが問題なのは、代入するクラスの方じゃなくって、代入される変数名の方だったんです。こいつらリテラル、と呼ばれるブツは通常自動生成は出来ません。たった一つの言語を除いて。


そうですね。Lispです。Lisperだったらこう言うお題を見た場合、即刻考えるのはまずは「変数名の自動生成」でしょう。そして恐らくマクロにしちゃいます。ところがこれがPythonじゃ出来ない。こりゃ困ったな、って思いました。


しかしこれをAlan Gauld氏は「辞書型」と言うデータ型を用いて華麗に切り抜けました。確かに凄い見事なんです。ただ、この手法は実はLisp的発想なのかもな、と気づいた。Alan Gauld氏が狙ったのは、実はPython上でのシンボル型のエミュレーションだったのでは、と。それを辞書型用いて実装してみた、ってのが本当のところではないか、と。


まあ、この辺は余談なんで、Lisp知らない人にはあんま関係ないです。がLispってのは全般的に(Java的な意味ではなく)データ型で組み立てられていて、いわゆるフツーの言語で言うリテラルさえ持ってません。「地の文」を構成する「何か」が無いわけです。フツーの言語的感覚で言うとね。その代わり、式を組みたてる「字」っつーのかな、それらは特に「シンボル型」と呼びます。れっきとしたデータ型なんです。


さて、Lispではインタプリタに何か入力すると、そのシンボルは即刻シンボルテーブルと言う内部の一種データベースに登録されるわけです。これをシンボルをインターンするとかLisperは言うわけなんですけど、実はこのPythonコードのこの部分は、この「シンボルのインターン」を明示的な方法を持ってPythonで実装したんじゃないのかな、と思えるわけです。すんごくシステム的には良く似てるんですよ。


多分PythonでLispを実装する、的なネタですと、このシンボルのインターンのシステムと言うのは、Lisp-1やLisp-2に限らず、恐らく辞書型で実装するのが一番効率が良いです。ハッシュは速いですし、シンボルとしてのキーを検索するには向いている。また、キーをメソッドっつーか関数としての「値」に結び付ける発想も至極良く似ています。このシステムは、かなりSchemeなんかに近いんですね。このコードの意味が分かった時、正直「一本取られた!」って思いました。非常にLisp的な実装ですし、明らかにLispの一部(っつーか考え方)を導入しています。


もちろんAlan Gauld氏はLispも知っている。初心者プログラマがPythonを終えた後に学ぶ言語としてLispを挙げている程です。そしてこう言う風にLispのプログラミングテクニックと言うより、その「発想/構造」を持ち込めるようになる、ってのが、ひょっとしたらエリックレイモンドが言っていた「悟り」の部分なのかもしれません。


終わりに


この見事なテクニック見たあとでは何も言えませんね。基本的にボタンとイベントを結びつける基本テクニックは上のコードで見たまんまですし、あとは地道に読めば分かる範囲です。取り敢えずCLIのコードからスタートして「多重継承」を経由して、GUIのアプリケーションへ変換する事は出来ました。wxPythonを使った「アプリケーション」を一本作り終えたわけです(よね?)。


[リセット][終了]ボタンに関して言うと、例えばGTK+をそのまま使うGladeなんかはGTK+が用意してくれているボタン画像なんかを使えたんですけど、wxGladeはどうだったかな。何か見つからなかった(笑)。その辺今後の課題かもしれませんね。まあ、課題って程でもないですけど(笑)。


最後に。一応このハングマンと言うサンプルはGitHubに上げてあります。

0 件のコメント:

コメントを投稿