KMP 实现鸿蒙跨端:Kotlin 小游戏 - 石头剪刀布游戏
本文介绍了使用Kotlin Multiplatform(KMP)开发跨端石头剪刀布游戏的方法。通过Map数据结构存储游戏规则,利用函数式编程实现游戏逻辑,包括胜负判断、结果格式化和统计功能。该游戏支持人机对战,采用经典胜负规则,并能通过KMP编译为JavaScript在OpenHarmony应用运行。案例展示了完整的Kotlin实现代码,包括游戏选项定义、胜负判断函数、结果格式化以及胜负统计功能,
·
目录
概述
本文档介绍如何在 Kotlin Multiplatform (KMP) 鸿蒙跨端开发中实现一个经典小游戏 - 石头剪刀布游戏。这个案例展示了如何使用 Kotlin 的集合操作、Map 数据结构和函数式编程来创建一个完整的游戏系统。通过 KMP,这个游戏可以无缝编译到 JavaScript,在 OpenHarmony 应用中运行。
游戏的特点
- 经典玩法:人机对战,规则简单易懂
- 数据结构应用:使用 Map 存储游戏规则
- 函数式设计:使用 Lambda 实现游戏逻辑
- 跨端兼容:一份 Kotlin 代码可同时服务多个平台
- 实时反馈:即时显示每轮结果和最终统计
游戏规则
基本规则
- 游戏选项:石头、剪刀、布
- 胜负规则:
- 石头 > 剪刀(石头赢)
- 剪刀 > 布(剪刀赢)
- 布 > 石头(布赢)
- 相同选择 = 平局
- 游戏流程:
- 玩家选择一个选项
- 电脑随机选择一个选项
- 比较结果并显示胜负
- 重复多轮
游戏流程图
开始游戏
↓
玩家选择 (石头/剪刀/布)
↓
电脑选择 (随机)
↓
比较选择
├→ 相同 → 平局
├→ 玩家赢 → 玩家得分
└→ 电脑赢 → 电脑得分
↓
显示结果
↓
继续下一轮或结束游戏
核心功能
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() 函数 | 循环计数 |
关键转换点
- Map 转换:Kotlin Map 转换为 JavaScript 对象
- Lambda 表达式:转换为 JavaScript 函数
- 集合操作:转换为数组操作
- 字符串处理:保持功能一致
游戏扩展
扩展 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:
- 避免不必要的对象创建
- 使用 inline 函数减少函数调用开销
- 使用 Sequence 处理大数据集
Q5: 如何添加音效?
A: 使用 Web Audio API:
external fun playSound(url: String)
// 游戏获胜时播放声音
if (playerWins > computerWins) {
playSound("victory.mp3")
}
总结
关键要点
- ✅ 使用 Map 存储游戏规则
- ✅ 使用 Lambda 实现游戏逻辑
- ✅ 使用集合操作处理游戏数据
- ✅ 使用 zip() 配对数据
- ✅ KMP 能无缝编译到 JavaScript
下一步
- 添加真正的随机选择
- 实现游戏历史记录
- 添加排行榜系统
- 实现网络多人对战
- 添加音效和动画
参考资源
更多推荐



所有评论(0)