Minecraftとタートルと僕

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

こちらのページは更新が滞っており、情報が古くなりつつあります。新しいCC情報サイトをはじめましたので、もしよければご参照ください。今後ともよろしくお願い申し上げます。

「百億のマインクラフトと千億のタートル」(https://hevo2.hatenablog.com/)

FS API を使いこなそう(12): logging API version0.4の解説と応用

はじめに

前回は、logging API version0.4を完成させました。

主な機能としては、以下のとおり。

  • ログをファイルに出力する機能を付与したturtle移動系の関数(logging.forward()など)を追加
    • turtle.forward()のような文字列をログとして出力するため、ログファイル自体が単体で実行できるプログラムファイルとなっている
  • また、任意の関数をログ出力機能付きにできる
  • ログファイル(log file)を元にして、タートルがスタート地点に逆戻りするための関数を記述したプログラムファイル(revised file)を作り出すことができる
    • 元の位置に戻すためには、逆戻りプログラムファイルを実行すればよい。shell.run(revised_file)

logging API version 0.3以前とはかなり仕様が変わっています。ご注意を。

version0.3なんてなかった。 いいね?

logging API (version0.4)の仕様書

ソースコード: http://pastebin.com/xLwUS6Y5

重要な変数

  • LOG_FILE:
    • ログファイルの名前。デフォルトは"mylog"。version0.3にあったファイル名を切り替える関数はリストラ。直接変数を書き換えてください。
  • REV_FILE:
    • 逆戻りプログラムの名前。デフォルトは"myrev"。
  • TRANS_TBL:
    • ログファイル内の文字列→逆戻りプログラムへと変換するための変換テーブル。タートル移動系の基本的な関数はすでに書き込んであるが、独自のカスタマイズや新しい変換ルールを追加したい場合はこのテーブルを書き換えること。TRANS_TBL["変換元文字列"]="変換後文字列"

利用できる関数

  • turtle移動系の関数(タートルが移動し、移動の成功/失敗により異なるログを出力)
    • logging.forward()
    • logging.back()
    • logging.up()
    • logging.down()
    • logging.turnRight()
    • logging.turnLeft()
  • 関数の実行とは関係なく直接文字列をログファイルに書き込む
    • logging.write( message )
  • 引数として関数を与えることで、その関数にログ機能を付け加えた新しい関数を作ることができる
    • logging.createLoggedFunc( function, succeeded_log, failed_log )
    • この関数を実行すると、引数として渡した関数にログ機能をつけた新しい無名関数を返してくれる。
  • ファイルをバックアップする。具体的には、ファイル名の末尾にtail_strをくっつけたファイル名に変更する(省略すると"-bak")。
    • つまりこの関数を用意したということは、ファイルのバックアップ処理は自分でやるということ。
    • logging.backupFile( filename, tail_str)
  • ログファイルを元に、逆戻りプログラムファイルを作る。引数を省略するとLOG_FILE、REV_FILEの値を使う。
    • logging.makeRevFile(log_filename, rev_filename)
  • なお、逆戻りプログラムファイルを自分のプログラムから実行するには、以下の記述を使うと良い。
    • shell.run("myrev") (逆戻りファイルの名前をもし変えたのならその名前で実行すること)

実行例

os.loadAPI("logging")

-- 以前のファイルが残っていたら念のためバックアップを取る
logging.backupFile(logging.LOG_FILE)
logging.backupFile(logging.REV_FILE)

-- 移動
for i=1, 20 do
  logging.forward()
end
logging.turnRight()
for i=1, 20 do
  logging.forward()
end

-- 逆戻りプログラムファイルを作って
local rev_file = logging.makeRevFile()

-- 実行して、元の位置に戻る
shell.run(rev_file)

応用と問題点

さて、ここまでできるならば、

タートルが作業中/移動中にゲームを強制中断したとしても、ゲームを再開したらスタート位置に戻ってくる。

そんなプログラムが書けそうですよね? 次のプログラムを"startup"というファイル名で保存してみましょう。

os.loadAPI("logging")

-- LOG_FILEが残っているということは、前回作業途中でゲームが強制終了したということ
if fs.exists(logging.LOG_FILE) then
  term.write("Return to home position..")
  logging.makeRevFile()
  logging.backupFile(logging.LOG_FILE)
  logging.backupFile(logging.REV_FILE)
  shell.run(logging.REV_FILE.."-bak")
  print("ok")
else -- ファイルが残っていないのは初めてこのプログラム動かすということ
  for i=1, 20 do
    logging.forward()
  end
  logging.turnRight()
  for i=1, 20 do
    logging.forward()
  end
end

そして、以下の手順で試してみましょう。

  1. あらかじめ、LOG_FILE(デフォルト"mylog")とREV_FILE(デフォルト"myrev")がタートルに存在しないことを確認
    • これらファイルが残っていたら消しましょう。たとえば次のようなコマンドをプロンプトに入力。
    • > rm my* (ファイル名が「my」から始まるファイルを消去)
  2. startup プログラムを実行
  3. タートル移動中に、Eキー押して自分のインベントリ開く→Windowのタスクマネージャー(CTRL+ALT+DEL)などを使って、Minecraftを(javawというプロセスを全て)強制終了。
    • MC1.7.2で試したところ、Window右上の×ボタンを押すと正常終了しているようです。自動セーブしてくれているのかな?
    • 今回はつまり、Javaのエラーなどで不正終了したケースを想定しています。常に最悪のケースを想定しましょう。
  4. Minecraftを再起動

ほら、かしこいタートル君は元の場所にもd……。

えぇぇぇぇぇぇぇ。ダメでしたw

なぜでしょう。失敗の原因を考えてみましょう。

原因考察

もっとも大きな原因は、Minecraftを強制終了したために、Minecraftが直前に保存したセーブファイルの状態に巻き戻っているためです。

Minecraftは、定期的に現在のワールドの状態をセーブファイルに保存します。たしか、デフォルトで2分だったでしょうか。

OptiFineなどのオートセーブ間隔を変更できるModを使っているともっと間隔が長かったかもしれませんが。

そのため、Minecraftを無理やり強制終了すると、この場合は最大で約2分前の状態に巻き戻ってしまうのです。

しかし今回のlogging APIは、タートルが行動した端からどんどんログを書きだしていきます。

この齟齬が、さきほど試した応用が失敗した原因だと考えられます。

わかりやすく例を挙げましょう。

ワールドセーブは一定間隔ごとに行われますが、たとえば、マイクラ時間12:05にワールドがセーブされたとします。

それに対して、タートルのログは実行されるたびにリアルタイムにログファイルに保存されていきます。 12:06にturtle.forward()、12:07にturtle.turnRight()など。

さてここで、何らかの原因によりマイクラが異常終了したとしましょう。

再起動するとマイクラ時間12:05に保存されたセーブファイルの状態にワールドが巻き戻ってしまいます。

しかし、ログファイルはそんなこと関係なく、12:06、12:07の行動履歴が残っているわけで、

それを元に逆行動ファイルを作ったらおかしなことになってしまいます。

次回のお話

それではこの対策としてどのような方法が考えられるでしょうか。

ヒント:タイムスタンプ

お楽しみに。