Skip to content

Commit 2fb844f

Browse files
committed
添加行消除动画
1 parent d1bcba3 commit 2fb844f

File tree

7 files changed

+222
-82
lines changed

7 files changed

+222
-82
lines changed

lib/gamer/block.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ class Block {
133133
}
134134

135135
///return null if do not show at [x][y]
136+
///return 1 if show at [x,y]
136137
int get(int x, int y) {
137138
x -= xy[0];
138139
y -= xy[1];

lib/gamer/gamer.dart

Lines changed: 119 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,30 @@ const GAME_PAD_MATRIX_W = 10;
1212

1313
///state of [GameControl]
1414
enum GameStates {
15+
///随时可以开启一把惊险而又刺激的俄罗斯方块
1516
none,
17+
18+
///游戏暂停中,方块的下落将会停止
1619
paused,
20+
21+
///游戏正在进行中,方块正在下落
22+
///按键可交互
1723
running,
24+
25+
///游戏正在重置
26+
///重置完成之后,[GameController]状态将会迁移为[none]
1827
reset,
19-
ended,
28+
29+
///下落方块已经到达底部,此时正在将方块固定在游戏矩阵中
30+
///固定完成之后,将会立即开始下一个方块的下落任务
31+
mixing,
32+
33+
///正在消除行
34+
///消除完成之后,将会立刻开始下一个方块的下落任务
35+
clear,
36+
37+
///方块快速下坠到底部
38+
drop,
2039
}
2140

2241
class Game extends StatefulWidget {
@@ -50,7 +69,6 @@ const _SPEED = [
5069
const Duration(milliseconds: 650),
5170
const Duration(milliseconds: 500),
5271
const Duration(milliseconds: 370),
53-
const Duration(milliseconds: 800),
5472
const Duration(milliseconds: 250),
5573
const Duration(milliseconds: 160),
5674
];
@@ -60,12 +78,21 @@ class GameControl extends State<Game> {
6078
//inflate game pad data
6179
for (int i = 0; i < GAME_PAD_MATRIX_H; i++) {
6280
_data.add(List.filled(GAME_PAD_MATRIX_W, 0));
81+
_mask.add(List.filled(GAME_PAD_MATRIX_W, 0));
6382
}
6483
}
6584

6685
///the gamer data
6786
final List<List<int>> _data = [];
6887

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+
6996
///from 1-6
7097
int _level = 1;
7198

@@ -122,20 +149,23 @@ class GameControl extends State<Game> {
122149
setState(() {});
123150
}
124151

125-
void drop() {
152+
void drop() async {
126153
if (_states == GameStates.running && _current != null) {
127154
for (int i = 0; i < GAME_PAD_MATRIX_H; i++) {
128155
final fall = _current.fall(step: i + 1);
129156
if (!fall.isValidInMatrix(_data)) {
130157
sounds.fall();
131158
_current = _current.fall(step: i);
159+
_states = GameStates.drop;
160+
setState(() {});
161+
await Future.delayed(const Duration(milliseconds: 50));
132162
_mixCurrentIntoData();
133163
break;
134164
}
135165
}
136166
setState(() {});
137167
} else if (_states == GameStates.paused || _states == GameStates.none) {
138-
_scheduledRunning();
168+
_startGame();
139169
}
140170
}
141171

@@ -154,15 +184,17 @@ class GameControl extends State<Game> {
154184
setState(() {});
155185
}
156186

157-
void _mixCurrentIntoData() {
187+
Timer _autoFallTimer;
188+
189+
///mix current into [_data]
190+
Future<void> _mixCurrentIntoData() async {
158191
if (_current == null) {
159192
return;
160193
}
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]);
166198

167199
//消除行
168200
final clearLines = [];
@@ -171,27 +203,82 @@ class GameControl extends State<Game> {
171203
clearLines.add(i);
172204
}
173205
}
206+
174207
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+
//移除所有被消除的行
175222
clearLines.forEach((line) {
176223
_data.setRange(1, line + 1, _data);
177224
_data[0] = List.filled(GAME_PAD_MATRIX_W, 0);
178225
});
179226
debugPrint("clear lines : $clearLines");
227+
180228
_cleared += clearLines.length;
181229
_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(() {});
182241
}
183242

243+
//_current已经融入_data了,所以不再需要
244+
_current = null;
245+
184246
//检查游戏是否结束,即检查第一行是否有元素为1
185247
for (var item in _data[0]) {
186248
if (item != 0) {
187249
reset();
250+
return;
188251
}
189252
}
190253

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+
});
195282
}
196283
}
197284

@@ -205,15 +292,15 @@ class GameControl extends State<Game> {
205292
void pauseOrResume() {
206293
if (_states == GameStates.running) {
207294
pause();
208-
} else {
209-
_scheduledRunning();
295+
} else if (_states == GameStates.paused || _states == GameStates.none) {
296+
_startGame();
210297
}
211298
}
212299

213300
void reset() {
214301
if (_states == GameStates.none) {
215302
//可以开始游戏
216-
_scheduledRunning();
303+
_startGame();
217304
return;
218305
}
219306
if (_states == GameStates.reset) {
@@ -250,30 +337,13 @@ class GameControl extends State<Game> {
250337
}();
251338
}
252339

253-
bool _runningScheduled = false;
254-
255-
void _scheduledRunning() {
256-
if (_states == GameStates.running) {
340+
void _startGame() {
341+
if (_states == GameStates.running && _autoFallTimer?.isActive == false) {
257342
return;
258343
}
259344
_states = GameStates.running;
260-
_current = _current ?? _getNext();
345+
_autoFall(true);
261346
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-
});
277347
}
278348

279349
@override
@@ -282,9 +352,16 @@ class GameControl extends State<Game> {
282352
for (var i = 0; i < GAME_PAD_MATRIX_H; i++) {
283353
mixed.add(List.filled(GAME_PAD_MATRIX_W, 0));
284354
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;
286362
}
287363
}
364+
debugPrint("game states : $_states");
288365
return GameState(
289366
mixed, _states, _level, sounds.muted, _points, _cleared, _next,
290367
child: widget.child);
@@ -305,6 +382,10 @@ class GameState extends InheritedWidget {
305382

306383
final Widget child;
307384

385+
///屏幕展示数据
386+
///0: 空砖块
387+
///1: 普通砖块
388+
///2: 高亮砖块
308389
final List<List<int>> data;
309390

310391
final GameStates states;

lib/material/briks.dart

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,26 @@ import 'package:flutter/material.dart';
33
///the size of a single brik
44
Size brikSize = Size(16, 16);
55

6+
const _COLOR_NORMAL = Colors.black87;
7+
8+
const _COLOR_NULL = Colors.black12;
9+
10+
const _COLOR_HIGHLIGHT = Color(0xFF3E2723);
11+
612
///the basic brik for game panel
713
class Brik extends StatelessWidget {
8-
final bool enable;
14+
final Color color;
15+
16+
const Brik._({Key key, this.color}) : super(key: key);
917

10-
const Brik({Key key, this.enable = true}) : super(key: key);
18+
const Brik.normal() : this._(color: _COLOR_NORMAL);
19+
20+
const Brik.empty() : this._(color: _COLOR_NULL);
21+
22+
const Brik.highlight() : this._(color: _COLOR_HIGHLIGHT);
1123

1224
@override
1325
Widget build(BuildContext context) {
14-
Color color = enable ? Colors.black87 : Colors.black12;
15-
1626
return SizedBox.fromSize(
1727
size: brikSize,
1828
child: Container(
@@ -25,4 +35,4 @@ class Brik extends StatelessWidget {
2535
),
2636
);
2737
}
28-
}
38+
}

lib/material/images.dart

Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,27 +3,11 @@ import 'dart:async';
33
import 'package:flutter/material.dart';
44
import 'dart:ui' as ui;
55

6-
GameImages gameImages = GameImages._();
7-
86
///the image data of /assets/material.png
97
ui.Image material;
108

119
const _DIGITAL_ROW_SIZE = Size(14, 24);
1210

13-
class GameImages {
14-
GameImages._();
15-
16-
Widget get number0 {
17-
return SizedBox(
18-
width: 14,
19-
height: 24,
20-
child: Transform.translate(
21-
offset: const Offset(15, 15),
22-
child: Image.asset("assets/material.png"),
23-
),
24-
);
25-
}
26-
}
2711

2812
class Number extends StatelessWidget {
2913
final int length;
@@ -43,14 +27,14 @@ class Number extends StatelessWidget {
4327

4428
@override
4529
Widget build(BuildContext context) {
46-
String digitals = number?.toString() ?? "";
47-
if (digitals.length > length) {
48-
digitals = digitals.substring(digitals.length - length);
30+
String digitalStr = number?.toString() ?? "";
31+
if (digitalStr.length > length) {
32+
digitalStr = digitalStr.substring(digitalStr.length - length);
4933
}
50-
digitals = digitals.padLeft(length, padWithZero ? "0" : " ");
34+
digitalStr = digitalStr.padLeft(length, padWithZero ? "0" : " ");
5135
List<Widget> children = [];
5236
for (int i = 0; i < length; i++) {
53-
children.add(Digital(int.tryParse(digitals[i])));
37+
children.add(Digital(int.tryParse(digitalStr[i])));
5438
}
5539
return Row(
5640
mainAxisSize: MainAxisSize.min,

lib/panel/player_panel.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,9 @@ class _PlayerPad extends StatelessWidget {
4444
children: GameState.of(context).data.map((list) {
4545
return Row(
4646
children: list.map((b) {
47-
return Brik(enable: b == 1);
47+
return b == 1
48+
? const Brik.normal()
49+
: b == 2 ? const Brik.highlight() : const Brik.empty();
4850
}).toList(),
4951
);
5052
}).toList(),

0 commit comments

Comments
 (0)