第 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)、indices、withIndex、..、until、downTo |
□ |
| 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 分钟完成)
在版本一的基础上增加两个功能:
- 记录猜测次数
- 游戏结束时根据次数给出评语
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 = 0和attempts++:用 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 根据选择设置 maxRange 和 maxAttempts 两个变量。
挑战 2:范围提示优化
每次猜测后,缩小提示范围:
text
请输入你的猜测:50
50~100 之间(已排除 1~49)
请输入你的猜测:75
50~75 之间(已排除 76~100)
提示:用 var low = 1 和 var 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 表达式输出不同评语
- 用
break和continue控制循环流程 - 将单局逻辑封装为独立函数
- 能将第 1 周全部知识点融合到一个程序中
十一、今日总结
Day 7 是第一周的收尾练习日,目标不是学新知识,而是把前 6 天的内容"串成一条线":
val / var → if / when → List → for / while → 函数 → 完整程序
你在这 1 天写完的猜数字游戏,实际上已经包含了第 1 周 80% 的知识点。如果顺利独立完成版本三并尝试了挑战题,说明第 1 周的基础已经扎实了。
下一步将进入 第 2 周:函数与类,学习如何把代码封装成可复用的函数、定义带有属性和方法的类。