教你怎么用Java开发扫雷游戏

网友投稿 275 2022-10-24


教你怎么用Java开发扫雷游戏

一、效果图

二、实现思路

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

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

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

if(data[ci][cj]==-1){

count++;

}

}

//右

ci = i;

cj = j+1;

if(cj

if(data[ci][cj]==-1){

count++;

}

}

//右下

ci = i+1;

cj = j+1;

if(ci

if(data[ci][cj]==-1){

count++;

}

}

//下

ci = i+1;

cj = j;

if(ci

if(data[ci][cj]==-1){

count++;

}

}

//左下

ci = i+1;

cj = j-1;

if(ci=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=9http://;

//显示周围的图标,并且递归

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

open(ci,cj);

}

//右

ci = i;

cj = j+1;

if(cj

open(ci,cj);

}

//右下

ci = i+1;

cj = j+1;

if(ci

open(ci,cj);

}

//下

ci = i+1;

cj = j;

if(ci

open(ci,cj);

}

//左下

ci = i+1;

cj = j-1;

if(ci=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

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

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

if(state[i][j]==2 && data[i][j]==-1){//是旗子,也是雷,则计数

count++;

}

}

}

}else{

//最终的未打开的数量与雷的数量一样,则表示成功

for (int i = 0; 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

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

icon = (ImageIcon)imageMap.get(10);

setBtnImage(btns[i][j],icon);

setBtnEnabled(btns[i][j],true);//按钮重新设置可以点击

}

}

}


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

上一篇:Tacacs ACS 服务器使用搭建配置
下一篇:Linux上实现网络设备配置文件自动备份
相关文章

 发表评论

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