Android?Flutter實現(xiàn)在多端運行的掃雷游戲
前言
當我們回憶起小時候的經典電腦游戲,掃雷一定是其中之一。這個簡單而富有挑戰(zhàn)的游戲不僅考驗我們的智力和耐性,而且在完成后還會讓我們感到一種無與倫比的成就感。現(xiàn)在,您可以使用Flutter來重新體驗這個經典游戲,無論您是Flutter新手還是老手,都能通過本文,讓您在Flutter的世界中開發(fā)出一個令人滿意的掃雷游戲。
代碼倉庫:https://github.com/taxze6/flutter_game_collection/tree/main/mine_sweeping
注:本文demo未使用任何第三方插件,F(xiàn)lutter版本3.7.3
效果圖
話不多說,先上效果圖。(包含不同端、不同難度、不同游戲主題)
Windows端

網頁端

Android端

開始實現(xiàn)
第一步:定義游戲設置
定義GameSetting單例類,確保掃雷程序中只有一個實例,并且該實例可以被全局訪問,主要用于共享資源。
class GameSetting {
GameSetting._();
}然后定義一個私有的、靜態(tài)的、不可變的 _default 對象,它是 GameSetting 類的默認實例,該實例在第一次使用時被創(chuàng)建。再定義一個 GameSetting 工廠構造函數,它通過返回 _default 對象實現(xiàn)了單例模式的實例化,該工廠構造函數是唯一可以實例化 GameSetting 對象的方法。
static final GameSetting _default = GameSetting._(); factory GameSetting() => _default;
完成了單例類的基本定義,現(xiàn)在再來定義與掃雷相關的,先定義游戲的難度。在掃雷中游戲的難度主要有兩部分組成:
棋盤格子的數量
///游戲的難度,默認為8*8 int difficulty = 8;
雷的數量
///雷的數量 (格子總數 * 0.18 向下取整),通常掃雷的雷數在0.16-0.2之間。 int get mines => (difficulty * difficulty * 0.18).floor();
最后在定義一些游戲的顏色主題:
List<Color> c_5ADFD0 = [ Color(0xFF299794), Color(0xFF2EC4C0), Color(0xFF2EC4C0) ]; List<Color> c_A0BBFF = [ Color(0xFF5067C5), Color(0xFF838CFF), Color(0xFFA0BBFF), ]; ///默認主題 Color themeColor = Color(0xFF5ADFD0);
第二步:定義游戲參數
在進行掃雷游戲的時候,需要記錄棋盤格子上每個格子的參數,記錄格子是否被標記為雷、是否被翻開。也需要記錄游戲是否獲勝、是否踩到了地雷。
late List<List<int>> board; // 棋盤 late List<List<bool>> revealed; // 記錄格子是否被翻開 late List<List<bool>> flagged; // 記錄格子是否被標記 late bool gameOver; // 游戲是否結束 late bool win; // 是否獲勝
其他初始化參數:
late int numRows; // 行數 late int numCols; // 列數 late int numMines; // 雷數 //游戲時間 late int _playTime;
第三步:編寫掃雷初始化游戲邏輯
定義了游戲的參數后,在游戲開始時,需要對其進行賦值。
numRows = gameSetting.difficulty; numCols = gameSetting.difficulty; numMines = gameSetting.mines; // 初始化棋盤 board = List.generate(numRows, (_) => List.filled(numCols, 0)); // 初始化格子是否被翻開 revealed = List.generate(numRows, (_) => List.filled(numCols, false)); // 初始化格子是否被標記 flagged = List.generate(numRows, (_) => List.filled(numCols, false)); // 將游戲定義為未結束 gameOver = false; // 將游戲定義為還未獲勝 win = false;
通過while循環(huán)在棋盤上隨機放置地雷,直到放置的地雷數量達到預定的 numMines。
int numMinesPlaced = 0;
while (numMinesPlaced < numMines) {
...
}
使用 Random().nextInt 方法生成兩個隨機數 i 和 j,分別用于表示棋盤中的行和列。
int i = Random().nextInt(numRows); int j = Random().nextInt(numCols);
通過 board[i][j] != -1 的判斷語句,檢查這個位置是否已經放置了地雷。如果沒有則將 board[i][j] 的值設置為 -1,表示在這個位置放置了地雷,并將numMinesPlaced 的值加 1。
if (board[i][j] != -1) {
board[i][j] = -1;
numMinesPlaced++;
}
放完了地雷,那么就到了掃雷的核心邏輯,計算每個非地雷格子周圍的地雷數量,然后將計算得到的地雷數量保存在對應的格子上。具體實現(xiàn)的邏輯為:通過兩個嵌套的 for 循環(huán)遍歷整個棋盤,內層的兩個嵌套循環(huán)會計算這個格子周圍的所有格子中地雷的數量,并將這個數量保存在 count 變量中。
for (int i = 0; i < numRows; i++) {
for (int j = 0; j < numCols; j++) {
...
}
}
循環(huán)中具體的邏輯是:在每個單元格上,如果它不是地雷(值不為為-1)則內部嵌套兩個循環(huán)遍歷當前單元格周圍的所有單元格,計算地雷數量并存儲在當前單元格中。
if (board[i][j] != -1) {
int count = 0;
for (int i2 = max(0, i - 1); i2 <= min(numRows - 1, i + 1); i2++) {
for (int j2 = max(0, j - 1);
j2 <= min(numCols - 1, j + 1);
j2++) {
if (board[i2][j2] == -1) {
count++;
}
}
}
board[i][j] = count;
}
第四步:編寫用戶交互游戲邏輯
只要用戶點擊了,就要將格子設置為翻開了。
void reveal(int i, int j) {
revealed[i][j] = true;
}
當用戶點擊了一個格子后,我們需要判斷以下幾點:
如果翻開的是地雷
if (board[i][j] == -1) {
//將所有的地雷翻開,告訴用戶所有的地雷位置
for (int i2 = 0; i2 < numRows; i2++) {
for (int j2 = 0; j2 < numCols; j2++) {
if (board[i2][j2] == -1) {
revealed[i2][j2] = true;
}
}
}
//游戲結束
gameOver = true;
//結束動畫
...
}
如果點擊的格子周圍都沒有雷就自動翻開相鄰的空格
if (board[i][j] == 0) {
for (int i2 = max(0, i - 1); i2 <= min(numRows - 1, i + 1); i2++) {
for (int j2 = max(0, j - 1); j2 <= min(numCols - 1, j + 1); j2++) {
if (!revealed[i2][j2]) {
reveal(i2, j2);
}
}
}
}
檢查是否勝利
///它會遍歷整個棋盤,檢查每一個未被翻開的格子是否都是地雷,
bool checkWin() {
for (int i = 0; i < numRows; i++) {
for (int j = 0; j < numCols; j++) {
if (board[i][j] != -1 && !revealed[i][j]) {
return false;
}
}
}
return true;
}
if (checkWin()) {
win = true;
gameOver = true;
_timer?.cancel();
//獲勝動畫
...
}
第五步:封裝格子
定義枚舉類BlockType,用于判斷不同的狀態(tài)下顯示不同的格子樣式。
enum BlockType {
//數字
figure,
//雷
mine,
//標記
label,
//未標記(未被翻開)
unlabeled,
}
封裝格子的代碼其實很簡單,根據不同的狀態(tài)封裝即可,這里就不過多展示了。
第六步:游戲布局
此處只分析游戲棋盤的布局。
通過GridView.builder構建棋盤,使用SliverGridDelegateWithFixedCrossAxisCount實現(xiàn)每一行具有相同數量的列。
GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: numCols,
childAspectRatio: 1.0,
),
itemBuilder: (BuildContext context, int index) {
...
}
)
通過對index的整除和取模,得到行和列,然后根據每個格子的當前狀態(tài)對封裝好的格子布局傳入不同的參數。
itemBuilder: (BuildContext context, int index) {
int i = index ~/ numCols;
int j = index % numCols;
BlockType blockType;
//格子被翻開
if (revealed[i][j]) {
//是地雷
if (board[i][j] == -1) {
blockType = BlockType.mine;
} else {
blockType = BlockType.figure;
}
} else {
//被用戶標記
if (flagged[i][j]) {
blockType = BlockType.label;
} else {
blockType = BlockType.unlabeled;
}
}
return GestureDetector(
onTap: () => reveal(i, j),
onDoubleTap: () => toggleFlag(i, j),
child: BlockContainer(
backColor: gameSetting.themeColor,
value: revealed[i][j] && board[i][j] != 0 ? board[i][j] : 0,
blockType: blockType,
),
);
},
其中,如果雙擊格子代表標記或取消標記,定義了一個方法toggleFlag。
///標記雷
void toggleFlag(int i, int j) {
if (!gameOver) {
setState(() {
flagged[i][j] = !flagged[i][j];
});
}
}
到這里,就完成了對掃雷這款游戲的實現(xiàn)。更改游戲的主題狀態(tài)或游戲難度,只需更改不同的初始化參數即可。
優(yōu)化-第七步:游戲時間
有一個計時器,會大大提高用戶玩算法類、解謎類這樣游戲的樂趣,例如魔方。在Flutter中通過Timer.periodic去實現(xiàn)計時器是很簡答的,就不過多講述了,主要看下如何格式化為時鐘的形式:
String get playTime {
int minutes = (_playTime ~/ 60); // 計算分鐘數
int seconds = (_playTime % 60); // 計算秒數
//padLeft方法用于補齊不足兩位的數字,第一個參數是補齊后的字符串總長度,第二個參數是用于補齊的字符。
return '${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}';
}以上就是Android Flutter實現(xiàn)在多端運行的掃雷游戲的詳細內容,更多關于Android Flutter掃雷游戲的資料請關注腳本之家其它相關文章!
相關文章
如何通過Battery Historian分析Android APP耗電情況
Android 從兩個層面統(tǒng)計電量的消耗,分別為軟件排行榜及硬件排行榜。它們各有自己的耗電榜單,軟件排行榜為機器中每個 App 的耗電榜單,硬件排行榜則為各個硬件的耗電榜單。這兩個排行榜的統(tǒng)計是互為獨立,互不干擾的2021-06-06
BootStrapValidator與My97日期校驗的實例代碼
這篇文章給大家介紹了bootstrapvalidator與my97日期校驗的實例代碼,代碼簡單易懂,非常不錯,具有參考借鑒價值,需要的朋友參考下吧2017-01-01
Android 幾種屏幕間跳轉的跳轉Intent Bundle
這篇文章主要介紹了Android 幾種屏幕間跳轉的跳轉Intent Bundle,有需要的朋友可以參考一下2013-12-12
AndroidStudio4.0 New Class的坑(小結)
這篇文章主要介紹了AndroidStudio4.0 New Class的坑,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-07-07
外層豎向ScrollView,里層橫向ScrollView滑動沖突的解決方法
下面小編就為大家?guī)硪黄鈱迂Q向ScrollView,里層橫向ScrollView滑動沖突的解決方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-04-04

