diff --git a/README.md b/README.md
index 44d8e14..01f8164 100644
--- a/README.md
+++ b/README.md
@@ -42,16 +42,28 @@ freader-media-player(FMP Player),一个使用 flutter 开发的简单的本地
## 更新说明
+### 2024-02-01 主要更新
+
+- feat:添加了扫雷小游戏
+ - 更多参看对应模块的 [readme](lib/views/game_center/minesweeper/readme.md) 。
+
+### 2024-01-31 主要更新
+
+- feat:添加了恐龙快跑小游戏
+ - 更多参看对应模块的 [readme](lib/views/game_center/t-rex_dinosaur/readme.md) 。
+- feat:添加了贪吃蛇小游戏
+ - 更多参看对应模块的 [readme](lib/views/game_center/snake/readme.md) 。
+
### 2024-01-30 主要更新
- feat:添加了俄罗斯方块小游戏
- - 更多参看对应模块的[readme](lib/views/game_center/tetris/readme.md).
+ - 更多参看对应模块的 [readme](lib/views/game_center/tetris/readme.md) 。
### 2024-01-29 主要更新
- feat:添加了 2048 小游戏
- - 更多参看对应模块的[readme](lib/views/game_center/flutter_2048/readme.md).
- - 添加了休闲游戏模块后,原本的“本地图片”和“本地视频”模块就初始默认隐藏,同样长按退出弹窗正文可切换.
+ - 更多参看对应模块的 [readme](lib/views/game_center/flutter_2048/readme.md) 。
+ - 添加了休闲游戏模块后,原本的“本地图片”和“本地视频”模块就初始默认隐藏,同样长按退出弹窗正文可切换。
### 2024-01-26 主要更新
diff --git a/assets/games/cover-dinosaur.jpg b/assets/games/cover-dinosaur.jpg
new file mode 100755
index 0000000..5999c8a
Binary files /dev/null and b/assets/games/cover-dinosaur.jpg differ
diff --git a/assets/games/cover-minesweeper.jpg b/assets/games/cover-minesweeper.jpg
new file mode 100755
index 0000000..8b147aa
Binary files /dev/null and b/assets/games/cover-minesweeper.jpg differ
diff --git a/assets/games/cover-snake.jpg b/assets/games/cover-snake.jpg
new file mode 100755
index 0000000..b9af94e
Binary files /dev/null and b/assets/games/cover-snake.jpg differ
diff --git a/assets/games/cover-sudoku.png b/assets/games/cover-sudoku.png
new file mode 100755
index 0000000..7a4eb16
Binary files /dev/null and b/assets/games/cover-sudoku.png differ
diff --git a/assets/games/dinosaur/cacti/cacti_group.png b/assets/games/dinosaur/cacti/cacti_group.png
new file mode 100755
index 0000000..795fa22
Binary files /dev/null and b/assets/games/dinosaur/cacti/cacti_group.png differ
diff --git a/assets/games/dinosaur/cacti/cacti_large_1.png b/assets/games/dinosaur/cacti/cacti_large_1.png
new file mode 100755
index 0000000..d002ac8
Binary files /dev/null and b/assets/games/dinosaur/cacti/cacti_large_1.png differ
diff --git a/assets/games/dinosaur/cacti/cacti_large_2.png b/assets/games/dinosaur/cacti/cacti_large_2.png
new file mode 100755
index 0000000..b2ec8c9
Binary files /dev/null and b/assets/games/dinosaur/cacti/cacti_large_2.png differ
diff --git a/assets/games/dinosaur/cacti/cacti_small_1.png b/assets/games/dinosaur/cacti/cacti_small_1.png
new file mode 100755
index 0000000..18534a4
Binary files /dev/null and b/assets/games/dinosaur/cacti/cacti_small_1.png differ
diff --git a/assets/games/dinosaur/cacti/cacti_small_2.png b/assets/games/dinosaur/cacti/cacti_small_2.png
new file mode 100755
index 0000000..9952701
Binary files /dev/null and b/assets/games/dinosaur/cacti/cacti_small_2.png differ
diff --git a/assets/games/dinosaur/cacti/cacti_small_3.png b/assets/games/dinosaur/cacti/cacti_small_3.png
new file mode 100755
index 0000000..48353c8
Binary files /dev/null and b/assets/games/dinosaur/cacti/cacti_small_3.png differ
diff --git a/assets/games/dinosaur/cloud.png b/assets/games/dinosaur/cloud.png
new file mode 100755
index 0000000..f6fd638
Binary files /dev/null and b/assets/games/dinosaur/cloud.png differ
diff --git a/assets/games/dinosaur/dino/dino_1.png b/assets/games/dinosaur/dino/dino_1.png
new file mode 100755
index 0000000..b065319
Binary files /dev/null and b/assets/games/dinosaur/dino/dino_1.png differ
diff --git a/assets/games/dinosaur/dino/dino_2.png b/assets/games/dinosaur/dino/dino_2.png
new file mode 100755
index 0000000..2b2b33f
Binary files /dev/null and b/assets/games/dinosaur/dino/dino_2.png differ
diff --git a/assets/games/dinosaur/dino/dino_3.png b/assets/games/dinosaur/dino/dino_3.png
new file mode 100755
index 0000000..6ba88a0
Binary files /dev/null and b/assets/games/dinosaur/dino/dino_3.png differ
diff --git a/assets/games/dinosaur/dino/dino_4.png b/assets/games/dinosaur/dino/dino_4.png
new file mode 100755
index 0000000..872142a
Binary files /dev/null and b/assets/games/dinosaur/dino/dino_4.png differ
diff --git a/assets/games/dinosaur/dino/dino_5.png b/assets/games/dinosaur/dino/dino_5.png
new file mode 100755
index 0000000..3dc22d1
Binary files /dev/null and b/assets/games/dinosaur/dino/dino_5.png differ
diff --git a/assets/games/dinosaur/dino/dino_6.png b/assets/games/dinosaur/dino/dino_6.png
new file mode 100755
index 0000000..9a7551c
Binary files /dev/null and b/assets/games/dinosaur/dino/dino_6.png differ
diff --git a/assets/games/dinosaur/dino_all.png b/assets/games/dinosaur/dino_all.png
new file mode 100755
index 0000000..5aab52f
Binary files /dev/null and b/assets/games/dinosaur/dino_all.png differ
diff --git a/assets/games/dinosaur/ground.png b/assets/games/dinosaur/ground.png
new file mode 100755
index 0000000..be13348
Binary files /dev/null and b/assets/games/dinosaur/ground.png differ
diff --git a/assets/games/dinosaur/ptera/ptera_1.png b/assets/games/dinosaur/ptera/ptera_1.png
new file mode 100755
index 0000000..79895d3
Binary files /dev/null and b/assets/games/dinosaur/ptera/ptera_1.png differ
diff --git a/assets/games/dinosaur/ptera/ptera_2.png b/assets/games/dinosaur/ptera/ptera_2.png
new file mode 100755
index 0000000..e7cd081
Binary files /dev/null and b/assets/games/dinosaur/ptera/ptera_2.png differ
diff --git a/assets/games/minesweeper/audio/blue.wav b/assets/games/minesweeper/audio/blue.wav
new file mode 100755
index 0000000..0d28c5e
Binary files /dev/null and b/assets/games/minesweeper/audio/blue.wav differ
diff --git a/assets/games/minesweeper/audio/clickEmpty.wav b/assets/games/minesweeper/audio/clickEmpty.wav
new file mode 100755
index 0000000..b6fb861
Binary files /dev/null and b/assets/games/minesweeper/audio/clickEmpty.wav differ
diff --git a/assets/games/minesweeper/audio/clickFour.wav b/assets/games/minesweeper/audio/clickFour.wav
new file mode 100755
index 0000000..84759cb
Binary files /dev/null and b/assets/games/minesweeper/audio/clickFour.wav differ
diff --git a/assets/games/minesweeper/audio/clickOne.wav b/assets/games/minesweeper/audio/clickOne.wav
new file mode 100755
index 0000000..82837be
Binary files /dev/null and b/assets/games/minesweeper/audio/clickOne.wav differ
diff --git a/assets/games/minesweeper/audio/clickThree.wav b/assets/games/minesweeper/audio/clickThree.wav
new file mode 100755
index 0000000..97a635d
Binary files /dev/null and b/assets/games/minesweeper/audio/clickThree.wav differ
diff --git a/assets/games/minesweeper/audio/clickTwo.wav b/assets/games/minesweeper/audio/clickTwo.wav
new file mode 100755
index 0000000..e7b1a93
Binary files /dev/null and b/assets/games/minesweeper/audio/clickTwo.wav differ
diff --git a/assets/games/minesweeper/audio/lastHit.wav b/assets/games/minesweeper/audio/lastHit.wav
new file mode 100755
index 0000000..bbc1f7a
Binary files /dev/null and b/assets/games/minesweeper/audio/lastHit.wav differ
diff --git a/assets/games/minesweeper/audio/lose.wav b/assets/games/minesweeper/audio/lose.wav
new file mode 100755
index 0000000..559197b
Binary files /dev/null and b/assets/games/minesweeper/audio/lose.wav differ
diff --git a/assets/games/minesweeper/audio/pink.wav b/assets/games/minesweeper/audio/pink.wav
new file mode 100755
index 0000000..6fb1be4
Binary files /dev/null and b/assets/games/minesweeper/audio/pink.wav differ
diff --git a/assets/games/minesweeper/audio/purple.wav b/assets/games/minesweeper/audio/purple.wav
new file mode 100755
index 0000000..655f62a
Binary files /dev/null and b/assets/games/minesweeper/audio/purple.wav differ
diff --git a/assets/games/minesweeper/audio/putFlag.wav b/assets/games/minesweeper/audio/putFlag.wav
new file mode 100755
index 0000000..5a18316
Binary files /dev/null and b/assets/games/minesweeper/audio/putFlag.wav differ
diff --git a/assets/games/minesweeper/audio/removeFlag.wav b/assets/games/minesweeper/audio/removeFlag.wav
new file mode 100755
index 0000000..3753cf0
Binary files /dev/null and b/assets/games/minesweeper/audio/removeFlag.wav differ
diff --git a/assets/games/minesweeper/audio/win.wav b/assets/games/minesweeper/audio/win.wav
new file mode 100755
index 0000000..9746298
Binary files /dev/null and b/assets/games/minesweeper/audio/win.wav differ
diff --git a/assets/games/minesweeper/images/flag.png b/assets/games/minesweeper/images/flag.png
new file mode 100755
index 0000000..9c28976
Binary files /dev/null and b/assets/games/minesweeper/images/flag.png differ
diff --git a/assets/games/minesweeper/images/homeScreenBg.png b/assets/games/minesweeper/images/homeScreenBg.png
new file mode 100755
index 0000000..f4d31b5
Binary files /dev/null and b/assets/games/minesweeper/images/homeScreenBg.png differ
diff --git a/assets/games/minesweeper/images/how_to_play/how_to_play_1.png b/assets/games/minesweeper/images/how_to_play/how_to_play_1.png
new file mode 100755
index 0000000..cef4722
Binary files /dev/null and b/assets/games/minesweeper/images/how_to_play/how_to_play_1.png differ
diff --git a/assets/games/minesweeper/images/how_to_play/how_to_play_2.png b/assets/games/minesweeper/images/how_to_play/how_to_play_2.png
new file mode 100755
index 0000000..3939971
Binary files /dev/null and b/assets/games/minesweeper/images/how_to_play/how_to_play_2.png differ
diff --git a/assets/games/minesweeper/images/how_to_play/how_to_play_3.png b/assets/games/minesweeper/images/how_to_play/how_to_play_3.png
new file mode 100755
index 0000000..c5c5182
Binary files /dev/null and b/assets/games/minesweeper/images/how_to_play/how_to_play_3.png differ
diff --git a/assets/games/minesweeper/images/how_to_play/how_to_play_4.png b/assets/games/minesweeper/images/how_to_play/how_to_play_4.png
new file mode 100755
index 0000000..5caa782
Binary files /dev/null and b/assets/games/minesweeper/images/how_to_play/how_to_play_4.png differ
diff --git a/assets/games/minesweeper/images/how_to_play/how_to_play_5.png b/assets/games/minesweeper/images/how_to_play/how_to_play_5.png
new file mode 100755
index 0000000..fe14956
Binary files /dev/null and b/assets/games/minesweeper/images/how_to_play/how_to_play_5.png differ
diff --git a/assets/games/minesweeper/images/logo.png b/assets/games/minesweeper/images/logo.png
new file mode 100755
index 0000000..aebe9fb
Binary files /dev/null and b/assets/games/minesweeper/images/logo.png differ
diff --git a/assets/games/minesweeper/images/loseScreen.png b/assets/games/minesweeper/images/loseScreen.png
new file mode 100755
index 0000000..97c2793
Binary files /dev/null and b/assets/games/minesweeper/images/loseScreen.png differ
diff --git a/assets/games/minesweeper/images/pickaxe.png b/assets/games/minesweeper/images/pickaxe.png
new file mode 100755
index 0000000..dcb4dca
Binary files /dev/null and b/assets/games/minesweeper/images/pickaxe.png differ
diff --git a/assets/games/minesweeper/images/pickaxe_old.png b/assets/games/minesweeper/images/pickaxe_old.png
new file mode 100755
index 0000000..485624e
Binary files /dev/null and b/assets/games/minesweeper/images/pickaxe_old.png differ
diff --git a/assets/games/minesweeper/images/redCross.png b/assets/games/minesweeper/images/redCross.png
new file mode 100755
index 0000000..6dea4db
Binary files /dev/null and b/assets/games/minesweeper/images/redCross.png differ
diff --git a/assets/games/minesweeper/images/stopwatch.png b/assets/games/minesweeper/images/stopwatch.png
new file mode 100755
index 0000000..d7b03be
Binary files /dev/null and b/assets/games/minesweeper/images/stopwatch.png differ
diff --git a/assets/games/minesweeper/images/trophy.png b/assets/games/minesweeper/images/trophy.png
new file mode 100755
index 0000000..d375dfd
Binary files /dev/null and b/assets/games/minesweeper/images/trophy.png differ
diff --git a/assets/games/minesweeper/images/winScreen.png b/assets/games/minesweeper/images/winScreen.png
new file mode 100755
index 0000000..2ade7a2
Binary files /dev/null and b/assets/games/minesweeper/images/winScreen.png differ
diff --git a/assets/games/sodoku/audio/answer_tip.mp3 b/assets/games/sodoku/audio/answer_tip.mp3
new file mode 100755
index 0000000..37e3cac
Binary files /dev/null and b/assets/games/sodoku/audio/answer_tip.mp3 differ
diff --git a/assets/games/sodoku/audio/gameover_tip.mp3 b/assets/games/sodoku/audio/gameover_tip.mp3
new file mode 100755
index 0000000..dcfee83
Binary files /dev/null and b/assets/games/sodoku/audio/gameover_tip.mp3 differ
diff --git a/assets/games/sodoku/audio/victory_tip.mp3 b/assets/games/sodoku/audio/victory_tip.mp3
new file mode 100755
index 0000000..01004f0
Binary files /dev/null and b/assets/games/sodoku/audio/victory_tip.mp3 differ
diff --git a/assets/games/sodoku/audio/wrong_tip.mp3 b/assets/games/sodoku/audio/wrong_tip.mp3
new file mode 100755
index 0000000..9d64120
Binary files /dev/null and b/assets/games/sodoku/audio/wrong_tip.mp3 differ
diff --git a/assets/games/sodoku/image/about_me.jpg b/assets/games/sodoku/image/about_me.jpg
new file mode 100755
index 0000000..147cab5
Binary files /dev/null and b/assets/games/sodoku/image/about_me.jpg differ
diff --git a/assets/games/sodoku/image/icon_eraser.png b/assets/games/sodoku/image/icon_eraser.png
new file mode 100755
index 0000000..f96d660
Binary files /dev/null and b/assets/games/sodoku/image/icon_eraser.png differ
diff --git a/assets/games/sodoku/image/icon_idea.png b/assets/games/sodoku/image/icon_idea.png
new file mode 100755
index 0000000..160259a
Binary files /dev/null and b/assets/games/sodoku/image/icon_idea.png differ
diff --git a/assets/games/sodoku/image/icon_life.png b/assets/games/sodoku/image/icon_life.png
new file mode 100755
index 0000000..09c4739
Binary files /dev/null and b/assets/games/sodoku/image/icon_life.png differ
diff --git a/assets/games/sodoku/image/logo.png b/assets/games/sodoku/image/logo.png
new file mode 100755
index 0000000..34354d1
Binary files /dev/null and b/assets/games/sodoku/image/logo.png differ
diff --git a/assets/games/sodoku/image/sudoku_icon.png b/assets/games/sodoku/image/sudoku_icon.png
new file mode 100755
index 0000000..e791b36
Binary files /dev/null and b/assets/games/sodoku/image/sudoku_icon.png differ
diff --git a/assets/games/sodoku/image/sudoku_logo.png b/assets/games/sodoku/image/sudoku_logo.png
new file mode 100755
index 0000000..4cd66b9
Binary files /dev/null and b/assets/games/sodoku/image/sudoku_logo.png differ
diff --git a/assets/games/sodoku/svg/idea.svg b/assets/games/sodoku/svg/idea.svg
new file mode 100755
index 0000000..70414b4
--- /dev/null
+++ b/assets/games/sodoku/svg/idea.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/lib/common/global/constants.dart b/lib/common/global/constants.dart
index 220853b..444fcb9 100644
--- a/lib/common/global/constants.dart
+++ b/lib/common/global/constants.dart
@@ -14,6 +14,10 @@ class GlobalConstants {
const String placeholderImageUrl = 'assets/launch_background.png';
const String cover2048ImageUrl = 'assets/games/cover-2048.jpg';
const String coverTetrisImageUrl = 'assets/games/cover-tetris.jpg';
+const String coverDinosaurImageUrl = 'assets/games/cover-dinosaur.jpg';
+const String coverSnakeImageUrl = 'assets/games/cover-snake.jpg';
+const String coverMinesweeperImageUrl = 'assets/games/cover-minesweeper.jpg';
+const String coverSudokuImageUrl = 'assets/games/cover-sudoku.png';
/*
// 音频播放列表支持的类型,使用扩展可以直接比较属性值
diff --git a/lib/layout/home.dart b/lib/layout/home.dart
index 36a1d05..6e99730 100644
--- a/lib/layout/home.dart
+++ b/lib/layout/home.dart
@@ -14,15 +14,24 @@ import '../views/local_video/index.dart';
/// 主页面
class HomePage extends StatefulWidget {
- const HomePage({super.key});
+ const HomePage({super.key, this.selectedIndex});
+
+ // 2024-02-01 新加可以指定默认选中的底部导航索引
+ // 主要是游戏中心的扫雷游戏,退出游戏界面后是使用pushAndRemoveUntil导航到home页面,
+ // 所以可以指定索引以便显示的是正确的游戏中心而不是初始化的第一个导航栏
+ final int? selectedIndex;
@override
State createState() => _HomePageState();
}
class _HomePageState extends State {
- // 默认选中第一个底部导航条目
- int _selectedIndex = 0;
+ // // 当前选中项的索引 默认选中第一个底部导航条目
+ int _currentIndex = 0;
+ // 2024-02-02 新加记录上一个底部导航索引,如果是从休闲游戏模块切换到其他模块。需要重新构建音频播放列表
+ // 因为目前游戏中心的背景音乐播放器和本地音乐模块播放器是同一个,因为背景播放插件的限制
+ // 上一个选中项的索引
+ int _previousIndex = 0;
final _audioHandler = getIt();
// 统一简单存储操作的工具类实例
@@ -44,6 +53,10 @@ class _HomePageState extends State {
// 2024-01-25 根据缓存值显示底部导航条目数量
changeBottomNavItemNum();
+
+ if (widget.selectedIndex != null) {
+ _currentIndex = widget.selectedIndex!;
+ }
}
/// 2024-01-25 彩蛋功能,根据缓存展示底部导航栏条目的数量
@@ -51,7 +64,7 @@ class _HomePageState extends State {
changeBottomNavItemNum() {
setState(() {
// 2024-01-25 注意,因为可能在4切换成2的时候,当前标签tab在2或者3,那就找不到对应的了。所以默认都改成第一个。
- _selectedIndex = 0;
+ _currentIndex = 0;
var num = _simpleStorage.getBottomNavItemMun();
@@ -106,7 +119,23 @@ class _HomePageState extends State {
void _onItemTapped(int index) {
setState(() {
- _selectedIndex = index;
+ _previousIndex = _currentIndex; // 更新上一个索引值
+ _currentIndex = index; // 更新当前索引值
+
+ // 2024-02-02
+ // 检查是否是从游戏中心tab切换到其他tab
+ int gameCenterIndex = _simpleStorage.getBottomNavItemMun() > 3 ? 4 : 2;
+ if (_previousIndex == gameCenterIndex &&
+ _currentIndex != _previousIndex) {
+ initAudio();
+ }
+
+ // 如果是从其他tab进入游戏中心,则暂停音乐播放
+ var num = _simpleStorage.getBottomNavItemMun();
+ if (num > 3 ? _currentIndex == 4 : _currentIndex == 2) {
+ // 默认是暂停状态
+ _audioHandler.pause();
+ }
});
}
@@ -198,11 +227,11 @@ class _HomePageState extends State {
],
),
)
- : Center(child: _widgetOptions.elementAt(_selectedIndex)),
+ : Center(child: _widgetOptions.elementAt(_currentIndex)),
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
items: bottomNavBarItems,
- currentIndex: _selectedIndex,
+ currentIndex: _currentIndex,
// // 底部导航栏的颜色
// backgroundColor: Theme.of(context).primaryColor,
// // 被选中的item的图标颜色和文本颜色
diff --git a/lib/services/my_get_storage.dart b/lib/services/my_get_storage.dart
index d2b26f0..07b8294 100644
--- a/lib/services/my_get_storage.dart
+++ b/lib/services/my_get_storage.dart
@@ -108,4 +108,18 @@ class MyGetStorage {
}
int? getTetrisBestScore() => box.read("gameTetrisBestScore");
+
+ /// 2024-01-31 恐龙游戏保存获取历史最高分
+ Future setDinosaurBestScore(int score) async {
+ await box.write("gameDinosaurBestScore", score);
+ }
+
+ int? getDinosaurBestScore() => box.read("gameDinosaurBestScore");
+
+ /// 2024-01-31 贪吃蛇小游戏保存获取历史最高分
+ Future setSnakeBestScore(int score) async {
+ await box.write("gameSnakeBestScore", score);
+ }
+
+ int? getSnakeBestScore() => box.read("gameSnakeBestScore");
}
diff --git a/lib/views/game_center/flutter_2048/managers/board.dart b/lib/views/game_center/flutter_2048/managers/board.dart
index c41e886..976f9fa 100755
--- a/lib/views/game_center/flutter_2048/managers/board.dart
+++ b/lib/views/game_center/flutter_2048/managers/board.dart
@@ -56,7 +56,9 @@ class BoardManager extends StateNotifier {
// 创建一个新游戏棋盘状态
Board _newGame() {
- return Board.newGame(state.best + state.score, state.bestNum, [random([])]);
+ // return Board.newGame(state.best + state.score, state.bestNum, [random([])]);
+ // 2024-02-19 初始化的最佳分数不应该包含上次得分,需要在游戏结束时更新最佳得分
+ return Board.newGame(state.best, state.bestNum, [random([])]);
}
// 开始新游戏
@@ -364,6 +366,7 @@ class BoardManager extends StateNotifier {
state = state.copyWith(
tiles: tiles,
+ best: max(state.score, state.best),
bestNum: max(maxValue, state.bestNum),
won: gameWon,
over: gameOver,
diff --git a/lib/views/game_center/index.dart b/lib/views/game_center/index.dart
index ec315ac..dce0a44 100644
--- a/lib/views/game_center/index.dart
+++ b/lib/views/game_center/index.dart
@@ -6,6 +6,11 @@ import 'package:flutter_screenutil/flutter_screenutil.dart';
import '../../common/global/constants.dart';
import 'flutter_2048/index.dart';
+import 'minesweeper/index.dart';
+
+import 'snake/index.dart';
+import 'sudoku/index.dart';
+import 't-rex_dinosaur/index.dart';
import 'tetris/index.dart';
class GameCenter extends StatefulWidget {
@@ -46,7 +51,7 @@ class _GameCenterState extends State {
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
- Expanded(flex: 1, child: Container()),
+ // Expanded(flex: 1, child: Container()),
Expanded(
flex: 2,
child: Row(
@@ -70,7 +75,53 @@ class _GameCenterState extends State {
],
),
),
- Expanded(flex: 1, child: Container()),
+ Expanded(
+ flex: 2,
+ child: Row(
+ children: [
+ Expanded(
+ child: buildCoverCardColumn(
+ context,
+ const TRexDinosaur(),
+ "恐龙快跑",
+ imageUrl: coverDinosaurImageUrl,
+ ),
+ ),
+ Expanded(
+ child: buildCoverCardColumn(
+ context,
+ const SnakeGame(),
+ "贪吃蛇",
+ imageUrl: coverSnakeImageUrl,
+ ),
+ )
+ ],
+ ),
+ ),
+ Expanded(
+ flex: 2,
+ child: Row(
+ children: [
+ Expanded(
+ child: buildCoverCardColumn(
+ context,
+ const InitMinesweeper(),
+ "扫雷",
+ imageUrl: coverMinesweeperImageUrl,
+ ),
+ ),
+ Expanded(
+ child: buildCoverCardColumn(
+ context,
+ const InitSudoku(),
+ "数独",
+ imageUrl: coverSudokuImageUrl,
+ ),
+ )
+ ],
+ ),
+ ),
+ // Expanded(flex: 1, child: Container()),
],
),
);
@@ -99,11 +150,13 @@ buildCoverCardColumn(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
- Padding(
- padding: EdgeInsets.all(5.sp),
- child: Image.asset(
- imageUrl ?? placeholderImageUrl,
- fit: BoxFit.contain,
+ Expanded(
+ child: Padding(
+ padding: EdgeInsets.all(5.sp),
+ child: Image.asset(
+ imageUrl ?? placeholderImageUrl,
+ fit: BoxFit.scaleDown,
+ ),
),
),
Text(
diff --git a/lib/views/game_center/minesweeper/controller/game_controller.dart b/lib/views/game_center/minesweeper/controller/game_controller.dart
new file mode 100755
index 0000000..055439a
--- /dev/null
+++ b/lib/views/game_center/minesweeper/controller/game_controller.dart
@@ -0,0 +1,414 @@
+import 'dart:async';
+import 'dart:math';
+
+import 'package:flutter/material.dart';
+
+import '../helper/audio_player.dart';
+import '../helper/shared_helper.dart';
+import '../model/tile_model.dart';
+import '../utils/game_consts.dart';
+import '../utils/game_sounds.dart';
+import '../view/home_view/home_view.dart';
+import '../widgets/game_popup_screen.dart';
+
+class GameController extends ChangeNotifier {
+ late GameAudioPlayer _audioPlayer;
+ SharedHelper? _sharedHelper;
+
+ GameController() {
+ _audioPlayer = GameAudioPlayer();
+ _createGameBoard();
+ }
+
+ /// The game board matrix / minefield
+ final List> _mineField = [];
+ List> get mineField => _mineField;
+
+ int _boardLength = 10;
+ int get boardLength => _boardLength;
+
+ int _flagCount = 15;
+ int get flagCount => _flagCount;
+
+ int _mineCount = 15;
+ int _openedTileCount = 0;
+
+ int _timeElapsed = 0;
+ int get timeElapsed => _timeElapsed;
+
+ bool _gameHasStarted = false;
+ bool get gameHasStarted => _gameHasStarted;
+ bool _gameOver = false;
+ bool _minesAnimation = false;
+
+ bool get isMineAnimationOn => _minesAnimation;
+
+ set minesAnimation(bool value) {
+ _minesAnimation = value;
+ notifyListeners();
+ }
+
+ bool _volumeOn = true;
+
+ /// Volume setting (on/off)
+ bool get volumeOn => _volumeOn;
+
+ /// Volume setting setter
+ set changeVolumeSetting(bool value) {
+ _volumeOn = value;
+ GameAudioPlayer.setVolume(_volumeOn);
+ notifyListeners();
+ }
+
+ /// Game difficulty setting.
+ /// This setting determines the matrix size and number of mines
+ GameMode _gameMode = GameMode.easy;
+ GameMode get gameMode => _gameMode;
+
+ /// Game mode setter
+ set gameMode(GameMode mode) {
+ _gameMode = mode;
+ _boardLength = getBoardLength(_gameMode);
+ _mineCount = mineCount(_gameMode);
+ resetGame();
+ createNewGame();
+ }
+
+ /// Starts the timer
+ void _startTimer() {
+ Timer.periodic(const Duration(seconds: 1), (timer) {
+ if (!_gameHasStarted || _gameOver || _timeElapsed >= 999) {
+ timer.cancel();
+ return;
+ }
+ _timeElapsed++;
+ notifyListeners();
+ });
+ }
+
+ List findMines() {
+ List mines = [];
+
+ for (List row in mineField) {
+ for (Tile tile in row) {
+ if (!tile.visible && tile.hasMine && !tile.hasFlag) {
+ mines.add(tile);
+ }
+ }
+ }
+
+ return mines;
+ }
+
+ List findMissPlacesFlags() {
+ List missPlacesFlags = [];
+
+ for (List row in mineField) {
+ for (Tile tile in row) {
+ if (!tile.visible && !tile.hasMine && tile.hasFlag) {
+ missPlacesFlags.add(tile);
+ }
+ }
+ }
+
+ return missPlacesFlags;
+ }
+
+ /// Makes all mines visible
+ Future showAllMines() async {
+ List mines = findMines();
+ mines.shuffle();
+
+ await Future.delayed(const Duration(milliseconds: 300));
+
+ _minesAnimation = true;
+
+ var rnd = Random();
+
+ for (var mine in mines) {
+ int r = mine.row;
+ int c = mine.col;
+
+ mineField[r][c].setVisible = true;
+ if (_minesAnimation) {
+ notifyListeners();
+ await _audioPlayer.playAudio(GameSounds.mineSound[rnd.nextInt(3)]);
+ await Future.delayed(const Duration(milliseconds: 300));
+ }
+ }
+ notifyListeners();
+
+ await showMissPlacesFlags();
+
+ _minesAnimation = false;
+ }
+
+ Future showMissPlacesFlags() async {
+ if (_minesAnimation) {
+ await Future.delayed(const Duration(milliseconds: 500));
+ }
+ List missPlacesFlags = findMissPlacesFlags();
+ for (var mine in missPlacesFlags) {
+ int r = mine.row;
+ int c = mine.col;
+
+ mineField[r][c].setVisible = true;
+ notifyListeners();
+ }
+ if (missPlacesFlags.isNotEmpty && _minesAnimation) {
+ await _audioPlayer.playAudio(GameSounds.removeFlag);
+ await Future.delayed(const Duration(milliseconds: 1500));
+ }
+ }
+
+ /// Creates empty board
+ void _createGameBoard() {
+ for (var i = 0; i < _boardLength; i++) {
+ _mineField.add([]);
+ for (var j = 0; j < 10; j++) {
+ _mineField[i].add(Tile(i, j));
+ }
+ }
+ }
+
+ /// Creates a new game
+ void createNewGame() {
+ resetGame();
+ _createGameBoard();
+ notifyListeners();
+ }
+
+ /// Game start function
+ void startGame(Tile tile) {
+ _gameHasStarted = true;
+ _addGameStartLog();
+ _startTimer();
+ _placeMines(tile);
+ _openTile(tile.row, tile.col, playSound: true);
+ }
+
+ Future _addGameStartLog() async {
+ _sharedHelper ??= await SharedHelper.init();
+
+ await _sharedHelper?.increaseGamesStarted(gameMode);
+ }
+
+ /// Reset game variables
+ void resetGame() {
+ _gameOver = true;
+ _mineField.clear();
+ _flagCount = _mineCount;
+ _openedTileCount = 0;
+ _gameHasStarted = false;
+ _gameOver = false;
+ _timeElapsed = 0;
+ GameAudioPlayer.playable = true;
+ notifyListeners();
+ }
+
+ /// Exit game function
+ void exitGame(BuildContext context) {
+ // 2024-02-01 因为和音乐播放器共用一个player,退出扫雷游戏是,停止当前音乐播放
+ GameAudioPlayer().resetPlayer(true);
+
+ if (isMineAnimationOn) {
+ minesAnimation = false;
+ } else if (gameHasStarted) {
+ GamePopupScreen.exitGame(context, this);
+ } else {
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+ Navigator.pushAndRemoveUntil(
+ context,
+ MaterialPageRoute(
+ builder: (context) => const MinesweeperHomeView(),
+ ),
+ (route) => false,
+ );
+ });
+ }
+ }
+
+ /// Win game function
+ Future winTheGame() async {
+ _gameOver = true;
+ notifyListeners();
+
+ _audioPlayer.playAudio(GameSounds.lastHit);
+ await Future.delayed(const Duration(milliseconds: 1500), () {
+ _audioPlayer.playAudio(GameSounds.win, loop: true);
+ });
+ }
+
+ /// Lose game function
+ Future loseTheGame() async {
+ _gameOver = true;
+ notifyListeners();
+ await showAllMines();
+ _audioPlayer.playAudio(GameSounds.lose, loop: true);
+ }
+
+ /// Places mines to empty game board. The number of mines depends on the game difficulty.
+ void _placeMines(Tile tile) {
+ var rnd = Random();
+ int mines = _mineCount;
+ int row = tile.row;
+ int col = tile.col;
+
+ while (mines > 0) {
+ var i = rnd.nextInt(_boardLength);
+ var j = rnd.nextInt(10);
+
+ List> restricted = [
+ [row - 1, col - 1],
+ [row - 1, col],
+ [row - 1, col + 1],
+ [row, col - 1],
+ [row, col],
+ [row, col + 1],
+ [row + 1, col - 1],
+ [row + 1, col],
+ [row + 1, col + 1],
+ ];
+
+ if (restricted.any((element) => element[0] == i && element[1] == j)) {
+ continue;
+ }
+
+ if (!_mineField[i][j].hasMine) {
+ _mineField[i][j].setMine = true;
+ mines--;
+ }
+ }
+ }
+
+ /// Remove/Add flag from/to specified tile
+ void placeFlag(Tile tile) {
+ if (!_gameOver) {
+ bool flagValue = !tile.hasFlag;
+ _mineField[tile.row][tile.col].setFlag = flagValue;
+ _flagCount += flagValue ? -1 : 1;
+ notifyListeners();
+ _audioPlayer
+ .playAudio(flagValue ? GameSounds.putFlag : GameSounds.removeFlag);
+ }
+ }
+
+ /// When user clicks a tile, this function calls the [_openTile] function and starts the game if it is the first move of user's
+ Future? clickTile(Tile tile) async {
+ if (!_gameHasStarted) {
+ startGame(tile);
+ } else if (!_gameOver) {
+ return await _openTile(tile.row, tile.col, playSound: true);
+ }
+ return null;
+ }
+
+ /// Opens the clicked tile. Calls the [checkMinesAround] function and updates
+ /// the tile value as mine count.
+ Future? _openTile(int row, int col, {bool playSound = false}) async {
+ if (row < 0 ||
+ col < 0 ||
+ row >= mineField.length ||
+ col >= mineField[0].length) return null;
+ if (mineField[row][col].visible) return null;
+ if (mineField[row][col].hasMine) {
+ mineField[row][col].setVisible = true;
+ _audioPlayer.playAudio(GameSounds.mineSound[0]);
+ await loseTheGame();
+ return false;
+ }
+
+ _openedTileCount++;
+ int minesAround = checkMinesAround(row, col);
+ mineField[row][col].setValue = minesAround;
+ if (mineField[row][col].hasFlag) {
+ _flagCount += 1;
+ }
+ notifyListeners();
+
+ if (_openedTileCount + _mineCount == _boardLength * 10) {
+ await winTheGame();
+ return true;
+ } else {
+ if (playSound) {
+ _audioPlayer.playAudio(GameSounds.clickSounds[
+ minesAround >= GameSounds.clickSounds.length
+ ? GameSounds.clickSounds.length - 1
+ : minesAround]);
+ }
+ if (minesAround == 0) {
+ _openTile(row + 1, col - 1);
+ _openTile(row + 1, col);
+ _openTile(row + 1, col + 1);
+ _openTile(row, col - 1);
+ _openTile(row, col + 1);
+ _openTile(row - 1, col - 1);
+ _openTile(row - 1, col);
+ _openTile(row - 1, col + 1);
+ }
+ }
+ return null;
+ }
+
+ /// Checks for surrounding mines and returns number of mines
+ int checkMinesAround(int row, int col) {
+ int rowLength = mineField.length;
+ int colLength = mineField[0].length;
+
+ int minesAround = 0;
+
+ if (row - 1 >= 0) {
+ // top-left
+ if (col - 1 >= 0 && mineField[row - 1][col - 1].hasMine) {
+ minesAround++;
+ } // top
+ if (mineField[row - 1][col].hasMine) {
+ minesAround++;
+ } // top-right
+ if (col + 1 < colLength && mineField[row - 1][col + 1].hasMine) {
+ minesAround++;
+ }
+
+ if (mineField[row - 1][col].visible == false) {
+ mineField[row - 1][col].addBorder = 3;
+ }
+ }
+
+ // left
+ if (col - 1 >= 0) {
+ if (mineField[row][col - 1].hasMine) {
+ minesAround++;
+ }
+ if (mineField[row][col - 1].visible == false) {
+ mineField[row][col - 1].addBorder = 2;
+ }
+ }
+ // right
+ if (col + 1 < colLength) {
+ if (mineField[row][col + 1].hasMine) {
+ minesAround++;
+ }
+ if (mineField[row][col + 1].visible == false) {
+ mineField[row][col + 1].addBorder = 0;
+ }
+ }
+
+ if (row + 1 < rowLength) {
+ // bottom-left
+ if (col - 1 >= 0 && mineField[row + 1][col - 1].hasMine) {
+ minesAround++;
+ } // bottom
+ if (mineField[row + 1][col].hasMine) {
+ minesAround++;
+ } // bottom-right
+ if (col + 1 < colLength && mineField[row + 1][col + 1].hasMine) {
+ minesAround++;
+ }
+ if (mineField[row + 1][col].visible == false) {
+ mineField[row + 1][col].addBorder = 1;
+ }
+ }
+
+ return minesAround;
+ }
+}
diff --git a/lib/views/game_center/minesweeper/helper/audio_player.dart b/lib/views/game_center/minesweeper/helper/audio_player.dart
new file mode 100755
index 0000000..aa5c281
--- /dev/null
+++ b/lib/views/game_center/minesweeper/helper/audio_player.dart
@@ -0,0 +1,85 @@
+import 'package:flutter/material.dart';
+import 'package:just_audio/just_audio.dart';
+import 'package:just_audio_background/just_audio_background.dart';
+
+import '../../../../services/my_audio_handler.dart';
+import '../../../../services/service_locator.dart';
+import '../utils/game_sounds.dart';
+
+class GameAudioPlayer {
+ final _audioHandler = getIt();
+
+ static late AudioPlayer _player;
+ static bool playable = true;
+
+ GameAudioPlayer() {
+ // 2024-02-01 因为有用到 just_audio_background,不支持多音源播放,可以使用 audio_service ,、
+ // 但暂时不做,因为放着歌还听背景音乐不方便,也主要是懒
+ // 因为和音乐播放器共用同一个player,所以这里直接复用音乐播放器的那个
+ // _player = AudioPlayer();
+ _player = _audioHandler.player();
+ // _player.play();
+ }
+
+ // 2024-02-01 因为和音乐播放器共用同一个player,所以扫雷重置音乐播放只是停止即可
+ void resetPlayer(bool soundOn) {
+ _player.stop();
+ _player.setVolume(soundOn ? 1 : 0);
+ }
+
+ static void pause() {
+ playable = false;
+ _player.pause();
+ }
+
+ static void resume() {
+ playable = true;
+ try {
+ _player.play();
+ } catch (e) {
+ debugPrint(e.toString());
+ }
+ }
+
+ static Future setVolume(bool soundOn) async {
+ await _player.setVolume(soundOn ? 1 : 0);
+ }
+
+ /// 2024-02-01 因为我原本使用的音频播放器也是just audio,所以这里会报错:
+ /// Unhandled Exception: PlatformException(error, just_audio_background supports only a single player instance, null, null)
+
+ Future _setAudio(String audioPath) async {
+ try {
+ // 2024-02-01 原本使用这些方式替换音乐,但是会报错如下:
+ // Error loading audio source: type 'Null' is not a subtype of type 'MediaItem' in type cast
+ // 应该还是和背景播放有些冲突,所以使用下面那个
+ // await _player.setAudioSource(
+ // AudioSource.asset(audioPath),
+ // );
+ // await _player.setAsset(audioPath);
+
+ await _player.setAudioSource(AudioSource.asset(
+ audioPath,
+ tag: MediaItem(
+ id: audioPath,
+ title: audioPath,
+ ),
+ ));
+
+ _player.setLoopMode(LoopMode.off);
+ return true;
+ } catch (e) {
+ debugPrint("Error loading audio source: $e");
+ }
+ return false;
+ }
+
+ Future playAudio(Sound sound, {bool loop = false}) async {
+ if (await _setAudio(sound.toPath) && playable) {
+ if (loop) {
+ _player.setLoopMode(LoopMode.one);
+ }
+ _player.play();
+ }
+ }
+}
diff --git a/lib/views/game_center/minesweeper/helper/shared_helper.dart b/lib/views/game_center/minesweeper/helper/shared_helper.dart
new file mode 100755
index 0000000..5e52942
--- /dev/null
+++ b/lib/views/game_center/minesweeper/helper/shared_helper.dart
@@ -0,0 +1,66 @@
+import 'package:shared_preferences/shared_preferences.dart';
+
+import '../utils/game_consts.dart';
+
+class SharedHelper {
+ late final SharedPreferences _prefs;
+
+ SharedHelper._create();
+
+ static Future init() async {
+ var sharedHelper = SharedHelper._create();
+ sharedHelper._prefs = await SharedPreferences.getInstance();
+ return sharedHelper;
+ }
+
+ Future getHowToPlayShown() async {
+ return _prefs.getBool("HowToPlay") ?? false;
+ }
+
+ Future setHowToPlayShown(bool value) async {
+ return _prefs.setBool("HowToPlay", value);
+ }
+
+ Future getBestTime(GameMode gameMode) async {
+ return _prefs.getInt("${gameMode.name}:BestTime");
+ }
+
+ Future setBestTime(GameMode gameMode, int time) async {
+ return _prefs.setInt("${gameMode.name}:BestTime", time);
+ }
+
+ Future getGamesWon(GameMode gameMode) async {
+ return _prefs.getInt("${gameMode.name}:GamesWon");
+ }
+
+ Future increaseGamesWon(GameMode gameMode) async {
+ int gamesWon = await getGamesWon(gameMode) ?? 0;
+ return _prefs.setInt("${gameMode.name}:GamesWon", gamesWon + 1);
+ }
+
+ Future getGamesStarted(GameMode gameMode) async {
+ return _prefs.getInt("${gameMode.name}:GamesStarted");
+ }
+
+ Future increaseGamesStarted(GameMode gameMode) async {
+ int gamesStarted = await getGamesStarted(gameMode) ?? 0;
+ return _prefs.setInt("${gameMode.name}:GamesStarted", gamesStarted + 1);
+ }
+
+ Future getAverageTime(GameMode gameMode) async {
+ return _prefs.getInt("${gameMode.name}:AverageTime");
+ }
+
+ Future updateAverageTime(GameMode gameMode, int time) async {
+ int? averageTime = await getAverageTime(gameMode);
+ int? gamesWon = await getGamesWon(gameMode);
+
+ if (averageTime == null || gamesWon == null) {
+ averageTime = time;
+ } else {
+ var totalTime = gamesWon * averageTime;
+ averageTime = ((totalTime + time) / gamesWon + 1).round();
+ }
+ return _prefs.setInt("${gameMode.name}:AverageTime", averageTime);
+ }
+}
diff --git a/lib/views/game_center/minesweeper/index.dart b/lib/views/game_center/minesweeper/index.dart
new file mode 100644
index 0000000..98db99f
--- /dev/null
+++ b/lib/views/game_center/minesweeper/index.dart
@@ -0,0 +1,20 @@
+import 'package:flutter/material.dart';
+
+import 'utils/exports.dart';
+import 'view/splash_view/splash_view.dart';
+
+class InitMinesweeper extends StatefulWidget {
+ const InitMinesweeper({super.key});
+
+ @override
+ State createState() => _InitMinesweeperState();
+}
+
+class _InitMinesweeperState extends State {
+ @override
+ Widget build(BuildContext context) {
+ GameSizes.init(context);
+
+ return const SplashView();
+ }
+}
diff --git a/lib/views/game_center/minesweeper/mixins/statistics_mixin.dart b/lib/views/game_center/minesweeper/mixins/statistics_mixin.dart
new file mode 100755
index 0000000..d5e66de
--- /dev/null
+++ b/lib/views/game_center/minesweeper/mixins/statistics_mixin.dart
@@ -0,0 +1,41 @@
+import '../helper/shared_helper.dart';
+import '../utils/game_consts.dart';
+
+mixin StatisticsMixin {
+ String timeFormatter(int? time) {
+ if (time == null) {
+ return "--:--";
+ }
+ Duration duration = Duration(seconds: time);
+ int minutes = duration.inMinutes;
+ int seconds = duration.inSeconds - minutes * 60;
+ return "${(minutes > 9 ? "" : "0")}$minutes:${(seconds > 9 ? "" : "0")}$seconds";
+ }
+
+ Future