目录

  1. 概述
  2. 游戏规则
  3. 核心功能
  4. 实战案例
  5. 编译过程详解
  6. 游戏扩展
  7. 最佳实践
  8. 常见问题

概述

本文档介绍如何在 Kotlin Multiplatform (KMP) 鸿蒙跨端开发中实现一个经典小游戏 - 井字棋 (Tic-Tac-Toe) 游戏。这个案例展示了如何使用 Kotlin 的集合操作、列表处理和递归算法来创建一个完整的游戏系统。通过 KMP,这个游戏可以无缝编译到 JavaScript,在 OpenHarmony 应用中运行。

游戏的特点

  • 经典玩法:人机对战,规则简单易懂
  • 算法应用:使用 Minimax 算法实现 AI 对手
  • 数据结构:使用列表和模式匹配处理棋盘状态
  • 跨端兼容:一份 Kotlin 代码可同时服务多个平台
  • 实时反馈:即时显示棋盘状态和游戏结果

游戏规则

基本规则

  1. 棋盘:3×3 的网格,共 9 个位置(编号 1-9)
  2. 玩家
    • 玩家 X:人类玩家
    • 电脑 O:AI 对手
  3. 获胜条件
    • 连成一行(横、竖、斜)的三个相同符号
    • 如果棋盘填满且无人获胜,则为平局
  4. 游戏流程
    • 玩家先手,输入 1-9 的数字表示落子位置
    • 电脑自动计算最优位置
    • 轮流落子直到游戏结束

棋盘布局

位置编号:
1 | 2 | 3
---------
4 | 5 | 6
---------
7 | 8 | 9

游戏示例:
X | O | X
---------
O | X | O
---------
  |   | X

游戏流程图

开始游戏
  ↓
初始化棋盘 (9 个空位)
  ↓
玩家输入移动 (1-9)
  ↓
检查移动是否有效
  ├→ 无效 → 重新输入
  └→ 有效 → 更新棋盘
  ↓
检查玩家是否获胜
  ├→ 获胜 → 游戏结束 (玩家赢)
  └→ 未获胜 → 继续
  ↓
检查棋盘是否满
  ├→ 满 → 游戏结束 (平局)
  └→ 未满 → 继续
  ↓
电脑计算最优移动
  ↓
更新棋盘
  ↓
检查电脑是否获胜
  ├→ 获胜 → 游戏结束 (电脑赢)
  └→ 未获胜 → 回到玩家输入

核心功能

1. 棋盘表示

Kotlin 代码

// 棋盘状态:0=空, 1=玩家X, 2=电脑O
// 使用可变列表存储棋盘状态,便于在游戏过程中更新
val board = MutableList(9) { 0 }

说明

  • 棋盘用一个包含 9 个元素的列表表示
  • 每个位置对应一个数字(0-8),对应棋盘位置 1-9
  • 0 表示空位,1 表示玩家 X,2 表示电脑 O
  • 使用 MutableList 可以在游戏进行中修改棋盘状态

2. 获胜模式定义

Kotlin 代码

// 定义所有可能的获胜模式
// 共 8 种:3 行 + 3 列 + 2 条对角线
val winPatterns = listOf(
    // 行
    listOf(0, 1, 2), listOf(3, 4, 5), listOf(6, 7, 8),
    // 列
    listOf(0, 3, 6), listOf(1, 4, 7), listOf(2, 5, 8),
    // 对角线
    listOf(0, 4, 8), listOf(2, 4, 6)
)

说明

  • 每个获胜模式是一个包含 3 个位置索引的列表
  • 如果这 3 个位置的值相同且不为 0,则该玩家获胜
  • 共 8 种获胜模式,覆盖所有可能的连线情况

3. 检查获胜函数

Kotlin 代码

fun checkWinner(board: List<Int>): Int {
    val winPatterns = listOf(
        listOf(0, 1, 2), listOf(3, 4, 5), listOf(6, 7, 8),
        listOf(0, 3, 6), listOf(1, 4, 7), listOf(2, 5, 8),
        listOf(0, 4, 8), listOf(2, 4, 6)
    )
    
    // 遍历所有获胜模式
    for (pattern in winPatterns) {
        val (a, b, c) = pattern
        // 检查这个模式的三个位置是否相同且不为空
        if (board[a] != 0 && board[a] == board[b] && board[b] == board[c]) {
            return board[a]  // 返回赢家 (1 或 2)
        }
    }
    return 0  // 没有赢家
}

说明

  • 遍历所有 8 种获胜模式
  • 对于每个模式,检查三个位置的值是否相同且不为 0
  • 如果找到获胜模式,返回赢家(1 或 2)
  • 如果没有找到,返回 0 表示游戏继续

4. Minimax AI 算法

Kotlin 代码

// Minimax 算法:通过递归评估所有可能的游戏状态,找到最优移动
fun minimax(board: MutableList<Int>, depth: Int, isMaximizing: Boolean): Int {
    val winner = checkWinner(board)
    
    // 基础情况:游戏已结束
    when {
        winner == 2 -> return 10 - depth      // 电脑赢(分数越高越好)
        winner == 1 -> return depth - 10      // 玩家赢(分数越低越好)
        board.all { it != 0 } -> return 0     // 平局
    }
    
    if (isMaximizing) {
        // 电脑的回合:最大化分数(选择最好的移动)
        var maxScore = Int.MIN_VALUE
        for (i in board.indices) {
            if (board[i] == 0) {
                // 尝试在这个位置放置电脑的棋子
                board[i] = 2
                val score = minimax(board, depth + 1, false)
                board[i] = 0  // 撤销移动
                maxScore = maxOf(maxScore, score)
            }
        }
        return maxScore
    } else {
        // 玩家的回合:最小化分数(选择最坏的移动)
        var minScore = Int.MAX_VALUE
        for (i in board.indices) {
            if (board[i] == 0) {
                // 尝试在这个位置放置玩家的棋子
                board[i] = 1
                val score = minimax(board, depth + 1, true)
                board[i] = 0  // 撤销移动
                minScore = minOf(minScore, score)
            }
        }
        return minScore
    }
}

说明

  • Minimax 算法原理:通过递归评估所有可能的游戏状态
  • 最大化阶段(电脑):选择分数最高的移动
  • 最小化阶段(玩家):假设玩家会选择分数最低的移动
  • 深度参数:用于评估获胜的快速性(越快获胜分数越高)
  • 回溯:尝试每个可能的移动后,撤销该移动以尝试其他选项

5. 找到最优移动

Kotlin 代码

// 为电脑找到最优的移动位置
fun findBestMove(board: MutableList<Int>): Int {
    var bestScore = Int.MIN_VALUE
    var bestMove = -1
    
    // 遍历所有空位
    for (i in board.indices) {
        if (board[i] == 0) {
            // 在这个位置放置电脑的棋子
            board[i] = 2
            // 使用 minimax 评估这个移动的分数
            val score = minimax(board, 0, false)
            // 撤销移动
            board[i] = 0
            
            // 选择分数最高的移动
            if (score > bestScore) {
                bestScore = score
                bestMove = i
            }
        }
    }
    return bestMove
}

说明

  • 遍历棋盘上的所有空位
  • 对每个空位,假设电脑在此落子
  • 使用 Minimax 算法评估该移动的分数
  • 选择分数最高的移动作为最优移动
  • 返回最优移动的位置索引

6. 棋盘可视化

Kotlin 代码

// 将棋盘状态转换为可读的字符串格式
fun visualizeBoard(board: List<Int>): String {
    val symbols = listOf(" ", "X", "O")  // 0->空, 1->X, 2->O
    val rows = board.chunked(3)  // 将 9 个元素分成 3 行
    
    return rows.mapIndexed { index, row ->
        // 将每行的元素用 " | " 分隔
        row.joinToString(" | ") { symbols[it] } +
        // 在行之间添加分隔线
        if (index < 2) "\n---------\n" else ""
    }.joinToString()
}

说明

  • 将棋盘列表转换为可视化的文本格式
  • chunked(3) 将 9 个元素分成 3 行
  • 每行用 " | " 分隔,行之间用 “---------” 分隔
  • 最终输出一个 3×3 的棋盘视图

实战案例

完整的游戏函数 (Kotlin)

Kotlin 源代码 (src/jsMain/kotlin/App.kt):

@OptIn(ExperimentalJsExport::class)
@JsExport
fun ticTacToeGame(inputMoves: String = "1 5 2 4 3 7 9"): String {
    // 第一步:解析玩家的移动
    // 将输入字符串按空格分割,转换为整数列表,并验证范围
    val moves = inputMoves.trim().split(" ")
        .filter { it.isNotEmpty() }           // 过滤空字符串
        .mapNotNull { it.toIntOrNull() }      // 转换为整数,无效的转换为 null
        .filter { it in 1..9 }                // 只保留 1-9 的有效位置
    
    // 检查是否有有效的移动
    if (moves.isEmpty()) {
        return "❌ 错误: 没有有效的移动\n请输入 1-9 之间的数字,用空格分隔"
    }
    
    // 第二步:初始化棋盘和游戏状态
    val board = MutableList(9) { 0 }  // 创建空棋盘
    var moveIndex = 0                  // 当前移动索引
    var result = "🎮 井字棋游戏开始!\n\n"  // 游戏结果字符串
    
    // 第三步:主游戏循环
    while (moveIndex < moves.size) {
        // 获取玩家的移动(转换为 0-8 的索引)
        val playerMove = moves[moveIndex] - 1
        
        // 检查移动是否有效
        if (playerMove < 0 || playerMove > 8 || board[playerMove] != 0) {
            result += "❌ 位置 ${playerMove + 1} 无效或已被占用\n"
            moveIndex++
            continue
        }
        
        // 玩家落子
        board[playerMove] = 1
        result += "第 ${moveIndex + 1} 轮:\n"
        result += "玩家落子于位置 ${playerMove + 1}\n"
        result += visualizeBoard(board) + "\n\n"
        
        // 检查玩家是否获胜
        if (checkWinner(board) == 1) {
            result += "🎉 玩家获胜!\n"
            return result
        }
        
        // 检查是否平局
        if (board.all { it != 0 }) {
            result += "🤝 平局!\n"
            return result
        }
        
        // 电脑计算最优移动
        val computerMove = findBestMove(board)
        if (computerMove != -1) {
            board[computerMove] = 2
            result += "电脑落子于位置 ${computerMove + 1}\n"
            result += visualizeBoard(board) + "\n\n"
            
            // 检查电脑是否获胜
            if (checkWinner(board) == 2) {
                result += "🤖 电脑获胜!\n"
                return result
            }
            
            // 检查是否平局
            if (board.all { it != 0 }) {
                result += "🤝 平局!\n"
                return result
            }
        }
        
        moveIndex++
    }
    
    return result + "游戏结束"
}

说明

  • @JsExport 注解使该函数可以被 JavaScript 调用
  • 函数接收一个字符串参数,包含玩家的移动序列
  • 返回一个格式化的字符串,包含完整的游戏过程和结果
  • 游戏循环处理玩家和电脑的轮流落子
  • 每一步都检查游戏是否结束(获胜或平局)

编译后的 JavaScript 代码

生成的 JavaScript 代码 (build/js/packages/hellokjs/kotlin/hellokjs.mjs):

// 编译后的 Kotlin 代码会转换为 JavaScript
// 以下是简化的示例,展示关键函数的 JavaScript 版本

export function ticTacToeGame(inputMoves = "1 5 2 4 3 7 9") {
    // 解析移动
    const moves = inputMoves
        .trim()
        .split(" ")
        .filter(m => m.length > 0)
        .map(m => parseInt(m))
        .filter(m => !isNaN(m) && m >= 1 && m <= 9);
    
    if (moves.length === 0) {
        return "❌ 错误: 没有有效的移动\n请输入 1-9 之间的数字,用空格分隔";
    }
    
    // 初始化棋盘
    const board = new Array(9).fill(0);
    let moveIndex = 0;
    let result = "🎮 井字棋游戏开始!\n\n";
    
    // 游戏循环
    while (moveIndex < moves.length) {
        const playerMove = moves[moveIndex] - 1;
        
        // 验证移动
        if (playerMove < 0 || playerMove > 8 || board[playerMove] !== 0) {
            result += `❌ 位置 ${playerMove + 1} 无效或已被占用\n`;
            moveIndex++;
            continue;
        }
        
        // 玩家落子
        board[playerMove] = 1;
        result += `${moveIndex + 1} 轮:\n`;
        result += `玩家落子于位置 ${playerMove + 1}\n`;
        result += visualizeBoard(board) + "\n\n";
        
        // 检查玩家是否获胜
        if (checkWinner(board) === 1) {
            result += "🎉 玩家获胜!\n";
            return result;
        }
        
        // 检查平局
        if (board.every(cell => cell !== 0)) {
            result += "🤝 平局!\n";
            return result;
        }
        
        // 电脑移动
        const computerMove = findBestMove(board);
        if (computerMove !== -1) {
            board[computerMove] = 2;
            result += `电脑落子于位置 ${computerMove + 1}\n`;
            result += visualizeBoard(board) + "\n\n";
            
            // 检查电脑是否获胜
            if (checkWinner(board) === 2) {
                result += "🤖 电脑获胜!\n";
                return result;
            }
            
            // 检查平局
            if (board.every(cell => cell !== 0)) {
                result += "🤝 平局!\n";
                return result;
            }
        }
        
        moveIndex++;
    }
    
    return result + "游戏结束";
}

// 辅助函数
function checkWinner(board) {
    const winPatterns = [
        [0, 1, 2], [3, 4, 5], [6, 7, 8],
        [0, 3, 6], [1, 4, 7], [2, 5, 8],
        [0, 4, 8], [2, 4, 6]
    ];
    
    for (const pattern of winPatterns) {
        const [a, b, c] = pattern;
        if (board[a] !== 0 && board[a] === board[b] && board[b] === board[c]) {
            return board[a];
        }
    }
    return 0;
}

function visualizeBoard(board) {
    const symbols = [" ", "X", "O"];
    const rows = [];
    for (let i = 0; i < 3; i++) {
        rows.push(board.slice(i * 3, i * 3 + 3)
            .map(cell => symbols[cell])
            .join(" | "));
    }
    return rows.join("\n---------\n");
}

function findBestMove(board) {
    let bestScore = -Infinity;
    let bestMove = -1;
    
    for (let i = 0; i < board.length; i++) {
        if (board[i] === 0) {
            board[i] = 2;
            const score = minimax(board, 0, false);
            board[i] = 0;
            
            if (score > bestScore) {
                bestScore = score;
                bestMove = i;
            }
        }
    }
    return bestMove;
}

function minimax(board, depth, isMaximizing) {
    const winner = checkWinner(board);
    
    if (winner === 2) return 10 - depth;
    if (winner === 1) return depth - 10;
    if (board.every(cell => cell !== 0)) return 0;
    
    if (isMaximizing) {
        let maxScore = -Infinity;
        for (let i = 0; i < board.length; i++) {
            if (board[i] === 0) {
                board[i] = 2;
                const score = minimax(board, depth + 1, false);
                board[i] = 0;
                maxScore = Math.max(maxScore, score);
            }
        }
        return maxScore;
    } else {
        let minScore = Infinity;
        for (let i = 0; i < board.length; i++) {
            if (board[i] === 0) {
                board[i] = 1;
                const score = minimax(board, depth + 1, true);
                board[i] = 0;
                minScore = Math.min(minScore, score);
            }
        }
        return minScore;
    }
}

说明

  • Kotlin 代码编译为 JavaScript 后,保持相同的逻辑
  • 使用 ES Module 格式,可以被其他模块导入
  • 包含完整的类型定义(.d.ts 文件)
  • 可以直接在浏览器或 Node.js 中运行

ArkTS 调用代码

在 OpenHarmony 应用中调用 (kmp_ceshiapp/entry/src/main/ets/pages/Index.ets):

import { ticTacToeGame } from './hellokjs';

@Entry
@Component
struct Index {
  @State message: string = '准备开始游戏';
  @State gameResult: string = '';
  @State playerMoves: string = '1 5 2 4 3 7 9';

  // 游戏初始化
  aboutToAppear(): void {
    this.startGame();
  }

  // 启动游戏函数
  startGame(): void {
    try {
      // 调用 Kotlin 编译的 JavaScript 函数
      const result: string = ticTacToeGame(this.playerMoves);
      this.gameResult = result;
      this.message = '✓ 游戏完成';
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : String(error);
      this.message = `✗ 错误: ${errorMessage}`;
    }
  }

  build() {
    Column() {
      // 顶部标题栏
      Row() {
        Text('KMP 鸿蒙跨端')
          .fontSize(16)
          .fontWeight(FontWeight.Bold)
          .fontColor(Color.White)

        Text('井字棋游戏')
          .fontSize(14)
          .fontColor(Color.White)
      }
      .width('100%')
      .height(50)
      .backgroundColor('#3b82f6')
      .padding({ left: 20, right: 20 })
      .alignItems(VerticalAlign.Center)
      .justifyContent(FlexAlign.SpaceBetween)

      // 游戏标题和状态
      Column() {
        Text('井字棋 (Tic-Tac-Toe)')
          .fontSize(20)
          .fontWeight(FontWeight.Bold)
          .fontColor('#1f2937')
        Text(this.message)
          .fontSize(13)
          .fontColor('#6b7280')
          .margin({ top: 5 })
      }
      .width('100%')
      .padding({ left: 20, right: 20, top: 20, bottom: 15 })
      .alignItems(HorizontalAlign.Start)

      // 输入框区域
      Column() {
        Text('输入移动序列 (1-9):')
          .fontSize(14)
          .fontWeight(FontWeight.Bold)
          .fontColor('#1f2937')
          .margin({ bottom: 8 })
        
        TextInput({ placeholder: '输入移动序列,用空格分隔...', text: this.playerMoves })
          .width('100%')
          .height(60)
          .padding(12)
          .border({ width: 1, color: '#d1d5db' })
          .borderRadius(6)
          .onChange((value: string) => {
            this.playerMoves = value
          })
        
        Button('开始游戏')
          .width('100%')
          .height(40)
          .margin({ top: 12 })
          .backgroundColor('#f59e0b')
          .fontColor(Color.White)
          .onClick(() => {
            this.startGame()
          })
      }
      .width('100%')
      .padding({ left: 16, right: 16, bottom: 16 })

      // 游戏结果显示区域
      Scroll() {
        Column() {
          if (this.gameResult) {
            Column() {
              // 使用 monospace 字体显示棋盘
              Text(this.gameResult)
                .fontSize(13)
                .fontFamily('monospace')
                .fontColor('#374151')
                .width('100%')
                .margin({ top: 10 })
            }
            .width('100%')
            .padding(16)
            .backgroundColor(Color.White)
            .border({ width: 1, color: '#e5e7eb' })
            .borderRadius(8)
            .margin({ bottom: 12 })
          }
        }
        .width('100%')
        .padding({ left: 16, right: 16 })
      }
      .layoutWeight(1)
      .width('100%')

      // 底部按钮区域
      Row() {
        Button('默认示例')
          .width('48%')
          .height(44)
          .backgroundColor('#10b981')
          .fontColor(Color.White)
          .fontSize(14)
          .onClick(() => {
            this.playerMoves = '1 5 2 4 3 7 9';
            this.startGame();
          })

        Button('清空')
          .width('48%')
          .height(44)
          .backgroundColor('#6b7280')
          .fontColor(Color.White)
          .fontSize(14)
          .onClick(() => {
            this.playerMoves = '';
            this.gameResult = '';
            this.message = '准备开始游戏';
          })
      }
      .width('100%')
      .padding({ left: 16, right: 16, bottom: 20 })
      .justifyContent(FlexAlign.SpaceBetween)
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#f9fafb')
  }
}

说明

  • import { ticTacToeGame } from './hellokjs' 导入编译后的 Kotlin 函数
  • @State 装饰器管理组件的状态(游戏结果、玩家输入等)
  • startGame() 方法调用 ticTacToeGame() 函数并处理结果
  • UI 包含输入框、按钮和结果显示区域
  • 使用 monospace 字体显示棋盘,保持格式对齐

使用示例

Kotlin 中的使用

// 示例 1:默认游戏
val result1 = ticTacToeGame()

// 示例 2:自定义移动
val result2 = ticTacToeGame("1 2 3 4 5 6 7")

// 示例 3:快速获胜
val result3 = ticTacToeGame("1 4 2 5 3")

JavaScript 中的使用

// 导入函数
import { ticTacToeGame } from './hellokjs.mjs';

// 调用函数
const result = ticTacToeGame("1 5 2 4 3 7 9");
console.log(result);

ArkTS 中的使用

// 在 startGame() 方法中调用
const result: string = ticTacToeGame(this.playerMoves);
this.gameResult = result;

编译过程详解

1. Kotlin 代码编译

编译命令

# 编译 Kotlin 代码为 JavaScript
./gradlew build

# 输出文件位置
build/js/packages/hellokjs/kotlin/hellokjs.mjs
build/js/packages/hellokjs/kotlin/hellokjs.d.ts

编译流程

  1. 源代码位置src/jsMain/kotlin/App.kt
  2. 编译工具:Kotlin/JS 编译器
  3. 输出格式:ES Module (.mjs 文件)
  4. 类型定义:TypeScript 声明文件 (.d.ts)

说明

  • Kotlin 代码使用 @JsExport 注解标记可导出的函数
  • 编译器将 Kotlin 代码转换为等效的 JavaScript 代码
  • 生成的代码可以在任何支持 ES Module 的环境中运行

2. 生成的 JavaScript 代码特点

文件结构

hellokjs.mjs (主文件)
├── 导出的函数
│   ├── ticTacToeGame()
│   ├── checkWinner()
│   ├── minimax()
│   ├── findBestMove()
│   └── visualizeBoard()
└── 内部辅助函数

hellokjs.d.ts (类型定义)
├── function ticTacToeGame(inputMoves?: string): string
├── function checkWinner(board: number[]): number
├── function minimax(board: number[], depth: number, isMaximizing: boolean): number
├── function findBestMove(board: number[]): number
└── function visualizeBoard(board: number[]): string

代码特点

  • ES Module 格式:使用 export 导出函数,便于模块化使用
  • 类型定义.d.ts 文件提供完整的 TypeScript 类型支持
  • 兼容性:可直接在 Node.js、浏览器或 ArkTS 中使用
  • 优化:编译器自动进行代码优化和压缩

3. 文件复制和替换

自动化脚本

# 使用 build-and-copy.bat 脚本自动编译并复制文件
d:\flutter_Obj\kmp_openharmony\build-and-copy.bat

脚本功能

  1. 编译:运行 gradlew build 编译 Kotlin 代码
  2. 验证:检查输出文件是否存在
  3. 复制:将文件复制到 ArkTS 项目目录

复制目标

源文件:
  build/js/packages/hellokjs/kotlin/hellokjs.mjs
  build/js/packages/hellokjs/kotlin/hellokjs.d.ts

目标位置:
  kmp_ceshiapp/entry/src/main/ets/pages/hellokjs.js
  kmp_ceshiapp/entry/src/main/ets/pages/hellokjs.d.ts

说明

  • .mjs 文件被重命名为 .js 以便在 ArkTS 中使用
  • .d.ts 文件保持不变,提供类型支持
  • 脚本自动处理文件覆盖,无需手动操作

4. 在 ArkTS 中调用

导入方式

// 导入编译后的 Kotlin 函数
import { ticTacToeGame } from './hellokjs';

// 调用游戏函数
const result = ticTacToeGame("1 5 2 4 3 7 9");
console.log(result);

调用流程

ArkTS 代码
    ↓
导入 hellokjs 模块
    ↓
调用 ticTacToeGame() 函数
    ↓
执行 Kotlin 编译的 JavaScript 代码
    ↓
返回游戏结果字符串
    ↓
在 UI 中显示结果

说明

  • ArkTS 是 OpenHarmony 的编程语言,基于 TypeScript
  • 可以直接导入和调用 JavaScript 模块
  • 类型定义文件提供代码补全和类型检查
  • 函数调用是同步的,立即返回结果

游戏扩展

1. 难度等级

enum class Difficulty {
    EASY,      // 随机移动
    MEDIUM,    // 部分最优移动
    HARD       // 完全最优移动 (Minimax)
}

fun ticTacToeGameWithDifficulty(
    inputMoves: String,
    difficulty: Difficulty
): String {
    // 根据难度选择不同的 AI 策略
    // ...
}

2. 多轮游戏

data class GameStats(
    val playerWins: Int,
    val computerWins: Int,
    val draws: Int
)

fun playMultipleGames(games: Int): GameStats {
    // 进行多轮游戏并统计结果
    // ...
}

3. 玩家 vs 玩家

fun ticTacToeGamePvP(
    player1Moves: String,
    player2Moves: String
): String {
    // 两个玩家对战
    // ...
}

最佳实践

1. 输入验证

  • 验证移动是否在 1-9 范围内
  • 检查位置是否已被占用
  • 处理无效输入

2. 性能优化

  • 使用 Alpha-Beta 剪枝优化 Minimax 算法
  • 缓存已计算的棋盘状态
  • 限制搜索深度

3. 代码组织

// 分离关注点
object TicTacToeRules {
    fun checkWinner(board: List<Int>): Int { /* ... */ }
    fun isValidMove(board: List<Int>, position: Int): Boolean { /* ... */ }
}

object TicTacToeAI {
    fun findBestMove(board: MutableList<Int>): Int { /* ... */ }
    fun minimax(board: MutableList<Int>, depth: Int, isMaximizing: Boolean): Int { /* ... */ }
}

object TicTacToeUI {
    fun visualizeBoard(board: List<Int>): String { /* ... */ }
    fun formatGameResult(board: List<Int>, winner: Int): String { /* ... */ }
}

4. 错误处理

try {
    val result = ticTacToeGame(userInput)
    // 处理结果
} catch (e: IllegalArgumentException) {
    println("输入格式错误: ${e.message}")
} catch (e: Exception) {
    println("游戏出错: ${e.message}")
}

常见问题

Q1: 为什么电脑总是赢或平局?

A: 这是因为使用了 Minimax 算法,电脑计算了所有可能的游戏状态,选择最优移动。这是正常的 - 井字棋是一个"已解决"的游戏,在双方都采用最优策略的情况下,结果总是平局。

Q2: 如何让电脑更容易被击败?

A: 可以实现不同的难度等级:

  • 简单:电脑随机选择
  • 中等:电脑有时做出最优移动
  • 困难:电脑总是做出最优移动

Q3: 如何扩展到 4×4 或 5×5 棋盘?

A: 需要修改:

  • 棋盘大小常量
  • 获胜模式列表
  • 输入验证范围
  • 可视化函数

Q4: 性能如何?

A: 对于 3×3 棋盘,Minimax 算法非常快(毫秒级)。对于更大的棋盘,需要使用 Alpha-Beta 剪枝或其他优化技术。

Q5: 如何在生产环境中使用?

A:

  1. 添加更多的错误处理
  2. 实现日志记录
  3. 添加单元测试
  4. 优化性能
  5. 考虑使用数据库存储游戏历史

总结

通过这个井字棋游戏案例,我们学到了:

  1. ✅ 如何使用 Kotlin 实现经典算法 (Minimax)
  2. ✅ 如何处理游戏状态和逻辑
  3. ✅ 如何使用 KMP 编译到 JavaScript
  4. ✅ 如何在 OpenHarmony 应用中集成 Kotlin 代码
  5. ✅ 如何进行输入验证和错误处理

这个案例展示了 Kotlin Multiplatform 的强大能力,一份代码可以在多个平台上运行,大大提高了开发效率。


相关资源


最后更新:2025年11月27日
作者:KMP 开发团队
版本:1.0

Logo

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

更多推荐