gamer.dart 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437
  1. import 'dart:async';
  2. import 'package:flutter/material.dart';
  3. import 'package:tetris/gamer/block.dart';
  4. import 'package:tetris/main.dart';
  5. import 'package:tetris/material/audios.dart';
  6. ///the height of game pad
  7. const gamePadMatrixH = 20;
  8. ///the width of game pad
  9. const gamePadMatrixW = 10;
  10. ///state of [GameControl]
  11. enum GameStates {
  12. ///随时可以开启一把惊险而又刺激的俄罗斯方块
  13. none,
  14. ///游戏暂停中,方块的下落将会停止
  15. paused,
  16. ///游戏正在进行中,方块正在下落
  17. ///按键可交互
  18. running,
  19. ///游戏正在重置
  20. ///重置完成之后,[GameController]状态将会迁移为[none]
  21. reset,
  22. ///下落方块已经到达底部,此时正在将方块固定在游戏矩阵中
  23. ///固定完成之后,将会立即开始下一个方块的下落任务
  24. mixing,
  25. ///正在消除行
  26. ///消除完成之后,将会立刻开始下一个方块的下落任务
  27. clear,
  28. ///方块快速下坠到底部
  29. drop,
  30. }
  31. class Game extends StatefulWidget {
  32. final Widget child;
  33. const Game({Key? key, required this.child}) : super(key: key);
  34. @override
  35. State<StatefulWidget> createState() {
  36. return GameControl();
  37. }
  38. static GameControl of(BuildContext context) {
  39. final state = context.findAncestorStateOfType<GameControl>();
  40. assert(state != null, "must wrap this context with [Game]");
  41. return state!;
  42. }
  43. }
  44. ///duration for show a line when reset
  45. const restLineDuration = Duration(milliseconds: 50);
  46. const levelMax = 6;
  47. const levelMin = 1;
  48. const speed = [
  49. Duration(milliseconds: 800),
  50. Duration(milliseconds: 650),
  51. Duration(milliseconds: 500),
  52. Duration(milliseconds: 370),
  53. Duration(milliseconds: 250),
  54. Duration(milliseconds: 160),
  55. ];
  56. class GameControl extends State<Game> with RouteAware {
  57. GameControl() {
  58. //inflate game pad data
  59. for (int i = 0; i < gamePadMatrixH; i++) {
  60. _data.add(List.filled(gamePadMatrixW, 0));
  61. _mask.add(List.filled(gamePadMatrixW, 0));
  62. }
  63. }
  64. @override
  65. void didChangeDependencies() {
  66. super.didChangeDependencies();
  67. routeObserver.subscribe(this, ModalRoute.of(context)!);
  68. }
  69. @override
  70. void dispose() {
  71. routeObserver.unsubscribe(this);
  72. super.dispose();
  73. }
  74. @override
  75. void didPushNext() {
  76. //pause when screen is at background
  77. pause();
  78. }
  79. ///the gamer data
  80. final List<List<int>> _data = [];
  81. ///在 [build] 方法中于 [_data]混合,形成一个新的矩阵
  82. ///[_mask]矩阵的宽高与 [_data] 一致
  83. ///对于任意的 _mask[x,y] :
  84. /// 如果值为 0,则对 [_data]没有任何影响
  85. /// 如果值为 -1,则表示 [_data] 中该行不显示
  86. /// 如果值为 1,则表示 [_data] 中该行高亮
  87. final List<List<int>> _mask = [];
  88. ///from 1-6
  89. int _level = 1;
  90. int _points = 0;
  91. int _cleared = 0;
  92. Block? _current;
  93. Block _next = Block.getRandom();
  94. GameStates _states = GameStates.none;
  95. Block _getNext() {
  96. final next = _next;
  97. _next = Block.getRandom();
  98. return next;
  99. }
  100. SoundState get _sound => Sound.of(context);
  101. void rotate() {
  102. if (_states == GameStates.running) {
  103. final next = _current?.rotate();
  104. if (next != null && next.isValidInMatrix(_data)) {
  105. _current = next;
  106. _sound.rotate();
  107. }
  108. }
  109. setState(() {});
  110. }
  111. void right() {
  112. if (_states == GameStates.none && _level < levelMax) {
  113. _level++;
  114. } else if (_states == GameStates.running) {
  115. final next = _current?.right();
  116. if (next != null && next.isValidInMatrix(_data)) {
  117. _current = next;
  118. _sound.move();
  119. }
  120. }
  121. setState(() {});
  122. }
  123. void left() {
  124. if (_states == GameStates.none && _level > levelMin) {
  125. _level--;
  126. } else if (_states == GameStates.running) {
  127. final next = _current?.left();
  128. if (next != null && next.isValidInMatrix(_data)) {
  129. _current = next;
  130. _sound.move();
  131. }
  132. }
  133. setState(() {});
  134. }
  135. void drop() async {
  136. if (_states == GameStates.running) {
  137. for (int i = 0; i < gamePadMatrixH; i++) {
  138. final fall = _current?.fall(step: i + 1);
  139. if (fall != null && !fall.isValidInMatrix(_data)) {
  140. _current = _current?.fall(step: i);
  141. _states = GameStates.drop;
  142. setState(() {});
  143. await Future.delayed(const Duration(milliseconds: 100));
  144. _mixCurrentIntoData(mixSound: _sound.fall);
  145. break;
  146. }
  147. }
  148. setState(() {});
  149. } else if (_states == GameStates.paused || _states == GameStates.none) {
  150. _startGame();
  151. }
  152. }
  153. void down({bool enableSounds = true}) {
  154. if (_states == GameStates.running) {
  155. final next = _current?.fall();
  156. if (next != null && next.isValidInMatrix(_data)) {
  157. _current = next;
  158. if (enableSounds) {
  159. _sound.move();
  160. }
  161. } else {
  162. _mixCurrentIntoData();
  163. }
  164. }
  165. setState(() {});
  166. }
  167. Timer? _autoFallTimer;
  168. ///mix current into [_data]
  169. Future<void> _mixCurrentIntoData({VoidCallback? mixSound}) async {
  170. if (_current == null) {
  171. return;
  172. }
  173. //cancel the auto falling task
  174. _autoFall(false);
  175. _forTable((i, j) => _data[i][j] = _current?.get(j, i) ?? _data[i][j]);
  176. //消除行
  177. final clearLines = [];
  178. for (int i = 0; i < gamePadMatrixH; i++) {
  179. if (_data[i].every((d) => d == 1)) {
  180. clearLines.add(i);
  181. }
  182. }
  183. if (clearLines.isNotEmpty) {
  184. setState(() => _states = GameStates.clear);
  185. _sound.clear();
  186. ///消除效果动画
  187. for (int count = 0; count < 5; count++) {
  188. clearLines.forEach((line) {
  189. _mask[line].fillRange(0, gamePadMatrixW, count % 2 == 0 ? -1 : 1);
  190. });
  191. setState(() {});
  192. await Future.delayed(const Duration(milliseconds: 100));
  193. }
  194. clearLines.forEach((line) => _mask[line].fillRange(0, gamePadMatrixW, 0));
  195. //移除所有被消除的行
  196. clearLines.forEach((line) {
  197. _data.setRange(1, line + 1, _data);
  198. _data[0] = List.filled(gamePadMatrixW, 0);
  199. });
  200. debugPrint("clear lines : $clearLines");
  201. _cleared += clearLines.length;
  202. _points += clearLines.length * _level * 5;
  203. //up level possible when cleared
  204. int level = (_cleared ~/ 50) + levelMin;
  205. _level = level <= levelMax && level > _level ? level : _level;
  206. } else {
  207. _states = GameStates.mixing;
  208. mixSound?.call();
  209. _forTable((i, j) => _mask[i][j] = _current?.get(j, i) ?? _mask[i][j]);
  210. setState(() {});
  211. await Future.delayed(const Duration(milliseconds: 200));
  212. _forTable((i, j) => _mask[i][j] = 0);
  213. setState(() {});
  214. }
  215. //_current已经融入_data了,所以不再需要
  216. _current = null;
  217. //检查游戏是否结束,即检查第一行是否有元素为1
  218. if (_data[0].contains(1)) {
  219. reset();
  220. return;
  221. } else {
  222. //游戏尚未结束,开启下一轮方块下落
  223. _startGame();
  224. }
  225. }
  226. ///遍历表格
  227. ///i 为 row
  228. ///j 为 column
  229. static void _forTable(dynamic function(int row, int column)) {
  230. for (int i = 0; i < gamePadMatrixH; i++) {
  231. for (int j = 0; j < gamePadMatrixW; j++) {
  232. final b = function(i, j);
  233. if (b is bool && b) {
  234. break;
  235. }
  236. }
  237. }
  238. }
  239. void _autoFall(bool enable) {
  240. if (!enable) {
  241. _autoFallTimer?.cancel();
  242. _autoFallTimer = null;
  243. } else if (enable) {
  244. _autoFallTimer?.cancel();
  245. _current = _current ?? _getNext();
  246. _autoFallTimer = Timer.periodic(speed[_level - 1], (t) {
  247. down(enableSounds: false);
  248. });
  249. }
  250. }
  251. void pause() {
  252. if (_states == GameStates.running) {
  253. _states = GameStates.paused;
  254. }
  255. setState(() {});
  256. }
  257. void pauseOrResume() {
  258. if (_states == GameStates.running) {
  259. pause();
  260. } else if (_states == GameStates.paused || _states == GameStates.none) {
  261. _startGame();
  262. }
  263. }
  264. void reset() {
  265. if (_states == GameStates.none) {
  266. //可以开始游戏
  267. _startGame();
  268. return;
  269. }
  270. if (_states == GameStates.reset) {
  271. return;
  272. }
  273. _sound.start();
  274. _states = GameStates.reset;
  275. () async {
  276. int line = gamePadMatrixH;
  277. await Future.doWhile(() async {
  278. line--;
  279. for (int i = 0; i < gamePadMatrixW; i++) {
  280. _data[line][i] = 1;
  281. }
  282. setState(() {});
  283. await Future.delayed(restLineDuration);
  284. return line != 0;
  285. });
  286. _current = null;
  287. _getNext();
  288. _points = 0;
  289. _cleared = 0;
  290. await Future.doWhile(() async {
  291. for (int i = 0; i < gamePadMatrixW; i++) {
  292. _data[line][i] = 0;
  293. }
  294. setState(() {});
  295. line++;
  296. await Future.delayed(restLineDuration);
  297. return line != gamePadMatrixH;
  298. });
  299. setState(() {
  300. _states = GameStates.none;
  301. });
  302. }();
  303. }
  304. void _startGame() {
  305. if (_states == GameStates.running && _autoFallTimer?.isActive == false) {
  306. return;
  307. }
  308. _states = GameStates.running;
  309. _autoFall(true);
  310. setState(() {});
  311. }
  312. @override
  313. Widget build(BuildContext context) {
  314. List<List<int>> mixed = [];
  315. for (var i = 0; i < gamePadMatrixH; i++) {
  316. mixed.add(List.filled(gamePadMatrixW, 0));
  317. for (var j = 0; j < gamePadMatrixW; j++) {
  318. int value = _current?.get(j, i) ?? _data[i][j];
  319. if (_mask[i][j] == -1) {
  320. value = 0;
  321. } else if (_mask[i][j] == 1) {
  322. value = 2;
  323. }
  324. mixed[i][j] = value;
  325. }
  326. }
  327. debugPrint("game states : $_states");
  328. return GameState(
  329. mixed, _states, _level, _sound.mute, _points, _cleared, _next,
  330. child: widget.child);
  331. }
  332. void soundSwitch() {
  333. setState(() {
  334. _sound.mute = !_sound.mute;
  335. });
  336. }
  337. }
  338. class GameState extends InheritedWidget {
  339. const GameState(
  340. this.data,
  341. this.states,
  342. this.level,
  343. this.muted,
  344. this.points,
  345. this.cleared,
  346. this.next, {
  347. Key? key,
  348. required this.child,
  349. }) : super(key: key, child: child);
  350. final Widget child;
  351. ///屏幕展示数据
  352. ///0: 空砖块
  353. ///1: 普通砖块
  354. ///2: 高亮砖块
  355. final List<List<int>> data;
  356. final GameStates states;
  357. final int level;
  358. final bool muted;
  359. final int points;
  360. final int cleared;
  361. final Block next;
  362. static GameState of(BuildContext context) {
  363. return context.dependOnInheritedWidgetOfExactType<GameState>()!;
  364. }
  365. @override
  366. bool updateShouldNotify(GameState oldWidget) {
  367. return true;
  368. }
  369. }