たくのろじぃのメモ部屋

プログラミング(C#)の基礎やそれを応用した技術情報をメモしておくブログです。

【C#】Minecraft自動化 #4 湧きつぶしの改良と地表判定ロジック

はじめに

前回は湧き潰しのプログラムを書きました。が、前回のプログラムのままでは障害物のブロックがあったときに、ブロックを置き換えてしまいます。ブロックを置き換えてしまうということは、地表に松明が設置されないことがあるので湧き潰しに不備が出てしまいます。

なので、今回はもうひとつの条件として「地表判定」を組み込みたいと思います。

お品書き

  1. 前回のプログラムの修正
  2. ブロックを調べるコマンド
  3. 地表判定のロジック

1. 前回のプログラムの修正

前回は絶対座標を指定して、もとの座標から指定した座標の差をとって範囲を指定していましたが、今回ははじめから範囲を入力してみたいと思います。入力はプレイヤーの位置を基準とした範囲とします。

// 省略
//任意の範囲を指定した後の絶対座標
ShowRange("x", Position_x, Input_x);
ShowRange("y", Position_y, Input_y);
ShowRange("z", Position_z, Input_z);
// 省略
static void ShowRange(string Vector, double Position, double Input)
{
    Console.Write(Vector + "= {0} to ", Position); // 現在の座標
    Console.ForegroundColor = ConsoleColor.Yellow;
    Console.WriteLine("{0}", Position + Input); // 範囲指定後の座標
    Console.ForegroundColor = ConsoleColor.White;
}

プレイヤーから x, y, z の方向へどれだけの範囲で松明を置くかを直接指定するようになっています。ちょっとアレンジして終点の座標を黄色で表示させてみました。

f:id:takunology:20190829032123p:plain

こんな感じに前回プログラムと入力する値を絶対座標から相対座標に変更しました。これで入力部分は完成です。

2. ブロックを調べるコマンド

イクラ1.7.10 には便利なコマンドがありまして、ブロックを調べるコマンドというものがあります。

/testforblock x座標 y座標 z座標 検索したいブロック名

こんな感じです。詳細はwikiを確認してください。

minecraft-ja.gamepedia.com

これでそのブロックが検索したいブロック名と一致するかどうかを調べることができます。試しに自分の立っている場所のブロックを調べてみます。そのまま相対座標を入力すると自分の頭の座標に当たるブロックが表示されてしまうので、y軸から1引きます。

/testforblock ~ ~-1 ~ grass

f:id:takunology:20190829042154p:plain

無事に見つかったようです。では、空気ブロックを調べてみます。座標はすべて相対座標で、ブロック名をairにすると

f:id:takunology:20190829042935p:plain

ん?Residual Heat? 空気のはずなのに空気ブロックではないようです。実はこれ、Rail Craft の MOD を入れている人には表示されるみたいです。どうやら熱源を示しているらしく、プレイヤー自身も熱が出ているという扱いらしいです。なので、プレイヤーから1ブロック以上離れれば air で問題ないようです。

ちなみにこのブロックのIDは "Railcrafr:rasidual.heat" らしいので、これで検索を書けると問題なく表示されます。

3. 地表判定のロジック

前回のプログラムは整地された平地ならば問題ないのですが、自動生成されたところで実行するとどんなブロックもすべて松明で置き換えてしまいます。建物があった場合、建物の壁を破壊してでも松明に置き換えます。やばいです。 悲劇を生まないためにも、地表かどうかを調べる必要があります。要はスキャニングですね。そのためのロジック(アルゴリズム)を考えていきます。

まず、地表かどうかは高さ y 軸を調べれば良いですね。y軸を調べるといっても岩盤から調べると計算量が多いのと、洞窟や空洞に当たった場合に松明がそこに設置される可能性があるので、プレイヤーの立っている座標を基準とします。プレイヤーの立っている座標から空に向かってスキャンしていき、そのブロックが空気 (air) であれば設置すると良さそうです。ただし、プレイヤーよりも下に地表がある場合も考慮すると、だいたいプレイヤーから10ブロック下からスキャンしてもいいかもしれません。

y軸のスキャンには線形探索で十分だと思います。湧き潰しは地表でやるのが一般的だと信じたいですが...。マイクラの世界では高さが255らしいので、最悪で O(n) (n=255) です。といっても最近のコンピュータは性能が良いので、問題ないです。二分探索とかしたい人は各自で実装してみてください。正直、計算している間にブロックを設置するコマンドを実行する余裕を持たせたいので、あえて遅らせるのも手です。

int Player_Position;

for(int y = -10; y < 255 - Player_Position; y++)
{ //プレイヤーの相対座標からスタート 以降インクリメントして最大255までスキャン
    var Block_Name = command.send("/testforblock... {1} ...", (Player_Position + y), ... ); //そのブロックが存在するか
    //ブロック名だけを取り出す処理
    if(BlockName == "air") //空気だった場合
    {
        command.send("/setblock..."); 松明を設置
        break; // 探索をやめる
    }
}

これは線形探索のための最低限のロジックです。入力や範囲などを考慮する必要があります。

まずは線形探索ができるかどうかを確認していきます。松明を置くのは後にして、プレイヤーの相対座標を基準に空へ向かってスキャンし、空気かどうかを確かめてみます。

//ShowRange の変数定義
string SearchItem = "air";

for(int y = -10; y < 255 - (int)Position_y; y++)
{
    string SearchBlock = "/testforblock " + Position_x + " " + (Position_y + y) + " " + Position_z + " " + SearchItem;

    result = await connection.SendCommandAsync(SearchBlock);
    Console.WriteLine(SearchBlock);
    Console.WriteLine(result);
}
// 省略

これでプログラムを送信してみます。

f:id:takunology:20190829120221p:plain

無事に相対座標の高度-10から限界高度255までスキャンすることができました。プレイヤー自身は熱を発生させているので、その1ブロック上から air となっていることが分かります。 これでスキャンは実装できましたが、空気ブロックさえ見つかればいいので、条件分岐を使って空気ブロックが見つかった段階で繰り返しを停止させるようにします。

見つかった場合、"Successfully" という文字列が表示されるので、Contains( ) メソッドを用いて文字列検索し、真であれば繰り返しをやめるロジックを実装します。

if (result.Contains("Successfully"))
{
    break;
}

これで空気ブロックが見つかったときに "Done" と表示されます。

f:id:takunology:20190829121605p:plain

さて、これを指定範囲内で行いたいですね。先ほど実装したロジックを前回作成した湧き潰しプログラムの2重 for文に組み込めばできます。 が、その前にもう一つやることがあります。それは草の判定です。草ブロックの上に草(装飾)が生えているので、このまま松明を置こうとすると、はじかれてしまいます。

f:id:takunology:20191012232539j:plain

なので、草が生えていたら刈らないといけません。刈るといっても空気ブロックに置換するだけです。判定基準は 空気 or 草 に変更してロジックを組みなおします。先ほどまで「空気ブロック」かどうかが前提になっていましたが、今回は「草」かどうかを条件に判定を行います。ちなみにマイクラでの草は tallgrass という名前らしいです。一旦空気ブロックに置換する処理を加えますが、もともと空気ブロックであれば置換しないのでそのままでもいいと思います。

//プレイヤー位置座標コード(省略)
string PutItem = "torch";
string SearchItem = "tallgrass";
for (int y = -10; y < 255 - (int)Position_y; y++) //プレイヤーの10ブロック下からスキャニング(255は限界高度)
{
    string SearchBlock = "/testforblock " + (Position_x + x) + " " + (Position_y + y) + " " + (Position_z + z) + " " + SearchItem;
 result = await connection.SendCommandAsync(SearchBlock); //ブロックの照合を行うコマンド
 //コンソール出力する場合はConsole.Writelnを追記
 if (result.Contains("Successfully") || result.Contains("air")) //そのブロックが空気ブロックまたは草(装飾)であるか
 {
        //とりあえず空気ブロックで草を置換
        string Removegrass = "/setblock " + (Position_x + x) + " " + (Position_y + y) + " " + (Position_z + z) + " " + "air";
        result = await connection.SendCommandAsync(Removegrass);
        //コンソール出力
        string SetToaches = "/setblock " + (Position_x + x) + " " + (Position_y + y) + " " + (Position_z + z) + " " + PutItem;
        result = await connection.SendCommandAsync(SetToaches);
       //コンソール出力
}

スキャニングロジックはこんな感じです。これを二重for文の中にぶち込めば動くはずです。二重for文のロジック(平面の配置)は前回の記事を参考にしてみてください。最終的にはこのようになります。

youtu.be

おわりに

これで地表に関係なく湧き潰しができるようになりました。次回は家を建てていきたいところですが、まずはそのための整地作業を自動化したいと思います。今回のロジックをそのまま使えば良さそうですね。

今回作成したコード

参考にどうぞ。 github.com

参考

くらでべの動画が参考になりました。ありがとうございます。

www.youtube.com