用字典搭一个地牢,用循环让你探索,用条件判断决定生死
一、你还记得小时候的”选故事”书吗
有一种书,读到一半会问你:主角面前有两条路,走左边翻到第30页,走右边翻到第57页。不同的选择,通向不同的结局。
今天我们要做的,就是这种书的程序版——文字冒险游戏。
程序会描述你身处的场景,给你几个选项。你输入选择,故事往前走。选对了找到宝藏,选错了掉进陷阱。而且这次不是小打小闹:我们要有多个结局、物品系统、存档功能。
先说清楚:这一篇不学任何新东西。但它会把前18篇学过的东西全用上——字典、循环、条件判断、函数、变量、文件操作、错误处理。这是目前为止最综合的一个项目。
二、游戏设计:地牢探险
先当一回游戏策划。我们的地牢大概长这样:
[入口]
/
[左通道] [右通道]
/ |
[宝箱房] [陷阱房] [守卫]
| | |
[宝藏室] (受伤) (需要钥匙)
- 玩家从入口出发
- 每个场景有描述和若干选项
- 有些场景会让你受伤(生命值减少)
- 有些场景给物品(比如钥匙)
- 有些路需要特定物品才能走
- 最后通向不同的结局:找到宝藏(赢)、逃出地牢(平局)、生命值归零(输)
这个游戏的核心数据结构,就是一个大字典:场景名做键,场景信息做值。每个场景又是一个小字典,装着描述和选项。
字典套字典——第八篇学的东西,今天要大用特用了。
三、先跟AI聊
你的提示词:
用Python写一个文字冒险游戏”地牢探险”。用字典存储所有场景数据,每个场景有描述文字和若干选项,选项指向下一个场景。玩家有生命值(初始100),某些场景会扣血,生命值为0游戏结束。玩家从入口开始,通过选择在场景间移动。要有多个结局(找到宝藏、逃出地牢、死亡)。用while循环、条件判断、字典实现,加上详细的中文注释。
AI会给你一段代码。因为这个游戏数据比较多,AI给的版本可能结构不太一样,这很正常。下面是我们自己的版本,逻辑更清晰,我们逐行来读。
四、基础版代码
# ========== 游戏数据:所有场景存在一个大字典里 ==========
场景数据 = {
"入口": {
"描述": "你举着火把,站在一个阴森的地牢入口。前方分出两条通道,冷风从左边吹来,右边隐约有水声。",
"选项": {
"1": ("走向左边的冷风通道", "左通道"),
"2": ("走向右边的水声通道", "右通道")
}
},
"左通道": {
"描述": "通道越来越窄,墙壁上结着冰霜。你看到前方有一扇木门,门后似乎有微光。",
"选项": {
"1": ("推开木门", "宝箱房"),
"2": ("退回入口", "入口")
}
},
"右通道": {
"描述": "地面湿滑,你差点摔倒。通道尽头站着一个石头守卫,眼睛闪着红光!",
"选项": {
"1": ("悄悄绕过守卫", "陷阱房"),
"2": ("退回入口", "入口")
}
},
"宝箱房": {
"描述": "房间里放着一个落满灰尘的宝箱。你打开它——里面是一把金钥匙!(获得物品:金钥匙)",
"选项": {
"1": ("拿着钥匙继续深入", "宝藏室"),
"2": ("退回左通道", "左通道")
}
},
"陷阱房": {
"描述": "地板突然塌陷!你摔进坑里,被尖刺划伤。(生命值-30)",
"选项": {
"1": ("爬起来继续走", "宝藏室"),
"2": ("退回右通道", "右通道")
}
},
"宝藏室": {
"描述": "地牢的最深处,堆满了金币和珠宝!你发财了!",
"选项": {} # 空选项 = 结局场景
}
}
# ========== 游戏状态:用变量记住玩家的情况 ==========
当前位置 = "入口" # 玩家在哪个场景
生命值 = 100 # 玩家的生命值
print("=== 地牢探险 ===")
print("你的每一个选择,都会改变命运。n")
# ========== 游戏主循环 ==========
while True:
当前场景 = 场景数据[当前位置] # 取出当前位置的场景信息
print("-" * 40)
print(当前场景["描述"]) # 显示场景描述
# 如果没有选项,说明到了结局
if not 当前场景["选项"]:
print("n===== 游戏结束 =====")
break
# 显示生命值
print(f"n❤️ 生命值:{生命值}")
# 显示所有选项
for 编号, (动作描述, 目的地) in 当前场景["选项"].items():
print(f" {编号}. {动作描述}")
# 获取玩家选择
选择 = input("n你的选择(输入数字):").strip()
# 验证选择是否有效
if 选择 in 当前场景["选项"]:
# 取出这个选项对应的目的地
动作描述, 目的地 = 当前场景["选项"][选择]
当前位置 = 目的地 # 移动!
else:
print("⚠️ 没有这个选项,请重新选择。")
# 判断最终结局
if 生命值 <= 0:
print("你倒在了地牢里,火把熄灭了……(坏结局)")
elif 当前位置 == "宝藏室":
print("你带着满袋金币走出了地牢!(好结局)")
五、逐行理解
5.1 嵌套字典:游戏世界的蓝图
整个游戏世界就装在场景数据这一个字典里。它的结构是这样的:
场景数据 = {
"场景名": {
"描述": "一段文字",
"选项": {
"1": ("动作描述", "目的地场景名"),
"2": ("动作描述", "目的地场景名")
}
},
...
}
字典套字典再套元组——听起来吓人,其实就是”查资料”:
场景数据["入口"]→ 拿到入口这个场景的全部信息场景数据["入口"]["描述"]→ 拿到入口的描述文字场景数据["入口"]["选项"]["1"]→ 拿到选项1的那一对信息
为什么用字典而不是一堆if-else?想象一下用if-else写6个场景的样子——每个场景都要写”如果在入口就显示……如果在左通道就显示……”,代码会又长又乱。而用字典,游戏数据和游戏逻辑分开了。想加一个新场景?往字典里加一项就行,游戏代码一行都不用改。
这是个重要的编程思想:数据驱动。真实的游戏引擎就是这么做的。
5.2 游戏状态变量
当前位置 = "入口"
生命值 = 100
两个变量记住玩家的所有情况:人在哪、还剩多少血。游戏的过程,就是不断修改这两个变量的过程。
5.3 主循环:游戏的心跳
while True:
当前场景 = 场景数据[当前位置]
print(当前场景["描述"])
每次循环,程序做四件事:
- 查:根据
当前位置,从字典里查出当前场景 - 演:显示场景描述
- 选:列出选项,等玩家输入
- 动:根据选择,更新
当前位置
然后循环又回到第1步。玩家在地牢里走的每一步,就是这个循环转了一圈。
5.4 遍历选项
for 编号, (动作描述, 目的地) in 当前场景["选项"].items():
print(f" {编号}. {动作描述}")
这行有点炫技,拆开看:
.items()把字典的”键值对”一个个拿出来(第八篇)- 每个值是一个元组
("动作描述", "目的地"),Python允许你直接拆成两个变量:(动作描述, 目的地) - 这叫元组解包
运行效果就是:
1. 走向左边的冷风通道
2. 走向右边的水声通道
5.5 选择验证
if 选择 in 当前场景["选项"]:
动作描述, 目的地 = 当前场景["选项"][选择]
当前位置 = 目的地
else:
print("⚠️ 没有这个选项,请重新选择。")
in检查玩家输入的数字是不是有效选项。有效就移动(更新当前位置),无效就提醒。注意无效选择时没有移动,循环重新转一圈,玩家重新选。
5.6 结局判断
if not 当前场景["选项"]:
break
宝藏室的选项是空字典{}。空字典在条件判断里等同于False,所以not 空字典是True——到了没选项的场景,就是故事的终点,跳出循环。
跳出后再根据情况宣布结局。生命值 <= 0是死亡,当前位置 == "宝藏室"是发财。
六、运行试试
保存为地牢探险.py,运行:
python 地牢探险.py
试几条路线:
- 入口 → 左通道 → 宝箱房 → 宝藏室:拿到钥匙,直通宝藏,完美结局
- 入口 → 右通道 → 陷阱房 → 宝藏室:摔进坑扣血,也能到宝藏,但带着伤
- 乱走:在入口和通道之间来回晃
你会发现两个问题:
- 陷阱房扣血那段是”写在描述里的”,程序并没有真的扣血——生命值一直是100
- 没有真正的死亡结局,因为没有任何地方真的减少生命值
第一个改进,就解决这个。
七、第一个改进:让事件真的发生
现在的描述文字说”生命值-30″,但程序没扣血,纯属”嘴炮”。我们给场景加上事件机制:
# 新版场景数据:加了"事件"字段
场景数据 = {
"入口": {
"描述": "你举着火把,站在一个阴森的地牢入口。前方分出两条通道。",
"事件": None, # None表示没有事件
"选项": {
"1": ("走向左边的冷风通道", "左通道"),
"2": ("走向右边的水声通道", "右通道")
}
},
"宝箱房": {
"描述": "房间里放着一个落满灰尘的宝箱。",
"事件": "获得钥匙", # 这个场景会触发事件
"选项": {
"1": ("拿着钥匙继续深入", "宝藏室"),
"2": ("退回左通道", "左通道")
}
},
"陷阱房": {
"描述": "地板突然塌陷!你摔进坑里!",
"事件": "扣血30",
"选项": {
"1": ("爬起来继续走", "宝藏室"),
"2": ("退回右通道", "右通道")
}
},
# ... 其他场景类似
}
背包 = [] # 新增:记录身上的物品
# 主循环里,在显示描述之后加上事件处理:
# ---- 处理场景事件 ----
事件 = 当前场景.get("事件") # .get()取不到时返回None,不会报错
if 事件 == "扣血30":
生命值 -= 30
print("💥 你受伤了!生命值-30")
elif 事件 == "获得钥匙":
if "金钥匙" not in 背包: # 防止重复获得
背包.append("金钥匙")
print("🔑 你获得了物品:金钥匙!")
# ---- 检查是否死亡 ----
if 生命值 <= 0:
print("n💀 你的生命值耗尽,倒在了冰冷的地牢里……")
print("===== 游戏结束:坏结局 =====")
break
几个亮点:
当前场景.get("事件"):有些场景没有”事件”字段,用.get()而不是["事件"],取不到时返回None而不是报错。这是第八篇讲过的字典技巧。背包.append("金钥匙"):用列表当背包,和猜数字游戏里用列表存猜测历史是一个套路。if "金钥匙" not in 背包:防止玩家退回再进来时重复拿钥匙。- 死亡检查放在事件处理之后:掉坑扣血后立刻检查,血量归零马上结束,不会再让他继续走。
现在死亡结局是真正可达的了——走右边,进陷阱房,再进去一次,再扣30血……总有一次会倒下。
八、第二个改进:物品系统 — 有些路不给走
有了背包,就能玩出更多花样:守卫要钥匙才放行。
# 在场景数据里加"需要物品"字段
"守卫": {
"描述": "石头守卫挡在路中间,红眼睛盯着你。它伸出手,似乎在索要什么。",
"事件": None,
"需要物品": "金钥匙", # 新增:进入下一步的条件
"选项": {
"1": ("把金钥匙交给守卫", "密室"),
"2": ("退回右通道", "右通道")
}
},
然后在选择验证那里,加一个检查:
# 获取玩家选择
选择 = input("n你的选择(输入数字):").strip()
if 选择 in 当前场景["选项"]:
动作描述, 目的地 = 当前场景["选项"][选择]
# ---- 检查是否需要物品 ----
需要 = 当前场景.get("需要物品")
if 需要 and 需要 not in 背包:
print(f"🚫 守卫拦住了你:需要【{需要}】才能通过!")
continue # 不移动,重新选择
当前位置 = 目的地
else:
print("⚠️ 没有这个选项,请重新选择。")
三行代码,游戏玩法完全变了:
- 没拿钥匙就去见守卫 → 被拦下,逼你先去宝箱房
- 拿了钥匙去见守卫 → 通过,进入隐藏的密室
物品条件把线性的地图变成了”有先后顺序的解谜”。这正是游戏设计的核心手法,而实现它只需要一个字典字段加一个if。
九、第三个改进:存档功能 — 游戏能保存进度
玩到一半要吃饭怎么办?总不能重新走一遍。用第九篇学的文件操作,把游戏状态存到文件里:
import json
存档文件 = "地牢存档.json"
def 保存存档():
"""把当前游戏状态写入文件"""
存档 = {
"位置": 当前位置,
"生命值": 生命值,
"背包": 背包
}
with open(存档文件, "w", encoding="utf-8") as f:
json.dump(存档, f, ensure_ascii=False, indent=2)
print("💾 游戏已保存!")
def 读取存档():
"""从文件读取游戏状态,读到就返回True"""
global 当前位置, 生命值, 背包 # 要修改全局变量,必须声明
try:
with open(存档文件, "r", encoding="utf-8") as f:
存档 = json.load(f)
当前位置 = 存档["位置"]
生命值 = 存档["生命值"]
背包 = 存档["背包"]
print(f"📂 读取存档成功!你在【{当前位置}】,生命值{生命值},背包:{背包}")
return True
except FileNotFoundError:
print("📭 没有找到存档,开始新游戏。")
return False
程序启动时先问玩家:
print("=== 地牢探险 ===")
if input("是否读取上次的存档?(y/n):").strip().lower() == "y":
if not 读取存档():
print("开始新游戏!")
游戏中随时可以保存——在主循环的选项里加一个:
# 除了场景选项,任何时候都可以输入s保存
选择 = input("n你的选择(输入数字,输入s保存存档):").strip()
if 选择 == "s":
保存存档()
continue
注意读取存档()里的global——第七篇说过,函数里改全局变量必须声明。不写global,Python会认为你在函数里创建一个新的局部变量,外面的当前位置根本不会变。
存档文件长这样(JSON格式,第十二篇用过):
{
"位置": "宝箱房",
"生命值": 70,
"背包": ["金钥匙"]
}
十、完整的游戏流程
把所有改进合起来,游戏的完整逻辑是:
启动
├─ 有存档?→ 读取 → 继续冒险
└─ 没存档 → 从入口开始
循环:
├─ 显示当前场景描述
├─ 触发场景事件(扣血/获得物品)
├─ 生命值归零?→ 死亡结局,结束
├─ 没有选项?→ 结局场景,结束
├─ 显示选项,等待输入
│ ├─ 输入s → 保存存档
│ ├─ 选项需要物品但背包里没有?→ 拦下
│ └─ 有效选择 → 移动到新场景
└─ 无效输入 → 提示重选
十一、回顾一下用到了什么
| 知识点 | 在哪里用的 |
|---|
|——–|———–|
| 字典 | 场景数据、存档数据 |
|---|---|
| 嵌套字典 | 场景里套描述和选项 |
| 字典.get() | 安全地取”事件”和”需要物品” |
| 字典.items() | 遍历显示选项 |
| 元组解包 | 动作描述, 目的地 = ... |
| 列表 | 背包 |
| append / in | 获得物品 / 检查物品 |
| 变量 | 当前位置、生命值 |
| while True | 游戏主循环 |
| break / continue | 结局跳出 / 无效选择重来 |
| if/elif/else | 事件处理、结局判断 |
| 函数 | 保存存档、读取存档 |
| global | 函数里修改全局游戏状态 |
| 文件操作 | JSON存档读写 |
| try/except | 存档文件不存在时不崩溃 |
| f-string | 动态显示描述和状态 |
| import | json模块 |
17个知识点。一个游戏全用上了。
而且注意:这些没有一个是新学的。你其实早就具备做这个游戏的所有知识,今天只是把它们组合在了一起。
十二、这个项目教会我们什么
回头看代码,有一个现象值得注意:游戏越做越复杂,但每个改进都不难。
- 加事件机制?一个
if-elif加30行 - 加物品条件?一个字典字段加5行
- 加存档?两个函数加几行调用
每个改动都是你熟悉的语法。复杂的东西,是简单的东西一层层叠出来的。这就是编程,也是所有工程的真相。
另一个收获是数据和逻辑分离的设计思想。我们的游戏逻辑(主循环)从头到尾没怎么变,但游戏内容(场景数据)改了好几版。想做一个”森林探险””太空冒险”?把场景数据整个换掉就行,代码照用。
十三、今日小练习
给游戏加一个新结局:隐藏结局——神秘密室。
设计要求:
- 在场景数据里加一个新场景”密室”,描述自己编(比如:一间堆满古代文献的房间,你找到了地牢建造者的日记,揭开了地牢的秘密)
- 密室是一个结局场景(选项为空)
- 进入密室的条件:身上有金钥匙,且生命值大于50(”只有活着且有钥匙的人才配知道真相”)
- 游戏结束时,如果玩家在密室,显示”隐藏结局”
提示:你需要在场景数据里加”密室”这个键,在守卫的选项里指向它,然后在结局判断那里加一个elif。条件”生命值大于50″可以在结局判断里检查。
想想你会怎么写?先自己动手,卡住了再问AI。
下一篇:单位转换器 — 做饭时克和毫升怎么换算?出差时差几小时?用字典和函数,做一个什么都能换的小工具。
发表回复