美文网首页
用flutter写一个俄罗斯方块

用flutter写一个俄罗斯方块

作者: 大肉虫子 | 来源:发表于2023-05-04 07:08 被阅读0次

以下是基于 Flutter 编写的一个简单的俄罗斯方块游戏。

在此过程中将使用以下 Flutter 包:

  1. flutter/material.dart (提供 Material 风格的组件和视觉效果)
  2. flutter/widgets.dart (各种基础的widget)
  3. flutter/animation.dart (用于动画处理)
  4. dart:math (用于生成随机数)

实现思路:

  • 游戏分为游戏区和下一个方块区两个区域。
  • 每次出现的方块为七种,而出现的形状和颜色随机,方块旋转时进行碰撞检测及旋转调整,碰撞时进行堆叠处理。
  • 当满足整行的方块都完整时消行,累计得分。当游戏区完全被填满方块时,游戏结束。

代码实现如下:

import 'dart:math';
import 'package:flutter/material.dart';

void main() => runApp(Tetris());

class Tetris extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Flutter Tetris Game',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> with SingleTickerProviderStateMixin {
  final int _numOfRows = 20;
  final int _numOfColumns = 10;
  final double _squareSize = 20.0;

  late AnimationController _controller;
  late Animation<double> _animation;
  late List<Point<int>> _activePosition;
  late List<Point<int>> _passivePosition;
  late List<List<bool>> _filledPosition;
  late List<Color> _colorList;
  late List<int> _scoreList;
  late int _currentScore = 0;
  late int _highestScore = 0;

  late int _numOfNextSquare;
  late List<Point<int>> _nextSquarePosition;

  late Timer _timer;

  late Random _random;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: Duration(milliseconds: 100),
      vsync: this,
    )..addListener(() {
        setState(() {});
      });

    _animation = Tween<double>(begin: 0.0, end: 1.0).animate(_controller);

    _activePosition = [Point(0, 3), Point(0, 4), Point(1, 3), Point(1, 4)];
    _passivePosition = [];
    _filledPosition = List.generate(
      _numOfRows,
      (_) => List<bool>.generate(_numOfColumns, (_) => false),
    );
    _colorList = [
      Colors.redAccent,
      Colors.lightGreenAccent,
      Colors.yellowAccent,
      Colors.blueAccent,
      Colors.deepPurpleAccent,
      Colors.tealAccent,
      Colors.orangeAccent
    ];
    _scoreList = [0, 100, 300, 500, 800];

    _numOfNextSquare = _random.nextInt(7);
    _nextSquarePosition = _getNextSquare(_numOfNextSquare);

    _timer = Timer.periodic(Duration(seconds: 1), (timer) {
      if (_activePosition.any((p) => p.x == _numOfRows - 1) ||
          _activePosition.any((p) => _filledPosition[p.x + 1][p.y])) {
        _timer.cancel();

        _passivePosition.addAll(_activePosition);
        _activePosition.clear();

        _checkIfLineIsComplete();
        _currentScore += _scoreList[_scoreList.indexWhere((s) => s > _currentScore) - 1];
        if (_currentScore > _highestScore) {
          _highestScore = _currentScore;
        }

        _activePosition = _getNextSquare(_numOfNextSquare);
        _fillActivePosition(true);
        _numOfNextSquare = _random.nextInt(7);
        _nextSquarePosition = _getNextSquare(_numOfNextSquare);

        if (_activePosition.any((p) => _filledPosition[p.x][p.y])) {
          _showGameOverDialog();
        } else {
          _animateMovement();
        }
      } else {
        _fillActivePosition(false);

        setState(() {
          _activePosition = _activePosition.map((p) => p + Offset(1, 0) as Point<int>).toList();
        });

        _fillActivePosition(true);
      }
    });
  }

  @override
  void dispose() {
    _controller.dispose();
    _timer.cancel();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Flutter Tetris Game'),
        centerTitle: true,
      ),
      body: Container(
        decoration: BoxDecoration(
          gradient: LinearGradient(
            begin: Alignment.topLeft,
            end: Alignment.bottomRight,
            colors: [Colors.amber.shade50, Colors.amber.shade900],
          ),
        ),
        child: Row(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Padding(
              padding: const EdgeInsets.only(top: 50, left: 20),
              child: Column(
                mainAxisAlignment: MainAxisAlignment.start,
                children: [
                  SizedBox(
                    width: 6 * _squareSize,
                    height: 6 * _squareSize,
                    child: GridView.builder(
                      physics: NeverScrollableScrollPhysics(),
                      itemCount: 4,
                      gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                        crossAxisCount: 2,
                        childAspectRatio: 1,
                      ),
                      itemBuilder: (context, index) {
                        int row = _nextSquarePosition[index].x;
                        int column = _nextSquarePosition[index].y;

                        return Container(
                          color: _colorList[_numOfNextSquare],
                          margin: EdgeInsets.all(1),
                          decoration: BoxDecoration(
                            border: Border.all(width: 1, color: Colors.black),
                            borderRadius: BorderRadius.circular(5),
                          ),
                          child: AnimatedOpacity(
                            opacity: _animation.value,
                            duration: Duration(milliseconds: 100),
                            child: _activePosition.any((p) => p == Point(row, column))
                                ? Container(
                                    decoration: BoxDecoration(
                                      shape: BoxShape.circle,
                                      color: Colors.white.withOpacity(0.7),
                                    ),
                                  )
                                : null,
                          ),
                        );
                      },
                    ),
                  ),
                  SizedBox(height: 50),
                  Text('Score: $_currentScore', style: TextStyle(fontSize: 24)),
                  SizedBox(height: 20),
                  Text('Highest Score: $_highestScore', style: TextStyle(fontSize: 24)),
                  SizedBox(height: 50),
                  FloatingActionButton(
                    onPressed: () {
                      setState(() {
                        _filledPosition = List.generate(
                          _numOfRows,
                          (_) => List<bool>.generate(_numOfColumns, (_) => false),
                        );
                        _activePosition.clear();
                        _passivePosition.clear();
                        _currentScore = 0;

                        _activePosition = _getNextSquare(_numOfNextSquare);
                        _fillActivePosition(true);
                      });

                      _timer.cancel();
                      _timer = Timer.periodic(Duration(seconds: 1), (timer) {
                        if (_activePosition.any((p) => p.x == _numOfRows - 1) ||
                            _activePosition.any((p) => _filledPosition[p.x + 1][p.y])) {
                          _timer.cancel();

                          _passivePosition.addAll(_activePosition);
                          _activePosition.clear();

                          _checkIfLineIsComplete();
                          _currentScore +=
                              _scoreList[_scoreList.indexWhere((s) => s > _currentScore) - 1];
                          if (_currentScore > _highestScore) {
                            _highestScore = _currentScore;
                          }

                          _activePosition = _getNextSquare(_numOfNextSquare);
                          _fillActivePosition(true);
                          _numOfNextSquare = _random.nextInt(7);
                          _nextSquarePosition = _getNextSquare(_numOfNextSquare);

                          if (_activePosition.any((p) => _filledPosition[p.x][p.y])) {
                            _showGameOverDialog();
                          } else {
                            _animateMovement();
                          }
                        } else {
                          _fillActivePosition(false);

                          setState(() {
                            _activePosition =
                                _activePosition.map((p) => p + Offset(1, 0) as Point<int>).toList();
                          });

                          _fillActivePosition(true);
                        }
                      });
                    },
                    tooltip: 'Restart',
                    child: Icon(Icons.refresh),
                  ),
                ],
              ),
            ),
            Expanded(
              child: Container(
                height: _numOfRows * _squareSize,
                child: GridView.builder(
                  physics: NeverScrollableScrollPhysics(),
                  itemCount: _numOfRows * _numOfColumns,
                  gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                    crossAxisCount: _numOfColumns,
                    childAspectRatio: 1,
                  ),
                  itemBuilder: (context, index) {
                    int row = index ~/ _numOfColumns;
                    int column = index % _numOfColumns;

                    if (_activePosition.any((p) => p.x == row && p.y == column)) {
                      return Container(
                        color: _colorList[_numOfNextSquare],
                        margin: EdgeInsets.all(1),
                      );
                    } else if (_passivePosition.any((p) => p.x == row && p.y == column)) {
                      return Container(
                        color: _colorList[_passivePosition
                            .indexWhere((p) => p.x == row && p.y == column)],
                        margin: EdgeInsets.all(1),
                      );
                    } else {
                      return Container(
                        color: _filledPosition[row][column] ? Colors.white : Colors.transparent,
                        margin: EdgeInsets.all(1),
                      );
                    }
                  },
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }

  void _showGameOverDialog() {
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: Text('Game Over!'),
        content: Text('Would You like to restart?'),
        actions: [
          TextButton(
            onPressed: () {
              Navigator.pop(context);
            },
            child: Text(
              'NO',
              style: TextStyle(fontSize: 24),
            ),
          ),
          TextButton(
            onPressed: () {
              Navigator.pop(context);

              setState(() {
                _filledPosition = List.generate(
                  _numOfRows,
                  (_) => List<bool>.generate(_numOfColumns, (_) => false),
                );
                _activePosition.clear();
                _passivePosition.clear();
                _currentScore = 0;

                _activePosition = _getNextSquare(_numOfNextSquare);
                _fillActivePosition(true);
              });

              _timer.cancel();
              _timer = Timer.periodic(Duration(seconds: 1), (timer) {
                if (_activePosition.any((p) => p.x == _numOfRows - 1) ||
                    _activePosition.any((p) => _filledPosition[p.x + 1][p.y])) {
                  _timer.cancel();

                  _passivePosition.addAll(_activePosition);
                  _activePosition.clear();

                  _checkIfLineIsComplete();
                  _currentScore +=
                      _scoreList[_scoreList.indexWhere((s) => s > _currentScore) - 1];
                  if (_currentScore > _highestScore) {
                    _highestScore = _currentScore;
                  }

                  _activePosition = _getNextSquare(_numOfNextSquare);
                  _fillActivePosition(true);
                  _numOfNextSquare = _random.nextInt(7);
                  _nextSquarePosition = _getNextSquare(_numOfNextSquare);

                  if (_activePosition.any((p) => _filledPosition[p.x][p.y])) {
                    _showGameOverDialog();
                  } else {
                    _animateMovement();
                  }
                } else {
                  _fillActivePosition(false);

                  setState(() {
                    _activePosition =
                        _activePosition.map((p) => p + Offset(1, 0) as Point<int>).toList();
                  });

                  _fillActivePosition(true);
                }
              });
            },
            child: Text(
              'YES',
              style: TextStyle(fontSize: 24),
            ),
          ),
        ],
      ),
    );
  }

  List<Point<int>> _getNextSquare(int numOfSquare) {
    switch (numOfSquare) {
      case 0:
        // Square
        return [Point(0, 4), Point(0, 5), Point(1, 4), Point(1, 5)];
      case 1:
        // L1
        return [Point(0, 3), Point(1, 3), Point(2, 3), Point(2, 4)];
      case 2:
        // L2
        return [Point(0, 4), Point(1, 4), Point(2, 4), Point(2, 3)];
      case 3:
        // I
        return [Point(0, 3), Point(1, 3), Point(2, 3), Point(3, 3)];
      case 4:
        // S1
        return [Point(0, 4), Point(0, 5), Point(1, 3), Point(1, 4)];
      case 5:
        // S2
        return [Point(0, 3), Point(0, 4), Point(1, 4), Point(1, 5)];
      default:
        // T
        return [Point(0, 4), Point(1, 3), Point(1, 4), Point(1, 5)];
    }
  }

  void _animateMovement() {
    _controller.reset();
    _controller.forward();
  }

  void _fillActivePosition(bool filled) {
    _activePosition.forEach((p) {
      _filledPosition[p.x][p.y] = filled;
    });
  }

  void _checkIfLineIsComplete() {
    for (int i = _numOfRows - 1; i >= 0; i--) {
      if (_filledPosition[i].every((filled) => filled)) {
        setState(() {
          for (int j = i - 1; j >= 0; j--) {
            _filledPosition[j + 1].clear();
            _filledPosition[j + 1].addAll(_filledPosition[j]);
          }

          _filledPosition[0].fillRange(0, _numOfColumns, false);
        });

        _checkIfLineIsComplete();
        break;
      }
    }
  }
}

相关文章

网友评论

      本文标题:用flutter写一个俄罗斯方块

      本文链接:https://www.haomeiwen.com/subject/jxstsdtx.html