はじめに
前回の記事で、奥行き(Depth)/幅(Width)/高さ(Height)を指定して、その範囲内を整地するプログラムを紹介しました。
以下のように引数を与えてプログラムを実行すると、奥行き16、幅4(タートル設置地点より右側へ)、高さ10(設置地点より上へ)の範囲を綺麗に整地してくれます。
> seichi 16 4 10
今回の改造・・・効率化
一通り問題なく動くのですが、整地スピードはそれほど速くありません。
基本的に、正面掘って正面移動・真上を掘って真上移動・真下を掘って真下移動のように、1回移動するたびに1回掘っているだけなので仕方ありません。
今回はこの点に手を加えてみましょう。
基本的なアイデア
タートルのdig系の関数は、turtle.dig()
、turtle.digUp()
、turtle.digDown()
の3種類あるので、タートルが移動するときに移動方向だけでなく、他の方向も同時に掘れば、効率が上がりますよね。
たとえば、前回採用したアルゴリズムを再考してみましょう。1セットで2山のブロックを採掘する関数です。
- seichiTwoStep()
- 真上を掘って、真上移動
- 指定された高さまで来たら、正面掘って正面移動
- 真下掘って、真下移動
- 最初の高さまで来たら1セット終了
この流れの中で1回移動するたびに二方向を採掘しましょう。
たとえば真上移動のときに、真上と同時に正面も掘ることで2つの山を掘ることができます。
また真下移動のときにも真下と同時に正面も掘ることでさらに2つの山を掘ることができます。
真上→真下という往復で計4つの山を掘ることができるなんてすばらしい。
それでは早速、1セットで4つの山を掘ることができるseichiFourStep()
関数を作りましょう。seichiTwoStep()
に比べれば、効率2倍! みなぎってきたー!
・・・といいたいところですが、ちょっと待ちましょう。
基本的なアイデアとしてはこれで問題ないのですが、場合によっては、おかしなことになる可能性があります。
真上移動時に正面と真上を掘ると・・・
整地範囲内に砂や砂利が混じっているとき、真上移動時に正面を掘ると問題が生じる可能性があります。
まず、下の図のような状況を考えて見ましょう。ここからタートルは、正面掘って、真上掘って、1歩上に移動します。
つまり、次の図のようになります。それではさらに、正面掘って、真上掘って、1歩上に移動したらどうなるでしょうか。
あらら・・・。下の図のようになってしまうのですね。
正面の土を掘ると、その上にあった砂が落下してしまうので、正面を綺麗に整地するためにはもう一度降りてきて、下に落ちた砂を採掘しないといけないのです。これは二度手間ですね・・・。
つまり、真上方向に移動するときに正面と真上の2つの山を同時に掘るのは問題ありなのです。
逆に一番上まで上昇したあと、真下に下りてくるときに2つの山を掘るのは問題ありません。
つまり、上下の往復で3つの山を掘る!
上下の往復で3つの山を掘ることができる次のような関数を作ります。
- seichiThreeStep()
- 真上を掘って真上移動
- 指定された高さまで来たら、正面掘って正面移動
- 真下掘って、さらに正面も掘って、真下移動
- 最初の高さまで来たら、1歩前進して、1セット終了
前回のプログラムでは、seichiOneLine()
が指定された奥行きまでの一列を整地する関数でした。
seichiTwoStep()
関数をできるだけ使っていき、指定された奥行きが奇数のとき、seichiOneStep()
関数を最後に1回だけ使うことで、ちょうど一列を整地していました。
今回のseichiThreeStep()
関数の導入により、seichiOneLine()
の挙動は以下のようになります。
- seichiOneLine() 【New!】
- できるだけ
seichiThreeStep()
を使って整地していく- depth/3 をしたときの商を求め、その回数だけ繰り返し。
- depth / 3を計算したときの余りが2のときには最後に、
seichiTwoStep()
実行 - depth / 3を計算したときの余りが1のときには最後に、
seichiOneStep()
実行
- できるだけ
ソースコード
簡単な解説
前回のversion0.1から変更した箇所は、以下の2点だけです。
- 関数
seichiThreeStep()
追加 - 関数
seichiOneLine()
を、seichiThreeStep()
を使用するように変更
関数seichiThreeStep()
追加
function seichiThreeStep() for i=1,HEIGHT-1 do surelyUp() end surelyFwd() for i=1,HEIGHT-1 do surelyDig() turtle.digDown() turtle.down() end surelyFwd() end
指定された高さまで上ったあと、降りてくるときに真下と同時に正面を掘っています。
正面を掘るために、正面が砂山であることを考慮してsurelyDig()
を使っています。
また、最初の高さまで降りたら、1歩前進surelyFwd()
することを忘れずに。
関数seichiOneLine()
を修正
function seichiOneLine(d) local d = d or DEPTH for i=1, d/3 do seichiThreeStep() if i<(d/3) then surelyFwd() end end if d%3==1 then seichiOneStep() elseif d%3==2 then seichiTwoStep() end end
local d = d or DEPTH
は、変数の初期値(default値)を指定する慣用表現です。もしこの関数seichiOneLine()
が引数(d)無しで実行されたら、変数dは定数DEPTH(プログラム実行時に指定された奥行き)を使います。d/3
によって奥行きを3で割った値を求めることができます(例:奥行き10なら、10/3=3.33333・・・)。forループ文では、カウンターiの値は1ずつ増えるのでそのままこの値を使ってseichiThreeStep()
を繰り返しています。if i<(d/3) then surelyFwd() end
は、1セットのseichiThreeStep()
終了後の地点から次回のseichiThreeStep()
実行開始地点までの移動です。当然ながらforループ文の最後の繰り返しのときには必要ないので行わないようにしています。- 奥行きを3で割ったときの余り
d%3
によって、seichiOneStep()
とseichiTwoStep()
を使い分けています。 - なお、厳密に商(整数)と余りが欲しいときには、次のように求めます。
syou = math.floor(d/3)
amari = d%3
- だから正確に商と余りをつかって関数を作るならば以下のようになります(手抜きって言わないで><)
function seichiOneLine(d) local d = d or DEPTH local syou = math.floor(d/3) local amari = d%3 for i=1, syou do seichiThreeStep() if i<syou then surelyFwd() end end if amari==1 then seichiOneStep() elseif amari==2 then seichiTwoStep() end end
おわりに
これまでseichiTwoStep()
関数を使っていたところをseichiThreeStep()
関数を使うようになったので、原理的にはほぼ、1.5倍のスピードアップになります。
次回は、さらなる機能追加に努めましょう。
たとえば、広い範囲を整地していると、採掘したブロックがもてなくなり溢れてしまいますよね。その対策のために、チェストを設置してそこに入れるようにしましょう。
また、広い範囲の整地をするとどうしてもMOBが湧いてしまいます。湧き防止のために松明を設置する機能も付け加えましょう。
それでは次回をお楽しみに。