在这里插入图片描述

在这里插入图片描述

摘要

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

本文系统阐述如何基于 React Native 0.72.5 构建一个经典的双人对战井字棋(Tic-Tac-Toe)游戏,并成功部署至 OpenHarmony 6.0+ 平台。通过集成 @react-native-oh/react-native-harmony 工具链,我们实现了从游戏逻辑设计、状态管理、UI 渲染到 HarmonyOS 原生构建的完整开发闭环。文章深入剖析了胜负判定算法、响应式棋盘布局、实时状态反馈机制,并重点解析了在 OpenHarmony 环境下特有的 Bundle 生成、原生运行时集成与调试策略。

该井字棋应用虽逻辑简洁,却完整体现了 声明式 UI 编程、不可变状态更新、条件渲染与事件驱动交互 的现代前端开发范式,为开发者在 OpenHarmony 生态中快速构建交互式应用提供了标准化模板。

关键词:React Native for OpenHarmony、井字棋、游戏开发、状态管理、胜负判定、HarmonyOS 构建


1. 引言:为何选择井字棋作为 RNOH 游戏开发示例?

井字棋作为最经典的双人策略游戏之一,具备以下优势,使其成为 React Native for OpenHarmony(RNOH) 游戏开发的理想入门项目:

  • 规则简单明确:仅需判断 8 种获胜组合,逻辑易于验证;
  • 状态有限:3×3 棋盘共 9 个格子,状态空间小,便于调试;
  • 交互典型:涵盖点击事件、状态切换、结果反馈等核心交互模式;
  • 无外部依赖:纯客户端逻辑,无需网络或数据库;
  • 教学价值高:天然适合演示数组操作、条件渲染与游戏循环。

更重要的是,它能有效验证 React Native 在 OpenHarmony 上的触摸响应性能、UI 更新流畅度及 JavaScript 执行效率,为更复杂的游戏开发奠定基础。


2. 技术栈与开发环境

2.1 核心依赖版本

组件 版本 作用
React Native 0.72.5 跨平台 UI 框架
React 18.2.0 提供 Hooks 与组件模型
TypeScript 4.8.4 类型安全,提升代码可维护性
@react-native-oh/react-native-harmony ^0.72.90 RNOH 桥接层,提供 Metro 配置与原生绑定

⚠️ 关键约束
RNOH 的版本号(如 0.72.90)必须与 React Native 主版本(0.72)严格对齐,否则将导致模块解析失败或运行时异常。

2.2 OpenHarmony 开发环境

  • DevEco Studio ≥ 6.0
  • OpenHarmony SDK:API Version 20(OpenHarmony 6.0+)
  • Node.js:v18.x(LTS)
  • 项目路径:必须位于盘符根目录(如 C:\RNProject),避免 Windows 路径过长错误

3. 游戏核心数据模型与状态管理

3.1 类型定义

使用 TypeScript 定义玩家类型与棋盘状态:

type Player = 'X' | 'O' | null;

// 棋盘:长度为 9 的数组,索引对应格子位置
// [0, 1, 2]
// [3, 4, 5]
// [6, 7, 8]
const [board, setBoard] = useState<Player[]>(Array(9).fill(null));

// 当前行动玩家
const [currentPlayer, setCurrentPlayer] = useState<Player>('X');

// 游戏结果:null(进行中)、'X'、'O' 或 'draw'
const [winner, setWinner] = useState<Player | 'draw' | null>(null);

不可变更新原则
所有状态变更均通过创建新数组实现,确保 React 正确触发重渲染。

3.2 胜负判定算法

预定义 8 种获胜组合:

const winningCombinations = [
  [0, 1, 2], [3, 4, 5], [6, 7, 8], // 横向
  [0, 3, 6], [1, 4, 7], [2, 5, 8], // 纵向
  [0, 4, 8], [2, 4, 6]              // 对角线
];

胜负检查函数:

const checkWinner = useCallback((currentBoard: Player[]): Player | 'draw' | null => {
  // 检查是否有玩家获胜
  for (const [a, b, c] of winningCombinations) {
    if (
      currentBoard[a] !== null &&
      currentBoard[a] === currentBoard[b] &&
      currentBoard[a] === currentBoard[c]
    ) {
      return currentBoard[a]; // 返回获胜玩家 'X' 或 'O'
    }
  }

  // 检查是否平局(棋盘已满且无胜者)
  if (currentBoard.every(cell => cell !== null)) {
    return 'draw';
  }

  return null; // 游戏继续
}, []);

🔍 算法复杂度:O(1) —— 固定 8 次比较,性能极佳。


4. 核心交互逻辑实现

4.1 格子点击处理

const handleCellPress = useCallback((index: number) => {
  // 若格子已被占用或游戏已结束,忽略点击
  if (board[index] !== null || winner !== null) return;

  // 创建新棋盘状态
  const newBoard = [...board];
  newBoard[index] = currentPlayer;
  setBoard(newBoard);

  // 检查游戏结果
  const gameResult = checkWinner(newBoard);
  setWinner(gameResult);

  // 若游戏未结束,切换玩家
  if (gameResult === null) {
    setCurrentPlayer(currentPlayer === 'X' ? 'O' : 'X');
  }
}, [board, currentPlayer, winner, checkWinner]);

4.2 重新开始逻辑

const handleReset = useCallback(() => {
  setBoard(Array(9).fill(null));
  setCurrentPlayer('X');
  setWinner(null);
}, []);

5. 响应式 UI 设计与实现

5.1 整体布局结构

采用垂直 Flex 布局:

<View style={styles.container}>
  <Text style={styles.title}>井字棋</Text>
  <Text style={styles.status}>{getStatusText()}</Text>
  <View style={styles.board}>
    {board.map((cell, index) => (
      <TouchableOpacity
        key={index}
        style={styles.cell}
        onPress={() => handleCellPress(index)}
        disabled={winner !== null} // 游戏结束后禁用点击
      >
        <Text style={[
          styles.cellText,
          cell === 'X' && styles.xText,
          cell === 'O' && styles.oText
        ]}>
          {cell}
        </Text>
      </TouchableOpacity>
    ))}
  </View>
  <TouchableOpacity style={styles.resetButton} onPress={handleReset}>
    <Text style={styles.resetButtonText}>重新开始</Text>
  </TouchableOpacity>
</View>

5.2 状态文本动态显示

const getStatusText = (): string => {
  if (winner === 'X') return '玩家 X 获胜!';
  if (winner === 'O') return '玩家 O 获胜!';
  if (winner === 'draw') return '平局!';
  return `当前玩家:${currentPlayer}`;
};

5.3 样式表(StyleSheet)

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#f5f5f5',
    alignItems: 'center',
    justifyContent: 'center',
    padding: 20,
  },
  title: {
    fontSize: 32,
    fontWeight: 'bold',
    marginBottom: 20,
    color: '#333',
  },
  status: {
    fontSize: 20,
    fontWeight: '600',
    marginBottom: 30,
    color: '#555',
  },
  board: {
    width: 300,
    height: 300,
    backgroundColor: '#ddd',
    flexDirection: 'row',
    flexWrap: 'wrap',
    borderRadius: 10,
    overflow: 'hidden',
    marginBottom: 30,
  },
  cell: {
    width: '33.33%',
    height: '33.33%',
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#fff',
    borderWidth: 1,
    borderColor: '#ccc',
  },
  cellText: {
    fontSize: 60,
    fontWeight: 'bold',
  },
  xText: {
    color: '#2196F3', // 蓝色
  },
  oText: {
    color: '#F44336', // 红色
  },
  resetButton: {
    backgroundColor: '#4CAF50',
    paddingHorizontal: 30,
    paddingVertical: 12,
    borderRadius: 8,
  },
  resetButtonText: {
    color: '#fff',
    fontSize: 18,
    fontWeight: '600',
  },
});

🎨 视觉反馈

  • X 用蓝色(#2196F3),O 用红色(#F44336),符合 Material Design 色彩规范;
  • 按钮禁用状态自动灰显,提升可用性。

6. OpenHarmony 构建与集成

6.1 Metro 配置

metro.config.js 必须包含 RNOH 专属配置:

const { createHarmonyMetroConfig } = require("@react-native-oh/react-native-harmony/metro.config");

module.exports = mergeConfig(
  getDefaultConfig(__dirname),
  createHarmonyMetroConfig({
    reactNativeHarmonyPackageName: '@react-native-oh/react-native-harmony'
  })
);

6.2 Bundle 生成与加载

执行 npm run harmony 后,JS Bundle 输出至:

harmony/entry/src/main/resources/rawfile/index.harmony.bundle

OpenHarmony 原生工程通过 RNAbility 自动加载此文件,无需手动干预。

6.3 原生侧最小化配置

  • EntryAbility.ets 继承 RNAbility
  • 无需修改 ArkTS 页面内容;
  • C++ 层 PackageProvider.cpp 返回空模块列表(本游戏无自定义原生功能)。

7. 性能优化与用户体验增强

7.1 触摸反馈优化

  • 使用 TouchableOpacity 提供按压透明度变化;
  • 可扩展为震动反馈(需调用 OpenHarmony @kit.DeviceCapabilityKit)。

7.2 无障碍支持

<TouchableOpacity
  accessibilityRole="button"
  accessibilityLabel={`格子 ${index + 1},当前状态:${cell || '空'}`}
  // ...
>

7.3 游戏状态持久化(可选)

若需保存未完成游戏,可集成 AsyncStorage(在 OpenHarmony 上由 RNOH 映射至本地存储):

useEffect(() => {
  const saveGame = async () => {
    await AsyncStorage.setItem('ticTacToeState', JSON.stringify({ board, currentPlayer, winner }));
  };
  saveGame();
}, [board, currentPlayer, winner]);

8. 测试策略

8.1 单元测试(Jest)

test('X wins horizontally', () => {
  const board: Player[] = ['X', 'X', 'X', null, 'O', 'O', null, null, null];
  expect(checkWinner(board)).toBe('X');
});

test('game ends in draw', () => {
  const board: Player[] = ['X', 'O', 'X', 'X', 'O', 'O', 'O', 'X', 'X'];
  expect(checkWinner(board)).toBe('draw');
});

8.2 手动测试用例

场景 预期结果
X 连成一行 显示“玩家 X 获胜!”
棋盘填满无胜者 显示“平局!”
获胜后点击格子 无响应
点击“重新开始” 棋盘清空,X 先手

9. 构建与部署流程

9.1 开发阶段

npm install
npm start                  # 启动 Metro
# 在 DevEco Studio 中运行 harmony 项目

9.2 发布构建

npm run harmony            # 生成 bundle
# DevEco Studio → Build → Build Hap(s)

10. 扩展方向

尽管当前为经典井字棋,但可轻松演进为更复杂应用:

  1. AI 对战:集成 Minimax 算法,支持人机对战;
  2. 网络对战:利用 OpenHarmony 分布式能力,实现跨设备联机;
  3. 战绩统计:记录胜/负/平局次数;
  4. 主题切换:支持深色模式、节日皮肤;
  5. 动画效果:落子时添加缩放/淡入动画(使用 Animated API)。

11. 总结

本文成功实现了一个 逻辑严谨、交互流畅、界面美观 的井字棋游戏,并完整跑通了 React Native → OpenHarmony 的开发与部署流程。通过此项目,我们验证了:

  • RNOH 工具链已具备支撑交互式应用的能力;
  • React 的状态驱动模型非常适合游戏开发;
  • Flexbox 布局可高效构建响应式棋盘;
  • OpenHarmony 原生集成过程标准化且可靠。

该井字棋不仅是学习 RNOH 的理想起点,也为开发更复杂的策略游戏、教育应用或多人协作工具提供了坚实的技术基础。

Logo

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

更多推荐