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

網(wǎng)頁(yè)端

Android端

開(kāi)始實(shí)現(xiàn)
第一步:定義游戲設(shè)置
定義GameSetting單例類,確保掃雷程序中只有一個(gè)實(shí)例,并且該實(shí)例可以被全局訪問(wèn),主要用于共享資源。
class GameSetting {
GameSetting._();
}然后定義一個(gè)私有的、靜態(tài)的、不可變的 _default 對(duì)象,它是 GameSetting 類的默認(rèn)實(shí)例,該實(shí)例在第一次使用時(shí)被創(chuàng)建。再定義一個(gè) GameSetting 工廠構(gòu)造函數(shù),它通過(guò)返回 _default 對(duì)象實(shí)現(xiàn)了單例模式的實(shí)例化,該工廠構(gòu)造函數(shù)是唯一可以實(shí)例化 GameSetting 對(duì)象的方法。
static final GameSetting _default = GameSetting._(); factory GameSetting() => _default;
完成了單例類的基本定義,現(xiàn)在再來(lái)定義與掃雷相關(guān)的,先定義游戲的難度。在掃雷中游戲的難度主要有兩部分組成:
棋盤(pán)格子的數(shù)量
///游戲的難度,默認(rèn)為8*8 int difficulty = 8;
雷的數(shù)量
///雷的數(shù)量 (格子總數(shù) * 0.18 向下取整),通常掃雷的雷數(shù)在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), ]; ///默認(rèn)主題 Color themeColor = Color(0xFF5ADFD0);
第二步:定義游戲參數(shù)
在進(jìn)行掃雷游戲的時(shí)候,需要記錄棋盤(pán)格子上每個(gè)格子的參數(shù),記錄格子是否被標(biāo)記為雷、是否被翻開(kāi)。也需要記錄游戲是否獲勝、是否踩到了地雷。
late List<List<int>> board; // 棋盤(pán) late List<List<bool>> revealed; // 記錄格子是否被翻開(kāi) late List<List<bool>> flagged; // 記錄格子是否被標(biāo)記 late bool gameOver; // 游戲是否結(jié)束 late bool win; // 是否獲勝
其他初始化參數(shù):
late int numRows; // 行數(shù) late int numCols; // 列數(shù) late int numMines; // 雷數(shù) //游戲時(shí)間 late int _playTime;
第三步:編寫(xiě)掃雷初始化游戲邏輯
定義了游戲的參數(shù)后,在游戲開(kāi)始時(shí),需要對(duì)其進(jìn)行賦值。
numRows = gameSetting.difficulty; numCols = gameSetting.difficulty; numMines = gameSetting.mines; // 初始化棋盤(pán) board = List.generate(numRows, (_) => List.filled(numCols, 0)); // 初始化格子是否被翻開(kāi) revealed = List.generate(numRows, (_) => List.filled(numCols, false)); // 初始化格子是否被標(biāo)記 flagged = List.generate(numRows, (_) => List.filled(numCols, false)); // 將游戲定義為未結(jié)束 gameOver = false; // 將游戲定義為還未獲勝 win = false;
通過(guò)while循環(huán)在棋盤(pán)上隨機(jī)放置地雷,直到放置的地雷數(shù)量達(dá)到預(yù)定的 numMines。
int numMinesPlaced = 0;
while (numMinesPlaced < numMines) {
...
}
使用 Random().nextInt 方法生成兩個(gè)隨機(jī)數(shù) i 和 j,分別用于表示棋盤(pán)中的行和列。
int i = Random().nextInt(numRows); int j = Random().nextInt(numCols);
通過(guò) board[i][j] != -1 的判斷語(yǔ)句,檢查這個(gè)位置是否已經(jīng)放置了地雷。如果沒(méi)有則將 board[i][j] 的值設(shè)置為 -1,表示在這個(gè)位置放置了地雷,并將numMinesPlaced 的值加 1。
if (board[i][j] != -1) {
board[i][j] = -1;
numMinesPlaced++;
}
放完了地雷,那么就到了掃雷的核心邏輯,計(jì)算每個(gè)非地雷格子周圍的地雷數(shù)量,然后將計(jì)算得到的地雷數(shù)量保存在對(duì)應(yīng)的格子上。具體實(shí)現(xiàn)的邏輯為:通過(guò)兩個(gè)嵌套的 for 循環(huán)遍歷整個(gè)棋盤(pán),內(nèi)層的兩個(gè)嵌套循環(huán)會(huì)計(jì)算這個(gè)格子周圍的所有格子中地雷的數(shù)量,并將這個(gè)數(shù)量保存在 count 變量中。
for (int i = 0; i < numRows; i++) {
for (int j = 0; j < numCols; j++) {
...
}
}
循環(huán)中具體的邏輯是:在每個(gè)單元格上,如果它不是地雷(值不為為-1)則內(nèi)部嵌套兩個(gè)循環(huán)遍歷當(dāng)前單元格周圍的所有單元格,計(jì)算地雷數(shù)量并存儲(chǔ)在當(dāng)前單元格中。
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;
}
第四步:編寫(xiě)用戶交互游戲邏輯
只要用戶點(diǎn)擊了,就要將格子設(shè)置為翻開(kāi)了。
void reveal(int i, int j) {
revealed[i][j] = true;
}
當(dāng)用戶點(diǎn)擊了一個(gè)格子后,我們需要判斷以下幾點(diǎn):
如果翻開(kāi)的是地雷
if (board[i][j] == -1) {
//將所有的地雷翻開(kāi),告訴用戶所有的地雷位置
for (int i2 = 0; i2 < numRows; i2++) {
for (int j2 = 0; j2 < numCols; j2++) {
if (board[i2][j2] == -1) {
revealed[i2][j2] = true;
}
}
}
//游戲結(jié)束
gameOver = true;
//結(jié)束動(dòng)畫(huà)
...
}
如果點(diǎn)擊的格子周圍都沒(méi)有雷就自動(dòng)翻開(kāi)相鄰的空格
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);
}
}
}
}
檢查是否勝利
///它會(huì)遍歷整個(gè)棋盤(pán),檢查每一個(gè)未被翻開(kāi)的格子是否都是地雷,
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();
//獲勝動(dòng)畫(huà)
...
}
第五步:封裝格子
定義枚舉類BlockType,用于判斷不同的狀態(tài)下顯示不同的格子樣式。
enum BlockType {
//數(shù)字
figure,
//雷
mine,
//標(biāo)記
label,
//未標(biāo)記(未被翻開(kāi))
unlabeled,
}
封裝格子的代碼其實(shí)很簡(jiǎn)單,根據(jù)不同的狀態(tài)封裝即可,這里就不過(guò)多展示了。
第六步:游戲布局
此處只分析游戲棋盤(pán)的布局。
通過(guò)GridView.builder構(gòu)建棋盤(pán),使用SliverGridDelegateWithFixedCrossAxisCount實(shí)現(xiàn)每一行具有相同數(shù)量的列。
GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: numCols,
childAspectRatio: 1.0,
),
itemBuilder: (BuildContext context, int index) {
...
}
)
通過(guò)對(duì)index的整除和取模,得到行和列,然后根據(jù)每個(gè)格子的當(dāng)前狀態(tài)對(duì)封裝好的格子布局傳入不同的參數(shù)。
itemBuilder: (BuildContext context, int index) {
int i = index ~/ numCols;
int j = index % numCols;
BlockType blockType;
//格子被翻開(kāi)
if (revealed[i][j]) {
//是地雷
if (board[i][j] == -1) {
blockType = BlockType.mine;
} else {
blockType = BlockType.figure;
}
} else {
//被用戶標(biāo)記
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,
),
);
},
其中,如果雙擊格子代表標(biāo)記或取消標(biāo)記,定義了一個(gè)方法toggleFlag。
///標(biāo)記雷
void toggleFlag(int i, int j) {
if (!gameOver) {
setState(() {
flagged[i][j] = !flagged[i][j];
});
}
}
到這里,就完成了對(duì)掃雷這款游戲的實(shí)現(xiàn)。更改游戲的主題狀態(tài)或游戲難度,只需更改不同的初始化參數(shù)即可。
優(yōu)化-第七步:游戲時(shí)間
有一個(gè)計(jì)時(shí)器,會(huì)大大提高用戶玩算法類、解謎類這樣游戲的樂(lè)趣,例如魔方。在Flutter中通過(guò)Timer.periodic去實(shí)現(xiàn)計(jì)時(shí)器是很簡(jiǎn)答的,就不過(guò)多講述了,主要看下如何格式化為時(shí)鐘的形式:
String get playTime {
int minutes = (_playTime ~/ 60); // 計(jì)算分鐘數(shù)
int seconds = (_playTime % 60); // 計(jì)算秒數(shù)
//padLeft方法用于補(bǔ)齊不足兩位的數(shù)字,第一個(gè)參數(shù)是補(bǔ)齊后的字符串總長(zhǎng)度,第二個(gè)參數(shù)是用于補(bǔ)齊的字符。
return '${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}';
}以上就是Android Flutter實(shí)現(xiàn)在多端運(yùn)行的掃雷游戲的詳細(xì)內(nèi)容,更多關(guān)于Android Flutter掃雷游戲的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android 全屏無(wú)標(biāo)題欄的三種實(shí)現(xiàn)方法
這篇文章主要介紹了Android的三種實(shí)現(xiàn)方法的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-07-07
Windows下Flutter+Idea環(huán)境搭建及配置
這篇文章介紹了Windows下Flutter+Idea環(huán)境搭建及配置的方法,對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-12-12
Android ViewPager制作新手導(dǎo)航頁(yè)(動(dòng)態(tài)加載)
這篇文章主要為大家詳細(xì)介紹了Android ViewPager制作新手導(dǎo)航頁(yè),了解什么是動(dòng)態(tài)加載指示器,感興趣的小伙伴們可以參考一下2016-05-05
Android EditText設(shè)置邊框的操作方法
這篇文章主要介紹了Android EditText設(shè)置邊框,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-12-12
android ocr——身份證識(shí)別的功能實(shí)現(xiàn)
本篇文章主要介紹了android ocr——身份證識(shí)別的功能實(shí)現(xiàn),具有一定的參考價(jià)值,有需要的可以了解一下。2016-11-11
Android報(bào)錯(cuò)Error:Could not find com.android.tools.build:gradle
這篇文章主要介紹了Android Studio報(bào)錯(cuò)Error:Could not find com.android.tools.build:gradle:4.1解決辦法,碰到該問(wèn)題的同學(xué)快過(guò)來(lái)看看吧2021-08-08
Android項(xiàng)目實(shí)戰(zhàn)手把手教你畫(huà)圓形水波紋loadingview
這篇文章主要為大家詳細(xì)介紹了Android項(xiàng)目實(shí)戰(zhàn)手把手教你畫(huà)圓形水波紋loadingview,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-01-01

