在这里插入图片描述

目录

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

概述

本文档介绍如何在 Kotlin Multiplatform (KMP) 鸿蒙跨端开发中实现一个掷骰子游戏。这个案例展示了如何使用 Kotlin 的数学运算、集合操作和统计功能来创建一个完整的游戏系统。通过 KMP,这个游戏可以无缝编译到 JavaScript,在 OpenHarmony 应用中运行。

游戏的特点

  • 数学运算:涉及骰子点数的比较和统计
  • 多轮游戏:支持多轮对战和总分统计
  • 数据分析:计算平均值、总分和胜负统计
  • 跨端兼容:一份 Kotlin 代码可同时服务多个平台
  • 实时反馈:即时显示每轮结果和详细统计

游戏规则

基本规则

  1. 骰子范围:1-6 点
  2. 游戏流程
    • 玩家掷骰子
    • 电脑掷骰子
    • 比较点数大小
    • 记录胜负
  3. 胜负判定
    • 点数大者获胜
    • 点数相同为平局
  4. 计分方式
    • 单轮:直接比较点数
    • 总分:累计所有轮次的点数

游戏流程图

开始游戏
  ↓
玩家掷骰子 (1-6)
  ↓
电脑掷骰子 (1-6)
  ↓
比较点数
  ├→ 玩家点数大 → 玩家赢
  ├→ 电脑点数大 → 电脑赢
  └→ 点数相同 → 平局
  ↓
记录结果和总分
  ↓
继续下一轮或结束游戏
  ↓
统计总分和平均值

核心功能

1. 骰子点数定义

val diceValues = listOf(1, 2, 3, 4, 5, 6)

代码说明:

这段代码定义了骰子的所有可能点数。使用 listOf() 创建一个不可变列表,包含 1 到 6 的所有点数。这个列表用于验证掷骰子的结果是否有效,或者在需要时随机选择一个点数。

2. 比较函数

val compareRolls: (Int, Int) -> String = { player, computer ->
    when {
        player > computer -> "玩家赢"
        computer > player -> "电脑赢"
        else -> "平局"
    }
}

代码说明:

这是一个 Lambda 函数,用于比较玩家和电脑的掷骰子结果。函数接收两个整数参数(玩家点数和电脑点数),返回一个字符串表示结果。使用 when 表达式进行条件判断:如果玩家点数大于电脑,返回"玩家赢";如果电脑点数大于玩家,返回"电脑赢";否则返回"平局"。这个函数是游戏的核心逻辑。

3. 格式化函数

val formatRound: (Int, Int, Int, String) -> String = { round, player, computer, result ->
    val icon = when (result) {
        "玩家赢" -> "🎯"
        "电脑赢" -> "🎲"
        else -> "⚖️"
    }
    "$icon${round}轮: 玩家[$player] vs 电脑[$computer] → $result"
}

代码说明:

这是一个 Lambda 函数,用于格式化每轮游戏结果为易读的字符串。接收四个参数:轮数、玩家点数、电脑点数和比较结果。首先根据结果选择相应的图标:玩家赢显示"🎯",电脑赢显示"🎲",平局显示"⚖️"。然后返回一个格式化的字符串,包含图标、轮数、双方点数和结果。这个函数用于生成游戏过程的日志。

4. 统计函数

val playerScore = playerRolls.sum()           // 总分
val playerAverage = playerScore / playerRolls.size  // 平均值
val playerWins = rounds.count { it.contains("玩家赢") }  // 胜数

代码说明:

这段代码展示了如何使用集合操作进行游戏统计。playerRolls.sum() 计算玩家所有轮次的总分。playerScore / playerRolls.size 计算平均每轮的得分。rounds.count { it.contains("玩家赢") } 统计玩家赢的轮数,通过计数包含"玩家赢"的字符串。这些统计数据用于生成游戏报告。


实战案例

案例:完整的掷骰子游戏

Kotlin 源代码
@OptIn(ExperimentalJsExport::class)
@JsExport
fun diceTossingGame(): String {
    // 定义骰子点数
    val diceValues = listOf(1, 2, 3, 4, 5, 6)
    
    // 模拟多次掷骰子
    val playerRolls = listOf(4, 2, 6, 3, 5, 1, 4, 6)
    val computerRolls = listOf(3, 5, 2, 6, 4, 2, 5, 3)
    
    // 定义比较函数
    val compareRolls: (Int, Int) -> String = { player, computer ->
        when {
            player > computer -> "玩家赢"
            computer > player -> "电脑赢"
            else -> "平局"
        }
    }
    
    // 定义格式化函数
    val formatRound: (Int, Int, Int, String) -> String = { round, player, computer, result ->
        val icon = when (result) {
            "玩家赢" -> "🎯"
            "电脑赢" -> "🎲"
            else -> "⚖️"
        }
        "$icon${round}轮: 玩家[$player] vs 电脑[$computer] → $result"
    }
    
    // 计算每轮结果
    val rounds = playerRolls.zip(computerRolls).mapIndexed { index, (player, computer) ->
        val result = compareRolls(player, computer)
        formatRound(index + 1, player, computer, result)
    }
    
    // 统计总分
    val playerScore = playerRolls.sum()
    val computerScore = computerRolls.sum()
    val playerWins = rounds.count { it.contains("玩家赢") }
    val computerWins = rounds.count { it.contains("电脑赢") }
    val draws = rounds.count { it.contains("平局") }
    
    // 计算平均值
    val playerAverage = playerScore / playerRolls.size
    val computerAverage = computerScore / computerRolls.size
    
    return "🎲 掷骰子游戏\n" +
           "━━━━━━━━━━━━━━━━━━━━━\n" +
           "总轮数: ${playerRolls.size}\n\n" +
           "游戏过程:\n" +
           rounds.joinToString("\n") + "\n\n" +
           "━━━━━━━━━━━━━━━━━━━━━\n" +
           "统计数据:\n" +
           "玩家总分: $playerScore (平均: $playerAverage)\n" +
           "电脑总分: $computerScore (平均: $computerAverage)\n\n" +
           "轮数统计:\n" +
           "玩家胜: $playerWins 轮\n" +
           "电脑胜: $computerWins 轮\n" +
           "平局: $draws 轮\n\n" +
           "最终结果: " + when {
               playerScore > computerScore -> "🏆 玩家获胜!总分更高"
               computerScore > playerScore -> "🤖 电脑获胜!总分更高"
               else -> "🤝 平手!总分相同"
           }
}

代码说明:

这是掷骰子游戏的完整 Kotlin 实现。函数使用 @JsExport 装饰器将其导出为 JavaScript 可调用的函数。首先定义骰子点数列表和模拟的掷骰子结果。定义比较函数和格式化函数。使用 zip() 配对玩家和电脑的掷骰子结果,然后使用 mapIndexed 遍历每一对,调用比较函数获取结果,格式化输出。统计总分、平均值和胜负数。最后根据总分确定最终胜者,返回完整的游戏过程和统计数据。

编译后的 JavaScript 代码
function diceTossingGame() {
  // 定义骰子点数
  var diceValues = [1, 2, 3, 4, 5, 6];
  
  // 模拟多次掷骰子
  var playerRolls = [4, 2, 6, 3, 5, 1, 4, 6];
  var computerRolls = [3, 5, 2, 6, 4, 2, 5, 3];
  
  // 定义比较函数
  var compareRolls = function(player, computer) {
    if (player > computer) {
      return '玩家赢';
    } else if (computer > player) {
      return '电脑赢';
    } else {
      return '平局';
    }
  };
  
  // 定义格式化函数
  var formatRound = function(round, player, computer, result) {
    var icon;
    if (result === '玩家赢') {
      icon = '🎯';
    } else if (result === '电脑赢') {
      icon = '🎲';
    } else {
      icon = '⚖️';
    }
    return icon + ' 第' + round + '轮: 玩家[' + player + '] vs 电脑[' + computer + '] → ' + result;
  };
  
  // 计算每轮结果
  var rounds = [];
  for (var i = 0; i < playerRolls.length; i++) {
    var player = playerRolls[i];
    var computer = computerRolls[i];
    var result = compareRolls(player, computer);
    rounds.push(formatRound(i + 1, player, computer, result));
  }
  
  // 统计总分
  var playerScore = 0, computerScore = 0;
  for (var i = 0; i < playerRolls.length; i++) {
    playerScore += playerRolls[i];
    computerScore += computerRolls[i];
  }
  
  var playerWins = 0, computerWins = 0, draws = 0;
  for (var i = 0; i < rounds.length; i++) {
    if (rounds[i].indexOf('玩家赢') !== -1) playerWins++;
    else if (rounds[i].indexOf('电脑赢') !== -1) computerWins++;
    else draws++;
  }
  
  // 计算平均值
  var playerAverage = Math.floor(playerScore / playerRolls.length);
  var computerAverage = Math.floor(computerScore / computerRolls.length);
  
  // 确定最终结果
  var finalResult;
  if (playerScore > computerScore) {
    finalResult = '🏆 玩家获胜!总分更高';
  } else if (computerScore > playerScore) {
    finalResult = '🤖 电脑获胜!总分更高';
  } else {
    finalResult = '🤝 平手!总分相同';
  }
  
  return '🎲 掷骰子游戏\n' +
         '━━━━━━━━━━━━━━━━━━━━━\n' +
         '总轮数: ' + playerRolls.length + '\n\n' +
         '游戏过程:\n' +
         rounds.join('\n') + '\n\n' +
         '━━━━━━━━━━━━━━━━━━━━━\n' +
         '统计数据:\n' +
         '玩家总分: ' + playerScore + ' (平均: ' + playerAverage + ')\n' +
         '电脑总分: ' + computerScore + ' (平均: ' + computerAverage + ')\n\n' +
         '轮数统计:\n' +
         '玩家胜: ' + playerWins + ' 轮\n' +
         '电脑胜: ' + computerWins + ' 轮\n' +
         '平局: ' + draws + ' 轮\n\n' +
         '最终结果: ' + finalResult;
}

代码说明:

这是 Kotlin 代码编译到 JavaScript 后的结果。可以看到 Kotlin 的语言特性被转换为 JavaScript 等价物:listOf() 变成数组字面量,sum() 变成循环求和,count() 变成条件计数循环,when 表达式变成 if-else 语句。虽然编译后的代码看起来不同,但它保留了原始 Kotlin 代码的逻辑。使用 ES Module 格式,可以被其他模块导入。包含完整的类型定义(.d.ts 文件),提供 TypeScript 支持。可以直接在浏览器或 Node.js 中运行。

ArkTS 调用代码
import { diceTossingGame } from './hellokjs';

@Entry
@Component
struct Index {
  @State message: string = '加载中...';
  @State results: string[] = [];
  @State caseTitle: string = '小游戏 - 掷骰子游戏';

  aboutToAppear(): void {
    this.loadResults();
  }

  loadResults(): void {
    try {
      // 调用 Kotlin 编译的 JavaScript 函数
      const gameResult = diceTossingGame();
      this.results = [gameResult];
      this.message = '✓ 游戏已加载';
    } catch (error) {
      this.message = `✗ 错误: ${error}`;
    }
  }

  build() {
    Column() {
      // 顶部标题栏
      Row() {
        Text('KMP 鸿蒙跨端')
          .fontSize(16)
          .fontWeight(FontWeight.Bold)
          .fontColor(Color.White)
        Spacer()
        Text('Kotlin 案例')
          .fontSize(14)
          .fontColor(Color.White)
      }
      .width('100%')
      .height(50)
      .backgroundColor('#3b82f6')
      .padding({ left: 20, right: 20 })
      .alignItems(VerticalAlign.Center)
      .justifyContent(FlexAlign.SpaceBetween)

      // 案例标题
      Column() {
        Text(this.caseTitle)
          .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)

      // 结果显示区域
      Scroll() {
        Column() {
          ForEach(this.results, (result: string) => {
            Column() {
              Text(result)
                .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('#3b82f6')
          .fontColor(Color.White)
          .fontSize(14)
          .onClick(() => {
            this.loadResults();
          })

        Button('返回')
          .width('48%')
          .height(44)
          .backgroundColor('#6b7280')
          .fontColor(Color.White)
          .fontSize(14)
          .onClick(() => {
            // 返回操作
          })
      }
      .width('100%')
      .padding({ left: 16, right: 16, bottom: 20 })
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#f9fafb')
  }
}

代码说明:

这是 OpenHarmony ArkTS 页面的完整实现,展示了如何集成和调用 Kotlin 编译生成的游戏函数。首先通过 import 语句从 ./hellokjs 模块导入 diceTossingGame 函数。页面使用 @Entry@Component 装饰器定义为可入口的组件。定义了三个响应式状态变量:message 显示操作状态,results 存储游戏结果,caseTitle 显示标题。aboutToAppear() 生命周期钩子在页面加载时调用 loadResults() 进行初始化。loadResults() 方法调用 Kotlin 函数进行游戏,将结果存储在 results 数组中,并更新 message 显示状态。使用 try-catch 块捕获异常。build() 方法定义了完整的 UI 布局,包括顶部标题栏、游戏标题、结果显示区域和底部按钮区域。使用 monospace 字体显示游戏过程,保持格式对齐。


编译过程详解

Kotlin 到 JavaScript 的转换

Kotlin 特性 JavaScript 等价物
List.sum() 数组求和循环
List.average() 数组平均值计算
mapIndexed() 带索引的数组映射
count() 条件计数
when 表达式 if-else 语句

关键转换点

  1. 数学运算:直接转换为 JavaScript 运算
  2. 集合操作:转换为数组操作
  3. Lambda 表达式:转换为 JavaScript 函数
  4. 字符串处理:保持功能一致

游戏扩展

扩展 1:添加多个骰子

val multiDice = listOf(
    listOf(4, 2, 6),  // 玩家掷3个骰子
    listOf(3, 5, 2)   // 电脑掷3个骰子
)
val playerTotal = multiDice[0].sum()
val computerTotal = multiDice[1].sum()

代码说明:

这段代码展示了如何支持多个骰子。使用嵌套列表存储多个骰子的结果:外层列表包含玩家和电脑的结果,内层列表包含每个玩家掷的多个骰子的点数。使用 sum() 计算每个玩家的总分。这允许实现更复杂的游戏规则。

扩展 2:添加特殊规则

// 豹子(三个相同数字)
val isPair = playerRolls.distinct().size == 1

// 顺子(连续数字)
val isStraight = playerRolls.sorted() == (playerRolls.min()..playerRolls.max()).toList()

代码说明:

这段代码展示了如何添加特殊规则。豹子检测:使用 distinct() 去重,如果去重后大小为 1,说明所有数字相同。顺子检测:先排序,然后与从最小值到最大值的范围比较,如果相等则是顺子。这些特殊规则可以增加游戏的复杂性和趣味性。

扩展 3:添加赌注系统

data class Bet(val amount: Int, val odds: Double)
var playerBalance = 1000
val currentBet = Bet(100, 2.0)

代码说明:

这段代码展示了如何添加赌注系统。定义一个数据类 Bet 存储赌注信息:赌注金额和赔率。维护玩家的余额。创建当前赌注对象,指定赌注金额为 100,赔率为 2.0。这允许实现更真实的赌博游戏体验。

扩展 4:添加排行榜

data class PlayerRecord(val name: String, val totalScore: Int, val wins: Int)
val leaderboard = mutableListOf<PlayerRecord>()

代码说明:

这段代码展示了如何添加排行榜系统。定义一个数据类 PlayerRecord 存储玩家记录:玩家名字、总分和胜数。创建一个可变列表存储所有玩家的记录。这允许跟踪多个玩家的成绩,实现排行榜功能。


最佳实践

1. 使用 sum() 计算总分

// ✅ 好:使用 sum() 函数
val total = playerRolls.sum()

// ❌ 不好:使用 for 循环
var total = 0
for (roll in playerRolls) {
    total += roll
}

代码说明:

这个示例对比了两种计算总分的方法。第一种方法使用 sum() 函数,简洁高效,一行代码完成。第二种方法使用 for 循环手动累加,代码冗长且容易出错。最佳实践是:使用 sum() 进行求和操作。

2. 使用 mapIndexed() 获取索引

// ✅ 好:使用 mapIndexed()
val rounds = playerRolls.zip(computerRolls).mapIndexed { index, (p, c) -> /* ... */ }

// ❌ 不好:使用 indices
for (i in playerRolls.indices) {
    val p = playerRolls[i]
    val c = computerRolls[i]
}

代码说明:

这个示例对比了两种获取索引和元素的方法。第一种方法使用 zip() 配对两个列表,然后使用 mapIndexed() 获得索引和配对元素,代码简洁。第二种方法使用 for 循环和索引访问,代码冗长。最佳实践是:当需要索引和元素时,使用 mapIndexed()

3. 使用 count() 统计

// ✅ 好:使用 count()
val wins = rounds.count { it.contains("玩家赢") }

// ❌ 不好:使用 filter().size
val wins = rounds.filter { it.contains("玩家赢") }.size

代码说明:

这个示例对比了两种统计满足条件元素的方法。第一种方法使用 count() 直接计数,高效且简洁。第二种方法先用 filter() 过滤,再获取大小,需要创建中间列表,效率较低。最佳实践是:使用 count() 进行条件计数。

4. 使用整数除法计算平均值

// ✅ 好:使用整数除法
val average = total / count

// ❌ 不好:使用浮点数
val average = (total.toDouble() / count).toInt()

代码说明:

这个示例对比了两种计算平均值的方法。第一种方法直接使用整数除法,简洁高效。第二种方法先转换为浮点数,进行浮点除法,再转换回整数,过程复杂且效率低。最佳实践是:当只需要整数平均值时,直接使用整数除法。


常见问题

Q1: 如何实现真正的随机掷骰子?

A: 在 Kotlin/JS 中,可以使用 JavaScript 的 Math.random()

external fun jsRandom(): Double = definedExternally

fun rollDice(): Int {
    return (jsRandom() * 6).toInt() + 1
}

代码说明:

这段代码展示了如何实现真正的随机掷骰子。使用 external 关键字声明一个外部函数 jsRandom(),它调用 JavaScript 的 Math.random() 方法。rollDice() 函数生成 0 到 1 之间的随机数,乘以 6 得到 0 到 6 之间的数,转换为整数后加 1,得到 1 到 6 之间的随机数。

Q2: 如何处理多个骰子的情况?

A: 使用嵌套列表或数据类:

data class DiceRoll(val rolls: List<Int>) {
    val total: Int get() = rolls.sum()
    val average: Int get() = total / rolls.size
}

代码说明:

这段代码展示了如何处理多个骰子。定义一个数据类 DiceRoll 存储多个骰子的结果列表。使用计算属性 total 计算总分,average 计算平均值。这个设计使代码更清晰,易于扩展。

Q3: 如何实现概率分析?

A: 计算每个结果出现的频率:

val frequency = rounds.groupingBy { it }.eachCount()
val probability = frequency.mapValues { (_, count) -> count.toDouble() / rounds.size }

代码说明:

这段代码展示了如何进行概率分析。使用 groupingBy() 按结果分组,然后使用 eachCount() 计算每个结果出现的次数。使用 mapValues() 将计数转换为概率,即每个结果出现的次数除以总轮数。这允许分析游戏的概率分布。

Q4: 如何保存游戏历史?

A: 使用本地存储:

external object localStorage {
    fun setItem(key: String, value: String)
    fun getItem(key: String): String?
}

localStorage.setItem("gameHistory", rounds.toString())

代码说明:

这段代码展示了如何保存游戏历史。定义一个 external object 来访问浏览器的 localStorage API。使用 setItem() 方法将游戏轮次信息转换为字符串并保存。使用 getItem() 方法可以读取保存的游戏历史。这允许玩家在刷新页面后查看历史记录。

Q5: 如何实现网络对战?

A: 使用 WebSocket:

external class WebSocket(url: String) {
    fun send(data: String)
    var onmessage: ((String) -> Unit)?
}

val ws = WebSocket("ws://game-server.com")
ws.send("roll:${rollDice()}")

代码说明:

这段代码展示了如何实现网络对战。定义一个 external class 来访问浏览器的 WebSocket API。创建一个 WebSocket 连接到游戏服务器。使用 send() 方法发送掷骰子结果到服务器。使用 onmessage 回调处理来自服务器的消息。这允许实现实时的网络对战功能。


总结

关键要点

  • ✅ 使用 sum() 计算总分
  • ✅ 使用 mapIndexed() 处理索引
  • ✅ 使用 count() 统计结果
  • ✅ 使用 zip() 配对数据
  • ✅ KMP 能无缝编译到 JavaScript

下一步

  1. 实现真正的随机掷骰子
  2. 添加多骰子支持
  3. 实现概率分析
  4. 添加游戏历史记录
  5. 实现网络对战

参考资源

Logo

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

更多推荐