たくのろじぃのメモ部屋

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

【C#】Minecraft自動化 #5 整地自動化

はじめに

前回は湧き潰し自動化を改良しました。具体的には任意の平面座標における湧き潰しを、地形を考慮して行うプログラムに直しました。 今回は指定範囲内を更地(整地)にするプログラムを作りたいと思います。整地は建築のための前準備ですね。整地しなくてもブロックを置き換えればいいかなと思ったのですが、砂ブロックなどがあった場合、家が埋まる可能性があるので一旦整地したほうがいいでしょう。 というわけで、整地するロジックを考えていきます。

お品書き

  1. ブロックの種類判定
  2. 高さ判定のロジック
  3. 整地の自動化

1. ブロックの種類判定

これは前回のプログラムを使いまわします。が、ただ使いまわすだけでは面白くないのでブロックの種類をカウントするプログラムにします。任意の座標平面は相対座標をもとに、入力した値を加算して範囲を決定します。 マイクラの世界において、空気ブロックが圧倒的に多いので空気ブロックをトリガーにして検索をかけていきます。もし空気ブロックでなければ、他のブロック名が出てくるのでこれを Contains() メソッドにて文字列検索を行います。当てはまる文字列条件(ブロックの種類)をもとに、各ブロックをカウントしていけば種類ごとに分類かつ数え上げができます。変数は配列のほうがいいですが、分かりやすく別にしました。

//カウント用変数
int GrassBlock = 0; //草ブロックの数
int Stone = 0; //石ブロックの数
int Dirt = 0; //土ブロックの数
int Other = 0; //そのほかのブロック
int Total = 0; //合計ブロック数

for (int x = 1; x <= Input_x; x++)
{
    for(int z = 1; z <= Input_y; z++)
    {
        for(int y = 0; y < Input_y; y++)
        {
            string SearchBlock = "/testforblock " + (Position_x + x) + " " + (Position_y + y) + " " + (Position_z + z) + " " + "air";
            result = await Connection.SendCommandAsync(SearchBlock); //ブロックの照合を行うコマンド
            if (result.Contains("Successfully"))//空気ブロックは加算しない
            { 
                Console.WriteLine(SearchBlock);
            }
            else
            {
                Console.WriteLine(SearchBlock);
                Console.WriteLine(result);
                if (result.Contains("Grass")) { GrassBlock++; Total++; }
                else if (result.Contains("Stone")) { Stone++; Total++; }
                else if (result.Contains("Dirt")) { Dirt++; Total++; }
                else { Other++; Total++; }
            }
       }
    }
}

この前後の処理は前回の資料を見ながらやってみてください。サーバの接続およびコンソール出力はご自由に。 自分の場合、動かすと大量のログ(送受信コマンド)が表示されて、最後に数えたブロックの数が表示されます。

f:id:takunology:20191013194153p:plain

これでブロックの数え上げが完了です。 ただ、このままでは座標が増える方向でしか使用できないので、それぞれの環境に合わせてご利用ください。

2. 高さ判定のロジック

整地するにはこのままでは色々と不便です。具体的には砂や砂利の下にあるブロック(土台)を先に空気に置換すると、落ちていってしまいます。落ちてしまうと、その座標にあったブロックが残ってしまいます。なので、上から消していく必要があります。 まずはプレイヤー基準の座標範囲を入力し、その範囲内の頂点に位置するブロックを高さとして取得し、その高さも入力として扱いたいですね。 ロジックとしては「高さ取得」と「整地」の2つに分かれます。

  1. 整地範囲を入力
  2. その範囲内の中で最も高い位置を取得
  3. 高さ255を初期値とし、線形探索で空気ブロック以外を見つける
  4. その高さをもとに整地を行う

2.1 指定範囲内の高さの最大値を取得

まずは空から線形探索するためのロジックをコーディングしていきます。空の高さは255なので、初期値 y=255とします。 上から探索すればいいので、y方向のロジックはプレーヤーの立っている位置まで空気ブロックかどうかを判定するでいいと思います。ただし、プレイヤーよりも高い位置にブロックがあった場合はそのブロックで探索を中断すればいいですね。最も高い地点を探す場合、古い値と比べて大きい場合は更新していく必要があるので、書き換え用の変数HighestYを用意しました。position_y はプレイヤーの相対座標です。前回の記事を参考にしてください。

int HighestY = 0;
for (int y = 255 - (int)Position_y; y > 0; y--) //空から探索
{
    string SearchBlock = "/testforblock " + (Position_x + x) + " " + (Position_y + y) + " " + (Position_z + z) + " " + "air";
    result = await Connection.SendCommandAsync(SearchBlock); //ブロックの照合を行うコマンド
    if (result.Contains("Successfully")) { }//空気ブロックだった場合
    else //何かしらのブロックだった場合
    {
        if(HighestY < (int)Position_y + y) //今までの値より大きければ記録更新
        {
            HighestY = (int)Position_y + y;
        }
        break; //探索終了
    }
}
Console.WriteLine("\nHighest:{0}", HighestY);

これで範囲内のうち最も高い地点を調べることができます。

2.2 最も高い地点の座標表示とテレポート(オプション)

これで高さは得られましたが、これだけでは面白くないのでちょっと付け足します。オプションなので、やりたい人はやってみてください。最も高いブロックの相対座標をまるごとテレポートコマンドにぶち込めば動きます。実装自体は簡単です。

if(HighestY < (int)Position_y + y) //今までの値より大きければ記録更新
{
    HighestY = (int)Position_y + y;
    //x, y方向の値を保持しておく変数に代入
    HighestX = (int)Position_x + x;
    HighestZ = (int)Position_z + z;
}
TPCommand = "/tp " + PlayerName + " " + HighestX + " " + HighestY + " " + HighestZ;
result = await Connection.SendCommandAsync(TPCommand); //最も高い地点へテレポート
Console.WriteLine(result);

動かすとこのようになります。

youtu.be

3. 整地の自動化

さて、ここまでで高さを得られましたので、このパラメータを用いて指定範囲内を整地できるようなプログラムを考えます。といっても今回の高さ判定と前回の松明プログラムを組み合わせれば実現できます。y方向のfor文の初期値を(最高点 - プレイヤーの座標)にすれば、プレイヤーと、プレイヤーを基準とした最高点の間の高さを指定することができます。例えばプレイヤーのy座標が60で、最高点が80だとすれば 80 - 60 = 20 なので、プレイヤーから上20ブロック分整地すればいいことになります。さすがに限界高度までやってるとプログラムが止まりそうなので、このようにしました。

//x方向, z方向に関するfor文
for (int y = 0; y < HighestY - (int)Position_y; y++) 
{
    SetBlock = "/setblock " + (Position_x + x) + " " + (Position_y + y) + " " + (Position_z + z) + " " + "air";
    result = await Connection.SendCommandAsync(SetBlock);
}

こんな感じのロジックで、取得した高さパラメータを利用してその範囲内を整地することができます。ちなみに、これはプレイヤーの高さで整地されます。 動かすとこんな感じになります。

youtu.be

おわりに

これで整地ができるようになりました。途中でプログラムが止まってしまう方は Thread.Sleep(ミリ秒)メソッドを追記してみてください。CoreRCONはマイクラサーバにコマンドを投げるツールなので、プログラムのほうが早く動いているとコマンドを受け付けなくなります。どこかでタイミングを合わせないといけません。あと、ソースコードが汚いので、慣れている人はクラスやインターフェースを使ってコンパクトにするべきでしょうね。私が書いているのはあくまでも下書き程度なので...。

さて、整地が終わったので次回は建築...ではなく、迷路みたいなものをつくってみたいと思います。まずは建築の練習ですね。すこしずつ難易度を上げていきますw

今回作成したコード

参考にどうぞ

github.com