教你怎么用Java開(kāi)發(fā)掃雷游戲
一、效果圖

二、實(shí)現(xiàn)思路
1.界面上可以點(diǎn)開(kāi)的各種實(shí)際都是按鈕,創(chuàng)建9行9列的二維數(shù)組,然后根據(jù)這個(gè)數(shù)組來(lái)創(chuàng)建JButton。
2.對(duì)應(yīng)創(chuàng)建二維數(shù)組data,用來(lái)存取數(shù)據(jù),0表示周圍無(wú)雷,-1表示當(dāng)前是雷,其他數(shù)字表示周圍雷的數(shù)量。
3.對(duì)應(yīng)創(chuàng)建二維數(shù)組state,用來(lái)存取按鈕狀態(tài),0未打開(kāi),1 打開(kāi) 2旗子 3 未知(控制顯示對(duì)應(yīng)的圖標(biāo))
4.設(shè)置雷:隨機(jī)行數(shù) i 和列數(shù) j,根據(jù)隨機(jī)到 i、j 從二維數(shù)組data中取出對(duì)應(yīng)的元素值,若值不為-1(不是雷),則將此元素data[i][j]設(shè)置為-1,若值是-1(已經(jīng)是雷了),則跳過(guò),不管是否跳過(guò)都進(jìn)行遞歸,直到雷的數(shù)量達(dá)到設(shè)定的最大數(shù)量,跳出遞歸。
5.設(shè)置周圍雷的數(shù)量:計(jì)算每個(gè)元素周圍的雷數(shù)量(周圍指的是 左上、上、右上、右、右下、下、左下、左 這8個(gè)位置),循環(huán)二維數(shù)組data,判斷當(dāng)前值不是-1,則需要計(jì)算周圍雷的數(shù)量,等會(huì)細(xì)說(shuō)。
6.有任一格子被揭開(kāi),則游戲開(kāi)始并且計(jì)時(shí),當(dāng)格子被揭開(kāi)的時(shí)候分3種情況
(1)格子是雷,執(zhí)行爆炸動(dòng)畫,游戲結(jié)束。
(2)當(dāng)前格子周圍有雷,則僅僅打開(kāi)此格子,對(duì)應(yīng)顯示周圍雷數(shù)量的數(shù)字圖片。
(3)當(dāng)前格子不是雷且周圍沒(méi)有雷(data取到的元素值為0),則依次打開(kāi)周圍,并且被打開(kāi)的周圍元素也沒(méi)有雷的情況下,繼續(xù)打開(kāi)(遞歸)。
7.右鍵可以進(jìn)行插小旗、打問(wèn)號(hào)等操作(對(duì)數(shù)組state進(jìn)行的操作)。
三、代碼實(shí)現(xiàn)
3.1 設(shè)置頭部
//設(shè)置頭部
private void setHeader() {
Container container = new Container();
container.setLayout(new GridLayout(1, 3));
timeJLabel = new JLabel("時(shí)間:"+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 設(shè)置游戲區(qū)域按鈕
1.創(chuàng)建容器,并采用GridLayout 布局。
2.根據(jù)設(shè)定的ROWS、COLS創(chuàng)建二維數(shù)組,數(shù)組存儲(chǔ)JButton,給每個(gè)按鈕設(shè)置圖標(biāo)。
3.給每個(gè)按鈕添加鼠標(biāo)點(diǎn)擊事件,右鍵事件。
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 設(shè)置雷
1.隨機(jī)行數(shù) i 和列數(shù) j,根據(jù)隨機(jī)到 i、j 從二維數(shù)組data中取出對(duì)應(yīng)的元素值。
2.判斷值,若值不為-1(不是雷),則將此元素data[i][j]設(shè)置為-1,若值是-1(已經(jīng)是雷了),則跳過(guò)。
3.不管上一步是否跳過(guò)都進(jìn)行遞歸,直到雷數(shù)量達(dá)到設(shè)定的最大數(shù)量,跳出遞歸。
private void setLei() {
if(computedLeiCount==LEICOUNT){//如果達(dá)到雷的最大數(shù)量則跳出
return;
}
Random random = new Random();
int r = random.nextInt(ROWS);
int c = random.nextInt(COLS);
//0 無(wú); -1表示雷 ; 其他表示周圍的雷數(shù)量
if(data[r][c]!=-1){//如果不是雷則設(shè)置為雷
data[r][c]=-1;
computedLeiCount++;
}
setLei();//遞歸調(diào)用
}
3.4 計(jì)算周圍雷的數(shù)量并顯示
1.循環(huán)之前的二維數(shù)組data,元素值是-1(雷)跳過(guò),不是-1則繼續(xù)。
2.如果當(dāng)前元素的下標(biāo)是(i,j),則左上為(i-1,j-1),上為(i-1,j ),右上為(i-1,j+1),以此類推,如下圖所示:

3.分別取出這8個(gè)元素,并判斷他們是不是雷,如果是則計(jì)數(shù)累加,最后把這個(gè)計(jì)數(shù)賦值給元素data[i][j]。
//設(shè)置周圍雷的數(shù)量
private void setAroundLei() {
for (int i = 0; i <ROWS; i++) {
for (int j = 0; j < COLS; j++) {
if(data[i][j]!=-1){如果當(dāng)前不是雷,則判斷他周圍有幾個(gè)雷,并設(shè)置值
data[i][j] = computedLei(i,j);
}
}
}
}
//計(jì)算周圍雷的數(shù)量
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 添加點(diǎn)擊事件
1.讓代碼實(shí)現(xiàn) ActionListener
2.重寫方法actionPerformed,獲取點(diǎn)擊的按鈕進(jìn)行揭開(kāi)操作(分3種情況):
(1)格子是雷,執(zhí)行爆炸動(dòng)畫,游戲結(jié)束。
(2)當(dāng)前格子周圍有雷,則僅僅打開(kāi)此格子,顯示周圍雷數(shù)量的數(shù)字圖片。
(3)當(dāng)前格子不是雷且周圍沒(méi)有雷(data取到的元素值為0),則依次打開(kāi)周圍,并且被打開(kāi)的周圍元素也沒(méi)有雷的情況下,繼續(xù)打開(kāi)(遞歸)。
3.6 打開(kāi)指定按鈕
//打開(kāi)指定的button
private void open(int i,int j) {
JButton button = btns[i][j];
if(state[i][j]==1){//已經(jīng)打開(kāi)直接返回
return ;
}
state[i][j]=1;//設(shè)置打開(kāi)狀態(tài)
int num = data[i][j];
if(num==-1){//直接使用雷的圖片
setBtnImage(button,(ImageIcon)imageMap.get(18));
//游戲結(jié)束,并爆炸
boom(button);
}else{//如果當(dāng)前不是雷,顯示對(duì)應(yīng)數(shù)字類圖片
if(num==0){
num=9;
//顯示周圍的圖標(biāo),并且遞歸
openAround(i,j);
}
setBtnImage(button,(ImageIcon)imageMap.get(num));
setBtnEnabled(button,false);//按鈕被打開(kāi)設(shè)置不能再點(diǎn)擊了
//判定是否成功
successOrNot(1);
}
}
3.7 觸雷爆炸
爆炸采用線程來(lái)執(zhí)行,就是切換圖片,圖片切換到最后一張后線程結(jié)束,回調(diào)定義好的方法進(jìn)行結(jié)束提示、打開(kāi)所有格子等操作。
//爆炸效果
private void boom(JButton button) {
flag=true;
GameRunnable gameRunnable = new GameRunnable();
gameRunnable.setParam(button, boomImageMap,this);
thread = new Thread(gameRunnable);
thread.start();
}
//爆炸回調(diào)方法
public void reback(JButton button) {
setBtnImage(button,(ImageIcon)imageMap.get(18));
flag=false;
//設(shè)置全部按鈕打開(kāi)
openAll();
//彈出結(jié)束提示
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, "你失敗了!點(diǎn)擊上方按鈕重新開(kāi)始");
}
3.8 遞歸打開(kāi)周圍
//打開(kāi)周圍
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 鼠標(biāo)右鍵事件
1.實(shí)現(xiàn)MouseListener,重寫mouseClicked方法。
2.如果按鈕是未打開(kāi)狀態(tài)則設(shè)置為旗子(設(shè)置state數(shù)組對(duì)應(yīng)的元素值:2)。
3.如果按鈕是旗子狀態(tài)則設(shè)置為未知(設(shè)置state數(shù)組對(duì)應(yīng)的元素值:3)。
4.如果按鈕是未知狀態(tài)則設(shè)置為原來(lái)的狀態(tài)未打開(kāi)(設(shè)置state數(shù)組對(duì)應(yīng)的元素值:0)。
//鼠標(biāo)右鍵的處理
@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])){//找到對(duì)應(yīng)的按鈕
if(state[i][j]==0){//如果是未打開(kāi)狀態(tài)則設(shè)置為旗子
state[i][j]=2;
setBtnImage(button,(ImageIcon)imageMap.get(12));
curLeiCount--;
leiJLabel.setText("雷:"+curLeiCount);
//需求判斷是否成功
successOrNot(2);
}else if(state[i][j]==2){//如果是旗子狀態(tài)則設(shè)置為未知
state[i][j]=3;
setBtnImage(button,(ImageIcon)imageMap.get(13));
curLeiCount++;
leiJLabel.setText("雷:"+curLeiCount);
}else if(state[i][j]==3){//如果是未知狀態(tài)則設(shè)置為原來(lái)的未打開(kāi)
state[i][j]=0;
setBtnImage(button,(ImageIcon)imageMap.get(10));
}
}
}
}
}
}

四、勝利判定
1.判斷旗子的位置是不是正確的雷,并統(tǒng)計(jì)數(shù)量,如果統(tǒng)計(jì)到的數(shù)量剛好和設(shè)定的雷總數(shù)一樣,證明雷全部被查出了,判定為勝利。
2.如果未打開(kāi)的數(shù)量與設(shè)定雷的總數(shù)一樣,也判定為勝利。
//判斷是否成功 參數(shù)type=2表示判斷右鍵插旗子,參數(shù) type=其他 表示判斷鼠標(biāo)點(diǎn)擊
private void successOrNot(int type) {
int count=0;
if(type==2){
/*
* 1.判斷旗子的位置是否是正確的雷,并統(tǒng)計(jì)數(shù)量
* 2.如果統(tǒng)計(jì)到的數(shù)量剛好和雷的總數(shù)一樣,證明全部被查出了,判定為勝利
*/
for (int i = 0; i <ROWS; i++) {
for (int j = 0; j < COLS; j++) {
if(state[i][j]==2 && data[i][j]==-1){//是旗子,也是雷,則計(jì)數(shù)
count++;
}
}
}
}else{
//最終的未打開(kāi)的數(shù)量與雷的數(shù)量一樣,則表示成功
for (int i = 0; i <ROWS; i++) {
for (int j = 0; j < COLS; j++) {
if(state[i][j]!=1){//未打開(kāi)就計(jì)數(shù)
count++;
}
}
}
}
if(count==LEICOUNT){//成功
//關(guān)閉計(jì)時(shí)線程
gameTimeRunnable.setFlag(false);
//設(shè)置全部按鈕打開(kāi)
openAll();
//彈出結(jié)束提示
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, "恭喜你獲得了勝利!你太棒了");
}
}

最后加入重新開(kāi)始事件就完成了。
重新開(kāi)始就是重新設(shè)置相關(guān)參數(shù)。
//重新開(kāi)始游戲
private void restart() {
//關(guān)閉計(jì)時(shí)線程(可能正在進(jìn)行游戲,所以要把計(jì)時(shí)線程關(guān)閉)
gameTimeRunnable.setFlag(false);
startFlag=false;
computedLeiCount=0;
curLeiCount=10;
leiJLabel.setText("雷:"+curLeiCount);
time=0;
timeJLabel.setText("時(shí)間:"+time);
data= new int[ROWS][COLS];//存取數(shù)據(jù)
state= new int[ROWS][COLS];//存取打開(kāi)狀態(tài),0未打開(kāi),1 打開(kāi)
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);//按鈕重新設(shè)置可以點(diǎn)擊
}
}
}
到此這篇關(guān)于教你怎么用Java開(kāi)發(fā)掃雷游戲的文章就介紹到這了,更多相關(guān)Java掃雷游戲內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java中使用Lambda表達(dá)式和函數(shù)編程示例
這篇文章介紹了Java中使用Lambda表達(dá)式和函數(shù)編程示例,該文章會(huì)演示多個(gè)示列,分別是變量聲明上下文中的lambda、return語(yǔ)句上下文中的lambda、賦值上下文中的lambda、lambda在數(shù)組初始值設(shè)定項(xiàng)上下文中的用法等等,需要的朋友可以參考一下2021-10-10
基于java實(shí)現(xiàn)停車場(chǎng)管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了基于java實(shí)現(xiàn)停車場(chǎng)管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-11-11
SpringBoot擴(kuò)展點(diǎn)EnvironmentPostProcessor實(shí)例詳解
這篇文章主要介紹了SpringBoot擴(kuò)展點(diǎn)EnvironmentPostProcessor的相關(guān)知識(shí),本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-04-04
JPA findById方法和getOne方法的區(qū)別說(shuō)明
這篇文章主要介紹了JPA findById方法和getOne方法的區(qū)別,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教。2021-08-08
關(guān)于自定義過(guò)濾器獲取不到session問(wèn)題
這篇文章主要介紹了關(guān)于自定義過(guò)濾器獲取不到session問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01

