提取碼:vz77
本節我們來完成一個很有意思的事情那就是走迷宮關於這個問題一個迷宮我們把其抽象爲一個圖,那麼走迷宮無非就是遍歷,我們知道圖的遍歷無非兩種形式廣度優先遍歷和深度優先遍歷以及深度優先遍歷的非遞歸方式,走迷宮我們要採用四聯通區域這樣一個圖形學思想無非就是對一個單元的左上右下四個方向進行搜索,深度和廣度優先遍歷搜索的順序不同但最後都會給我們一個解
我們迷宮的構造的要素無非就是路(road)和牆(wall),用上述我們所說的方法進行搜索終止條件無非就是當遇到出口時終止,我們的迷宮都是有解的所以我們不考慮無解的情況
還有一個要點就是我們在搜索迷宮的過程中我們訪問過的單元不能被再次訪問這樣我們保證路徑回溯的不重複性
數據層
本節我們採用讀的方式構建迷宮我們事先準備好一個迷宮的txt用#表示牆用空格來代表路我們txt的第一行是我們的迷宮的行和列
1.首先我們需要一個char maze[][] 來保存我們的迷宮數據,我們需要一個boolean visited[][] 來記錄我們是否訪問過座標對應單元,boolean[][] path用來記錄當前路徑和最後的解
2.int[][] distance={{0,-1},{1,0},{0,1},{-1,0}} 用來表示上下右左的四個方向
3.int entranceX,entranceY用來表示迷宮入口
4.int exitX,exitY用來表示迷宮出口
···package com.lipengge.dfsmaze.data;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
public class MazeData {
private char[][] maze;
private int entranceX,entranceY;
private int exitX,exitY;
public boolean[][] path;
public boolean[][] visited;
public int[][] distance={{0,-1},{1,0},{0,1},{-1,0}};
public int getEntranceX() {
return entranceX;
}
public void setEntranceX(int entranceX) {
this.entranceX = entranceX;
}
public int getEntranceY() {
return entranceY;
}
public void setEntranceY(int entranceY) {
this.entranceY = entranceY;
}
public int getExitX() {
return exitX;
}
public void setExitX(int exitX) {
this.exitX = exitX;
}
public int getExitY() {
return exitY;
}
public void setExitY(int exitY) {
this.exitY = exitY;
}
public MazeData(String fileName) {
readFile(fileName);
}
public int getMazeL(){
return maze.length;
}
public int getMazeW(){
return maze[getMazeL()-1].length;
}
public char getMazeElement(int x,int y){
if(isIllegal(x,y)){
throw new IllegalArgumentException("數組越界");
}
return maze[x][y];
}
public boolean isIllegal(int x,int y){
return !(x>=0&&x<getMazeL()&&y>=0&&y<getMazeW());
}
public void readFile(String fileName){
BufferedReader buf=null;
try{
File file=new File(fileName);
if(!file.exists()){
throw new IllegalArgumentException("文件不存在");
}
buf=new BufferedReader(new InputStreamReader(new BufferedInputStream(new FileInputStream(file))));
String[] num=buf.readLine().split(" ");
int r=Integer.parseInt(num[0]);
int c=Integer.parseInt(num[1]);
maze=new char[r][c];
path=new boolean[r][c];
visited=new boolean[r][c];
for(int i=0;i<r;i++){
maze[i]=buf.readLine().toCharArray();
}
}catch(Exception e){
e.printStackTrace();
}finally{
if( buf!=null){
try {
buf.close();
} catch (IOException e) {
}
}
}
}
public void print(){
System.out.println(getMazeL()+" "+getMazeW());
for(int i=0;i<getMazeL();i++){
for(int j=0;j<getMazeW();j++){
System.out.print(getMazeElement(i, j));
}
System.out.println();
}
}
}
視圖層
我們視圖層一如既往的簡單只需要改變繪製規則
1.int w=canvasWidth/data.getMazeL();畫布的寬/數組的列數 表示窗體一行被分爲多少份
2.int h=canvasHeight/data.getMazeW();畫布的高/數組的行 表示窗體一列被分爲多少份
3.當我們的path[x][y]爲真時表示當前正在探索的路爲藍色
4.不然如果maze[x][y]爲WALL上黑色爲ROAD上白色
```package com.lipengge.dfsmaze.view;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import com.lipengge.dfsmaze.data.MazeData;
import com.lipengge.dfsmaze.util.Constant;
import com.lipengge.randomquestion.util.AlgoVisHelper;
public class AlgoFrame extends JFrame{
private static final long serialVersionUID = -3035088527551930125L;
private int canvasWidth;
private int canvasHeight;
private MazeData data;
public int getCanvasWidth() {
return canvasWidth;
}
public int getCanvasHeight() {
return canvasHeight;
}
public void render(MazeData data){
this.data=data;
repaint();
}
public AlgoFrame(String title,int canvasWidth, int canvasHeight){
super(title);
this.canvasWidth=canvasWidth;
this.canvasHeight=canvasHeight;
setDefaultCloseOperation(EXIT_ON_CLOSE);
setResizable(false);
setVisible(true);
AlgoCanvas algoCanvas = new AlgoCanvas();
setContentPane(algoCanvas);
pack();
}
class AlgoCanvas extends JPanel{
private static final long serialVersionUID = -6056196800598676936L;
private AlgoCanvas(){
super(true);
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d=(Graphics2D) g;
AlgoVisHelper.setRenderingHints(g2d);
AlgoVisHelper.setStrokeWidth(g2d, 1);
int w=canvasWidth/data.getMazeL();
int h=canvasHeight/data.getMazeW();
for(int i=0;i<data.getMazeL();i++){
for(int j=0;j<data.getMazeW();j++){
if(data.path[i][j]){
g2d.setColor(Color.BLUE);
}else if(data.getMazeElement(i, j)==Constant.WALL){
g2d.setColor(Color.BLACK);
}else if(data.getMazeElement(i, j)==Constant.ROAD){
g2d.setColor(Color.WHITE);
}
AlgoVisHelper.fillReactangle(g2d,w*j,h*i,w,h);
}
}
}
@Override
public Dimension getPreferredSize() {
return new Dimension(canvasWidth,canvasHeight);
}
}
}
控制層
1.對於我們控制層來說我們只需要傳入要讀取的迷宮文件傳給數據層就可以了,之後就是設置畫布的寬和高我們用迷宮數組的行乘10像素表示高,列乘10像素表示寬
2.這裏我們在一個setData方法裏設置我們的出口並且如果座標合法給path[x][y]賦值
3.重點在我們的go函數裏面傳入入口座標我們採用遞歸的深度優先遍歷我們的座標合法並且沒有被訪問過的話那麼我們給訪問標記置true,判斷如果是出口我們就返回一個true,不然我們將按照四個方向對座標合法,沒有被訪問過並且是ROAD的路徑進行遞歸如果遞歸函數返回true說明座標已經找到返回true否則繼續遞歸如果遇見已經訪問過的返回false
package com.lipengge.dfsmaze.controller;
import java.awt.EventQueue;
import com.lipengge.dfsmaze.data.MazeData;
import com.lipengge.dfsmaze.util.Constant;
import com.lipengge.dfsmaze.view.AlgoFrame;
import com.lipengge.randomquestion.util.AlgoVisHelper;
public class AlgoVisualizer {
private AlgoFrame frame;
private MazeData data;
private int screenWidth;
private int screenHeight;
public AlgoVisualizer(String fileName){
data=new MazeData(fileName);
this.screenWidth=data.getMazeL()*Constant.BLOCK;
this.screenHeight=data.getMazeW()*Constant.BLOCK;
EventQueue.invokeLater(()->{
frame=new AlgoFrame("maze",screenWidth,screenHeight);
new Thread(()->run()).start();
});
}
public void run(){
setData(-1,-1,false);
go(1,0);
setData(-1,-1,false);
}
private void setData(int x,int y,boolean isPath) {
data.setExitX(data.getMazeL()-2);
data.setExitY(data.getMazeW()-1);
if(!data.isIllegal(x, y)){
data.path[x][y]=isPath;
}
frame.render(data);
AlgoVisHelper.pause(0);
}
public boolean go(int x,int y){
if(data.isIllegal(x, y)){
throw new IllegalArgumentException("數組越界");
}
setData(x,y,true);
data.visited[x][y]=true;
if(data.getExitX()==x&&data.getExitY()==y){
return true;
}
for(int i=0;i<4;i++){
int newX=data.distance[i][0]+x;
int newY=data.distance[i][1]+y;
if(!data.isIllegal(newX,newY)&&data.getMazeElement(newX,newY)==Constant.ROAD&&!data.visited[newX][newY]){
if(go(newX,newY)){
return true;
}
}
}
setData(x,y,false);
return false;
}
public static void main(String[] args) {
new AlgoVisualizer("maze_101_101.txt");
}
}
常量類
package com.lipengge.dfsmaze.util;
public class Constant {
public static final int BLOCK=10;
public static final char WALL='#';
public static final char ROAD=' ';
}
繪製幫助類
package com.lipengge.minesweeper.util;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Rectangle2D;
import javax.swing.ImageIcon;
public class AlgoVisHelper {
private AlgoVisHelper(){}
public static void strokeCircle(Graphics2D g,int x,int y,int r){
Ellipse2D circle = new Ellipse2D.Double(x-r,y-r,2*r,2*r);
g.draw(circle);
}
public static void fillCircle(Graphics2D g,int x,int y,int r){
Ellipse2D circle = new Ellipse2D.Double(x-r,y-r,2*r,2*r);
g.fill(circle);
}
public static void strokeReactangle(Graphics2D g,int x,int y,int width,int height){
Rectangle2D reactangle = new Rectangle2D.Double(x,y,width,height);
g.draw(reactangle);
}
public static void fillReactangle(Graphics2D g,int x,int y,int width,int height){
Rectangle2D reactangle = new Rectangle2D.Double(x,y,width,height);
g.fill(reactangle);
}
public static void setColor(Graphics2D g,Color color){
g.setColor(color);
}
public static void setStrokeWidth(Graphics2D g,int w){
g.setStroke(new BasicStroke(w,BasicStroke.CAP_ROUND,BasicStroke.JOIN_ROUND));
}
public static void setRenderingHints(Graphics2D g){
RenderingHints hints = new RenderingHints(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
hints.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g.addRenderingHints(hints);
}
public static void pause(long millsecond){
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void drawImage(Graphics2D g,String imageUrl,int x,int y,int x1,int y1){
ImageIcon imageIcon=new ImageIcon(imageUrl);
Image image=imageIcon.getImage();
g.drawImage(image,x, y,x1,y1,null);
}
}
效果圖
最終效果
下節我們將採用非遞歸的深度優先遍歷