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

来源:互联网 发布:javascript模块化写法 编辑:程序博客网 时间:2024/05/17 22:10

本人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对象@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);mMazeView = new MazeView(this);setContentView(mMazeView);mMazeView.setOnFindCompletedListener(this);}@Overridepublic 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>@Overridepublic 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:// 当栈顶元素方向已查找完,返回falsereturn 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() {@Overridepublic void handleMessage(Message msg) {onFindCompletedListener.onFindComplete(msg.what);}};@Overridepublic 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;}@Overridepublic 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();}@Overridepublic void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {}@Overridepublic 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 {@Overridepublic 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;}@Overridepublic 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;}@Overridepublic 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:// 当栈顶元素方向已查找完,返回falsereturn 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 0
原创粉丝点击