迷宫小游戏Java实现

迷宫小游戏


一、项目概述

(1)项目目标和主要内容
<1>迷宫游戏是非常经典的游戏,在该题中要求随机生成一个迷宫,并求解迷宫;
<2>要求查找并理解迷宫生成的算法,并尝试用两种不同的算法来生成随机的迷宫。
<3>要求游戏支持玩家走迷宫,和系统走迷宫路径两种模式。玩家走迷宫,通过键盘 方向键控制,并在行走路径上留下痕迹;系统走迷宫路径要求基于 A*算法实现,输出走迷宫的最优路径并显示。设计交互友好的游戏图形界面。
(2)项目的主要功能
<1>迷宫游戏可以锻炼我们对数据结构中图的更好的理解
<2>迷宫游戏可以通过键盘控制方向,输出走的方向。

二、项目初步设计

1.项目总体框架

在这里插入图片描述

2.类关系图

在这里插入图片描述

3.程序流程图

在这里插入图片描述

三、实验准备

1.迷宫生成算法了解

Depth First Search Algorithm(深度优先搜索算法)
将起点作为当前迷宫单元并标记为已访问
当还存在未标记的迷宫单元,进行循环
如果当前迷宫单元有未被访问过的的相邻的迷宫单元
随机选择一个未访问的相邻迷宫单元
将当前迷宫单元入栈
移除当前迷宫单元与相邻迷宫单元的墙
标记相邻迷宫单元并用它作为当前迷宫单元
如果当前迷宫单元不存在未访问的相邻迷宫单元,并且栈不空
栈顶的迷宫单元出栈
令其成为当前迷宫单元
Depth First Search Algorithm生成的迷宫极度扭曲,有着一条明显的主路。
Randomized Prim’s Algorithm(随机普利姆算法)
让迷宫全是墙.
随机选一个单元格作为迷宫的通路,然后把它的邻墙放入列表
当列表里还有墙时
从列表里随机选一个墙,如果这面墙分隔的两个单元格只有一个单元格被访问过
那就从列表里移除这面墙,即把墙打通,让未访问的单元格成为迷宫的通路
把这个格子的墙加入列表
如果墙两面的单元格都已经被访问过,那就从列表里移除这面墙
相对于深度优先的算法,Randomized Prim’s Algorithm不是优先选择最近选中的单元格,而是随机的从所有的列表中的单元格进行选择,新加入的单元格和旧加入的单元格同样概率会被选择,新加入的单元格没有优先权。因此其分支更多,生成的迷宫更复杂,岔路更多,难度更大,也更自然。
Recursive Division Algorithm(递归分割算法)
让迷宫全是迷宫单元
随机选择一偶数行和一偶数列让其全部变为墙,通过这两堵墙将整个迷宫分为四个子迷宫
在3面墙上各挖一个洞(为了确保连通)
如果子迷宫仍可分割成四个子迷宫,返回继续分割子迷宫
Recursive Division Algorithm十分高效,生成的迷宫较为简单,有点像四叉树,直路多且不扭曲。

本实验我采用了深度优先搜索算法生成迷宫,所以生成的迷宫看似复杂,其实走最靠边的一条路肯定是正确的。。。

2.了解A*寻路算法

找了两篇文章,链接1 链接2
算法有点复杂,我优先考虑使用二叉树的深度优先遍历的逆推方法。

3.学习二叉树的三种深度优先遍历

我找了两篇文章,虽然文章1是爬虫的教程,但算法讲的很清楚,文章2是java的教程。
文章1 文章2
还有实现队列的讲解:java实现队列
看完之后,总结一下,这个算法一共有四个步骤。
首先实现一颗二叉树,要对二叉树进行遍历,需要先创建一棵二叉树,然后进行先序遍历,中序遍历,后序遍历。详细代码在下一部分。

4.学习使用Swing组件

JFrame组件、JPanel组件、JOptionPane组件是我们这个实验需要使用的。详细教程链接在下
JFrame组件
Jpanel组件
JOptionPane组件

5.类划分

我把该项目的实现分为以下八类
在这里插入图片描述
分别控制迷宫小球的创建,迷宫的大小控制及建造,按钮功能模块按钮功能模块,生成迷宫算法,键位控制,迷宫主界面模块迷宫主界面模块,时间和步数的检测,找到出口算法,屏幕刷新率控制。

四、具体代码(代码后有功能解释)

Ball类:

package maze;


import java.awt.Color;

class Ball {
   
	private int x;//横坐标
	private int y;//纵坐标
	private Color color;//小球颜色
	public Ball(int x,int y) {
   
		setX(x);
		setY(y);
		setColor(Color.GREEN);
	}

	public int getX() {
   
		return x;
	}

	public void setX(int x) {
   
		this.x = x;
	}

	public int getY() {
   
		return y;
	}

	public int setY(int y) {
   
		this.y = y;
		return y;
	}

	public Color getColor() {
   
		return color;
	}

	public void setColor(Color color) {
   
		this.color = color;
	}
	
}

Creatmaze类:

package maze;

import java.awt.Point;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.Scanner;
import java.util.Stack;


abstract class AbstractCreateMaze {
   
	// 验证横纵坐标是否超界
	protected boolean isOutofBorder(int x, int y, int colNumber, int rowNumber) {
   
		if ((x == 0 && y == 1) || (x == colNumber + 1 && y == rowNumber))
			return false;
		else
			return (x > colNumber || y > rowNumber || x < 1 || y < 1) ? true : false;
	}

	abstract void createMaze(Lattice[][] mazeLattice, int colNumber, int rowNumber);
}

class DepthFirstSearchCreateMaze extends AbstractCreateMaze {
   

	// 随机选择一个p点未访问的相邻迷宫单元 ,并移除两者之间的墙,为createMaze()函数调用
	protected Point ArroundPoint(Lattice[][] mazeLattice, Point p, Stack<Point> s, Random rand, int colNumber,
			int rowNumber) {
   
		final int[] arroundPoint = {
    -2, 0, 2, 0, -2 };// 一个点周围四个点的坐标变化,顺序为左上右下
		int r = rand.nextInt(4);
		for (int i = 0; i < 4; ++i) {
   
			int j = r % 4;
			int x = p.x + arroundPoint[j];
			int y = p.y + arroundPoint[j + 1];
			++r;
			if (!isOutofBorder(x, y, colNumber, rowNumber) && !mazeLattice[y][x].isPassable()) {
   
				mazeLattice[y][x].setPassable(true);
				mazeLattice[p.y + arroundPoint[j + 1] / 2][p.x + arroundPoint[j] / 2].setPassable(true);
				return new Point(x, y);
			}
		}
		return null;
	}

	@Override
	public void createMaze(Lattice[][] mazeLattice, int colNumber, int rowNumber) {
   
		// TODO Auto-generated method stub
		Random rand = new Random();
		Point currentPoint = new Point(2 * rand.nextInt(colNumber / 2) + 1, 2 * rand.nextInt(rowNumber / 2) + 1);
		mazeLattice[currentPoint.y][currentPoint.x].setPassable(true);
		Stack<Point> pathStack = new Stack<Point>();
		pathStack.push(currentPoint);
		currentPoint = ArroundPoint(mazeLattice, currentPoint, pathStack, rand, colNumber, rowNumber);
		while (true) {
   
			Point p = ArroundPoint(mazeLattice, currentPoint, pathStack, rand, colNumber, rowNumber);
			if (p != null) {
   
				pathStack.push(currentPoint);
				currentPoint = p;
			} else if (!pathStack.isEmpty())
				currentPoint = pathStack.pop();
			else
				break;
		}
	}
}

class RandomizedPrimCreateMaze extends AbstractCreateMaze {
   

	// 将点p的邻墙放入列表中
	protected void pushArroundWallToList(Lattice[][] mazeLattice, Point p, List<Point> list, int colNumber,
			int rowNumber) {
   
		final int[] arroundWall = {
    -1, 0, 1, 0, -1 };// 一个点周围四个墙的坐标变化,顺序为左上右下
		for (int i = 0; i < 4;) {
   
			int x = p.x + arroundWall[i];
			int y = p.y + arroundWall[++i];
			if (!isOutofBorder(x, y, colNumber, rowNumber) && !mazeLattice[y][x].isPassable()) {
   
				list.add(new Point(x, y));
			}
		}
	}

	// 找到墙wall未被访问过的点,如果没有返回Null
	protected Point findPoint(Point wall, Lattice[][] mazeLattice) {
   
		final int[] arroundWall = {
    -1, 0, 1, 0, -1 };// 顺序为左上右下
		Point p = null;
		for (int i = (wall.y + 1) % 2; i < 2; i += 2) {
   
			boolean add = mazeLattice[wall.y + arroundWall[i + 1]][wall.x + arroundWall[i]].isPassable(),
					sub = mazeLattice[wall.y - arroundWall[i + 1]][wall.x - arroundWall[i]].isPassable();
			if (add && !sub) {
   
				p = new Point(wall.x - arroundWall[i], wall.y - arroundWall[i + 1]);
				break;
			}
			if (!add && sub) {
   
				p = new Point(wall.x + arroundWall[i], wall.y + arroundWall[i + 1]);
				break;
			}
		}
		return p;
	}

	@Override
	public void createMaze(Lattice[][] mazeLattice, int colNumber, int rowNumber) {
   
		// TODO Auto-generated method stub
		Random rand = new Random();
		Point currentPoint = new Point(2 * rand.nextInt(colNumber / 2) + 1, 2 * rand.nextInt(rowNumber / 2) + 1);
		mazeLattice[currentPoint.y][currentPoint.x].setPassable(true);
		List<Point> listWall = new LinkedList<Point>();
		pushArroundWallToList(mazeLattice, currentPoint, listWall, colNumber, rowNumber);
		while (!listWall.isEmpty()) {
   
			int k = rand.nextInt(listWall.size());
			Point wall = listWall.remove(k);
			currentPoint = findPoint(wall, mazeLattice);
			if (currentPoint != null) {
   
				mazeLattice[wall.y][wall.x].setPassable(true);
				mazeLattice[currentPoint.y][currentPoint.x].setPassable(true);
				pushArroundWallToList(mazeLattice, currentPoint, listWall, colNumber, rowNumber);
			}
		}
	}

}

class RecursiveDivisionCreateMaze extends AbstractCreateMaze {
   

	// 随机在给定墙壁上开一个门
	protected void openAdoor(Lattice[][] mazeLattice, Point p1, Point p2, Random r) {
   
		if(p1.y==p2.y&&p1.x==p2.x){
   
			mazeLattice[p1.y][p1.x].setPassable(true);
			return;
		}
		if (p1.y == p2.y) {
   
			int pos = p1.x + r.nextInt((p2.x - p1.x) / 2 ) * 2;// 在奇数位置开门
			mazeLattice[p1.y][pos].setPassable(true);
		} else if (p1.x == p2.x) {
   
			int pos = p1.y + r.nextInt((p2.y - p1.y) / 2 ) * 2;
			mazeLattice[pos][p1.x].setPassable(true);
		}
	}

	// 递归生成迷宫
	private void recursiveCreateMaze(Lattice[][] mazeLattice, Point start, int height, int width, Random rand) {
   
		if (height <= 2 || width <= 2)
			return;
		// 在偶数行建立一条墙壁
		int drawx = start.y + rand.nextInt(height / 2) * 2 + 1;
		for (int i = start.x; i < start.x + width; ++i)
			mazeLattice[drawx][i].setPassable(false);
		// 在偶数列建立一条墙壁
		int drawy = start.x + rand.nextInt(width / 2) * 2 + 1;
		for (int i = start.y; i < start.y + height; ++i)
			mazeLattice[i][drawy].setPassable(false);
		// 从左侧墙壁开始按逆时针顺序随机在四个墙壁上开三个门,左侧墙壁记为1
		int opendoor =  rand.nextInt(4)+ 1;
		switch (opendoor) {
   
		case 1:
			openAdoor(mazeLattice, new Point(drawy, drawx + 1), new Point(drawy, start.y + height - 1), rand);// 2
			openAdoor(mazeLattice, new Point(drawy + 1, drawx), new Point(start.x + width - 1, drawx), rand);// 3
			openAdoor(mazeLattice,new Point(drawy, start.y), new Point(drawy, drawx - 1) , rand);// 4
			break;
		case 2:
			openAdoor(mazeLattice, new Point(drawy + 1, drawx), new Point(start.x + width - 1, drawx), rand);// 3
			openAdoor(mazeLattice,new Point(drawy, start.y), new Point(drawy, drawx - 1) , rand);// 4
			openAdoor(mazeLattice, new Point(start.x, drawx), new Point(drawy - 1, drawx), rand);// 1
			break;
		case 3:
			openAdoor(mazeLattice,new Point(drawy, start.y), new Point(drawy, drawx - 1) , rand);// 4
			openAdoor(mazeLattice, new Point(start.x, drawx), new Point(drawy - 1, drawx), rand);// 1
			openAdoor(mazeLattice, new Point(drawy, drawx + 1), new Point(drawy, start.y + height - 1), rand);// 2
			break;
		case 4:
			openAdoor(mazeLattice, new Point(start.x, drawx), new Point(drawy - 1, drawx), rand);// 1
			openAdoor(mazeLattice, new Point(drawy, drawx + 1), new Point(drawy, start.y + height - 1), rand);// 2
			openAdoor(mazeLattice, new Point(drawy + 1, drawx), new Point(start.x +
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值