【C言語】棒倒し法で迷路を自動生成するプログラムの作り方

はじめに

どうも! 高杉 皆為(@MinatameT)です。

この記事では、棒倒し法で迷路を自動生成するプログラムの作り方を説明しています。記事名の通り、プログラミング言語はC言語です。

まず、「棒倒し法って何? どういう手順で迷路を生成するの?」という方は、次の記事をお読みください。

それでは、上の記事の内容を知っている前提で、話を進めていきます。

棒倒し法のプログラムの例

ソースコード

#include<stdio.h>
#include<stdlib.h>
#include<time.h>

//どちらも5以上の奇数にすること。
#define max_y 5
#define max_x 5

int main(void)
{
  int field[max_y][max_x]; //フィールド(0が通路で、1が壁。)
  int y; //フィールド配列の縦の要素番号
  int x; //フィールド配列の横の要素番号
  int r; //乱数の値

  srand((unsigned)time(NULL)); //乱数の仕組みを初期化。

  //通路(0)の生成
  for(y=0; y<max_y; y=y+1) //フィールドの縦幅の分だけループする。
  {
    for(x=0; x<max_x; x=x+1) //フィールドの横幅の分だけループする。
    {
      field[y][x] = 0;
    }
  }

  //上下の外壁(1)の生成
  for(x=0; x<max_x; x=x+1) //フィールドの横幅の分だけループする。
  {
    field[0][x] = 1;
    field[max_y-1][x] = 1;
  }

  //左右の外壁(1)の生成
  for(y=0; y<max_y; y=y+1) //フィールドの縦幅の分だけループする。
  {
    field[y][0] = 1;
    field[y][max_x-1] = 1;
  }

  //棒倒し法を使った壁(1)の生成(1行めのみ)
  y = 2; //1行め
  for(x=2; x<max_x-1; x=x+2) //xの要素番号2から要素番号max_x-1まで、1マス飛ばしで棒倒し。
  {
    r = (rand()%12) + 1; //乱数生成(r = 1から12のランダムな値)
    field[y][x] = 1; //中心から……
    if(r>=1 && r<=3) //rが1から3のとき
    {
      if(field[y-1][x]==0) //上に棒(壁)がなければ
      {
        field[y-1][x] = 1; //上に棒を倒す。
      }
      else if(field[y-1][x]==1) //上に棒(壁)があれば
      {
        x = x - 2; //棒を倒さずに、乱数生成をやり直す。
      }
    }
    if(r>=4 && r<=6) //rが4から6のとき
    {
      if(field[y+1][x]==0) //下に棒(壁)がなければ
      {
        field[y+1][x] = 1; //下に棒を倒す。
      }
      else if(field[y+1][x]==1) //下に棒(壁)があれば
      {
        x = x - 2; //棒を倒さずに、乱数生成をやり直す。
      }
    }
    if(r>=7 && r<=9) //rが7から9のとき
    {
      if(field[y][x-1]==0) //左に棒(壁)がなければ
      {
        field[y][x-1] = 1; //左に棒を倒す。
      }
      else if(field[y][x-1]==1) //左に棒(壁)があれば
      {
        x = x - 2; //棒を倒さずに、乱数生成をやり直す。
      }
    }
    if(r>=10 && r<=12) //rが10から12のとき
    {
      if(field[y][x+1]==0) //右に棒(壁)がなければ
      {
        field[y][x+1] = 1; //右に棒を倒す。
      }
      else if(field[y][x+1]==1) //右に棒(壁)があれば
      {
        x = x - 2; //棒を倒さずに、乱数生成をやり直す。
      }
    }
  }
	
  //棒倒し法を使った壁(1)の生成(2行め以降)
  for(y=4; y<max_y-1; y=y+2) //yの要素番号4から要素番号max_y-1まで、1マス飛ばしで棒倒し。
  {
    for(x=2; x<max_x-1; x=x+2) //xの要素番号2から要素番号max_x-1まで、1マス飛ばしで棒倒し。
    {
      r = (rand()%12) + 1; //乱数生成(r = 1から12のランダムな値)
      field[y][x] = 1; //中心から……
      if(r>=1 && r<=4) //rが1から4のとき
      {
        if(field[y+1][x]==0) //下に棒(壁)がなければ
        {
          field[y+1][x] = 1; //下に棒を倒す。
        }
        else if(field[y+1][x]==1) //下に棒(壁)があれば
        {
          x = x - 2; //棒を倒さずに、乱数生成をやり直す。
        }
      }
      if(r>=5 && r<=8) //rが5から8のとき
      {
        if(field[y][x-1]==0) //左に棒(壁)がなければ
        {
          field[y][x-1] = 1; //左に棒を倒す。
        }
        else if(field[y][x-1]==1) //左に棒(壁)があれば
        {
          x = x - 2; //棒を倒さずに、乱数生成をやり直す。
        }
      }
      if(r>=9 && r<=12) //rが9から12のとき
      {
        if(field[y][x+1]==0) //右に棒(壁)がなければ
        {
          field[y][x+1] = 1; //右に棒を倒す。
        }
        else if(field[y][x+1]==1) //右に棒(壁)があれば
        {
          x = x - 2; //棒を倒さずに、乱数生成をやり直す。
        }
      }
    }
  }
	
  field[0][1] = 0; //スタート地点の壁を撤去する。
  field[max_y-1][max_x-2] = 0; //ゴール地点の壁を撤去する。

  //結果表示
  for(y=0; y<max_y; y=y+1) //フィールドの縦幅の分だけループする。
  {
    for(x=0; x<max_x; x=x+1) //フィールドの横幅の分だけループする。
    {
      if(field[y][x]==0) //通路なら
      {
        printf("%2s","  "); //空白で表す。
      }
      else if(field[y][x]==1) //壁なら
      {
        printf("%2s","■"); //四角形で表す。
      }
    }
    printf("\n"); //行の終了時点で改行する。
  }

  return(0);
}

コードについての説明はコメント文でおこなっているのですが、補足説明が必要そうな部分を追加で説明していきます。

まずは、迷路のサイズを決めます。その部分が、次のコードです。

#define max_y 5
#define max_x 5

上の行の数値が縦のサイズで、下の行の数値が横のサイズです。ただし、どちらも5以上の奇数にしてください。これを守っていないと、迷路が崩れてしまいます。

最小の5×5マスの迷路にすると、迷路空間(field配列)の座標は次の画像のようになります。

迷路の座標

次のコードは、通路を用意し、外壁を配置するコードです。

  //通路(0)の生成
  for(y=0; y<max_y; y=y+1) //フィールドの縦幅の分だけループする。
  {
    for(x=0; x<max_x; x=x+1) //フィールドの横幅の分だけループする。
    {
      field[y][x] = 0;
    }
  }

  //上下の外壁(1)の生成
  for(x=0; x<max_x; x=x+1) //フィールドの横幅の分だけループする。
  {
    field[0][x] = 1;
    field[max_y-1][x] = 1;
  }

  //左右の外壁(1)の生成
  for(y=0; y<max_y; y=y+1) //フィールドの縦幅の分だけループする。
  {
    field[y][0] = 1;
    field[y][max_x-1] = 1;
  }

このコードを画像で表すと、こんな感じになります。

外壁の配置

次のコードでは、1行めの棒倒し(壁の生成)をおこなっています。

  //棒倒し法を使った壁(1)の生成(1行めのみ)
  y = 2; //1行め
  for(x=2; x<max_x-1; x=x+2) //xの要素番号2から要素番号max_x-1まで、1マス飛ばしで棒倒し。
  {
    r = (rand()%12) + 1; //乱数生成(r = 1から12のランダムな値)
    field[y][x] = 1; //中心から……
    if(r>=1 && r<=3) //rが1から3のとき
    {
      if(field[y-1][x]==0) //上に棒(壁)がなければ
      {
        field[y-1][x] = 1; //上に棒を倒す。
      }
      else if(field[y-1][x]==1) //上に棒(壁)があれば
      {
        x = x - 2; //棒を倒さずに、乱数生成をやり直す。
      }
    }
    if(r>=4 && r<=6) //rが4から6のとき
    {
      if(field[y+1][x]==0) //下に棒(壁)がなければ
      {
        field[y+1][x] = 1; //下に棒を倒す。
      }
      else if(field[y+1][x]==1) //下に棒(壁)があれば
      {
        x = x - 2; //棒を倒さずに、乱数生成をやり直す。
      }
    }
    if(r>=7 && r<=9) //rが7から9のとき
    {
      if(field[y][x-1]==0) //左に棒(壁)がなければ
      {
        field[y][x-1] = 1; //左に棒を倒す。
      }
      else if(field[y][x-1]==1) //左に棒(壁)があれば
      {
        x = x - 2; //棒を倒さずに、乱数生成をやり直す。
      }
    }
    if(r>=10 && r<=12) //rが10から12のとき
    {
      if(field[y][x+1]==0) //右に棒(壁)がなければ
      {
        field[y][x+1] = 1; //右に棒を倒す。
      }
      else if(field[y][x+1]==1) //右に棒(壁)があれば
      {
        x = x - 2; //棒を倒さずに、乱数生成をやり直す。
      }
    }
  }

1から12の乱数を発生させて、1から3が出た場合は「上」に、4から6が出た場合は「下」に、7から9が出た場合は「左」に、10から12が出た場合は「右」に棒を倒します。

棒がすでに倒されている場所には棒を倒せないので、その場合は乱数生成からやり直します。

そして、次のコードは2行め以降の棒倒し(壁の生成)です。

  //棒倒し法を使った壁(1)の生成(2行め以降)
  for(y=4; y<max_y-1; y=y+2) //yの要素番号4から要素番号max_y-1まで、1マス飛ばしで棒倒し。
  {
    for(x=2; x<max_x-1; x=x+2) //xの要素番号2から要素番号max_x-1まで、1マス飛ばしで棒倒し。
    {
      r = (rand()%12) + 1; //乱数生成(r = 1から12のランダムな値)
      field[y][x] = 1; //中心から……
      if(r>=1 && r<=4) //rが1から4のとき
      {
        if(field[y+1][x]==0) //下に棒(壁)がなければ
        {
          field[y+1][x] = 1; //下に棒を倒す。
        }
        else if(field[y+1][x]==1) //下に棒(壁)があれば
        {
          x = x - 2; //棒を倒さずに、乱数生成をやり直す。
        }
      }
      if(r>=5 && r<=8) //rが5から8のとき
      {
        if(field[y][x-1]==0) //左に棒(壁)がなければ
        {
          field[y][x-1] = 1; //左に棒を倒す。
        }
        else if(field[y][x-1]==1) //左に棒(壁)があれば
        {
          x = x - 2; //棒を倒さずに、乱数生成をやり直す。
        }
      }
      if(r>=9 && r<=12) //rが9から12のとき
      {
        if(field[y][x+1]==0) //右に棒(壁)がなければ
        {
          field[y][x+1] = 1; //右に棒を倒す。
        }
        else if(field[y][x+1]==1) //右に棒(壁)があれば
        {
          x = x - 2; //棒を倒さずに、乱数生成をやり直す。
        }
      }
    }
  }

1から12の乱数を発生させて、1から4が出た場合は「下」に、5から8が出た場合は「左」に、9から12が出た場合は「右」に棒を倒します。

棒がすでに倒されている場所には棒を倒せないので、その場合は乱数生成からやり直します。

最後に、次のコードは迷路の表示です。

  //結果表示
  for(y=0; y<max_y; y=y+1) //フィールドの縦幅の分だけループする。
  {
    for(x=0; x<max_x; x=x+1) //フィールドの横幅の分だけループする。
    {
      if(field[y][x]==0) //通路なら
      {
        printf("%2s","  "); //空白で表す。
      }
      else if(field[y][x]==1) //壁なら
      {
        printf("%2s","■"); //四角形で表す。
      }
    }
    printf("\n"); //行の終了時点で改行する。
  }

ただし、通路を「0」、壁を「1」とそのまま表示すると非常に見づらくなるため、0の部分には「空白」、1の部分には「四角形」を表示するようにしています。

それでは、さっそく実行してみましょう。

実行結果の例

実行結果の例を文字のまま掲載したかったのですが、当サイトのコードの部分は「プロポーショナルフォント(文字ごとに文字の幅が違うフォント)」を使用しているため、迷路が崩れて表示されてしまいます。

そこで、今回は実行結果の「画像(スクショ)」を掲載します。

5×5迷路

まずは上の画像です。これは、最小サイズの5×5マスの迷路です。迷路というか、普通の通路ですね(笑)。

7×7迷路

上の画像は、7×7マスの迷路です。これも迷路ではなく通路ですね。

9×9迷路

上の画像は、9×9マスの迷路です。このあたりの規模までの迷路は、瞬間的にゴールできますね。

11×11迷路

上の画像は、11×11マスの迷路です。まあ簡単ですね。

13×13迷路

上の画像は、13×13マスの迷路です。これも難しくはありませんね。

15×15迷路

上の画像は、15×15マスの迷路です。ほんの少しだけ難しくなりましたね。

……ここで迷路の紹介は終わりにしたかったのですが、最後に大きなサイズの迷路を出力してみましたので、ついでに紹介します。

35×35迷路

上の画像は、35×35マスの迷路です。今までの迷路と比較すると、やや難しいと思います。

迷路で遊んだところで、終了としましょう。お疲れさまでした。


 

みなためじゃんけん

 

このコーナーは、私と擬似的にじゃんけんできるコーナーです。

みなためじゃんけん、じゃんけんぽん!

 

私が出したのは……





 

チョキでした! グーの勝利です!



この記事をSNSでシェアする

プログラミングカテゴリーの最新記事(5件)

最新記事(10件)

管理人のTwitter

内部リンク集