(学习笔记)Android使用SurfaceView编写“迷宫搜索”例子

本人Android小菜鸟一名,刚入行不久,最近自己学习SurfaceView,想做一些小游戏。在这里放上一个自己编写的“迷宫寻路”的Demo,用于记录学习心得,同时分享给大家。当然有很多地方我不知道用法是否合理,希望大家多指正。


设计思想

1.首先是使用Android的SurfaceView来实现寻路的动画效果。

2.寻路算法使用深度优先遍历的思想。

3.一定程度上改进寻路算法,使算法在“大多数情况”下变得更有效。

Android代码

1.MazePathActivity

迷宫主要的Activity,布局没有使用xml文件,直接初始化的一个自定义继承SurfaceView的MazeView对象。实现了一个自定义的OnFindCompletedListener的回调接口,当迷宫寻路成功或失败时,会通过该接口进行回调。

public class MazePathActivity extends Activity implements OnFindCompletedListener {

	private MazeView mMazeView; // 迷宫的View对象

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		mMazeView = new MazeView(this);
		setContentView(mMazeView);
		mMazeView.setOnFindCompletedListener(this);
	}

	@Override
	public void onFindComplete(int resultCode) {
		switch (resultCode) {
		case MazeView.RESULT_SUCCESS:
			showToast("Find the path");
			break;
		case MazeView.RESULT_NO_WAY:
			showToast("There is no way");
		default:
			break;
		}
	}

	private void showToast(String content) {
		Toast.makeText(this, content, Toast.LENGTH_SHORT).show();
	}

}


2.MazeView

继承与SurfaceView并实现SurfaceHolder.CallBack接口,该接口可以监听Surface的OnCreated、OnChanged、OnDestory几个生命周期。

其中特别说明几个重要方法

  • 生成迷宫
下面是当MazeView创建时调用,主要初始化一些视图参数,迷宫矩阵,起点终点等变量,绘制线程使用DrawBackgroundThread。
<span style="white-space:pre">	</span>@Override
	public void surfaceCreated(SurfaceHolder holder) {
		initViewParam();
		// 生成随机地图
		mMazeMatrix = RandomUtil.getRandomWall(COLUMN, ROW, System.currentTimeMillis());
		// 生成随机起点终点
		mStartPoint[0] = RandomUtil.getRandomPoint(COLUMN, System.currentTimeMillis());
		mStartPoint[1] = 0;
		mEndPoint[0] = RandomUtil.getRandomPoint(COLUMN, System.currentTimeMillis());
		mEndPoint[1] = ROW - 1;
		// 将起点和终点置为0,起点或终点不能作为墙壁
		mMazeMatrix[mStartPoint[0]][mStartPoint[1]] = 0;
		mMazeMatrix[mEndPoint[0]][mEndPoint[1]] = 0;
		mSpiritStack = new Stack<MazeSpirit>();
		mPopList = new ArrayList<MazeSpirit>();
		// 起点入栈
		MazeSpirit spirit = new MazeSpirit(mStartPoint[0], mStartPoint[1]);
		mSpiritStack.push(spirit);
		isRun = false;
		// 绘制背景
		new Thread(new DrawBackgroundThread()).start();
	}
下面方法就是随机获得迷宫矩阵,这里的迷宫用二维数组来作为数据表现,其中有障碍的地方用1来表示,无障碍的地方用0来表示。我这里生成迷宫只是简单的获得随机数赋值,所以生成的迷宫很多时候起点到终点没有路径,所以又添加了通过点击可以修改某一格是否有围墙。如果大家想要生成合理的,自然的迷宫,可以搜索一下 迷宫生成算法
	/**
	 * 获取随机墙壁
	 * 
	 * @param mapArray
	 * @return
	 */
	public static int[][] getRandomWall(int column, int row, long seed) {
		Random mRandom = new Random(seed);
		int[][] mapArray = new int[column][row];
		for (int i = 0; i < mapArray.length; i++) {
			for (int j = 0; j < mapArray[i].length; j++) {
				// 用随机数生成围墙,生成通路的概率为围墙的两倍,此处很坑爹,如果想写好一点的迷宫请百度迷宫生成算法
				mapArray[i][j] = mRandom.nextInt(3) % 2;
			}
		}
		return mapArray;
	}
迷宫创建好后就是下面这个挫样。


起点和终点很明白了,蓝色方框表示围墙,即不可通行的地方,空白就是可以通行的地方,万幸这次生成的迷宫还可以走通。右上角有两个矩形,点击红色的矩形是播放优化后的寻路过程,点击蓝色矩形是比较死板的深度优先遍历过程。之后会介绍到。

  • 迷宫寻路
1.死板的深度优先遍历,深度优先遍历思想我理解的为,每次沿着一条路一直走,知道这条路走不通在返回上一路口换个方向走。这样的思想一看就很死脑筋,不过这个思想本身就是用来遍历而不是寻找,下面是寻找下一节点的主要方法。
<span style="white-space:pre">	</span>/**
	 * 使用深度优先搜索,寻找下一个位置,如果有下一位置,返回true并把该位置入桟,否则为false 思想为:
	 * 
	 * 1.取得栈顶位置,变换当前方向,获得该位置当前方向的下一位置
	 * 
	 * 2.如果该方向下一位置合法,则将下一位置入桟,并以下一位置为基础,从初始方向开始寻找下一位置,
	 * 
	 * 3.如果方向下一位置不合法,则按左上右下的位置变换方向
	 * 
	 * 4.如果四个方向已经尝试完,则该位置出栈,加入被抛弃的列表中
	 * 
	 * 5.重复第一步,直到找到终点,或者栈内元素为空,则表示没有路径
	 */
	private MazeSpirit findNextSpirit() {

		if (mSpiritStack.isEmpty()) {
			return null;
		}
		// 取得栈顶元素
		MazeSpirit topSpirit = mSpiritStack.peek();
		// 改变栈顶元素方向
		topSpirit.direction++;
		int topX = topSpirit.index[0];
		int topY = topSpirit.index[1];
		MazeSpirit nextSpirit = null;
		// 判断方向,确定写一个位置;
		switch (topSpirit.direction) {
		case MazeSpirit.DIRECTION_LEFT:
			nextSpirit = new MazeSpirit(--topX, topY);
			break;
		case MazeSpirit.DIRECTION_TOP:
			nextSpirit = new MazeSpirit(topX, --topY);
			break;
		case MazeSpirit.DIRECTION_RIGHT:
			nextSpirit = new MazeSpirit(++topX, topY);
			break;
		case MazeSpirit.DIRECTION_BOTTOM:
			nextSpirit = new MazeSpirit(topX, ++topY);
			break;
		case MazeSpirit.DIRECTION_END:
			// 当栈顶元素方向已查找完,返回false
			return null;
		default:
			return null;
		}
		// Log.v("next", nextSpirit.index[0] + "-" + nextSpirit.index[1]);
		// 查找下一位置是否合法
		if (isLegalIndex(nextSpirit.index[0], nextSpirit.index[1])) {
			return nextSpirit;
		} else {
			return findNextSpirit();
		}
	}

2.稍微优化了一下上面这个寻找方法,主要变化为第一个步骤,每次到达某一节点后,会将该节点四个方向的合法节点(合法节点表示不是围墙、没有在栈内,且没有将所有方向都已经用完)都获得,然后根据一个简单的公式算每一节点到终点的距离,选择距离最小的节点继续走。其实这样的优化在某些情况下并不一定优于上面的算法,但只是少数。

	/**
	 * 优化深度优先算法
	 * 
	 * 1.每次获取四个方向的下一位置
	 * 
	 * 2.比较四个方向哪一个最接近终点。
	 * 
	 * 3.每次选择最接近终点的位置
	 * 
	 * 这样可以减少一些不必要的查找,例如:终点在下方,且下方位置有效,但因为上方向的优先高于下方,所以选择了上方向
	 */
	private MazeSpirit findNextSpiritOptimize() {
		if (mSpiritStack.isEmpty()) {
			return null;
		}
		// 取得栈顶元素
		MazeSpirit topSpirit = mSpiritStack.peek();
		MazeSpirit bestSpirit = null; // 保存最优位置
		for (int i = 0; i < topSpirit.directionExpend.length; i++) {
			int topX = topSpirit.index[0];
			int topY = topSpirit.index[1];
			// 如果方向还未被开销
			if (!topSpirit.directionExpend[i]) {
				MazeSpirit nextSpirit;
				switch (i + 1) {
				case MazeSpirit.DIRECTION_LEFT:
					nextSpirit = new MazeSpirit(--topX, topY);
					break;
				case MazeSpirit.DIRECTION_TOP:
					nextSpirit = new MazeSpirit(topX, --topY);
					break;
				case MazeSpirit.DIRECTION_RIGHT:
					nextSpirit = new MazeSpirit(++topX, topY);
					break;
				case MazeSpirit.DIRECTION_BOTTOM:
					nextSpirit = new MazeSpirit(topX, ++topY);
					break;
				default:
					return null;
				}
				nextSpirit.direction = i;
				// 查找下一位置是否合法
				if (!isLegalIndex(nextSpirit.index[0], nextSpirit.index[1])) {
					continue;
				}
				if (bestSpirit == null) {
					bestSpirit = nextSpirit;
				} else {
					// 计算目前最优选择的长度
					int bestPath = (bestSpirit.index[0] - mEndPoint[0]) * (bestSpirit.index[0] - mEndPoint[0])
							+ (bestSpirit.index[1] - mEndPoint[1]) * (bestSpirit.index[1] - mEndPoint[1]);
					// 计算另一位置的长度
					int nextPath = (nextSpirit.index[0] - mEndPoint[0]) * (nextSpirit.index[0] - mEndPoint[0])
							+ (nextSpirit.index[1] - mEndPoint[1]) * (nextSpirit.index[1] - mEndPoint[1]);
					if (nextPath < bestPath) {
						bestSpirit = nextSpirit;
					}
				}
			}
		}
		// 如果找到了最优的下一位置,则当前位置该方向被消耗
		if (bestSpirit != null) {
			topSpirit.directionExpend[bestSpirit.direction] = true;
		}
		return bestSpirit;
	}

最后是同一张地图,以上两种方法寻路的过程,其中红色圆点表示当前位置,绿色圆点表示走过路径,灰色圆点走过但绝不可通行的节点。下面这张地图可以看出来第二种算法比第一种少了很多步。

普通深度遍历寻路过程


优化深度遍历寻路过程


最后贴上自定义的SurfaceView类完整代码,之后会将项目打包放上

/**
 * 迷宫绘制的View
 * 
 * @author zhoupeng
 * 
 */
public class MazeView extends SurfaceView implements SurfaceHolder.Callback {

	public static final int RESULT_SUCCESS = 0x1;
	public static final int RESULT_NO_WAY = 0x0;
	/** 视图常量 **/
	private static float VIEW_WIDTH; // 当前视图宽度
	private static float VIEW_HEIGHT; // 当前视图高度
	private static final float VIEW_PADDING = 20f; // 视图边距
	private static final int FRAME_RATE = 10; // 帧数

	/** 边框坐标 **/
	private static float LEFT_X;
	private static float TOP_Y;
	private static float RIGHT_X;
	private static float BOTTOM_Y;

	/** 字体大小 **/
	private static final float FONT_TITLE_SIZE = 30f; // 字体大小
	private static final float FONT_CONTENT_SIZE = 20f; // 字体大小
	/** 矩阵行列常量 **/
	private static final int COLUMN = 11;
	private static final int ROW = 20;

	/** 单元常量 **/
	private static float CELL_WIDTH;
	private static float CELL_HEIGHT;
	private static final float CELL_SPACE = 2f;

	/** 精灵常量 **/
	private static final float SPIRIT_SIZE = 10f;

	/** 普通深度优先搜索的开始按钮顶点坐标 **/
	private static RectF BUTTON_START;

	/** 优化深度优先搜索的开始按钮顶点坐标 **/
	private static RectF BUTTON_START_OPTIMIZE;

	private Context mContext;
	private SurfaceHolder mHolder;
	private Paint mDrawPaint; // 画笔
	private Paint mDivierPaint; // 分割线画笔
	private Paint mTextTitlePaint; // 文字画笔
	private Paint mTextContentPaint; // 文字内容画笔
	private Paint mSpiritPaint; // 精灵画笔
	private Paint mPushPaint; // 栈内元素画笔,表明当前小球路径
	private Paint mPopPaint; // 无效路径画笔
	private Paint mClearPaint; // 橡皮擦
	private Bitmap mStaticBitmap; // 背景图
	private Canvas mStaticCanvas; // 静态画布
	private OnFindCompletedListener onFindCompletedListener;

	private int[][] mMazeMatrix; // 迷宫矩阵
	private int[] mStartPoint = { 0, 0 }; // 起点
	private int[] mEndPoint = { COLUMN - 1, ROW - 1 }; // 终点
	private Stack<MazeSpirit> mSpiritStack; // 路径栈
	private List<MazeSpirit> mPopList; // 无效路径,代表列表中位置无法找到通路
	private boolean isRun; // 标记精灵是否开始移动
	private boolean isFirstStart = true; // 标记是否第一次启动,避免点击按钮重复绘制

	public MazeView(Context context) {
		super(context);
		mContext = context;
		mHolder = getHolder();
		mHolder.addCallback(this);
		mDrawPaint = new Paint();
		mDrawPaint.setColor(context.getResources().getColor(R.color.btn_default));
		mDrawPaint.setTextSize(FONT_CONTENT_SIZE);
		mDivierPaint = new Paint();
		mDivierPaint.setColor(Color.LTGRAY);
		mTextTitlePaint = new Paint();
		mTextTitlePaint.set(mDrawPaint);
		mTextTitlePaint.setTextSize(FONT_TITLE_SIZE);
		mTextTitlePaint.setAntiAlias(true);
		mTextContentPaint = new Paint(mTextTitlePaint);
		mTextContentPaint.setTextSize(FONT_CONTENT_SIZE);
		mSpiritPaint = new Paint();
		mSpiritPaint.setColor(Color.RED);
		mPushPaint = new Paint();
		mPushPaint.setColor(Color.GREEN);
		mPopPaint = new Paint();
		mPopPaint.setColor(Color.GRAY);
		mClearPaint = new Paint();
		mClearPaint.setColor(Color.WHITE);
	}

	/**
	 * 初始化视图参数
	 */
	private void initViewParam() {
		VIEW_WIDTH = getWidth();
		VIEW_HEIGHT = getHeight();
		LEFT_X = VIEW_PADDING;
		RIGHT_X = VIEW_WIDTH - VIEW_PADDING;
		TOP_Y = VIEW_PADDING + FONT_TITLE_SIZE;
		BOTTOM_Y = VIEW_HEIGHT - VIEW_PADDING;
		CELL_WIDTH = (RIGHT_X - LEFT_X) / COLUMN;
		CELL_HEIGHT = (BOTTOM_Y - TOP_Y) / ROW;
		BUTTON_START_OPTIMIZE = new RectF(RIGHT_X - 50f, VIEW_PADDING, RIGHT_X, TOP_Y - 10);
		BUTTON_START = new RectF(RIGHT_X - 150f, VIEW_PADDING, RIGHT_X - 100f, TOP_Y - 10);
		Log.v("screen", "width:" + VIEW_WIDTH + "-height:" + VIEW_HEIGHT + "-LEFT_X:" + LEFT_X + "-RIGHT_X:" + RIGHT_X
				+ "-TOP_Y:" + TOP_Y + "-BOTTOM_Y:" + BOTTOM_Y + "-CELL_WIDTH:" + CELL_WIDTH + "-CELL_HEIGHT:"
				+ CELL_HEIGHT);
	}

	// 因为查找路径是在线程中进行,所以需要用handler让线程通知主线程
	private Handler handler = new Handler() {

		@Override
		public void handleMessage(Message msg) {
			onFindCompletedListener.onFindComplete(msg.what);
		}

	};

	@Override
	public boolean onTouchEvent(MotionEvent event) {
		switch (event.getAction()) {
		case MotionEvent.ACTION_DOWN:
			float x = event.getX();
			float y = event.getY();
			// 开始按钮被点击
			if (x > BUTTON_START_OPTIMIZE.left && x < BUTTON_START_OPTIMIZE.right && y > BUTTON_START_OPTIMIZE.top
					&& y < BUTTON_START_OPTIMIZE.bottom) {
				if (!isRun) {
					isRun = true;
					if (!isFirstStart) {
						// 清除数据
						mSpiritStack.clear();
						mPopList.clear();
						mStaticBitmap.recycle();
						mStaticBitmap = null;
					} else {
						isFirstStart = false;
					}
					// 起点入栈
					MazeSpirit spirit = new MazeSpirit(mStartPoint[0], mStartPoint[1]);
					mSpiritStack.push(spirit);
					new Thread(new DrawSpiritThread(0)).start();
				}
				break;
			}
			// 开始按钮被点击
			if (x > BUTTON_START.left && x < BUTTON_START.right && y > BUTTON_START.top && y < BUTTON_START.bottom) {
				if (!isRun) {
					isRun = true;
					if (!isFirstStart) {
						// 清除数据
						mSpiritStack.clear();
						mPopList.clear();
						mStaticBitmap.recycle();
						mStaticBitmap = null;
					} else {
						isFirstStart = false;
					}
					// 起点入栈
					MazeSpirit spirit = new MazeSpirit(mStartPoint[0], mStartPoint[1]);
					mSpiritStack.push(spirit);
					new Thread(new DrawSpiritThread(1)).start();
				}
				break;
			}
			if (!isRun) {
				// Log.v("location", "x:" + x + "-right" + RIGHT_X);
				if (x > LEFT_X && x < RIGHT_X && y > TOP_Y && y < BOTTOM_Y) {
					int[] index = getIndexByLocation(x, y);
					if (!Arrays.equals(index, mStartPoint) && !Arrays.equals(index, mEndPoint)) {
						if (mMazeMatrix[index[0]][index[1]] == 0) {
							mMazeMatrix[index[0]][index[1]] = 1;
							drawWallByIndex(index[0], index[1], 1);
						} else if (mMazeMatrix[index[0]][index[1]] == 1) {
							mMazeMatrix[index[0]][index[1]] = 0;
							drawWallByIndex(index[0], index[1], 0);
						}
						// onCellClickListener.onCellClick(index[0], index[1]);
					}
				}
			}
			break;

		default:
			break;
		}
		return true;
	}

	@Override
	public void surfaceCreated(SurfaceHolder holder) {
		initViewParam();
		// 生成随机地图
		mMazeMatrix = RandomUtil.getRandomWall(COLUMN, ROW, System.currentTimeMillis());
		// 生成随机起点终点
		mStartPoint[0] = RandomUtil.getRandomPoint(COLUMN, System.currentTimeMillis());
		mStartPoint[1] = 0;
		mEndPoint[0] = RandomUtil.getRandomPoint(COLUMN, System.currentTimeMillis());
		mEndPoint[1] = ROW - 1;
		// 将起点和终点置为0,起点或终点不能作为墙壁
		mMazeMatrix[mStartPoint[0]][mStartPoint[1]] = 0;
		mMazeMatrix[mEndPoint[0]][mEndPoint[1]] = 0;
		mSpiritStack = new Stack<MazeSpirit>();
		mPopList = new ArrayList<MazeSpirit>();
		// 起点入栈
		MazeSpirit spirit = new MazeSpirit(mStartPoint[0], mStartPoint[1]);
		mSpiritStack.push(spirit);
		isRun = false;
		// 绘制背景
		new Thread(new DrawBackgroundThread()).start();
	}

	@Override
	public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

	}

	@Override
	public void surfaceDestroyed(SurfaceHolder holder) {
		isRun = false;
		mStaticBitmap.recycle();
		mStaticBitmap = null;
	}

	/**
	 * 绘制围墙对外方法
	 * 
	 * @param x
	 * @param y
	 * @param state
	 */
	public void drawWallByIndex(int x, int y, int state) {
		new Thread(new DrawWallThread(x, y, state)).start();
	}

	/**
	 * 绘制背景线程
	 * 
	 * @author zhoupeng
	 * 
	 */
	class DrawBackgroundThread implements Runnable {

		@Override
		public void run() {
			Canvas canvas = null;
			synchronized (mHolder) {
				canvas = mHolder.lockCanvas();
				drawStatic(canvas);
			}
			if (canvas != null) {
				mHolder.unlockCanvasAndPost(canvas);
			}
		}

	}

	/**
	 * 绘制围墙线程
	 * 
	 * @author zhoupeng
	 * 
	 */
	class DrawWallThread implements Runnable {

		int mX;
		int mY;
		int mState;

		public DrawWallThread(int indexX, int indexY, int state) {
			mX = indexX;
			mY = indexY;
			mState = state;
		}

		@Override
		public void run() {
			Canvas canvas = null;
			synchronized (mHolder) {
				canvas = mHolder.lockCanvas();
				canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
				drawStatic(canvas);
				drawWall(canvas, mX, mY, mState);
			}
			if (canvas != null) {
				mHolder.unlockCanvasAndPost(canvas);
			}
		}

	}

	/**
	 * 绘制精灵线程
	 * 
	 * @author zhoupeng
	 * 
	 */
	class DrawSpiritThread implements Runnable {

		int methodCode; // 方法选择码

		public DrawSpiritThread() {
			methodCode = 0;
		}

		public DrawSpiritThread(int code) {
			methodCode = code;
		}

		@Override
		public void run() {
			while (isRun) {
				Canvas canvas = null;
				synchronized (mHolder) {
					canvas = mHolder.lockCanvas();
					canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
					drawStatic(canvas);
					drawSpiritNext(canvas, methodCode);
				}
				if (canvas != null) {
					mHolder.unlockCanvasAndPost(canvas);
				}
				try {
					Thread.sleep(1000 / FRAME_RATE);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}

	/**
	 * 根据坐标获取标
	 * 
	 * @param x
	 * @param y
	 * @return
	 */
	private int[] getIndexByLocation(float x, float y) {
		int[] index = new int[2];
		index[0] = (int) ((x - LEFT_X) / CELL_WIDTH);
		index[1] = (int) ((y - TOP_Y) / CELL_HEIGHT);
		return index;
	}

	/**
	 * 绘制静态图像,包括背景,边框,分割线等
	 * 
	 * @param canvas
	 */
	private void drawStatic(Canvas mCanvas) {
		if (mStaticBitmap == null) {
			mStaticBitmap = Bitmap.createBitmap((int) VIEW_WIDTH, (int) VIEW_HEIGHT, Bitmap.Config.ARGB_8888);
			mStaticCanvas = new Canvas(mStaticBitmap);
			mStaticCanvas.drawColor(Color.WHITE);
			mStaticCanvas.drawText(mContext.getString(R.string.maze), LEFT_X, TOP_Y - 10, mTextTitlePaint);
			mStaticCanvas.drawRect(BUTTON_START_OPTIMIZE, mDrawPaint);
			mStaticCanvas.drawRect(BUTTON_START, mSpiritPaint);
			mStaticCanvas.drawLine(LEFT_X, TOP_Y, LEFT_X, BOTTOM_Y, mDrawPaint);
			mStaticCanvas.drawLine(LEFT_X, TOP_Y, RIGHT_X, TOP_Y, mDrawPaint);
			mStaticCanvas.drawLine(RIGHT_X, TOP_Y, RIGHT_X, BOTTOM_Y, mDrawPaint);
			mStaticCanvas.drawLine(LEFT_X, BOTTOM_Y, RIGHT_X, BOTTOM_Y, mDrawPaint);
			mStaticCanvas.drawText("起点", mStartPoint[0] * CELL_WIDTH + LEFT_X, (mStartPoint[1] + 1) * CELL_HEIGHT
					+ TOP_Y, mTextContentPaint);
			mStaticCanvas.drawText("终点", mEndPoint[0] * CELL_WIDTH + LEFT_X, (mEndPoint[1] + 1) * CELL_HEIGHT + TOP_Y,
					mTextContentPaint);
			for (int i = 1; i < COLUMN; i++) {
				mStaticCanvas.drawLine(LEFT_X + i * CELL_WIDTH, TOP_Y, LEFT_X + i * CELL_WIDTH, BOTTOM_Y, mDivierPaint);
			}
			for (int i = 1; i < ROW; i++) {
				mStaticCanvas.drawLine(LEFT_X, TOP_Y + i * CELL_HEIGHT, RIGHT_X, TOP_Y + i * CELL_HEIGHT, mDivierPaint);
			}
			for (int i = 0; i < mMazeMatrix.length; i++) {
				for (int j = 0; j < mMazeMatrix[i].length; j++) {
					if (mMazeMatrix[i][j] == 1) {
						drawWallByIndex(i, j, 1);
					}
				}
			}
		}
		mCanvas.drawBitmap(mStaticBitmap, 0, 0, null);
	}

	/**
	 * 绘制围墙
	 * 
	 * @param canvas
	 *            画布
	 * @param indexX
	 *            矩阵横坐标
	 * @param indexY
	 *            矩阵纵坐标
	 * @param state
	 *            围墙状态,0 为无,1 为有
	 */
	private void drawWall(Canvas canvas, int indexX, int indexY, int state) {
		if (mStaticBitmap != null) {
			if (state == 0) {
				float left = LEFT_X + indexX * CELL_WIDTH + CELL_SPACE;
				float top = TOP_Y + indexY * CELL_HEIGHT + CELL_SPACE;
				float right = LEFT_X + (indexX + 1) * CELL_WIDTH - CELL_SPACE;
				float bottom = TOP_Y + (indexY + 1) * CELL_HEIGHT - CELL_SPACE;
				mStaticCanvas.drawRect(left, top, right, bottom, mClearPaint);
			}
			if (state == 1) {
				float left = LEFT_X + indexX * CELL_WIDTH + CELL_SPACE;
				float top = TOP_Y + indexY * CELL_HEIGHT + CELL_SPACE;
				float right = LEFT_X + (indexX + 1f) * CELL_WIDTH - CELL_SPACE;
				float bottom = TOP_Y + (indexY + 1f) * CELL_HEIGHT - CELL_SPACE;
				// Log.v("test", left + "-" + right + "-" + top + "-" + bottom +
				// "-");
				mStaticCanvas.drawRect(left, top, right, bottom, mDrawPaint);
			}
		}
		canvas.drawBitmap(mStaticBitmap, 0, 0, null);
	}

	/**
	 * 迷宫算法核心内容,寻找下一个位置
	 * 
	 * @param canvas
	 */
	private void drawSpiritNext(Canvas canvas, int code) {
		MazeSpirit nextSpirit;
		switch (code) {
		case 0:
			nextSpirit = findNextSpirit(); // 是否有下一位置
			break;
		case 1:
			nextSpirit = findNextSpiritOptimize();
			break;
		default:
			nextSpirit = findNextSpirit(); // 是否有下一位置
			break;
		}

		// 如果栈不为空
		if (!mSpiritStack.isEmpty()) {
			MazeSpirit topSpirit;
			// 获取当前栈顶位置
			topSpirit = mSpiritStack.peek();
			// 如果该位置是终点,则停止寻找并回调
			if (topSpirit.index[0] == mEndPoint[0] && topSpirit.index[1] == mEndPoint[1]) {
				isRun = false;
				handler.sendEmptyMessage(RESULT_SUCCESS);
			}
			float[] cLocation;
			if (nextSpirit != null) {
				// 获取当前栈顶位置,并绘制栈内路径
				topSpirit = mSpiritStack.peek();
				cLocation = getCenterByIndex(topSpirit.index[0], topSpirit.index[1]);
				mStaticCanvas.drawCircle(cLocation[0], cLocation[1], SPIRIT_SIZE, mPushPaint);
				// 将下一位置入桟,并绘制
				mSpiritStack.push(nextSpirit);
				cLocation = getCenterByIndex(nextSpirit.index[0], nextSpirit.index[1]);
				mStaticCanvas.drawCircle(cLocation[0], cLocation[1], SPIRIT_SIZE, mSpiritPaint);
			} else {
				// 将栈顶元素出栈,并将该位置加入无效列表中
				topSpirit = mSpiritStack.pop();
				mPopList.add(topSpirit);
				cLocation = getCenterByIndex(topSpirit.index[0], topSpirit.index[1]);
				mStaticCanvas.drawCircle(cLocation[0], cLocation[1], SPIRIT_SIZE, mPopPaint);
				if (!mSpiritStack.isEmpty()) {
					// 获取当前栈顶位置,并绘制
					topSpirit = mSpiritStack.peek();
					cLocation = getCenterByIndex(topSpirit.index[0], topSpirit.index[1]);
					mStaticCanvas.drawCircle(cLocation[0], cLocation[1], SPIRIT_SIZE, mSpiritPaint);
				}
			}
		} else {
			isRun = false;
			handler.sendEmptyMessage(RESULT_NO_WAY);
		}
	}

	/**
	 * 使用深度优先搜索,寻找下一个位置,如果有下一位置,返回true并把该位置入桟,否则为false 思想为:
	 * 
	 * 1.取得栈顶位置,变换当前方向,获得该位置当前方向的下一位置
	 * 
	 * 2.如果该方向下一位置合法,则将下一位置入桟,并以下一位置为基础,从初始方向开始寻找下一位置,
	 * 
	 * 3.如果方向下一位置不合法,则按左上右下的位置变换方向
	 * 
	 * 4.如果四个方向已经尝试完,则该位置出栈,加入被抛弃的列表中
	 * 
	 * 5.重复第一步,直到找到终点,或者栈内元素为空,表示没有路径
	 */
	private MazeSpirit findNextSpirit() {

		if (mSpiritStack.isEmpty()) {
			return null;
		}
		// 取得栈顶元素
		MazeSpirit topSpirit = mSpiritStack.peek();
		// 改变栈顶元素方向
		topSpirit.direction++;
		int topX = topSpirit.index[0];
		int topY = topSpirit.index[1];
		MazeSpirit nextSpirit = null;
		// 判断方向,确定写一个位置;
		switch (topSpirit.direction) {
		case MazeSpirit.DIRECTION_LEFT:
			nextSpirit = new MazeSpirit(--topX, topY);
			break;
		case MazeSpirit.DIRECTION_TOP:
			nextSpirit = new MazeSpirit(topX, --topY);
			break;
		case MazeSpirit.DIRECTION_RIGHT:
			nextSpirit = new MazeSpirit(++topX, topY);
			break;
		case MazeSpirit.DIRECTION_BOTTOM:
			nextSpirit = new MazeSpirit(topX, ++topY);
			break;
		case MazeSpirit.DIRECTION_END:
			// 当栈顶元素方向已查找完,返回false
			return null;
		default:
			return null;
		}
		// Log.v("next", nextSpirit.index[0] + "-" + nextSpirit.index[1]);
		// 查找下一位置是否合法
		if (isLegalIndex(nextSpirit.index[0], nextSpirit.index[1])) {
			return nextSpirit;
		} else {
			return findNextSpirit();
		}
	}

	/**
	 * 优化深度优先算法
	 * 
	 * 1.每次获取四个方向的下一位置
	 * 
	 * 2.比较四个方向哪一个最接近终点。
	 * 
	 * 3.每次选择最接近终点的位置
	 * 
	 * 这样可以减少一些不必要的查找,例如:终点在下方,且下方位置有效,但因为上方向的优先高于下方,所以选择了上方向
	 */
	private MazeSpirit findNextSpiritOptimize() {
		if (mSpiritStack.isEmpty()) {
			return null;
		}
		// 取得栈顶元素
		MazeSpirit topSpirit = mSpiritStack.peek();
		MazeSpirit bestSpirit = null; // 保存最优位置
		for (int i = 0; i < topSpirit.directionExpend.length; i++) {
			int topX = topSpirit.index[0];
			int topY = topSpirit.index[1];
			// 如果方向还未被开销
			if (!topSpirit.directionExpend[i]) {
				MazeSpirit nextSpirit;
				switch (i + 1) {
				case MazeSpirit.DIRECTION_LEFT:
					nextSpirit = new MazeSpirit(--topX, topY);
					break;
				case MazeSpirit.DIRECTION_TOP:
					nextSpirit = new MazeSpirit(topX, --topY);
					break;
				case MazeSpirit.DIRECTION_RIGHT:
					nextSpirit = new MazeSpirit(++topX, topY);
					break;
				case MazeSpirit.DIRECTION_BOTTOM:
					nextSpirit = new MazeSpirit(topX, ++topY);
					break;
				default:
					return null;
				}
				nextSpirit.direction = i;
				// 查找下一位置是否合法
				if (!isLegalIndex(nextSpirit.index[0], nextSpirit.index[1])) {
					continue;
				}
				if (bestSpirit == null) {
					bestSpirit = nextSpirit;
				} else {
					// 计算目前最优选择的长度
					int bestPath = (bestSpirit.index[0] - mEndPoint[0]) * (bestSpirit.index[0] - mEndPoint[0])
							+ (bestSpirit.index[1] - mEndPoint[1]) * (bestSpirit.index[1] - mEndPoint[1]);
					// 计算另一位置的长度
					int nextPath = (nextSpirit.index[0] - mEndPoint[0]) * (nextSpirit.index[0] - mEndPoint[0])
							+ (nextSpirit.index[1] - mEndPoint[1]) * (nextSpirit.index[1] - mEndPoint[1]);
					if (nextPath < bestPath) {
						bestSpirit = nextSpirit;
					}
				}
			}
		}
		// 如果找到了最优的下一位置,则当前位置该方向被消耗
		if (bestSpirit != null) {
			topSpirit.directionExpend[bestSpirit.direction] = true;
		}
		return bestSpirit;
	}

	/**
	 * 检查坐标位置是否合法,如果该位置越界、为围墙、栈内已存在,则不合法
	 */
	private boolean isLegalIndex(int indexX, int indexY) {
		if (indexX < 0 || indexY < 0 || indexX > COLUMN - 1 || indexY > ROW - 1 || mMazeMatrix[indexX][indexY] == 1) {
			return false;
		}
		for (int i = 0; i < mSpiritStack.size(); i++) {
			MazeSpirit spirit = mSpiritStack.elementAt(i);
			if (indexX == spirit.index[0] && indexY == spirit.index[1]) {
				return false;
			}
		}
		for (MazeSpirit spirit : mPopList) {
			if (indexX == spirit.index[0] && indexY == spirit.index[1]) {
				return false;
			}
		}
		return true;
	}

	/**
	 * 获取某一位置中心点
	 * 
	 * @param indexX
	 * @param indexY
	 * @return
	 */
	private float[] getCenterByIndex(int indexX, int indexY) {
		float location[] = new float[2];
		location[0] = LEFT_X + indexX * CELL_WIDTH + CELL_WIDTH / 2f;
		location[1] = TOP_Y + indexY * CELL_HEIGHT + CELL_HEIGHT / 2f;
		return location;
	}

	public interface OnFindCompletedListener {
		void onFindComplete(int resultCode);
	}

	public void setOnFindCompletedListener(OnFindCompletedListener onFindCompletedListener) {
		this.onFindCompletedListener = onFindCompletedListener;
	}
}


项目源码,http://download.csdn.net/detail/u010668114/8317595

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值