Flutter for OpenHarmony 实战:从零开发一款五子棋游戏

Flutter for OpenHarmony 实战:从零开发一款五子棋游戏

文章目录

  • [Flutter for OpenHarmony 实战:从零开发一款五子棋游戏](#Flutter for OpenHarmony 实战:从零开发一款五子棋游戏)

摘要

本文将详细介绍如何使用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方法进行更新,触发界面重绘。状态转换逻辑如下:

  1. 初始状态:黑棋先行,游戏未结束
  2. 落子后:检查是否获胜或平局
  3. 切换玩家:黑→白→黑→白...
  4. 游戏结束:显示结果,禁止继续落子

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. 正向:从落子点向一个方向延伸,统计连续同色棋子数量
  2. 反向:从落子点向相反方向延伸,统计连续同色棋子数量
  3. 总数:正向数量 + 反向数量 + 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开发五子棋游戏的完整过程,涵盖了以下核心技术点:

  1. 数据结构设计:使用二维数组表示棋盘状态,实现高效的落子和查询操作
  2. CustomPainter绘制:通过自定义绘制器实现棋盘网格线和立体棋子效果
  3. 状态管理:使用setState进行游戏状态更新,实现响应式UI
  4. 胜负算法:四方向双向计数算法,确保准确判定游戏结果

这个项目展示了Flutter在鸿蒙平台上的跨平台开发能力,代码结构清晰,易于扩展。读者可以基于此项目添加更多功能,如悔棋、保存棋局、AI对战等。

希望本文能为Flutter for OpenHarmony的学习者提供有价值的参考。


欢迎加入开源鸿蒙跨平台社区 : 开源鸿蒙跨平台开发者社区

相关推荐
BlackWolfSky3 小时前
鸿蒙中级课程笔记8—Native适配开发
笔记·华为·harmonyos
一起养小猫3 小时前
Flutter for OpenHarmony 实战:天气预报应用UI设计与主题切换
jvm·数据库·spring·flutter·ui·harmonyos
晚霞的不甘3 小时前
Flutter for OpenHarmony全面升级「今日运势」 应用的视觉与交互革新
前端·学习·flutter·前端框架·交互
向哆哆3 小时前
高校四六级报名系统通知公告模块实战:基于 Flutter × OpenHarmony 跨端开发
flutter·开源·鸿蒙·openharmony·开源鸿蒙
BlackWolfSky3 小时前
鸿蒙中级课程笔记7—给应用添加通知
笔记·华为·harmonyos
学嵌入式的小杨同学3 小时前
【Linux 封神之路】文件操作 + 时间编程实战:从缓冲区到时间格式化全解析
linux·c语言·开发语言·前端·数据库·算法·ux
●VON3 小时前
从像素到语义:React Native Text 组件在 OpenHarmony 上的渲染哲学与工程实现
android·react native·react.js
RFCEO4 小时前
学习前端编程:精准选中 HTML 元素的技巧与方法
前端·学习·css类选择器·兄弟元素选中·父子选中·关系选中·选择器选中
想睡好4 小时前
ref和reactive
前端·javascript·vue.js