迷路を生成してみよう3

 

1回動いて終わり、というプログラムの場合
コンストラクタに全ての処理を書くことも多いと思います。

毎度、管理人イガジーです。

ただ、あとでそれを extends して使おうとした場合に
処理を分離した方が良いと気づくこともあります。

昨日のプログラム例が

public static void main(String[] args) {
	MazeT0 mt=new MazeT0();
	mt.generate(3,4);
}

と2段構成になっているのは、実はあとで
extendsするのに備えているわけなのです。

それは後日理解して頂くとして、島ができないように
対処したプログラム例を以下に示します。

import java.util.Random;

public class MazeT1 {
	Random rnd;
	int xt,yt,xn,yn;
	int [][]mz;

	final int unset=0, wall=1, making=2;

	MazeT1(){ // constructor
		rnd=new Random();
	}

	void generate(int ynn, int xnn){
		xt=xnn; yt=ynn;
		xn=xt*2+3;
		yn=yt*2+3;
		mz=new int[yn][xn];
		for (int x=0;x<xn;++x) {
			mz[0][x]=mz[yn-1][x]=wall; // 外枠 上辺と下辺
		}
		for (int y=1;y<(yn-1);++y) {
			// Arrays.fill(mz[y],unset);
			mz[y][0]=mz[y][xn-1]=wall; // 外枠 左辺と右辺
		}

		for (int j=0;j<yt;++j){ // 各格子点から
			int y=j*2+2;
			for (int i=0;i<xt;++i){
				int x=i*2+2;
				trace(y,x); // 壁を伸ばしてゆく
			}
		}
		mz[0][1]=mz[yn-1][xn-2]=unset; // 入り口と出口をあける
		dispmz(mz); // 表示
	}

	void trace(int yy,int xx) {
		boolean ok=true;
		int x00=xx;
		int y00=yy;

		int dx,dy;
		while (mz[yy][xx]==unset) {
			mz[yy][xx]=making; // 格子点描画

			dx=dy=0;
			int dir=rnd.nextInt(4); // 伸ばす方向を乱数で決める
			if (dir==0) dy=-1; // 上へ
			else if (dir==1) dx=1; // 右へ
			else if (dir==2) dy=1; // 下へ
			else dx=-1; // 左へ

			ok=true;
			if (mz[yy+dy+dy][xx+dx+dx]==making) { // 描画中の自分に接するなら
										// 方向を変える

				ok=false; // 伸ばせないかもしれないと仮定する
				for (int t=1;t<4;++t) {
					int t2=(dir+t)%4;
					dx=dy=0;

					if (t2==0) dy=-1; // 上へ
					else if (t2==1) dx=1; // 右へ
					else if (t2==2) dy=1; // 下へ
					else dx=-1; // 左へ

					if (mz[yy+dy+dy][xx+dx+dx]!=making) { // 2つ先が自分ではなければ
						ok=true; // 伸ばしてよい
						break; // for
					}
				}// end for t
			}// endif mz[][]==1 伸ばした先が自分の軌跡だった時の処理はここまで

			if (ok) { // 伸ばしてヨシなら
				xx+=dx; yy+=dy;
				mz[yy][xx]=making; // 中間点描画
				xx+=dx; yy+=dy; // 次の格子点
			}else {
			// 渦巻き的に自分の軌跡に囲まれて伸ばせない時は作りなおす
	//	System.out.printf("NG y=%d x=%d dy=%d dx=%d¥n",yy,xx, dy,dx ); dispmz(mz);
				fixthem(unset);// やりなおし
				xx=x00; yy=y00;
			}
		}
		fixthem(wall); // 壁にする
	}

	void fixthem(int d) { // 壁にする/やりなおす(消す)
		for (int y=0;y<yn;++y) {
			for (int x=0;x<xn;++x) {
				if (mz[y][x]==making) mz[y][x]=d;
			}
		}
	}

	void dispmz(int [][]zz){
		for (int y=0;y<yn;++y) {
			for (int x=0;x<xn;++x) {
				if (zz[y][x]==wall) System.out.print("■");  // 壁
				else System.out.print(" ");  // 通路
			}
			System.out.println();
		}
	}

	public static void main(String[] args) {
		MazeT1 mt=new MazeT1();
		mt.generate(3,4);
	}
}

内部データ的に、unset(通路)とwall(壁)に加えて
making(作成途上の壁)を使用するようにしています。
making を伸ばしていって、wallに到達したらOKなので
making→wall に変更します。
making を伸ばしていって、袋小路的になってしまったら
making→unset に戻して、やりなおします。
making→wall や making→unset をするのが、fixthem()メソッドです。

//System.out.printf("NG y=%d x=%d dy=%d dx=%d¥n",yy,xx, dy,dx ); dispmz(mz);のコメント(//)をはずすと、袋小路状態を確認できます。

■を並べた図は迷路っぽくないので、ちゃんと壁を線で描画したい
と感じるでしょうから、次回は上記のクラスを extends(継承)して
グラフィック描画するようにしてみましょう。

この記事へのコメントはこちら

メールアドレスは公開されませんのでご安心ください。
また、* が付いている欄は必須項目となりますので、必ずご記入をお願いします。

内容に問題なければ、下記の「コメント送信」ボタンを押してください。