老程序员教你一天时间完成Java迷宫小游戏

网友投稿 242 2022-10-05


老程序员教你一天时间完成Java迷宫小游戏

目录效果图实现思路迷宫算法(网上参考的)相关图示说明代码实现创建窗口创建菜单及菜单选项绘制迷宫的每个单元计算并打通迷宫绘制起点终点加入键盘移动监听收尾总结

效果图

实现思路

1.创建运行窗口。

2.创建菜单。

3.绘制迷宫的每个单元。

4.通过算法计算迷宫路径,并打通路径,形成迷宫。

5.绘制起点终点。

6.添加键盘事件控制起点方块移动。

7.收尾。

迷宫算法(网上参考的)

1.将起点作为当前迷宫单元并标记为已访问

2.当还存在未标记的迷宫单元,进行循环

1).如果当前迷宫单元有未被访问过的的相邻的迷宫单元

(1).随机选择一个未访问的相邻迷宫单元

(2).将当前迷宫单元入栈

(3).移除当前迷宫单元与相邻迷宫单元的墙

(4).标记相邻迷宫单元并用它作为当前迷宫单元

2).如果当前迷宫单元不存在未访问的相邻迷宫单元,并且栈不空

(1).栈顶的迷宫单元出栈

(2).令其成为当前迷宫单元

**这个算法叫做“深度优先”,简单来说,就是从起点开始走,寻找它的上下左右4个邻居,然后随机一个走,到走不通的时候就返回上一步继续走,直到全部单元都走完。 **

相关图示说明

每个单元的墙,分为上墙、右墙、下墙、左墙,把这些墙用长度为4的数组表示,元素的值为true则表示墙存在,否则墙不存在,代码里数组的下标方式来确定墙是否存在。

单元是根据行列来创建的,会用到双循环,类似表格,比如第二行用 i 来表示的话就是 1,第3列用 j 来表示就是2,那第二行第3列的元素组合起来就是(1,2)

那同理它的上邻居就是(0,2),右邻居(1,3),下邻居(2,2),左邻居(1,1),也就是上下邻居是 i 减加1,左右邻居是 j 减加1。

正方形4个点的坐标分别为(x1,y1)(x2,y2)(x3,y3)(x4,y4),计算坐标的公式为(其中start为相对偏移量,为了让迷宫两边有些空隙):

//i代表行 j代表列 h为单元高度

//左上角坐标

this.x1=start+j*h;

this.y1=start+i*h;

//右上角坐标

http://this.x2=start+(j+1)*h;

this.y2=start+i*h;

//右下角坐标

this.x3=start+(j+1)*h;

this.y3=start+(i+1)*h;

//左下角坐标

this.x4=start+j*h;

this.y4=start+(i+1)*h;

计算坐标,假如每个正方形的宽高都是40,那么(1,2)这个单元的坐标如下图:

5. 墙的处理,之前说到墙是以一个4个元素的数组来表示的,比如数组为:[true,true,true,true],则图为:

如果数组为[false,true,true,true],则图为:

6. 如果要联通右边的邻居要怎么做呢?当前单元去除右墙,右边单元去除左墙,这样就联通了。

去除后就这样,以此类推

代码实现

创建窗口

首先创建一个游戏窗体类GameFrame,继承至JFrame,用来显示在屏幕上(window的对象),每个游戏都有一个窗口,设置好窗口标题、尺寸、布局等就可以。

import javax.swing.JFrame;

/**

*窗体类

*/

public class GameFrame extends JFrame {

//构造方法

public GameFrame(){

setTitle("迷宫");//设置标题

setSize(420, 470);//设置窗体大小

setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//关闭后进程退出

setLocationRelativeTo(null);//居中

setResizable(false);//不允许变大

//setVisible(true);//设置显示窗体

}

}

创建面板容器GamePanel继承至JPanel

import javax.swing.JMenuBar;

import javax.swing.JPanel;

/*

* 画布类

*/

public class GamePanel extends JPanel{

private JMenuBar jmb = null;

private GameFrame mainFrame = null;

private GamePanel panel = null;

private String gameFlag="start";//游戏状态

//构造方法

public GamePanel(GameFrame mainFrame){

this.setLayout(null);

this.setOpaque(false);

this.mainFrame=mainFrame;

this.panel =this;

}

}

再创建一个Main类,来启动这个窗口。

//Main类

public class Main {

public static void main(String[] args) {

GameFrame frame = new GameFrame();

GamePanel panel = new GamePanel(frame);

frame.add(panel);

frame.setVisible(true);

}

}

右键执行这个Main类,窗口建出来了

创建菜单及菜单选项

创建菜单

private Font createFont(){

return new Font("思源宋体",Font.BOLD,18);

}

//创建菜单

private void createMenu() {

//创建JMenuBar

jmb = new JMenuBar();

//取得字体

Font tFont = createFont();

//创建游戏选项

JMenu jMenu1 = new JMenu("游戏");

jMenu1.setFont(tFont);

//创建帮助选项

JMenu jMenu2 = new JMenu("帮助");

jMenu2.setFont(tFont);

JMenuItem jmi1 = new JMenuItem("新游戏");

jmi1.setFont(tFont);

JMenuItem jmi2 = new JMenuItem("退出");

jmi2.setFont(tFont);

//jmi1 jmi2添加到菜单项“游戏”中

jMenu1.add(jmi1);

jMenu1.add(jmi2);

JMenuItem jmi3 = new JMenuItem("操作帮助");

jmi3.setFont(tFont);

JMenuItem jmi4 = new JMenuItem("胜利条件");

jmi4.setFont(tFont);

//jmi13 jmi4添加到菜单项“游戏”中

jMenu2.add(jmi3);

jMenu2.add(jmi4);

jmb.add(jMenu1);

jmb.add(jMenu2);

mainFrame.setJMenuBar(jmb);

//添加监听

jmi1.addActionListener(this);

jmi2.addActionListener(this);

jmi3.addActionListener(this);

jmi4.addActionListener(this);

//设置指令

jmi1.setActionCommand("restart");

jmi2.setActionCommand("exit");

jmi3.setActionCommand("help");

jmi4.setActionCommand("win");

}

实现ActionListener并重写方法actionPerformed

此时GamePanel是报错的,需重写actionPerformed方法。

actionPerformed方法的实现

@Override

public void actionPerformed(ActionEvent e) {

String command = e.getActionCommand();

System.out.println(command);

UIManager.put("OptionPane.buttonFont", new FontUIResource(new Font("思源宋体", Font.ITALIC, 18)));

UIManager.put("OptionPane.messageFont", new FontUIResource(new Font("思源宋体", Font.ITALIC, 18)));

if ("exit".equals(command)) {

Object[] options = { "确定", "取消" };

int response = JOptionPane.showOptionDialog(this, "您确认要退出吗", "",

JOptionPane.YES_OPTION, JOptionPane.QUESTION_MESSAGE, null,

options, options[0]);

if (response == 0) {

System.exit(0);

}

}else if("restart".equals(command)){

restart();

}else if("help".equals(command)){

JOptionPane.showMessageDialog(null, "通过键盘的上下左右来移动",

"提示!", JOptionPane.INFORMATION_MESSAGE);

}else if("win".equals(command)){

JOptionPane.showMessageDialog(null, "移动到终点获得胜利",

"提示!", JOptionPane.INFORMATION_MESSAGE);

}

}

http://

此时的GamePanel代码如下:

package main;

import java.awt.Font;

import java.awt.event.ActionEvent;

import java.awt.event.ActionListener;

import javax.swing.JMenu;

import javax.swing.JMenuBar;

import javax.swing.JMenuItem;

import javax.swing.JOptionPane;

import javax.swing.JPanel;

import javax.swing.UIManager;

import javax.swing.plaf.FontUIResource;

/*

* 画布类

*/

public class GamePanel extends JPanel implements ActionListener{

private JMenuBar jmb = null;

private GameFrame mainFrame = null;

private GamePanel panel = null;

private String gameFlag="start";//游戏状态

//构造方法

public GamePanel(GameFrame mainFrame){

this.setLayout(null);

this.setOpaque(false);

this.mainFrame=mainFrame;

this.panel =this;

//创建菜单

createMenu();

}

private Font createFont(){

return new Font("思源宋体",Font.BOLD,18);

}

//创建菜单

private void createMenu() {

//创建JMenuBar

jmb = new JMenuBar();

//取得字体

Font tFont = createFont();

//创建游戏选项

JMenu jMenu1 = new JMenu("游戏");

jMenu1.setFont(tFont);

//创建帮助选项

JMenu jMenu2 = new JMenu("帮助");

jMenu2.setFont(tFont);

JMenuItem jmi1 = new JMenuItem("新游戏");

jmi1.setFont(tFont);

JMenuItem jmi2 = new JMenuItem("退出");

jmi2.setFont(tFont);

//jmi1 jmi2添加到菜单项“游戏”中

jMenu1.add(jmi1);

jMenu1.add(jmi2);

JMenuItem jmi3 = new JMenuItem("操作帮助");

jmi3.setFont(tFont);

JMenuItem jmi4 = new JMenuItem("胜利条件");

jmi4.setFont(tFont);

//jmi13 jmi4添加到菜单项“游戏”中

jMenu2.add(jmi3);

jMenu2.add(jmi4);

jmb.add(jMenu1);

jmb.add(jMenu2);

mainFrame.setJMenuBar(jmb);

//添加监听

jmi1.addActionListener(this);

jmi2.addActionListener(this);

jmi3.addActionListener(this);

jmi4.addActionListener(this);

//设置指令

jmi1.setActionCommand("restart");

jmi2.setActionCommand("exit");

jmi3.setActionCommand("help");

jmi4.setActionCommand("win");

}

@Override

public void actionPerformed(ActionEvent e) {

String command = e.getActionCommand();

System.out.println(command);

UIManager.put("OptionPane.buttonFont", new FontUIResource(new Font("思源宋体", Font.ITALIC, 18)));

UIManager.put("OptionPane.messageFont", new FontUIResource(new Font("思源宋体", Font.ITALIC, 18)));

if ("exit".equals(command)) {

Object[] options = { "确定", "取消" };

int response = JOptionPane.showOptionDialog(this, "您确认要退出吗", "",

JOptionPane.YES_OPTION, JOptionPane.QUESTION_MESSAGE, null,

options, options[0]);

if (response == 0) {

System.exit(0);

}

}else if("restart".equals(command)){

restart();

}else if("help".equals(command)){

JOptionPane.showMessageDialog(null, "通过键盘的上下左右来移动",

"提示!", JOptionPane.INFORMATION_MESSAGE);

}else if("win".equals(command)){

JOptionPane.showMessageDialog(null, "移动到终点获得胜利",

"提示!", JOptionPane.INFORMATION_MESSAGE);

}

}

void restart(){

}

}

运行它

绘制迷宫的每个单元

初始化相关参数

public final int ROWS=20;//行

public final int COLS=20;//列

public final int H=20;//每一块的宽高

Block[][] blocks = null;//存储每个单元的二维数组

创建迷宫单元类(如果对坐标计算不明白,可以往上翻,有图示说明解释)

import java.awt.GraphiPLbTmcs;

import java.util.ArrayList;

import java.util.List;

/*

* 迷宫单元类

*/

public class Block {

private GamePanel panel = null;

private int i=0;//二维数组的下标i

private int j=0;//二维数组的下标j

private int h=0;//宽高

private int start=6;//偏移像素

//4个顶点坐标

private int x1=0;//x1坐标

private int y1=0;//y1坐标

private int x2=0;//x2坐标

private int y2=0;//y2坐标

private int x3=0;//x3坐标

private int y3=0;//y3坐标

private int x4=0;//x4坐标

private int y4=0;//y4坐标

//上下左右4个墙是否显示,true:显示,false:隐藏

boolean[] walls=new boolean[4];

private boolean visited=false;//是否被访问

//构造

public Block(int i,int j,int h,GamePanel panel){

this.i=i;

this.j=j;

this.h=h;

this.panel=panel;

//计算4个顶点的坐标

init();

}

//计算4个顶点的坐标

private void init() {

//i代表行 j代表列

//左上角坐标

this.x1=start+j*h;

this.y1=start+i*h;

//右上角坐标

this.x2=start+(j+1)*h;

this.y2=start+i*h;

//右下角坐标

this.x3=start+(j+1)*h;

this.y3=start+(i+1)*h;

//左下角坐标

this.x4=start+j*h;

this.y4=start+(i+1)*h;

//默认上下左右4个墙都显示

walls[0]=true;

walls[1]=true;

walls[2]=true;

walls[3]=true;

}

//绘制指示器的方法

public void draw(Graphics g) {

//绘制迷宫块

drawBlock(g);

}

//绘制迷宫块

private void drawBlock(Graphics g) {

//判断上、右、下、左 的墙,true的话墙就会有,否则墙就没有

boolean top = walls[0];

boolean right = walls[1];

boolean bottom = walls[2];

boolean left = walls[3];

if(top){//绘制上墙

g.drawLine(x1, y1, x2, y2);

}

if(right){//绘制右墙

g.drawLine(x2, y2, x3, y3);

}

if(bottom){//绘制下墙

g.drawLine(x3, y3, x4, y4);

}

if(left){//绘制左墙

g.drawLine(x4, y4, x1, y1);

}

}

}

在GamePanel类中创建方法createBlocks

//创建数组内容

private void createBlocks() {

blocks = new Block[ROWS][COLS];

Block block ;

for (int i = 0; i < ROWS; i++) {

for (int j = 0; j < COLS; j++) {

block = new Block(i, j,H,this);

blocks[i][j]=block;

}

}

}

在构造函数中调用此方法

//构造方法

public GamePanel(GameFrame mainFrame){

this.setLayout(null);

this.setOpaque(false);

this.mainFrame=mainFrame;

this.panel =this;

//创建菜单

createMenu();

//创建数组内容

createBlocks();

}

在GamePanel中重新paint方法,绘制这些方块

public void paint(Graphics g) {

super.paint(g);

//绘制网格

drawBlock(g);

}

//绘制迷宫块

private void drawBlock(Graphics g) {

Block block ;

for (int i = 0; i < ROWS; i++) {

for (int j = 0; j < COLS; j++) {

block = blocks[i][j];

if(block!=null){

block.draw(g);

}

}

}

}

运行可以看到一个个的方形绘制出来了

计算并打通迷宫

给每个单元都增加邻居查找方法(Block类中)

//查找当前单元是否有未被访问的邻居单元

public List findNeighbors() {

//邻居分为上下左右

List res= new ArrayList();//返回的数组

Block top = this.getNeighbor(0,false);

Block right = this.getNeighbor(1,false);

Block bottom = this.getNeighbor(2,false);

Block left = this.getNeighbor(3,false);

if(top!=null){

res.add(top);

}

if(right!=null){

res.add(right);

}

if(bottom!=null){

res.add(bottom);

}

if(left!=null){

res.add(left);

}

return res;//返回邻居数组

}

//根据方向,获得邻居

public Block getNeighbor(int type,boolean lose_visited) {

Block neighbor;

int ti=0,tj=0;

if(type==0){

ti = this.i-1;

tj = this.j;

}else if(type==1){

ti = this.i;

tj = this.j+1;

}else if(type==2){

ti = this.i+1;

tj = this.j;

}else if(type==3){

ti = this.i;

tj = this.j-1;

}

Block[][] blocks = panel.blocks;

if(ti<0 || tj<0 || ti>=panel.ROWS || tj>=panel.COLS){//超出边界了

neighbor = null;

}else{

//首先找到这个邻居

neighbor = blocks[ti][tj];

//判断是否被访问,如果被访问了返回null

if(neighbor.visited && !lose_visited){//lose_visited等于true表示忽略访问

neighbor = null;

}

}

return neighbor;

}

计算

跟着算法来写的代码,唯一要注意的是我设置了一个值unVisitedCount,初始值为所有单元的数量,每当一个单元被标记为已访问后,这个值就递减1,当值为0后就终止循环,结束算法。

//线路的计算处理

private void computed(){

/*

1.将起点作为当前迷宫单元并标记为已访问

2.当还存在未标记的迷宫单元,进行循环

1).如果当前迷宫单元有未被访问过的的相邻的迷宫单元

(1).随机选择一个未访问的相邻迷宫单元

(2).将当前迷宫单元入栈

(3).移除当前迷宫单元与相邻迷宫单元的墙

(4).标记相邻迷宫单元并用它作为当前迷宫单元

2).如果当前迷宫单元不存在未访问的相邻迷宫单元,并且栈不空

(1).栈顶的迷宫单元出栈

(2).令其成为当前迷宫单元

*/

Random random = new Random();

Stack stack = new Stack();//栈

Block current = blocks[0][0];//取第一个为当前单元

current.setVisited(true);//标记为已访问

int unVisitedCount=ROWS*COLS-1;//因为第一个已经设置为访问了

List neighbors ;//定义邻居

Block next;

while(unVisitedCount>0){

neighbors = current.findNeighbors();//查找邻居集合(未被访问的)

if(neighbors.size()>0){//如果当前迷宫单元有未被访问过的的相邻的迷宫单元

//随机选择一个未访问的相邻迷宫单元

int index = random.nextInt(neighbors.size());

next = neighbors.get(index);

//将当前迷宫单元入栈

stack.push(current);

//移除当前迷宫单元与相邻迷宫单元的墙

this.removeWall(current,next);

//标记相邻迷宫单元并用它作为当前迷宫单元

next.setVisited(true);

//标记一个为访问,则计数器递减1

unVisitedCount--;//递减

current = next;

}else if(!stack.isEmpty()){//如果当前迷宫单元不存在未访问的相邻迷宫单元,并且栈不空

/*

1.栈顶的迷宫单元出栈

2.令其成为当前迷宫单元

*/

Block cell = stack.pop();

current = cell;

}

}

}

移除墙

//移除当前迷宫单元与相邻迷宫单元的墙

private void removeWall(Block current, Block next) {

if(current.getI()==next.getI()){//横向邻居

if(current.getJ()>next.getJ()){//匹配到的是左边邻居

//左边邻居的话,要移除自己的左墙和邻居的右墙

current.walls[3]=false;

next.walls[1]=false;

}else{//匹配到的是右边邻居

//右边邻居的话,要移除自己的右墙和邻居的左墙

current.walls[1]=false;

next.walls[3]=false;

}

}elhttp://se if(current.getJ()==next.getJ()){//纵向邻居

if(current.getI()>next.getI()){//匹配到的是上边邻居

//上边邻居的话,要移除自己的上墙和邻居的下墙

current.walls[0]=false;

next.walls[2]=false;

}else{//匹配到的是下边邻居

//下边邻居的话,要移除自己的下墙和邻居的上墙

current.walls[2]=false;

next.walls[0]=false;

}

}

}

在构造函数中调用computed方法

//构造方法

public GamePanel(GameFrame mainFrame){

this.setLayout(null);

this.setOpaque(false);

this.mainFrame=mainFrame;

this.panel =this;

//创建菜单

createMenu();

//创建数组内容

createBlocks();

//计算处理线路

computed();

}

运行效果

绘制起点终点

创建Rect类

package main;

import java.awt.Color;

import java.awt.Graphics;

//开始结束方块

public class Rect {

private int i=0;//二维数组的下标i

private int j=0;//二维数组的下标j

private int x=0;//x坐标

private int y=0;//y坐标

private int h=0;//宽高

private int start=6;//偏移像素

private String type="";//start end

public Rect(int i,int j,int h,String type){

this.i=i;

this.j=j;

this.h=h;

this.type=type;

}

//初始化

private void init() {

this.x=start+j*h+2;

this.y=start+i*h+2;

}

//绘制

void draw(Graphics g){

//计算x、y坐标

init();

Color oColor = g.getColor();

if("start".equals(type)){//红色

g.setColor(Color.red);

}else{

g.setColor(Color.blue);

}

g.fillRect(x, y, h-3, h-3);

g.setColor(oColor);

}

//移动

public void move(int type, Block[][] blocks,GamePanel panel) {

//根据当前start方形,获得对应的迷宫单元

Block cur = blocks[this.i][this.j];

boolean wall = cur.walls[type];//得到对应的那面墙

if(!wall){

//得到移动方块对应的单元

Block next = cur.getNeighbor(type,true);

if(next!=null){

this.i = next.getI();

this.j = next.getJ();

panel.repaint();

//判断如果i,j等于终点的,则表示获得成功

if(this.i==panel.end.i && this.j==panel.end.j){

panel.gameWin();

}

}

}

}

public int getI() {

return i;

}

public void setI(int i) {

this.i = i;

}

public int getJ() {

return j;

}

public void setJ(int j) {

this.j = j;

}

}

在GamePanel类中创建方法,并在构造中调用。

//创建开始结束的方形

private void createRects() {

start = new Rect(0, 0, H, "start") ;

end = new Rect(ROWS-1, COLS-1, H, "end") ;

}

//构造方法

public GamePanel(GameFrame mainFrame){

this.setLayout(null);

this.setOpaque(false);

this.mainFrame=mainFrame;

this.panel =this;

//创建菜单

createMenu();

//创建数组内容

createBlocks();

//计算处理线路

computed();

//创建开始结束的方形

createRects();

}

在paint方法中绘制

@Override

public void paint(Graphics g) {

super.paint(g);

//绘制网格

drawBlock(g);

//绘制开始结束方向

drawRect(g);

}

//绘制开始结束方块

private void drawRect(Graphics g) {

end.draw(g);

start.draw(g);

}

运行一下

加入键盘移动监听

创建监听方法

//添加键盘监听

private void createKeyListener() {

KeyAdapter l = new KeyAdapter() {

//按下

@Override

public void keyPressed(KeyEvent e) {

if(!"start".equals(gameFlag)) return ;

int key = e.getKeyCode();

switch (key) {

//向上

case KeyEvent.VK_UP:

case KeyEvent.VK_W:

if(start!=null) start.move(0,blocks,panel);

break;

//向右

case KeyEvent.VK_RIGHT:

case KeyEvent.VK_D:

if(start!=null) start.move(1,blocks,panel);

break;

//向下

case KeyEvent.VK_DOWN:

case KeyEvent.VK_S:

if(start!=null) start.move(2,blocks,panel);

break;

//向左

case KeyEvent.VK_LEFT:

case KeyEvent.VK_A:

if(start!=null) start.move(3,blocks,panel);

break;

}

}

//松开

@Override

public void keyReleased(KeyEvent e) {

}

};

//给主frame添加键盘监听

mainFrame.addKeyListener(l);

}

在构造中调用

//构造方法

public GamePanel(GameFrame mainFrame){

this.setLayout(null);

this.setOpaque(false);

this.mainFrame=mainFrame;

this.panel =this;

//创建菜单

createMenu();

//创建数组内容

createBlocks();

//计算处理线路

computed();

//创建开始结束的方形

createRects();

//添加键盘事件监听

createKeyListener();

}

运行

收尾

此时代码已经基本完成,加入游戏胜利、重新开始等方法即可

//重新开始

void restart() {

/*参数重置

1.游戏状态

2.迷宫单元重置

3.重新计算线路

*/

//1.游戏状态

gameFlag="start";

//2.迷宫单元重置

Block block ;

for (int i = 0; i < ROWS; i++) {

for (int j = 0; j < COLS; j++) {

block = blocks[i][j];

if(block!=null){

block.setVisited(false);

block.walls[0]=true;

block.walls[1]=true;

block.walls[2]=true;

block.walls[3]=true;

}

}

}

//3.计算处理线路

computed();

//开始方块归零

start.setI(0);

start.setJ(0);

//重绘

repaint();

}

总结

本篇文章就到这里了,希望能给你带来帮助,也希望您能够多多关注我们的更多内容!


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:kali初始配置(kali最低配置)
下一篇:App 不想被“点名”,mPaaS 隐私合规检测为开发者护航数字生态建设(apple watch)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~