高中有一段時間特愛玩踩地雷,就對踩地雷萌生了一股特殊情感。沒想到幾年後,一個心血來潮就自己刻出來了 XD
我從大學開始自學前端,喜歡尋找有趣的題目做 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:
2. 放置地雷
為了避免遊戲還沒開始就結束了,第一次點擊後才會插入地雷,先產生一個 0, 1, …, n 的 index 陣列代表每個格子並移除一開始點擊的格子:
隨機挑選 mines 個格子放入地雷,同時把周圍格子的 minesAround + 1,這裡用到 lodash.sampleSize,可隨機從陣列取出 n 個元素。
由於一個格子周圍最多有 8 個地雷,我先把地雷的 minesAround 設為 -10 保證為負數:
3. 滑鼠位置和狀態
我認為這部分是精髓!我在 mousemove 時更新滑鼠正處於哪個格子上,並針對 mousedown 事件做出三種反應才能完全符合原生踩地雷的行為,滑鼠狀態的用途待會兒就提到:
4. 凹陷的格子
被按住或是已開啟的格子會呈凹陷狀,改變格子的外框 CSS 就有不錯的效果(Codepen)。
5. 打開格子
mouseup 時若狀態是單開會依照 minesAround 有以下反應:
- minesAround < 0:踩到地雷,遊戲結束
- minesAround > 0:開啟格子,顯示周圍地雷數量 (minesAround)
- minesAround = 0:自動展開周圍格子
自動展開的做法是先把每個格子加上 walked: false
代表是否掃過,接著從點擊的格子開始遞迴,回傳所有要開啟的格子:
- minesAround < 0、掃過的、已插旗:return []
- minesAround > 0:return [自己]
- minesAround = 0:return [自己和周圍格子]
6. 自動開啟格子
mouseup 時若狀態是多開會自動開啟周圍的格子,前提是當前格子周圍已經插上等同 minesAround 數量的旗子,當然,插錯的話多開會直接爆炸。
7. 插旗
右鍵 mousedown 且只有右鍵是按下狀態時會直接觸發插旗,另外可以把旗幟改成問號或是復原,遊戲左上角也會顯示地雷數減插旗數。
8. 支援手機
踩地雷有三種難度可以選,本來當選擇難度為「困難 」(16 x 30 格) 時,畫面會塞不下所有格子。我以 scroll 的方式解決,為了避免誤開格子,只要 touchStart 後有 touchmove 就不開啟格子,另外按住格子超過 150 毫秒再放開就會插旗。
後記
這個 project 完成後,看到有些人玩完回饋:「第一次踩地雷破關成就達成!」,覺得自己做了一件小善事呢 😂
對於自學程式而言,打通任督二脈最快的方法就是實作 project。以此 project 來說,我就理清了許多有關滑鼠事件的觀念,非常受用!
感謝您讀到這兒,如果你喜歡這篇文章或是我的作品,請不吝幫我拍拍手或是給我一顆 Star,若內容有任何問題非常歡迎一起討論或協助修正。