在RPG游戏中,我们常常可以看到怪物很聪明,自己能找到玩家,游戏中有地图,怪物在地图中以特定的算法搜索以找到玩家。今天我们来看看一种基本的图搜索算法——深度优先搜索算法
深度优先搜索算法讲的是
1。从某个点开始,遍历所有与该点相连的点
2。如果找到一个点并且该点没有访问过,则把这个点设为已访问
3。对后面的点执行1,2操作。
4。如果某个点所连接的所有点已被访问过,回溯,直到回溯到某个点所连的点有未访问过的。执行1,2操作。
5。当所有点被访问过,结束搜索。
显然,这是一个递归的过程。递归的结束条件是搜索到达目的地的时候。深度优先搜索是往深里搜的算法,就是一条路走到底,直到无路可走了再回来,回到有岔路的地方并且这条岔路没走过,然后走岔路,以此走下去。直到到达目的地……
昨天心血来潮,撸了一个例子,用View绘图。
效果图:
MainActivity.java
package com.luoye.maze;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.Toast;
public class MainActivity extends Activity {
static Context context;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(1);
setContentView(new MainView(this));
showToast(this,"点击屏幕让小球走迷宫");
context=this;
}
static Toast toast;
public static void showToast(Context context,String text)
{
if(toast==null)
{
toast=Toast.makeText(context,text,Toast.LENGTH_LONG);
}else
{
toast.setText(text);
}
toast.show();
}
}
MainView.java
package com.luoye.maze;
import android.content.*;
import android.os.Handler;
import android.os.Message;
import android.view.*;
import android.graphics.*;
/**
*主视图类
*落叶似秋编写
*2016-3-11
*/
public class MainView extends View implements Runnable
{
boolean isFirst=true;//标记是否是第一次走完
final int SLEEP=10;
Paint wallPaint;//墙画笔
Paint manPaint;//人画笔
boolean isDraw=false;//标记是否刷新画布
boolean canTouch=true;//标记是否可以触摸屏幕
Maze maze;
int circleX,circleY;//实时记录人的位置
byte[][] data;//迷宫数据矩阵
Rect rect;
final int[][] direct={
{-1,0},//左
{0,1},//上
{1,0},//右
{0,-1}//下
};//方向,相对于当前位置的偏移
public MainView(Context context)
{
super(context);
wallPaint = new Paint();
wallPaint.setColor(Color.BLACK);
wallPaint.setAntiAlias(true);
wallPaint.setDither(true);
manPaint = new Paint();
manPaint.setColor(Color.RED);
manPaint.setAntiAlias(true);
manPaint.setDither(true);
maze = new Maze(0, 0, 35, 61);//迷宫长和宽都必须为奇数。
init();
}
private void init()
{
data = maze.generate().clone();//生成迷宫
circleX = (2) * 20 + maze.mx;
circleY = (1) * 20 + maze.my;
}
@Override
protected void onDraw(Canvas canvas)
{
// TODO: Implement this method
canvas.drawARGB(255, 255, 255, 255);
maze.draw(canvas, wallPaint);
drawMan(canvas, circleX, circleY);//这句的位置很重要
}
Handler handler=new Handler() {
@Override
public void handleMessage(Message msg)
{
if (msg.what == 0)
{
if (isDraw)
{
invalidate();//刷新画布
}
else
{
if (isFirst)
{
isFirst = false;
MainActivity.showToast(MainActivity.context, "找到出口了");
}
}
}
else if (msg.what == 1)
{
MainActivity.showToast(MainActivity.context, "点击屏幕重新开始");
}
}
};
@Override
public void run()
{
// TODO: Implement this method
//从开始位置搜索
dfs(2, 1);
canTouch = true;
handler.sendEmptyMessage(1);
}
/**
* 画人
* @param canvas
* @param x
* @param y
*/
private void drawMan(Canvas canvas, int x, int y)
{
canvas.drawCircle(x + 10, y + 10, 10, manPaint);
}
/**
* 深度优先搜索出口
* @param x
* @param y
*/
private void dfs(int x, int y)
{
int offsetX=0;
int offsetY=0;
//标记为访问过
data[x][y] = Maze.VISITED;
//每走到一个点就遍历四个方向
for (int i=0;i < 4;i++)
{
if (isDraw == false)
return;
offsetX = direct[i][0];
offsetY = direct[i][1];
if (data[x + offsetX][y + offsetY] == Maze.SPACE)
{
try
{
Thread.sleep(SLEEP);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
circleX = (x + offsetX) * 20 + maze.mx;
circleY = (y + offsetY) * 20 + maze.my;
//通知刷新画布
handler.sendEmptyMessage(0);
//深度优先搜索下一位置**************
dfs(x + offsetX, y + offsetY);
//回溯--------------------------
try
{
Thread.sleep(SLEEP);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
circleX = (x + offsetX) * 20 + maze.mx;
circleY = (y + offsetY) * 20 + maze.my;
//通知刷新画布
handler.sendEmptyMessage(0);
}
}
//如果到达终点
if (x == maze.width - 3 && y == maze.height - 2)
{
handler.sendEmptyMessage(0);
isDraw = false;
isFirst = true;
return;
}
try
{
Thread.sleep(SLEEP);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
//通知刷新画布
handler.sendEmptyMessage(0);
}
/**
* 屏幕点击事件
* @param event
* @return
*/
@Override
public boolean onTouchEvent(MotionEvent event)
{
if (event.getAction() == MotionEvent.ACTION_DOWN)
{
if (canTouch)
{
canTouch = false;
if (!isFirst)
{
data = maze.generate().clone();
}
circleX = (2) * 20 + maze.mx;
circleY = (1) * 20 + maze.my;
isFirst = false;
new Thread(this).start();
isDraw = true;
}
}
return super.onTouchEvent(event);
}
}
Maze.java
package com.luoye.maze;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.Toast;
public class MainActivity extends Activity {
static Context context;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(1);
setContentView(new MainView(this));
showToast(this,"点击屏幕让小球走迷宫");
context=this;
}
static Toast toast;
public static void showToast(Context context,String text)
{
if(toast==null)
{
toast=Toast.makeText(context,text,Toast.LENGTH_LONG);
}else
{
toast.setText(text);
}
toast.show();
}
}
package com.luoye.maze;
import android.content.*;
import android.os.Handler;
import android.os.Message;
import android.view.*;
import android.graphics.*;
/**
*主视图类
*落叶似秋编写
*2016-3-11
*/
public class MainView extends View implements Runnable
{
boolean isFirst=true;//标记是否是第一次走完
final int SLEEP=10;
Paint wallPaint;//墙画笔
Paint manPaint;//人画笔
boolean isDraw=false;//标记是否刷新画布
boolean canTouch=true;//标记是否可以触摸屏幕
Maze maze;
int circleX,circleY;//实时记录人的位置
byte[][] data;//迷宫数据矩阵
Rect rect;
final int[][] direct={
{-1,0},//左
{0,1},//上
{1,0},//右
{0,-1}//下
};//方向,相对于当前位置的偏移
public MainView(Context context)
{
super(context);
wallPaint = new Paint();
wallPaint.setColor(Color.BLACK);
wallPaint.setAntiAlias(true);
wallPaint.setDither(true);
manPaint = new Paint();
manPaint.setColor(Color.RED);
manPaint.setAntiAlias(true);
manPaint.setDither(true);
maze = new Maze(0, 0, 35, 61);//迷宫长和宽都必须为奇数。
init();
}
private void init()
{
data = maze.generate().clone();//生成迷宫
circleX = (2) * 20 + maze.mx;
circleY = (1) * 20 + maze.my;
}
@Override
protected void onDraw(Canvas canvas)
{
// TODO: Implement this method
canvas.drawARGB(255, 255, 255, 255);
maze.draw(canvas, wallPaint);
drawMan(canvas, circleX, circleY);//这句的位置很重要
}
Handler handler=new Handler() {
@Override
public void handleMessage(Message msg)
{
if (msg.what == 0)
{
if (isDraw)
{
invalidate();//刷新画布
}
else
{
if (isFirst)
{
isFirst = false;
MainActivity.showToast(MainActivity.context, "找到出口了");
}
}
}
else if (msg.what == 1)
{
MainActivity.showToast(MainActivity.context, "点击屏幕重新开始");
}
}
};
@Override
public void run()
{
// TODO: Implement this method
//从开始位置搜索
dfs(2, 1);
canTouch = true;
handler.sendEmptyMessage(1);
}
/**
* 画人
* @param canvas
* @param x
* @param y
*/
private void drawMan(Canvas canvas, int x, int y)
{
canvas.drawCircle(x + 10, y + 10, 10, manPaint);
}
/**
* 深度优先搜索出口
* @param x
* @param y
*/
private void dfs(int x, int y)
{
int offsetX=0;
int offsetY=0;
//标记为访问过
data[x][y] = Maze.VISITED;
//每走到一个点就遍历四个方向
for (int i=0;i < 4;i++)
{
if (isDraw == false)
return;
offsetX = direct[i][0];
offsetY = direct[i][1];
if (data[x + offsetX][y + offsetY] == Maze.SPACE)
{
try
{
Thread.sleep(SLEEP);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
circleX = (x + offsetX) * 20 + maze.mx;
circleY = (y + offsetY) * 20 + maze.my;
//通知刷新画布
handler.sendEmptyMessage(0);
//深度优先搜索下一位置**************
dfs(x + offsetX, y + offsetY);
//回溯--------------------------
try
{
Thread.sleep(SLEEP);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
circleX = (x + offsetX) * 20 + maze.mx;
circleY = (y + offsetY) * 20 + maze.my;
//通知刷新画布
handler.sendEmptyMessage(0);
}
}
//如果到达终点
if (x == maze.width - 3 && y == maze.height - 2)
{
handler.sendEmptyMessage(0);
isDraw = false;
isFirst = true;
return;
}
try
{
Thread.sleep(SLEEP);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
//通知刷新画布
handler.sendEmptyMessage(0);
}
/**
* 屏幕点击事件
* @param event
* @return
*/
@Override
public boolean onTouchEvent(MotionEvent event)
{
if (event.getAction() == MotionEvent.ACTION_DOWN)
{
if (canTouch)
{
canTouch = false;
if (!isFirst)
{
data = maze.generate().clone();
}
circleX = (2) * 20 + maze.mx;
circleY = (1) * 20 + maze.my;
isFirst = false;
new Thread(this).start();
isDraw = true;
}
}
return super.onTouchEvent(event);
}
}
package com.luoye.dfsmaze;
import java.util.*;
import android.graphics.*;
/*
*迷宫类
*/
public class Maze
{
public static final int WALL = 0;//墙
public static final int SPACE = 1;//空
public static final int VISITED=2;//标记已访问过
public static final int MOVE=3;//迷宫中可以动的物体
public static final int EMPTY=4;
private byte[][] data;//数据
public int width;//迷宫宽
public int height;//迷宫高
private Random rand = new Random();
public int mx,my;
final int[] upx = { 1, -1, 0, 0 };//分别代表了左右上下4个方向
final int[] upy = { 0, 0, 1, -1 };
public Maze(int mx,int my,int width, int height)
{
this.width = width;
this.height = height;
data = new byte[width][];
this.mx=mx;
this.my=my;
}
/**
*打洞
*从入口打洞,随机选择一个方向,如果那个方向前面两格都是墙,则打通
*这里也是用了深度优先搜索算法
*
*/
private void carve(int x, int y)
{
int dir = rand.nextInt(4);//0~3
int count = 0;
while (count < 4)
{
final int x1 = x + upx[dir];
final int y1 = y + upy[dir];
final int x2 = x1 + upx[dir];//加强,与x1同个方向
final int y2 = y1 + upy[dir];//加强,与y1同一个方向
if (data[x1][y1] == WALL && data[x2][y2] == WALL)//如果为都为墙。表示打中空格
{
data[x1][y1] = SPACE;
data[x2][y2] = SPACE;
carve(x2, y2);//条件符合,开挖
}
else
{
dir = (dir + 1) % 4;//产生一个不同与dir的,又在0~3范围内的数(即产生下一个方向)
count += 1;
}
}
}
/**
*生成
*没什么好说的
*/
public byte[][] generate()
{
//都填为墙
for (int x = 0; x < width; x++)
{
data[x] = new byte[height];
for (int y = 0; y < height; y++)
{
data[x][y] = WALL;
}
}
//第一行和最后一行清空
for (int x = 0; x < width; x++)
{
data[x][0] = EMPTY;
data[x][height - 1] = EMPTY;
}
//第一列和最后列填充为空
for (int y = 0; y < height; y++)
{
data[0][y] = EMPTY;
data[width - 1][y] = EMPTY;
}
//入口下来那一格打空
data[2][2] = SPACE;
//开始打洞
carve(2, 2);
//开入口
data[2][1] = SPACE;
//开出口
data[width - 3][height - 2] = SPACE;
return data;
}
/**
*画迷宫
*Paint传进来就可以自定义方块颜色
*/
public void draw(Canvas canvas,Paint paint)
{
int x1=mx,y1=my;
final int rectW=20,rectH=20;
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
if (data[x][y] == WALL)
{
canvas.drawRect(x1,y1,x1+rectW,y1+rectH,paint);
x1=x1+rectW;
}
else
{
//如果是空白,直接不画,右移一格就行
x1=x1+rectW;
}
}
y1=y1+rectH;//下一行
x1=mx;//定位到一行开始
}
}
}
例子下载地址: https://github.com/luoyesiqiu/MazeGame