一统天下 flutter - 游戏: 俄罗斯方块

发布时间 2023-09-18 11:19:31作者: webabcd

源码 https://github.com/webabcd/flutter_demo
作者 webabcd

一统天下 flutter - 游戏: 俄罗斯方块

示例如下:

lib\game\tetris\tetris.dart

/*
 * 俄罗斯方块
 *
 * 使用了 flame 库,在 pubspec.yaml 中做如下配置,然后 flutter pub get
 * dependencies:
 *   flame: ^1.7.3
 */


import 'package:flame/game.dart';
import 'package:flutter/material.dart';
import 'package:flutter_demo/game/tetris/shape/shape.dart';
import 'package:flutter_demo/game/tetris/shape/square.dart';
import 'package:flutter_demo/game/tetris/shape/t.dart';

import 'config.dart';
import 'controller.dart';
import 'core.dart';

class Tetris extends StatefulWidget {
  const Tetris({Key? key}) : super(key: key);

  @override
  TetrisState createState() => TetrisState();
}

class TetrisState extends State<Tetris> {

  final controller = Controller();

  @override
  Widget build(BuildContext context) {
    return Material(
      color: Colors.orange,
      child: Column(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: [
          Container(
            width: Config.mainMatrixWidth * Config.squareWidth,
            height: Config.mainMatrixHeight * Config.squareWidth,
            child: GameWidget(
              game: MyGame(controller: controller),
            ),
          ),
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
            children: [
              _button1(
                Icons.arrow_back, () { controller.left(); }, () { },
              ),
              _button1(
                Icons.arrow_forward, () { controller.right(); }, () { },
              ),
              _button1(
                Icons.rotate_right_rounded, () { controller.rotate(); }, () { },
              ),
              _button1(
                Icons.arrow_downward, () { controller.downPressed = true; }, () { controller.downPressed = false; },
              ),
            ],
          ),
        ],
      ),
    );
  }

  Ink _button1(IconData iconData, VoidCallback onDown, VoidCallback onUp) {
    return Ink(
      padding: EdgeInsets.zero,
      width: 36,
      height: 36,
      decoration: BoxDecoration(
        color: Colors.blue,
        borderRadius: BorderRadius.circular(18),
      ),
      child: InkWell(
        child: Icon(iconData, color: Colors.white,),
        onTapDown: (details) { onDown(); },
        onTapUp: (details) { onUp(); },
      ),
    );
  }
}

class MyGame extends FlameGame {
  MyGame({required this.controller});
  final Controller controller;
  bool shapeUpdated = false;

  List<Shape> shapeList = <Shape>[];
  List<List<Square?>> mainMatrix = <List<Square?>>[];

  @override
  Future<void>? onLoad() async {

    shapeList.add(Core.createShape());
    Core.initMainMatrix(mainMatrix);

    controller.addListener(() {
      if (controller.rotateTimes > 0) {
        shapeList[0].rotate();
        controller.rotateTimes--;
      } else if (controller.leftTimes > 0) {
        shapeList[0].left();
        controller.leftTimes--;
      } else if (controller.rightTimes > 0) {
        shapeList[0].right();
        controller.rightTimes--;
      }
      shapeUpdated = true;
    });

    return super.onLoad();
  }

  @override
  void render(Canvas canvas) {
    super.render(canvas);

    shapeList[0].render(canvas);

    for (var j = 0; j < mainMatrix.length; j++) {
      for (var i = 0; i < mainMatrix[j].length; i++) {
        var square = mainMatrix[j][i];
        if (square != null) {
          var offsetCenter = Offset(square.width / 2 + i * square.width, square.width / 2 + j * square.width);
          canvas.drawRect(Rect.fromCenter(center: offsetCenter, width: square.width, height: square.width), square.borderPaint);
          canvas.drawRect(Rect.fromCenter(center: offsetCenter, width: square.width - square.borderWidth, height: square.width - square.borderWidth), square.paint);
        }
      }
    }
  }

  @override
  void update(double dt) {
    super.update(dt);

    if (controller.downPressed) {
      Config.speed = 500;
    } else {
      Config.speed = 20;
    }

    var shape = shapeList[0];
    if (shapeUpdated) {
      shapeUpdated = false;
    } else {
      shape.update(dt);
    }

    var collisionType = Core.checkCollision(shape, mainMatrix);
    if (collisionType == CollisionType.bottom) {
      shape.loadPrev();
      Core.pinShape(shape, mainMatrix);
      controller.downPressed = false;
      Core.removeLineShape(mainMatrix);

      shapeList.removeAt(0);
      var newShape = Core.createShape();
      shapeList.add(newShape);
      if (Core.checkCollision(newShape, mainMatrix) == CollisionType.bottom) {
        Core.initMainMatrix(mainMatrix); // 死了重来
      }
    } else if (collisionType == CollisionType.edge) {
      shape.loadPrev();
    }
  }
}

lib\game\tetris\core.dart

import 'dart:math';

import 'package:flutter/material.dart';
import 'package:flutter_demo/game/tetris/shape/i.dart';
import 'package:flutter_demo/game/tetris/shape/l.dart';
import 'package:flutter_demo/game/tetris/shape/l2.dart';
import 'package:flutter_demo/game/tetris/shape/n.dart';
import 'package:flutter_demo/game/tetris/shape/n2.dart';
import 'package:flutter_demo/game/tetris/shape/o.dart';
import 'package:flutter_demo/game/tetris/shape/shape.dart';
import 'package:flutter_demo/game/tetris/shape/square.dart';
import 'package:flutter_demo/game/tetris/shape/t.dart';

import 'config.dart';

enum CollisionType { none, edge, bottom }

class Core {

  static var random = Random();

  static CollisionType checkCollision(Shape shape, List<List<Square?>> mainMatrix) {
    var shapeMatrix = shape.matrixList[shape.currentIndex];
    for (var j = 0; j < shapeMatrix.length; j++) { // 纵轴是 j 横轴是 i
      for (var i = 0; i < shapeMatrix[j].length; i++) {
        var cell = shapeMatrix[j][i];
        if (cell == 1) {
          var leftTopPoint = shape.leftTopPoint;
          var prevLeftTopPoint = shape.prevLeftTopPoint;
          var mi = i + leftTopPoint.dx.toInt();
          var mj = j + leftTopPoint.dy.toInt();
          if (mi < 0 || mi >= mainMatrix[0].length) {
            return CollisionType.edge;
          }
          if (mj >= mainMatrix.length) {
            return CollisionType.bottom;
          }
          var square = mainMatrix[mj][mi];
          if (square != null) {
            if (leftTopPoint.dx == prevLeftTopPoint.dx) {
              return CollisionType.bottom;
            } else {
              return CollisionType.edge;
            }
          }
        }
      }
    }
    return CollisionType.none;
  }

  static void pinShape(Shape shape, List<List<Square?>> mainMatrix) {
    var shapeMatrix = shape.matrixList[shape.currentIndex];
    for (var j = 0; j < shapeMatrix.length; j++) { // 纵轴是 j 横轴是 i
      for (var i = 0; i < shapeMatrix[j].length; i++) {
        var cell = shapeMatrix[j][i];
        if (cell == 1) {
          var mi = i + shape.leftTopPoint.dx.toInt();
          var mj = j + shape.leftTopPoint.dy.toInt();
          if (mi < 0 || mi >= mainMatrix[0].length) {
            return;
          }
          if (mj < 0 || mj >= mainMatrix.length) {
            return;
          }

          mainMatrix[mj][mi] = Square(shape.square.paint.color, shape.square.borderPaint.color);
        }
      }
    }
  }

  static void removeLineShape(List<List<Square?>> mainMatrix) {
    for (var j = 0; j < mainMatrix.length; j++) { // 纵轴是 j 横轴是 i
      var line = true;
      for (var i = 0; i < mainMatrix[j].length; i++) {
        var square = mainMatrix[j][i];
        if (square == null) {
          line = false;
          continue;
        }
      }

      if (line) {
        mainMatrix.removeAt(j);

        var row = <Square?>[];
        mainMatrix.insert(0, row);
        for (var i = 0; i < 10; i++) {
          row.add(null);
        }
      }
    }
  }

  static void initMainMatrix(List<List<Square?>> mainMatrix) {
    mainMatrix.clear();
    for (var i = 0; i < Config.mainMatrixHeight; i++) {
      var row = <Square?>[];
      mainMatrix.add(row);
      for (var j = 0; j < Config.mainMatrixWidth; j++) {
        row.add(null);
      }
    }
  }

  static Shape createShape() {
    var shapeList = <Shape>[I(), L(), L2(), N(), N2(), O(), T()];
    var shape = shapeList[random.nextInt(shapeList.length)];
    return shape;
  }
}

lib\game\tetris\config.dart

class Config {
  static double mainMatrixWidth = 10;
  static double mainMatrixHeight = 20;

  static double squareWidth = 20;
  static double squareBorderWidth = 2;

  static double speed = 50;
}

lib\game\tetris\controller.dart

import 'package:flutter/material.dart';

class Controller extends ChangeNotifier {

  int rotateTimes = 0;
  int leftTimes = 0;
  int rightTimes = 0;

  bool downPressed = false;

  void left() {
    leftTimes++;
    notifyListeners();
  }

  void right() {
    rightTimes++;
    notifyListeners();
  }

  void rotate() {
    rotateTimes++;
    notifyListeners();
  }
}

lib\game\tetris\shape\square.dart

import 'package:flutter/material.dart';

import '../config.dart';

class Square {

  late Paint paint;
  late Paint borderPaint;

  double width = Config.squareWidth;
  double borderWidth = Config.squareBorderWidth;

  Square(Color color, Color borderColor) {
    paint = Paint()..color = color;
    borderPaint = Paint()..color = borderColor;
  }
}

lib\game\tetris\shape\shape.dart

import 'package:flutter/material.dart';
import 'package:flutter_demo/game/tetris/shape/square.dart';

import '../config.dart';

class Shape {

  Shape(this.square) {
    leftTopOffset = Offset(3 * Config.squareWidth, 0);
    prevLeftTopOffset = Offset(3 * Config.squareWidth, 0);
  }

  Square square;
  Offset leftTopOffset = const Offset(0, 0);
  int currentIndex = 0;
  int maxIndex = 3;

  Offset prevLeftTopOffset = const Offset(0, 0);
  int prevCurrentIndex = 0;

  Offset get leftTopPoint {
    return _getLeftTopPoint(leftTopOffset);
  }
  Offset get prevLeftTopPoint {
    return _getLeftTopPoint(prevLeftTopOffset);
  }
  Offset _getLeftTopPoint(Offset offset) {
    var x = offset.dx % square.width == 0 ? offset.dx ~/ square.width : offset.dx ~/ square.width + 1;
    var y = offset.dy % square.width == 0 ? offset.dy ~/ square.width : offset.dy ~/ square.width + 1;
    return Offset(x.toDouble(), y.toDouble());
  }

  List<List<List<int>>> get matrixList => [
    [
      [],
    ],
  ];

  void render(Canvas canvas) {
    var matrix = matrixList[currentIndex];

    for (var j = 0; j < matrix.length; j++) {
      for (var i = 0; i < matrix[j].length; i++) {
        var cell = matrix[j][i];
        if (cell == 1) {
          var offsetCenter = leftTopOffset.translate(square.width / 2 + i * square.width, square.width / 2 + j * square.width);
          canvas.drawRect(Rect.fromCenter(center: offsetCenter, width: square.width, height: square.width), square.borderPaint);
          canvas.drawRect(Rect.fromCenter(center: offsetCenter, width: square.width - square.borderWidth, height: square.width - square.borderWidth), square.paint);
        }
      }
    }
  }

  void update(double dt) {
    savePrev();
    leftTopOffset = leftTopOffset.translate(0, dt * Config.speed);
  }

  void rotate() {
    savePrev();
    if (currentIndex < maxIndex) {
      currentIndex ++;
    } else {
      currentIndex = 0;
    }
  }

  void left() {
    savePrev();
    leftTopOffset = leftTopOffset.translate(-square.width, 0);
  }

  void right() {
    savePrev();
    leftTopOffset = leftTopOffset.translate(square.width, 0);
  }

  void savePrev() {
    prevLeftTopOffset = leftTopOffset;
    prevCurrentIndex = currentIndex;
  }

  void loadPrev() {
    leftTopOffset = prevLeftTopOffset;
    currentIndex = prevCurrentIndex;
  }
}

lib\game\tetris\shape\i.dart

import 'package:flutter/material.dart';
import 'package:flutter_demo/game/tetris/shape/square.dart';
import 'shape.dart';

class I extends Shape {
  I() : super(Square(Colors.red, Colors.white70));

  @override
  List<List<List<int>>> get matrixList => [
    [
      [0,1,0,0],
      [0,1,0,0],
      [0,1,0,0],
      [0,1,0,0],
    ],
    [
      [0,0,0,0],
      [1,1,1,1],
      [0,0,0,0],
      [0,0,0,0],
    ],
  ];

  @override
  int get maxIndex => 1;
}

lib\game\tetris\shape\l.dart

import 'package:flutter/material.dart';
import 'package:flutter_demo/game/tetris/shape/square.dart';
import 'shape.dart';

class L extends Shape {
  L() : super(Square(Colors.green[200]!, Colors.white70));

  @override
  List<List<List<int>>> get matrixList => [
    [
      [0,1,0,0],
      [0,1,0,0],
      [0,1,1,0],
      [0,0,0,0],
    ],
    [
      [0,0,0,0],
      [1,1,1,0],
      [1,0,0,0],
      [0,0,0,0],
    ],
    [
      [1,1,0,0],
      [0,1,0,0],
      [0,1,0,0],
      [0,0,0,0],
    ],
    [
      [0,0,1,0],
      [1,1,1,0],
      [0,0,0,0],
      [0,0,0,0],
    ],
  ];

  @override
  int get maxIndex => 3;
}

lib\game\tetris\shape\l2.dart

import 'package:flutter/material.dart';
import 'package:flutter_demo/game/tetris/shape/square.dart';
import 'shape.dart';

class L2 extends Shape {
  L2() : super(Square(Colors.green[800]!, Colors.white70));

  @override
  List<List<List<int>>> get matrixList => [
    [
      [0,0,1,0],
      [0,0,1,0],
      [0,1,1,0],
      [0,0,0,0],
    ],
    [
      [0,1,0,0],
      [0,1,1,1],
      [0,0,0,0],
      [0,0,0,0],
    ],
    [
      [0,0,1,1],
      [0,0,1,0],
      [0,0,1,0],
      [0,0,0,0],
    ],
    [
      [0,0,0,0],
      [0,1,1,1],
      [0,0,0,1],
      [0,0,0,0],
    ],
  ];

  @override
  int get maxIndex => 3;
}

lib\game\tetris\shape\n.dart

import 'package:flutter/material.dart';
import 'package:flutter_demo/game/tetris/shape/square.dart';
import 'shape.dart';

class N extends Shape {
  N() : super(Square(Colors.blue[200]!, Colors.white70));

  @override
  List<List<List<int>>> get matrixList => [
    [
      [0,0,1,0],
      [0,1,1,0],
      [0,1,0,0],
      [0,0,0,0],
    ],
    [
      [0,1,1,0],
      [0,0,1,1],
      [0,0,0,0],
      [0,0,0,0],
    ],
  ];

  @override
  int get maxIndex => 1;
}

lib\game\tetris\shape\n2.dart

import 'package:flutter/material.dart';
import 'package:flutter_demo/game/tetris/shape/square.dart';
import 'shape.dart';

class N2 extends Shape {
  N2() : super(Square(Colors.blue[800]!, Colors.white70));

  @override
  List<List<List<int>>> get matrixList => [
    [
      [0,1,0,0],
      [0,1,1,0],
      [0,0,1,0],
      [0,0,0,0],
    ],
    [
      [0,0,1,1],
      [0,1,1,0],
      [0,0,0,0],
      [0,0,0,0],
    ],
  ];

  @override
  int get maxIndex => 1;
}

lib\game\tetris\shape\o.dart

import 'package:flutter/material.dart';
import 'package:flutter_demo/game/tetris/shape/square.dart';
import 'shape.dart';

class O extends Shape {
  O() : super(Square(Colors.yellow, Colors.white70));

  @override
  List<List<List<int>>> get matrixList => [
    [
      [0,1,1,0],
      [0,1,1,0],
      [0,0,0,0],
      [0,0,0,0],
    ],
  ];

  @override
  int get maxIndex => 0;
}

lib\game\tetris\shape\t.dart

import 'package:flutter/material.dart';
import 'package:flutter_demo/game/tetris/shape/square.dart';
import 'shape.dart';

class T extends Shape {
  T() : super(Square(Colors.purple, Colors.white70));

  @override
  List<List<List<int>>> get matrixList => [
    [
      [0,1,0,0],
      [1,1,1,0],
      [0,0,0,0],
      [0,0,0,0],
    ],
    [
      [0,1,0,0],
      [0,1,1,0],
      [0,1,0,0],
      [0,0,0,0],
    ],
    [
      [0,0,0,0],
      [1,1,1,0],
      [0,1,0,0],
      [0,0,0,0],
    ],
    [
      [0,1,0,0],
      [1,1,0,0],
      [0,1,0,0],
      [0,0,0,0],
    ],
  ];

  @override
  int get maxIndex => 3;
}

源码 https://github.com/webabcd/flutter_demo
作者 webabcd