移動履歴がなぜ重要か「ゲーム中断・再開問題」
タートルプログラミングに慣れたころ、誰もが悩むのがゲーム中断に伴ってタートルのプログラムも中断してしまうという問題です。
タートルが何かの作業している途中でゲームを中断すると、Luaプログラムも止まってしまいます。
再びゲームを開始したとしてもタートルはどこまで作業したのか覚えていません。そのため、止まっているタートルを壊して回収して元の位置に手動で設置してプログラム再実行という手段をとらなくてはなりません。
たとえプログラムファイル名を「startup」にして自動スタートさせたとしても、中断したその場からプログラムを開始してしまうので、最悪の場合、全く関係のないところで暴走状態となってしまう可能性すらあります。
このようなゲーム中断問題に対処する方法はいくつかありますが、ここでは、タートルの移動履歴をファイルに残しておく方法を紹介します(以降、履歴をログ。ログを保存したファイルをログファイルと呼びます)。
ゲームを再開したときにログファイルを調べることで、どこまで作業を進めたのかプログラム的に判別できるでしょうし、またあるいはログファイルを末尾から逆にたどり反対の関数を実行することで、最初の位置に戻ることも可能です。 たとえば、「前→前→右回転→前」とログにあったら、「後ろ→左回転→後ろ→後ろ」と実行することで元の位置に戻ることができるでしょう(実際には後ろ向きに進むのは色々と問題を抱えているのでもうひとひねり必要ですが)。
最終的な目標を「ログファイルを見て自動的に最初の位置に戻るプログラム」とします。
今回はその前段階としてログファイルに移動履歴を残すプログラム、すなわち「移動ログを取るプログラム」を紹介・解説しましょう。
基本的な考え方
Turtle APIに含まれるタートル移動系の関数は、以下の6つとなります。
- 前進: turtle.forward()
- 後進: turtle.back()
- 上昇: turtle.up()
- 下降: turtle.down()
- 右回転: turtle.turnRight()
- 左回転: turtle.turnLeft
これら関数を元にして、移動を試みたらログを取るような新しい移動系の関数を作りましょう。
基本的には、一つの試行を行ったらログファイルの末尾に一行ずつその試行内容を追加していきます。
移動を試みたときに、何も問題なく移動に成功するときと何らかの問題で移動に失敗するときの二種類の結果が考えられます。
移動に成功したらログファイルにその実行した関数(たとえば前進に成功したらturtle.forward()
)を記述。
移動に失敗したらその関数をコメントアウトした形(たとえば-- turtle.forward()
)で記述しましょう。
つまり、ログファイルそれ自体をLuaプログラムとして実行できるよう記述することが今回のポイントとなります。
プログラムの重要な部分を抜粋
-- ########################### -- config LOGFILE = "mylog" -- ########################### -- functions function logging(message) local fh = fs.open(LOGFILE, "a") fh.writeLine(message) fh.close() end -- forward() function forward() local status, error_msg = turtle.forward() if status then -- succeeded logging("turtle.forward()") else -- failed logging("-- turtle.forward()") end return status, error_msg end -- (以下、同じように他の5つの関数についても定義) -- (スペースの問題で、ここでは省略しています) -- back() -- up() -- down() -- turnRight() -- turnLeft() -- ########################### -- main -- ログファイルの準備。すでに同名ファイルがあるならバックアップする。 if fs.exists(LOGFILE) then -- backup file is "XXXX.bak" local bak_file = LOGFILE..".bak" if fs.exists(bak_file) then fs.delete(bak_file) end fs.move(LOGFILE, bak_file) end -- (あとは、好きにプログラムを書く) for i=1,5 do forward() end
プログラムの大まかな解説
プログラムは大きく分けて、設定(config)部分、関数定義(functions)部分、メイン(main)部分の3つに分かれています。順番に解説しましょう。
config
ログファイルの名前を指定します。
ここでは、「mylog」というファイル名でログファイルを作ります。
functions
FS APIを使うことで、ログファイルに文字列(message)を一行書き加えるlogging(message)
という関数を定義しています。
ログファイルを追加書き込みモード("a")で開き、そのファイルハンドルを取得、 メッセージを1行書き込んだらファイルを閉じる。ただそれだけの関数です。
logging()を使うことでログを取ることができます。
一例として、ログを取る機能がついた前進する関数であるforward()
の定義を見てみましょう
(turtle.forward()
とforward()
は表記は似ていますが別の関数なので注意。ここでは、turtle.forward()
を元にしてforward()
関数を新しく作っています)。
まずturtle.forward()
の実行を試みて、その結果が成功したかどうかをローカル変数status
に代入しています。前進に成功したらtrueが、失敗したらfalseがローカル変数statusの中に入ります。
また前進失敗したときにはそのエラーメッセージ(文字列)がローカル変数error_msgに代入されます。
IF文で条件分岐させることで、前進成功ならばログファイルにturtle.forward()
と1行追加し、失敗ならばコメントアウト記号付きで-- turtle.forward()
と追加します。
今回は省略していますが、forward()
関数と同じように、残りの5つの移動系関数であるback()
、up()
、down()
、turnRight()
、turnLeft()
も定義します。
main
このプログラムを実行したときに、すでに同じ名前のファイルが存在しているならば、ファイル名の末尾に".bak"をつけてバックアップを取ります。
あとは、新しく定義した以下の6つのログ機能付き移動系関数を使ってプログラムを書きましょう。
- forward()
- back()
- up()
- down()
- turnRight()
- turnLeft()
上記のプログラムでは、5回前進するよう記述しています。
たとえばどのようなログファイル
たとえば5回前進しようとして、2歩進んだ時点で障害物に当たり、残りの3回は前進に失敗したとします。
このときログファイルは以下のように記述されています。
このログファイルは、これ自体がLuaプログラムになっていることが特徴です。
このログファイルをプログラムとして実行すると、ただ2回だけ前進します。
つまり実際に行動した内容と同じことを再現してくれるわけですね。
まとめ
今回は基本的なアイデアを説明しました。
プログラムの完全版は次回紹介します。
APIとして手軽に利用できるものをお見せする予定です。
実は、今回紹介したプログラムの末尾のfor~end部分を削ってしまえば、これはこれでAPIとして利用できるようにしてあるんですけどね:P