2018年6月8日 星期五

[JAVA] 踩地雷

※ 此程式碼並非優化版本,僅供個人學習用途,若有任何問題請告知。

※ 準備1個jpg圖檔:flag.jpg。
※ 已知bug:第一次點擊時,若為炸彈,按鈕不會變成紅色,且所有按鈕皆無法點擊。
※ 待新增功能:
1. 新增上方列表,包含剩餘炸彈數及遊戲遊玩時間。
2. 炸彈數歸零判斷。

程式碼如下:
import java.awt.CardLayout;
import java.awt.Color;
import java.awt.Dialog;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.Image;
import java.awt.Menu;
import java.awt.MenuBar;
import java.awt.MenuItem;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
public class Minesweeper extends JFrame implements ActionListener, MouseListener {
public int bomballnum = 15, block_width = 9, block_height = 9, jf_x = 0,
jf_y = 0, jf_width = 0, jf_height = 0, clickcount = 0;
public int[] first_click_location = new int[2];
public int[][] map_hint, map_bomb_location;
public boolean[][] map_bomb;
public boolean gameover = false, card_pan_first = true;
JButton[][] btn;
Dialog dialog;
JPanel main, index_panel, bomb_panel;
ImageIcon flag = new ImageIcon("src\\G\\flag.jpg");
public int jf_getWidth() {return this.getWidth();} //取得視窗寬度
public int jf_getHeight() {return this.getHeight();} //取得視窗高度
public int jf_getX() {return this.getX();} //取得視窗X位置
public int jf_getY() {return this.getY();} //取得視窗Y位置
public Minesweeper(int x, int y, int width, int height) { //設置版面
/*視窗設定*/
super("踩地雷");
jf_x = x;
jf_y = y;
jf_width = width;
jf_height = height;
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
this.setResizable(false);
this.setBounds(jf_x, jf_y, jf_width, jf_height);
flag.setImage(flag.getImage().getScaledInstance(30, 30, Image.SCALE_DEFAULT));
/*選單列*/
MenuBar menu = new MenuBar();
this.setMenuBar(menu);
Menu file = new Menu("檔案");
MenuItem backindex = new MenuItem("回到主選單");
file.add(backindex);
MenuItem restart = new MenuItem("重新開始");
file.add(restart);
MenuItem line = new MenuItem("------------");
file.add(line);
MenuItem exit = new MenuItem("結束遊戲");
file.add(exit);
menu.add(file);
Menu edit = new Menu("編輯");
Menu selectmodel = new Menu("難度選擇");
MenuItem mod_easy = new MenuItem("(簡易)9x9");
selectmodel.add(mod_easy);
MenuItem mod_normal = new MenuItem("(普通)16x16");
selectmodel.add(mod_normal);
MenuItem mod_hard = new MenuItem("(困難)30x16");
selectmodel.add(mod_hard);
edit.add(selectmodel);
MenuItem selectcount = new MenuItem("自訂遊戲");
edit.add(selectcount);
menu.add(edit);
Menu help = new Menu("幫助");
MenuItem about = new MenuItem("關於此程式");
help.add(about);
menu.add(help);
/*面板*/
CardLayout cards = new CardLayout();
main = new JPanel(); //主面板
main.setLayout(cards);
index_panel = new JPanel(); //第一個面板 開始畫面
index_panel.setLayout(null);
index_panel.setBackground(Color.GRAY);
bomb_panel = new JPanel(); //第二個面板 炸彈畫面
main.add("1", index_panel);
main.add("2", bomb_panel);
this.add(main);
/*開始畫面 - 排版設置*/
JLabel text_title = new JLabel("踩 地 雷");
text_title.setFont(new Font("標楷體", Font.BOLD, 65));
text_title.setForeground(Color.BLACK);
text_title.setBounds(60, 25, 350, 150);
index_panel.add(text_title);
JLabel text_strat = new JLabel("開始遊戲");
text_strat.setFont(new Font(Font.DIALOG, Font.BOLD, 30));
text_strat.setForeground(Color.BLACK);
text_strat.setBounds(130, 210, 130, 30);
index_panel.add(text_strat);
JLabel text_exit = new JLabel("離開遊戲");
text_exit.setFont(new Font(Font.DIALOG, Font.BOLD, 30));
text_exit.setForeground(Color.BLACK);
text_exit.setBounds(130, 260, 130, 30);
index_panel.add(text_exit);
text_strat.addMouseListener(new MouseListener() { //開始遊戲文字
public void mouseReleased(MouseEvent arg0) {}
public void mousePressed(MouseEvent arg0) {}
public void mouseExited(MouseEvent arg0) { //滑鼠離開時 變成黑色
text_strat.setForeground(Color.BLACK);
}
public void mouseEntered(MouseEvent arg0) { //滑鼠經過時 變成白色
text_strat.setForeground(Color.WHITE);
}
public void mouseClicked(MouseEvent arg0) {
clean(jf_getX(), jf_getY(), jf_getWidth(), jf_getHeight());
bomb(9, 9, 15, first_click_location);
cards.show(main, "2");
card_pan_first = false;
}
});
text_exit.addMouseListener(new MouseListener() { //結束遊戲文字
public void mouseReleased(MouseEvent arg0) {}
public void mousePressed(MouseEvent arg0) {}
public void mouseExited(MouseEvent arg0) { //滑鼠離開時 變成黑色
text_exit.setForeground(Color.BLACK);
}
public void mouseEntered(MouseEvent arg0) { //滑鼠經過時 變成白色
text_exit.setForeground(Color.WHITE);
}
public void mouseClicked(MouseEvent arg0) {System.exit(0);}
});
/*炸彈畫面 - 排版設置*/
bomb(block_width, block_height, bomballnum, first_click_location);
/*選單列功能*/
backindex.addActionListener(new ActionListener() { //回到主選單
public void actionPerformed(ActionEvent arg0) {
clean(x, y, width, height);
gameover = false;
cards.show(main, "1");
card_pan_first = true;
}
});
restart.addActionListener(new ActionListener() { //重新開始
public void actionPerformed(ActionEvent arg0) {
clean(jf_x, jf_y, jf_width, jf_height);
bomb(block_width, block_height, bomballnum, first_click_location);
}
});
exit.addActionListener(new ActionListener() { //結束遊戲
public void actionPerformed(ActionEvent arg0) {
System.exit(0);
}
});
selectcount.addActionListener(new ActionListener() { //選擇炸彈數目
public void actionPerformed(ActionEvent arg0) {
edit(x, y, width, height, bomb_panel);
}
});
mod_normal.addActionListener(new ActionListener() { //設置難度普通
public void actionPerformed(ActionEvent arg0) {
if(card_pan_first == false) {
clean(x, y, width+350, height+150);
gameover = false;
block_width = 16;
block_height = 16;
bomballnum = (block_width * block_height) / 5;
bomb(block_width, block_height, bomballnum, first_click_location);
}
}
});
mod_hard.addActionListener(new ActionListener() { //設置難度困難
public void actionPerformed(ActionEvent arg0) {
if(card_pan_first == false) {
clean(50, 10, width+900, height+350);
gameover = false;
block_width = 30;
block_height = 16;
bomballnum = (block_width * block_height) / 5;
bomb(block_width, block_height, bomballnum, first_click_location);
}
}
});
about.addActionListener(new ActionListener() { //關於此程式
public void actionPerformed(ActionEvent arg0) {
JFrame info_jf = new JFrame("關於此程式");
info_jf.setBounds(jf_getX()+80, jf_getY()+130, 250, 130);
info_jf.setDefaultCloseOperation(info_jf.DISPOSE_ON_CLOSE);
info_jf.setResizable(false);
JPanel info_jp = new JPanel();
info_jp.setLayout(null);
info_jf.add(info_jp);
JLabel info_text = new JLabel("練習用程式");
info_text.setBounds(90, 5, 100, 50);
info_jp.add(info_text);
JLabel info_text2 = new JLabel("感謝使用");
info_text2.setBounds(95, 30, 100, 50);
info_jp.add(info_text2);
info_jf.setVisible(true);
}
});
this.setVisible(true);
}
public void edit(int x, int y, int width, int height, JPanel bomb_panel) { //自訂遊戲
JFrame edit_jf = new JFrame("自訂遊戲");
edit_jf.setBounds(x+100, y+80, 230, 290);
edit_jf.setDefaultCloseOperation(edit_jf.DISPOSE_ON_CLOSE);
edit_jf.setResizable(false);
/*面板*/
JPanel jp_edit = new JPanel();
jp_edit.setLayout(null);
edit_jf.add(jp_edit);
/*文字顯示*/
JLabel text_width = new JLabel("地圖寬度:");
text_width.setFont(new Font(Font.DIALOG, Font.BOLD, 15));
text_width.setBounds(20, 30, 80, 30);
jp_edit.add(text_width);
JLabel text_height = new JLabel("地圖高度:");
text_height.setFont(new Font(Font.DIALOG, Font.BOLD, 15));
text_height.setBounds(20, 90, 80, 30);
jp_edit.add(text_height);
JLabel text_bomb = new JLabel("地雷數目:");
text_bomb.setFont(new Font(Font.DIALOG, Font.BOLD, 15));
text_bomb.setBounds(20, 150, 80, 30);
jp_edit.add(text_bomb);
/*單行輸入*/
JTextField jtf_width = new JTextField();
jtf_width.setBounds(110, 30, 80, 30);
jp_edit.add(jtf_width);
JTextField jtf_height = new JTextField();
jtf_height.setBounds(110, 90, 80, 30);
jp_edit.add(jtf_height);
JTextField jtf_bomb = new JTextField();
jtf_bomb.setBounds(110, 150, 80, 30);
jp_edit.add(jtf_bomb);
/*按鈕*/
JButton btn_confirm = new JButton("確認");
btn_confirm.setBounds(25, 205, 70, 30);
jp_edit.add(btn_confirm);
JButton btn_cancel = new JButton("取消");
btn_cancel.setBounds(120, 205, 70, 30);
jp_edit.add(btn_cancel);
btn_confirm.addActionListener(new ActionListener() { //確認
public void actionPerformed(ActionEvent e) {
System.out.println(jtf_bomb.getText());
clean(x, y, width, height); //清空地圖
gameover = false;
block_width = Integer.parseInt(jtf_width.getText());
block_height = Integer.parseInt(jtf_height.getText());
bomballnum = Integer.parseInt(jtf_bomb.getText());
bomb(block_width, block_height, bomballnum, first_click_location);
edit_jf.setVisible(false);
}
});
btn_cancel.addActionListener(new ActionListener() { //取消
public void actionPerformed(ActionEvent arg0) {
jtf_width.setText("");
jtf_height.setText("");
jtf_bomb.setText("");
edit_jf.setVisible(false);
}
});
edit_jf.setVisible(true);
}
public void bomb(int block_width, int block_height, int bomballnum, int first_click_location[]) {
int bomb_num = 0, location = 0; //bomb_num為目前產生之炸彈數目 bomballnum為總炸彈數目
btn = new JButton[block_width][block_height]; //所有按鈕
map_hint = new int[block_width][block_height]; //地圖上所有提示
map_bomb = new boolean[block_width][block_height]; //地圖上所有炸彈
map_bomb_location = new int[2][bomballnum]; //所有炸彈的位置
first_click_location = new int[2]; //第一次點擊的位址
bomb_panel.setLayout(new GridLayout(block_height, block_width));
gameover = false;
System.out.println("目前地圖寬度:"+block_width+"\n目前地圖高度:"+block_height
+"\n目前地雷數目:"+bomballnum);
for(int i = 0; i < block_width; ++i) { //預設所有炸彈為false 下方進行判斷變為true
for(int j = 0; j < block_height; ++j) {
map_bomb[i][j] = false;
btn[i][j] = new JButton();
btn[i][j].setBackground(Color.WHITE);
btn[i][j].setFont(new Font(Font.DIALOG, Font.BOLD, 1));
btn[i][j].addMouseListener(this);
btn[i][j].addActionListener(this);
btn[i][j].setActionCommand(i + " " + j);
bomb_panel.add(btn[i][j]);
}
}
if(gameover == false) {
while(bomb_num < bomballnum) { //隨機設置炸彈
for(int i = 0; i < block_width; ++i) {
for(int j = 0; j < block_height; ++j) {
if(((int)(Math.random() * 50 + 0) > 40) &&
map_bomb[i][j] != true && bomb_num < bomballnum
&& i != first_click_location[0] && j != first_click_location[1]) {
map_bomb[i][j] = true; //將炸彈設為true
++bomb_num; //炸彈產生個數+1
}
}
}
}
for(int i = 0; i < block_width; ++i) { //紀錄炸彈位置
for(int j = 0; j < block_height; ++j) {
if(map_bomb[i][j]) { //當這格炸彈為ture
System.out.print("*"); //控制台顯示當前所有炸彈位置
map_bomb_location[0][location] = i;
map_bomb_location[1][location] = j;
++location;
}
else {System.out.print("-");}
}
System.out.println();
}
for(int i = 0; i < bomb_num; ++i) { //印出所有炸彈陣列位址
System.out.println("第 "+ (i + 1) +" 個\t--->\t[" + map_bomb_location[0][i] +
"," + map_bomb_location[1][i] + "]");
}
System.out.println();
}
}
public void clean(int x, int y, int width, int height) { //清空地圖
jf_x = x; jf_y = y; jf_width = width; jf_height = height;
gameover = false;
this.setBounds(jf_x, jf_y, jf_width, jf_height);
for(int i = 0; i < block_width; i++) {
for(int j = 0; j < block_height; j++) {
btn[i][j].setText("");
btn[i][j].setBackground(Color.WHITE);
}
}
bomb_panel.removeAll();
}
public void turn(int x, int y, int bombcount) { //翻開
btn[x][y].setBackground(Color.GRAY);
int[][] direct = {{x-1, y-1}, {x, y-1}, {x+1, y-1}, {x-1, y}, {x+1, y}, {x-1, y+1}, {x, y+1}, {x+1, y+1}};
if(bombcount != 0) { //若此格不為空白 則翻開並顯示周圍炸彈數
btn[x][y].setFont(new Font(Font.DIALOG, Font.BOLD, 15));
btn[x][y].setText(Integer.toString(bombcount));
}
else if(bombcount == 0) { //若此格為空白 則翻開並繼續搜尋周圍的按鈕
for(int i = 0; i < 8; ++i) {
if(direct[i][0] != -1 && direct[i][1] != -1
&& direct[i][0] < block_width && direct[i][1] < block_width
&& btn[direct[i][0]][direct[i][1]].getBackground().equals(Color.WHITE)) {
turn(direct[i][0], direct[i][1], hint(direct[i][0], direct[i][1]));
}
}
}
}
public int hint(int x, int y) { //判斷周圍炸彈數
int aroundbomb = 0;
//左上 左 左下 上 下 右上 右 右下
int[][] direct = {{x-1, y-1}, {x, y-1}, {x+1, y-1}, {x-1, y}, {x+1, y},
{x-1, y+1}, {x, y+1}, {x+1, y+1}};
for(int i = 0; i < 8; ++i) {
//若是位址為 -1 或 大於邊界 則挑掉
if(direct[i][0] != -1 && direct[i][1] != -1 && direct[i][0] < block_width
&& direct[i][1] < block_width) {
if(map_bomb[direct[i][0]][direct[i][1]]) {++aroundbomb;}
}
}
map_hint[x][y] = aroundbomb;
return aroundbomb;
}
public void actionPerformed(ActionEvent e) {
String[] command = e.getActionCommand().split(" "); //以空格作為分隔符號 擷取所傳入之i和j
int x = Integer.parseInt(command[0]), y = Integer.parseInt(command[1]);
++clickcount;
/*若點選的為炸彈且不為第一次點擊 則遊戲結束*/
if(map_bomb[x][y] && gameover != true && clickcount != 1) {
btn[x][y].setBackground(Color.RED);
gameover = true;
/*遊戲結束訊息*/
dialog = new Dialog(this, "遊戲結束");
dialog.setResizable(false);
dialog.setLayout(null);
dialog.setBounds(jf_getX()+80, jf_getY()+130, 240, 160);
JLabel dialog_jlb = new JLabel("BOMB! GAME OVER!");
dialog_jlb.setBounds(30, 60, 190, 20);
Font dialog_jlb_text = new Font("Lucida Sans", Font.BOLD, 18);
dialog_jlb.setFont(dialog_jlb_text);
dialog.add(dialog_jlb);
JButton dialog_btn = new JButton("確認");
dialog_btn.setBounds(80, 105, 70, 30);
dialog.add(dialog_btn);
dialog.setVisible(true);
dialog_btn.addActionListener(new ActionListener() { //關閉dialog視窗
public void actionPerformed(ActionEvent arg0) {dialog.dispose();}
});
}
else if(clickcount == 1 && map_bomb[x][y]) { //若是第一次點擊就碰到炸彈 則重新排列
first_click_location[0] = x; first_click_location[1] = y;
clean(jf_x, jf_y, jf_width, jf_height);
bomb(block_width, block_height, bomballnum, first_click_location);
System.out.println("bomb!!!bomb!!!bomb!!!");
}
else if(gameover != true) {turn(x, y, hint(x, y));}
}
public void mouseClicked(MouseEvent e) { //右鍵設置旗子
if(e.getButton() == MouseEvent.BUTTON3 && gameover != true) { //BUTTON3為右鍵
if(((JButton)e.getSource()).getIcon() != flag) {
((JButton)e.getSource()).setIcon(flag);
}
else {
((JButton)e.getSource()).setIcon(null);
((JButton)e.getSource()).setBackground(Color.WHITE);
}
}
}
public void mouseEntered(MouseEvent arg0) {}
public void mouseExited(MouseEvent arg0) {}
public void mousePressed(MouseEvent arg0) {}
public void mouseReleased(MouseEvent arg0) {}
public static void main(String[] args) {new Minesweeper(400, 100, 400, 400);}
}

結果如下:



沒有留言:

張貼留言