前端自學筆記|用 React 重現經典踩地雷

sh1zuku
開發者特攻隊
Published in
7 min readMay 1, 2019

高中有一段時間特愛玩踩地雷,就對踩地雷萌生了一股特殊情感。沒想到幾年後,一個心血來潮就自己刻出來了 XD

趕緊試玩 👉 https://mines.now.sh

我從大學開始自學前端,喜歡尋找有趣的題目做 side project 磨練手感。前陣子就花了兩個月用 React 刻出 Windows XP,有夠累但成就感爆表,今天來分享我如何刻出懷舊踩地雷小遊戲。

本文會解釋我如何以 React Hooks 完整重現 Windows XP 踩地雷。我會把踩地雷拆解成幾個關鍵步驟,從中說明重要觀念和細節。附上專案原始碼,歡迎參考。

讓我們開始吧!

有關踩地雷

踩地雷最早於1992年被加入Windows 3.1。在那個剛開始有電腦的年代,設計這個小遊戲的目的,其實不是在玩,而是在幫助用戶培養使用滑鼠的能力。

所以要能夠寫出這個小遊戲,最大重點為:滑鼠點擊觸發不同的事件組合,比如:如何更新滑鼠正處於哪個格子上,並針對滑鼠事件做出不同反應,符合踩地雷的行為。

遊戲介紹

玩家要找出藏有地雷的格子並插上旗子。點開沒有地雷的格子,會顯示周圍的九宮格內有幾個地雷,找出所有非地雷的格子就獲勝、若踩到地雷則遊戲結束。

遊戲特色

開始刻之前,我會先列出這個 project 有哪些關鍵功能一定要做出來,就像產品開發的一開始要先列出 user stories,設想使用者要如何和你的程式互動。

  • 第一次點開格子不會踩到地雷。
  • 滑鼠按下但還沒放開時,即將開啟的格子會呈凹陷狀。
  • 若點開的格子周圍沒有地雷,沒地雷的格子會自動展開,也是呈凹現狀。
  • 點選右鍵,在格子上放旗幟提示自己哪些格子是地雷。
  • 若數字周圍已經放置同數量的旗幟,在數字上同時放開左右鍵會開啟周圍的格子。

正文開始

遊戲的資料結構如下,記錄著難度、遊戲狀態、欄列數量、地雷數量、格子狀態:

state: {
difficulty: 當前難度,
status: 遊戲進行狀態,
rows: 列數,
columns: 欄數,
mines: 地雷數,
ceils: 格子們 {
state: 格子狀態,
minesAround: 周圍地雷數,
opening: 正在開啟
}
}

格子的三個屬性:

  • state: 未開啟、已開啟、旗幟等等
  • minesAround: 周圍的地雷數量,負數代表自身是地雷
  • opening: 是否正在開啟,是則呈凹陷狀

踩地雷的一大重點是滑鼠點擊觸發不同的事件組合,我另外也整理了一篇文章詳細介紹 MouseEvent 的應用,可以搭配著讀。

1. 取得周圍 indexes

很多動作都要先取得周圍格子的 index,我寫了一個 getNearIndexes 來取得中心為 index 的格子周圍有哪些 indexes:

https://github.com/ShizukuIchi/minesweeper/blob/master/src/Minesweeper/index.js#L317

2. 放置地雷

為了避免遊戲還沒開始就結束了,第一次點擊後才會插入地雷,先產生一個 0, 1, …, n 的 index 陣列代表每個格子並移除一開始點擊的格子:

隨機挑選 mines 個格子放入地雷,同時把周圍格子的 minesAround + 1,這裡用到 lodash.sampleSize,可隨機從陣列取出 n 個元素。
由於一個格子周圍最多有 8 個地雷,我先把地雷的 minesAround 設為 -10 保證為負數:

https://github.com/ShizukuIchi/minesweeper/blob/master/src/Minesweeper/index.js#L273

3. 滑鼠位置和狀態

我認為這部分是精髓!我在 mousemove 時更新滑鼠正處於哪個格子上,並針對 mousedown 事件做出三種反應才能完全符合原生踩地雷的行為,滑鼠狀態的用途待會兒就提到:

https://github.com/ShizukuIchi/minesweeper/blob/master/src/Minesweeper/MinesweeperView.js#L137

4. 凹陷的格子

被按住或是已開啟的格子會呈凹陷狀,改變格子的外框 CSS 就有不錯的效果(Codepen)。

同時按住左右鍵會讓整個九宮格凹陷

5. 打開格子

mouseup 時若狀態是單開會依照 minesAround 有以下反應:

  • minesAround < 0:踩到地雷,遊戲結束
  • minesAround > 0:開啟格子,顯示周圍地雷數量 (minesAround)
  • minesAround = 0:自動展開周圍格子

自動展開的做法是先把每個格子加上 walked: false 代表是否掃過,接著從點擊的格子開始遞迴,回傳所有要開啟的格子:

  • minesAround < 0、掃過的、已插旗:return []
  • minesAround > 0:return [自己]
  • minesAround = 0:return [自己和周圍格子]
https://github.com/ShizukuIchi/minesweeper/blob/master/src/Minesweeper/index.js#L293

6. 自動開啟格子

mouseup 時若狀態是多開會自動開啟周圍的格子,前提是當前格子周圍已經插上等同 minesAround 數量的旗子,當然,插錯的話多開會直接爆炸。

想要快速破關就要使用這個功能

7. 插旗

右鍵 mousedown 且只有右鍵是按下狀態時會直接觸發插旗,另外可以把旗幟改成問號或是復原,遊戲左上角也會顯示地雷數減插旗數。

8. 支援手機

踩地雷有三種難度可以選,本來當選擇難度為「困難 」(16 x 30 格) 時,畫面會塞不下所有格子。我以 scroll 的方式解決,為了避免誤開格子,只要 touchStart 後有 touchmove 就不開啟格子,另外按住格子超過 150 毫秒再放開就會插旗。

後記

這個 project 完成後,看到有些人玩完回饋:「第一次踩地雷破關成就達成!」,覺得自己做了一件小善事呢 😂

對於自學程式而言,打通任督二脈最快的方法就是實作 project。以此 project 來說,我就理清了許多有關滑鼠事件的觀念,非常受用!

感謝您讀到這兒,如果你喜歡這篇文章或是我的作品,請不吝幫我拍拍手或是給我一顆 Star,若內容有任何問題非常歡迎一起討論或協助修正。

--

--