又经过四天的摸索和学习,对第四个实验“实现迷宫游戏”有了新的认识。
我一开始自己写了一个版本,但是效果并不好,只实现了地图的随机生成和棋子的控制移动。
自己的版本:
程序代码分为两部分,第一部分为地图的生成,以Text类实现;第二部分为棋子类,以Piece类实现。
Text类:
1.初始化数组,以数组为基础建立地图
public int[][] createMazeData() {
mazeData = new int[height][width];// 初始化迷宫,给迷宫添加一圈外墙
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
if (x == 0 || x == width - 1 || y == 0 || y == height - 1) {
mazeData[y][x] = WALL;
} else if (x == 0 && y == 1) {
mazeData[y][x] = ENTRANCE;
} else if (x == width - 1 && y == height - 2) {
mazeData[y][x] = EXIT;
} else
mazeData[y][x] = ROUND;
}
}
division(1, 1, width - 2, height - 2);
// 设置起点和终点
mazeData[1][0] = ENTRANCE;
mazeData[height - 2][width - 1] = EXIT;
return mazeData;
}
2.使用递归分割算法生成随机地图
private void division(int startX, int startY, int endX, int endY) {
Random random = new Random();
// 如果迷宫的宽度或者高度小于2了就不能再分割了
if (endX - startX < 2 || endY - startY < 2)
return;
// x,y只能是偶数
int posX = startX + 1 + random.nextInt((endX - startX) / 2) * 2;// 纵向分割分割线的横坐标
int posY = startY + 1 + random.nextInt((endY - startY) / 2) * 2;// 横向分割线的纵坐标
for (int i = startX; i <= endX; i++) // 横向分割
mazeData[posY][i] = WALL;
for (int i = startY; i <= endY; i++) // 纵向分割
mazeData[i][posX] = WALL;
// 递归
division(startX, startY, posX - 1, posY - 1);// 左下区域
division(startX, posY + 1, posX - 1, endY);// 左上区域
division(posX + 1, posY + 1, endX, endY);// 右上区域
division(posX + 1, startY, endX, posY - 1);// 右下区域
// 随机打开三扇门
switch (random.nextInt(4)) {// 生成随机数0~3
case 0:
openDoor(startX, posY, posX - 1, posY); // 开左边的墙
openDoor(posX, posY + 1, posX, endY); // 开上方的墙
openDoor(posX + 1, posY, endX, posY); // 开右边的墙
break;
case 1:
openDoor(posX, posY + 1, posX, endY); // 开上方的墙
openDoor(posX + 1, posY, endX, posY); // 开右边的墙
openDoor(posX, startY, posX, posY - 1);// 开下面的墙
break;
case 2:
openDoor(posX + 1, posY, endX, posY); // 开右边的墙
openDoor(posX, startY, posX, posY - 1);// 开下面的墙
openDoor(startX, posY, posX - 1, posY); // 开左边的墙
break;
case 3:
openDoor(posX, startY, posX, posY - 1);// 开下面的墙
openDoor(startX, posY, posX - 1, posY); // 开左边的墙
openDoor(posX, posY + 1, posX, endY); // 开上方的墙
break;
}
}
// 在指定的一面墙上开一个随机的门
private void openDoor(int startX, int startY, int endX, int endY) {
Random random = new Random();
int x;// 开门的横坐标
int y;// 开门的纵坐标
// 墙是横着的
if (startY == endY) {
x = startX + random.nextInt((endX - startX) / 2 + 1) * 2;
mazeData[startY][x] = ROUND;
}
// 墙是竖着的
if (startX == endX) {
y = startY + random.nextInt((endY - startY) / 2 + 1) * 2;// 在奇数墙上开门
mazeData[y][startX] = ROUND;
}
}
3.打印迷宫
public static void printMaze(int[][] data) {
for (int i = 0; i < data.length; i++) {
for (int j = 0; j < data[0].length; j++) {
if (data[i][j] == 0) {
System.out.print("[]");
} else if (data[i][j] == 1) {
System.out.print(" “);
} else if (data[i][j] == 2) {
System.out.print(”@");
} else
System.out.print("*");
}
System.out.println();// 换行
}
}
Piece类:
1.控制棋子移动
switch (accept) {
case “w”: // 向上走
if (mazeData[piece1.getX() - 1][piece1.getY()] != 0) {
mazeData[piece1.getX()][piece1.getY()] = 1;
mazeData[piece1.getX() - 1][piece1.getY()] = 2;
piece1.x1 = piece1.getX() - 1;
}
break;
case “s”: //向下走
if (mazeData[piece1.getX() + 1][piece1.getY()] != 0) {
mazeData[piece1.getX()][piece1.getY()] = 1;
mazeData[piece1.getX() + 1][piece1.getY()] = 2;
piece1.x1 = piece1.getX() + 1;
}
break;
case “a”: // 向左走
if (mazeData[piece1.getX()][piece1.getY() - 1] != 0) {
mazeData[piece1.getX()][piece1.getY()] = 1;
mazeData[piece1.getX()][piece1.getY() - 1] = 2;
piece1.y1 = piece1.getY() - 1;
}
break;
case “d”: //向右走
if (mazeData[piece1.getX()][piece1.getY() + 1] != 0) {
mazeData[piece1.getX()][piece1.getY()] = 1;
mazeData[piece1.getX()][piece1.getY() + 1] = 2;
piece1.y1 = piece1.getY() + 1;
}
break;
default:// 违规操作或没路可走不进行任何操作
System.out.println();
break;
}
2.每走一步,画一次地图
for (int i = 0; i < array.length; i++) {
for (int j = 0; j < array[0].length; j++) {
if (array[i][j] == 0) {
System.out.print("[]");
} else if (array[i][j] == 1) {
System.out.print(" “);
} else if (array[i][j] == 2) {
System.out.print(”@");
} else if (array[i][j] == 3) {
System.out.print("*");
}
}
System.out.println();
}
完整代码:
Text类:
package cn.edu.Maze;
import java.util.Random;
/**
- 递归分割生成迷宫
- @author SongErrors
*/
public class Text {
protected static int[][] mazeData;// 迷宫数据
private final int WALL = 0;// 墙
private final int ROUND = 1;// 路
private final int ENTRANCE = 2;// 入口
private final int EXIT = 3;// 出口
protected int width;// 迷宫的宽度
protected int height;// 迷宫的高度
/**
* 生成自定义大小的迷宫
*
* @param width
* @param height
*/
// 构造方法
public Text() {
}
// 构造方法
public Text(int width, int height) {
this.width = width % 2 + 1 == 0 ? width : width + 1;
this.height = height % 2 + 1 == 0 ? height : height + 1;
}
// 方法
public int getWIDTH() {
return width;
}
public int getHEIGHT() {
return height;
}
/**
* 自动生成迷宫
*
* @return
*/
public int[][] createMazeData() {
mazeData = new int[height][width];// 初始化迷宫,给迷宫添加一圈外墙
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
if (x == 0 || x == width - 1 || y == 0 || y == height - 1) {
mazeData[y][x] = WALL;
} else if (x == 0 && y == 1) {
mazeData[y][x] = ENTRANCE;
} else if (x == width - 1 && y == height - 2) {
mazeData[y][x] = EXIT;
} else
mazeData[y][x] = ROUND;
}
}
division(1, 1, width - 2, height - 2);
// 设置起点和终点
mazeData[1][0] = ENTRANCE;
mazeData[height - 2][width - 1] = EXIT;
return mazeData;
}
/**
* 递归分割画迷宫
*
* @param startX:迷宫的起点横坐标
* @param startY:迷宫的起点纵坐标
* @param endX:迷宫的终点横坐标
* @param endY:迷宫的终点纵坐标
*/
private void division(int startX, int startY, int endX, int endY) {
Random random = new Random();
// 如果迷宫的宽度或者高度小于2了就不能再分割了
if (endX - startX < 2 || endY - startY < 2)
return;
// x,y只能是偶数
int posX = startX + 1 + random.nextInt((endX - startX) / 2) * 2;// 纵向分割分割线的横坐标
int posY = startY + 1 + random.nextInt((endY - startY) / 2) * 2;// 横向分割线的纵坐标
for (int i = startX; i <= endX; i++) // 横向分割
mazeData[posY][i] = WALL;
for (int i = startY; i <= endY; i++) // 纵向分割
mazeData[i][posX] = WALL;
// 递归
division(startX, startY, posX - 1, posY - 1);// 左下区域
division(startX, posY + 1, posX - 1, endY);// 左上区域
division(posX + 1, posY + 1, endX, endY);// 右上区域
division(posX + 1, startY, endX, posY - 1);// 右下区域
// 随机打开三扇门
switch (random.nextInt(4)) {// 生成随机数0~3
case 0:
openDoor(startX, posY, posX - 1, posY); // 开左边的墙
openDoor(posX, posY + 1, posX, endY); // 开上方的墙
openDoor(posX + 1, posY, endX, posY); // 开右边的墙
break;
case 1:
openDoor(posX, posY + 1, posX, endY); // 开上方的墙
openDoor(posX + 1, posY, endX, posY); // 开右边的墙
openDoor(posX, startY, posX, posY - 1);// 开下面的墙
break;
case 2:
openDoor(posX + 1, posY, endX, posY); // 开右边的墙
openDoor(posX, startY, posX, posY - 1);// 开下面的墙
openDoor(startX, posY, posX - 1, posY); // 开左边的墙
break;
case 3:
openDoor(posX, startY, posX, posY - 1);// 开下面的墙
openDoor(startX, posY, posX - 1, posY); // 开左边的墙
openDoor(posX, posY + 1, posX, endY); // 开上方的墙
break;
}
}
/**
* 在指定的一面墙上开一个随机的门
*
* @param startX:迷宫开始的横坐标
* @param startY:迷宫开始的纵坐标
* @param endX:迷宫结束的横坐标
* @param endY:迷宫结束的纵坐标
*/
private void openDoor(int startX, int startY, int endX, int endY) {
Random random = new Random();
int x;// 开门的横坐标
int y;// 开门的纵坐标
// 墙是横着的
if (startY == endY) {
x = startX + random.nextInt((endX - startX) / 2 + 1) * 2;
mazeData[startY][x] = ROUND;
}
// 墙是竖着的
if (startX == endX) {
y = startY + random.nextInt((endY - startY) / 2 + 1) * 2;// 在奇数墙上开门
mazeData[y][startX] = ROUND;
}
}
public void setWidth(int width) {
this.width = width % 2 + 1 == 0 ? width : width + 1;
}
public void setHeight(int height) {
this.height = height % 2 + 1 == 0 ? height : height + 1;
}
/**
* 打印迷宫
*
* @param data
*/
public static void printMaze(int[][] data) {
for (int i = 0; i < data.length; i++) {
for (int j = 0; j < data[0].length; j++) {
if (data[i][j] == 0) {
System.out.print("[]");
} else if (data[i][j] == 1) {
System.out.print(" ");
} else if (data[i][j] == 2) {
System.out.print("@");
} else
System.out.print("*");
}
System.out.println();// 换行
}
}
}
Piece类:
package cn.edu.Maze;
import java.util.Scanner;
public class Piece extends Text {
private int x1;
private int y1;
public static int[][] array;
// 构造方法
Piece() {
x1 = 1;
y1 = 0;// array[1][0]代表人物的初始位置
}
// 构造方法
Piece(int x1, int y1) {
this.x1 = x1;
this.y1 = y1;
}
// 方法
public int getX() {
return x1;
}
// 方法
public int getY() {
return y1;
}
// 程序开始
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
System.out.println("Enter the width and height :");
int width = in.nextInt(), height = in.nextInt();
Text maze = new Text(width, height);
Piece piece1 = new Piece();
piece1.printMaze(maze.createMazeData());
array = maze.createMazeData();
while (true) {
Scanner input = new Scanner(System.in);// 创建一个键盘接收器
String accept = input.nextLine();// 接受键盘输入的内容
// 判断人物角色的走向
switch (accept) {
case "w":// 向上走
if (mazeData[piece1.getX() - 1][piece1.getY()] != 0) {
mazeData[piece1.getX()][piece1.getY()] = 1;
mazeData[piece1.getX() - 1][piece1.getY()] = 2;
piece1.x1 = piece1.getX() - 1;
}
break;
case "s":// 向下走
if (mazeData[piece1.getX() + 1][piece1.getY()] != 0) {
mazeData[piece1.getX()][piece1.getY()] = 1;
mazeData[piece1.getX() + 1][piece1.getY()] = 2;
piece1.x1 = piece1.getX() + 1;
}
break;
case "a":// 向左走
if (mazeData[piece1.getX()][piece1.getY() - 1] != 0) {
mazeData[piece1.getX()][piece1.getY()] = 1;
mazeData[piece1.getX()][piece1.getY() - 1] = 2;
piece1.y1 = piece1.getY() - 1;
}
break;
case "d":// 向右走
if (mazeData[piece1.getX()][piece1.getY() + 1] != 0) {
mazeData[piece1.getX()][piece1.getY()] = 1;
mazeData[piece1.getX()][piece1.getY() + 1] = 2;
piece1.y1 = piece1.getY() + 1;
}
break;
default:// 违规操作或没路可走不进行任何操作
System.out.println();
break;
}
for (int i = 0; i < array.length; i++) {
for (int j = 0; j < array[0].length; j++) {
if (array[i][j] == 0) {
System.out.print("[]");
} else if (array[i][j] == 1) {
System.out.print(" ");
} else if (array[i][j] == 2) {
System.out.print("@");
} else if (array[i][j] == 3) {
System.out.print("*");
}
}
System.out.println();
}
}
}
}
之后我又找了一位大佬的资源,基本上能够把代码给搞懂,他的代码一开始没注释,我有些看不懂,我就试着联系了作者,说明了原因,作者第二天就把部分注释给加上了,我又把注释完善了下,以下是完整代码:
package maze;
import java.awt.Color;//绘制颜色的类
import java.awt.Graphics;//绘制图像
import java.awt.event.KeyAdapter;//适配器
import java.awt.event.KeyEvent;//键盘按键
import java.util.Random;//随机生成
import java.util.Stack;//栈操作
import javax.swing.JFrame;//创建图形化框架
import javax.swing.JOptionPane;//编写图形用户界面
import javax.swing.JPanel;//面板
class Lattice {
static final int INTREE = 1;
static final int NOTINTREE = 0;
private int x = -1;
private int y = -1;
private int flag = NOTINTREE;
private Lattice father = null;
// 构造方法
public Lattice(int xx, int yy) {
x = xx;
y = yy;
}
// 方法
public int getX() {
return x;
}
public int getY() {
return y;
}
public int getFlag() {
return flag;
}
public void setFlag(int f) {
flag = f;
}
public Lattice getFather() {
return father;
}
public void setFather(Lattice f) {
father = f;
}
public String toString() {
return new String("(" + x + "," + y + ")\n");
}
}
public class Maze extends JPanel {
private int NUM, width, padding;// NUM 迷宫的大小,width 每个格子的宽度和高度,padding 边框
private Lattice[][] maze;// 创建数组
private int ballX, ballY;//球的位置
private boolean drawPath = false;//标识是否画出路径
// 构造方法
Maze(int m, int w, int p) {
NUM = m;
width = w;
padding = p;
maze = new Lattice[NUM][NUM];//在栈内开辟内存空间
for (int i = 0; i <= NUM - 1; i++)
for (int j = 0; j <= NUM - 1; j++)
maze[i][j] = new Lattice(i, j);
createMaze();//在一个类中,直接使用方法名调用方法
setKeyListener();
this.setFocusable(true);
}
// 方法
//初始化游戏,重开一局时使用
private void init() {
for (int i = 0; i <= NUM - 1; i++)
for (int j = 0; j <= NUM - 1; j++) {
maze[i][j].setFather(null);
maze[i][j].setFlag(Lattice.NOTINTREE);
}
ballX = 0;
ballY = 0;//入口位置
drawPath = false;
createMaze();
// setKeyListener();
this.setFocusable(true);
repaint();
}
// 由格子的行数,得到格子中心点的像素X座标
public int getCenterX(int x) {
return padding + x * width + width / 2;
}
// 由格子的列数,得到格子中心点的像素Y座标
public int getCenterY(int y) {
return padding + y * width + width / 2;
}
//重载
public int getCenterX(Lattice p) {
return padding + p.getY() * width + width / 2;
}
public int getCenterY(Lattice p) {
return padding + p.getX() * width + width / 2;
}
//检查是否赢得了游戏
private void checkIsWin() {
if (ballX == NUM - 1 && ballY == NUM - 1) {
JOptionPane.showMessageDialog(null, "YOU WIN !", "你走出了迷宫。", JOptionPane.PLAIN_MESSAGE);
init();//重新开局
}
}
synchronized private void move(int c) {// synchronized实现同步控制
int tx = ballX, ty = ballY;
// System.out.println(c);
switch (c) {
case KeyEvent.VK_LEFT://左
ty--;
break;
case KeyEvent.VK_RIGHT://右
ty++;
break;
case KeyEvent.VK_UP://上
tx--;
break;
case KeyEvent.VK_DOWN://下
tx++;
break;
case KeyEvent.VK_SPACE://空格建提示正确路径
if (drawPath == true) {
drawPath = false;
} else {
drawPath = true;
}
break;
default:
}
// 若移动后未出界且格子之间有路径,则进行移动,更新小球位置,否则移动非法
if (!isOutOfBorder(tx, ty)
&& (maze[tx][ty].getFather() == maze[ballX][ballY] || maze[ballX][ballY].getFather() == maze[tx][ty])) {
ballX = tx;
ballY = ty;
}
}
private void setKeyListener() {
this.addKeyListener(new KeyAdapter() {
public void keyPressed(KeyEvent e) {
int c = e.getKeyCode();
move(c);
repaint();
checkIsWin();
}
});
}
//方法重载,判断是否出界
private boolean isOutOfBorder(Lattice p) {
return isOutOfBorder(p.getX(), p.getY());
}
//上面的方法调用后会调用此方法
private boolean isOutOfBorder(int x, int y) {
return (x > NUM - 1 || y > NUM - 1 || x < 0 || y < 0) ? true : false;
}
//方法,获取格子的邻居格子
private Lattice[] getNeis(Lattice p) {
final int[] adds = { -1, 0, 1, 0, -1 };// 顺序为上右下左
if (isOutOfBorder(p)) {
return null;
}//如果小球已出界,则返回空值
Lattice[] ps = new Lattice[4];// 四个邻居格子,顺序为上右下左,出界的邻居为null
int xt;
int yt;
for (int i = 0; i <= 3; i++) {
xt = p.getX() + adds[i];
yt = p.getY() + adds[i + 1];
if (isOutOfBorder(xt, yt))
continue;
ps[i] = maze[xt][yt];
}
return ps;
}
//方法,构建随机树,创建迷宫
private void createMaze() {
// 随机选一个格子作为树的根
Random random = new Random();
int rx = Math.abs(random.nextInt()) % NUM;//Math.abs取绝对值
int ry = Math.abs(random.nextInt()) % NUM;
//深度优先遍历
Stack<Lattice> s = new Stack<Lattice>();//创建一个对象s
Lattice p = maze[rx][ry];
Lattice neis[] = null;
s.push(p);//压栈
while (!s.isEmpty()) {
p = s.pop();//出栈
p.setFlag(Lattice.INTREE);
neis = getNeis(p);
int ran = Math.abs(random.nextInt()) % 4;
for (int a = 0; a <= 3; a++) {
ran++;
ran %= 4;
if (neis[ran] == null || neis[ran].getFlag() == Lattice.INTREE) {
continue;
}
s.push(neis[ran]);
neis[ran].setFather(p);
}
}
// changeFather(maze[0][0],null);
}
// 抹掉两个格子之间的边
private void clearFence(int i, int j, int fx, int fy, Graphics g) {
int sx = padding + ((j > fy ? j : fy) * width), sy = padding + ((i > fx ? i : fx) * width),
dx = (i == fx ? sx : sx + width), dy = (i == fx ? sy + width : sy);
if (sx != dx) {
sx++;
dx--;
} else {
sy++;
dy--;
}
g.drawLine(sx, sy, dx, dy);
}
// 画迷宫
protected void paintComponent(Graphics g) {
super.paintComponent(g);
// 画NUM*NUM条黑线
for (int i = 0; i <= NUM; i++) {
g.drawLine(padding + i * width, padding, padding + i * width, padding + NUM * width);
}//画竖线
for (int j = 0; j <= NUM; j++) {
g.drawLine(padding, padding + j * width, padding + NUM * width, padding + j * width);
}//画横线
// 使用背景色,在有路径的格子之间画边,把墙抹掉
g.setColor(this.getBackground());
for (int i = NUM - 1; i >= 0; i--) {
for (int j = NUM - 1; j >= 0; j--) {
Lattice f = maze[i][j].getFather();
if (f != null) {
int fx = f.getX(), fy = f.getY();
clearFence(i, j, fx, fy, g);
}
}
}
// 画左上角的入口
g.drawLine(padding, padding + 1, padding, padding + width - 1);
int last = padding + NUM * width;
// 画右下角出口
g.drawLine(last, last - 1, last, last - width + 1);
// 画小球
g.setColor(Color.RED);
g.fillOval(getCenterX(ballY) - width / 3, getCenterY(ballX) - width / 3, width / 2, width / 2);
if (drawPath == true)
drawPath(g);
}
private void drawPath(Graphics g) {
Color PATH_COLOR = Color.ORANGE, BOTH_PATH_COLOR = Color.PINK;
if (drawPath == true)
g.setColor(PATH_COLOR);//正确路径
else
g.setColor(this.getBackground());//错误路径
Lattice p = maze[NUM - 1][NUM - 1];
while (p.getFather() != null) {
p.setFlag(2);
p = p.getFather();
}
g.fillOval(getCenterX(p) - width / 3, getCenterY(p) - width / 3, width / 2, width / 2);
p = maze[0][0];
while (p.getFather() != null) {
if (p.getFlag() == 2) {
p.setFlag(3);
g.setColor(BOTH_PATH_COLOR);
}
g.drawLine(getCenterX(p), getCenterY(p), getCenterX(p.getFather()), getCenterY(p.getFather()));
p = p.getFather();
}
g.setColor(PATH_COLOR);
p = maze[NUM - 1][NUM - 1];
while (p.getFather() != null) {
if (p.getFlag() == 3)
break;
g.drawLine(getCenterX(p), getCenterY(p), getCenterX(p.getFather()), getCenterY(p.getFather()));
p = p.getFather();
}
}
public static void main(String[] args) {
final int n = 30, width = 600, padding = 20, LX = 200, LY = 100;
JPanel p = new Maze(n, (width - padding - padding) / n, padding);//生成一个对象p
JFrame frame = new JFrame("MAZE(按空格键显示或隐藏路径)");//生成一个对象frame
frame.getContentPane().add(p);//将面板添加到窗口上
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//关闭窗口
frame.setSize(width + padding, width + padding + padding);//窗口大小
frame.setLocation(LX, LY);//窗口生成位置
frame.setVisible(true);//窗口可视
}
}
总结:
这次软件实习共历时约三周半,经过三个实验的洗礼,受到的打击也蛮大的,一想就明白,一写就完蛋,自身的知识深度和广度远远达不到要求,看来努力的道路还很漫长。
对了,补充一下,第二个完整代码的来源是一位大佬的文章,链接如下:
https://www.cnblogs.com/maxuewei2/p/5470157.html