摘要:最后一次更新于效果演示圖實現經典掃雷游戲本掃雷游戲有以下功能如果點中炸彈會顯示炸彈。代碼如下所示此類是用來運行掃雷游戲程序的。游戲窗口的高度。炸彈的總數返回檢查結果的布爾值。指定方塊的查詢結果,用布爾類型表示。確認結果用布爾值表示。
最后一次更新于 2019/07/08
效果演示圖 Java 實現經典掃雷游戲本掃雷游戲有以下功能:
如果點中炸彈會顯示炸彈。
玩家左鍵點擊方塊能顯示該方塊周圍會出現幾個炸彈,如果不存在炸彈的話掃描范圍會被放大。
滿足各種行數,列數和炸彈個數要求。
對不同水平的玩家提供不同的游戲難度級別。
如果玩家單擊鼠標右鍵會顯示紅旗。
如果玩家雙擊鼠標右鍵會顯示問號。
如果玩家游戲挑戰失敗顯示所有炸彈隱藏的地方以及玩家失誤標記的地方。
如果玩家挑戰成功顯示所有的炸彈(原本炸彈的位置有可能已被玩家用小紅旗標識了)。
源代碼包括抽象類和接口。我將程序分為三個部分來介紹:GameDriver,Library,UserInterface。
游戲驅動這部分相當簡單(因為只有一個主函數)。Driver 類被用于作為啟動游戲的接口。
代碼如下所示:
package GameDriver; import UserInterface.Menu; /** * @author Hephaest * @since 3/21/2019 8:41 PM * 此類是用來運行掃雷游戲程序的。 */ public class Driver { public static void main(String[] Args) { // 游戲啟動的時候會附帶一個選項菜單窗口。 new Menu("Minesweeper"); } }工具庫
包 Library 內只有兩個文件。第一個是抽象類 Bomb,它用于存儲和有關游戲窗口的物理信息。
代碼如下所示:
package Library; import UserInterface.GameBoard; /** * @author Hephaest * @since 3/21/2019 8:41 PM * 這個抽象類中的抽象方法會在被繼承時實現。 */ public abstract class Bomb { /** 游戲窗口實例 **/ protected GameBoard board; /** 實例的高度 **/ protected int boardHeight; /** 實例的寬度 **/ protected int boardWidth; /** * Create bombs, which can be placed on a GameBoard. * @param board the GameBoard upon which user clicks on. */ public Bomb(GameBoard board) { this.board = board; // 真正加入計算的高和寬去需要減去填充邊距的長度。 boardHeight = (board.getHeight() - 20) / 20; boardWidth = (board.getWidth() - 20) / 20; } /** * 該方法將會被用于分布炸彈的位置。 */ protected abstract void reproduceBomb(); }
第二個工具就是 TimeChecker 接口,它使用將毫秒時間轉換成相對應的時間表達,將會被用于 SmartSquare 類。
代碼如下所示:
package Library; /** * @author Hephaest * @since 3/21/2019 8:41 PM * 這個接口有個靜態方法通過給定的毫秒時間換算成相對應的時間表達。 */ public interface TimeChecker { /** * 根據程序給定的運行時間返回程序運行時間的標準表達。 * @param time 在游戲開始和結束之間的時間。 * @return 總用時的文本描述。 */ static String calculateTime(long time) { int CONVERT_TO_SEC = 1000; int CONVERT_TO_OTHERS = 60; int ms = (int) time; int sec = ms / CONVERT_TO_SEC; int min = sec / CONVERT_TO_OTHERS; // 把秒轉換成分。 int hr = min / CONVERT_TO_OTHERS; // 把分轉化成小時。 if (hr == 0) { if(min == 0) { if (sec == 0) return ms + " ms"; else return sec + " sec " + ms % 1000 + " ms"; } else return min + " min " + sec % CONVERT_TO_OTHERS + " sec " + ms % CONVERT_TO_SEC + " ms"; } else return hr + " hour " + min % CONVERT_TO_OTHERS + " min " + sec % CONVERT_TO_OTHERS + " sec " + ms % CONVERT_TO_SEC + " ms"; } }用戶界面
下方的 UML 圖 可以幫助您理解以下幾個類之間的關系:
菜單Menu 類為玩家提供了4種難度級別的選項:初級,中級,高級和自定義。尤其對于自定義來說,程序需要檢驗玩家的輸入是否符合要求。如果玩家確定選擇了以后,選擇菜單消失,啟動游戲窗口。
代碼如下所示:
package UserInterface; import javax.swing.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.regex.Pattern; /** * 該類繼承 JFrame 類。 * 該類實現了 ActionListener 里對不用點擊事件的反饋。 * 該類提供4個選項供玩家選擇。 * 這4個選項分別是 "初級","中級","高級" 和 "自定義"。 * 在點擊 "New Game" 按鈕之后,菜單窗口自動關閉。 * @author Hephaest * @since 3/21/2019 8:41 PM */ public class Menu extends JFrame implements ActionListener { private JButton start; private JRadioButton beginner, intermediate, advanced, custom; private JTextField width, height, mines; /** * 創建一個給定標題的菜單。 * @param title 菜單上的標題。 */ public Menu(String title) { // 設置菜單標題。 setTitle(title); // 創建菜單子標題。 JLabel subtitle = new JLabel("Difficulty"); subtitle.setBounds(100,10,100,20); add(subtitle); // 創建 "初級" 選擇按鈕。 beginner = new JRadioButton("Beginner"); beginner.setBounds(40,40,150,20); add(beginner); // 設置 "初級" 選擇的描述。 JLabel bDescFirstLine = new JLabel("10 mines"); bDescFirstLine.setBounds(70,60,100,20); JLabel bDescSecondLine = new JLabel("10 x 10 tile grid"); bDescSecondLine.setBounds(70,80,100,20); add(bDescFirstLine); add(bDescSecondLine); // 創建 "中級" 選擇按鈕。 intermediate=new JRadioButton("Intermediate"); intermediate.setBounds(40,100,150,20); add(intermediate); // 設置 "中級" 選擇的描述。 JLabel iDescFirstLine = new JLabel("40 mines"); iDescFirstLine.setBounds(70,120,100,20); JLabel iDescSecondLine = new JLabel("16 x 16 tile grid"); iDescSecondLine.setBounds(70,140,100,20); add(iDescFirstLine); add(iDescSecondLine); // 創建 "高級" 選擇按鈕。 advanced=new JRadioButton("Advanced"); advanced.setBounds(40,160,160,20); add(advanced); // 設置 "高級" 選擇的描述。 JLabel aDescFirstLine = new JLabel("100 mines"); aDescFirstLine.setBounds(70,180,100,20); JLabel aDescSecondLine = new JLabel("30 x 25 tile grid"); aDescSecondLine.setBounds(70,200,100,20); add(aDescFirstLine); add(aDescSecondLine); // 創建 "自定義" 選擇按鈕。 custom = new JRadioButton("Custom"); custom.setBounds(40,220,100,20); add(custom); // 設置 "自定義" 選擇的描述。 JLabel widthLabel = new JLabel("Width (10-30):"); widthLabel.setBounds(70,240,80,20); add(widthLabel); width = new JTextField(); width.setBounds(170,240,40,20); add(width); JLabel heightLabel = new JLabel("height (10-25):"); heightLabel.setBounds(70,260,90,20); add(heightLabel); height = new JTextField(); height.setBounds(170,260,40,20); add(height); JLabel mineLabel = new JLabel("Mines (10-100):"); mineLabel.setBounds(70,280,90,20); add(mineLabel); mines = new JTextField(); mines.setBounds(170,280,40,20); add(mines); // 創建 "開始游戲" 選擇按鈕。 start = new JButton("New Game"); start.setBounds(80,320,100,20); add(start); // 初始化每個文本框的編輯狀態。 width.setEditable(false); height.setEditable(false); mines.setEditable(false); // 在每個按鍵上添加監聽事件。 custom.addActionListener(this); beginner.addActionListener(this); intermediate.addActionListener(this); advanced.addActionListener(this); start.addActionListener(this); // 確保單選。 ButtonGroup group = new ButtonGroup(); group.add(beginner); group.add(intermediate); group.add(advanced); group.add(custom); // 初始化菜單實例。 beginner.setSelected(true); setSize(280,400); setLayout(null); setVisible(true); setResizable(false); setDefaultCloseOperation(EXIT_ON_CLOSE); } /** * 實現 ActionListener 接口。 * @param e 點擊事件。 */ public void actionPerformed(ActionEvent e) { // 如果用戶選擇 "自定義",設置文本框為可編輯狀態。 if (e.getSource() == custom) { width.setEditable(true); height.setEditable(true); mines.setEditable(true); } else if (e.getSource() == start) { // 如果用戶點擊 "開始游戲" 按鈕,獲得相對應的炸彈總數,游戲窗口的長和寬。 int boardWidth = 0; int boardHeight = 0; int bombs = 0; boolean errorFlag = false; if (beginner.isSelected()) { boardWidth = 10; boardHeight = 10; bombs = 10; } else if (intermediate.isSelected()) { boardWidth = 16; boardHeight = 16; bombs = 40; } else if (advanced.isSelected()) { boardWidth = 30; boardHeight = 25; bombs = 100; } else { if(!checkValid(width.getText(), height.getText(), mines.getText())) { // 設置標記并在窗口上彈出錯誤提示。 errorFlag = true; JOptionPane.showMessageDialog(null, "Please enter correct numbers!"); } else { boardWidth = Integer.parseInt(width.getText()); boardHeight = Integer.parseInt(height.getText()); bombs = Integer.parseInt(mines.getText()); } } if(!errorFlag) { // 關閉當前菜單窗口并彈出與之對應的游戲窗口。 this.dispose(); GameBoard b = new GameBoard("Minesweeper", boardWidth, boardHeight); new ProduceBombs(b, bombs); ((SmartSquare) b.getSquareAt(0, 0)).setStartTime(System.currentTimeMillis()); } } else{ // 如果玩家即沒有選擇 "Custom" 也沒有點擊 "New Game" 按鈕,這些文本框要設置成不可編輯的狀態。 width.setEditable(false); height.setEditable(false); mines.setEditable(false); } } /** * 檢查玩家的輸入是否符合要求。 * @param bWidth 游戲窗口的寬度。 * @param bHeight 游戲窗口的高度。 * @param bomb 炸彈的總數 * @return 返回檢查結果的布爾值。 */ private boolean checkValid(String bWidth, String bHeight, String bomb) { Pattern pattern = Pattern.compile("[0-9]*"); if (bWidth == null || bHeight== null || bomb == null) return false; else if (bWidth.isEmpty() || bHeight.isEmpty() || bomb.isEmpty()) return false; else if (!pattern.matcher(bWidth).matches() || !pattern.matcher(bHeight).matches() || !pattern.matcher(bomb).matches()) return false; else if (Integer.parseInt(bWidth) < 10 || Integer.parseInt(bWidth) > 30 || Integer.parseInt(bHeight) < 10 || Integer.parseInt(bHeight) > 25 || Integer.parseInt(bomb) < 10 || Integer.parseInt(bomb) > 100) return false; else return Integer.parseInt(bWidth) * Integer.parseInt(bHeight) >= Integer.parseInt(bomb); } }窗口
GameBoard 類可以創建一個新的窗口,但要求這個窗口不被縮放不然會影響到計算時需要用到的長和寬。除此之外,在游戲窗口里的每一個小方塊應該有自己的事件監聽器。
代碼如下所示:
package UserInterface; import javax.swing.*; import java.awt.*; import java.awt.event.*; /** * 該類為游戲窗口提供圖形模型。 * 該類創建了可點擊的矩形面板 * 如果玩家點擊了小方塊,會在響應的 SmartSquare 實例種調用回調函數。 * 該類是基于平鋪的游戲的基礎。 * @author joe finney */ public class GameBoard extends JFrame implements ActionListener { private JPanel boardPanel = new JPanel(); private int boardHeight; private int boardWidth; private GameSquare[][] board; /** * 創建給定大小的游戲窗口。 * 一旦該類實例被創建,窗口將可視化。 * * @param title 窗口欄的標題。 * @param width 以方塊作單位的窗口的寬。 * @param height 以方塊作單位的窗口的高。 */ public GameBoard(String title, int width, int height) { super(); this.boardWidth = width; this.boardHeight = height; // 創建游戲初始方塊。 this.board = new GameSquare[width][height]; // 新建窗口。 setTitle(title); setSize(20 + width * 20,20 + height * 20); setContentPane(boardPanel); setResizable(false); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); boardPanel.setLayout(new GridLayout(height,width)); for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { board[x][y] = new SmartSquare(x, y, this); board[x][y].addActionListener(this); boardPanel.add(board[x][y]); } } // 使窗口可視化。 setVisible(true); } /** * 返回給定位置的方塊。 * @param x 給定方塊的 x 的坐標。 * @param y 給定方塊的 y 的坐標。 * @return 返回給定位置的方塊。 * 如果 x 和 y 的位置都在邊界范圍內,則給出響應的方塊對象,否則返回 null. */ public GameSquare getSquareAt(int x, int y) { if (x < 0 || x >= boardWidth || y < 0 || y >= boardHeight) return null; return board[x][y]; } public void actionPerformed(ActionEvent e) { // 被點擊的方塊,需要處理點擊情況。 GameSquare b = (GameSquare)e.getSource(); b.clicked(); } }方塊
抽象類 GameSquare 只提供基本的方法。
代碼如下所示:
package UserInterface; import javax.swing.*; import java.net.URL; /** * 該類描述了方塊對象中主要的屬性和方法。 * 該類是抽象的,將在之后被繼承,被繼承的抽象方法需要被實現。 * @author joe finney */ public abstract class GameSquare extends JButton { /** 方塊的 x 坐標 **/ protected int xLocation; /** 方塊的 y 坐標 **/ protected int yLocation; /** 方塊所在的游戲窗口 **/ protected GameBoard board; /** * 創建一個會被放在游戲窗口的方塊對象。 * @param x 方塊相對于游戲窗口的 x 坐標。 * @param y 方塊相對于游戲窗口的 y 坐標。 * @param filename 圖片文件所在位置。 * @param board 游戲窗口。 */ public GameSquare(int x, int y, URL filename, GameBoard board) { super(new ImageIcon(filename)); this.board = board; xLocation = x; yLocation = y; } /** * 根據所給的文件地址更改當前方塊渲染的圖像。 * * @param filename 需要更新的圖片的地址, */ public void setImage(URL filename) { this.setIcon(new ImageIcon(filename)); } /** * 用戶點擊調用的方法。 */ public abstract void clicked(); }
然而,SmartSquare 類繼承了 GameSquare 類并重寫了 clicked() 方法。 click() 方法需要實現以下功能:
玩家左鍵點擊方塊能顯示該方塊周圍會出現幾個炸彈。
如果玩家單擊鼠標右鍵會顯示紅旗。
如果玩家雙擊鼠標右鍵會顯示問號
除此之外,該對象的實例擁有兩個特殊的屬性: guessThisSquareIsBomb 和 thisSquareHasTraversed。這些屬性都是布爾類型的。 guessThisSquareIsBomb 為真當玩家右鍵單擊鼠標。 thisSquareHasTraversed 是用來防止無限遞歸的。一旦用戶左鍵點擊過方塊該方塊的 thisSquareHasTraversed 為真。
代碼如下所示:
package UserInterface; import Library.TimeChecker; import javax.swing.*; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; /** * 該類繼承 GameSquare 類。 * 該類實現了 ActionListener 和 MouseListener 的方法,對不同的點擊事件有不同的響應。 * 每一個方塊有自己獨一無二的二維坐標和屬性值。 * 一旦玩家鼠標左擊點擊該類的實例,會馬上顯示出該方塊周圍存在幾個炸彈。 * 該類提供一個彈出窗口無論玩家挑戰成功或失敗。 * @author Hephaest * @since 3/21/2019 8:41 PM */ public class SmartSquare extends GameSquare implements MouseListener, TimeChecker { /** 炸彈在該方塊的存在與否 **/ private boolean thisSquareHasBomb; /** 玩家是否有在該方塊上設置紅旗 **/ private boolean guessThisSquareIsBomb; /** 該方塊是否被遍歷過 **/ private boolean thisSquareHasTraversed; /** 該方塊的 x 坐標 **/ private int xLocation; /** 該方塊的 y 坐標 **/ private int yLocation; /** 該方塊記錄的游戲開始時間 **/ private long startTime; /** * 創建該類的新實例并放到游戲窗口上去。 * @param x 該方塊相對于游戲窗口的 x 的坐標。 * @param y 該方塊相對于游戲窗口的 y 的坐標。 * @param board 該方塊所在的游戲窗口。 */ public SmartSquare(int x, int y, GameBoard board) { // 初始化時將方塊變成灰色。 super(x, y, SmartSquare.class.getResource("/block.png"), board); // 賦值地址。 xLocation = x; yLocation = y; // 初始化屬性。 thisSquareHasBomb = false; thisSquareHasTraversed = false; guessThisSquareIsBomb = false; startTime = 0; // 添加右鍵監聽器。 addMouseListener(this); } /** * 為炸彈是否存在于該方塊設定值。 * @param result 給定的布爾值。 */ protected void setBombExist(boolean result) { thisSquareHasBomb = result; } /** * 獲取炸彈是否存在于該方塊的結果。 * @return 布爾結果。 */ protected boolean getBombExist() { return thisSquareHasBomb; } /** * 返回該方塊是否遍歷過的狀態。 * @return 該方塊的狀態。 */ protected boolean getTraverse() { return thisSquareHasTraversed; } /** * 根據給定值設置該方塊當前的狀態。 * @param result 布爾值表示當前的狀態。 */ protected void setTraverse(boolean result) { thisSquareHasTraversed = result; } /** * 返回該方塊是否插上小紅旗的查詢結果。 * @return 返回查詢狀態。 */ protected boolean getGuessThisSquareIsBomb() { return guessThisSquareIsBomb; } /** * 記錄游戲開始的時間戳。 * @param time 以毫秒表示的時間。 */ protected void setStartTime(long time) { startTime = time; } /** * 返回游戲剛開始的時間。 * @return 返回以毫秒表示的時間。 */ protected long getStartTime() { return startTime; } /** * 從 GameSquare 實現的抽象方法。 * 一旦獲得點擊事件,檢測炸彈的存在和擴大空白的面積。 */ public void clicked() { CheckSquare cq = new CheckSquare(board); guessThisSquareIsBomb = false; if(thisSquareHasBomb) { /* * 如果該方塊包含炸彈,顯示炸彈。 * 彈出失敗窗口。 */ setImage(SmartSquare.class.getResource("/bombReveal.png")); long costTime = System.currentTimeMillis() - ((SmartSquare) board.getSquareAt(0, 0)).getStartTime(); cq.showBomb(xLocation, yLocation); window("You used " + TimeChecker.calculateTime(costTime) +". Do you want to try again?", "Game Over", new ImageIcon(SmartSquare.class.getResource("/failFace.png"))); } else{ thisSquareHasTraversed = false; /* * 如果該方塊不包含炸彈,計算它周圍8個格子里炸彈的總數。 * 如果周遭也沒有炸彈,擴大空白區域直到檢測到炸彈或者越界。 */ cq.countBomb(xLocation, yLocation); if (cq.isSuccess()) { long costTime = System.currentTimeMillis() - ((SmartSquare) board.getSquareAt(0, 0)).getStartTime(); cq.showBomb(xLocation, yLocation); window("You win this game in " + TimeChecker.calculateTime(costTime) + "! Do you want to try again?","Congratulations", new ImageIcon(SmartSquare.class.getResource("/passFace.jpg"))); } } } /** * 一個實現彈出窗口的方法。 * @param msg 要顯示在窗口上的信息。 * @param title 窗口的標題。 * @param img the 窗口的圖標。 */ public void window(String msg, String title, Icon img) { int choose = JOptionPane.showConfirmDialog(board, msg, title, JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE,img); if (choose == JOptionPane.YES_OPTION) { new Menu("Mine sweeper"); } // 關閉彈出窗口并重返菜單。 board.dispose(); } /** * 實現對右鍵鼠標的反饋。 * @param e 玩家點擊方塊的事件。 */ @Override public void mouseClicked(MouseEvent e) { // 如果玩家右擊方塊。 if (e.getButton() == MouseEvent.BUTTON3) { int clickCount = e.getClickCount(); // 顯示小紅旗。 if (clickCount == 1) { setImage(SmartSquare.class.getResource("/redFlag.png")); guessThisSquareIsBomb = true; } // 顯示問號。 if (clickCount == 2) { setImage(SmartSquare.class.getResource("/questionMark.png")); guessThisSquareIsBomb = false; } } } // 下列鼠標事件不處理。 @Override public void mousePressed(MouseEvent e) {} @Override public void mouseReleased(MouseEvent e) {} @Override public void mouseEntered(MouseEvent e) {} @Override public void mouseExited(MouseEvent e) {} }
最重要的類是 CheckSquare,它會檢查其余未遍歷的方塊,并顯示所選方塊周圍的炸彈數量。如果周圍炸彈總數為0,則以八方作為中心探測炸彈,以此類推。遞歸終止條件是該方塊已被遍歷過或者所查詢方塊超過游戲窗口的邊界。注意八方的方塊指的是左上,正上,右上,正左,正右,左下,正下,右下。
判斷成功的條件是 總方塊數 = 炸彈的總數(生成后即被標記遍歷過) + 用戶點擊的方塊的個數(用戶左擊方塊,該方塊即被標記遍歷過)。因此簡化了判斷條件,程序只需要去遍歷所有的方塊,如果每個方塊都被遍歷過了,即說明玩家挑戰成功。
代碼如下所示:
package UserInterface; /** * @author Hephaest * @since 3/21/2019 8:41 PM * 該類用于計算指定方塊周圍的炸彈總數。 */ public class CheckSquare { /** 游戲窗口實例 **/ private GameBoard board; /** 實例的高度**/ private int boardHeight; /** 實例的寬度 **/ private int boardWidth; private static final int[] distantX = {-1, 0, 1}; private static final int[] distantY = {-1, 0, 1}; /** * 在游戲窗口中創建該類的實例。 * @param board 玩家點擊的游戲窗口。 */ public CheckSquare(GameBoard board) { this.board = board; // 長寬都要減去邊距的長度。 boardHeight = (board.getHeight() - 20) / 20; boardWidth = (board.getWidth() - 20) / 20; } /** * 返回指定位置方塊的檢查結果。 * @param x 指定方塊的 x 坐標。 * @param y 指定方塊的 y 坐標。 * @return 指定方塊的查詢結果,用布爾類型表示。 */ private boolean hasKickedBoundary(int x, int y) { return x < 0 || x >= boardWidth || y < 0 || y >= boardHeight; } /** * 返回玩家是否挑戰成功的確認結果。 * @return 確認結果用布爾值表示。 */ protected boolean isSuccess() { // 確保調用該方法時計數器從0開始。 int count = 0; for (int y = 0; y < boardHeight; y++) { for (int x = 0; x < boardWidth; x++) { if (((SmartSquare) board.getSquareAt(x, y)).getTraverse()) count++; } } return count == boardHeight * boardWidth; // 也可以寫成這種形式。 // for (int y = 0; y < boardHeight; y++) // { // for (int x = 0; x < boardWidth; x++) // { // if (!((SmartSquare) board.getSquareAt(x, y)).getTraverse()) return false; // } // } // return true; } /** * 該方法會顯示所有炸彈的位置,檢驗用戶猜測是否正確。 * @param currentX 該方塊的 x 坐標。 * @param currentY 該方塊的 y 坐標。 */ protected void showBomb(int currentX, int currentY) { for (int y = 0; y < boardHeight; y++) { for (int x = 0; x < boardWidth; x++) { if (currentX == x && currentY == y){} else if (((SmartSquare) board.getSquareAt(x, y)).getBombExist()) board.getSquareAt(x, y).setImage(CheckSquare.class.getResource("/bomb.png")); else if(((SmartSquare) board.getSquareAt(x, y)).getGuessThisSquareIsBomb()) board.getSquareAt(x, y).setImage(CheckSquare.class.getResource("/flagWrong.png")); // Wrong guess! } } } /** * 該方法計算指定方塊周圍的炸彈總數。 * 如果該方塊周圍沒有炸彈,把該方塊繪制成白色并擴大檢測范圍。 * 直到周圍的炸彈總數不為0。該方法用遞歸算法實現。 * @param currentX 該方塊的 x 坐標 * @param currentY 該方塊的 y 坐標。 */ protected void countBomb(int currentX, int currentY) { // 確保每次調用時計數器從0開始計數。 int count = 0; SmartSquare currentObject; if (hasKickedBoundary(currentX, currentY)) return; // 無需往下檢驗,直接跳出循環。 else if(((SmartSquare)board.getSquareAt(currentX, currentY)).getTraverse()) return; // 無需往下檢驗,直接跳出循環。 else { // 聲明 SmartSquare 實例。 SmartSquare squareObject; // 獲取當前方塊對象。 currentObject = (SmartSquare)board.getSquareAt(currentX, currentY); currentObject.setTraverse(true); /* * 檢測周圍 8 個方塊: * 如果所指定的方塊位置超出游戲窗口邊界,跳出本次循環。 * 如果所指定的方塊位置恰恰是自己,跳出本次循環。 * 否則檢驗該方塊周圍是否含有炸彈。如果有,計算機累加。 */ for (int x : distantX) { for (int y: distantY) { if (hasKickedBoundary(currentX + x, currentY + y)){} else if (x == 0 && y == 0){} else{ squareObject = (SmartSquare)board.getSquareAt(currentX + x, currentY + y); count = squareObject.getBombExist() ? count + 1 : count; } } } } /* * 如果循環后計數器仍為0,用該方塊周圍的方塊們作為中心繼續探測。 */ if (count != 0) currentObject.setImage(CheckSquare.class.getResource( "/" + count + ".png")); else { // 將當前方塊渲染為空白。 currentObject.setImage(CheckSquare.class.getResource("/0.png")); countBomb(currentX - 1, currentY -1); // 左上。 countBomb(currentX, currentY -1); // 正上。 countBomb(currentX + 1, currentY -1); // 右上。 countBomb(currentX - 1, currentY); // 正左。 countBomb(currentX + 1, currentY); // 正右。 countBomb(currentX - 1, currentY + 1); // 左下。 countBomb(currentX, currentY + 1); // 正下。 countBomb(currentX + 1, currentY + 1); // 右下。 } } }炸彈
隨機分配炸彈位置很容易造成位置沖突。ProduceBombs 類專門用于處理這個問題,每個方塊都有一個屬性 thisSquareHasBomb 用來記錄當前的方塊是否為炸彈。 如果是,程序就會重新分配炸彈的位置直到不再和其他炸彈位置沖突為止。這個方法主要利用 尾遞歸 來優化內存占用空間。
代碼如下所示:
package UserInterface; import Library.Bomb; import java.util.Random; /** * 該類用于在游戲窗口生成炸彈。 * 該類主要用尾遞歸算法來分配炸彈的位置。 * @version V1.0 * @author Hephaest * @since 2019-03-12 20:18 */ public class ProduceBombs extends Bomb { /** * 在給定游戲窗口創建該類的實例。 * 使用遞歸函數避免炸彈位置重疊。 * @param board 用戶點擊的游戲窗口。 * @param number 炸彈的總數。 */ public ProduceBombs(GameBoard board, int number) { super(board); int count =0; do { reproduceBomb(); count++; }while (count < number); } /** * 該類用于在游戲窗口隨機生成炸彈的位置。如果該位置已被占,則通過調用自己重新生成新的位置,以此類推。 */ public void reproduceBomb() { Random r = new Random(); int xLocation = r.nextInt(boardWidth); int yLocation = r.nextInt(boardHeight); SmartSquare square = (SmartSquare) board.getSquareAt(xLocation, yLocation); if (!square.getBombExist()) { // 標記該方塊含有炸彈并被遍歷過了。 square.setBombExist(true); square.setTraverse(true); } else { reproduceBomb(); } } }源碼
已在源碼地址中分享了本程序的下載地址。如果我的文章可以幫到您,勞煩您點進源碼點個 ★ Star 哦!
https://github.com/Hephaest/M...
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/75229.html
摘要:掃雷小游戲掃雷小游戲簡介一分析與實現設計棋盤放置雷以及排雷二掃雷小游戲演示三源碼總結掃雷小游戲簡介想必很多人小時候電腦沒網的時候都玩兒過這個經典的小游戲,也都被它折磨過。這次我們用語言來實現一個簡單的掃雷小游戲。 ...
摘要:上一期咱們用語言實現了三子棋的小游戲語言實現三子棋今天我們再來寫個掃雷的游戲,說起掃雷,相信大家都不陌生,可能許多朋友還是玩掃雷的高手。 ? ? ?上一期咱們用C語言實現了三子棋的小游戲? C語言實現三子棋? ? ? ?今天我們再來寫個掃雷的游戲,說起掃雷,相信大家都不陌生,可能許多朋友還是...
摘要:函數游戲菜單請選擇掃雷游戲退出游戲選擇錯誤解析函數內部利用時間戳,形成隨機數,主要目的是實現游戲中地雷的隨機埋放。 前言 本篇文章使用C語言實現簡單小游戲---掃雷。(文章最后有完整代碼鏈接) 想必大多數人都玩過或者了解過掃雷的游戲規則,但是在這里,我們在一起重溫一下掃雷的游戲規則,也更好...
摘要:寫在前面我們已經寫過了三子棋小游戲肯定沒玩過癮,我們再寫個掃雷小游戲吧目錄寫在前面認識游戲游戲規則游戲框架游戲實現效果展示全部代碼文件文件文件認識游戲相信大家對掃雷都不陌生每臺電腦必備的小游戲游戲規則就是在規定的時間將 ...
閱讀 2609·2021-11-18 10:02
閱讀 2278·2021-09-30 09:47
閱讀 1745·2021-09-27 14:01
閱讀 3109·2021-08-16 11:00
閱讀 3163·2019-08-30 11:06
閱讀 2391·2019-08-29 17:29
閱讀 1529·2019-08-29 13:19
閱讀 444·2019-08-26 13:54