@@ -12,11 +12,30 @@ const GAME_PAD_MATRIX_W = 10;
12
12
13
13
///state of [GameControl]
14
14
enum GameStates {
15
+ ///随时可以开启一把惊险而又刺激的俄罗斯方块
15
16
none,
17
+
18
+ ///游戏暂停中,方块的下落将会停止
16
19
paused,
20
+
21
+ ///游戏正在进行中,方块正在下落
22
+ ///按键可交互
17
23
running,
24
+
25
+ ///游戏正在重置
26
+ ///重置完成之后,[GameController] 状态将会迁移为[none]
18
27
reset,
19
- ended,
28
+
29
+ ///下落方块已经到达底部,此时正在将方块固定在游戏矩阵中
30
+ ///固定完成之后,将会立即开始下一个方块的下落任务
31
+ mixing,
32
+
33
+ ///正在消除行
34
+ ///消除完成之后,将会立刻开始下一个方块的下落任务
35
+ clear,
36
+
37
+ ///方块快速下坠到底部
38
+ drop,
20
39
}
21
40
22
41
class Game extends StatefulWidget {
@@ -50,7 +69,6 @@ const _SPEED = [
50
69
const Duration (milliseconds: 650 ),
51
70
const Duration (milliseconds: 500 ),
52
71
const Duration (milliseconds: 370 ),
53
- const Duration (milliseconds: 800 ),
54
72
const Duration (milliseconds: 250 ),
55
73
const Duration (milliseconds: 160 ),
56
74
];
@@ -60,12 +78,21 @@ class GameControl extends State<Game> {
60
78
//inflate game pad data
61
79
for (int i = 0 ; i < GAME_PAD_MATRIX_H ; i++ ) {
62
80
_data.add (List .filled (GAME_PAD_MATRIX_W , 0 ));
81
+ _mask.add (List .filled (GAME_PAD_MATRIX_W , 0 ));
63
82
}
64
83
}
65
84
66
85
///the gamer data
67
86
final List <List <int >> _data = [];
68
87
88
+ ///在 [build] 方法中于 [_data] 混合,形成一个新的矩阵
89
+ ///[_mask] 矩阵的宽高与 [_data] 一致
90
+ ///对于任意的 _shinning[x,y] :
91
+ /// 如果值为 0,则对 [_data] 没有任何影响
92
+ /// 如果值为 -1,则表示 [_data] 中该行不显示
93
+ /// 如果值为 1,则表示 [_data] 中改行高亮
94
+ final List <List <int >> _mask = [];
95
+
69
96
///from 1-6
70
97
int _level = 1 ;
71
98
@@ -122,20 +149,23 @@ class GameControl extends State<Game> {
122
149
setState (() {});
123
150
}
124
151
125
- void drop () {
152
+ void drop () async {
126
153
if (_states == GameStates .running && _current != null ) {
127
154
for (int i = 0 ; i < GAME_PAD_MATRIX_H ; i++ ) {
128
155
final fall = _current.fall (step: i + 1 );
129
156
if (! fall.isValidInMatrix (_data)) {
130
157
sounds.fall ();
131
158
_current = _current.fall (step: i);
159
+ _states = GameStates .drop;
160
+ setState (() {});
161
+ await Future .delayed (const Duration (milliseconds: 50 ));
132
162
_mixCurrentIntoData ();
133
163
break ;
134
164
}
135
165
}
136
166
setState (() {});
137
167
} else if (_states == GameStates .paused || _states == GameStates .none) {
138
- _scheduledRunning ();
168
+ _startGame ();
139
169
}
140
170
}
141
171
@@ -154,15 +184,17 @@ class GameControl extends State<Game> {
154
184
setState (() {});
155
185
}
156
186
157
- void _mixCurrentIntoData () {
187
+ Timer _autoFallTimer;
188
+
189
+ ///mix current into [_data]
190
+ Future <void > _mixCurrentIntoData () async {
158
191
if (_current == null ) {
159
192
return ;
160
193
}
161
- for (int i = 0 ; i < GAME_PAD_MATRIX_H ; i++ ) {
162
- for (int j = 0 ; j < GAME_PAD_MATRIX_W ; j++ ) {
163
- _data[i][j] = _current.get (j, i) ?? _data[i][j];
164
- }
165
- }
194
+ //cancel the auto falling task
195
+ _autoFall (false );
196
+
197
+ _forTable ((i, j) => _data[i][j] = _current.get (j, i) ?? _data[i][j]);
166
198
167
199
//消除行
168
200
final clearLines = [];
@@ -171,27 +203,82 @@ class GameControl extends State<Game> {
171
203
clearLines.add (i);
172
204
}
173
205
}
206
+
174
207
if (clearLines.isNotEmpty) {
208
+ setState (() => _states = GameStates .clear);
209
+
210
+ ///消除效果动画
211
+ for (int count = 0 ; count < 6 ; count++ ) {
212
+ clearLines.forEach ((line) {
213
+ _mask[line].fillRange (0 , GAME_PAD_MATRIX_W , count % 2 == 0 ? - 1 : 1 );
214
+ });
215
+ setState (() {});
216
+ await Future .delayed (Duration (milliseconds: 80 ));
217
+ }
218
+ clearLines
219
+ .forEach ((line) => _mask[line].fillRange (0 , GAME_PAD_MATRIX_W , 0 ));
220
+
221
+ //移除所有被消除的行
175
222
clearLines.forEach ((line) {
176
223
_data.setRange (1 , line + 1 , _data);
177
224
_data[0 ] = List .filled (GAME_PAD_MATRIX_W , 0 );
178
225
});
179
226
debugPrint ("clear lines : $clearLines " );
227
+
180
228
_cleared += clearLines.length;
181
229
_points += clearLines.length * _level * 5 ;
230
+
231
+ //up level possible when cleared
232
+ int level = (_cleared ~ / 50 ) + _LEVEL_MIN ;
233
+ _level = level <= _LEVEL_MAX && level > _level ? level : _level;
234
+ } else {
235
+ _states = GameStates .mixing;
236
+ _forTable ((i, j) => _mask[i][j] = _current.get (j, i) ?? _mask[i][j]);
237
+ setState (() {});
238
+ await Future .delayed (const Duration (milliseconds: 120 ));
239
+ _forTable ((i, j) => _mask[i][j] = 0 );
240
+ setState (() {});
182
241
}
183
242
243
+ //_current已经融入_data了,所以不再需要
244
+ _current = null ;
245
+
184
246
//检查游戏是否结束,即检查第一行是否有元素为1
185
247
for (var item in _data[0 ]) {
186
248
if (item != 0 ) {
187
249
reset ();
250
+ return ;
188
251
}
189
252
}
190
253
191
- if (_states == GameStates .reset) {
192
- _current = null ;
193
- } else {
194
- _current = _getNext ();
254
+ //游戏尚未结束,开启下一轮方块下落
255
+ _startGame ();
256
+ }
257
+
258
+ ///遍历表格
259
+ ///i 为 row
260
+ ///j 为 column
261
+ static void _forTable (dynamic function (int row, int column)) {
262
+ for (int i = 0 ; i < GAME_PAD_MATRIX_H ; i++ ) {
263
+ for (int j = 0 ; j < GAME_PAD_MATRIX_W ; j++ ) {
264
+ final b = function (i, j);
265
+ if (b is bool && b) {
266
+ break ;
267
+ }
268
+ }
269
+ }
270
+ }
271
+
272
+ void _autoFall (bool enable) {
273
+ if (! enable && _autoFallTimer != null ) {
274
+ _autoFallTimer.cancel ();
275
+ _autoFallTimer = null ;
276
+ } else if (enable) {
277
+ _autoFallTimer? .cancel ();
278
+ _current = _current ?? _getNext ();
279
+ _autoFallTimer = Timer .periodic (_SPEED [_level], (t) {
280
+ down (enableSounds: false );
281
+ });
195
282
}
196
283
}
197
284
@@ -205,15 +292,15 @@ class GameControl extends State<Game> {
205
292
void pauseOrResume () {
206
293
if (_states == GameStates .running) {
207
294
pause ();
208
- } else {
209
- _scheduledRunning ();
295
+ } else if (_states == GameStates .paused || _states == GameStates .none) {
296
+ _startGame ();
210
297
}
211
298
}
212
299
213
300
void reset () {
214
301
if (_states == GameStates .none) {
215
302
//可以开始游戏
216
- _scheduledRunning ();
303
+ _startGame ();
217
304
return ;
218
305
}
219
306
if (_states == GameStates .reset) {
@@ -250,30 +337,13 @@ class GameControl extends State<Game> {
250
337
}();
251
338
}
252
339
253
- bool _runningScheduled = false ;
254
-
255
- void _scheduledRunning () {
256
- if (_states == GameStates .running) {
340
+ void _startGame () {
341
+ if (_states == GameStates .running && _autoFallTimer? .isActive == false ) {
257
342
return ;
258
343
}
259
344
_states = GameStates .running;
260
- _current = _current ?? _getNext ( );
345
+ _autoFall ( true );
261
346
setState (() {});
262
-
263
- if (_runningScheduled) {
264
- return ;
265
- }
266
- _runningScheduled = true ;
267
- Future .doWhile (() async {
268
- if (! mounted) {
269
- return false ;
270
- }
271
- await Future .delayed (_SPEED [_level]);
272
- down (enableSounds: false );
273
- return _states == GameStates .running;
274
- }).whenComplete (() {
275
- _runningScheduled = false ;
276
- });
277
347
}
278
348
279
349
@override
@@ -282,9 +352,16 @@ class GameControl extends State<Game> {
282
352
for (var i = 0 ; i < GAME_PAD_MATRIX_H ; i++ ) {
283
353
mixed.add (List .filled (GAME_PAD_MATRIX_W , 0 ));
284
354
for (var j = 0 ; j < GAME_PAD_MATRIX_W ; j++ ) {
285
- mixed[i][j] = _current? .get (j, i) ?? _data[i][j];
355
+ int value = _current? .get (j, i) ?? _data[i][j];
356
+ if (_mask[i][j] == - 1 ) {
357
+ value = 0 ;
358
+ } else if (_mask[i][j] == 1 ) {
359
+ value = 2 ;
360
+ }
361
+ mixed[i][j] = value;
286
362
}
287
363
}
364
+ debugPrint ("game states : $_states " );
288
365
return GameState (
289
366
mixed, _states, _level, sounds.muted, _points, _cleared, _next,
290
367
child: widget.child);
@@ -305,6 +382,10 @@ class GameState extends InheritedWidget {
305
382
306
383
final Widget child;
307
384
385
+ ///屏幕展示数据
386
+ ///0: 空砖块
387
+ ///1: 普通砖块
388
+ ///2: 高亮砖块
308
389
final List <List <int >> data;
309
390
310
391
final GameStates states;
0 commit comments