In case Flash no longer exists; a copy of this site is included in the Flashpoint archive's "ultimate" collection.

Dead Code Preservation :: Archived AS3 works from wonderfl.net

forked from: A*(A-star)探索アルゴリズム(ご指摘を受けて修正)

参考
http://itpro.nikkeibp.co.jp/article/COLUMN/20070223/263174/

===追記===
miyaokaさん < 確かに最短になってないですねorz
ちょっとソースコードを修正してみました。
どなたか、ここおかしいなと思うところがあったら連絡ください(汗
Get Adobe Flash player
by Leon 02 Jul 2009
// forked from rsakane's A*(A-star)探索アルゴリズム(ご指摘を受けて修正)
package
{
/*
 * 参考
 * http://itpro.nikkeibp.co.jp/article/COLUMN/20070223/263174/
 * 
 * ===追記===
 * miyaokaさん < 確かに最短になってないですねorz
 * ちょっとソースコードを修正してみました。
 * どなたか、ここおかしいなと思うところがあったら連絡ください(汗
*/
	import flash.display.Sprite;
	import flash.events.MouseEvent;
	import flash.text.TextField;
	import flash.text.TextFormat;
	import flash.text.TextFieldAutoSize;
	
	[SWF(width="465", height="465", frameRate="30", backgroundColor="0xFFFFFF")]
	public class Main extends Sprite
	{
		private const WIDTH:int = 12;
		private const HEIGHT:int = 12;
		private const BLOCKSIZE:int = 30;
		private const pos:Array =   [[0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0],
								     [0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0],
								     [0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0],
								     [0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0],
								     [0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0],
								     [0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0],
								     [0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0],
								     [0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0],
									 [0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0],
									 [0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0],
									 [0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0],
									 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]];
		//
		private const DIR4:Array = [[0, -1], [ -1, 0], [0, 1], [1, 0]]; // 上下左右の4方向に対応
		private const DIR8:Array = [[0, -1], [ -1, 0], [0, 1], [1, 0], [-1, -1], [-1, 1], [1, -1], [1, 1]]; // 上下左右斜めの8方向に対応
		private var map:Array;
		private var searchQue:Array;
		private var clickable:Boolean;
		private var useDir4:Boolean = true;
		private var tf:TextField;
		
		public function Main()
		{
			// Wonderfl.capture_delay(20);
			clickable = true;
			init();
			drawMap();
			stage.addEventListener(MouseEvent.CLICK, onMouseClick);
			
			var button:Sprite = new Sprite();
			button.x = 170;
			button.y = 410;
			button.graphics.beginFill(0xFBE1F9);
			button.graphics.drawEllipse(-18, -6, 63, 35);
			button.buttonMode = true;
			button.graphics.endFill();
			button.addEventListener(MouseEvent.CLICK, buttonClick);
			tf = createTextField(button, "OFF");
			addChild(button);
			
			createTextField(null, "斜め移動を可能にする - ", 0, 410); 
			createTextField(null, "のマスをクリックをすると最短経路を表示します(スタート位置は固定)", 31, 370); 
		}
		
		private function buttonClick(event:MouseEvent):void
		{
			if (useDir4) useDir4 = false, tf.text = "ON"
			else useDir4 = true, tf.text = "OFF";
		}
		
		private function init():void
		{
			map = new Array(HEIGHT);
			for (var y:int = 0; y < HEIGHT; y++)
			{
				map[y] = new Array(WIDTH); // 二次元配列生成
				for (var x:int = 0; x < WIDTH; x++)
				{
					var node:Node = new Node();
					node.pos.x = x;
					node.pos.y = y;
					if (pos[y][x]) node.attr = ATTR.WALL // // WALL = 壁
					else node.attr = ATTR.EMPTY; // EMPTY = 空間
					map[y][x] = node;
				}
			}
		}
		
		private function onMouseClick(event:MouseEvent):void
		{
			if (!clickable) return; // 探索中はクリックしても反応しないようにする
			
			init();
			
			var start:Pos = new Pos(); 		// スタートからゴールの位置へ
			var goal:Pos = new Pos();		//
			start.y = 7, start.x = 0;		// スタート位置は固定で
			
			
			var dy:int = mouseY / BLOCKSIZE; // マップの座標に直す
			var dx:int = mouseX / BLOCKSIZE; //
			
			if (dy < 0 || HEIGHT <= dy || dx < 0 || WIDTH <= dx) return; // マップの外側をクリックされていたらスルーで
			if (pos[dy][dx] || (dy == start.y && dx == start.x)) return; // 壁 または スタート地点をクリックされていたらスルーで
			
			goal.y = dy, goal.x = dx; // クリックした地点をゴールとする
			rootSearch(start, goal);
		}
		
		private function rootSearch(start:Pos, goal:Pos):void
		{
			clickable = false; // 探索中なのでクリック禁止
			map[start.y][start.x].attr = ATTR.START; // スタートの印を入れる
			map[goal.y][goal.x].attr = ATTR.GOAL;	 // ゴールの印を入れる
			
			searchQue = new Array();
			searchQue.push(map[start.y][start.x]); // 探索リストにスタート位置の情報を入れる
						
			do
			{
				searchQue.sortOn(["count"], Array.DESCENDING | Array.NUMERIC); // countプロパティの値を基準に降順に並べる。(pop()したときに値が一番小さいやつを出したい)
				
				var cpos:Node = searchQue.pop();
				if (cpos == null) return; // もう移動するマスがないということで処理終了。 ゴールが見つからなかった。
				
				var dir:Array;
				if (useDir4) dir = DIR4;
				else dir = DIR8;
				for (var i:int = 0; i < dir.length; i++)
				{
					var cx:int = cpos.pos.x + dir[i][0];
					var cy:int = cpos.pos.y + dir[i][1];
						
					if (cx < 0 || WIDTH <= cx || cy < 0 || HEIGHT <= cy) continue; // マップからはみ出ていたらスルーで
					if (map[cy][cx].attr == ATTR.GOAL) // ゴールが見つかった
					{
						map[cy][cx].backpos.x = cpos.pos.x; // 後でゴールからスタート位置まで辿る為に今居る位置をゴールnodeのbackposに入れておく
						map[cy][cx].backpos.y = cpos.pos.y;
						break; // ゴールが見つかったのでcpos.posの8方向はもう調べる必要がない。
					}
						
					if (map[cy][cx].attr == ATTR.WALL || map[cy][cx].attr == ATTR.START)
					{
						continue; // 調べている所が壁、もしくはスタート位置だった。関係ないので次の対象マスへ。
					}
						
					// まだ調査していないマス
					if (map[cy][cx].count == 999)
					{
						map[cy][cx].count = cpos.count + 1;	// 一歩進める
						map[cy][cx].backpos.x = cpos.pos.x; // 後でゴールからスタート位置まで辿る為に今居る位置をゴールnodeのbackposに入れておく
						map[cy][cx].backpos.y = cpos.pos.y;
						searchQue.push(map[cy][cx]); // このマスを移動候補にする
					}
					// 既に調査済みのマスだけど、今来た道のほうが近道(?)だった場合。歩数、backposの更新
					else if (cpos.count + 1 < map[cy][cx].count)
					{
						map[cy][cx].count = cpos.count + 1; // 一歩進める
						map[cy][cx].backpos.x = cpos.pos.x; // 後でゴールからスタート位置まで辿る為に今居る位置をゴールnodeのbackposに入れておく
						map[cy][cx].backpos.y = cpos.pos.y;
					}						
				}
				
				// (α) ループを抜け出た時点でcxとcyが配列外の値になる可能性がある(と思う)。一応チェックして、はみだしていたら安全な値に変更しておく
				if (cx < 0 || WIDTH <= cx || cy < 0 || HEIGHT <= cy) cx = cpos.pos.x, cy = cpos.pos.y;
			}
			while (map[cy][cx].attr != ATTR.GOAL);
						
			drawMap();
			
			graphics.beginFill(0xFF0000);
			graphics.drawRect(start.x * 30, start.y * 30, 30, 30);
			graphics.drawRect(goal.x * 30, goal.y * 30, 30, 30);
			graphics.endFill();
			
			var bpos:Pos = new Pos();
			bpos.y = goal.y;
			bpos.x = goal.x;
			do
			{
				bpos = map[bpos.y][bpos.x].backpos;
				if (bpos.x == start.x && bpos.y == start.y) break;
				
				graphics.beginFill(0xE4AA7A);
				graphics.drawRect(bpos.x * 30, bpos.y * 30, 30, 30);
				graphics.endFill();
			}
			while (true);
			
			clickable = true; // 探索おわたのでクリック許可
		}
		
		private function drawMap():void
		{
			graphics.clear();
			for (var y:int = 0; y < HEIGHT; y++)
			{
				for (var x:int = 0; x < WIDTH; x++)
				{
					if (pos[y][x]) graphics.beginFill(0x995030);
					else graphics.beginFill(0xF4F0E5);
					graphics.drawRect(x * 30, y * 30, 30, 30);
					graphics.endFill();
				}
			}
			
			// 説明部分
			graphics.beginFill(0xF4F0E5);
			graphics.drawRect(0, 365, 30, 30);
			graphics.endFill();
		}
		
		private function createTextField(parent:Sprite = null, text:String = "", x:int = 0, y:int = 0, fontSize:int = 13):TextField
		{
			var tf:TextField = new TextField();
			tf.x = x, tf.y = y;
			tf.defaultTextFormat = new TextFormat("_typeWriter", fontSize, 0x393939);
			tf.text = text;
			tf.selectable = false;
			tf.autoSize = TextFieldAutoSize.LEFT;
			if (parent) parent.addChild(tf);
			else this.addChild(tf);
			
			return tf;
		}
	}
}


class Pos
{
	public var x:int;
	public var y:int;
	
	public function Pos(){}
}

class Node
{	
	public var pos:Pos = new Pos();
	public var backpos:Pos = new Pos();
	public var count:int = 999;
	public var attr:int;
	
	public function Node(){}
}

class ATTR
{
	public static const START:int = 0; 	// スタート地点
	public static const GOAL:int = 1;	// ゴール地点
	public static const EMPTY:int = 2;	// 空間
	public static const WALL:int = 3;	// 壁
	
	public function ATTR(){}
}