123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437 |
- import 'dart:async';
- import 'package:flutter/material.dart';
- import 'package:tetris/gamer/block.dart';
- import 'package:tetris/main.dart';
- import 'package:tetris/material/audios.dart';
- ///the height of game pad
- const gamePadMatrixH = 20;
- ///the width of game pad
- const gamePadMatrixW = 10;
- ///state of [GameControl]
- enum GameStates {
- ///随时可以开启一把惊险而又刺激的俄罗斯方块
- none,
- ///游戏暂停中,方块的下落将会停止
- paused,
- ///游戏正在进行中,方块正在下落
- ///按键可交互
- running,
- ///游戏正在重置
- ///重置完成之后,[GameController]状态将会迁移为[none]
- reset,
- ///下落方块已经到达底部,此时正在将方块固定在游戏矩阵中
- ///固定完成之后,将会立即开始下一个方块的下落任务
- mixing,
- ///正在消除行
- ///消除完成之后,将会立刻开始下一个方块的下落任务
- clear,
- ///方块快速下坠到底部
- drop,
- }
- class Game extends StatefulWidget {
- final Widget child;
- const Game({Key? key, required this.child}) : super(key: key);
- @override
- State<StatefulWidget> createState() {
- return GameControl();
- }
- static GameControl of(BuildContext context) {
- final state = context.findAncestorStateOfType<GameControl>();
- assert(state != null, "must wrap this context with [Game]");
- return state!;
- }
- }
- ///duration for show a line when reset
- const restLineDuration = Duration(milliseconds: 50);
- const levelMax = 6;
- const levelMin = 1;
- const speed = [
- Duration(milliseconds: 800),
- Duration(milliseconds: 650),
- Duration(milliseconds: 500),
- Duration(milliseconds: 370),
- Duration(milliseconds: 250),
- Duration(milliseconds: 160),
- ];
- class GameControl extends State<Game> with RouteAware {
- GameControl() {
- //inflate game pad data
- for (int i = 0; i < gamePadMatrixH; i++) {
- _data.add(List.filled(gamePadMatrixW, 0));
- _mask.add(List.filled(gamePadMatrixW, 0));
- }
- }
- @override
- void didChangeDependencies() {
- super.didChangeDependencies();
- routeObserver.subscribe(this, ModalRoute.of(context)!);
- }
- @override
- void dispose() {
- routeObserver.unsubscribe(this);
- super.dispose();
- }
- @override
- void didPushNext() {
- //pause when screen is at background
- pause();
- }
- ///the gamer data
- final List<List<int>> _data = [];
- ///在 [build] 方法中于 [_data]混合,形成一个新的矩阵
- ///[_mask]矩阵的宽高与 [_data] 一致
- ///对于任意的 _mask[x,y] :
- /// 如果值为 0,则对 [_data]没有任何影响
- /// 如果值为 -1,则表示 [_data] 中该行不显示
- /// 如果值为 1,则表示 [_data] 中该行高亮
- final List<List<int>> _mask = [];
- ///from 1-6
- int _level = 1;
- int _points = 0;
- int _cleared = 0;
- Block? _current;
- Block _next = Block.getRandom();
- GameStates _states = GameStates.none;
- Block _getNext() {
- final next = _next;
- _next = Block.getRandom();
- return next;
- }
- SoundState get _sound => Sound.of(context);
- void rotate() {
- if (_states == GameStates.running) {
- final next = _current?.rotate();
- if (next != null && next.isValidInMatrix(_data)) {
- _current = next;
- _sound.rotate();
- }
- }
- setState(() {});
- }
- void right() {
- if (_states == GameStates.none && _level < levelMax) {
- _level++;
- } else if (_states == GameStates.running) {
- final next = _current?.right();
- if (next != null && next.isValidInMatrix(_data)) {
- _current = next;
- _sound.move();
- }
- }
- setState(() {});
- }
- void left() {
- if (_states == GameStates.none && _level > levelMin) {
- _level--;
- } else if (_states == GameStates.running) {
- final next = _current?.left();
- if (next != null && next.isValidInMatrix(_data)) {
- _current = next;
- _sound.move();
- }
- }
- setState(() {});
- }
- void drop() async {
- if (_states == GameStates.running) {
- for (int i = 0; i < gamePadMatrixH; i++) {
- final fall = _current?.fall(step: i + 1);
- if (fall != null && !fall.isValidInMatrix(_data)) {
- _current = _current?.fall(step: i);
- _states = GameStates.drop;
- setState(() {});
- await Future.delayed(const Duration(milliseconds: 100));
- _mixCurrentIntoData(mixSound: _sound.fall);
- break;
- }
- }
- setState(() {});
- } else if (_states == GameStates.paused || _states == GameStates.none) {
- _startGame();
- }
- }
- void down({bool enableSounds = true}) {
- if (_states == GameStates.running) {
- final next = _current?.fall();
- if (next != null && next.isValidInMatrix(_data)) {
- _current = next;
- if (enableSounds) {
- _sound.move();
- }
- } else {
- _mixCurrentIntoData();
- }
- }
- setState(() {});
- }
- Timer? _autoFallTimer;
- ///mix current into [_data]
- Future<void> _mixCurrentIntoData({VoidCallback? mixSound}) async {
- if (_current == null) {
- return;
- }
- //cancel the auto falling task
- _autoFall(false);
- _forTable((i, j) => _data[i][j] = _current?.get(j, i) ?? _data[i][j]);
- //消除行
- final clearLines = [];
- for (int i = 0; i < gamePadMatrixH; i++) {
- if (_data[i].every((d) => d == 1)) {
- clearLines.add(i);
- }
- }
- if (clearLines.isNotEmpty) {
- setState(() => _states = GameStates.clear);
- _sound.clear();
- ///消除效果动画
- for (int count = 0; count < 5; count++) {
- clearLines.forEach((line) {
- _mask[line].fillRange(0, gamePadMatrixW, count % 2 == 0 ? -1 : 1);
- });
- setState(() {});
- await Future.delayed(const Duration(milliseconds: 100));
- }
- clearLines.forEach((line) => _mask[line].fillRange(0, gamePadMatrixW, 0));
- //移除所有被消除的行
- clearLines.forEach((line) {
- _data.setRange(1, line + 1, _data);
- _data[0] = List.filled(gamePadMatrixW, 0);
- });
- debugPrint("clear lines : $clearLines");
- _cleared += clearLines.length;
- _points += clearLines.length * _level * 5;
- //up level possible when cleared
- int level = (_cleared ~/ 50) + levelMin;
- _level = level <= levelMax && level > _level ? level : _level;
- } else {
- _states = GameStates.mixing;
- mixSound?.call();
- _forTable((i, j) => _mask[i][j] = _current?.get(j, i) ?? _mask[i][j]);
- setState(() {});
- await Future.delayed(const Duration(milliseconds: 200));
- _forTable((i, j) => _mask[i][j] = 0);
- setState(() {});
- }
- //_current已经融入_data了,所以不再需要
- _current = null;
- //检查游戏是否结束,即检查第一行是否有元素为1
- if (_data[0].contains(1)) {
- reset();
- return;
- } else {
- //游戏尚未结束,开启下一轮方块下落
- _startGame();
- }
- }
- ///遍历表格
- ///i 为 row
- ///j 为 column
- static void _forTable(dynamic function(int row, int column)) {
- for (int i = 0; i < gamePadMatrixH; i++) {
- for (int j = 0; j < gamePadMatrixW; j++) {
- final b = function(i, j);
- if (b is bool && b) {
- break;
- }
- }
- }
- }
- void _autoFall(bool enable) {
- if (!enable) {
- _autoFallTimer?.cancel();
- _autoFallTimer = null;
- } else if (enable) {
- _autoFallTimer?.cancel();
- _current = _current ?? _getNext();
- _autoFallTimer = Timer.periodic(speed[_level - 1], (t) {
- down(enableSounds: false);
- });
- }
- }
- void pause() {
- if (_states == GameStates.running) {
- _states = GameStates.paused;
- }
- setState(() {});
- }
- void pauseOrResume() {
- if (_states == GameStates.running) {
- pause();
- } else if (_states == GameStates.paused || _states == GameStates.none) {
- _startGame();
- }
- }
- void reset() {
- if (_states == GameStates.none) {
- //可以开始游戏
- _startGame();
- return;
- }
- if (_states == GameStates.reset) {
- return;
- }
- _sound.start();
- _states = GameStates.reset;
- () async {
- int line = gamePadMatrixH;
- await Future.doWhile(() async {
- line--;
- for (int i = 0; i < gamePadMatrixW; i++) {
- _data[line][i] = 1;
- }
- setState(() {});
- await Future.delayed(restLineDuration);
- return line != 0;
- });
- _current = null;
- _getNext();
- _points = 0;
- _cleared = 0;
- await Future.doWhile(() async {
- for (int i = 0; i < gamePadMatrixW; i++) {
- _data[line][i] = 0;
- }
- setState(() {});
- line++;
- await Future.delayed(restLineDuration);
- return line != gamePadMatrixH;
- });
- setState(() {
- _states = GameStates.none;
- });
- }();
- }
- void _startGame() {
- if (_states == GameStates.running && _autoFallTimer?.isActive == false) {
- return;
- }
- _states = GameStates.running;
- _autoFall(true);
- setState(() {});
- }
- @override
- Widget build(BuildContext context) {
- List<List<int>> mixed = [];
- for (var i = 0; i < gamePadMatrixH; i++) {
- mixed.add(List.filled(gamePadMatrixW, 0));
- for (var j = 0; j < gamePadMatrixW; j++) {
- int value = _current?.get(j, i) ?? _data[i][j];
- if (_mask[i][j] == -1) {
- value = 0;
- } else if (_mask[i][j] == 1) {
- value = 2;
- }
- mixed[i][j] = value;
- }
- }
- debugPrint("game states : $_states");
- return GameState(
- mixed, _states, _level, _sound.mute, _points, _cleared, _next,
- child: widget.child);
- }
- void soundSwitch() {
- setState(() {
- _sound.mute = !_sound.mute;
- });
- }
- }
- class GameState extends InheritedWidget {
- const GameState(
- this.data,
- this.states,
- this.level,
- this.muted,
- this.points,
- this.cleared,
- this.next, {
- Key? key,
- required this.child,
- }) : super(key: key, child: child);
- final Widget child;
- ///屏幕展示数据
- ///0: 空砖块
- ///1: 普通砖块
- ///2: 高亮砖块
- final List<List<int>> data;
- final GameStates states;
- final int level;
- final bool muted;
- final int points;
- final int cleared;
- final Block next;
- static GameState of(BuildContext context) {
- return context.dependOnInheritedWidgetOfExactType<GameState>()!;
- }
- @override
- bool updateShouldNotify(GameState oldWidget) {
- return true;
- }
- }
|