Java實現(xiàn)經(jīng)典游戲復(fù)雜迷宮
前言
人類建造迷宮已有5000年的歷史。在世界的不同文化發(fā)展時期,這些奇特的建筑物始終吸引人們沿著彎彎曲曲、困難重重的小路吃力地行走,尋找真相。迷宮類小游戲應(yīng)運而生。在游戲中,迷宮被表現(xiàn)為冒險舞臺里,藏有各式各樣奇妙與謎題或?qū)毑氐奈kU區(qū)域。型態(tài)有洞窟、人工建筑物、怪物巢穴、密林或山路等。迷宮內(nèi)有惡徒或兇猛的生物(真實存在或想像物體都有)徘徊,其中可能會有陷阱、不明設(shè)施、遺跡等。
《復(fù)雜迷宮》游戲是用java語言實現(xiàn),采用了swing技術(shù)進(jìn)行了界面化處理,設(shè)計思路用了面向?qū)ο笏枷搿?/p>
主要需求
方向鍵控制移動,角色走出迷宮,游戲勝利。增加游戲難度和增加隨機地圖。
主要設(shè)計
1、構(gòu)建游戲地圖面板
2、設(shè)定迷宮地圖,包含可走的通道,不可走的墻體,還有出口位置
3、鍵盤的上下左右按鍵,來控制角色的移動
4、角色移動的算法,通道可走,遇到墻體不可走
5、走到終點,有成功通關(guān)的提示。
6、增加游戲的難度選擇,難度1,難度2和難度3
7、每次生成的地圖是隨機的
8、地圖大小可選擇,迷宮的長在10-45之間,寬在10-90之間
9、增加撞墻的音樂效果
功能截圖
游戲開始頁面

生成難度1,10*10的迷宮地圖

隨機地圖:生成難度1,10*10的迷宮地圖

生成難度2,30*30的迷宮地圖

生成難度3,90*45的迷宮地圖

成功過關(guān)-效果

代碼實現(xiàn)
窗口布局
public class StartView extends JFrame {
public StartView() {
this.setTitle("復(fù)雜迷宮");
this.setSize(240, 265);
this.setLocationRelativeTo(null);
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
this.setResizable(false);
initialize();
this.setVisible(true);
}
private void initialize() {
JPanel contentPane = new JPanel();
this.setContentPane(contentPane);
contentPane.setLayout(null);
JLabel widthLabel = new JLabel("迷宮長度:");
JLabel heightLabel = new JLabel("迷宮高度:");
JLabel levelLabel = new JLabel("難度:");
JTextField widthText = new JTextField();
JTextField heightText = new JTextField();
JRadioButton level1 = new JRadioButton("1");
JRadioButton level2 = new JRadioButton("2");
JRadioButton level3 = new JRadioButton("3");
ButtonGroup levelGroup = new ButtonGroup();
levelGroup.add(level1);
levelGroup.add(level2);
levelGroup.add(level3);
JButton run = new JButton("生成迷宮");
// 設(shè)定標(biāo)簽位置
widthLabel.setBounds(20, 20, 100, 30);
heightLabel.setBounds(20, 70, 110, 30);
widthText.setBounds(120, 20, 70, 30);
heightText.setBounds(120, 70, 70, 30);
levelLabel.setBounds(20, 120, 60, 30);
level1.setBounds(80, 120, 50, 30);
level2.setBounds(130, 120, 50, 30);
level3.setBounds(180, 120, 50, 30);
run.setBounds(55, 170, 120, 30);
// 限制輸入框只接收數(shù)字
widthText.setDocument(new NumberTextField());
heightText.setDocument(new NumberTextField());
// 改變字體
Font font = new Font("楷體", Font.PLAIN, 17);
widthLabel.setFont(font);
heightLabel.setFont(font);
widthText.setFont(font);
heightText.setFont(font);
levelLabel.setFont(font);
level1.setFont(font);
level2.setFont(font);
level3.setFont(font);
run.setFont(font);
// 取消按鈕選中邊框
level1.setFocusPainted(false);
level2.setFocusPainted(false);
level3.setFocusPainted(false);
// 默認(rèn)選擇難度3
level3.setSelected(true);
contentPane.add(widthLabel);
contentPane.add(heightLabel);
contentPane.add(widthText);
contentPane.add(heightText);
contentPane.add(levelLabel);
contentPane.add(level1);
contentPane.add(level2);
contentPane.add(level3);
contentPane.add(run);
// 生成迷宮監(jiān)聽器
run.addActionListener(e -> {
// 建議寬在10-90,長在10-45之間
if (widthText.getText().equals("")) {
JOptionPane.showMessageDialog(null, "長度不能為空!", "提示", JOptionPane.INFORMATION_MESSAGE);
} else if (heightText.getText().equals("")) {
JOptionPane.showMessageDialog(null, "高度不能為空!", "提示", JOptionPane.INFORMATION_MESSAGE);
} else {
int width = Integer.parseInt(widthText.getText());
int height = Integer.parseInt(heightText.getText());
if (width >= 10 && width <= 90 && height >= 10 && height <= 45) {
int level = level1.isSelected() ? 1 : level2.isSelected() ? 2 : 3;
MazeModel maze = new MazeModel(width, height, level);
this.dispose();
maze.draw();
} else {
JOptionPane.showMessageDialog(null, "迷宮的長必須在10-45之間,寬必須在10-90之間,請檢查輸入是否有誤!", "錯誤輸入", JOptionPane.ERROR_MESSAGE);
}
}
});
// 添加回車鍵入監(jiān)聽器
KeyAdapter enterAdapter = new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
run.doClick(); // 回車即生成迷宮
}
}
};
widthText.addKeyListener(enterAdapter);
heightText.addKeyListener(enterAdapter);
}
public static void main(String[] args) {
new StartView();
}
}
迷宮的數(shù)學(xué)模型
public class MazeModel {
private int width;
private int height;
private ArrayList<MazePoint> mazePoints;
/**
* 迷宮的構(gòu)造方法
*
* @param width 迷宮的寬度
* @param height 迷宮的
* @param level 1 -> 遞歸分割算法生成迷宮,2 -> 遞歸回溯算法生成迷宮,3 -> 普里姆算法生成迷宮
*/
public MazeModel(int width, int height, int level) {
super();
this.width = width;
this.height = height;
switch (level) {
case 1 : this.mazePoints = recursiveDivision();
case 2 : this.mazePoints = recursiveBacktracker();
case 3 : this.mazePoints = prim();
}
}
/**
* 遞歸回溯生成迷宮
*
* @return 生成的迷宮的單元格集合
*/
private ArrayList<MazePoint> recursiveBacktracker() {
ArrayList<MazePoint> maze = new ArrayList<>();
// 初始化所以單元格都被強包圍
for (int h = 0; h < height; h++) {
for (int w = 0; w < width; w++) {
MazePoint point = new MazePoint(w, h, true);
maze.add(point);
}
}
// 建立一個存放操作單元格的棧
Stack<MazePoint> stack = new Stack<>();
// 選擇(0,0)點作為起始點,開始打通迷宮
stack.push(maze.get(0));
maze.get(0).visited = true;
Random random = new Random();
int x; // 操作單元格的橫坐標(biāo)
int y; // 操作單元格的縱坐標(biāo)
int direction; // 方向
while (!stack.empty()) {
// 選擇棧頂元素作為當(dāng)前操作數(shù)
MazePoint operatingPoint = stack.peek();
x = operatingPoint.getX();
y = operatingPoint.getY();
direction = random.nextInt(4);
MazePoint adjacency;
switch (direction) {
case 0: // 左邊
if ((x - 1) >= 0) { // 判斷左邊是否為邊緣
adjacency = maze.get(x - 1 + y * width); // 判斷左邊單元格是否被訪問過
if (!adjacency.visited) {
operatingPoint.setLeft(0); // 打通操作單元格的左墻,和左邊單元格的右墻
adjacency.setRight(0);
stack.push(adjacency); // 將左墻入棧,作為下次循環(huán)的操作單元格
adjacency.visited = true; // 將左邊的單元格設(shè)置為訪問過了
x--; // 改變操作單元格的坐標(biāo),方便后面判斷當(dāng)前單元格四周是否都訪問過
}
}
break;
case 1: // 右邊
// 注釋參照case0
if ((x + 1) < width) {
adjacency = maze.get(x + 1 + y * width);
if (!adjacency.visited) {
operatingPoint.setRight(0);
adjacency.setLeft(0);
stack.push(adjacency);
adjacency.visited = true;
x++;
}
}
break;
case 2: // 上邊
// 注釋參照case0
if ((y - 1) >= 0) {
adjacency = maze.get(x + (y - 1) * width);
if (!adjacency.visited) {
operatingPoint.setUp(0);
adjacency.setDown(0);
stack.push(adjacency);
adjacency.visited = true;
y--;
}
}
break;
case 3: // 下邊
// 注釋參照case0
if ((y + 1) < height) {
adjacency = maze.get(x + (y + 1) * width);
if (!adjacency.visited) {
operatingPoint.setDown(0);
adjacency.setUp(0);
stack.push(adjacency);
adjacency.visited = true;
y++;
}
}
break;
}
// 若操作單元格四周都被訪問過,將該單元格出棧。
if ((x - 1 < 0 || maze.get(x - 1 + y * width).visited)
&& (x + 1 >= width || maze.get(x + 1 + y * width).visited)
&& (y - 1 < 0 || maze.get(x + (y - 1) * width).visited)
&& (y + 1 >= height || maze.get(x + (y + 1) * width).visited)) {
stack.pop();
}
}
maze.get(0).setLeft(0); // 左上角開墻作為入口
maze.get(width * height - 1).setRight(0); // 右下角開墻作為出口
return maze;
}
/**
* 分割迷宮區(qū)域
*
* @param maze 單元格集合
* @param right 區(qū)域的寬
* @param top 區(qū)域的高
*/
private void divide(ArrayList<MazePoint> maze, int left, int right, int top, int down) {
if (right - left > 0 && top - down > 0) {
// 在區(qū)域中心”十“字筑墻
for (int x = left, y = (top - down) / 2 + down; x <= right; x++) {
maze.get(x + y * this.width).setDown(1);
maze.get(x + (y + 1) * this.width).setUp(1);
}
for (int x = (right - left) / 2 + left, y = down; y <= top; y++) {
maze.get(x + y * this.width).setRight(1);
maze.get(x + 1 + y * this.width).setLeft(1);
}
// 在“十”字墻中選其中三個方向拆一面墻
Random random = new Random();
int direction = random.nextInt(4);
int x = (right - left) / 2 + left;
int y = (top - down) / 2 + down;
int tempX;
int tempY;
if (direction != 0) { // 打通一面左邊的墻
if (x - left > left) {
tempX = random.nextInt(x - left + 1) + left;
} else {
tempX = left;
}
tempY = y;
maze.get(tempX + tempY * this.width).setDown(0);
maze.get(tempX + (tempY + 1) * this.width).setUp(0);
}
if (direction != 1) { // 打通一面右邊的墻
if (right - (x + 1) > x + 1) {
tempX = random.nextInt(right - (x + 1) + 1) + x + 1;
} else {
tempX = x + 1;
}
tempY = y;
maze.get(tempX + tempY * this.width).setDown(0);
maze.get(tempX + (tempY + 1) * this.width).setUp(0);
}
if (direction != 2) { // 打通一面上面的墻
tempX = x;
if (y - down > down) {
tempY = random.nextInt(y - down + 1) + down;
} else {
tempY = down;
}
maze.get(tempX + tempY * this.width).setRight(0);
maze.get(tempX + 1 + tempY * this.width).setLeft(0);
}
if (direction != 3) { // 打通一面下面的墻
tempX = x;
if (top - (y + 1) > y + 1) {
tempY = random.nextInt(top - (y + 1) + 1) + y + 1;
} else {
tempY = y + 1;
}
maze.get(tempX + tempY * this.width).setRight(0);
maze.get(tempX + 1 + tempY * this.width).setLeft(0);
}
maze.stream().limit(this.width).forEach(m -> m.setUp(1));
maze.stream().skip((this.height - 1) * this.width).forEach(m -> m.setDown(1));
maze.stream().filter(m -> m.getX() == 0).forEach(m -> m.setLeft(1));
maze.stream().filter(m -> m.getX() == width - 1).forEach(m -> m.setRight(1));
divide(maze, left, (right - left) / 2 + left, (top - down) / 2 + down, down);
divide(maze, left, (right - left) / 2 + left, top, (top - down) / 2 + down + 1);
divide(maze, (right - left) / 2 + left + 1, right, (top - down) / 2 + down, down);
divide(maze, (right - left) / 2 + left + 1, right, top, (top - down) / 2 + down + 1);
}
}
/**
* 遞歸分割生成迷宮
*
* @return 生成的迷宮的單元格集合
*/
private ArrayList<MazePoint> recursiveDivision() {
// 初始化迷宮的所有單元格
ArrayList<MazePoint> maze = new ArrayList<>();
for (int h = 0; h < height; h++) {
for (int w = 0; w < width; w++) {
MazePoint point = new MazePoint(w, h);
maze.add(point);
}
}
divide(maze, 0, width - 1, height - 1, 0); // 遞歸分割迷宮
maze.get(0).setLeft(0); // 左上角開墻作為入口
maze.get(width * height - 1).setRight(0); // 右下角開墻作為出口
return maze;
}
private ArrayList<MazePoint> prim() {
ArrayList<MazePoint> mazePoints = new ArrayList<>();
PrimMaze primMaze = new PrimMaze(width * 2 + 1, height * 2 + 1);
int[][] tempMaze = primMaze.getMaze();
for (int i = 0; i < tempMaze.length; i++) {
for (int j = 0; j < tempMaze[i].length; j++) {
if (i % 2 != 0 && j % 2 != 0) {
MazePoint mazePoint = new MazePoint(i / 2, j / 2);
if (tempMaze[i - 1][j] == 10) {
mazePoint.setLeft(1);
}
if (tempMaze[i + 1][j] == 10) {
mazePoint.setRight(1);
}
if (tempMaze[i][j - 1] == 11) {
mazePoint.setUp(1);
}
if (tempMaze[i][j + 1] == 11) {
mazePoint.setDown(1);
}
mazePoints.add(mazePoint);
}
}
}
mazePoints.get(0).setLeft(0); // 左上角開墻作為入口
mazePoints.get(width * height - 1).setRight(0); // 右下角開墻作為出口
return mazePoints;
}
public void draw() {
new PlayView(mazePoints);
}
}
普里姆算法
class PrimMaze {
private int[][] maze;
public int[][] getMaze() {
return maze;
}
PrimMaze(int row, int column) {
int row1 = row / 2;
int column1 = column / 2;
maze = new int[row1 * 2 + 1][column1 * 2 + 1];
for (int x = 0; x < row1 * 2 + 1; x++) //初始化迷宮
{
for (int y = 0; y < column1 * 2 + 1; y++) {
if (x == 0 || x == row1 * 2) {
maze[x][y] = -1;
}
if (y == 0 || y == column1 * 2) {
maze[x][y] = -1;
}
}
}
for (int x = 1; x < row1 * 2; x++) {
for (int y = 1; y < column1 * 2; y++) {
if (x % 2 == 1 || y % 2 == 1) {
maze[x][y] = 0;
}
if (x % 2 == 0 || y % 2 == 0) {
maze[x][y] = 1;
}
}
}
ArrayList<int[]> list = new ArrayList<>(); //記錄已連通的"路"的坐標(biāo)的集合
int[] coordinate = new int[2]; //記錄未訪問的點坐標(biāo)
int x = 1, y = 1; //設(shè)置起點位置
coordinate[0] = coordinate[1] = 1;
list.add(coordinate); //將起點加入已經(jīng)連通的路集合
//x,y表示當(dāng)前訪問坐標(biāo)
while (list.size() < row1 * column1) //當(dāng)所有點都已訪問完時結(jié)束
{
boolean flag1; //標(biāo)識坐標(biāo)是否已經(jīng)被訪問
int[] record = {-1, -1, -1, -1}; //用于記錄四周未被訪問的方位,0代表上,1代表下,2代表左,3代表右
if (x - 2 > 0) //判斷當(dāng)前位置上方是否有路
{
int[] a = new int[2];
a[0] = x - 2;
a[1] = y;
flag1 = judge(a, list); //判斷上方是否已經(jīng)被訪問
if (flag1) {
record[0] = 0;
}
}
if (x + 2 < row1 * 2) //判斷當(dāng)前位置下方是否有路
{
int[] a = new int[2];
a[0] = x + 2;
a[1] = y;
flag1 = judge(a, list); //判斷下方是否已經(jīng)被訪問
if (flag1) {
record[1] = 1;
}
}
if (y - 2 > 0) //判斷當(dāng)前位置左方是否有路
{
int[] a = new int[2];
a[0] = x;
a[1] = y - 2;
flag1 = judge(a, list); //判斷左方是否已經(jīng)被訪問
if (flag1) {
record[2] = 2;
}
}
if (y + 2 < column1 * 2) //判斷當(dāng)前位置右方是否有路
{
int[] a = new int[2];
a[0] = x;
a[1] = y + 2;
flag1 = judge(a, list); //判斷右方是否已經(jīng)被訪問
if (flag1) {
record[3] = 3;
}
}
boolean flag2 = false; //flag2標(biāo)識四周是否有未訪問過的路
for (int i = 0; i < 4; i++) //判斷當(dāng)前位置的四個方位是否有未訪問過的路
{
if (record[i] == i) {
flag2 = true; //如果有未訪問過的路,跳出循環(huán)
break;
}
}
int r = new Random().nextInt(4);
while (record[r] == r) {
r = new Random().nextInt(4);
}
while (record[r] != r && flag2) //當(dāng)方位標(biāo)識錯誤且當(dāng)前位置四周有未訪問過的點時繼續(xù)隨機獲取一個新的方位標(biāo)識,直到標(biāo)識正確
{
r = new Random().nextInt(4); //隨機選取一個可以符合條件的墻并將其敲碎
if (record[r] == r) //當(dāng)標(biāo)識正確時,敲碎兩點之間的墻
{
if (r == 0) { //當(dāng)上方有未訪問過的點時,敲碎上方的墻
maze[x - 1][y] = 0;
}
if (r == 1) { //當(dāng)下方有未訪問過的點時,敲碎下方的墻
maze[x + 1][y] = 0;
}
if (r == 2) { //當(dāng)左方有未訪問過的點時,敲碎左方的墻
maze[x][y - 1] = 0;
}
if (r == 3) { //當(dāng)右方有未訪問過的點時,敲碎右方的墻
maze[x][y + 1] = 0;
}
}
}
//將與當(dāng)前坐標(biāo)之間的墻被敲碎的路的坐標(biāo)從未被訪問的集合中移出
if (r == 0 && flag2) //如果敲碎的是上方的墻,則將上方的路加入到已連通的路集合
{
int[] b = new int[2];
b[0] = x - 2;
b[1] = y;
if (judge(b, list)) {
list.add(b);
}
}
if (r == 1 && flag2) //如果敲碎的是下方的墻,則將下方的路加入到已連通的路集合
{
int[] b = new int[2];
b[0] = x + 2;
b[1] = y;
if (judge(b, list)) {
list.add(b);
}
}
if (r == 2 && flag2) //如果敲碎的是左方的墻,則將左方的路加入到已連通的路集合
{
int[] b = new int[2];
b[0] = x;
b[1] = y - 2;
if (judge(b, list)) {
list.add(b);
}
}
if (r == 3 && flag2) //如果敲碎的是右方的墻,則將右方的路加入到已連通的路集合
{
int[] b = new int[2];
b[0] = x;
b[1] = y + 2;
if (judge(b, list)) {
list.add(b);
}
}
int i = new Random().nextInt(list.size()); //隨機選取一個被連通的路坐標(biāo)
x = list.get(i)[0]; //獲取路坐標(biāo)
y = list.get(i)[1];
}
for (int r = 0; r < maze.length; r++)//將方格墻轉(zhuǎn)為線條墻,10表示橫,11表示豎
{
for (int c = 0; c < maze[r].length; c++) {
if (r % 2 == 0 && c % 2 == 1) {
if (maze[r][c] != 0) {
maze[r][c] = 10;
}
}
if (r % 2 == 1 && c % 2 == 0) {
if (maze[r][c] != 0) {
maze[r][c] = 11;
}
}
}
}
}
boolean judge(int[] coordinate, ArrayList<int[]> list) //判斷路是否已經(jīng)加入通路集合,已加入則返回false
{
boolean flag = true;
for (int[] ints : list) {
if (coordinate[0] == ints[0] && coordinate[1] == ints[1]) //若已訪問點集合中含有該位置的坐標(biāo),表示該位置已訪問過,不用重復(fù)加入該位置的坐標(biāo)
{
flag = false;
break;
}
}
return flag;
}
}
總結(jié)
通過此次的《復(fù)雜迷宮》游戲?qū)崿F(xiàn),讓我對swing的相關(guān)知識有了進(jìn)一步的了解,對java這門語言也有了比以前更深刻的認(rèn)識。
java的一些基本語法,比如數(shù)據(jù)類型、運算符、程序流程控制和數(shù)組等,理解更加透徹。java最核心的核心就是面向?qū)ο笏枷?,對于這一個概念,終于悟到了一些。
以上就是Java實現(xiàn)經(jīng)典游戲復(fù)雜迷宮的詳細(xì)內(nèi)容,更多關(guān)于Java迷宮游戲的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java?NIO緩沖區(qū)Buffer基礎(chǔ)教程示例
這篇文章主要介紹了Java?NIO緩沖區(qū)Buffer基礎(chǔ)教程示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-10-10
SpringBoot使用異步線程池實現(xiàn)生產(chǎn)環(huán)境批量數(shù)據(jù)推送
本文主要介紹了SpringBoot使用異步線程池實現(xiàn)生產(chǎn)環(huán)境批量數(shù)據(jù)推送,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-02-02
基于Springboot執(zhí)行多個定時任務(wù)并動態(tài)獲取定時任務(wù)信息
這篇文章主要為大家詳細(xì)介紹了基于Springboot執(zhí)行多個定時任務(wù)并動態(tài)獲取定時任務(wù)信息,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-04-04
Java servlet通過事件驅(qū)動進(jìn)行高性能長輪詢詳解
這篇文章主要介紹了基于servlet3.0+事件驅(qū)動實現(xiàn)高性能長輪詢的操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2022-06-06
springboot項目集成swagger-bootstrap-ui全過程
這篇文章主要介紹了springboot項目集成swagger-bootstrap-ui全過程,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-05-05

