gamer.dart 10 KB

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