教你怎么用Java开发扫雷游戏
作者:编程界明世隐 发布时间:2023-07-22 09:49:26
一、效果图
二、实现思路
1.界面上可以点开的各种实际都是按钮,创建9行9列的二维数组,然后根据这个数组来创建JButton。
2.对应创建二维数组data,用来存取数据,0表示周围无雷,-1表示当前是雷,其他数字表示周围雷的数量。
3.对应创建二维数组state,用来存取按钮状态,0未打开,1 打开 2旗子 3 未知(控制显示对应的图标)
4.设置雷:随机行数 i 和列数 j,根据随机到 i、j 从二维数组data中取出对应的元素值,若值不为-1(不是雷),则将此元素data[i][j]设置为-1,若值是-1(已经是雷了),则跳过,不管是否跳过都进行递归,直到雷的数量达到设定的最大数量,跳出递归。
5.设置周围雷的数量:计算每个元素周围的雷数量(周围指的是 左上、上、右上、右、右下、下、左下、左 这8个位置),循环二维数组data,判断当前值不是-1,则需要计算周围雷的数量,等会细说。
6.有任一格子被揭开,则游戏开始并且计时,当格子被揭开的时候分3种情况
(1)格子是雷,执行 * 动画,游戏结束。
(2)当前格子周围有雷,则仅仅打开此格子,对应显示周围雷数量的数字图片。
(3)当前格子不是雷且周围没有雷(data取到的元素值为0),则依次打开周围,并且被打开的周围元素也没有雷的情况下,继续打开(递归)。
7.右键可以进行插小旗、打问号等操作(对数组state进行的操作)。
三、代码实现
3.1 设置头部
//设置头部
private void setHeader() {
Container container = new Container();
container.setLayout(new GridLayout(1, 3));
timeJLabel = new JLabel("时间:"+time,JLabel.CENTER);
timeJLabel.setForeground(Color.DARK_GRAY);
timeJLabel.setFont(new Font("微软雅黑",Font.BOLD, 16));
leiJLabel = new JLabel("雷:"+curLeiCount,JLabel.CENTER);
leiJLabel.setForeground(Color.DARK_GRAY);
leiJLabel.setFont(new Font("微软雅黑",Font.BOLD, 16));
reStart = new JButton((ImageIcon)imageMap.get(21));
Dimension preferredSize = new Dimension(100,40);
reStart.setPreferredSize(preferredSize);
reStart.addActionListener(this);
//注意添加顺序
container.add(timeJLabel);
container.add(reStart);
container.add(leiJLabel);
mainFrame.add(container,BorderLayout.NORTH);
}
3.2 设置游戏区域按钮
1.创建容器,并采用GridLayout 布局。
2.根据设定的ROWS、COLS创建二维数组,数组存储JButton,给每个按钮设置图标。
3.给每个按钮添加鼠标点击事件,右键事件。
private void setButtons() {
Container container = new Container();
container.setLayout(new GridLayout(ROWS, COLS));
ImageIcon icon=null;
for (int i = 0; i <ROWS; i++) {
for (int j = 0; j < COLS; j++) {
JButton btn = new JButton();
btn.setBounds(0, 0, 39, 39);
icon = (ImageIcon)imageMap.get(10);
setBtnImage(btn,icon);
container.add(btn);
btns[i][j]=btn;
btn.addActionListener(this);
btn.addMouseListener(this);
}
}
mainFrame.add(container,BorderLayout.CENTER);
}
3.3 设置雷
1.随机行数 i 和列数 j,根据随机到 i、j 从二维数组data中取出对应的元素值。
2.判断值,若值不为-1(不是雷),则将此元素data[i][j]设置为-1,若值是-1(已经是雷了),则跳过。
3.不管上一步是否跳过都进行递归,直到雷数量达到设定的最大数量,跳出递归。
private void setLei() {
if(computedLeiCount==LEICOUNT){//如果达到雷的最大数量则跳出
return;
}
Random random = new Random();
int r = random.nextInt(ROWS);
int c = random.nextInt(COLS);
//0 无; -1表示雷 ; 其他表示周围的雷数量
if(data[r][c]!=-1){//如果不是雷则设置为雷
data[r][c]=-1;
computedLeiCount++;
}
setLei();//递归调用
}
3.4 计算周围雷的数量并显示
1.循环之前的二维数组data,元素值是-1(雷)跳过,不是-1则继续。
2.如果当前元素的下标是(i,j),则左上为(i-1,j-1),上为(i-1,j ),右上为(i-1,j+1),以此类推,如下图所示:
3.分别取出这8个元素,并判断他们是不是雷,如果是则计数累加,最后把这个计数赋值给元素data[i][j]。
//设置周围雷的数量
private void setAroundLei() {
for (int i = 0; i <ROWS; i++) {
for (int j = 0; j < COLS; j++) {
if(data[i][j]!=-1){如果当前不是雷,则判断他周围有几个雷,并设置值
data[i][j] = computedLei(i,j);
}
}
}
}
//计算周围雷的数量
private int computedLei(int i,int j) {
int count=0;
//左上
int ci = i-1;
int cj = j-1;
if(ci>=0 && cj>=0){
if(data[ci][cj]==-1){
count++;
}
}
//上
ci = i-1;
cj = j;
if(ci>=0){
if(data[ci][cj]==-1){
count++;
}
}
//右上
ci = i-1;
cj = j+1;
if(ci>=0 && cj<COLS){
if(data[ci][cj]==-1){
count++;
}
}
//右
ci = i;
cj = j+1;
if(cj<COLS){
if(data[ci][cj]==-1){
count++;
}
}
//右下
ci = i+1;
cj = j+1;
if(ci<ROWS && cj<COLS){
if(data[ci][cj]==-1){
count++;
}
}
//下
ci = i+1;
cj = j;
if(ci<ROWS){
if(data[ci][cj]==-1){
count++;
}
}
//左下
ci = i+1;
cj = j-1;
if(ci<ROWS && cj >=0){
if(data[ci][cj]==-1){
count++;
}
}
//左
ci = i;
cj = j-1;
if(cj >= 0){
if(data[ci][cj]==-1){
count++;
}
}
return count;
}
3.5 添加点击事件
1.让代码实现 ActionListener
2.重写方法actionPerformed,获取点击的按钮进行揭开操作(分3种情况):
(1)格子是雷,执行 * 动画,游戏结束。
(2)当前格子周围有雷,则仅仅打开此格子,显示周围雷数量的数字图片。
(3)当前格子不是雷且周围没有雷(data取到的元素值为0),则依次打开周围,并且被打开的周围元素也没有雷的情况下,继续打开(递归)。
3.6 打开指定按钮
//打开指定的button
private void open(int i,int j) {
JButton button = btns[i][j];
if(state[i][j]==1){//已经打开直接返回
return ;
}
state[i][j]=1;//设置打开状态
int num = data[i][j];
if(num==-1){//直接使用雷的图片
setBtnImage(button,(ImageIcon)imageMap.get(18));
//游戏结束,并 *
boom(button);
}else{//如果当前不是雷,显示对应数字类图片
if(num==0){
num=9;
//显示周围的图标,并且递归
openAround(i,j);
}
setBtnImage(button,(ImageIcon)imageMap.get(num));
setBtnEnabled(button,false);//按钮被打开设置不能再点击了
//判定是否成功
successOrNot(1);
}
}
3.7 触雷 *
* 采用线程来执行,就是切换图片,图片切换到最后一张后线程结束,回调定义好的方法进行结束提示、打开所有格子等操作。
// * 效果
private void boom(JButton button) {
flag=true;
GameRunnable gameRunnable = new GameRunnable();
gameRunnable.setParam(button, boomImageMap,this);
thread = new Thread(gameRunnable);
thread.start();
}
// * 回调方法
public void reback(JButton button) {
setBtnImage(button,(ImageIcon)imageMap.get(18));
flag=false;
//设置全部按钮打开
openAll();
//弹出结束提示
UIManager.put("OptionPane.buttonFont", new FontUIResource(new Font("宋体", Font.ITALIC, 13)));
UIManager.put("OptionPane.messageFont", new FontUIResource(new Font("宋体", Font.ITALIC, 13)));
JOptionPane.showMessageDialog(mainFrame, "你失败了!点击上方按钮重新开始");
}
3.8 递归打开周围
//打开周围
private void openAround(int i,int j) {
//左上
int ci = i-1;
int cj = j-1;
if(ci>=0 && cj>=0){
open(ci,cj);
}
//上
ci = i-1;
cj = j;
if(ci>=0){
open(ci,cj);
}
//右上
ci = i-1;
cj = j+1;
if(ci>=0 && cj<COLS){
open(ci,cj);
}
//右
ci = i;
cj = j+1;
if(cj<COLS){
open(ci,cj);
}
//右下
ci = i+1;
cj = j+1;
if(ci<ROWS && cj<COLS){
open(ci,cj);
}
//下
ci = i+1;
cj = j;
if(ci<ROWS){
open(ci,cj);
}
//左下
ci = i+1;
cj = j-1;
if(ci<ROWS && cj >=0){
open(ci,cj);
}
//左
ci = i;
cj = j-1;
if(cj >= 0){
open(ci,cj);
}
}
3.9 鼠标右键事件
1.实现MouseListener,重写mouseClicked方法。
2.如果按钮是未打开状态则设置为旗子(设置state数组对应的元素值:2)。
3.如果按钮是旗子状态则设置为未知(设置state数组对应的元素值:3)。
4.如果按钮是未知状态则设置为原来的状态未打开(设置state数组对应的元素值:0)。
//鼠标右键的处理
@Override
public void mouseClicked(MouseEvent e) {
if(e.getButton()==MouseEvent.BUTTON3){
JButton button = (JButton)e.getSource();
for (int i = 0; i <ROWS; i++) {
for (int j = 0; j < COLS; j++) {
if(button.equals(btns[i][j])){//找到对应的按钮
if(state[i][j]==0){//如果是未打开状态则设置为旗子
state[i][j]=2;
setBtnImage(button,(ImageIcon)imageMap.get(12));
curLeiCount--;
leiJLabel.setText("雷:"+curLeiCount);
//需求判断是否成功
successOrNot(2);
}else if(state[i][j]==2){//如果是旗子状态则设置为未知
state[i][j]=3;
setBtnImage(button,(ImageIcon)imageMap.get(13));
curLeiCount++;
leiJLabel.setText("雷:"+curLeiCount);
}else if(state[i][j]==3){//如果是未知状态则设置为原来的未打开
state[i][j]=0;
setBtnImage(button,(ImageIcon)imageMap.get(10));
}
}
}
}
}
}
四、胜利判定
1.判断旗子的位置是不是正确的雷,并统计数量,如果统计到的数量刚好和设定的雷总数一样,证明雷全部被查出了,判定为胜利。
2.如果未打开的数量与设定雷的总数一样,也判定为胜利。
//判断是否成功 参数type=2表示判断右键插旗子,参数 type=其他 表示判断鼠标点击
private void successOrNot(int type) {
int count=0;
if(type==2){
/*
* 1.判断旗子的位置是否是正确的雷,并统计数量
* 2.如果统计到的数量刚好和雷的总数一样,证明全部被查出了,判定为胜利
*/
for (int i = 0; i <ROWS; i++) {
for (int j = 0; j < COLS; j++) {
if(state[i][j]==2 && data[i][j]==-1){//是旗子,也是雷,则计数
count++;
}
}
}
}else{
//最终的未打开的数量与雷的数量一样,则表示成功
for (int i = 0; i <ROWS; i++) {
for (int j = 0; j < COLS; j++) {
if(state[i][j]!=1){//未打开就计数
count++;
}
}
}
}
if(count==LEICOUNT){//成功
//关闭计时线程
gameTimeRunnable.setFlag(false);
//设置全部按钮打开
openAll();
//弹出结束提示
UIManager.put("OptionPane.buttonFont", new FontUIResource(new Font("宋体", Font.ITALIC, 13)));
UIManager.put("OptionPane.messageFont", new FontUIResource(new Font("宋体", Font.ITALIC, 13)));
JOptionPane.showMessageDialog(mainFrame, "恭喜你获得了胜利!你太棒了");
}
}
最后加入重新开始事件就完成了。
重新开始就是重新设置相关参数。
//重新开始游戏
private void restart() {
//关闭计时线程(可能正在进行游戏,所以要把计时线程关闭)
gameTimeRunnable.setFlag(false);
startFlag=false;
computedLeiCount=0;
curLeiCount=10;
leiJLabel.setText("雷:"+curLeiCount);
time=0;
timeJLabel.setText("时间:"+time);
data= new int[ROWS][COLS];//存取数据
state= new int[ROWS][COLS];//存取打开状态,0未打开,1 打开
setLei();
setAroundLei();
ImageIcon icon = null;
for (int i = 0; i <ROWS; i++) {
for (int j = 0; j < COLS; j++) {
icon = (ImageIcon)imageMap.get(10);
setBtnImage(btns[i][j],icon);
setBtnEnabled(btns[i][j],true);//按钮重新设置可以点击
}
}
}
来源:https://blog.csdn.net/dkm123456/article/details/116637184


猜你喜欢
- maven 打包 动态启动脚本介绍如何通过maven的环境变量动态打包, 并动态改变启动脚本中的环境参数之前都是每个环境一个启动脚本, 其实
- 一、通过程序看现象在开始为大家讲解Java 多线程缓存模型之前,我们先看下面的这一段代码。这段代码的逻辑很简单:主线程启动了两个子线程,一个
- 1.map遍历快速实现边距,文字自适应改变大小Container( // padding: EdgeI
- 这个应该是简易版的美图秀秀(小伙伴们吐槽:你这也叫简易版的??我们看着怎么不像啊……)。好吧,只是在图片上绘制涂鸦,然后保存。一、选择图片这
- 什么是进程?当一个程序开始运行时,它就是一个进程,进程包括运行中的程序和程序所使用到的内存和系统资源。而一个进程又是由多个线程所组成的。什么
- 这种情况,十有八九是SD存储卡的ext分区出错了,修复错误后重新开机即可重新启用a2sd+,找回原来安装的应用程序同修复FAT分区一样,这个
- 我们知道,Maven 是通过仓库对依赖进行管理的,当 Maven 项目需要某个依赖时,只要其 POM 中声明了依赖的坐标信息,Maven 就
- 前言相信有很多小伙伴,在日常的开发中都有遇到过需要调用第三方接口的需求吧,但是自己有没有写过接口提供给第三方使用呢,常规的都是我们调用别人的
- Spring Boot 项目之热部署配置前言所谓热部署,简单来说,就是代码修改后不需重启项目就可自动加载出新的内容。注意:热部署在 debu
- 1.user实体package com.demo.dto;public class User { private Integer
- Java实现驼峰、下划线互转1.使用 Guava 实现先引入相关依赖<dependency> <
- 前言实现轨迹回放,GMap.NET有对应的类GMapRoute。这个类函数很少,功能有限,只能实现简单的轨迹回放。要实现更复杂的轨迹回放,就
- 题目:使用栈计算类似表达式:5+2*3-2 的计算结果 提示:简易计算器操作符号限于+,-,*,/的计算分析思路:1、
- 定时/计划功能主要使用的就是Timer对象,它在内部还是使用多线程的方式进行处理,所以它和线程技术还是有非常大的关联。Timer类主要作用就
- 1. 为什么需要智能指针?简单的说,智能指针是为了实现类似于Java中的垃圾回收机制。Java的垃圾回收机制使程序员从繁杂的内存管理任务中彻
- 碎片,它的出现是为了更好展示UI的设计,让程序更加得到充分的展示。Fragment的出现,如微信的额主界面包含多个Fragment,使得微信
- 一、基本RPC框架简介在分布式计算中,远程过程调用(Remote Procedure Call,缩写 RPC)允许运行于一台计算机的程序调用
- 本文将介绍一段实例代码,来讲解利用正则表达式使C#判断输入日期格式是否正确的方法。希望这段代码能对大家有所帮助。 通常我们在用C#
- 一、背景单机节点下,WebSocket连接成功后,可以直接发送消息。而多节点下,连接时通过nginx会代理到不同节点。假设一开始用户连接了n
- 为了学习数据库,重装了系统,之前前一直在用eclipse,现在准备换成myeclipse,这之前当然需要重新设置环境变量,顺手写下有关jdk