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

Minecraftとタートルと僕

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

FS API を使いこなそう(11): 移動履歴を使って元の位置に戻る機能を実現

チュートリアル Lua ComputerCraft Minecraft API

前回の復習

前回までで、logging APIによるタートル移動履歴機能を完成させました。

logging.forward()などを使って移動したらログファイルに移動したことを書き込んでいきます。

今回は、そのログファイルの内容を使って、スタート地点に戻る機能を実現します。

基本的な考え方

考え方はとてもシンプルです。

たとえばタートルが移動を完了した後、ログファイルには以下のようなログが残っているとします。

turtle.forward()
turtle.forward()
turtle.turnRight()
turtle.forward()

(1)このファイルの中身を読んで、(2)反対の意味の関数に置換して、(3)逆順に入れ替えればよいのです。

つまりはこんな感じに。

turtle.back()
turtle.turnLeft()
turtle.back()
turtle.back()

いまはFS APIというテーマでやっているので、置換&逆順にしたものをファイルに書き出すことにしましょう。

最終的に、この変換後ファイルをプログラムとして実行することで元の場所に戻ってきてくれるはずです。

ログファイルを変換するプログラム

アルゴリズムはいくつか考えられるのですが*1、 わかりやすさ重視でこのようなプログラムを書いてみました。

-- config
LOG_FILE = "mylog" -- log file
REV_FILE = "myrev" -- reverted log file

TRANS_TBL = {}
TRANS_TBL["turtle.forward()"] = "turtle.back()"
TRANS_TBL["turtle.back()"] = "turtle.forward()"
TRANS_TBL["turtle.up()"] = "turtle.down()"
TRANS_TBL["turtle.down()"] = "turtle.up()"
TRANS_TBL["turtle.turnRight()"] = "turtle.turnLeft()"
TRANS_TBL["turtle.turnLeft()"] = "turtle.turnRight()"


-- functions
function backupFile(filename, tail_str)
  local tail_str = tail_str or "-bak"
  if fs.exists(filename) then
    fs.delete(filename..tail_str)
    fs.move(filename, filename..tail_str)
    return true
  else
    return false, "File doesn't exist"
  end
end

function readTrans(filename, trans_tbl)
  local fh = fs.open(filename, 'r')
  local tmp_tbl = {}
  repeat
    local line = fh.readLine()
    local tLine = TRANS_TBL[line]
    if tLine then
      table.insert(tmp_tbl, tLine)
    end
  until not line
  fh.close()
  return tmp_tbl
end

function reverseWrite(my_array, filename)
  local fh = fs.open(filename, 'a')
  for i=#my_array,1,-1 do
    fh.writeLine(my_array[i])
  end
  fh.close()
end

-- main
backupFile(REV_FILE)
local translated_tbl = readTrans(LOG_FILE, TRANS_TBL)
reverseWrite(translated_tbl, REV_FILE)

いつものように、設定項目(config)部分、メイン(main)部分、メイン部分で使っている関数(functions)部分の順に簡単に説明しましょう。

解説(Config)

このプログラムの利用者が自分の好きに書き換えられるパラメータです。

  • LOG_FILE = "mylog"
    • タートルが移動したときにログを出力するファイルの名前です。
    • このファイルをプログラムとして実行すると、同じ地点に移動することができます。
  • REV_FILE = "myrev"
    • 逆の意味の関数に変換して、それを逆順にした関数リスト。その結果を書き出すファイルです。
    • このファイルをプログラムとして実行すると、元の場所に戻ってきます。
  • TRANS_TBL = {} (以下略)
    • 逆の意味の関数に変換するための変換テーブルです。
    • テーブルのキーとして"turtle.forward()"という文字列を指定して参照すると、その逆の意味の関数文字列が返ってきます。
    • つまり、TRANS_TBL["turtle.forward()"]とすると、その結果は"turtle.back()"
    • なお、TRANS_TBL["-- 登録されていない文字列"]のように、定義していないキーで参照すると結果はnilとなります。

解説(main)

簡単に3ステップ。

  1. 最終結果を保存するファイルと同じ名前のファイルがすでにあるならばバックアップをとる。
    • backupFile(REV_FILE)
  2. LOG_FILEを読み込んで逆の意味の関数に変換したテーブルをローカル変数translated_tblに代入
    • local translated_tbl = readTrans(LOG_FILE, TRANS_TBL)
  3. translated_tblの中身を逆順に並べ替えてREV_FILEに書き込み。
    • reverseWrite(translated_tbl, REV_FILE)

解説(Functions)

  • function backupFile(filename, tail_str)

    • これまでも何度も行っている処理です。ファイルが存在したら念のためバックアップ。
    • そのバックアップ先のファイル名もあったら、そちらは消します。
  • function readTrans(filename, trans_tbl)

    • 指定されたファイルを読んで(read)、変換テーブルを使って変換(translate)
    • 返値としてテーブル(配列)を返します。
  • function reverseWrite(my_array, filename)

    • 与えられたテーブル(配列)を、逆順に並べ替えてファイルに書き込み。
    • なお、配列を逆順に(つまり後ろから)読み出すために、for文の特殊構文を使っています。
    • 詳細は以下のとおり
-- 普通の使い方
-- for文でi=1から4まで順に増やしつつ繰り返す
for i=1,4 do
  print(i)
end

-- 4のあとにさらに数値を付け加えることで増加量を指定可能。
-- この場合、print(1)、print(3)が実行される。
for i=1, 4, 2 do
  print(i)
end

-- 増加量にマイナスの値も指定可能なので次のようなことも。
-- for文でi=4から1まで順に値を減らしつつ繰り返す
for i=4, 1, -1 do
  print(i)
end

-- 配列を逆順に(後ろから)読み出す。#arrayで配列の要素数。
array = {'a', 'b', 'c', 'd'}
for i=#array, 1, -1 do
  array[i]
end

なお、配列を逆順に読み出すには、次のようなやり方もあります*2。 お好きなほうをどうぞ。

-- 配列を逆順に(後ろから)読み出す
array = {'a', 'b', 'c', 'd'}
for i=1, #array do
  array[#array-i+1]
end

今回のまとめ

変換プログラムができたので、これをこれまでのlogging APIに組み込みました。

その結果はこちら。

次回はこのAPIの使い方からはじめましょう。

*1:たとえば再帰使うとプログラム自体はシンプルになるけど、説明がががが><

*2:配列の添字が1から始まるLuaの仕様が面倒ですね。