AI编程(十九):文字冒险游戏 — 把所有知识揉进一场冒险

作者:

在

用字典搭一个地牢,用循环让你探索,用条件判断决定生死

一、你还记得小时候的”选故事”书吗

有一种书,读到一半会问你:主角面前有两条路,走左边翻到第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. 查:根据当前位置,从字典里查出当前场景
  2. 演:显示场景描述
  3. 选:列出选项,等玩家输入
  4. 动:根据选择,更新当前位置

然后循环又回到第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

试几条路线:

  • 入口 → 左通道 → 宝箱房 → 宝藏室:拿到钥匙,直通宝藏,完美结局
  • 入口 → 右通道 → 陷阱房 → 宝藏室:摔进坑扣血,也能到宝藏,但带着伤
  • 乱走:在入口和通道之间来回晃

你会发现两个问题:

  1. 陷阱房扣血那段是”写在描述里的”,程序并没有真的扣血——生命值一直是100
  2. 没有真正的死亡结局,因为没有任何地方真的减少生命值

第一个改进,就解决这个。

七、第一个改进:让事件真的发生

现在的描述文字说”生命值-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行
  • 加存档?两个函数加几行调用

每个改动都是你熟悉的语法。复杂的东西,是简单的东西一层层叠出来的。这就是编程,也是所有工程的真相。

另一个收获是数据和逻辑分离的设计思想。我们的游戏逻辑(主循环)从头到尾没怎么变,但游戏内容(场景数据)改了好几版。想做一个”森林探险””太空冒险”?把场景数据整个换掉就行,代码照用。

十三、今日小练习

给游戏加一个新结局:隐藏结局——神秘密室。

设计要求:

  1. 在场景数据里加一个新场景”密室”,描述自己编(比如:一间堆满古代文献的房间,你找到了地牢建造者的日记,揭开了地牢的秘密)
  2. 密室是一个结局场景(选项为空)
  3. 进入密室的条件:身上有金钥匙,且生命值大于50(”只有活着且有钥匙的人才配知道真相”)
  4. 游戏结束时,如果玩家在密室,显示”隐藏结局”

提示:你需要在场景数据里加”密室”这个键,在守卫的选项里指向它,然后在结局判断那里加一个elif。条件”生命值大于50″可以在结局判断里检查。

想想你会怎么写?先自己动手,卡住了再问AI。


下一篇:单位转换器 — 做饭时克和毫升怎么换算?出差时差几小时?用字典和函数,做一个什么都能换的小工具。

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注