読者です 読者をやめる 読者になる 読者になる

Minecraftとタートルと僕

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

ComputerCraftの無線通信を使いこなそう(7) ―遠隔操作でプログラムや関数を呼び出そう

はじめに

今回の記事は、前回の記事に対する「QuartzMiner_D」氏からのコメントをヒントに構成しています。百万の感謝を。

さて今回の目標は、タートル側に存在する「任意のプログラム」「任意の関数」を遠隔操作して呼び出して実行しよう、です。

任意のプログラムを呼び出すこと

前回の記事では、"os.run({},'/rom/programs/turtle/dance')" という文字列をタートル側に送信することで、タートル側の"dance"プログラムを呼び出しました。このプログラムによりタートルが踊りだします。

しかし、絶対パス表現(プログラムファイルの位置を完全表記)って、結構大変ですよね。できるならばもっと簡単な表記にしたいものです。

ファイルパスの解決をしてくれる関数として、shell.resolveProgram()があるのですが、これを内部的に呼び出してくれるshell.run()関数が使えると幸せになれそうです。

でも、前回のプログラムのままではshell.resolveProgram()の実行に失敗してしまうため、shell.run()は使えないのですよね……。

  • f:id:hevohevo:20140318001306p:plain

任意の関数を呼び出すこと

また現状では、タートル側のmsg_receiverプログラム内で定義したサブ関数を、遠隔操作で呼び出することができません。

たとえば以下は、前回のmsg_receiverプログラムの一部を抜粋し、少しだけ追記したものです。 このプログラム内で関数hevo()を定義していますが、この関数は外部からのメッセージで呼び出すことができないのです。

-- 以下のプログラム実行中に、"hevo()"というメッセージを受信したとき
rednet.open("right")

function hevo()
  print('hevohevo!')
end

while true do
  local sender_id, message, distance = rednet.receive() -- "hevo()"というメッセージ受信
  local func = loadstring(message) -- loadstring("hevo()")で関数コンパイル。
  local status, err = pcall(func) -- 実行失敗。エラー。
end

これらの原因とその解決

一言でいうならば、「だいたいloadstring()のせい」

loadstring()によって新しい関数funcが作成されるのですが、作成された関数funcの環境(Lua用語、環境テーブルとも呼ぶ)がグローバル環境テーブル(Luaでは変数「_G」で表記します)に固定されていることが原因です。

グローバル環境テーブル下では、ファイルパスの検索がうまくいきません。そのためshell.resolveProgram()が失敗します。

また、hevo()関数が保存されている現在の環境、新しく作成されたfunc関数が見ているグローバル環境、これらが異なっているので関数funcを実行しようとしても「hevo()ってなに?しらんよ」となってしまうのです。

ということはこれら問題を解決するためには、関数funcの環境をhevo()が保存されている現在の環境に変更してあげればよいわけです。

現在の環境を取り出す関数は、getfenv(1)(引数"1"は省略可能)です。また、ある関数に新しい環境を設定する関数はsetfenv(関数,環境テーブル)です。

よって、setfenv(func, getfenv())という一文をプログラム中に書き込むだけで問題は解決します。

なお今回は「環境」に関する説明を大幅に簡略化しましたが、また後日、新しく記事を作って詳しく解説しようと思います。

今のところは、loadstring()によって作られる関数は現在の環境とは違うところを見ているのね、程度に理解しておけば問題ないかと(なげやり)。

受信側(タートル側)プログラム バージョン4

msg_receiver4

http://pastebin.com/dEtcqCHY

変更箇所は、2箇所だけ

重要な変更点はL14に追加した1行です。

L6-8で関数hevoを定義していますが、これはテストコードなので、自分で好きな関数を定義してかまいません。

送信側プログラム バージョン4

msg_sender4

http://pastebin.com/n1LPLrij

変更箇所は、L20-22の3行だけ

"z"キーを押すと、shell.run('dance')という文字列をタートルに送信します。タートルはこれを関数として実行、インストールされているプログラムの中からdanceという名前のプログラムを探し実行します。つまり、danceプログラムを自分の中からいい感じで探してきて実行、踊れ!という命令です。

"x"キーを押すと、先ほどと同様に登録されているパスを全て検索して、boringプログラムを探し出し、実行します。

"c"キーを押すと、タートル側プログラム内で定義されている関数hevo()を呼び出し、実行します。ちなみにhevo()は、画面に"hevohevo!"という文字を表示するだけの関数です。 この関数自体に特に意味はありませんが、今後、このプログラムを拡張するときに利用すると良いでしょう。たとえば、以下のような燃料補給関数を作っておくと便利かもしれません。

function hevo()
  turtle.select(16)
  turtle.refuel()
  turtle.select(1)
end

おわりに

これで遠隔操作プログラムとしてはおおよそ問題のないものができたと思われます。

次回は、使いやすいように少しだけプログラムを微調整した完成版を紹介したいと思います。

もう少しだけ続くんじゃよ。

おたのしみに。