在这里插入图片描述

引言:数字益智游戏的经典之作

经过俄罗斯方块游戏的开发,我们已经掌握了复杂的网格操作和游戏状态管理。现在,我们将开发一个风靡全球的益智游戏——2048。这个游戏虽然规则简单,但涉及数学运算、列表操作、游戏状态判断等高级概念。

2048 游戏在 Flutter 和 OpenHarmony 的跨平台环境中展示了如何实现一个具有优雅算法的益智游戏。通过这个项目,你将学到如何实现行列的压缩和合并、如何判断游戏的胜负状态,以及如何使用颜色来提供视觉反馈。

游戏设计与核心机制

游戏规则

2048 游戏的核心机制包括:

  1. 方块移动:玩家可以向上、下、左、右四个方向移动方块
  2. 方块合并:相同数字的方块相碰时会合并成两倍的数字
  3. 新方块生成:每次移动后,随机位置生成一个新方块(90% 概率为 2,10% 概率为 4)
  4. 得分累计:每次合并时,得分增加合并后的数字
  5. 胜利条件:合并出 2048 的方块
  6. 失败条件:无法进行任何移动

设计理念

这个游戏的设计充分考虑了 OpenHarmony 设备的特性:

  • 4×4 网格系统:标准的 2048 游戏尺寸
  • 四向控制:简单的上下左右按钮
  • 彩色反馈:不同数字使用不同颜色,提供视觉反馈
  • 状态判断:精确的游戏结束检测

核心数据结构与算法

游戏网格初始化

void initializeGame() {
  grid = List.generate(gridSize, (_) => List.generate(gridSize, (_) => 0));
  score = 0;
  gameOver = false;
  won = false;
  addNewTile();
  addNewTile();
}

初始化游戏时,创建一个 4×4 的网格,所有单元格初始值为 0。然后添加两个新方块,这是 2048 游戏的标准开局。

新方块生成

void addNewTile() {
  List<List<int>> emptyPositions = [];
  for (int i = 0; i < gridSize; i++) {
    for (int j = 0; j < gridSize; j++) {
      if (grid[i][j] == 0) {
        emptyPositions.add([i, j]);
      }
    }
  }
  
  if (emptyPositions.isNotEmpty) {
    Random random = Random();
    List<int> pos = emptyPositions[random.nextInt(emptyPositions.length)];
    grid[pos[0]][pos[1]] = random.nextDouble() < 0.9 ? 2 : 4;
  }
}

这个方法生成新方块。关键点包括:

查找空位:遍历整个网格,找出所有值为 0 的位置。

随机选择:从空位中随机选择一个位置放置新方块。

概率分配:90% 的概率生成 2,10% 的概率生成 4。这个比例确保了游戏的平衡性。

行压缩

List<int> compressRow(List<int> row) {
  List<int> compressed = row.where((val) => val != 0).toList();
  while (compressed.length < gridSize) {
    compressed.add(0);
  }
  return compressed;
}

压缩行是 2048 游戏的基础操作。这个方法:

移除零:使用 where() 过滤掉所有 0 值。

补齐长度:在列表末尾添加 0,使其长度恢复为 4。

例如,[2, 0, 4, 0] 会被压缩为 [2, 4, 0, 0]

行合并

List<int> mergeRow(List<int> row) {
  for (int i = 0; i < gridSize - 1; i++) {
    if (row[i] != 0 && row[i] == row[i + 1]) {
      row[i] *= 2;
      score += row[i];
      row[i + 1] = 0;
      if (row[i] == 2048) won = true;
    }
  }
  return row;
}

合并行是 2048 游戏的核心算法。关键点包括:

相邻比较:遍历行中的每个元素,与下一个元素比较。

合并逻辑:如果两个相邻的非零元素相等,将左边的元素翻倍,右边的元素设为 0。

得分计算:每次合并时,得分增加合并后的数字。

胜利检测:如果合并后的数字等于 2048,设置胜利标志。

例如,[2, 2, 4, 4] 会被合并为 [4, 0, 8, 0],得分增加 12(4 + 8)。

方向移动

void moveLeft() {
  setState(() {
    bool moved = false;
    for (int i = 0; i < gridSize; i++) {
      List<int> row = grid[i];
      row = compressRow(row);
      row = mergeRow(row);
      row = compressRow(row);
      if (row != grid[i]) moved = true;
      grid[i] = row;
    }
    if (moved) {
      addNewTile();
      checkGameState();
    }
  });
}

左移操作的流程:

  1. 压缩:移除所有 0 值,使方块靠左
  2. 合并:相同的方块相碰时合并
  3. 再次压缩:合并后可能产生新的 0 值,需要再次压缩

这个三步流程确保了正确的游戏逻辑。

右移、上移和下移的实现类似,但需要对行或列进行反向操作:

void moveRight() {
  setState(() {
    bool moved = false;
    for (int i = 0; i < gridSize; i++) {
      List<int> row = grid[i].reversed.toList();  // 反向
      row = compressRow(row);
      row = mergeRow(row);
      row = compressRow(row);
      row = row.reversed.toList();  // 再次反向
      if (row != grid[i]) moved = true;
      grid[i] = row;
    }
    if (moved) {
      addNewTile();
      checkGameState();
    }
  });
}

通过反向操作,我们可以复用相同的压缩和合并逻辑。

游戏状态检查

void checkGameState() {
  bool hasEmpty = false;
  for (int i = 0; i < gridSize; i++) {
    for (int j = 0; j < gridSize; j++) {
      if (grid[i][j] == 0) hasEmpty = true;
    }
  }
  
  if (!hasEmpty) {
    bool canMove = false;
    for (int i = 0; i < gridSize; i++) {
      for (int j = 0; j < gridSize - 1; j++) {
        if (grid[i][j] == grid[i][j + 1]) canMove = true;
      }
    }
    for (int j = 0; j < gridSize; j++) {
      for (int i = 0; i < gridSize - 1; i++) {
        if (grid[i][j] == grid[i + 1][j]) canMove = true;
      }
    }
    if (!canMove) gameOver = true;
  }
}

这个方法检查游戏是否结束。关键点包括:

空位检查:如果还有空位,游戏继续。

可合并检查:如果没有空位,检查是否有相邻的相同方块可以合并。

游戏结束:只有当没有空位且没有可合并的方块时,游戏才结束。

渲染与UI设计

方块颜色映射

Color getTileColor(int value) {
  switch (value) {
    case 2: return Colors.amber[100]!;
    case 4: return Colors.amber[200]!;
    case 8: return Colors.orange[300]!;
    case 16: return Colors.orange[400]!;
    case 32: return Colors.deepOrange[300]!;
    case 64: return Colors.deepOrange[400]!;
    case 128: return Colors.yellow[600]!;
    case 256: return Colors.yellow[700]!;
    case 512: return Colors.lime[600]!;
    case 1024: return Colors.green[600]!;
    case 2048: return Colors.blue[600]!;
    default: return Colors.grey[300]!;
  }
}

不同的数字使用不同的颜色,从浅色(小数字)到深色(大数字)。这提供了清晰的视觉反馈,帮助玩家快速识别方块的值。

网格渲染

GridView.builder(
  physics: const NeverScrollableScrollPhysics(),
  gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 4,
    crossAxisSpacing: 8,
    mainAxisSpacing: 8,
  ),
  itemCount: 16,
  itemBuilder: (context, index) {
    int row = index ~/ 4;
    int col = index % 4;
    int value = grid[row][col];
    return Container(
      decoration: BoxDecoration(
        color: getTileColor(value),
        borderRadius: BorderRadius.circular(4),
      ),
      child: Center(
        child: value == 0
            ? const SizedBox()
            : Text(
                '$value',
                style: TextStyle(
                  fontSize: 28,
                  fontWeight: FontWeight.bold,
                  color: value <= 4 ? Colors.grey[700] : Colors.white,
                ),
              ),
      ),
    );
  },
),

使用 GridView.builder 渲染 4×4 的网格。每个方块的颜色根据其值动态确定。

OpenHarmony 兼容性与性能优化

跨平台设计

这个游戏的设计充分考虑了 OpenHarmony 设备的特性:

  • 网格系统:基于网格的设计避免了复杂的坐标计算
  • 四向控制:简单的按钮在所有设备上都有一致的支持
  • 算法优化:使用高效的列表操作,不需要复杂的数据结构

性能优化

  • 列表操作:使用 Dart 的内置方法如 where()reversed 等,性能高效
  • 状态管理:只在移动时更新状态,避免不必要的重绘
  • 颜色缓存getTileColor() 方法可以被优化为缓存常用颜色

游戏设计分析

难度曲线

游戏的难度通过以下方式递增:

  1. 方块数量:随着游戏进行,方块越来越多
  2. 可用空间:空间减少,移动变得困难
  3. 合并难度:需要更多的策略来创建大数字

游戏心理学

  • 即时反馈:方块立即响应玩家的输入
  • 得分系统:每次合并立即获得分数,给玩家成就感
  • 明确的目标:达到 2048 是明确的胜利条件

代码架构的优势

可扩展性

当前的设计使得添加新功能变得简单:

  • 改变网格大小:修改 gridSize 常数
  • 改变新方块概率:修改 addNewTile() 中的概率
  • 添加新的颜色方案:扩展 getTileColor() 方法

可维护性

  • 清晰的方法划分:每个方法都有明确的职责
  • 有意义的变量名:代码易于理解和修改
  • 注释说明:关键的算法有注释说明

未来的改进方向

  1. 撤销功能:允许玩家撤销最后一步移动
  2. 难度级别:添加不同的初始方块或新方块生成概率
  3. 排行榜:保存玩家的最高分
  4. 动画效果:添加方块合并的动画
  5. 声音效果:添加移动和合并的音效
  6. 触摸手势:支持滑动手势而不仅仅是按钮
  7. 主题系统:支持不同的配色方案

总结

2048 游戏展示了如何在 Flutter 和 OpenHarmony 上实现一个具有优雅算法的益智游戏。关键的学习点包括:

  • 行列压缩和合并的算法
  • 游戏状态的判断
  • 列表操作的高效使用
  • 颜色映射和视觉反馈
  • 游戏循环的设计
  • 跨平台游戏开发的最佳实践

通过这个项目,你已经掌握了开发数学益智游戏所需的核心技能。在接下来的游戏中,我们将继续探索新的游戏类型,包括推箱子、五子棋和其他经典游戏。


Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐