読者です 読者をやめる 読者になる 読者になる
スポンサーリンク

迷路をウネウネと生成する,ライフゲーム的なJavaScript (アスキーアートで反復描画アニメーション)

プログラミング javascript 作品 アスキーアート

迷路をウネウネと生成する,ライフゲーム的なJavaScript」ができた。




  • ボタンを押すと,迷路が生成されてゆく途中経過を見れる。(ポコポコッ・・・ワサワサッ…)
  • 巨大な迷路がアスキーアートで描画される。"┤┐├"みたいな罫線AAと,"■"によるブロックAAの2通り。

こちらから動かせます。↓

迷路をウネウネと生成するJavaScript (アスキーアートで反復描画アニメーション)
http://name-of-this-site.org/coding/j...


特長:

  • AAなので,プレーンテキスト形式で,コピペに便利。
  • 「一時停止」ボタンで,生成を一時停止/再開できる。
  • 迷路のサイズや,反復描画の速さはカスタマイズできる。
  • ソースコードは改造しやすくしてある。
  • 既存のルートを徐々に拡張してゆく,というアルゴリズムでルートを生成している。

迷路が「成長してゆく途中」の経過を見れるのが面白い。ライフゲームみたい。


完成した迷路の難易度は,そこそこ。

パラメータを調節してある。


「トルネコ」のように,毎回違う結果になる。

縦30×横90の例:

┌───────┬─┬───┬──┬──┬┬─┬─┬─┬─┬┬──┬────┬────┬──┬─────┬─────┬──┬─┬─┬──┬─┬───┬──┬──┬─┬─┬─┬──- ι
│┌─┬───┐│ι└┐┌- │┌- │ι┌┘´ι│ι│ι│ι´│ -┐´┌──┐│┌──┐└┐ι│ -──┬- │ -──┬- │ -┐│ι│ι´ι┌┘ι│┌- ┌┼- ι│ -┐´ι´ι´ι└─┬- │
│´ι´┌─┐│´├- ││ -┘│ -┘│├┐ -┤││´││├- ├┐└┬┘┌┐││´┌┐└┐││├┬─┐│┌┴┬─┐│ -┼- ││││└─┤│┌┘││┌┘│ -┤└┐└─┴─┴─┴─┐└- │
├┬┴┐│ι└┤┌┘┌┘├──┴┬─┘│└┐││└─┤││┌┘├- │┌┘│´├┬┘└- │´│´´ι│││ι´ι´├- │┌┘││├─┬┘´│┌┘│´ι└┐└┐├┬─┬─┬──┐└──┤
│´ι├┘├- ││ -┤ -┤┌─┐´┌─┘ι´│└─- │││´ι´┌┘└- └┬┘└┐┌─┴─┴──┘│││└─┴─┤ -┘│┌┘│´ι│ -─┤´┌┴─┴┐└- │´´ι´ι└- ι└──- │
│ -┤│ -┤┌┘└┐└┐│´ι└┬┤┌─┴─┴───┤│├─┴─┤ -──┬┘ι -┤│┌─┬─┐┌─┤│└┬──┐└──┤│ -┼─┘├─┐├─┤ι┌- ├─┬┴┬─┴┬┴┬─┼────┤
├- │└- │└┬┐├- │└┬┴- ││´┌─────- │´│ -┬- └──- │ -┼- │││ι│ι└┤ι´│ -┤ι┌┴┬─- │└┐│┌─┤ι│´ι│├┴┐´ι│ι│┌- │ι´ι´┌──┐│
│ -┴──┴- │´│┌┴┐│┌┬┘└┬┴┐ -────┴─┤┌┴┬─┬┬─┴┐│ -┘││││├- │└┬┴- │││ι│ -─┴┐´││ι││└─┤´│ι└─┤´│´│┌┘├─┴─┘ι -┘│
│ι┌──-  -┤┌┘│ι´│´´ι -┘ι├───┬──- │´ι│ι´├- ι│└─┬┘││´│ -┼- │ -─┤│´│└┬┬┐└┬┘│││└─- └┬┘│ -─┼─┼- │´┌┘ -───┴──┤
├┴┤ -──┐´│┌┘├─┴──┴┬─┘│ -┬- │ -──┴─┘│├- ├- │└┬- │ι│└─┴┐│┌┴─- │└─┼- │´└- │ -┤│└─- ┌─┤┌┴─- │ι│┌┴┬┴┬─┬────- │
│┌┴┬─- ├─┘│ -┤┌─┬─┐´┌─┼┐│ -┴┬───┐ -┤│ -┤┌┴- ├- │├┴──- │´│ -┬─┼─┐│ -┼┐ -─┼- ││ -┬─┘ι´│ -─┬┘│├┘ι│ι´ι│ -─┬──┤
│´ι│ -┬┘┌─┼- ││ι´ι└┬┘ι│´└──┤ -─┐´ι│└┐´│ -─┤ -┤│ -───┴┬┴┐´ι´ι´├- │└┐ι│┌┘└┐│ -─┴┬┴- ┌┤ -┤│ -┤│└─┤├─┐´┌┐│
│┌┘├- │┌┘ι└┐││├─┴- │ -┤│ι┌─- ├─┐└─┤└- ├─┴─- ├- │├┬┬──- │ι├─┴─┴─┘┌┘ι├┘│├─- │└──┐│┌─┤└- │├- │└─┐├┘ι└─┘´│
││┌┤ -┤│┌┴- ││││┌─┬┼┐│├┴┘┌─┤ι├─┐└┬─┘┌┬─┬┤ -┤│´│ -─┬┘│´┌─┬──┬┤ -┤│ -┤│ -─┴─┬- │││ι│ -─┤│┌┴─- │´ι│ -───┤
││´└- │´│ -─┘│´││ι´│´│´ι┌┘ι││´ι└- │┌─┘´ι´├- │´ι└┬- │┌┴┬┘ι└┐ι´└- │└- │└┬──- │ -┘│´│├─- │´│┌─┬┴─┼┴─┬─- │
│├- ιι´ι´ -─┐│ι││├- │ι´┌┤´┌┘´│ -┴──┤´┌- ι´ι│ -┴┐´ι│┌┘│ι│ -┼- │└┐ -┐´ -┬┴┐│ -─┐│ -┐│ι│´ -┬┴─┤│ι│ -┐│┌- │ -─┤
│└─┴┴┬┼───┘│└┤´│ -┘├─┘└─┼-  -┼──┬- ├─┴┐├─┘├─┐└─┤││┌┘│└┐│ -┴┐└─┴┐ι´ι│├-  -┤├- │´├┴┐ι└┬- │´│└┐│´│ -┤┌┐│
├─- ┌─┘│┌──┬┴┐└─┴──┤┌─┬─┼- ι│ -─┘ -┤ι -┴┘┌─┤ι└┬- │´││ -┴┐│└┐ι├──- ├┴┬┘││┌─┘│ -┴─┘ι└┴┐│ -┴┬┴┐´│┌┘ι´│││
│ -┐└─- │´┌┐´ι├────┐│´ι│ι│┌┴┴──-  -┤└─┬─┤ι│└┐│ -┴─┤└┬- │├- ├┘│┌──┤┌┘┌┘││┌─┴┬───┼─- │├─- │ι└─┴┘┌┴─┤││
├- ├───┼┬┘└─┤│ -──┐│└┬┤││´│┌───┬- └┬- │ι││└┐│└┬─┐└┐│┌┤´┌┤ -┤│┌- ││ -┤┌┘││┌- │ -┬- │ -─┤´┌─┤└─┬──┤ -─┤││
│ -┴─- ι´´┌─┐│└┬─┐│└- ´│´│┌┘│ -┬- │ι┌┘┌┘│´├- ´└┐│ι├- │││└─┤└- │´│┌┘├- ││┌┘´│ -┼- │┌┴─┐└─┘ι└─┐´ -┐└─┐│││
├────┴─┬┘ι´└┐´ι´│ -┐ -┼─┴┘┌┴- │ -┤││ -┘ -┼┬┴┬─- ││││ -┤´│ -─┘ -┬┴─┤└┐│ι││└┐┌┴- │┌┘│ -┐├─┬─┼─- │┌─┴─- ││││
│ -────┐│ -┴┬─┼─┴┐└─┤ -┤ -─┬┘ι┌┘ι│└┼───┘└- │ι┌┘││├- ├─┴─┬─- │ -┐└┐│´└┤└┐└┤ -─┤│ -┴┐││ι´ι´┌─┴┘┌──┬┘│││
├────- │└─┐´ι│ -─┼─┐└─┴┬- │ -┴┴┐│├- │ -─┬─┬─┘├┘ -┤││ι│ -┐ -┘┌─┴┐└┐│└─┬┴┐└- ├─- │└┐ι││´├─┴─┘┌─┬┘┌- │ -┤││
│ι┌───┴─┐└┐└┴─┐│ι└┬─- │ -┴┬─┐│││ -┴─┐´ι│ -┐├- ι´││├┴- ├──┤┌- ├- │└┐ -┘ι├─┬┘┌─┴- ├┘│└─┤┌─┬─┘ι´┌┘┌┴┐│││
│└┘┌───- └┐└──┐││└┐│┌─┴┐ι´ι│├┘├──┐└─┤│ -┴┴┬┴┐│´│ -┬┘ -┐││┌┘┌┤ι└─┬┘│ι´┌┘ι┌─┘┌┴─┐│´ι│ -─┴┬┘┌┘ι││││
├──┘ -───┬┴─┐ι´│├- │´│ -┐│└┬┘││┌┘ιι├─┐│└┬─- │ι├┴─┴┐│ιι││││ -┤│└┬- │ -┤├─┤ -┤│┌─┘ιι´├─┘├──- │ -┘┌┘´│´│
│ -─┐┌┬┬─┴- ┌┴┼─┤´ι├─┼─┤└┐│┌┘│´┌┴┤│ι│└┐│┌─┘│´┌─┐´│├┤│´│└- │├- ├- ├- │´ι└- ├┘│ -─┴┼─┘┌─┤ -─┬┴┬─┴──┴─┤
├─┐││´´ -──┘ι´ι└─┴┘ι´ι└┐│││ -┴─┘ι│││├- │´│ -─┼─┤ι├─┤││├─┘┌─┘│ -┤ -┤ -┴─┴──┤┌┴┬─┐│┌─┤ι└┐ -┘ι´┌──┬─┐│
│ι´│└──────┴─┴────┴─┴- │´│└────┘│´│´ι├─┴- ι´ι´│´ι´│´│ -─┴┐ -┴- │ι└─────- │´ι´ι´│´ι´│ι└──┴─┘ιι´ι´│
´└─┴──────────────────┴─┴──────┴─┴─┴┴───┴─┴─┴─┴─┴─┴───┴───┴┴───────┴─┴─┴─┴─┴─┴┴──────┴┴─┴─┘


実装のコツ:

  • i行j列目と,(x, y)座標系は,タテヨコの概念が逆。両方使おうとするとごっちゃになって行き詰る。どちらか片方で済ませるべし。
  • 「あるマスの右の壁をセット」したら「左側のマスの左側の壁もセットされる」というふうに,アトミックな更新操作をひとまとめにする。そうすれば,データに矛盾が生じない。
  • 上下左右の処理を一般化しようとすると,わけがわからないコードになり,改造がしづらくなる。保守性の高いコードを書くためには,上下左右の処理を個別にコツコツと実装すべし。

以下はソースコード。全部で千行ぐらい。

<!doctype html>
<html>
<meta charset="utf-8">
<head>
  <title>迷路をウネウネと生成するJavaScript (アスキーアートで反復描画アニメーション)</title>
</head>
<body style="background-color:ghostwhite;">


<h2>迷路をウネウネと生成するJavaScript (アスキーアートで反復描画アニメーション)</h2>


迷路のサイズ:
 
タテ<input type="text" value="30" size="2" id="max_i">行
×
ヨコ<input type="text" value="90" size="2" id="max_j">列
   

変形の時間間隔
 
<input type="text" value="10" size="4" id="interval_ms">ミリ秒

   

迷路の文字サイズ
 
<input type="text" value="10" size="2" id="resultFontSize"> px
<br><br>

<input type=button value="迷路の生成を開始" onclick="beginIncrement()" style="font-weight:bold;">
  

<span id="spanStatus"></span>
<br>

<input type=button value="一時停止" onclick="Maze.pauseIncrement()">
<br>
<br>


<!-- ここに迷路のAAが書き込まれる -->
<div id="div_aa" style="line-height:100%;background-color:white;font-family:'モトヤLシーダ3等幅',' 	HiraKakuProN-W3','MS ゴシック';white-space:nowrap;">

</div>
<br>


<script>


// 画面読み込み時
function initPage(){

	// 迷路のサイズ
	var maxi = parseInt( document.getElementById("max_i").value, 10 );
	var maxj = parseInt( document.getElementById("max_j").value, 10 );
	
	// フォントサイズ
	var font_px = parseInt( document.getElementById("resultFontSize").value, 10 );
	document.getElementById("div_aa").style.fontSize = font_px + "px";


	// 迷路を初期化
	Maze.clearAllNodes( maxi, maxj );
	Maze.initRoute(function(){
	
		// ※アルゴリズムは,この関数内で自由に書き換えてよい。

		// 左下に入り口
		this.getNodeAt( this.getimax() - 1, 0 ).setWayToBottom( true );
		
		// 右上に出口
		this.getNodeAt( 0, this.getjmax() - 1 ).setWayToTop( true );
		
		
		// 経路:まず半分まで上へ行ってから
		var half_i = Math.floor( ( this.getimax() - 1 ) / 2 );
		for( var i = this.getimax() - 1; i > half_i; i -- )
			this.getNodeAt( i, 0 )
				.setRightWall( true )
				.setWayToTop( true )
			;
		this.getNodeAt( half_i, 0 ).setWayToRight( true ).setTopWall( true );

		// 右端まで横断して
		for( var j = 1; j < this.getjmax() - 1; j ++ )
			this.getNodeAt( half_i, j )
				.setWayToRight( true )
				.setTopWall( true )
				.setBottomWall( true )
			;
		this.getNodeAt( half_i, this.getjmax() - 1 ).setWayToTop( true ).setBottomWall( true );

		// 上端まで
		for( var i = half_i - 1; i >= 0; i -- )
			this.getNodeAt( i, this.getjmax() - 1 )
				.setWayToTop( true )
				.setLeftWall( true )
			;

	});

	outputMazeAA();
	alertStatus("<font color=blue>外周を初期化しました。左のボタンを押して,変形を開始してください。</font>");
}
window.onload = initPage;


// 迷路をAAで出力
function outputMazeAA()
{
	// AAで表現
	var s1 = Maze.describeByLineAA();
	
	var s2 = Maze.describeByBlockAA();
	
	document.getElementById("div_aa").innerHTML = s1 
		+ "<br><br>" 
		+ s2
	;

}


// 迷路の変形を開始
function beginIncrement(){
	initPage();

	// インターバル
	var ms = parseInt( document.getElementById("interval_ms").value, 10 );

	Maze.beginIncrement({
		interval_ms : ms,
		
		// 1ステップ分の経路変形
		increment : function( cnt ){
			alertStatus( "<b>" + cnt + "回目の変形処理</b>" );
		
			// ※変形のアルゴリズムは,自由に追加してよい。
			
			// 複数の変形アルゴリズムの中から,任意の確率配分で一つを選択し実行
			this.updateRouteByPlans([
				// 実行確率の重み付け数値,実行関数 の順に記載
				
				[ 30, plan_OneStepInvasion ] // これがあれば縦横に単調にならない。これだけだと初期経路が最後まで残り続ける
				,
				[ 70, plan_ItoC ] // これだけだと孤立した1マスが埋まってくれない
			]);
			
			// AAで描画
			outputMazeAA();
		},
		
		// 変形の終了判定
		judgeIncrementCompleted : function( cnt ){
			//stub
			//if( cnt == 1 ) return true;

			// ※アルゴリズムは,この関数内で自由に書き換えてよい。

			// すべての格子点が孤立していなければ経路変形の完了とみなす
			for( var i = 0; i < this.getimax(); i++ )
			{
				for( var j = 0; j < this.getjmax(); j++)
				{
					if( this.getNodeAt( i, j ).isIsolated() ) return false;
				}
			}
		
			return true;
		},

		// 迷路生成の完了時
		onFinish : function( cnt ){
			alertStatus("<font color=blue>完成しました。合計" + cnt + "ステップ</font>");
		}
	});
}


// 迷路の変形のアルゴリズム(自由に追加してよい)


// 「1マス侵食法」:孤立した点へ1歩ずつ踏み出す戦略
function plan_OneStepInvasion(){
	var n1, n2;
	
	// 1マス侵食
	var tryInvasionFromOneNode = function( n1 ){
		if( n1 ){
			// 受け取った点の隣にある孤立した点を任意に選んで
			n2 = n1.getRandomNeighboringIsolatedNode();

			if( n2 ){
				// そこに向かって袋小路を1マス分作る
				n1.stepIntoIsolatedNode( n2 );
				
				// ある確率で先端から再帰し,分岐の深さを深める
				if( Math.random() <= 0.4 )
					tryInvasionFromOneNode( n2 );
			}
		}
	};
	
	// 隣接した点が孤立点であるような経路上の点を選び
	n1 = this.getRandomOneNodeOnRouteThatHasNeighboringIsolatedNode();
	
	// そこから1マス侵食を行なう
	tryInvasionFromOneNode( n1 )
	
	// ある確率で,同じ根元からもう一回行なう(分岐を増やすため)
	if( Math.random() <= 0.5 )
		tryInvasionFromOneNode( n1 );
	
/*
	// この部分のコードを実行すると,迷路内に循環(開ループ)箇所が生まれてしまうのでコメントアウト。
	// 経路部分から孤立部分への侵食は経路を安全に太らせるだけだからOKだが,逆はNG。
	
	// 孤立した点のうち,隣接した経路点を持つものをランダムに一つ選び
	n1 = this.getRandomIsolatedOneNodeThatHasNeighboringRouteNode();
	if( n1 ){
		// その隣にある経路点を任意に選んで
		n2 = n1.getRandomNeighboringNodeOnRoute();

		// そこに向かって1マス分の道を作る
		n2.stepIntoIsolatedNode( n1 );
	}
*/
}



// 「わき道はみだし法」:まっすぐ進む道を,わきに1マス太らせ迂回させる
function plan_ItoC(){
	// I型の道をC型に変化させるので,ItoCと名づける。
	//
	//  ┼┤├      ┼┘├
	//  ┼┤├  →  ┤ -┼
	//  ┼┤├      ┼┐├

	// 横方向に二つ経路点が並び,それにそって孤立点が並ぶような点セットを探して経路変更
	this.changeOneIPathToCPath_Yoko();

	// 縦方向に二つ経路点が並び,それにそって孤立点が並ぶような点セットを探して経路変更
	this.changeOneIPathToCPath_Tate();
}



function debug( s ){
	console.log( s );
}

function alertStatus( s ){
	document.getElementById("spanStatus").innerHTML = s;
}


// -----------------------------------------------------

/*

	迷路をウネウネと生成するJavaScriptライブラリ

	id:language_and_engineering


	・格子点とは,四方に壁を持つマス目のこと。
	
	・格子点の位置指定方法は「i行j列目」で統一する。
	    j <= jmax, 
	    i <= imax
	
	・経路上の格子点を経路点,そうでない点を孤立点と呼ぶ。

*/


// 格子点
var MazeNode = function( i, j, maze ){
	this.i = i;
	this.j = j;
	this._maze = maze;
};
MazeNode.prototype = {
	// 格子点の存在する座標(0以上)
	i : -1,
	j : -1,
	
	// 親となる迷路
	_maze : null,
	
	// 点の上下左右が壁かどうか
	_topWall : false,
	_rightWall : false,
	_bottomWall : false,
	_leftWall : false,
	
	// 点の上下左右へ道が登録されているか
	_wayToTop : false,
	_wayToRight : false,
	_wayToBottom : false,
	_wayToLeft : false,
	
	
	// 点の上下左右に壁を作成(道は作成されない)
	// (もし隣接する点があれば同時にその壁も作る。アトミックな更新操作)
	setTopWall : function( f ){
		this._topWall = f;
		if( this._getTopNode() ) this._getTopNode()._bottomWall = f;
		return this;
	},
	setRightWall : function( f ){
		this._rightWall = f;
		if( this._getRightNode() ) this._getRightNode()._leftWall = f;
		return this;
	},
	setBottomWall : function( f ){
		this._bottomWall = f;
		if( this._getBottomNode() ) this._getBottomNode()._topWall = f;
		return this;
	},
	setLeftWall : function( f ){
		this._leftWall = f;
		if( this._getLeftNode() ) this._getLeftNode()._rightWall = f;
		return this;
	},
	
	
	/* 上下左右とのつながり */
	
	// 上下左右の点を取得(無ければnull)
	_getTopNode : function(){
		return this._maze.getNodeAt( this.i - 1, this.j );
	},
	_getRightNode : function(){
		return this._maze.getNodeAt( this.i, this.j + 1 );
	},
	_getBottomNode : function(){
		return this._maze.getNodeAt( this.i + 1, this.j );
	},
	_getLeftNode : function(){
		return this._maze.getNodeAt( this.i, this.j - 1 );
	},
	
	// 上下左右へ道を開通(壁も操作)
	setWayToTop : function( f ){
		this.setTopWall( ! f ); 
		this._wayToTop = f;
		if( this._getTopNode() ){
			this._getTopNode()._wayToBottom = f; 
		}
		return this;
	},
	setWayToRight : function( f ){
		this.setRightWall( ! f ); 
		this._wayToRight = f;
		if( this._getRightNode() ){
			this._getRightNode()._wayToLeft = f; 
		}
		return this;
	},
	setWayToBottom : function( f ){
		this.setBottomWall( ! f ); 
		this._wayToBottom = f;
		if( this._getBottomNode() ){
			this._getBottomNode()._wayToTop = f; 
		}
		return this;
	},
	setWayToLeft : function( f ){
		this.setLeftWall( ! f ); 
		this._wayToLeft = f;
		if( this._getLeftNode() ){
			this._getLeftNode()._wayToRight = f; 
		}
		return this;
	},
	
	// 特定の方向に道があるか
	hasWayToTop    : function(){ return this._wayToTop; },
	hasWayToRight  : function(){ return this._wayToRight; },
	hasWayToBottom : function(){ return this._wayToBottom; },
	hasWayToLeft   : function(){ return this._wayToLeft; },
	
	// この点が孤立しているかどうか(道が一つもないか)
	isIsolated : function(){
		return ! (
			this._wayToTop ||
			this._wayToRight ||
			this._wayToBottom ||
			this._wayToLeft
		);
	},
	
	// 経路上にあるか
	onRoute : function(){
		return ! this.isIsolated();
	},
	
	
	/* この格子点の周りを,ブロック形式のAAで表現する */
	
	// この格子をブロック形式のAA文字列で表現(2行に分けて1行ずつ)
	describeByBlockAALine1 : function(){
		return "■" +
			( this._topWall ? "■" : " " ) +
			( ( this.j == this._maze.getjmax() - 1 ) ? "■": "" )
		;
	},
	describeByBlockAALine2 : function(){
		return ( this._leftWall ? "■" : " " ) +
			( " " ) +
			( ( this.j == this._maze.getjmax() - 1 ) ?
				( this._rightWall ? "■" : " " ) :
				""
			)
		;
	},
	describeByBlockAALine3 : function(){
		return "■" +
			( this._bottomWall ? "■" : " " ) +
			( ( this.j == this._maze.getjmax() - 1 ) ? "■" : "" )
		;
	},
	
	
	/* この格子点の周りを,罫線のAAで表現する */
	
	// この格子を罫線形式のAA文字列で表現(2行に分けて1行ずつ)
	describeByLineAAAroundLeftTop : function(){
		// 格子マスの左上の点を中心にして壁の有無を言い換える
		var topWall = this._getTopNode() && this._getTopNode()._leftWall;
		var rightWall = this._topWall;
		var bottomWall = this._leftWall;
		var leftWall = this._getLeftNode() && this._getLeftNode()._topWall;
		
		return this._fourWallsToOneChar( topWall, rightWall, bottomWall, leftWall );
	},
	describeByLineAAAroundRightTop : function(){
		// 格子マスの右上の点を中心にして壁の有無を言い換える
		var topWall = this._getTopNode() && this._getTopNode()._rightWall;
		var rightWall = this._getRightNode() && this._getRightNode()._topWall;
		var bottomWall = this._rightWall;
		var leftWall = this._topWall;
		
		return this._fourWallsToOneChar( topWall, rightWall, bottomWall, leftWall );
	},
	describeByLineAAAroundLeftBottom : function(){
		// 格子マスの左下の点を中心にして壁の有無を言い換える
		var topWall = this._leftWall;
		var rightWall = this._bottomWall;
		var bottomWall = this._getBottomNode() && this._getBottomNode()._leftWall;
		var leftWall = this._getLeftNode() && this._getLeftNode()._bottomWall;
		
		return this._fourWallsToOneChar( topWall, rightWall, bottomWall, leftWall );
	},
	describeByLineAAAroundRightBottom : function(){
		// 格子マスの右下の点を中心にして壁の有無を言い換える
		var topWall = this._rightWall;
		var rightWall = this._getRightNode() && this._getRightNode()._bottomWall;
		var bottomWall = this._getBottomNode() && this._getBottomNode()._rightWall;
		var leftWall = this._bottomWall;
		
		return this._fourWallsToOneChar( topWall, rightWall, bottomWall, leftWall );
	},
	
	// 4つの壁フラグの組み合わせを1つのAA文字に変換
	_fourWallsToOneChar : function( topWall, rightWall, bottomWall, leftWall ){
		var num = ( topWall ? 1 : 0 ) +
			( rightWall     ? 2 : 0 ) +
			( bottomWall    ? 4 : 0 ) +
			( leftWall      ? 8 : 0 )
		;
		
		// 壁の位置に応じた罫線の辞書
		// 半角スペースが2個連続すると1個になってしまうので実態参照で記述する
		var dic = [
			" ", "´", "&nbsp;-", "└",
			"ι", "│", "┌", "├",
			"-&nbsp;", "┘", "─", "┴",
			"┐", "┤", "┬", "┼"
		];
		
		return dic[ num ];
	},
	
	
	/* 経路作成のための便利関数 */
	
	// 隣の格子点が孤立していればどれか一つをランダムに返す(無ければnull)
	getRandomNeighboringIsolatedNode : function(){
		var arr = [];
		
		if( this._getTopNode()    && this._getTopNode().isIsolated()    ) arr.push( this._getTopNode()    );
		if( this._getRightNode()  && this._getRightNode().isIsolated()  ) arr.push( this._getRightNode()  );
		if( this._getBottomNode() && this._getBottomNode().isIsolated() ) arr.push( this._getBottomNode() );
		if( this._getLeftNode()   && this._getLeftNode().isIsolated()   ) arr.push( this._getLeftNode()   );
		
		return arr[ Math.floor( Math.random() * arr.length ) ];
	},
	
	// 隣の格子点が孤立していなければ,どれか一つをランダムに返す(無ければnull)
	getRandomNeighboringNodeOnRoute : function(){
		var arr = [];
		
		if( this._getTopNode()    && this._getTopNode().onRoute()    ) arr.push( this._getTopNode()    );
		if( this._getRightNode()  && this._getRightNode().onRoute()  ) arr.push( this._getRightNode()  );
		if( this._getBottomNode() && this._getBottomNode().onRoute() ) arr.push( this._getBottomNode() );
		if( this._getLeftNode()   && this._getLeftNode().onRoute()   ) arr.push( this._getLeftNode()   );
		
		return arr[ Math.floor( Math.random() * arr.length ) ];
	},
	
	// ある隣の孤立した格子点へ,袋小路の経路を延ばす(必ず隣接しているとする)
	stepIntoIsolatedNode : function( node ){
		//debug( "from:" + this.getDebug_ij() + " to:" + node.getDebug_ij() );
		
		if( node.isTopOf( this ) ){ 
			//debug("isTop");
			this.setWayToTop( true );
			node.setTopWall( true ); node.setRightWall( true ); node.setLeftWall( true ); 
		}
		else
		if( node.isRightOf( this ) ){ 
			//debug("isRight");
			this.setWayToRight( true );
			node.setTopWall( true ); node.setRightWall( true ); node.setBottomWall( true ); 
		}
		else
		if( node.isBottomOf( this ) ){ 
			//debug("isBottom");
			this.setWayToBottom( true );
			node.setRightWall( true ); node.setBottomWall( true ); node.setLeftWall( true ); 
		}
		else
		if( node.isLeftOf( this ) ){ 
			//debug("isLeft");
			this.setWayToLeft( true );
			node.setTopWall( true ); node.setBottomWall( true ); node.setLeftWall( true ); 
		}
	},
	
	// 引数に受け取った格子点との位置関係
	isTopOf : function( node ){
		return ( this.i - node.i == -1 ) && ( this.j - node.j == 0 );
	},
	isRightOf : function( node ){
		return ( this.i - node.i == 0  ) && ( this.j - node.j == 1 );
	},
	isBottomOf : function( node ){
		return ( this.i - node.i == 1  ) && ( this.j - node.j == 0 );
	},
	isLeftOf : function( node ){
		return ( this.i - node.i == 0  ) && ( this.j - node.j == -1 );
	},
	
	// 座標をデバッグ出力
	getDebug_ij : function(){
		return "(" + this.i + "," + this.j + ")";
	}
};


// 迷路
var Maze = {

	// 保持する格子点のセット
	_nodes : [],
	
	// 変形した回数
	_increment_cnt : 0,
	
	// 1ステップ分の変形関数
	_repeat_func : null,
	
	// 変形の終了判定関数
	_judgeIncrementCompleted_func : null,
	
	// 変形のインターバル・ミリ秒
	_interval_ms : -1,

	// 変形終了時に実行される関数
	_onIncrementFinish_func : null,
	
	// 変形用のタイマーID
	_repeating_timer_id : null,
	
	// いま変形を実行中か
	_now_modifying : false,

	
	// ある座標の格子を取得
	getNodeAt : function( i, j )
	{
		//debug(i + "," + j);
		
		if( this.validPosition( i, j ) )
		{
			return this._nodes[ i ][ j ];
		}
		else
		{
			return null;
		}
	}
	,
	
	// 有効な座標か
	validPosition : function( i, j )
	{
		return (
			( 0 <= j ) &&
			( j < this.getjmax() ) &&
			( 0 <= i ) &&
			( i < this.getimax() )
		);
	}
	,

	// 迷路のi方向の幅
	getimax : function(){
		return this._nodes.length;
	}
	,
	
	// 迷路のj方向の幅
	getjmax : function(){
		return this._nodes[0].length;
	}
	,
	
	
	/* 迷路の初期化 */
	
	// 与えられたマス目のサイズで,全ての格子点を初期化
	clearAllNodes : function( imax, jmax ){

		var _nodes = new Array( imax );
		
		// 横を埋める
		for( var i = 0; i < imax; i ++ )
		{
			_nodes[ i ] = new Array( jmax );
			
			// 縦を埋める
			for( var j = 0; j < jmax; j ++ )
			{
				// 新規格子点
				_nodes[ i ][ j ] = new MazeNode( i, j, this );
			}
		}
		
		this._nodes = _nodes;
		
		// 周囲を全て壁で囲む
		this._createDefaultBorder();
	}
	,
	
	// 周囲を全て壁で囲む
	_createDefaultBorder : function(){

		// 横方向
		for( var j = 0; j < this.getjmax(); j ++ )
		{
			// 上端は壁
			this.getNodeAt( 0, j )
				.setTopWall( true );

			// 下端は壁
			this.getNodeAt( this.getimax() - 1, j )
				.setBottomWall( true );
		}

		// 縦方向
		for( var i = 0; i < this.getimax(); i ++ )
		{
			// 左端は壁
			this.getNodeAt( i, 0 )
				.setLeftWall( true );

			// 右端は壁
			this.getNodeAt( i, this.getjmax() - 1 )
				.setRightWall( true );
		}
	}
	,
	
	
	/* 迷路をAAで表現 */
	
	// 迷路をブロックAA文字列で表現
	describeByBlockAA : function(){
		var s = "";
		for( var i = 0; i < this.getimax(); i++ )
		{
			for( var j = 0; j < this.getjmax(); j ++ ) s += this.getNodeAt( i, j ).describeByBlockAALine1();
			s += "<br>";
			for( var j = 0; j < this.getjmax(); j ++ ) s += this.getNodeAt( i, j ).describeByBlockAALine2();
			s += "<br>";
		}
		for( var j = 0; j < this.getjmax(); j ++ ) s += this.getNodeAt( this.getimax() - 1, j ).describeByBlockAALine3();
		s += "<br>";

		return s;
	}
	,

	// 迷路を罫線AA文字列で表現
	describeByLineAA : function(){
		var s = "";
		for( var i = 0; i < this.getimax(); i++ )
		{
			// 行内の格子の左上
			for( var j = 0; j < this.getjmax(); j ++ )
				s += this.getNodeAt( i, j ).describeByLineAAAroundLeftTop();
			
			// 行の右端の格子の右上
			s += this.getNodeAt( i, this.getjmax() - 1 ).describeByLineAAAroundRightTop();
			s += "<br>";
		}
		
		// 一番下の行の行内の左下
		for( var j = 0; j < this.getjmax(); j ++ )
			s += this.getNodeAt( this.getimax() - 1, j ).describeByLineAAAroundLeftBottom();

		// 一番下の行の右端の右下
		s += this.getNodeAt( this.getimax() - 1, this.getjmax() - 1 ).describeByLineAAAroundRightBottom();
			s += "<br>";
		
		return s;
	}
	,
	
	
	/* 経路の作成 */
	
	// 経路の初期状態を作成
	// 関数を受け取る。関数の中身はthisを使って記述できる。
	initRoute : function( func )
	{
		func.apply( this );
	}
	,
	
	// 経路の変形を開始
	beginIncrement : function( opt ){
		this._repeat_func = opt["increment"];
		this._judgeIncrementCompleted_func = opt["judgeIncrementCompleted"];
		this._interval_ms = opt["interval_ms"];
		this._onIncrementFinish_func = opt["onFinish"];
		
		this._increment_cnt = 0;
		this._startRepeatingTimer();
	}
	,
	
	// 変形プランを選んで実行
	updateRouteByPlans : function( plans_arr ){
		// 確率の和(ここでは1とは限らない)を設定
		var prob_sum = 0;
		for( var i = 0; i < plans_arr.length; i ++ )
			prob_sum += plans_arr[ i ][0];
		
		// 確率の和に基づいてランダム値を設定
		var random_num = Math.random() * prob_sum
		
		// ランダム値がどのプランを指したのか調べる
		var lower_limit = 0, upper_limit = 0, old_upper_limit = 0, plan_index = null;
		for( var i = 0; i < plans_arr.length; i ++ )
		{
			// 数値範囲の加減と上限を設定しなおし
			lower_limit = old_upper_limit;
			upper_limit += plans_arr[i][0];
			
			// 先のランダム値がこの範囲に入れば,該当とみなす
			if( ( lower_limit <= random_num ) && ( random_num < upper_limit ) )
				plan_index = i;
			
			old_upper_limit = upper_limit;
		}

		// 実行すべきプラン
		var func = plans_arr[ plan_index ][1];
		func.apply( this, [ this._increment_cnt ] );
		
	}
	,

	// タイマーを開始
	_startRepeatingTimer : function(){

		// 繰り返し実行
		var _this = this;
		this._repeating_timer_id = setInterval(function(){
		
			// 終了したら繰り返しを解除
			if( _this._execJudgeIncrementCompleted() ){
				_this._stopRepeatingTimer();
				_this._onIncrementFinish_func( _this._increment_cnt );

				return;
			}

			// 変形
			_this._repeat_func.apply( _this, [ _this._increment_cnt ] );
			
			_this._increment_cnt ++;
			
		}, this._interval_ms);
		
		this._now_modifying = true;
	}
	,
	
	// タイマーを中断
	_stopRepeatingTimer : function(){
		clearInterval( this._repeating_timer_id );
		this._now_modifying = false;
	},
	
	// 経路の変形終了をジャッジ
	_execJudgeIncrementCompleted : function(){
		return this._judgeIncrementCompleted_func.apply( this, [ this._increment_cnt ] );
	},
	
	// 経路の変形を一時停止および再開
	pauseIncrement : function(){
		if( this._now_modifying )
		{
			// 中断
			this._stopRepeatingTimer();
			alertStatus( this._increment_cnt + "回の時点で一時停止");
		}
		else
		{
			// 再開
			this._startRepeatingTimer();
		}
	},
	
	
	// 経路生成のための便利関数
	
	// ランダムに経路上の格子点で隣接した孤立点を持つものを返す
	getRandomOneNodeOnRouteThatHasNeighboringIsolatedNode : function(){
		var arr = [];
		
		for( var i = 0; i < this.getimax(); i ++ )
			for( var j = 0; j < this.getjmax(); j ++ )
				if( 
					// 経路上にあるか
					this.getNodeAt( i, j ).onRoute() &&
					
					// 孤立した隣接点を持つか
					this.getNodeAt( i, j ).getRandomNeighboringIsolatedNode()
				)
					arr.push( this.getNodeAt( i, j ) );
		
		// ランダムに一個返す
		return arr[ Math.floor( ( Math.random() ) * arr.length ) ];
	}
	,
	
	// ランダムに孤立点で隣接した経路点を持つものを返す
	getRandomIsolatedOneNodeThatHasNeighboringRouteNode : function(){
		var arr = [];
		
		for( var i = 0; i < this.getimax(); i ++ )
			for( var j = 0; j < this.getjmax(); j ++ )
				if( 
					// 孤立点か
					this.getNodeAt( i, j ).isIsolated() &&
					// 経路上の隣接点を持つか
					this.getNodeAt( i, j ).getRandomNeighboringNodeOnRoute()
				)
					arr.push( this.getNodeAt( i, j ) );
		
		// ランダムに一個返す
		return arr[ Math.floor( ( Math.random() ) * arr.length ) ];
	}
	,
	
	// I型の通路で横向きのものを探し変形
	changeOneIPathToCPath_Yoko : function(){
		var ii, jj, ij, arr;

		// ○○
		// ×× という形。基準点は左上
		arr = [];
		for( var i = 0; i < this.getimax() - 1; i ++ )
			for( var j = 0; j < this.getjmax() - 1; j ++ )
				if( 
					// 経路上にあるか
					this.getNodeAt( i, j ).onRoute() &&
					// 右側の点へ道があるか
					this.getNodeAt( i, j ).hasWayToRight()
				)
				{
					// 下2つが孤立点か
					if(
						this.getNodeAt( i + 1, j ).isIsolated() &&
						this.getNodeAt( i + 1, j + 1 ).isIsolated()
					)
					arr.push([ i, j ]);
				}
		if( arr.length > 0 ){
			// 作業箇所をランダムに選ぶ
			ij = arr[ Math.floor( Math.random() * arr.length ) ];
			
			// 変更を実行
			ii = ij[0]; jj = ij[1];
			
			this.getNodeAt( ii, jj ).setRightWall( true );
			this.getNodeAt( ii, jj ).setWayToBottom( true );
			
			this.getNodeAt( ii + 1, jj ).setWayToRight( true );
			this.getNodeAt( ii + 1, jj ).setLeftWall( true );
			this.getNodeAt( ii + 1, jj ).setBottomWall( true );
			
			this.getNodeAt( ii + 1, jj + 1 ).setWayToTop( true );
			this.getNodeAt( ii + 1, jj + 1 ).setBottomWall( true );
			this.getNodeAt( ii + 1, jj + 1 ).setRightWall( true );
		}
		
		// ××
		// ○○ という形。基準点は左下
		arr = [];
		for( var i = 1; i < this.getimax(); i ++ )
			for( var j = 0; j < this.getjmax() - 1; j ++ )
				if( 
					// 経路上にあるか
					this.getNodeAt( i, j ).onRoute() &&
					// 右側の点へ道があるか
					this.getNodeAt( i, j ).hasWayToRight()
				)
				{
					// 上2つが孤立点か
					if(
						this.getNodeAt( i - 1, j ).isIsolated() &&
						this.getNodeAt( i - 1, j + 1 ).isIsolated()
					)
					arr.push([ i, j ]);
				}
		if( arr.length > 0 ){
			// 作業箇所をランダムに選ぶ
			ij = arr[ Math.floor( Math.random() * arr.length ) ];
			
			// 変更を実行
			ii = ij[0]; jj = ij[1];
			
			this.getNodeAt( ii, jj ).setRightWall( true );
			this.getNodeAt( ii, jj ).setWayToTop( true );
			
			this.getNodeAt( ii - 1, jj ).setWayToRight( true );
			this.getNodeAt( ii - 1, jj ).setLeftWall( true );
			this.getNodeAt( ii - 1, jj ).setTopWall( true );
			
			this.getNodeAt( ii - 1, jj + 1 ).setWayToBottom( true );
			this.getNodeAt( ii - 1, jj + 1 ).setTopWall( true );
			this.getNodeAt( ii - 1, jj + 1 ).setRightWall( true );
		}
		
	}
	,
	
	// I型の通路で縦向きのものを探し変形
	changeOneIPathToCPath_Tate : function(){
		var ii, jj, ij, arr;
	
		// ○×
		// ○× という形。基準点は左上
		arr = [];
		for( var i = 0; i < this.getimax() - 1; i ++ )
			for( var j = 0; j < this.getjmax() - 1; j ++ )
				if( 
					// 経路上にあるか
					this.getNodeAt( i, j ).onRoute() &&
					// 下側の点へ道があるか
					this.getNodeAt( i, j ).hasWayToBottom()
				)
				{
					// 右2つが孤立点か
					if(
						this.getNodeAt( i,     j + 1 ).isIsolated() &&
						this.getNodeAt( i + 1, j + 1 ).isIsolated()
					)
					arr.push([ i, j ]);
				}
		if( arr.length > 0 ){
			// 作業箇所をランダムに選ぶ
			ij = arr[ Math.floor( Math.random() * arr.length ) ];
			
			// 変更を実行
			ii = ij[0]; jj = ij[1];
			
			this.getNodeAt( ii, jj ).setBottomWall( true );
			this.getNodeAt( ii, jj ).setWayToRight( true );
			
			this.getNodeAt( ii,     jj + 1 ).setWayToBottom( true );
			this.getNodeAt( ii,     jj + 1 ).setTopWall( true );
			this.getNodeAt( ii,     jj + 1 ).setRightWall( true );
			
			this.getNodeAt( ii + 1, jj + 1 ).setWayToLeft( true );
			this.getNodeAt( ii + 1, jj + 1 ).setRightWall( true );
			this.getNodeAt( ii + 1, jj + 1 ).setBottomWall( true );
		}
		
		// ×○
		// ×○ という形。基準点は右上
		arr = [];
		for( var i = 0; i < this.getimax() - 1; i ++ )
			for( var j = 1; j < this.getjmax(); j ++ )
				if( 
					// 経路上にあるか
					this.getNodeAt( i, j ).onRoute() &&
					// 下側の点へ道があるか
					this.getNodeAt( i, j ).hasWayToBottom()
				)
				{
					// 左2つが孤立点か
					if(
						this.getNodeAt( i,     j - 1 ).isIsolated() &&
						this.getNodeAt( i + 1, j - 1 ).isIsolated()
					)
					arr.push([ i, j ]);
				}
		if( arr.length > 0 ){
			// 作業箇所をランダムに選ぶ
			ij = arr[ Math.floor( Math.random() * arr.length ) ];
			
			// 変更を実行
			ii = ij[0]; jj = ij[1];
			
			this.getNodeAt( ii, jj ).setBottomWall( true );
			this.getNodeAt( ii, jj ).setWayToLeft( true );
			
			this.getNodeAt( ii,     jj - 1 ).setWayToBottom( true );
			this.getNodeAt( ii,     jj - 1 ).setTopWall( true );
			this.getNodeAt( ii,     jj - 1 ).setLeftWall( true );
			
			this.getNodeAt( ii + 1, jj - 1 ).setWayToRight( true );
			this.getNodeAt( ii + 1, jj - 1 ).setBottomWall( true );
			this.getNodeAt( ii + 1, jj - 1 ).setLeftWall( true );
		}
		
	}

};


</script>



</body>

</html>


ちなみに,iPadでは等幅表示ができないので,文字が崩れる。

等幅のフォントをスタイルシートで設定する - Fridayeight
http://fridayeight.jp/2013/02/28/1450/

  • 等幅の日本語フォントというとWindowsでは「MS ゴシック」や「MS 明朝」がMacintoshではOsaka−等幅がありますね。


ArcAid.jp - CSSのフォントの指定
http://www.arcaid.jp/modules/arcaid_l...

  • Android 4.0 モトヤLシーダ3等幅


iOS7 ipad safari で 全角幅=2半角幅を実現するfontは? | Apple サポートコミュニティ
https://discussionsjapan.apple.com/th...

  • iOS7 ipad safari で 全角幅=2半角幅を実現するfontはありますでしょうか?


iPhone Safari に等幅フォントってあるの? - 犬ターネット
http://app-mgng.rhcloud.com/347

  • iPhone 4 だと「Arial Unicode MS」と「HKGPW3UI」なら「●▲■」がつぶれないけど、どちらも等幅フォントじゃない。

関連する記事:

あなたが,勝つことも引き分けることもできない三目並べ (jQueryプラグイン「jQuery.fakeTicTacToe.js」によるマルバツ・ゲーム)
http://language-and-engineering.hatenablog.jp/entry/20121204/jQueryFakeTicTac...


わずか1.7キロバイトのJavaScript マリオ風のゲーム (脱力系)
http://language-and-engineering.hatenablog.jp/entry/20081006/1223209263


コマンドラインからマウスを操作する方法 (rundll32.exeで動くDLLの作成法)
http://language-and-engineering.hatenablog.jp/entry/20081117/1226943698


ハワイ語表記への文字列変換フォーム (JavaScript)
http://language-and-engineering.hatenablog.jp/entry/20080928/1222577098


日経平均株価の下落ぶりをMIDIサウンドで味わう (コマンドラインでMIDI生成)
http://language-and-engineering.hatenablog.jp/entry/20081027/1225038111