本人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;
}
}