混合练习 —— 猜数字游戏

第 1 周 Day 7:混合练习 ------ 猜数字游戏

学习主题:综合练习(变量、空安全、if / when、List、for / while 循环)

建议时长:1 天(约 1.5-2 小时)

学习目标:独立完成一个完整的控制台猜数字游戏,综合运用第 1 周所学全部知识点

一、适合读者与学习目标

本文适合已完成 Kotlin 第 1 周前 6 天学习的零基础或初学者。

如果你已经学过 val / var、可空类型、if / when、List 和 for / while 循环,但还不太清楚如何把它们组合成一个完整程序,可以通过本文的三个递进版本,从简易到完善,一步步完成一个猜数字游戏。

阅读前需要掌握:

  • val / var 变量声明,String? 可空类型,?:?. 操作符
  • if / when 条件判断,is 类型检查和智能转换
  • List / MutableList 的基本操作(add、索引访问、遍历)
  • for 循环(in、indices、withIndex)和 while 循环

二、为什么练这个

猜数字游戏虽然简单,但它几乎覆盖了第 1 周的全部知识点:

  • 变量(val / var)保存目标数字、用户猜测、猜测次数
  • 可空类型 (String?、?:)处理用户的非数字输入
  • if / when 判断猜测是偏大还是偏小
  • MutableList 存储历史猜测记录
  • for 循环 在游戏结束后打印所有猜测
  • while 循环 让用户反复猜测直到猜对

把零散的知识点串成一个能跑的小项目,是检验学习效果最好的方式。

三、学前自检:第 1 周知识点回顾

在开始编码前,先快速回顾第 1 周学过的内容:

知识点 核心内容 自检
val / var val 只读,var 可变;优先用 val
基础类型 Int、Double、Boolean、String、Char
字符串模板 "$name""${表达式}"
Null 安全 String??.?:!!
if 表达式 if 能返回值,替代三元运算符
when 表达式 值匹配、范围匹配(in)、类型匹配(is)
智能转换 is 检查后自动转类型,val 局部变量可用
List listOf(不可变)、mutableListOf(可变)、[索引]、size
for 循环 for (item in list)indiceswithIndex..untildownTo
while 循环 while(先判断)、do-while(先执行)、break、continue
集合操作 filter、map、forEach、sorted、sum、average

如果某个知识点还不熟,可以先回去翻一下对应的 Day 笔记再开始。

四、版本一:最简版猜数字(5 分钟完成)

先用最少的知识点写出一个能跑的版本,建立信心。

kotlin 复制代码
import kotlin.random.Random

fun main() {
    val target = Random.nextInt(1, 101)
    var guess: Int

    println("=== 猜数字游戏 ===")
    println("我已经想好了一个 1~100 之间的数字,你来猜猜看。")

    while (true) {
        print("请输入你的猜测:")
        guess = readlnOrNull()?.toIntOrNull() ?: continue

        if (guess > target) {
            println("太大了,再小一点")
        } else if (guess < target) {
            println("太小了,再大一点")
        } else {
            println("恭喜!猜对了,数字就是 $target")
            break
        }
    }
}

涉及的知识点:

  • val target:用 val 保存不变的目标数字
  • var guess:用 var 保存每次变化的猜测
  • Random.nextInt(1, 101):生成 1~100 的随机整数
  • readlnOrNull()?.toIntOrNull() ?: continue:空安全处理用户输入,非数字时跳过
  • if / else if / else:判断大小关系
  • while (true) + break:无限循环 + 猜对退出

试着运行几次,感受程序的完整流程。

五、版本二:增加猜测次数和评语(10 分钟完成)

在版本一的基础上增加两个功能:

  1. 记录猜测次数
  2. 游戏结束时根据次数给出评语
kotlin 复制代码
import kotlin.random.Random

fun main() {
    val target = Random.nextInt(1, 101)
    var guess: Int
    var attempts = 0  // 猜测次数

    println("=== 猜数字游戏 ===")
    println("我已经想好了一个 1~100 之间的数字,你来猜猜看。")

    while (true) {
        print("请输入你的猜测:")
        guess = readlnOrNull()?.toIntOrNull() ?: continue
        attempts++

        when {
            guess > target -> println("太大了,再小一点")
            guess < target -> println("太小了,再大一点")
            else -> {
                println("恭喜!猜对了,数字就是 $target")
                println("你一共猜了 $attempts 次")
                break
            }
        }
    }

    // 根据猜测次数给出评语
    val comment = when (attempts) {
        1 -> "一发入魂!你是天才吧?"
        in 2..3 -> "运气非常好!"
        in 4..6 -> "表现不错,继续加油"
        in 7..10 -> "还可以,多练练会更好"
        else -> "数字范围只有 100 个,下次用二分法试试?"
    }
    println("评语:$comment")
}

新增知识点:

  • var attempts = 0attempts++:用 var 计数
  • when { ... } 无参数写法:替代 if-else if 链,更清晰
  • when (attempts) { 1 -> ... in 2..3 -> ... }:when 范围匹配 + 表达式返回值

六、版本三:完整版 ------ 历史记录 + 状态分析 + 重玩(15 分钟完成)

加入第 1 周全部知识点的综合运用:

kotlin 复制代码
import kotlin.random.Random

fun main() {
    var playAgain = true

    while (playAgain) {
        playOneRound()
        print("再来一局?(y/n):")
        val answer = readlnOrNull()
        playAgain = answer?.lowercase() == "y"
    }

    println("游戏结束,下次再见!")
}

fun playOneRound() {
    val target = Random.nextInt(1, 101)
    val history = mutableListOf<Int>()  // 猜测历史
    var guess: Int

    println("\n=== 猜数字游戏(1~100)===")

    while (true) {
        print("请输入你的猜测:")
        val input = readlnOrNull()

        // 空安全处理:输入为空或非数字时给出提示
        guess = input?.toIntOrNull() ?: run {
            println("请输入有效的数字!")
            continue
        }

        // 记录猜测
        history.add(guess)

        // 判断结果
        when {
            guess > target -> println("太大了,再小一点")
            guess < target -> println("太小了,再大一点")
            else -> {
                println("恭喜!猜对了,数字就是 $target")
                break
            }
        }
    }

    // 打印猜测历史
    println("\n--- 你的猜测记录 ---")
    for ((index, value) in history.withIndex()) {
        val direction = when {
            value > target -> "偏高 ${value - target}"
            value < target -> "偏低 ${target - value}"
            else -> "正确!"
        }
        println("第 ${index + 1} 次:$value($direction)")
    }

    // 统计信息
    val totalAttempts = history.size
    val highGuesses = history.count { it > target }
    val lowGuesses = history.count { it < target }

    println("\n--- 统计 ---")
    println("总猜测次数:$totalAttempts")
    println("偏大次数:$highGuesses")
    println("偏小次数:$lowGuesses")

    // 评语
    val comment = when {
        totalAttempts == 1 -> "一发入魂!"
        totalAttempts in 2..3 -> "运气非常好!"
        totalAttempts in 4..6 -> "表现不错"
        totalAttempts in 7..10 -> "还有提升空间"
        else -> "建议试试二分查找法:每次猜中间的数字"
    }
    println("评语:$comment")
}

运行示例:

text 复制代码
=== 猜数字游戏(1~100)===
请输入你的猜测:50
太小了,再大一点
请输入你的猜测:75
太大了,再小一点
请输入你的猜测:63
太大了,再小一点
请输入你的猜测:56
恭喜!猜对了,数字就是 56

--- 你的猜测记录 ---
第 1 次:50(偏低 6)
第 2 次:75(偏高 19)
第 3 次:63(偏高 7)
第 4 次:56(正确!)

--- 统计 ---
总猜测次数:4
偏大次数:2
偏小次数:1
评语:表现不错

再来一局?(y/n):n
游戏结束,下次再见!

版本三综合运用的知识点清单:

知识点 体现在哪里
val target、history、totalAttempts 等不变的量
var guess、playAgain 等会变化的量
String? + ?. + ?: readlnOrNull 返回可空,input?.toIntOrNull 安全转换
if 表达式 answer?.lowercase() == "y" 判断是否重玩
when 表达式 判断猜测大小、输出评语
when 无参数 when { guess > target -> ... }
MutableList history 存储猜测记录
list.count { } 统计偏大/偏小次数
for + withIndex 遍历打印猜测历史
while 循环 外层控制重玩、内层控制猜数字
break + continue 猜对退出、无效输入跳过
函数 playOneRound() 封装单局逻辑

七、自己动手:进阶挑战

完成版本三后,尝试以下 4 个增强功能。这些练习不再提供完整代码,请自己独立完成。

挑战 1:难度选择

游戏开始时让用户选择难度:

text 复制代码
请选择难度:
1. 简单(1~50,最多 10 次)
2. 中等(1~100,最多 7 次)
3. 困难(1~200,最多 5 次)

提示:用 when 根据选择设置 maxRangemaxAttempts 两个变量。

挑战 2:范围提示优化

每次猜测后,缩小提示范围:

text 复制代码
请输入你的猜测:50
50~100 之间(已排除 1~49)
请输入你的猜测:75
50~75 之间(已排除 76~100)

提示:用 var low = 1var high = maxRange 两个变量记录当前范围,每次猜测后更新它们。

挑战 3:排行榜

记录每局的猜测次数,游戏结束后用 for 循环打印排行榜:

text 复制代码
--- 排行榜 ---
第 1 局:4 次(评语:表现不错)
第 2 局:1 次(评语:一发入魂!)
第 3 局:8 次(评语:还有提升空间)

最少猜测次数:1 次(第 2 局)

提示:用 mutableListOf<Int>() 存储每局的猜测次数。

挑战 4:输入更多数字信息

如果用户输入的不是数字,给出更友好的提示:

text 复制代码
请输入你的猜测:abc
"abc" 不是有效数字,请输入 1~100 之间的整数。
请输入你的猜测:-5
请输入 1~100 之间的正整数!

提示:在 toIntOrNull() 之前或之后,额外判断数值是否在范围内。

八、完整版参考代码(含全部 4 个挑战)

下面是对照答案。建议先自己尝试完成挑战,遇到卡顿再参考。

kotlin 复制代码
import kotlin.random.Random
import kotlin.system.exitProcess

fun main() {
    val leaderboard = mutableListOf<Int>()  // 每局猜测次数
    var playAgain = true

    println("===== 猜数字游戏(完整版)=====")

    while (playAgain) {
        val (difficultyName, maxRange, maxAttempts) = selectDifficulty()
        val attempts = playOneRound(difficultyName, maxRange, maxAttempts)
        leaderboard.add(attempts)

        print("\n再来一局?(y/n):")
        playAgain = readlnOrNull()?.lowercase() == "y"
    }

    // 打印排行榜
    printLeaderboard(leaderboard)
    println("游戏结束,下次再见!")
}

fun selectDifficulty(): Triple<String, Int, Int> {
    println("\n请选择难度:")
    println("1. 简单(1~50,最多 10 次)")
    println("2. 中等(1~100,最多 7 次)")
    println("3. 困难(1~200,最多 5 次)")

    return when (readlnOrNull()?.toIntOrNull()) {
        1 -> Triple("简单", 50, 10)
        2 -> Triple("中等", 100, 7)
        3 -> Triple("困难", 200, 5)
        else -> {
            println("输入无效,默认选择中等难度")
            Triple("中等", 100, 7)
        }
    }
}

fun playOneRound(difficulty: String, maxRange: Int, maxAttempts: Int): Int {
    val target = Random.nextInt(1, maxRange + 1)
    val history = mutableListOf<Int>()
    var low = 1
    var high = maxRange
    var guess: Int

    println("\n--- $difficulty 模式(1~$maxRange,最多 $maxAttempts 次)---")

    while (history.size < maxAttempts) {
        print("[${history.size + 1}/$maxAttempts] 请输入你的猜测:")
        val input = readlnOrNull()

        // 挑战 4:更友好的输入验证
        guess = input?.toIntOrNull() ?: run {
            println("\"$input\" 不是有效数字,请输入 $low~$high 之间的整数。")
            continue
        }

        if (guess !in 1..maxRange) {
            println("请输入 1~$maxRange 之间的正整数!")
            continue
        }

        history.add(guess)

        when {
            guess > target -> {
                if (guess < high) high = guess - 1
                println("太大了,${low}~$high 之间")  // 挑战 2:范围提示
            }
            guess < target -> {
                if (guess > low) low = guess + 1
                println("太小了,${low}~$high 之间")  // 挑战 2:范围提示
            }
            else -> {
                println("恭喜!猜对了,数字就是 $target")
                printHistory(history, target)
                return history.size
            }
        }
    }

    // 次数用完了还没猜对
    println("次数用完了!正确答案是 $target")
    return history.size
}

fun printHistory(history: List<Int>, target: Int) {
    println("\n--- 猜测记录 ---")
    for ((index, value) in history.withIndex()) {
        val direction = when {
            value > target -> "偏高 ${value - target}"
            value < target -> "偏低 ${target - value}"
            else -> "正确!"
        }
        println("第 ${index + 1} 次:$value($direction)")
    }
}

fun printLeaderboard(leaderboard: List<Int>) {
    if (leaderboard.isEmpty()) return

    println("\n--- 排行榜 ---")
    for ((index, attempts) in leaderboard.withIndex()) {
        val comment = when {
            attempts == 1 -> "一发入魂!"
            attempts in 2..3 -> "运气非常好!"
            attempts in 4..6 -> "表现不错"
            else -> "还有提升空间"
        }
        println("第 ${index + 1} 局:$attempts 次($comment)")
    }

    val minAttempts = leaderboard.min()
    val bestGame = leaderboard.indexOf(minAttempts) + 1
    println("最佳记录:$minAttempts 次(第 $bestGame 局)")
}

九、常见错误

错误 1:readlnOrNull 后忘记处理 null

kotlin 复制代码
// ❌ 危险:如果用户直接按回车,input 为 null,toIntOrNull 可能出问题
val input = readlnOrNull()
val guess = input.toIntOrNull()  // input 是 String?,需要 ?.

解决:使用安全调用。

kotlin 复制代码
// ✅ 安全
val guess = readlnOrNull()?.toIntOrNull() ?: continue

错误 2:while 循环的 break 放错位置

kotlin 复制代码
// ❌ 无限循环:break 永远不会执行
while (true) {
    if (guess == target) {
        // 但 guess 在循环中没有被更新
    }
    break  // 放在了循环体内一定会执行的位置
}

解决:确保 break 在正确的条件分支内。

错误 3:在 for 循环中修改正在遍历的 List

kotlin 复制代码
// ❌ 运行时可能抛异常
for (item in history) {
    history.remove(item)
}

解决:用 history.toList() 创建副本再遍历,或者用 history.clear() 一次性清空。

错误 4:忘记 update 循环条件

kotlin 复制代码
var count = 0
while (count < 10) {
    println(count)
    // 忘记 count++,导致死循环
}

十、自测清单

完成 Day 7 后,你应该能做到:

  • 独立写出一个能运行的猜数字游戏(不参考代码)
  • 正确使用 readlnOrNull()?.toIntOrNull() ?: continue 处理输入
  • MutableList 记录动态数据
  • withIndex() 遍历并打印带序号的结果
  • list.count { } 做条件统计
  • 用 when 表达式输出不同评语
  • breakcontinue 控制循环流程
  • 将单局逻辑封装为独立函数
  • 能将第 1 周全部知识点融合到一个程序中

十一、今日总结

Day 7 是第一周的收尾练习日,目标不是学新知识,而是把前 6 天的内容"串成一条线":

复制代码
val / var → if / when → List → for / while → 函数 → 完整程序

你在这 1 天写完的猜数字游戏,实际上已经包含了第 1 周 80% 的知识点。如果顺利独立完成版本三并尝试了挑战题,说明第 1 周的基础已经扎实了。

下一步将进入 第 2 周:函数与类,学习如何把代码封装成可复用的函数、定义带有属性和方法的类。

十二、参考资料

相关推荐
前端不太难2 小时前
鸿蒙游戏 CI/CD:为什么你还在手动打包?
游戏·ci/cd·harmonyos
装杯让你飞起来啊2 小时前
Kotlin 条件判断 if / when 与智能转换 smart cast
开发语言·python·kotlin
pengyu2 小时前
【Kotlin 协程修仙录 · 金丹境 · 初阶】 | 并发艺术:async/await 与并发组合的优雅之道
android·kotlin
YJlio4 小时前
用女娲蒸馏 Mark Russinovich 排障思维:打造 Windows 桌面运维专家 Skill
运维·windows·飞书·ai办公·多维表格·飞书v7.63·飞书问卷
YJlio4 小时前
OpenClaw v2026.3.24 更新解析:Gateway 兼容、Teams SDK、Slack 交互、容器命令与升级避坑
windows·自动化运维·版本更新·ai agent·teams·openclaw·gateway slack
开开心心就好4 小时前
自动分类存储PPT素材的实用工具
科技·游戏·智能手机·电脑·powerpoint·sublime text·phpstorm
黄林晴5 小时前
重磅发布!KMP 双端订阅支付彻底封神,一套代码搞定 iOS+Android
android·kotlin
lbb 小魔仙5 小时前
Ubuntu 22.04 + Windows 本地部署 AI 大模型完全指南:Ollama + Python 调用实战(附国内加速配置)
人工智能·windows·python·ubuntu
la_vie_est_belle5 小时前
Pygame Studio——用Python自制的一款可视化游戏编辑器
python·游戏·编辑器·游戏引擎·pygame·pyside6·pygame-ce