Flutter for OpenHarmony 实战:从零开发一款五子棋游戏
文章目录
- [Flutter for OpenHarmony 实战:从零开发一款五子棋游戏](#Flutter for OpenHarmony 实战:从零开发一款五子棋游戏)
-
- 摘要
- 一、项目背景与技术选型
-
- [1.1 为什么选择Flutter for OpenHarmony](#1.1 为什么选择Flutter for OpenHarmony)
- [1.2 五子棋游戏的功能需求](#1.2 五子棋游戏的功能需求)
- 二、五子棋游戏的核心逻辑设计
-
- [2.1 棋盘数据结构设计](#2.1 棋盘数据结构设计)
- [2.2 游戏状态管理](#2.2 游戏状态管理)
- [2.3 落子逻辑流程](#2.3 落子逻辑流程)
- 三、Flutter界面架构实现
-
- [3.1 整体UI结构设计](#3.1 整体UI结构设计)
- [3.2 状态显示组件](#3.2 状态显示组件)
- [3.3 棋盘网格布局](#3.3 棋盘网格布局)
- 四、CustomPainter绘制棋盘与棋子
-
- [4.1 为什么使用CustomPainter](#4.1 为什么使用CustomPainter)
- [4.2 棋盘网格线绘制原理](#4.2 棋盘网格线绘制原理)
- [4.3 星位点定位](#4.3 星位点定位)
- [4.4 棋子绘制与立体效果](#4.4 棋子绘制与立体效果)
- 五、胜负判定算法详解
-
- [5.1 胜负规则说明](#5.1 胜负规则说明)
- [5.2 四方向检测算法](#5.2 四方向检测算法)
- [5.3 算法实现细节](#5.3 算法实现细节)
- [5.4 平局检测](#5.4 平局检测)
- 六、完整代码实现
-
- [6.1 主程序入口](#6.1 主程序入口)
- [6.2 游戏主页面组件](#6.2 游戏主页面组件)
- [6.3 落子处理逻辑](#6.3 落子处理逻辑)
- [6.4 界面构建代码](#6.4 界面构建代码)
- [6.5 CustomPainter绘制器完整代码](#6.5 CustomPainter绘制器完整代码)
- 七、运行效果展示
-
- [7.2 功能测试要点](#7.2 功能测试要点)
- 八、总结
摘要
本文将详细介绍如何使用Flutter for OpenHarmony框架,从零开始开发一款完整的五子棋游戏。文章涵盖了游戏核心逻辑设计、Flutter自定义绘制、状态管理、胜负算法等关键技术点,并提供完整的可运行代码示例。通过本文,读者可以深入理解Flutter在鸿蒙平台上的跨平台开发能力,掌握游戏开发的核心技巧。
一、项目背景与技术选型
1.1 为什么选择Flutter for OpenHarmony
随着鸿蒙生态的快速发展,跨平台开发成为了众多开发者的首选方案。Flutter作为Google推出的UI框架,凭借其"一套代码,多端运行"的特性,在OpenHarmony平台上展现出了强大的适配能力。选择Flutter开发五子棋游戏,主要基于以下考虑:
首先是渲染性能的保障。五子棋游戏需要频繁绘制棋盘网格和棋子,Flutter的Skia渲染引擎能够提供流畅的60fps体验,即使在低端设备上也能保持稳定的帧率。
其次是开发效率的提升。Flutter的热重载功能让开发者可以实时查看UI调整效果,这对于需要反复调试棋盘布局和棋子样式的游戏开发来说尤为重要。
最后是代码的可维护性。Flutter采用声明式UI编程范式,组件化的开发方式让代码结构清晰,便于后续功能扩展和维护。
1.2 五子棋游戏的功能需求
在正式编码之前,我们需要明确游戏的核心功能需求:
基础功能模块
- 15x15标准棋盘布局
- 双人轮流落子机制
- 实时显示当前玩家
- 胜负判定与平局检测
- 重新开始游戏功能
交互体验优化
- 点击棋盘交叉点落子
- 已占用位置不可重复落子
- 游戏结束后显示获胜方
- 友好的视觉反馈
二、五子棋游戏的核心逻辑设计
2.1 棋盘数据结构设计
五子棋棋盘由15条横线和15条纵线组成,共225个交叉点。在程序中,我们使用二维数组来表示棋盘状态:
dart
// 棋盘状态数组
// 0 = 空位
// 1 = 黑棋
// 2 = 白棋
List<List<int>> _board = [];
每个数组元素对应棋盘上的一个交叉点,通过行索引和列索引可以精确定位任意位置。这种数据结构的优势在于:
- 时间复杂度为O(1)的随机访问
- 空间复杂度固定为O(n²),n为棋盘尺寸
- 便于后续的胜负判定算法实现
2.2 游戏状态管理
游戏过程中需要维护几个关键状态变量:
dart
// 当前玩家:1=黑棋,2=白棋
int _currentPlayer = 1;
// 游戏是否结束
bool _gameOver = false;
// 获胜者信息
String? _winner;
这些状态变量通过Flutter的setState方法进行更新,触发界面重绘。状态转换逻辑如下:
- 初始状态:黑棋先行,游戏未结束
- 落子后:检查是否获胜或平局
- 切换玩家:黑→白→黑→白...
- 游戏结束:显示结果,禁止继续落子
2.3 落子逻辑流程
玩家点击棋盘后,系统执行以下处理流程:

这个流程确保了游戏逻辑的完整性和一致性。
三、Flutter界面架构实现
3.1 整体UI结构设计
五子棋游戏的UI采用典型的垂直布局结构:
dart
Scaffold
├── AppBar(顶部导航栏)
│ ├── 标题:"五子棋游戏"
│ └── 重新开始按钮
├── Column(主体内容)
│ ├── 状态显示区域
│ ├── 棋盘区域(中心)
│ └── 游戏说明区域
这种布局的优势在于层次清晰,各个功能模块职责明确。棋盘区域使用AspectRatio组件保持正方形比例,确保在不同屏幕尺寸下都能正确显示。
3.2 状态显示组件
状态显示组件实时反馈当前游戏信息:
dart
Container(
padding: const EdgeInsets.all(16),
child: Text(
_gameOver
? (_winner == '平局' ? '游戏结束:平局!' : '游戏结束:$_winner 获胜!')
: '当前玩家:${_currentPlayer == 1 ? '黑棋 ●' : '白棋 ○'}',
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
)
该组件根据游戏状态动态显示不同的文本内容,让玩家随时了解当前局面。
3.3 棋盘网格布局
棋盘使用GridView.builder进行构建,这是处理二维网格数据的高效方案:
dart
GridView.builder(
padding: const EdgeInsets.all(4),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: boardSize,
crossAxisSpacing: 0,
mainAxisSpacing: 0,
),
itemCount: boardSize * boardSize,
itemBuilder: (context, index) {
int row = index ~/ boardSize;
int col = index % boardSize;
// 返回棋盘单元格...
},
)
GridView的懒加载特性确保了即使棋盘尺寸扩大,也不会影响初始渲染性能。
四、CustomPainter绘制棋盘与棋子
4.1 为什么使用CustomPainter
Flutter提供了多种绘制方式,但对于五子棋这种需要自定义图形的场景,CustomPainter是最佳选择。原因如下:
精确控制绘制细节
- 棋盘网格线的位置和粗细需要精确控制
- 棋子需要添加高光效果增强立体感
- 星位点需要精确定位在特定坐标
性能优化
- CustomPainter直接操作Canvas,减少组件层级
- 绘制结果会被缓存,避免重复计算
- 每个单元格独立绘制,互不干扰
4.2 棋盘网格线绘制原理
棋盘由横线和竖线交织而成,绘制时需要注意边界处理:
dart
// 绘制网格线
// 横线:除了最后一行,每行底部画一条横线
if (row < 14) {
canvas.drawLine(
Offset(0, cellSize),
Offset(cellSize, cellSize),
linePaint,
);
}
// 竖线:除了最后一列,每列右侧画一条竖线
if (col < 14) {
canvas.drawLine(
Offset(cellSize, 0),
Offset(cellSize, cellSize),
linePaint,
);
}
这种绘制方式确保了棋盘边缘不会出现多余的线条。
4.3 星位点定位
标准五子棋棋盘有9个星位点,分别位于:
- 4个角星位:(3,3)、(3,11)、(11,3)、(11,11)
- 4个边星位:(3,7)、(7,3)、(7,11)、(11,7)
- 1个天元:(7,7)
绘制代码:
dart
// 绘制天元和星位小黑点
if ((row == 3 || row == 7 || row == 11) &&
(col == 3 || col == 7 || col == 11)) {
canvas.drawCircle(
Offset(centerX, centerY),
3,
linePaint,
);
}
4.4 棋子绘制与立体效果
为了让棋子看起来更有立体感,我们添加了高光效果:
黑棋绘制
dart
// 绘制黑色圆形棋子
canvas.drawCircle(
Offset(centerX, centerY),
pieceSize / 2,
blackPiecePaint,
);
// 添加左上角高光
canvas.drawCircle(
Offset(centerX - pieceSize * 0.15, centerY - pieceSize * 0.15),
pieceSize * 0.1,
Paint()
..color = Colors.white.withOpacity(0.3)
..style = PaintingStyle.fill,
);
白棋绘制
dart
// 绘制白色圆形棋子
canvas.drawCircle(
Offset(centerX, centerY),
pieceSize / 2,
whitePiecePaint,
);
// 添加边框增强对比度
canvas.drawCircle(
Offset(centerX, centerY),
pieceSize / 2,
Paint()
..color = Colors.black26
..style = PaintingStyle.stroke
..strokeWidth = 1,
);
五、胜负判定算法详解
5.1 胜负规则说明
五子棋的胜负判定规则很简单:任意一方在横、竖、斜任意方向上连成5个或以上同色棋子即获胜。需要注意的是,长连(超过5子)也算获胜。
5.2 四方向检测算法
每次落子后,需要检测当前落子位置四个方向上的连线情况:
dart
// 四个方向:水平、垂直、主对角线、副对角线
List<List<int>> directions = [
[0, 1], // 水平方向
[1, 0], // 垂直方向
[1, 1], // 主对角线(左上到右下)
[1, -1], // 副对角线(右上到左下)
];
对于每个方向,执行双向计数:
- 正向:从落子点向一个方向延伸,统计连续同色棋子数量
- 反向:从落子点向相反方向延伸,统计连续同色棋子数量
- 总数:正向数量 + 反向数量 + 1(当前落子)
5.3 算法实现细节
dart
bool _checkWin(int row, int col) {
int player = _board[row][col];
for (var dir in directions) {
int count = 1; // 当前落子算1个
// 正方向查找
int r = row + dir[0];
int c = col + dir[1];
while (_isValid(r, c) && _board[r][c] == player) {
count++;
r += dir[0];
c += dir[1];
}
// 反方向查找
r = row - dir[0];
c = col - dir[1];
while (_isValid(r, c) && _board[r][c] == player) {
count++;
r -= dir[0];
c -= dir[1];
}
if (count >= 5) return true;
}
return false;
}
具体的还在后面

算法复杂度分析
- 时间复杂度:O(1),每次只检查固定数量的位置
- 空间复杂度:O(1),只使用常量级别的额外空间
5.4 平局检测
当棋盘所有位置都被占用且无人获胜时,判定为平局:
dart
bool _checkDraw() {
for (int i = 0; i < boardSize; i++) {
for (int j = 0; j < boardSize; j++) {
if (_board[i][j] == 0) return false;
}
}
return true;
}
六、完整代码实现
6.1 主程序入口
dart
import 'package:flutter/material.dart';
void main() {
runApp(const GobangApp());
}
class GobangApp extends StatelessWidget {
const GobangApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '五子棋游戏',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.brown),
useMaterial3: true,
),
home: const GobangGamePage(),
);
}
}
6.2 游戏主页面组件
dart
class GobangGamePage extends StatefulWidget {
const GobangGamePage({super.key});
@override
State<GobangGamePage> createState() => _GobangGamePageState();
}
class _GobangGamePageState extends State<GobangGamePage> {
// 棋盘大小 15x15
static const int boardSize = 15;
// 棋盘状态:0=空,1=黑棋,2=白棋
List<List<int>> _board = [];
// 当前玩家:1=黑棋,2=白棋
int _currentPlayer = 1;
// 游戏是否结束
bool _gameOver = false;
// 获胜者
String? _winner;
@override
void initState() {
super.initState();
_initBoard();
}
// 初始化棋盘
void _initBoard() {
_board = List.generate(boardSize, (_) => List.filled(boardSize, 0));
_currentPlayer = 1;
_gameOver = false;
_winner = null;
}
6.3 落子处理逻辑
dart
// 落子
void _placePiece(int row, int col) {
if (_gameOver || _board[row][col] != 0) return;
setState(() {
_board[row][col] = _currentPlayer;
// 检查是否获胜
if (_checkWin(row, col)) {
_gameOver = true;
_winner = _currentPlayer == 1 ? '黑棋' : '白棋';
} else if (_checkDraw()) {
_gameOver = true;
_winner = '平局';
} else {
// 切换玩家
_currentPlayer = _currentPlayer == 1 ? 2 : 1;
}
});
}
6.4 界面构建代码
dart
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: const Text('五子棋游戏'),
actions: [
IconButton(
icon: const Icon(Icons.refresh),
onPressed: () {
setState(() {
_initBoard();
});
},
tooltip: '重新开始',
),
],
),
body: Column(
children: [
Container(
padding: const EdgeInsets.all(16),
child: Text(
_gameOver
? (_winner == '平局' ? '游戏结束:平局!' : '游戏结束:$_winner 获胜!')
: '当前玩家:${_currentPlayer == 1 ? '黑棋 ●' : '白棋 ○'}',
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
),
Expanded(
child: Center(
child: AspectRatio(
aspectRatio: 1.0,
child: Container(
margin: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.brown[200],
borderRadius: BorderRadius.circular(8),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.3),
blurRadius: 10,
spreadRadius: 2,
),
],
),
child: GridView.builder(
padding: const EdgeInsets.all(4),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: boardSize,
crossAxisSpacing: 0,
mainAxisSpacing: 0,
),
itemCount: boardSize * boardSize,
itemBuilder: (context, index) {
int row = index ~/ boardSize;
int col = index % boardSize;
return GestureDetector(
onTap: () => _placePiece(row, col),
child: CustomPaint(
painter: _BoardCellPainter(
row: row,
col: col,
piece: _board[row][col],
isLastMove: false,
),
),
);
},
),
),
),
),
),
Container(
padding: const EdgeInsets.all(16),
child: const Text(
'点击棋盘落子,先连成五子者获胜',
style: TextStyle(fontSize: 14, color: Colors.grey),
),
),
],
),
);
}
}
6.5 CustomPainter绘制器完整代码
dart
class _BoardCellPainter extends CustomPainter {
final int row;
final int col;
final int piece;
final bool isLastMove;
_BoardCellPainter({
required this.row,
required this.col,
required this.piece,
required this.isLastMove,
});
@override
void paint(Canvas canvas, Size size) {
double cellSize = size.width;
double centerX = cellSize / 2;
double centerY = cellSize / 2;
double pieceSize = cellSize * 0.8;
Paint linePaint = Paint()
..color = Colors.black87
..strokeWidth = 1
..style = PaintingStyle.stroke;
Paint blackPiecePaint = Paint()
..color = Colors.black
..style = PaintingStyle.fill;
Paint whitePiecePaint = Paint()
..color = Colors.white
..style = PaintingStyle.fill;
// 绘制网格线
if (row > 0) {
canvas.drawLine(Offset(0, 0), Offset(cellSize, 0), linePaint);
}
if (row < 14) {
canvas.drawLine(Offset(0, cellSize), Offset(cellSize, cellSize), linePaint);
}
if (col > 0) {
canvas.drawLine(Offset(0, 0), Offset(0, cellSize), linePaint);
}
if (col < 14) {
canvas.drawLine(Offset(cellSize, 0), Offset(cellSize, cellSize), linePaint);
}
// 绘制星位小黑点
if ((row == 3 || row == 7 || row == 11) && (col == 3 || col == 7 || col == 11)) {
canvas.drawCircle(Offset(centerX, centerY), 3, linePaint);
}
// 绘制棋子
if (piece == 1) {
canvas.drawCircle(Offset(centerX, centerY), pieceSize / 2, blackPiecePaint);
canvas.drawCircle(
Offset(centerX - pieceSize * 0.15, centerY - pieceSize * 0.15),
pieceSize * 0.1,
Paint()..color = Colors.white.withOpacity(0.3)..style = PaintingStyle.fill,
);
} else if (piece == 2) {
canvas.drawCircle(Offset(centerX, centerY), pieceSize / 2, whitePiecePaint);
canvas.drawCircle(
Offset(centerX, centerY),
pieceSize / 2,
Paint()..color = Colors.black26..style = PaintingStyle.stroke..strokeWidth = 1,
);
}
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
}
七、运行效果展示
7.2 功能测试要点
运行后建议测试以下功能点:
- 黑棋和白棋能否正常轮流落子
- 已占用位置是否禁止重复落子
- 五子连珠时是否正确判定获胜
- 棋盘下满时是否正确判定平局
- 重新开始按钮是否正常工作
八、总结
本文详细介绍了使用Flutter for OpenHarmony开发五子棋游戏的完整过程,涵盖了以下核心技术点:
- 数据结构设计:使用二维数组表示棋盘状态,实现高效的落子和查询操作
- CustomPainter绘制:通过自定义绘制器实现棋盘网格线和立体棋子效果
- 状态管理:使用setState进行游戏状态更新,实现响应式UI
- 胜负算法:四方向双向计数算法,确保准确判定游戏结果
这个项目展示了Flutter在鸿蒙平台上的跨平台开发能力,代码结构清晰,易于扩展。读者可以基于此项目添加更多功能,如悔棋、保存棋局、AI对战等。
希望本文能为Flutter for OpenHarmony的学习者提供有价值的参考。
欢迎加入开源鸿蒙跨平台社区 : 开源鸿蒙跨平台开发者社区