Flutter 框架跨平台鸿蒙开发 - 开发双人对战五子棋游戏

⚫⚪ Flutter + HarmonyOS 实战:开发双人对战五子棋游戏


运行效果预览图

📋 文章导读

章节 内容概要 预计阅读
五子棋规则与游戏设计 3分钟
棋盘数据结构与渲染 8分钟
胜负判定算法详解 10分钟
游戏交互与状态管理 8分钟
UI美化与动画效果 5分钟
完整源码与运行 3分钟

💡 写在前面:五子棋是一款老少皆宜的经典棋类游戏,规则简单却变化无穷。本文将带你用Flutter实现一款支持双人对战的五子棋游戏,重点讲解棋盘绘制、胜负判定算法、以及如何打造流畅的游戏体验。无论你是想学习游戏开发,还是想和朋友来一局,这篇文章都能满足你。


一、游戏设计

1.1 五子棋规则

五子棋的规则可以用一句话概括:先在横、竖、斜任意方向连成五子者获胜
:
标准棋盘为15×15,共225个交叉点
执子顺序 黑棋先行,双方交替落子 获胜条件 任意方向连成5个或以上同色棋子 平局条件 棋盘下满且无人获胜(极少出现)

1.2 功能设计

五子棋游戏
核心功能
双人对战
落子判定
胜负检测
悔棋功能
界面元素
15×15棋盘
黑白棋子
玩家指示器
步数统计
视觉效果
最后落子高亮
获胜棋子标记
棋子渐变光泽
星位标记

1.3 界面布局

区域 内容 说明
顶部AppBar 标题、悔棋、重开按钮 游戏控制
玩家指示器 黑棋/白棋卡片 显示当前执子方
棋盘区域 15×15网格 + 棋子 游戏主体
底部信息 棋盘规格、步数、状态 游戏统计

二、棋盘数据结构与渲染

2.1 数据模型设计

dart 复制代码
/// 棋子类型枚举
enum ChessType { 
  none,   // 空位
  black,  // 黑棋
  white   // 白棋
}

/// 棋盘数据:15×15的二维数组
late List<List<ChessType>> board;

/// 初始化棋盘
void _initGame() {
  board = List.generate(
    15,
    (_) => List.generate(15, (_) => ChessType.none),
  );
}

2.2 棋盘坐标系统

棋盘采用标准的二维坐标系,左上角为原点:

复制代码
     0   1   2   3   4   ...  14
   ┌───┬───┬───┬───┬───┬───┬───┐
 0 │   │   │   │   │   │   │   │
   ├───┼───┼───┼───┼───┼───┼───┤
 1 │   │   │   │   │   │   │   │
   ├───┼───┼───┼───┼───┼───┼───┤
 2 │   │   │   │ ● │   │   │   │  ← board[2][3] = black
   ├───┼───┼───┼───┼───┼───┼───┤
 3 │   │   │   │   │ ○ │   │   │  ← board[3][4] = white
   ...

2.3 棋盘绘制

使用 CustomPainter 绘制棋盘网格和星位:

dart 复制代码
class BoardPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()
      ..color = Colors.black
      ..strokeWidth = 1;

    final cellSize = size.width / 15;
    final offset = cellSize / 2;

    // 绘制15条横线和15条竖线
    for (int i = 0; i < 15; i++) {
      // 横线
      canvas.drawLine(
        Offset(offset, offset + i * cellSize),
        Offset(size.width - offset, offset + i * cellSize),
        paint,
      );
      // 竖线
      canvas.drawLine(
        Offset(offset + i * cellSize, offset),
        Offset(offset + i * cellSize, size.height - offset),
        paint,
      );
    }

    // 绘制星位(5个黑点)
    final starPositions = [
      [7, 7],   // 天元(中心)
      [3, 3], [3, 11], [11, 3], [11, 11],  // 四角星
    ];
    
    for (var pos in starPositions) {
      canvas.drawCircle(
        Offset(offset + pos[0] * cellSize, offset + pos[1] * cellSize),
        4,
        Paint()..color = Colors.black,
      );
    }
  }
}

2.4 星位说明

标准五子棋棋盘有5个星位,用于辅助定位:

星位名称 坐标 位置说明
天元 (7, 7) 棋盘正中心
左上星 (3, 3) 左上角区域
右上星 (11, 3) 右上角区域
左下星 (3, 11) 左下角区域
右下星 (11, 11) 右下角区域

三、胜负判定算法

3.1 算法思路

每次落子后,只需检查以该点为中心的四个方向是否形成五连:


落子位置 x,y
检查水平方向 ─
检查垂直方向 │
检查主对角线 ╲
检查副对角线 ╱
连续 ≥ 5?
游戏结束,当前玩家获胜
继续游戏

3.2 四个检查方向

方向 向量 (dx, dy) 说明
水平 → (1, 0) 左右方向
垂直 ↓ (0, 1) 上下方向
主对角线 ↘ (1, 1) 左上到右下
副对角线 ↙ (1, -1) 右上到左下

3.3 核心算法实现

dart 复制代码
/// 检查是否获胜
/// [x], [y] 最后落子的坐标
bool _checkWin(int x, int y) {
  // 四个方向的向量
  final directions = [
    [1, 0],   // 水平
    [0, 1],   // 垂直
    [1, 1],   // 主对角线
    [1, -1],  // 副对角线
  ];

  for (var dir in directions) {
    List<List<int>> pieces = [[x, y]];  // 记录连续棋子位置
    int count = 1;

    // 正方向搜索
    for (int i = 1; i < 5; i++) {
      int nx = x + dir[0] * i;
      int ny = y + dir[1] * i;
      if (_isValidPos(nx, ny) && board[ny][nx] == currentPlayer) {
        count++;
        pieces.add([nx, ny]);
      } else {
        break;  // 遇到边界或不同棋子,停止搜索
      }
    }

    // 反方向搜索
    for (int i = 1; i < 5; i++) {
      int nx = x - dir[0] * i;
      int ny = y - dir[1] * i;
      if (_isValidPos(nx, ny) && board[ny][nx] == currentPlayer) {
        count++;
        pieces.add([nx, ny]);
      } else {
        break;
      }
    }

    // 判断是否达到5个
    if (count >= 5) {
      winningPieces = pieces;  // 保存获胜棋子位置
      return true;
    }
  }
  return false;
}

3.4 算法复杂度分析

指标 复杂度 说明
时间复杂度 O(1)O(1)O(1) 每次最多检查 4×8=32 个位置
空间复杂度 O(1)O(1)O(1) 只需常数级额外空间

由于每次只检查落子点周围的有限范围,算法效率非常高。

3.5 边界检查

dart 复制代码
/// 检查坐标是否在棋盘范围内
bool _isValidPos(int x, int y) {
  return x >= 0 && x < 15 && y >= 0 && y < 15;
}

四、游戏交互与状态管理

4.1 状态变量

dart 复制代码
// 当前执子方
ChessType currentPlayer = ChessType.black;

// 游戏是否结束
bool gameOver = false;

// 获胜者
ChessType? winner;

// 最后落子位置(用于高亮)
int? lastX, lastY;

// 获胜的五个棋子位置(用于标记)
List<List<int>> winningPieces = [];

// 落子历史(用于悔棋)
List<List<int>> history = [];

4.2 落子流程

界面 游戏逻辑 用户 界面 游戏逻辑 用户 alt [获胜] [平局] [继续] alt [可以落子] [不能落子] 点击棋盘位置(x, y) 检查游戏是否结束 检查该位置是否为空 放置棋子 board[y][x] = currentPlayer 记录历史 history.add([x, y]) 检查是否获胜 显示获胜对话框 显示平局对话框 切换玩家 刷新界面 忽略点击

4.3 落子函数实现

dart 复制代码
void _placePiece(int x, int y) {
  // 前置检查
  if (gameOver || board[y][x] != ChessType.none) return;

  setState(() {
    // 1. 放置棋子
    board[y][x] = currentPlayer;
    lastX = x;
    lastY = y;
    history.add([x, y]);

    // 2. 检查胜利
    if (_checkWin(x, y)) {
      gameOver = true;
      winner = currentPlayer;
      _showWinDialog();
    } 
    // 3. 检查平局
    else if (_isBoardFull()) {
      gameOver = true;
      _showDrawDialog();
    } 
    // 4. 切换玩家
    else {
      currentPlayer = currentPlayer == ChessType.black
          ? ChessType.white
          : ChessType.black;
    }
  });
}

4.4 悔棋功能

dart 复制代码
void _undoMove() {
  if (history.isEmpty || gameOver) return;

  setState(() {
    // 取出最后一步
    final last = history.removeLast();
    // 清除该位置的棋子
    board[last[1]][last[0]] = ChessType.none;
    // 切换回上一个玩家
    currentPlayer = currentPlayer == ChessType.black
        ? ChessType.white
        : ChessType.black;
    // 更新最后落子位置
    if (history.isNotEmpty) {
      lastX = history.last[0];
      lastY = history.last[1];
    } else {
      lastX = null;
      lastY = null;
    }
  });
}

五、UI美化与动画效果

5.1 棋子渲染

使用渐变色让棋子更有立体感:

dart 复制代码
Widget _buildPiece(ChessType type, bool isLast, bool isWinning) {
  return AnimatedContainer(
    duration: const Duration(milliseconds: 200),
    decoration: BoxDecoration(
      shape: BoxShape.circle,
      // 渐变效果,模拟光照
      gradient: type == ChessType.black
          ? RadialGradient(
              center: const Alignment(-0.3, -0.3),  // 光源位置
              colors: [Colors.grey.shade700, Colors.black],
            )
          : RadialGradient(
              center: const Alignment(-0.3, -0.3),
              colors: [Colors.white, Colors.grey.shade300],
            ),
      // 边框:获胜棋子红色,最后落子金色
      border: Border.all(
        color: isWinning ? Colors.red : isLast ? Colors.amber : Colors.grey,
        width: isWinning || isLast ? 3 : 1,
      ),
      // 阴影效果
      boxShadow: [
        BoxShadow(
          color: Colors.black.withValues(alpha: 0.4),
          blurRadius: 4,
          offset: const Offset(2, 2),
        ),
      ],
    ),
  );
}

5.2 视觉效果对照表

状态 边框颜色 边框宽度 特殊标记
普通棋子 灰色 1px
最后落子 金色 3px 中心红点
获胜棋子 红色 3px

5.3 玩家指示器动画

dart 复制代码
AnimatedContainer(
  duration: const Duration(milliseconds: 300),
  padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
  decoration: BoxDecoration(
    // 当前玩家高亮
    color: isActive ? Colors.brown.shade600 : Colors.white,
    borderRadius: BorderRadius.circular(16),
    // 获胜者金色边框
    border: isWinner
        ? Border.all(color: Colors.amber, width: 3)
        : Border.all(color: Colors.brown.shade300),
    // 当前玩家添加阴影
    boxShadow: isActive ? [BoxShadow(...)] : null,
  ),
)

六、完整源码与运行

6.1 项目结构

复制代码
flutter_gomoku/
├── lib/
│   └── main.dart       # 五子棋游戏代码(约350行)
├── ohos/               # 鸿蒙平台配置
├── pubspec.yaml        # 依赖配置
└── README.md           # 项目说明

6.2 运行命令

bash 复制代码
# 获取依赖
flutter pub get

# 运行游戏
flutter run

# 运行到鸿蒙设备
flutter run -d ohos

6.3 功能清单

功能 状态 说明
15×15标准棋盘 CustomPainter绘制
双人对战 黑白交替落子
胜负判定 四方向五连检测
悔棋功能 支持多步悔棋
重新开始 一键重置
最后落子高亮 金色边框+红点
获胜棋子标记 红色边框
玩家指示器 动画切换
步数统计 实时显示

七、扩展方向

7.1 功能扩展

五子棋游戏
AI对战
在线对战
棋谱保存
禁手规则
极小化极大算法
Alpha-Beta剪枝
WebSocket
房间匹配
三三禁手
四四禁手
长连禁手

7.2 AI算法简介

如果想添加人机对战,可以使用以下算法:

算法 难度 特点
随机落子 最简单,随机选择空位
评分函数 ⭐⭐ 对每个位置打分,选最高分
极小化极大 ⭐⭐⭐ 博弈树搜索,考虑对手反应
Alpha-Beta剪枝 ⭐⭐⭐⭐ 优化搜索,减少计算量
蒙特卡洛树搜索 ⭐⭐⭐⭐⭐ 随机模拟,适合复杂局面

7.3 简单AI示例

dart 复制代码
/// 简单AI:评分法
List<int>? findBestMove() {
  int bestScore = -1;
  List<int>? bestMove;
  
  for (int y = 0; y < 15; y++) {
    for (int x = 0; x < 15; x++) {
      if (board[y][x] == ChessType.none) {
        int score = _evaluatePosition(x, y);
        if (score > bestScore) {
          bestScore = score;
          bestMove = [x, y];
        }
      }
    }
  }
  return bestMove;
}

/// 评估某个位置的分数
int _evaluatePosition(int x, int y) {
  int score = 0;
  // 检查周围是否有己方棋子(进攻)
  // 检查周围是否有对方棋子(防守)
  // 优先选择中心位置
  // ...
  return score;
}

八、常见问题

Q1: 为什么用枚举而不是数字表示棋子?

使用枚举(enum ChessType)比数字(0, 1, 2)有以下优势:

  1. 可读性ChessType.black1 更直观
  2. 类型安全:编译器会检查类型错误
  3. IDE支持:自动补全和重构更方便

Q2: 为什么检查获胜只需要检查最后落子的位置?

因为只有最后落下的棋子才可能形成新的五连。之前的棋子如果能形成五连,游戏早就结束了。这样可以将检查范围从整个棋盘(225个点)缩小到1个点的周围,大大提高效率。
Q3: 如何实现禁手规则?

禁手是专业五子棋的规则,限制黑棋的某些走法:

dart 复制代码
bool isForbidden(int x, int y) {
  if (currentPlayer != ChessType.black) return false;
  
  // 检查三三禁手:同时形成两个活三
  // 检查四四禁手:同时形成两个四
  // 检查长连禁手:形成六子或以上
  
  return false;  // 具体实现较复杂
}

九、总结

本文实现了一款完整的双人对战五子棋游戏,核心技术点包括:

  1. 棋盘绘制:CustomPainter绘制网格和星位
  2. 胜负判定:四方向搜索算法,O(1)复杂度
  3. 状态管理:棋盘数据、玩家切换、历史记录
  4. 交互设计:落子、悔棋、重开、高亮显示
  5. 视觉效果:渐变棋子、动画切换、阴影效果

五子棋虽然规则简单,但要下好却需要深厚的功力。希望这个项目能帮你理解游戏开发的基本思路,也欢迎和朋友来一局!


⚫⚪ 完整源码已上传,欢迎Star支持!


欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

相关推荐
Dilettante2583 小时前
这一招让 Node 后端服务启动速度提升 75%!
typescript·node.js
SoaringHeart15 小时前
Flutter调试组件:打印任意组件尺寸位置信息 NRenderBox
前端·flutter
jonjia20 小时前
模块、脚本与声明文件
typescript
jonjia20 小时前
配置 TypeScript
typescript
jonjia20 小时前
TypeScript 工具函数开发
typescript
jonjia20 小时前
注解与断言
typescript
jonjia20 小时前
IDE 超能力
typescript
jonjia20 小时前
对象类型
typescript
jonjia20 小时前
快速搭建 TypeScript 开发环境
typescript
九狼20 小时前
Flutter URL Scheme 跨平台跳转
人工智能·flutter·github