【C#】迷路を作ろう 迷路自動生成

スポンサーリンク

前回、迷路の作成方法を記事にしました。

今回は、実際に作ってみたいと思います。

今回自分が作るのは、【穴掘り法】でやってみたいと思います。

GUIの作成

今回のGUIは下記の通りにします。

Labelを縦横7×7で配置します。

ラベルの命名規則は、左上を(0,0)とし、右にX方向/下にY方向とします。

フローを作る

まずは、ざっくりとフロー表を作ります。

フロー表を作る事で、必要な関数を整理する事ができるので、作るようにしましょう。

ただ慣れてきている人は、必要ないかもしれません。

ざっくり2ステップです。

下記、関数を作っていきましょう。

迷路初期化迷路作成

必要な関数を作る(初期化)

まずは、初期化を作りましょう。

GUIに設置したLabel『lblMazeX*_Y*』のバックカラー(BackColor)を全てグレー(Gray)にします。

命名規則の決めているラベルなので、XYの数値からラベルを取得できるように関数を作ります。

private Label lblMaze(int x, int y)
{
   return (Label)this.Controls["lblMazeX" + x.ToString() + "_Y" + y.ToString()];
}

これで、XYの値が分かれば、ラベルを取得することが出来ます。

では、本題の【初期化】です。

private int Maze_X = 7; //迷路のXの数
private int Maze_Y = 7; //迷路のYの数

private void Initialize_Maze()
{
  for (int x = 0; x < Maze_X; x++)
  {
    for (int y = 0; y < Maze_Y; y++)
    {
      lblMaze(x, y).BackColor = Color.Gray;
    }
  }
}

XYのラベルの個数が分からない為、変数として定義しましょう。

(後々は、可変できるようにします。)

先ほど作った関数【lblMaze(int x,int y)】が普通のラベル用に使われてシンプルです!!

必要な関数を作る(迷路自動生成)

では、本題の迷路自動作成に入ります。

【穴掘り法】をするにあたり、ざっくりフローの確認です。

では、作っていきます。

List lstHole = new List();

private void Create_Maze()
{
  //1,3,5からランダムを選択したい(0,2,4,6は壁の可能性あり)
  Random rd = new Random();
  int posX = 2 * (rd.Next(0, Maze_X / 2)) + 1;
  int posY = 2 * (rd.Next(0, Maze_Y / 2)) + 1;

  lblMaze(posX, posY).BackColor = Color.White;

  //穴を掘ったリストを作る。
  lstHole.Clear();
  lstHole.Add(new Point(posX, posY));

  do
  {
    //新着が見えるように、ウェイト
    Application.DoEvents();
    System.Threading.Thread.Sleep(100);

    int direct = rd.Next();
    int result = Digging(direct, ref posX, ref posY);
    if (result != 0)
    {
      if (result == -1)
      {
        MessageBox.Show("異常");
        return;
      }

      int idx = rd.Next(0, lstHole.Count);
      posX = lstHole[idx].X;
      posY = lstHole[idx].Y;

      continue;
    }

    //全て穴を掘ったか確認する。
    bool res = Check_Hole();
    if (res == true)
    {
      //(奇数,奇数)座標が全てが白になっている。
      break;
    }

    lstHole.Add(new Point(posX, posY));

  } while (true);

  //最後にスタートとゴールを(固定)
  lblMaze(0, 1).BackColor = Color.LightBlue;
  lblMaze(Maze_X - 1, Maze_Y - 2).BackColor = Color.LightPink;
}

初期位置は、X/Yともに奇数座標の時は必ず道になるので、座標が奇数/奇数になるようにします。

XYの数を半分にした(小数点切り捨て)した範囲でランダムを取ります。

2n+1で必ず奇数になるようにします。※ここでのnがランダムの数字

方向を決め、いざ穴を掘ります。【関数:Digging(int 方向,ref int x ,ref int y)】

そして、穴を掘り終わったかのチェックをします【関数:Check_Hole】

戻りがTrueの時にループを抜けます。

そして、ループを抜けたら、スタートの位置とゴールの位置を作ります。

各関数を記載

【関数:Digging(int 方向,ref int x ,ref int y)】

    private int Digging(int direct, ref int posX, ref int posY)
    {
        direct = direct % 4;        //余りを取得して方向を決定 0:↑ 1:→ 2:↓ 3:←

        //範囲外でないことを確認
        //([Maze_X/Maze_Y]は個数なので[posX/posY]は0から始まるので、個数にするために+1する)
        if ((Maze_X < posX + 1) || (Maze_Y < posY + 1))
        {
            return -1;
        }

        //現在のラベル
        Label lbl_Current = lblMaze(posX, posY);
        Label lbl_Process, lbl_Target;

        int beforeX = posX;
        int beforeY = posY;
        Get_GotoLabel(direct, ref posX, ref posY, out lbl_Process, out lbl_Target);

        //移動先がラベル無ければ、終了
        if (lbl_Target == null)
        {
            posX = beforeX;
            posY = beforeY;
            return 1;
        }
        else if(lbl_Target.BackColor == Color.Gray && lbl_Process.BackColor == Color.Gray)
        {
            lbl_Process.BackColor = Color.White;
            lbl_Target.BackColor = Color.White;
        }

        return 0;
    }

穴を掘るときは2マス分進みます。

その為、1マス先と2マス先のLabelのマスを取得する必要があります。

それ用に作ったのが【関数:GetGotoLabel】です。

    private void Get_GotoLabel(
        int direct, ref int posX, ref int posY, 
        out Label lbl_Process, out Label lbl_Target)
    {
        direct = direct % 4;        //余りを取得して方向を決定 0:↑ 1:→ 2:↓ 3:←

        //移動先のラベル
        switch (direct)
        {
            case 0: //↑
                lbl_Process = lblMaze(posX, posY - 1);
                lbl_Target = lblMaze(posX, posY - 2);
                posY = posY - 2;
                break;
            case 1: //→
                lbl_Process = lblMaze(posX + 1, posY);
                lbl_Target = lblMaze(posX + 2, posY);
                posX = posX + 2;
                break;
            case 2: //↓
                lbl_Process = lblMaze(posX, posY + 1);
                lbl_Target = lblMaze(posX, posY + 2);
                posY = posY + 2;
                break;
            case 3:
                lbl_Process = lblMaze(posX - 1, posY);
                lbl_Target = lblMaze(posX - 2, posY);
                posX = posX - 2;
                break;
            default:
                lbl_Process = null;
                lbl_Target = null;
                break;
        }
    }

最後にチェック用の関数です。

【関数:Check_Hole】

    private bool Check_Hole()
    {
        for (int x = 1; x < Maze_X; x = x + 2)
        {
            for (int y = 1; y < Maze_Y; y = y + 2)
            {
                if (lblMaze(x, y).BackColor == Color.Gray)
                {
                    return false;
                }
            }
        }

        return true;
    }

チェック用の関数ではXY座標がともに奇数の座標が白抜きになっているかチェックし、1つでもグレーであれば、まだ穴は掘れるとして、抜けます。

逆に考えるとループが最後まで続いたら、全てが白抜きになったという事になるので、迷路作成終了です。

これで必要な関数が揃ったので、【作成】のクリックを作っていきましょう。

作成ボタンの処理を作る

関数が全て揃ったので、あとは、フロー通り並べるだけです。

    private void btnCreate_Click(object sender, EventArgs e)
    {
        Initialize_Maze();

        Create_Maze();

        MessageBox.Show("終了");
    }

完成です。では、実際に動かしてみます。

4回動かして、4回とも違う迷路になっています。

まとめ

今回の記事では、穴掘り法で迷路を作成する事でした。参考になればと思います。

実はまだ完成ではありません。

今回の目的は【子供に遊んでもらえるアプリケーション】です。

操作するプレーヤーを置いてゴールまで操作してもらえるように作りましょう。

コメント

タイトルとURLをコピーしました