Minecraftとタートルと僕

PCゲームMinecraftのMOD「ComputerCraft」の情報を集めたニッチなブログです。

sleep実装からイベントを学ぶ(9)-モニタボタンでプログラムを操作する

はじめに

今回は以下のようなプログラムを紹介します。

「モニタボタンによる画面表示操作」プログラムの仕様

  • コンピュータに隣接した巨大モニタを表示専用モニタとする。
    • 表示専用モニタの中央にキャラクタ「@」を表示する。
    • また同時にモニタの一番上に、どのような操作をしたかという情報も表示する。
  • コンピュータに隣接したもうひとつの(小さな)モニタをコントロール用モニタとする。
    • コントロール用モニタの表示領域を縦2×横3に6分割し、計6個のボタンを作成する。
    • また、表示されたボタンを右クリックすることで、表示専用モニタに表示されているキャラクタ「@」を上下左右に自由に動かしたり、キャラを初期位置に戻したり(リセット)、プログラムそのものを終了したり、さまざまな操作ができる。

コンピュータとモニタの設置について

前回のようにコンピュータの上"top"と横"right"にモニタを設置しても良いのですが、
以下の図のようにコンピュータとモニタを重ねてもかまいません。
表示用モニタが大きいと、ボタン操作中に表示用モニタがゲーム画面からはみ出しがちですし。
この図では、コントロールモニタが上"top"、表示用モニタが後ろ"back"です。プログラムのconfigを書き換えてください。

f:id:hevohevo:20140106145336p:plain:w500

プログラム


プログラムの解説

総行数140!! これまでの最長キタコレ。

かなり長いので、main部分の解説をしてこのプログラムの全体像をつかんだ上で、主要なポイントである以下の3点について集中的に解説します。

  1. ボタンを作成する部分について
  2. 自家製イベントである"push_button"イベントについて
  3. ボタン操作による画面表示について

main部分の解説(このプログラムの概略)

L100-140の部分でとても長く見えますが、やっていることはたいしたことはありません。

  • L100-103: 表示用モニタを初期化しています。
    • main_mon.setTextScale(1)は、文字の大きさ、つまりはモニタの表示ドット数を設定しています。0.5単位で増減できますが、1は標準の大きさです。
  • L105-109: 表示用モニタにキャラクタ「@」を表示させています。
    • 初期位置はモニタの中央です。キャラの現在位置は変数xとyに記憶させます。
  • L111-114: コントローラモニタの初期化をしています。
    • ひとつのモニタで細かな文字を表示させるために、そして表示ドット数を増やすためにctrl_mon.setTextScale(0.5)を指定しています。これがもっとも細かな文字です。
  • L116-118: コントローラモニタのサイズを元に、画面を6分割してボタンを作成しています。
    • 作成した6つのボタンは変数「buttons」の中に入れています。各ボタンにはいくつかのパラメータがありますが、重要なのは、名前「name」、一番左上の座標「min_x」「min_y」、一番右下の座標「max_x」「max_y」です。
  • L120-140: メインのループ部分で、自家製イベントである"push_button"イベントを拾い、押されたボタンによってキャラクタを動かしています。
    • 終了条件は、「quit」ボタンが押されたとき、です。
    • 僕の環境ではボタンクリック時の反応が良すぎるため、ウエイトとして、末尾にsleep(0)を入れています。

ポイント1「ボタンの作成について」

このプログラムの一番面倒な部分です。ここのデバッグに一番時間がかかりました。

  • L36: 関数「makeSixButtons()」で、コントローラモニタの表示領域を6分割して、ボタンを6個作成しています。
    • 「mon.setTextScale(0.5)」で表示ドット数を限界まで増やしていますが、それでもひとつのモニタでは、(15,10)が限界です。その結果、各ボタンは3x5サイズの長方形となっています。
    • たとえばボタン1(btns[1]、ボタン名はreset)の左上の座標は(1,1)、右下の座標は(3,5)です。
  • L57: 関数「drowButtons()」によりボタン名を画面に表示しています。本当は色分けして表示するとわかりやすいのですが、プログラムが煩雑になるので手抜きです。

ポイント2「"push_button"イベントについて」

このプログラムのもっとも重要な部分です。この部分さえ理解できれば、このプログラムの読解は半分以上完了したといえるくらい重要です。

  • L86: os.pullEvent()をwrapして、pullPushButtonEvent()関数を定義しています。
    • os.pullEvent()で拾ってくる"monitor_touch"イベント(とクリック座標)を、作成した6つのボタン領域と比較することで、どのボタンを押したかという"push_button"イベントを返すようでっちあげています。
    • pullPushButtonEvent()の返し値は、第1返値がイベント名"push_button"、第2返値が押されたボタン(正確にはボタンTable)です。
    • このように、既存のイベントをwrapして、よりメタな(抽象度の高い)イベントを作り上げ処理しているところが、このプログラムの最重要ポイントになります。

ポイント3「ボタン操作による画面表示について」

  • L12: 関数「writeEventInfo()」は、どのボタンが押されたのかという情報を表示用モニタの1行目に表示します。
  • L18: 関数「writePosInfo()」は、キャラクタ「@」がどの位置に動いたのかという座標情報を表示用モニタの2行目に表示します。
  • L25: 関数「moveSymbol(mon, start_x, start_y, goal_x, goal_y)」は、キャラクタ移動に関して、もっとも重要な関数です。
    • この関数の引数として「現在座標」「目標座標」をとり、(1)現在座標にある「@」を半角スペースで上書きして消去、(2)目標座標に「@」を書き込み、という流れでキャラクタを移動しているかのように見せかけています。

その他、このプログラムを読み解く上でのポイント

  • L7-9: グローバル変数は、設定項目(config)でしか使っていません。他はすべてローカル変数です。
  • このプログラムにおける関数設計の方針
    • 関数内で参照する変数は全て、その関数の引数として明示的に与えるよう定義しています。関数の外で定義した変数を漫然と関数内部で使うようなことはしていません。
  • 関数内でローカル関数を定義することで、見通しの良いプログラムになるよう心がけています
    • L69: 関数「whichButton()」内でローカル関数「within()」を定義しています。
  • 【補足】テーブル(Table)の値を参照する方法について
    • x={name="hevohevo"}というテーブルがあるとき、nameの値を参照するには以下のどちらの方法を使ってもかまいません。
      • x["name"]
      • x.name
  • math.floor(実数)は、引数として与えた実数の整数部分だけを取り出す関数。つまりは小数部分切捨て関数。このサイトではまだ出ていなかったよね? たぶん。

次回のお話

今回のプログラム自体は、実用性皆無です。
しかし、内部的には、今後のプログラミングのヒントになるようないろいろなTIPSが含まれています。
たとえば、

  • 一つのモニタ上に、移動するキャラクタとイベント情報という異なる種類の情報まとめて表示することができる
  • ひとつのプログラムで複数のモニタを同時に操作できるので、用途別にモニタを使い分けることもできる
  • モニタは情報出力だけでなく、入力装置、コントローラとしても使える

などなど。

このプログラムを部分的に改造することで、面白いプログラムを作ることができるはずです(他力本願)。

次回は、タートルをボタンで操作するプログラムを紹介しましょう。
こちらは今回のプログラムよりも簡単で短くなります。
・・・もしかしたら紹介する順番を間違えたかも? まぁ、最初に要素山盛りなプログラムを紹介してドン引きさせておいて、その後にその一部を使った簡単なプログラムを紹介すると受け入れやすくなるよね。きっと。

それではお楽しみに。