Minecraftとタートルと僕

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

sleep実装からイベントを学ぶ(10)-モニタボタンでタートルを操作する

はじめに

今回は、モニタボタンを使ってタートルを直接操作するプログラムを紹介します。

前回の最後に予想したとおり、前回のプログラムよりかなり短いですよ今回は。

機能的にも前回のプログラムの一部分を使っているだけなので、前回よりも理解しやすいプログラムだと思います。
ただしそれだけだとつまらないので、少しだけ高度な機能を付け加えてみました。
この機能を覚えるととても便利なのでがんばって覚えましょう。

「モニタボタンを使ったタートル操作プログラム」の仕様

f:id:hevohevo:20140108194043p:plain:h400

  • タートルとAdvanced Monitorの設置
    • タートル(タートルの種類は問わない)を設置後、そのタートルの上に金色のAdvancedMonitorを設置する
  • 真上にあるモニタはコントローラーです
    • モニタの表示領域を9分割して、計9つのボタンを表示する
    • ボタンを右クリックして押すことによって、真下のタートルを操作できる
    • ただし、タートルがモニタから離れると操作できなくなるので、移動系の操作は実装しない。そのため燃料系の操作も除外。
    • たとえば、次のような操作ができます。
      • 目の前にあるブロックを破壊してゲット、左向いて、選択スロットを16に切り替えて、スロット16のブロックを設置。とか
      • 敵対モブがやってくるのを待ち受けてタートルの正面にきたらAtkボタン連打して攻撃、あるいは日光消毒などで目の前に落ちたアイテムをSuckボタンでゲット。などなど。
  • このプログラムは、設定の自由度を意識しました
    • プログラムのConfig部分で、「ボタンの名前」とボタンを押すことで「実行するコマンド」を自由に書き換えることができる。
    • たとえば、「ボタン6はturtle.suckUp()」にしたい! という場合は、Config部分を書き換えるだけでおk。

このプログラムで学べること

  • モニタボタンの表示やボタンイベントの作り方
    • 前回説明したので、説明を省略しています。
  • 「eval(文字列)」関数の作成  ←今回の目玉機能
    • 「eval("turtle.select(1)")」のように、与えた文字列を評価(関数として実行)する関数を定義します。
    • 他のプログラム言語、特にスクリプト系(sh、ruby、perl、phpなどなど)のプログラムを組んだことある人ならば、eval関数の有用性はわかりますよね。

プログラムの解説

いつも同様、グローバル変数を押さえた上で、メイン部分を説明し、それから重要な部分に焦点を当てて説明しましょう。

グローバル変数(プログラムの設定パラメータ)

今回もプログラム使用者が自由に書き換えられるConfig部分にしかグローバル変数を使っていません。

  • L7: 「CtrlMonSide」コントローラとなるモニタをタートルのどこにおくか
  • L8-2: 計9つのボタンの設定。設定項目は、「ボタンの名前」と、そのボタンを押すことによって「実際に実行するコマンド」の2つです。

メイン部分の解説(プログラムの全体像)

前回に比べると、シンプルでしょ?

  • L93-96: コントロールモニタの初期化をしています。
  • L98-100: Configで設定したボタンパラメータとコントロールモニタを引数に取ることで、ボタン(Table)の作成と、モニタへのボタン表示を行っています。
  • 102-111: メインループです。終了条件は特に指定していません。ループ中の具体的な動作は以下のとおり。
  • L104: 自家製関数であるpullPushButtonEvent()を使うことで、"push_button" イベントを拾っています。この説明は前回行いました。
  • L105: 拾ったイベント名が "push_button" であるときに、以下を実行します。(このIFはなくても動きますがw なんとなく)
    • L106: どのボタンが押されたのかを、ターミナル画面に表示します。
    • L107: Configで設定されたボタンコマンドを実行します。eval()は自家製の関数です。

今日のポイント「評価関数 eval() 」について

押すボタンによってコマンドを実行する部分は、L107の「eval(コマンド文字列)」だけで実現しているのですよ。実質的に、Cで言うswitch構文を1行で実現しているわけですね。すばらしい(自画自賛)*1

このeval()はL27-30で定義していますが、実際にはL29だけが重要です。
もうね、慣用表現だと思って覚えちゃってください。最後につける()は忘れないように!
理屈がもっと知りたいという方のための説明は以下のとおり。

  • loadstring(文字列)によって関数そのものを作り出します。そして関数の末尾に「()」をつけることによってその関数を実行します。これについては次回、例を挙げながらもう少し詳しく説明しましょう。そもそもLuaにおける関数とは……のように、そもそも論の話をしなくてはいけないのでw
  • 「loadstring(コマンド文字列)()」と書いても動きます。しかしエラー処理のため、assert()をクッションとしてはさみこむのがお作法のようです。
  • また、assert()の代わりに「pcall」「xpcall」*2を使う方法もあります。エラー処理を厳密に行うならこちらが適しているのですが、めんどうですしおすし。
  • なおこの実装方法は、Lua5.1リファレンスにあった以下の部分をそのまま使っています。

loadstring (string [, chunkname])

  load と同じであるが、 指定した文字列からチャンクを読み込む。
  与えられた文字列をロードして実行するには、以下の慣用句を使う。
     
  assert(loadstring(s))()

プログラム


このプログラムの応用について

RedNet APIについて説明しつつ、ラジコンプログラムへと発展させるか、
あるいは、ボタンを押して実行コマンドを記憶させ、後でまとめて実行させるような、ボタンプログラミング式タートル操作プログラムへと発展させるか。

迷うわー。どちらかを作ってくれる人いないかなー?(チラッチラッ

*1:というかこの手法はスクリプト言語ではありふれた手法なんですけどね。

*2:例の、bios.luaファイル内で定義されています。