目录

一、前言

二、效果展示

三、项目结构

核心文件

项目配置

四、核心代码实现

1. 鸿蒙风格主题配置

2. 游戏状态管理

3. 游戏核心逻辑

游戏初始化

点击事件处理

胜负判断

4. 棋盘UI实现

五、遇到的问题

1. 状态管理问题

2. 获胜线路高亮问题

3. 动画效果优化

六、总结


一、前言

随着鸿蒙操作系统的快速发展,越来越多的开发者开始关注鸿蒙平台的应用开发。Flutter作为一款优秀的跨平台UI框架,也在鸿蒙平台上得到了良好的支持。本文将介绍如何使用Flutter框架开发一款适合鸿蒙平台的井字棋小游戏,通过实际项目学习Flutter在鸿蒙平台上的开发流程和技术要点。

井字棋(Tic-Tac-Toe)是一款经典的双人对战游戏,规则简单易懂,非常适合作为入门级别的游戏开发案例。通过本项目,我们可以学习到Flutter的状态管理、UI设计、游戏逻辑实现以及如何适配鸿蒙OS设计风格等知识点。

二、效果展示

三、项目结构

核心文件

lib/main.dart:主应用入口,包含完整的游戏实现代码
pubspec.yaml:项目依赖配置文件
ohos/:鸿蒙平台相关代码目录

项目配置

✅  Flutter SDK:3.0+版本

✅  Dart语言:2.17+版本

✅  鸿蒙OS:支持最新版本

四、核心代码实现

1. 鸿蒙风格主题配置

// 鸿蒙风格主题配置 - 严格遵循鸿蒙OS设计规范
final harmonyTheme = ThemeData(
  // 鸿蒙OS核心色彩系统
  colorScheme: ColorScheme.fromSeed(
    seedColor: const Color(0xFF007DFF), // 鸿蒙蓝 - 主色调
    primary: const Color(0xFF007DFF),
    secondary: const Color(0xFF34C759), // 鸿蒙绿 - 辅助色
    background: const Color(0xFFF2F2F7), // 鸿蒙背景色
    surface: Colors.white,
    error: const Color(0xFFFF3B30), // 错误色
    onPrimary: Colors.white,
    onSecondary: Colors.white,
    onBackground: const Color(0xFF000000),
    onSurface: const Color(0xFF000000),
    onError: Colors.white,
  ),
  useMaterial3: true,
  // 鸿蒙风格文本主题 - 严格遵循鸿蒙字体规范
  textTheme: const TextTheme(
    headlineLarge: TextStyle(fontSize: 32, fontWeight: FontWeight.bold),
    headlineMedium: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
    headlineSmall: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
    titleLarge: TextStyle(fontSize: 18, fontWeight: FontWeight.w600),
    titleMedium: TextStyle(fontSize: 16, fontWeight: FontWeight.w500),
    titleSmall: TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
    bodyLarge: TextStyle(fontSize: 16, fontWeight: FontWeight.normal),
    bodyMedium: TextStyle(fontSize: 14, fontWeight: FontWeight.normal),
    bodySmall: TextStyle(fontSize: 12, fontWeight: FontWeight.normal),
    labelLarge: TextStyle(fontSize: 14, fontWeight: FontWeight.w600),
    labelMedium: TextStyle(fontSize: 12, fontWeight: FontWeight.w500),
    labelSmall: TextStyle(fontSize: 11, fontWeight: FontWeight.w500),
  ),
  // 鸿蒙风格AppBar - 无阴影,居中标题
  appBarTheme: const AppBarTheme(
    backgroundColor: Color(0xFF007DFF),
    foregroundColor: Colors.white,
    elevation: 0,
    centerTitle: true,
  ),
  // 鸿蒙风格Scaffold背景色
  scaffoldBackgroundColor: Color(0xFFF2F2F7),
);

2. 游戏状态管理

/// 游戏状态枚举
enum GameState {
  playing, // 游戏进行中
  playerXWon, // X玩家获胜
  playerOWon, // O玩家获胜
  draw, // 平局
}

// 棋盘大小
static const int _boardSize = 3;
// 空状态,X玩家,O玩家
static const String _empty = '';
static const String _playerX = 'X';
static const String _playerO = 'O';

// 棋盘状态,二维数组表示
late List<List<String>> _board;
// 当前玩家
late String _currentPlayer;
// 游戏状态
late GameState _gameState;
// 获胜线路
late List<int> _winningLine;
// 游戏历史记录
late List<String> _gameHistory;

3. 游戏核心逻辑

游戏初始化
/// 初始化游戏
void _initializeGame() {
  // 初始化空棋盘
  _board = List.generate(
    _boardSize,
    (_) => List.generate(_boardSize, (_) => _empty),
  );
  // X玩家先手
  _currentPlayer = _playerX;
  // 游戏开始
  _gameState = GameState.playing;
  // 初始化获胜线路为[-1, -1, -1, -1],表示无获胜线路
  _winningLine = [-1, -1, -1, -1];
  // 清空游戏历史
  _gameHistory = [];
}
点击事件处理
/// 处理玩家点击棋盘
void _handleTap(int row, int col) {
  // 如果游戏已结束或该位置已被占用,不处理点击
  if (_gameState != GameState.playing || _board[row][col] != _empty) {
    return;
  }

  setState(() {
    // 在点击位置放置当前玩家的棋子
    _board[row][col] = _currentPlayer;
    // 添加到游戏历史
    _gameHistory.add('${_currentPlayer} 在 ($row, $col) 位置落子');
    // 检查游戏状态
    _checkGameState();
    // 如果游戏未结束,切换玩家
    if (_gameState == GameState.playing) {
      _currentPlayer = _currentPlayer == _playerX ? _playerO : _playerX;
    }
  });
}
胜负判断
/// 检查游戏状态
void _checkGameState() {
  // 检查所有行
  for (int row = 0; row < _boardSize; row++) {
    if (_board[row][0] != _empty &&
        _board[row][0] == _board[row][1] &&
        _board[row][1] == _board[row][2]) {
      _gameState = _board[row][0] == _playerX ? GameState.playerXWon : GameState.playerOWon;
      _winningLine = [row, 0, row, 2];
      return;
    }
  }

  // 检查所有列
  for (int col = 0; col < _boardSize; col++) {
    if (_board[0][col] != _empty &&
        _board[0][col] == _board[1][col] &&
        _board[1][col] == _board[2][col]) {
      _gameState = _board[0][col] == _playerX ? GameState.playerXWon : GameState.playerOWon;
      _winningLine = [0, col, 2, col];
      return;
    }
  }

  // 检查对角线(从左上到右下)
  if (_board[0][0] != _empty &&
      _board[0][0] == _board[1][1] &&
      _board[1][1] == _board[2][2]) {
    _gameState = _board[0][0] == _playerX ? GameState.playerXWon : GameState.playerOWon;
    _winningLine = [0, 0, 2, 2];
    return;
  }

  // 检查对角线(从右上到左下)
  if (_board[0][2] != _empty &&
      _board[0][2] == _board[1][1] &&
      _board[1][1] == _board[2][0]) {
    _gameState = _board[0][2] == _playerX ? GameState.playerXWon : GameState.playerOWon;
    _winningLine = [0, 2, 2, 0];
    return;
  }

  // 检查是否平局
  bool isDraw = true;
  for (int row = 0; row < _boardSize; row++) {
    for (int col = 0; col < _boardSize; col++) {
      if (_board[row][col] == _empty) {
        isDraw = false;
        break;
      }
    }
    if (!isDraw) break;
  }

  if (isDraw) {
    _gameState = GameState.draw;
    _winningLine = [-1, -1, -1, -1];
  }
}

4. 棋盘UI实现

/// 构建棋盘
Widget _buildBoard() {
  return GridView.builder(
    gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
      crossAxisCount: _boardSize,
      mainAxisSpacing: 8.0,
      crossAxisSpacing: 8.0,
    ),
    itemCount: _boardSize * _boardSize,
    itemBuilder: (context, index) {
      final row = index ~/ _boardSize;
      final col = index % _boardSize;
      return _buildCell(row, col);
    },
  );
}

/// 构建棋盘单元格
Widget _buildCell(int row, int col) {
  final cellValue = _board[row][col];
  final isSelected = cellValue != _empty;
  final isWinningCell = _isWinningCell(row, col);

  return GestureDetector(
    onTap: () => _handleTap(row, col),
    child: Container(
      decoration: BoxDecoration(
        color: isWinningCell ? Colors.yellow.withOpacity(0.3) : Colors.white,
        borderRadius: BorderRadius.circular(12.0),
        boxShadow: [
          BoxShadow(
            color: Colors.black.withOpacity(0.05),
            blurRadius: 4,
            offset: const Offset(0, 2),
          ),
        ],
        border: Border.all(
          color: isWinningCell ? Colors.yellow : const Color(0xFFE0E0E0),
          width: isWinningCell ? 2.0 : 1.0,
        ),
      ),
      child: Center(
        child: AnimatedScale(
          scale: isSelected ? 1.0 : 0.5,
          duration: const Duration(milliseconds: 300),
          curve: Curves.easeOut,
          child: Text(
            cellValue,
            style: TextStyle(
              fontSize: 48.0,
              fontWeight: FontWeight.bold,
              color: cellValue == _playerX ? Colors.red : Colors.blue,
            ),
          ),
        ),
      ),
    ),
  );
}

五、遇到的问题

1. 状态管理问题

问题描述:在游戏逻辑实现中,初始尝试使用多个独立变量管理游戏状态,导致代码混乱且难以维护。

解决方案:改用枚举类型GameState统一管理游戏状态,使用二维数组表示棋盘,使代码结构更清晰,便于维护和扩展。

2. 获胜线路高亮问题

问题描述:如何高效地高亮显示获胜线路,同时不影响游戏性能。

解决方案:使用_winningLine数组存储获胜线路的起始和结束坐标,通过_isWinningCell方法判断每个单元格是否在获胜线路上,然后动态调整单元格样式。

3. 动画效果优化

问题描述:初始实现中,棋子显示缺乏动画效果,用户体验不佳。

解决方案:使用AnimatedScale组件为棋子添加缩放动画,使棋子显示更加流畅自然。

六、总结

本项目成功实现了一款基于Flutter框架和鸿蒙OS设计风格的井字棋小游戏。通过本项目,我们学习到了以下知识点:

  1. Flutter跨平台开发:如何使用Flutter框架开发跨平台应用,特别是针对鸿蒙平台的适配。
  2. 鸿蒙OS设计风格:如何在Flutter应用中实现鸿蒙OS的设计风格,包括色彩、字体、阴影等。
  3. 状态管理:如何使用Flutter的StatefulWidget进行复杂的状态管理,特别是游戏状态的管理。
  4. 游戏逻辑实现:如何实现完整的游戏逻辑,包括棋盘状态管理、玩家切换、胜负判断等。
  5. UI设计与动画:如何设计美观的UI界面,以及如何添加流畅的动画效果。
  6. 项目优化:如何优化项目结构,移除不必要的代码,提高项目的可维护性。

本项目虽然简单,但涵盖了Flutter开发的许多核心知识点,适合作为Flutter入门学习的案例。通过不断优化和扩展,我们可以将这款简单的井字棋游戏发展成为功能更丰富、体验更好的游戏应用,例如添加AI对手、多种游戏模式、游戏记录等功能。


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

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐