週末無聊,用Java
寫了一個掃雷程式,說起來,這個應該是在學校的時候,寫會比較好玩,畢竟自己實現一個小遊戲,還是比較好玩的。說實話,掃雷程式裡面核心的東西,只有點選的時候,去觸發更新資料這一步。
Swing 是過時了,但是好玩不會過時,不喜勿噴
原始碼的地址:https://github.com/Damaer/Gam...
下面講講裡面的設計:
- 資料結構設計
- 檢視和資料儘可能分開
- 點選時候使用
BFS
掃描 - 判斷成功失敗
資料結構設計
在這個程式裡面,為了方便,使用了全域性的資料類Data
類來維護整個遊戲的資料,直接設定為靜態變數,也就是一次只能有一個遊戲視窗執行,否則會有資料安全問題。(僅僅是為了方便)
有以下的資料(部分程式碼):
public class Data {
// 遊戲狀態
public static Status status = Status.LOADING;
// 雷區大小
public static int size = 16;
// 雷的數量
public static int numOfMine = 0;
// 表示是否有雷,1:有,0沒有
public static int[][] maps = null;
// 是否被訪問
public static boolean[][] visited = null;
// 周邊雷的數量
public static int[][] nums = null;
// 是否被標記
public static boolean[][] flags = null;
// 上次被訪問的塊座標
public static Point lastVisitedPoint = null;
// 困難模式
private static DifficultModeEnum mode;
...
}
需要維護的資料如下:
- 遊戲狀態:是否開始,結束,成功,失敗等等
- 模式:簡單,中等或者困難,這個會影響自動生成的雷的數量
- 雷區的大小:16*16的小方塊
- 雷的數量:與模式選擇有關,是個隨機數
- 標識每個方塊是否有雷:最基礎的資料,生成之後需要同步更新這個資料
- 標識每個方塊是否被掃過:預設沒有掃過
- 每個方塊周邊類雷的數量:生成的時候同步計算該結果,不想每次點選後再計算,畢竟是個不會更新的資料,一勞永逸
- 標識方塊是否被標記:掃雷的時候我們使用小旗子標記方塊,表示這裡是雷,標識完所有的雷的時候,成功
- 上次訪問的方塊座標:這個其實可以不記錄,但是為了表示爆炸效果,與其他的雷展示不一樣,故而記錄下來
檢視與資料分開
儘量遵循一個原則,檢視與資料或者資料變更分開,方便維護。我們知道Java
裡面是用Swing
來畫圖形介面,這個東西確實難畫,檢視寫得比較複雜但是畫不出什麼東西。
檢視與資料分開,也是幾乎所有框架的優秀特點,主要是方便維護,如果檢視和資料糅合在一起,更新資料,還要操作檢視,那就會比較亂。(當然我寫的是粗糙版本,只是簡單區分了一下)
在這個掃雷程式裡面基本都是點選事件,觸發了資料變更,資料變更後,呼叫檢視重新整理,檢視渲染的邏輯與資料變更的邏輯分開維護。
每個小方塊都新增了點選事件,Data.visit(x, y)
是資料重新整理,repaintBlocks()
是重新整理檢視,具體的程式碼就不放了,有興趣可以Github
看看原始碼:
new MouseListener() {
@Override
public void mouseClicked(MouseEvent e) {
if (Data.status == Status.GOING) {
int c = e.getButton(); // 得到按下的滑鼠鍵
Block block = (Block) e.getComponent();
int x = block.getPoint_x();
int y = block.getPoint_y();
if (c == MouseEvent.BUTTON1) {
Data.visit(x, y);
} else if (c == MouseEvent.BUTTON3) {// 推斷是滑鼠右鍵按下
if (!Data.visited[x][y]) {
Data.flags[x][y] = !Data.flags[x][y];
}
}
}
repaintBlocks();
}
}
這裡很遺憾的一點是每個方塊裡面還有一個背景的`url
沒有抽取出來,這個是變化的資料,不應該放在檢視裡面:
public class Block extends JPanel {
private int point_x;
private int point_y;
private String backgroundPath = ImgPath.DEFAULT;
public Block(int x, int y) {
this.point_x = x;
this.point_y = y;
setBorder(BorderFactory.createEtchedBorder());
}
}
重新設定方塊背景,需要居中處理,重新繪製,重寫void paintComponent(Graphics g)
方法即可:
@Override
protected void paintComponent(Graphics g) {
refreshBackground();
URL url = getClass().getClassLoader().getResource(backgroundPath);
ImageIcon icon = new ImageIcon(url);
if (backgroundPath.equals(ImgPath.DEFAULT) || backgroundPath.equals(ImgPath.FLAG)
|| backgroundPath.equals(String.format(ImgPath.NUM, 0))) {
g.drawImage(icon.getImage(), 0, 0, getWidth(), getHeight(), this);
} else {
int x = (int) (getWidth() * 0.1);
int y = (int) (getHeight() * 0.15);
g.drawImage(icon.getImage(), x, y, getWidth() - 2 * x, getHeight() - 2 * y, this);
}
}
BFS掃描
BFS
,也稱為廣度優先搜尋,這算是掃雷裡面的核心知識點,也就是點選的時候,如果當前方塊是空的,那麼就會觸發掃描周邊的方塊,同時周邊方塊如果也是空的,會繼續遞迴下去,我用了廣度優先搜尋,也就是先將它們放到佇列裡面,取出來,再判斷是否為空,再將周邊符合的方塊新增進去,進行一一處理。
廣度優先搜尋在這裡不展開,其本質是優先搜尋與其直接關聯的資料,也就是方塊周圍的點,這也是為什麼需要佇列的原因,我們需要佇列來儲存遍歷的順序。
public static void visit(int x, int y) {
lastVisitedPoint.x = x;
lastVisitedPoint.y = y;
if (maps[x][y] == 1) {
status = Status.FAILED;
// 遊戲結束,暴露所有的雷
} else {
// 點選的不是雷
Queue<Point> points = new LinkedList<>();
points.add(new Point(x, y));
while (!points.isEmpty()) {
Point point = points.poll();
visited[point.x][point.y] = true;
if (nums[point.x][point.y] == 0) {
addToVisited(points, point.x, point.y);
}
}
}
}
public static void addToVisited(Queue<Point> points, int i, int j) {
int x = i - 1;
while (x <= i + 1) {
if (x >= 0 && x < size) {
int y = j - 1;
while (y <= j + 1) {
if (y >= 0 && y < size) {
if (!(x == i && j == y)) {
// 沒訪問過且不是雷
if (!visited[x][y] && maps[x][y] == 0) {
points.add(new Point(x, y));
}
}
}
y++;
}
}
x++;
}
}
值得注意的是,周邊的點,如果它的周邊沒有雷,那麼會繼續擴充,但是隻要周邊有雷,就會停止擴充,只會顯示數字。
判斷成功失敗
當挖到雷的時候,就失敗了,同時會將所有的雷暴露出來,為了展示我們當前挖到的點,有爆炸效果,我們記錄了上一步操作的點,在重新整理檢視後,彈窗提示:
判斷成功則需要將所有的雷遍歷一次,判斷是否被標記出來,這是我簡單想的規則,忘記了掃雷是不是這樣了,或者可以實現將其他所有非雷區都挖空的時候,成功,也是可以的。
總結
掃雷,一個簡單的遊戲,無聊的時候可以嘗試一下,但是Java
的Swing
真的難用,想找一個資料驅動檢視修改的框架,但是貌似沒有,那就簡單實現一下。其實大部分時間都在找圖示,測試UI
,核心的程式碼並沒有多少。
在這裡推薦一下icon
網站:https://www.iconfont.cn/
,即使是沒有什麼技術含量的掃雷,寫一下還是挺有趣的。
【作者簡介】:
秦懷,公眾號【秦懷雜貨店】作者,個人網站:http://aphysia.cn,技術之路不在一時,山高水長,縱使緩慢,馳而不息。