溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Android?Flutter如何實(shí)現(xiàn)在多端運(yùn)行的掃雷游戲

發(fā)布時(shí)間:2023-03-13 11:47:28 來源:億速云 閱讀:147 作者:iii 欄目:開發(fā)技術(shù)

這篇文章主要介紹“Android Flutter如何實(shí)現(xiàn)在多端運(yùn)行的掃雷游戲”的相關(guān)知識(shí),小編通過實(shí)際案例向大家展示操作過程,操作方法簡(jiǎn)單快捷,實(shí)用性強(qiáng),希望這篇“Android Flutter如何實(shí)現(xiàn)在多端運(yùn)行的掃雷游戲”文章能幫助大家解決問題。

效果圖

話不多說,先上效果圖。(包含不同端、不同難度、不同游戲主題)

Windows端

Android?Flutter如何實(shí)現(xiàn)在多端運(yùn)行的掃雷游戲

網(wǎng)頁端

Android?Flutter如何實(shí)現(xiàn)在多端運(yùn)行的掃雷游戲

Android端

Android?Flutter如何實(shí)現(xiàn)在多端運(yùn)行的掃雷游戲

開始實(shí)現(xiàn)

第一步:定義游戲設(shè)置

定義GameSetting單例類,確保掃雷程序中只有一個(gè)實(shí)例,并且該實(shí)例可以被全局訪問,主要用于共享資源。

class GameSetting {
  GameSetting._();
}

然后定義一個(gè)私有的、靜態(tài)的、不可變的 _default 對(duì)象,它是 GameSetting 類的默認(rèn)實(shí)例,該實(shí)例在第一次使用時(shí)被創(chuàng)建。再定義一個(gè) GameSetting 工廠構(gòu)造函數(shù),它通過返回 _default 對(duì)象實(shí)現(xiàn)了單例模式的實(shí)例化,該工廠構(gòu)造函數(shù)是唯一可以實(shí)例化 GameSetting 對(duì)象的方法。

static final GameSetting _default = GameSetting._();
factory GameSetting() => _default;

完成了單例類的基本定義,現(xiàn)在再來定義與掃雷相關(guā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í)候,需要記錄棋盤格子上每個(gè)格子的參數(shù),記錄格子是否被標(biāo)記為雷、是否被翻開。也需要記錄游戲是否獲勝、是否踩到了地雷。

late List<List<int>> board; // 棋盤
late List<List<bool>> revealed; // 記錄格子是否被翻開
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;

第三步:編寫掃雷初始化游戲邏輯

定義了游戲的參數(shù)后,在游戲開始時(shí),需要對(duì)其進(jìn)行賦值。

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));
// 初始化格子是否被標(biāo)記
flagged = List.generate(numRows, (_) => List.filled(numCols, false));
// 將游戲定義為未結(jié)束
gameOver = false;
// 將游戲定義為還未獲勝
win = false;

通過while循環(huán)在棋盤上隨機(jī)放置地雷,直到放置的地雷數(shù)量達(dá)到預(yù)定的 numMines。

int numMinesPlaced = 0;
while (numMinesPlaced < numMines) {
	...
}

使用 Random().nextInt 方法生成兩個(gè)隨機(jī)數(shù) i 和 j,分別用于表示棋盤中的行和列。

int i = Random().nextInt(numRows);
int j = Random().nextInt(numCols);

通過 board[i][j] != -1 的判斷語句,檢查這個(gè)位置是否已經(jīng)放置了地雷。如果沒有則將 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)的邏輯為:通過兩個(gè)嵌套的 for 循環(huán)遍歷整個(gè)棋盤,內(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;
}

第四步:編寫用戶交互游戲邏輯

只要用戶點(diǎn)擊了,就要將格子設(shè)置為翻開了。

void reveal(int i, int j) {
	revealed[i][j] = true;
}

當(dāng)用戶點(diǎn)擊了一個(gè)格子后,我們需要判斷以下幾點(diǎn):

如果翻開的是地雷

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;
      }
    }
  }
  //游戲結(jié)束
  gameOver = true;
	//結(jié)束動(dòng)畫
	...
}

如果點(diǎn)擊的格子周圍都沒有雷就自動(dòng)翻開相鄰的空格

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è)棋盤,檢查每一個(gè)未被翻開的格子是否都是地雷,
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)畫
  ...
}

第五步:封裝格子

定義枚舉類BlockType,用于判斷不同的狀態(tài)下顯示不同的格子樣式。

enum BlockType {
  //數(shù)字
  figure,
  //雷
  mine,
  //標(biāo)記
  label,
  //未標(biāo)記(未被翻開)
  unlabeled,
}

封裝格子的代碼其實(shí)很簡(jiǎn)單,根據(jù)不同的狀態(tài)封裝即可,這里就不過多展示了。

第六步:游戲布局

此處只分析游戲棋盤的布局。

通過GridView.builder構(gòu)建棋盤,使用SliverGridDelegateWithFixedCrossAxisCount實(shí)現(xiàn)每一行具有相同數(shù)量的列。

GridView.builder(
  gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: numCols,
    childAspectRatio: 1.0,
  ),
  itemBuilder: (BuildContext context, int index) {
  	...
  }
)

通過對(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;	
//格子被翻開
 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ì)大大提高用戶玩算法類、解謎類這樣游戲的樂趣,例如魔方。在Flutter中通過Timer.periodic去實(shí)現(xiàn)計(jì)時(shí)器是很簡(jiǎn)答的,就不過多講述了,主要看下如何格式化為時(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')}';
}

關(guān)于“Android Flutter如何實(shí)現(xiàn)在多端運(yùn)行的掃雷游戲”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí),可以關(guān)注億速云行業(yè)資訊頻道,小編每天都會(huì)為大家更新不同的知識(shí)點(diǎn)。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI