目录

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

概述

本文档介绍如何在 Kotlin Multiplatform (KMP) 鸿蒙跨端开发中实现一个经典小游戏 - 石头剪刀布游戏。这个案例展示了如何使用 Kotlin 的集合操作、Map 数据结构和函数式编程来创建一个完整的游戏系统。通过 KMP,这个游戏可以无缝编译到 JavaScript,在 OpenHarmony 应用中运行。

游戏的特点

  • 经典玩法:人机对战,规则简单易懂
  • 数据结构应用:使用 Map 存储游戏规则
  • 函数式设计:使用 Lambda 实现游戏逻辑
  • 跨端兼容:一份 Kotlin 代码可同时服务多个平台
  • 实时反馈:即时显示每轮结果和最终统计

游戏规则

基本规则

  1. 游戏选项:石头、剪刀、布
  2. 胜负规则
    • 石头 > 剪刀(石头赢)
    • 剪刀 > 布(剪刀赢)
    • 布 > 石头(布赢)
    • 相同选择 = 平局
  3. 游戏流程
    • 玩家选择一个选项
    • 电脑随机选择一个选项
    • 比较结果并显示胜负
    • 重复多轮

游戏流程图

开始游戏
  ↓
玩家选择 (石头/剪刀/布)
  ↓
电脑选择 (随机)
  ↓
比较选择
  ├→ 相同 → 平局
  ├→ 玩家赢 → 玩家得分
  └→ 电脑赢 → 电脑得分
  ↓
显示结果
  ↓
继续下一轮或结束游戏

核心功能

1. 游戏规则 Map

val winRules = mapOf(
    "石头" to "剪刀",  // 石头赢剪刀
    "剪刀" to "布",    // 剪刀赢布
    "布" to "石头"     // 布赢石头
)

这个 Map 定义了每个选项能赢哪个选项。

2. 判断胜负函数

val determineWinner: (String, String) -> String = { player, computer ->
    when {
        player == computer -> "平局"
        winRules[player] == computer -> "玩家赢"
        else -> "电脑赢"
    }
}

这是一个 Lambda 函数,接收玩家和电脑的选择,返回游戏结果。

3. 格式化结果函数

val formatResult: (String, String, String) -> String = { player, computer, result ->
    val icon = when (result) {
        "玩家赢" -> "✅"
        "电脑赢" -> "❌"
        else -> "🤝"
    }
    "$icon 玩家: $player vs 电脑: $computer$result"
}

这个函数将游戏结果格式化为易读的字符串。

4. 统计函数

val playerWins = gameResults.count { it.contains("玩家赢") }
val computerWins = gameResults.count { it.contains("电脑赢") }
val draws = gameResults.count { it.contains("平局") }

使用集合操作统计各种结果的出现次数。


实战案例

案例:完整的石头剪刀布游戏

Kotlin 源代码
@OptIn(ExperimentalJsExport::class)
@JsExport
fun rockPaperScissorsGame(): String {
    // 定义游戏选项
    val choices = listOf("石头", "剪刀", "布")
    
    // 定义获胜规则
    val winRules = mapOf(
        "石头" to "剪刀",  // 石头赢剪刀
        "剪刀" to "布",    // 剪刀赢布
        "布" to "石头"     // 布赢石头
    )
    
    // 定义判断结果的函数
    val determineWinner: (String, String) -> String = { player, computer ->
        when {
            player == computer -> "平局"
            winRules[player] == computer -> "玩家赢"
            else -> "电脑赢"
        }
    }
    
    // 定义显示结果的函数
    val formatResult: (String, String, String) -> String = { player, computer, result ->
        val icon = when (result) {
            "玩家赢" -> "✅"
            "电脑赢" -> "❌"
            else -> "🤝"
        }
        "$icon 玩家: $player vs 电脑: $computer$result"
    }
    
    // 模拟游戏过程
    val playerMoves = listOf("石头", "布", "剪刀", "石头", "布")
    val computerMoves = listOf("剪刀", "石头", "剪刀", "布", "布")
    
    // 计算游戏结果
    val gameResults = playerMoves.zip(computerMoves).map { (player, computer) ->
        val result = determineWinner(player, computer)
        formatResult(player, computer, result)
    }
    
    // 统计胜负
    val playerWins = gameResults.count { it.contains("玩家赢") }
    val computerWins = gameResults.count { it.contains("电脑赢") }
    val draws = gameResults.count { it.contains("平局") }
    
    return "🎮 石头剪刀布游戏\n" +
           "━━━━━━━━━━━━━━━━━━━━━\n" +
           "总轮数: ${playerMoves.size}\n\n" +
           "游戏过程:\n" +
           gameResults.joinToString("\n") + "\n\n" +
           "━━━━━━━━━━━━━━━━━━━━━\n" +
           "玩家胜: $playerWins 次\n" +
           "电脑胜: $computerWins 次\n" +
           "平局: $draws 次\n" +
           "最终结果: " + when {
               playerWins > computerWins -> "🏆 玩家获胜!"
               computerWins > playerWins -> "🤖 电脑获胜!"
               else -> "🤝 平手!"
           }
}
编译后的 JavaScript 代码
function rockPaperScissorsGame() {
  // 定义游戏选项
  var choices = ['石头', '剪刀', '布'];
  
  // 定义获胜规则
  var winRules = {
    '石头': '剪刀',
    '剪刀': '布',
    '布': '石头'
  };
  
  // 定义判断结果的函数
  var determineWinner = function(player, computer) {
    if (player === computer) {
      return '平局';
    } else if (winRules[player] === computer) {
      return '玩家赢';
    } else {
      return '电脑赢';
    }
  };
  
  // 定义显示结果的函数
  var formatResult = function(player, computer, result) {
    var icon;
    if (result === '玩家赢') {
      icon = '✅';
    } else if (result === '电脑赢') {
      icon = '❌';
    } else {
      icon = '🤝';
    }
    return icon + ' 玩家: ' + player + ' vs 电脑: ' + computer + ' → ' + result;
  };
  
  // 模拟游戏过程
  var playerMoves = ['石头', '布', '剪刀', '石头', '布'];
  var computerMoves = ['剪刀', '石头', '剪刀', '布', '布'];
  
  // 计算游戏结果
  var gameResults = [];
  for (var i = 0; i < playerMoves.length; i++) {
    var player = playerMoves[i];
    var computer = computerMoves[i];
    var result = determineWinner(player, computer);
    gameResults.push(formatResult(player, computer, result));
  }
  
  // 统计胜负
  var playerWins = 0, computerWins = 0, draws = 0;
  for (var i = 0; i < gameResults.length; i++) {
    if (gameResults[i].indexOf('玩家赢') !== -1) playerWins++;
    else if (gameResults[i].indexOf('电脑赢') !== -1) computerWins++;
    else draws++;
  }
  
  // 确定最终结果
  var finalResult;
  if (playerWins > computerWins) {
    finalResult = '🏆 玩家获胜!';
  } else if (computerWins > playerWins) {
    finalResult = '🤖 电脑获胜!';
  } else {
    finalResult = '🤝 平手!';
  }
  
  return '🎮 石头剪刀布游戏\n' +
         '━━━━━━━━━━━━━━━━━━━━━\n' +
         '总轮数: ' + playerMoves.length + '\n\n' +
         '游戏过程:\n' +
         gameResults.join('\n') + '\n\n' +
         '━━━━━━━━━━━━━━━━━━━━━\n' +
         '玩家胜: ' + playerWins + ' 次\n' +
         '电脑胜: ' + computerWins + ' 次\n' +
         '平局: ' + draws + ' 次\n' +
         '最终结果: ' + finalResult;
}
ArkTS 调用代码
import { rockPaperScissorsGame } 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 = rockPaperScissorsGame();
      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')
  }
}

编译过程详解

Kotlin 到 JavaScript 的转换

Kotlin 特性 JavaScript 等价物
Map 数据结构 对象 (Object)
Lambda 函数 匿名函数
when 表达式 if-else 语句
List.zip() 数组配对
count() 函数 循环计数

关键转换点

  1. Map 转换:Kotlin Map 转换为 JavaScript 对象
  2. Lambda 表达式:转换为 JavaScript 函数
  3. 集合操作:转换为数组操作
  4. 字符串处理:保持功能一致

游戏扩展

扩展 1:添加难度级别

val easyMode = listOf("石头", "石头", "布")  // 电脑偏好
val normalMode = listOf("石头", "剪刀", "布")  // 随机
val hardMode = listOf("剪刀", "布", "石头")  // 克制玩家

扩展 2:添加玩家历史记录

data class GameRecord(val playerMove: String, val computerMove: String, val result: String)
val history = mutableListOf<GameRecord>()

扩展 3:添加排行榜

data class PlayerScore(val name: String, val wins: Int, val losses: Int, val draws: Int)
val leaderboard = mutableListOf<PlayerScore>()

扩展 4:添加连胜统计

var currentWinStreak = 0
var maxWinStreak = 0

最佳实践

1. 使用 Map 存储规则

// ✅ 好:使用 Map 存储游戏规则
val winRules = mapOf("石头" to "剪刀", "剪刀" to "布", "布" to "石头")

// ❌ 不好:使用多个 if 语句
if (player == "石头" && computer == "剪刀") { /* ... */ }
if (player == "剪刀" && computer == "布") { /* ... */ }

2. 使用 zip() 配对数据

// ✅ 好:使用 zip() 配对
val results = playerMoves.zip(computerMoves).map { (p, c) -> /* ... */ }

// ❌ 不好:使用索引循环
for (i in playerMoves.indices) {
    val p = playerMoves[i]
    val c = computerMoves[i]
}

3. 使用 count() 统计

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

// ❌ 不好:使用 for 循环
var wins = 0
for (result in results) {
    if (result.contains("玩家赢")) wins++
}

4. 使用 emoji 增加可读性

// ✅ 好:使用 emoji
"✅ 玩家赢"
"❌ 电脑赢"
"🤝 平局"

// ❌ 不好:没有视觉反馈
"Player wins"
"Computer wins"
"Draw"

常见问题

Q1: 如何实现真正的随机选择?

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

external fun jsRandom(): Double = definedExternally

fun getRandomChoice(): String {
    val choices = listOf("石头", "剪刀", "布")
    val index = (jsRandom() * choices.size).toInt()
    return choices[index]
}

Q2: 如何保存游戏历史?

A: 使用本地存储:

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

// 保存游戏记录
localStorage.setItem("gameHistory", history.toString())

Q3: 如何实现多人联网对战?

A: 使用 WebSocket:

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

val ws = WebSocket("ws://game-server.com")
ws.send("石头")

Q4: 如何优化游戏性能?

A:

  1. 避免不必要的对象创建
  2. 使用 inline 函数减少函数调用开销
  3. 使用 Sequence 处理大数据集

Q5: 如何添加音效?

A: 使用 Web Audio API:

external fun playSound(url: String)

// 游戏获胜时播放声音
if (playerWins > computerWins) {
    playSound("victory.mp3")
}

总结

关键要点

  • ✅ 使用 Map 存储游戏规则
  • ✅ 使用 Lambda 实现游戏逻辑
  • ✅ 使用集合操作处理游戏数据
  • ✅ 使用 zip() 配对数据
  • ✅ KMP 能无缝编译到 JavaScript

下一步

  1. 添加真正的随机选择
  2. 实现游戏历史记录
  3. 添加排行榜系统
  4. 实现网络多人对战
  5. 添加音效和动画

参考资源

Logo

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

更多推荐