使用Flutter编写的一个2048小游戏

        最近在学习flutter,随手写了一个2048的小游戏,目前只实现了基本的功能,还有一些功能没有加上去,主要是因为在调用setState()方法更新UI的时候出现了一些异常,暂时没有找到解决的方法。

        下面是程序执行效果:

        主要widget就是两个,一个是游戏页面,一个是游戏页面中每一个方块对应的GameBoxWidget,其中游戏页面主要负责GameBox的创建,维护和移动等操作,GameBoxWidget主要负责游戏方块自身的数字更新,颜色更新等。下面是代码:

//滑动方向,默认垂直方向
  bool _moveVertical = true;
  //滑动距离,配合滑动方向确定方块的移动信息
  double _moveDistance = 0;
  bool _needBuildNextBox = false;
  //创建游戏方块对象
  List<GameBoxWidget> _gameBoxList = List();
  int _firstPosition;
  int _secondPosition;
  @override
  void initState() {
    super.initState();
    //依次创建创建16个方块,避免重复创建对象
    _initGameBox();
  }

  //初始化方块信息
  void _initGameBox() {
    _gameBoxList.clear();
    for (int i = 0; i < 16; i++) {
      GameBoxWidget gameBoxWidget = GameBoxWidget(
        position: i,
      );
      _gameBoxList.add(gameBoxWidget);
    }
    _firstPosition = generalPoaition();
    _secondPosition = generalPoaition();
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: EdgeInsets.only(top: MediaQuery.of(context).padding.top),
      color: Color.fromARGB(255, 227, 204, 169),
      child: Column(
        mainAxisAlignment: MainAxisAlignment.start,
        crossAxisAlignment: CrossAxisAlignment.center,
        children: <Widget>[
          //上面是显示游戏名称和得分
          Expanded(
            flex: 2,
            child: Stack(
              alignment: Alignment.centerLeft,
              children: <Widget>[
                Positioned(
                  left: 10.0,
                  //左边显示游戏名称
                  child: Text(
                    "2048",
                    style: TextStyle(
                      color: Color.fromARGB(200, 105, 75, 35),
                      fontSize: 20.0,
                      fontWeight: FontWeight.bold,
                    ),
                    textScaleFactor: 2.0,
                  ),
                ),
          ),

          Expanded(
            flex: 5,
            child: GestureDetector(
              child: Center(
                child: Container(
                  padding: EdgeInsets.all(15.0),
                  constraints: BoxConstraints.tightFor(),
                  child: Container(
                    padding: EdgeInsets.only(bottom: 10.0, right: 10.0),
                    constraints: BoxConstraints.tightFor(),
                    decoration: BoxDecoration(
                        color: Color.fromARGB(255, 105, 75, 35),
                        borderRadius: BorderRadius.circular(5.0)),
                    child: Stack(
                      children: _gameBoxList.map((item) {
                        if (item.position == _firstPosition ||
                            item.position == _secondPosition) {
                          item.value = generatorRandom();
                        }
                        return item;
                      }).toList(),
                    ),
                  ),
                ),
              ),
              onVerticalDragUpdate: (updateEvent) {
                _moveVertical = true;
                _moveDistance += updateEvent.delta.dy;
              },
              onVerticalDragEnd: (dragUpDetails) {
                checkPostionMoveInfo(_moveVertical, _moveDistance);
                _moveDistance = 0;
              },
              onHorizontalDragUpdate: (updateEvent) {
                _moveVertical = false;
                _moveDistance += updateEvent.delta.dx;
              },
              onHorizontalDragEnd: (details) {
                checkPostionMoveInfo(_moveVertical, _moveDistance);
                _moveDistance = 0;
              },
            ),
          ),
          Spacer(
            flex: 1,
          ),
        ],
      ),
    );
  }

  //遍历查看需要滑动的position和滑动的距离
  void checkPostionMoveInfo(bool isVertical, double path) {
    //如果是垂直方向滑动
    if (isVertical) {
      List<GameBoxWidget> column0Box = List();
      List<GameBoxWidget> column1Box = List();
      List<GameBoxWidget> column2Box = List();
      List<GameBoxWidget> column3Box = List();
      for (int i = 0; i < 16; i++) {
        switch (i % 4) {
          case 0:
            //将第一列的数据加入到List中
            column0Box.add(_gameBoxList[i]);
            break;
          case 1:
            column1Box.add(_gameBoxList[i]);
            break;
          case 2:
            column2Box.add(_gameBoxList[i]);
            break;
          case 3:
            column3Box.add(_gameBoxList[i]);
            break;
          default:
        }
      }
      if (path < 0) {
        print("向上滑动");
        //垂直方向向上移动
        moveVerticalUp(column0Box, true);
        moveVerticalUp(column1Box, true);
        moveVerticalUp(column2Box, true);
        moveVerticalUp(column3Box, true);
      } else {
        moveVerticalDown(column0Box, true);
        moveVerticalDown(column1Box, true);
        moveVerticalDown(column2Box, true);
        moveVerticalDown(column3Box, true);
      }
    } else {
      List<GameBoxWidget> row0Box = List();
      List<GameBoxWidget> row1Box = List();
      List<GameBoxWidget> row2Box = List();
      List<GameBoxWidget> row3Box = List();
      for (int i = 0; i < 16; i++) {
        switch (i ~/ 4) {
          case 0:
            //将第一列的数据加入到List中
            row0Box.add(_gameBoxList[i]);
            break;
          case 1:
            row1Box.add(_gameBoxList[i]);
            break;
          case 2:
            row2Box.add(_gameBoxList[i]);
            break;
          case 3:
            row3Box.add(_gameBoxList[i]);
            break;
          default:
        }
      }
      if (path < 0) {
        //水平方向向左移动
        moveVerticalUp(row0Box, false);
        moveVerticalUp(row1Box, false);
        moveVerticalUp(row2Box, false);
        moveVerticalUp(row3Box, false);
        print("循环完成:是否需要生成下一个游戏方块$_needBuildNextBox");
      } else {
        moveVerticalDown(row0Box, false);
        moveVerticalDown(row1Box, false);
        moveVerticalDown(row2Box, false);
        moveVerticalDown(row3Box, false);
      }
    }

    //在此处判断是否需要生成洗一个方块
    if (_needBuildNextBox) {
      _generalNextRandonBox();
    } else {
      SnackBar(
        content: Text("请向其它方向移动"),
        duration: Duration(seconds: 1),
      );
    }
    _needBuildNextBox = false;
  }
    
/**
*具体的移动方块的代码
*/

  //查看当前方块是否存在
  bool checkGameBoxExit(GameBoxWidget widget) {
    if (widget.value != null && widget.value > 0) {
      return true;
    }
    return false;
  }

  //查看两个方块是否可以合并
  bool checkGameBoxMerge(GameBoxWidget widget1, GameBoxWidget widget2) {
    if (widget1.value == widget2.value) {
      return true;
    }
    return false;
  }

  //在0~15之间生成一个随机数
  int generalPoaition() {
    //首先循环遍历查看是否所有的方块都被填充
    bool allIn = true;
    for (var i = 0; i < _gameBoxList.length; i++) {
      if (_gameBoxList[i].value == null || _gameBoxList[i].value < 2) {
        allIn = false;
        break;
      }
    }
    if (allIn) {
      return -1;
    }

    int generalPosition;
    do {
      generalPosition = Random().nextInt(16);
    } while (_gameBoxList[generalPosition].value != null &&
        _gameBoxList[generalPosition].value > 0);

    return generalPosition;
  }

  //生成一个2或者4的随机数
  int generatorRandom() {
    int value;
    do {
      value = Random().nextInt(5);
    } while (value != 2 && value != 4);

    return value;
  }
}

 

下面是游戏方块GameBoxWidget的代码

class GameBoxWidget extends StatefulWidget {
  //方块所在的位置
  final int position;
  //方块显示的数值
  int value;

  _GameBoxState _gameBoxState;

  GameBoxWidget({Key key, this.position = 0}) : super(key: key);

  //更新value值
  void updateValue(int newValue){
    _gameBoxState.setState((){
      this.value =newValue;
    });
  }

  //设置动画执行
  void startAnimation(int movePosition, bool vertical) {
    _gameBoxState.startPaddingAnimation(movePosition, vertical);
  }

  @override
  _GameBoxState createState() {
    _gameBoxState = _GameBoxState();
    return _gameBoxState;
  }
}

class _GameBoxState extends State<GameBoxWidget>
    with SingleTickerProviderStateMixin {
  //方块的宽度
  double _boxWidth;
  //方块初始时的颜色,根据方块中的数值来显示
  Color _baseColor = Colors.orangeAccent;
  int _baseColorR = 20;
  int _baseColorG = 30;
  int _baseColorB = 30;

  //距离顶部的padding
  double _topPadding = 0;
  //距离左边的padding
  double _leftPadding = 0;

  //动画控制器
  AnimationController _controller;
  //距离顶部的动画
  Animation<double> _topAnimation;
  Tween<double> _topTween;
  //距离左边的动画
  Animation<double> _leftAnimation;
  Tween<double> _leftTween;

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    //获取颜色值
    //_getBoxColor();

    //设置动画控制器
    _controller = new AnimationController(
      duration: Duration(milliseconds: 200),
      vsync: this,
    );

    //初始化动画参数
    _topTween = Tween<double>();
    _topAnimation = _topTween.animate(_controller);

    _leftTween = Tween<double>();
    _leftAnimation = _leftTween.animate(_controller);
  }

  //根据不同的数字设置不同的背景颜色
  Color _getBoxColor() {
    int currentValue = widget.value;
    if (widget.value != null && widget.value > 0) {
      do {
        if (_baseColorR + 30 < 255) {
          _baseColorR += 22;
        } else {
          _baseColorR = 70;
        }

        if (_baseColorG + 20 < 255) {
          _baseColorG += 22;
        } else {
          _baseColorG = 80;
        }

        if (_baseColorB + 33 < 255) {
          _baseColorB += 22;
        } else {
          _baseColorB = 90;
        }
        currentValue = (currentValue ~/ 2).toInt();
      } while (currentValue / 2 != 1);
    }
    print("获取到的颜色值:$_baseColorR,$_baseColorG,$_baseColorB");
    _baseColor = Color.fromARGB(255, _baseColorR, _baseColorG, _baseColorB);
    return _baseColor;
  }

  //根据不同的value值设置不同的背景颜色
  Color _getBackColor() {
    switch (widget.value ?? 0) {
      case 0:
        return Colors.orangeAccent;
      case 2:
        return Colors.pinkAccent;
      case 4:
        return Colors.purpleAccent;

      case 8:
        return Colors.redAccent;

      case 16:
        return Colors.tealAccent;

      case 32:
        return Colors.deepOrange;
      case 64:
        return Colors.amberAccent;
      case 128:
        return Colors.blueAccent;
      case 256:
        return Colors.brown;
      case 512:
        return Colors.cyanAccent;
      case 1024:
        return Colors.deepOrangeAccent;
      case 2048:
        return Colors.deepPurpleAccent;
      case 4096:
        return Colors.indigoAccent;
      case 8192:
        return Colors.limeAccent;
      case 16384:
        return Colors.orangeAccent;

      default:
    }
  }

  //启动动画
  /**
   * value: 移动距离
   * vertical:方向信息
   */
  void startPaddingAnimation(int movePosition, bool isVertical) async{
    print("开始执行动画:$isVertical");
    if (widget.position < 16) {
      if (isVertical) {
        _topTween.begin = 10.0 +
            (widget.position ~/ 4) * _boxWidth +
            (widget.position ~/ 4) * 10;

        _topTween.end =
            10.0 + (movePosition ~/ 4) * _boxWidth + (movePosition ~/ 4) * 10;
      } else {
        _leftTween.begin = 10.0 +
            (widget.position % 4) * _boxWidth +
            (widget.position % 4) * 10;
        _leftTween.end =
            10.0 + (movePosition % 4) * _boxWidth + (movePosition % 4) * 10;
      }
      _controller.addStatusListener((listener) {
        switch (listener) {
          case AnimationStatus.completed:
            //动画执行完成,重置topPadding和leftPadding
            _topPadding = 10.0 +
                (widget.position ~/ 4) * _boxWidth +
                (widget.position ~/ 4) * 10;
            _leftPadding = 10.0 +
                (widget.position % 4) * _boxWidth +
                (widget.position % 4) * 10;
            //_getBoxColor();
            break;
          default:
        }
      });
      _controller.addListener(() {
        if (isVertical) {
          _topPadding = _topAnimation.value;
        } else {
          _leftPadding = _leftAnimation.value;
        }
      });
      await _controller.forward().orCancel;
    }
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
        animation: _controller,
        builder: (context, child) {
          return Positioned(
            left: _leftPadding,
            top: _topPadding,
            child: Container(
              width: _boxWidth,
              height: _boxWidth,
              alignment: Alignment.center,
              decoration: BoxDecoration(
                color: _getBackColor(),
                borderRadius: BorderRadius.circular(4.0),
              ),
              child: Text(
                widget.value == null ? "" : widget.value.toString(),
                style: TextStyle(
                  color: Colors.white,
                  fontSize: 14.0,
                ),
                textScaleFactor: 1.3,
              ),
            ),
          );
        });
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    _boxWidth = (MediaQuery.of(context).size.width - 30 - 50) / 4;
    //设置每一个gameBox的位置
    _topPadding =
        10.0 + (widget.position ~/ 4) * _boxWidth + (widget.position ~/ 4) * 10;
    _leftPadding =
        10.0 + (widget.position % 4) * _boxWidth + (widget.position % 4) * 10;
  }

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

 

    上面的代码没有写完,主要是复制粘贴太麻烦,完整的代码在github上:https://github.com/yiwenfanjuan/project2048

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值