作者简介
作者名:编程界明世隐 简介:CSDN博客专家,从事软件开发多年,精通Java、JavaScript,博主也是从零开始一步步把学习成长、深知学习和积累的重要性,喜欢跟广大ADC一起打野升级,欢迎您关注,期待与您一起学习、成长、起飞!
系列目录
1. Java俄罗斯方块 2. Java五子棋小游戏 3. 老Java程序员花一天时间写了个飞机大战 4. Java植物大战僵尸 5. 老Java程序员花2天写了个连连看 6. Java消消乐(天天爱消除) 7. Java贪吃蛇小游戏 8. Java扫雷小游戏 9. Java坦克大战
效果图
实现思路
1.创建运行窗口。 2.创建菜单。 3.绘制迷宫的每个单元。 4.通过算法计算迷宫路径,并打通路径,形成迷宫。 5.绘制起点终点。 6.添加键盘事件控制起点方块移动。 7.收尾。
迷宫算法(网上参考的)
- 将起点作为当前迷宫单元并标记为已访问
- 当还存在未标记的迷宫单元,进行循环
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为相对偏移量,为了让迷宫两边有些空隙):
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;
计算坐标,假如每个正方形的宽高都是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);
}
}
创建面板容器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类,来启动这个窗口。
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() {
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);
jMenu1.add(jmi1);
jMenu1.add(jmi2);
JMenuItem jmi3 = new JMenuItem("操作帮助");
jmi3.setFont(tFont);
JMenuItem jmi4 = new JMenuItem("胜利条件");
jmi4.setFont(tFont);
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);
}
}
此时的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() {
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);
jMenu1.add(jmi1);
jMenu1.add(jmi2);
JMenuItem jmi3 = new JMenuItem("操作帮助");
jmi3.setFont(tFont);
JMenuItem jmi4 = new JMenuItem("胜利条件");
jmi4.setFont(tFont);
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.Graphics;
import java.util.ArrayList;
import java.util.List;
public class Block {
private GamePanel panel = null;
private int i=0;
private int j=0;
private int h=0;
private int start=6;
private int x1=0;
private int y1=0;
private int x2=0;
private int y2=0;
private int x3=0;
private int y3=0;
private int x4=0;
private int y4=0;
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;
init();
}
private void init() {
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;
walls[0]=true;
walls[1]=true;
walls[2]=true;
walls[3]=true;
}
public void draw(Graphics g) {
drawBlock(g);
}
private void drawBlock(Graphics g) {
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<Block> findNeighbors() {
List<Block> res= new ArrayList<Block>();
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];
if(neighbor.visited && !lose_visited){
neighbor = null;
}
}
return neighbor;
}
- 计算
跟着算法来写的代码,唯一要注意的是我设置了一个值unVisitedCount,初始值为所有单元的数量,每当一个单元被标记为已访问后,这个值就递减1,当值为0后就终止循环,结束算法。
private void computed(){
Random random = new Random();
Stack<Block> stack = new Stack<Block>();
Block current = blocks[0][0];
current.setVisited(true);
int unVisitedCount=ROWS*COLS-1;
List<Block> 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);
unVisitedCount--;
current = next;
}else if(!stack.isEmpty()){
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;
}
}else 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;
private int j=0;
private int x=0;
private int y=0;
private int h=0;
private int start=6;
private String type="";
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){
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) {
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();
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) {
}
};
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() {
gameFlag="start";
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;
}
}
}
computed();
start.setI(0);
start.setJ(0);
repaint();
}
看到这里的大佬,动动发财的小手 点赞 + 回复 + 收藏,能【 关注 】一波就更好了。
代码获取方式:
帮忙文章【点赞】 +【 收藏】+【关注】+【评论】 后,加我主页微信或者私聊我,我发给你!
更多精彩
1. Java俄罗斯方块 2. Java五子棋小游戏 3. 老Java程序员花一天时间写了个飞机大战 4. Java植物大战僵尸 5. 老Java程序员花2天写了个连连看 6. Java消消乐(天天爱消除) 7. Java贪吃蛇小游戏 8. Java扫雷小游戏 9. Java坦克大战
相关阅读
1. JavaWeb图书管理系统 2. JavaWeb学生宿舍管理系统 3. JavaWeb在线考试系统
另外
为了帮助更多小白从零进阶 Java 工程师,从CSDN官方那边搞来了一套 《Java 工程师学习成长知识图谱》,尺寸 870mm x 560mm,展开后有一张办公桌大小,也可以折叠成一本书的尺寸,原件129元现价 29 元,先到先得,有兴趣的小伙伴可以了解一下!
|