はじめに
それでは、お待ちかねの整地プログラムです。
今回は、プログラム実行時のパラメータとして、奥行き・幅・高さ(Depth/Width/Height)を指定することで、その範囲内を掘るというプログラムを実装します。
前回検討した、surelyFwd()
やsurelyUp()
を使っていく予定です。
アルゴリズムの検討
現在の設置場所を基準高度と考えたとき、それよりも上側を「切土」するアルゴリズムはいろいろ考えられます。
まず一番最初に思いつくのが、最初からインストールしてあるexcavateプログラム形式の応用ですね。
まず最初に奥行き・幅のパラメータを使ってその範囲の平面を整地する関数を作る。
そしてこの関数を、指定された高さパラメータの分だけ、下から上へ(総合的に考えると上から下への方が良いかもしれませんが)繰り返すというアルゴリズムです。
とてもわかりやすいですね。このアルゴリズムを使ってもいいのですが、この連載では別のアルゴリズムを使います。
・・・え?なぜかって? それは100%僕の好みです。
うそですw
実は今後の整地プログラムの改造、機能追加のことを考えると、これから説明するアルゴリズムの方が都合が良いからです。
少々複雑になりますが、できるだけわかりやすく説明するのでお付き合いください。
縦方向に削るアルゴリズム
簡単に言うと、縦→奥行き→幅の順番に整地するプログラムです。
- まず
surelyFwd()
で1歩前進。 surelyUp()
で真上に掘り進める。- 指定された高さまで来たら、
surelyFwd()
で一歩前進する - その位置から真下へ掘り進め、最初の高さまできたらストップ
これを1つのセットと考えます。
この1セットで、縦方向のブロックを2山分だけ掘ることができます。
これを(surelyFwd()
→上→surelyFwd()
→下)→(surelyFwd()
→上→surelyFwd()
→下)・・・と繰り返すことで、まずは指定した奥行きまで掘ってしまいます。
一番奥まで行ったら右(左)方向に折り返し戻ってきて、・・・という繰り返しで、指定した領域内を掘り進めるわけです。
組み込む関数を検討する
基本となる、(surelyFwd()
→上→surelyFwd()
→下)という一連の挙動の中で、(上→surelyFwd()
→下)という部分をseichiTwoStep()
という関数にして実行することにしましょう。
ただし毎回2つの山を掘ることになるので、指定した奥行きパラメータが奇数のときに困ってしまいます。そのまま素直に繰り返すと、範囲からはみ出してしまいますよね。
そこで、(上を掘って→そのまま下に降りてくる)という1山だけ掘る関数も作ってしまいましょう。こちらは、seichiOneStep()
という名前にしましょう。
奥行きの長さをできるだけseichiTwoStep()
で消化し、あまりが1のときにseichiOneStep()
を使用しましょう。このあたりの細かい計算は、seichiOneLine()
という関数を使ってまとめてしまいます。
ソースコード
簡単な解説
いつものようにソースコードを上から順に、設定項目(config)、関数定義(functions)、メイン部分(main)と見ていきましょう。
config
グローバル変数として、奥行き(DEPTH)、幅(WIDTH)、高さ(HEIGHT)という変数を使います。デフォルト値として値があらかじめ設定してありますが、このデフォルト値は、このプログラムをパラメータ指定無しで実行したときに使います。
毎回パラメータを入力するのめんどくさいという方はこの値を書き換えてください。
functions
surely系の関数は前回までに全て説明済みですので問題ありませんね?
- seichiTwoStep()
- 指定された高度まで上へ掘り進み、1歩前進、最初の高さまで下へ掘り進む
- 2つの山を掘ることになります。
- seichiOneStep()
- 指定された高度まで上へ掘り進み、そのまま最初の高さまで降りてくる
- 1つの山を掘ることになります。
- seichiOneLine()
- 奥行き(DEPTH)分を、できるだけ
seichiTwoStep()
で掘っていき、1山余ったらseichiOneStep()
で掘ります。 - 1列分を掘ることになります。
- 奥行き(DEPTH)分を、できるだけ
- countAllItems()
- 現在のインベントリ内のアイテム数を数えます。プログラム実行完了後に、「○○個のブロックを入手しました」と表示するために使う関数です。
main
パラメータチェック
まず最初に、プログラム実行時のパラメータの処理を行っています。
実行時の引数が3個のときは、その値をそれぞれ数字変換して、DEPTH/WIDTH/HEIGHTに代入します。引数無しのときには、ソースコード冒頭のデフォルト値をそのまま使います。
以下は、変数にデフォルト値を指定するときの決まり文句です。
もし仮に、args[1]
すなわち第1引数が文字など不正な引数だったら、tonumber(args[1])
はnilを返すので、結果として、DEPTHのデフォルト値を使うことになります。
DEPTH = tonumber(args[1]) or DEPTH
燃料チェック
掘る範囲がわかっているのですから、あらかじめ必要燃料を計算して、現在の燃料で足りないときにエラーで停止するようにしています。
整地実行部分
- L115: 最後にいくつブロックを入手したかを表示するために、現在のブロック所持数を記憶しておく。
- L116: まずは1歩前進
- L118-130:
- 基本的に、1列の整地
seichiOneLine()
を幅の値だけ繰り返す - 折り返しの計算は、現在の横方向位置が奇数か偶数かで行う。
- 基本的に、1列の整地
まとめ
アルゴリズムさえ理解できれば、比較的理解しやすいプログラムだと思います。
しかし、実行しているといくつか不満点も出てくるのではないでしょうか。
次回以降、このプログラムを改造・機能追加していく予定です。お楽しみに。