【C#】Minecraft自動化 #6 迷路の生成

はじめに

イクラ自動化はもともとC#でどこまでできるかを検証するためにやってきましたが、どうやら本当に色々できるようです。

とりあえず座標移動(テレポート)と配置(ブロック配置)ができれば何でもできそうです。

今回は迷路を作ります。これは建設自動化のための準備みたいなものです。

1. 外壁をつくる

迷路を作るためには、まずは外壁を作る必要があります。

外壁ロジックは、最初と最後の座標だけ囲みます。座標を二次元配列として扱ったほうが分かりやすいので、二次元配列を使ってみました。GetLengthメソッドを用いると、その配列の要素数を得ることができます。

ロジックとしては、まずはX方向の座標から考えます。最初と最後のX方向は壁にするので、そのX座標が最初か最後かを判定させます。Z方向も同様です。これをぶん回せば迷路の壁が生成されます。

static void Wall()
{
    for (int i = 0; i < Maze.GetLength(0); i++)
    {
        if (i == 0 || i == (Maze.GetLength(0) - 1)) // X方向の始めと終り
        {
            for (int j = 0; j < Maze.GetLength(1); j++)
            {
                Maze[i, j] = 1;
                Console.Write(Maze[i, j]); //壁生成
            }
            Console.WriteLine();
        }
        else
        {
            for (int j = 0; j < Maze.GetLength(1); j++)
            {
                if (j == 0 || j == (Maze.GetLength(1) - 1)) // Z方向の始めと終り
                {
                    Maze[i, j] = 1;
                    Console.Write(Maze[i, j]); //壁生成
                }
                else
                {
                    Maze[i, j] = 0;
                    Console.Write(Maze[i, j]);
                }
            }
            Console.WriteLine();
        }
    }
}

f:id:takunology:20191111171230p:plain

2 壁伸ばし法の実装

これは自分のアルゴリズムに対する理解力が十分でないため、不完全なところがあります。

壁伸ばし法は2通りあります。外壁から壁をランダムな方向に伸ばしていく方法と、ランダムな座標から外壁に向かって伸ばしていく方法です。私は後者を選択しました。

手順というかロジックはこんな感じですかね...。

  • ランダムに壁伸ばし開始座標を選ぶ
  • その座標からランダムな方向へ壁を伸ばす
  • 1マスずつ伸ばすと隣接した壁がくっつく可能性があるので、2マスずつ伸ばす
  • このとき、隣接した座標とその隣の座標が通路でないと伸ばせない
  • ただし、その座標の2つ先が外壁だったら1マス伸ばして開始座標を選びなおす
  • 座標が外壁に達したら、再びランダムな開始座標を選びなおす
  • 開始座標候補がすでに全て壁なら迷路完成

ただ、これだと不完全で伸ばしている最中の壁の座標を保持しておき、新しく伸ばそうとしている壁の座標が、すでに存在すればその方向には伸ばさないという手法も必要らしいです。が、今現在理解できていないので組み込んでいません。スタックに保持する必要があるみたいですが...。

一応ロジックの一部を載せますが、微妙です。

static void Generate()
{
    int PositionX; //開始座標
    int PositionY; //開始座標
    int count = 0;
    Random rnd = new Random();
ReGenerate:
    while (true)
    {
        PositionX = rnd.Next(2, (Maze.GetLength(0) - 2));
        PositionY = rnd.Next(2, (Maze.GetLength(1) - 2));
        count++;
        if (PositionX % 2 == 0 && PositionY % 2 == 0)
        {//偶数になるまで再抽選
            if (Maze[PositionX, PositionY] != "■") //その座標に壁が存在しないか
                break;
        }
    }
    int direction = Direction();

    while (PositionX != 0 || PositionY != 0 || PositionX != (Maze.GetLength(0) - 1) || PositionY != (Maze.GetLength(1))) //その座標が外壁でない限り繰り返す
    {
        direction = Direction();
        Maze[PositionX, PositionY] = "■";

        switch (direction)
        {
            case 0://上
                if ((Maze[PositionX - 1, PositionY] == " ") && (Maze[PositionX - 2, PositionY] == " "))
                { //隣接が通路かつその隣も通路
                    Maze[PositionX - 1, PositionY] = "■";
                    Maze[PositionX - 2, PositionY] = "■";
                    PositionX -= 2;
                }
                else if (PositionX - 2 == 0) //もし現在の座標の2マス上が壁だったら、1マス上を壁にする
                {//壁に隣接した部分を壁にしたので作り直し
                    Maze[PositionX - 1, PositionY] = "■";
                    break;
                }
                else if ((Maze[PositionX - 2, PositionY] == "■") && (Maze[PositionX + 2, PositionY] == "■") && (Maze[PositionX, PositionY - 2] == "■") && (Maze[PositionX, PositionY + 2] == "■"))
                { //うずまき対策
                    goto ReGenerate;
                }
                else
                {
                    break;
                }
                break;
        //下、左、右の方向ロジックも書く。
        }
        Console.Clear();
        Console.WriteLine($"X:{PositionX} Y:{PositionY} 方向:{direction}");
        ShowMaze();
    }
}

迷路の途中経過を表示する関数と方向を決める関数

static void ShowMaze()
{
    for (int x = 0; x < Maze.GetLength(0); x++)
    {
        for (int y = 0; y < Maze.GetLength(1); y++)
        {
            Console.Write(Maze[x, y]);
        }
        Console.WriteLine();
    }
}

static int Direction() //方向を決める
{
    Random rnd = new Random();
    int direction = rnd.Next(0, 4);
    return direction;
}

実行してみるとこんな感じですね。

f:id:takunology:20191114200441p:plain

ちょっと不完全ですが、このまま続けます。いつか必ずリベンジを...。

3 Minecraft で実行してみる

あとは Console.WriteLineの部分を RCON 送信メソッドに置き換えるなりすれば迷路が生成できます。

あと、いままで遅延を発生するために

Thread.Sleep(10);

と書いていましたが、正しくは

await Task.Delay(10);

でした。どうやら非同期処理中に非同期でない遅延メソッドを使うとそこで止まってしまうようです。なぜかはわかりませんが...。

動かしてみるとこんな感じになります。

www.youtube.com

おわりに

今回は迷路(?)を作りました。まだアルゴリズムが未熟な部分があるのでいつか修正したいと思います。

次は建築やってみたいですね。エクセルシートの設計図をもとに自動建築を行いたいと考えています。

今回作ったコード

少し汚いコードですが、参考にどうぞ。

github.com

参考

壁伸ばし法で参考になりました。ありがとうございます。

www5d.biglobe.ne.jp

algoful.com

こちらは非同期遅延メソッドで参考にしました。ありがとうございます。

qiita.com