
在开发实战教学中很多刚学完 Python 基础的同学都会遇到同一个问题语法都认识循环、判断、函数、列表、字典每一项都练过但一说到“写个小项目”就不知道从哪里开始。今天我们就用《末日逃生列车》这个主题做一个完整可运行的中文文字冒险游戏重点实现其中的“无限续美食车厢”玩法把变量、流程控制、函数、字典、随机事件、状态管理这些知识点串起来。这个项目最大的特点是不依赖任何第三方库纯 Python 标准库就能运行。你只需要一个 Python 3.6 环境代码复制到本地保存为train_game.py然后python train_game.py就能开始游戏。无论是学生练手、课程设计还是作为 Python 基础阶段的综合项目都非常合适。1. 项目背景与玩法设计1.1 末日列车的世界观设定游戏的故事背景是这样的某一天城市陷入混乱一列编号为“T-117”的列车从废弃城市边缘驶出铁轨延伸向未知的黑暗。你从最后一节货运车厢醒来身边只有少量物资而列车前方还有六节风格各异的车厢等着你探索。整列列车被幸存者改造成了一座移动堡垒第一节是储物车厢堆着还没来得及整理的物资箱。第二节被改成了美食车厢这也是整个游戏的核心亮点服务机器人会提供无限续餐。第三节是临时医疗区一位疲惫的医生负责给伤员包扎。第四节是物资交易区一个穿皮夹克的男人在这里倒卖物资。最后一节是驾驶室列车前方出现了断桥危机你需要做出最终选择。玩家的目标非常简单从车尾走到车头在探索过程中保持自己存活并根据不同的选择触发不同结局。1.2 核心玩法拆解这个游戏在玩法上属于“文字冒险游戏”玩家通过输入数字或文字做出选择程序根据选择改变角色属性并推动剧情发展。核心玩法可以拆成四个部分身份选择游戏开始时玩家可以从医生、士兵、厨师中选择一个身份。不同身份的生命值、饱腹度、心情值和信任值初始数值不同。属性管理每个角色有四项核心属性。生命值归零则游戏结束饱腹度太低会持续掉血心情值影响剧情中的部分选择信任值则关系到能否从 NPC 那里拿到物资。车厢事件每节车厢都有一个独立事件玩家进入后通过输入选项来触发不同的结果。无限续餐机制美食车厢允许玩家反复吃食物没有次数上限这是很多技术交流群里讨论最热的部分。为了不让“无限续餐”变成无脑刷属性我设计了一套数值递减规则让“能吃”和“划算地吃”变成两个问题。1.3 技术选型说明选择纯 Python 实现主要出于三个考虑文字冒险游戏的逻辑核心是“状态管理 分支选择”非常适合用 Python 的类、字典和函数组合实现。不引入第三方库可以降低运行门槛任何装了 Python 的机器都能直接跑。方便后续扩展比如换成 Flask 做 Web 版或者用 PyQt 做成窗口程序核心逻辑都可以复用。2. 环境准备与项目结构2.1 运行环境本项目的运行环境非常宽松环境项要求操作系统Windows 10/11、macOS、Ubuntu 均可Python 版本3.6 及以上第三方依赖无运行方式命令行终端如果你的电脑还没有安装 Python可以从 Python 官网下载安装包安装时记得勾选“Add Python to PATH”。2.2 项目文件结构项目采用单文件结构全部代码放在一个train_game.py中方便初学者理解和运行。末日逃生列车/ └── train_game.py如果后续要扩展成更完整的项目建议拆分成下面的结构末日逃生列车/ ├── main.py # 游戏入口 ├── player.py # 玩家类 ├── carriages.py # 各种车厢事件 ├── config.py # 属性配置与数值常量 └── utils.py # 通用工具函数本次演示先使用单文件版本把关注点放在游戏逻辑本身。3. 数据结构设计3.1 玩家属性类设计整个游戏的核心数据是玩家的四项属性。属性变化贯穿游戏全过程因此我用一个Player类来管理。class Player: def __init__(self, name, role): self.name name self.role role self.hp 100 # 生命值 self.hunger 50 # 饱腹度 self.mood 70 # 心情值 self.trust 60 # 信任值 self.meal_count 0 # 已吃美食次数 self.meal_bonus 30 # 首次吃美食的基础增益对新手来说这里可以学到类的最核心用法把一组相关的数据属性和操作这些数据的方法函数绑定在一起。为什么要用类因为游戏里需要频繁修改玩家属性如果不用类你得写一堆全局变量然后每个函数都要用global声明代码会非常混乱。而把这些属性封装到类里之后任何函数只要拿到player对象就能方便地读取和修改角色状态。3.2 属性数值边界为了避免属性值出现负数或超过 100我设计了一个apply方法统一处理属性增减。这个方法会在所有事件里复用def apply(self, effects): mapping { hp: 生命值, hunger: 饱腹度, mood: 心情值, trust: 信任值, } for key, value in effects.items(): if not hasattr(self, key): continue current getattr(self, key) new_value max(0, min(100, current value)) setattr(self, key, new_value) delta new_value - current if delta 0: print(f[效果] {mapping.get(key, key)} {delta}) elif delta 0: print(f[效果] {mapping.get(key, key)} {delta})这里用到了几个很实用的 Python 技巧hasattr(self, key)用于判断玩家对象是否拥有对应的属性。getattr和setattr可以动态读取、修改属性比写一堆 if-else 判断要简洁。max(0, min(100, ...))是属性边界控制的经典写法保证数值始终在 0 到 100 之间。比如在医疗车厢里选择“索要医疗用品”事件代码只需要写player.apply({hp: 20, trust: -10})程序就会自动完成“生命值 20信任值 -10”的修改并且打印出修改结果。这种设计的好处是后续新增属性或新增事件时不需要改apply方法事件代码更简洁、更不容易出错。3.3 事件函数结构游戏里每节车厢对应一个普通函数函数接收player对象作为参数。车厢函数内部使用input()接收玩家输入再通过 if-elif 结构处理不同分支。用一个列表保存所有车厢函数主流程只需要一次循环就能依次进入每节车厢carriages [food_carriage, medical_carriage, supply_carriage, engine_carriage] for idx, carriage in enumerate(carriages, start1): if not player.alive: print(\n你的生命值归零倒在了列车的走廊上。) print(结局死亡) return print(f\n—— 第 {idx 1} 节车厢 ——) carriage(player)player.alive是一个只读属性用来判断角色是否存活property def alive(self): return self.hp 0用property装饰器可以让player.alive像普通属性一样被读取而不是通过方法调用的写法。这个知识点在项目实战中非常常见。4. “无限续美食车厢”的核心实现4.1 玩法规则说明美食车厢是整个游戏里最特别的一节车厢。服务机器人会对玩家说“欢迎光临末日餐厅本车厢支持无限续餐费用全免。”我把规则设计成了这样玩家可以反复选择“吃一份美食”没有次数限制。第一次续餐时饱腹度增加 30。之后每多吃一次增加的饱腹度递减 2最低不低于 5。每次吃食物心情值减少 3。也就是说玩家从第 1 次到第 13 次续餐的实际效果是续餐次数饱腹度增加值心情值变化130-3228-3326-3424-3522-3620-3718-3816-3914-31012-31110-3128-3136-314 次以后5-3这里的关键数值是meal_bonus - meal_count * 2再配合max(5, ...)保证任何时候续餐都有保底收益。这种“收益递减”设计在游戏数值里非常常用它能让玩家觉得“还有收益”但收益越来越小从而避免无限重复刷取。4.2 为什么不能做成无脑白嫖很多同学第一反应是既然无限续餐那我不停吃把饱腹度刷满不就能轻松通关了吗于是我在规则里加了一条限制当饱腹度已经达到 100 时继续进食会触发负面效果。实现代码如下def eat(self): if self.hunger 100: self.hp max(0, self.hp - 10) self.mood max(0, self.mood - 5) return 你实在太撑了再吃下去胃开始抗议……生命值 -10心情值 -5。 food_gain max(5, self.meal_bonus - self.meal_count * 2) self.hunger min(100, self.hunger food_gain) self.mood max(0, self.mood - 3) self.meal_count 1 return f你吃下了第 {self.meal_count} 份美食饱腹度 {food_gain}心情值 -3。饱腹度数值的范围依然是 0 到 100。当已经满值时再吃会掉血、掉心情。这给“无限”加了一层限制玩家必须思考我到底该吃多少既能把饱腹度补足又不至于影响后续探索。从设计角度来说这就叫“有代价的无限”游戏性和平衡性都更有保障。作为技术练习它也演示了条件判断、属性边界控制和返回值文本拼接这三种基础能力。4.3 美食车厢完整逻辑美食车厢的完整代码里我还增加了一个“打包食物”选项但限制只能打包一次避免玩家无限拿罐头。用isinstance配合一个布尔变量就可以实现def food_carriage(player): print(\n第二节车厢飘来浓郁的香味。这里被改造成了末日餐厅桌上有炖菜、罐头、硬面包和烤土豆。) print(一台戴着厨师帽的服务机器人滑到你面前『欢迎光临末日餐厅本车厢支持无限续餐』) print(广播里传来建议『美食可以缓解恐惧但请控制食量以免影响健康。』) packed False while True: player.show_status() print(1. 吃一份美食无限续餐效果会逐渐递减) print(2. 打包一份食物带走饱腹度 10心情 5仅限一次) print(3. 离开美食车厢) choice input(请输入你的选择).strip() if choice 1: print(player.eat()) if not player.alive: return elif choice 2: if packed: print(你的背包已经塞满了再也装不下任何罐头了。) else: packed True player.apply({hunger: 10, mood: 5}) print(你从餐车上拿了一罐罐头塞进背包。) elif choice 3: print(你推开餐车连接门向下一节车厢走去。) break else: print(无效输入请重新选择。)这里使用了while True循环让玩家可以反复在同一节车厢里活动直到主动输入“离开”。if not player.alive的判断很重要因为吃撑会导致生命值归零必须阻止程序继续输出后面的剧情。5. 完整游戏代码下面把整个train_game.py的完整代码贴出来。代码已经整合了身份选择、玩家类、四节车厢事件和结局判定复制保存后可以直接运行。import random class Player: def __init__(self, name, role): self.name name self.role role self.hp 100 self.hunger 50 self.mood 70 self.trust 60 self.meal_count 0 self.meal_bonus 30 def show_status(self): print( * 44) print(f幸存者{self.name}{self.role}) print(f生命值{self.hp}/100) print(f饱腹度{self.hunger}/100) print(f心情值{self.mood}/100) print(f信任值{self.trust}/100) print( * 44) def apply(self, effects): mapping { hp: 生命值, hunger: 饱腹度, mood: 心情值, trust: 信任值, } for key, value in effects.items(): if not hasattr(self, key): continue current getattr(self, key) new_value max(0, min(100, current value)) setattr(self, key, new_value) delta new_value - current if delta 0: print(f[效果] {mapping.get(key, key)} {delta}) elif delta 0: print(f[效果] {mapping.get(key, key)} {delta}) def eat(self): if self.hunger 100: self.hp max(0, self.hp - 10) self.mood max(0, self.mood - 5) return 你实在太撑了再吃下去胃开始抗议……生命值 -10心情值 -5。 food_gain max(5, self.meal_bonus - self.meal_count * 2) self.hunger min(100, self.hunger food_gain) self.mood max(0, self.mood - 3) self.meal_count 1 return f你吃下了第 {self.meal_count} 份美食饱腹度 {food_gain}心情值 -3。 property def alive(self): return self.hp 0 def choose_role(): roles { 1: (医生, 90, 60, 60, 80), 2: (士兵, 100, 70, 50, 60), 3: (厨师, 80, 100, 85, 70), } print(\n请选择你的身份) print(1. 医生生命值 90饱腹度 60心情值 60信任值 80) print(2. 士兵生命值 100饱腹度 70心情值 50信任值 60) print(3. 厨师生命值 80饱腹度 100心情值 85信任值 70) while True: choice input(请输入数字).strip() if choice in roles: return roles[choice] print(无效输入请输入 1、2 或 3。) def food_carriage(player): print(\n第二节车厢飘来浓郁的香味。这里被改造成了末日餐厅桌上有炖菜、罐头、硬面包和烤土豆。) print(一台戴着厨师帽的服务机器人滑到你面前『欢迎光临末日餐厅本车厢支持无限续餐费用全免』) print(广播里传来建议『美食可以缓解恐惧但请控制食量以免影响健康。』) packed False while True: player.show_status() print(1. 吃一份美食无限续餐效果会逐渐递减) print(2. 打包一份食物带走饱腹度 10心情 5仅限一次) print(3. 离开美食车厢) choice input(请输入你的选择).strip() if choice 1: print(player.eat()) if not player.alive: return elif choice 2: if packed: print(你的背包已经塞满了再也装不下任何罐头了。) else: packed True player.apply({hunger: 10, mood: 5}) print(你从餐车上拿了一罐罐头塞进背包。) elif choice 3: print(你推开餐车连接门向下一节车厢走去。) break else: print(无效输入请重新选择。) def medical_carriage(player): print(\n第三节车厢弥漫着消毒水的气味。墙上挂着医疗箱角落里坐着一名神色疲惫的医生。) print(医生抬起头『你看起来不像感染者。这里有些药品但需要你帮忙……』) print(1. 帮医生搬药品信任值 15心情 5) print(2. 索要医疗用品生命值 20信任值 -10) print(3. 保持警惕快速通过) choice input(请输入你的选择).strip() if choice 1: player.apply({trust: 15, mood: 5}) print(你帮医生把药品搬到车头方向换来了他的感谢。) elif choice 2: player.apply({hp: 20, trust: -10}) print(医生虽然不满但还是给了你一卷绷带和几片止痛药。) else: print(你没有停留继续向车头方向前进。) def supply_carriage(player): print(\n第四节车厢堆满了物资箱有些箱子被暴力拆开。一个穿皮夹克的人靠在箱子上打量着你。) print(『嘿想换点好东西吗』他拍了拍身边的箱子。) print(1. 用信任值换取物资信任值 50 时可用生命值 15心情 10) print(2. 直接拿物资生命值 10信任值 -20) print(3. 不理会继续前进) choice input(请输入你的选择).strip() if choice 1: if player.trust 50: player.apply({hp: 15, mood: 10}) print(皮夹克男人对你点点头『你值得信任这些物资拿去。』) else: print(对方嗤笑一声『就凭你谁信你。』什么也没给你。) elif choice 2: player.apply({hp: 10, trust: -20}) print(你抓了几瓶水就跑背后传来愤怒的骂声。) else: print(你绕开物资箱继续前进。) def engine_carriage(player): print(\n你来到列车最前方的驾驶室。控制台闪烁着红光一名乘客正试图扳动制动杆。) print(他看见你大声喊道『列车前方桥梁断了必须现在停车』) print(你注意到广播里传来另一条指令『不要停车列车上有急需送往幸存者基地的物资』) print(1. 支持立刻制动停车信任值 20) print(2. 支持继续冲过断桥风险未知) print(3. 先回到美食车厢冷静一下将再次进入美食车厢) choice input(请输入你的选择).strip() if choice 1: player.apply({trust: 20}) print(你按下红色紧急制动按钮。列车发出刺耳的摩擦声最终在断桥前停下。) print(车厢里的人惊魂未定但都向你投来感激的目光。) print(结局你选择了大多数人生命优先。幸存者基地派来救援队列车停在旷野上食物还算充足。) print(——幸存者结局守序者——) elif choice 2: player.apply({trust: -10}) if random.random() 0.5: print(列车冲向断桥你抓紧扶手最终凭借残余车速滑到了对岸。) print(结局你赌赢了。列车继续前行但也有人在争吵中伤了和气。) print(——幸存者结局赌徒——) else: print(列车冲出轨道世界在你眼前翻转。) player.apply({hp: -80}) if player.alive: print(你奇迹般活了下来但列车已不能再启动。) print(——幸存者结局幸存者——) else: print(你的故事到这里结束了。) print(——结局坠落——) else: print(你一时无法决定决定先回到美食车厢吃点东西冷静一下。) player.apply({mood: 5}) food_carriage(player) if not player.alive: print(\n你的故事在美食车厢里画上了句号。) return print(\n当你再次回到驾驶室时列车已经冲过了断桥有惊无险。) print(——幸存者结局逃跑者——) def main(): print( * 44) print(末日逃生列车无限续美食车厢) print( * 44) name input(请输入你的名字).strip() if not name: name 无名幸存者 role_name, hp, hunger, mood, trust choose_role() player Player(name, role_name) player.hp hp player.hunger hunger player.mood mood player.trust trust print(\n列车从废弃城市边缘驶出铁轨延伸向黑暗的远方。你从最后一节货运车厢醒来。) player.show_status() carriages [food_carriage, medical_carriage, supply_carriage, engine_carriage] for idx, carriage in enumerate(carriages, start1): if not player.alive: print(\n你的生命值归零倒在了列车的走廊上。) print(结局死亡) return print(f\n—— 第 {idx 1} 节车厢 ——) carriage(player) print(\n游戏结束。感谢游玩《末日逃生列车》) if __name__ __main__: main()注意main()里的player.hp hp这段代码它的作用是用玩家选择身份后的初始数值覆盖默认值。这样Player类只需保留一套通用初始化逻辑身份差异化通过外部赋值完成代码更清晰。6. 运行与演示6.1 启动方式将上面的代码保存为train_game.py然后在命令行中执行python train_game.py如果你电脑上同时安装了 Python 2 和 Python 3请使用python3 train_game.py6.2 一次完整游戏演示启动程序后终端会先显示游戏标题然后询问玩家名字 末日逃生列车无限续美食车厢 请输入你的名字小明输入名字后进入身份选择请选择你的身份 1. 医生生命值 90饱腹度 60心情值 60信任值 80 2. 士兵生命值 100饱腹度 70心情值 50信任值 60 3. 厨师生命值 80饱腹度 100心情值 85信任值 70 请输入数字3选择厨师后程序会展示玩家初始状态列车从废弃城市边缘驶出铁轨延伸向黑暗的远方。你从最后一节货运车厢醒来。 幸存者小明厨师 生命值80/100 饱腹度100/100 心情值85/100 信任值70/100 进入美食车厢后玩家可以不断输入数字 1 来体验无限续餐。当饱腹度到达 100 后继续吃会触发惩罚你实在太撑了再吃下去胃开始抗议……生命值 -10心情值 -5。如果一直吃到生命值归零程序会输出死亡结局并结束游戏。这就是“无限机制”和“风险管理”的结合点。7. 常见问题与排查思路7.1 运行后中文乱码或显示异常在 Windows 终端下如果程序输出中文出现乱码通常是终端编码格式问题。解决方法是在代码文件开头加一行编码声明或者避免在代码中直接写特殊字符。更稳妥的方式是在命令行先执行chcp 65001切换到 UTF-8 编码后再运行 Python 脚本。7.2 输入 1 进入吃美食分支但是没有任何反应出现这种情况首先要检查input()函数是否把玩家输入的内容存到了变量中比如choice input(请输入你的选择).strip()如果缺少.strip()玩家输入时空格会导致字符串比较失败。例如输入 1 和1不相等程序就会走到 else 分支并提示无效输入。建议所有输入都使用strip()去除首尾空白字符。7.3 从美食车厢离开后已经死掉的玩家还在继续剧情这是因为food_carriage()函数返回之后主循环中的alive判断没有及时检查。看主循环代码for idx, carriage in enumerate(carriages, start1): if not player.alive: print(你的生命值归零……) return carriage(player)这个检查逻辑是正确的但如果玩家在其他函数内部比如food_carriage死亡函数自身需要主动拦截。所以我在food_carriage里加了if not player.alive: return。如果有同学二次开发新增了车厢函数一定要记得在函数内做存活判断避免“尸体继续逛车厢”的尴尬情况。7.4 随机结果每次运行都是同一个代码里使用了random.random()来实现列车冲出断桥时的概率判定if random.random() 0.5:如果发现结果每次都一样可能是random模块没有正确导入。检查文件顶部是否有import random单纯用from random import random也是可以的但需要注意函数名冲突问题。建议直接import random调用时写random.random()可读性更好。7.5 新增车厢后主流程没有生效主流程里的车厢列表是carriages新增车厢时需要把对应的函数名加到这个列表里。新手最容易忘记的是“只写了函数忘了添加到列表”导致运行起来永远少一节车厢。carriages [food_carriage, medical_carriage, supply_carriage, engine_carriage]注意列表里放的是函数名不是字符串不能加引号。这个细节虽然简单但在实际开发中很容易踩坑。8. 扩展方向与工程实践建议8.1 存档系统当前版本关闭终端后游戏进度就会丢失。可以增加一个存档功能用 JSON 保存玩家状态{ name: 小明, role: 厨师, hp: 80, hunger: 100, mood: 85, trust: 70, meal_count: 3 }读取存档时只要恢复Player对象的属性就能继续游戏。这是练习文件读写和字典转换的很好场景。8.2 随机事件系统目前每节车厢的事件都是写死的玩几次就会腻。可以增加一个随机事件表每次进入车厢时从事件列表中随机抽取events [ {title: 发现一箱罐头, effects: {hunger: 15}}, {title: 遇到流浪者, effects: {trust: -10, mood: -5}}, {title: 捡到药品, effects: {hp: 10}}, ] event random.choice(events)再结合apply方法事件系统就能非常轻量地扩展。8.3 改成 Web 版本如果想让更多人玩到可以考虑用 Flask 做浏览器版本。核心思路是用 Flask 路由接收玩家选择。每次选择后更新 session 里的玩家属性。车厢事件函数可以基本复用只需把input()换成表单提交。这也是一次很好的“从命令行程序转向 Web 应用”的练手实践。8.4 工程实践建议最后分享几条这个项目沉淀下来的工程建议。第一数值配置集中管理。美食续餐的初始增益、递减幅度、饱腹度上限这些数值最好抽成常量放在文件顶部或者独立配置模块里。比如MEAL_INITIAL_BONUS 30和MEAL_DECAY_STEP 2这样改数值时不需要翻代码也便于测试不同平衡方案。第二输入校验要统一处理。无论是角色选择还是车厢选项都应该做“非法输入重新询问”的处理。新手写代码时最容易忽略这个边界导致程序直接报错退出。这个项目里我使用了while True循环来处理身份选择做法简单有效。第三逻辑与文案分离。游戏里的剧情文本和事件逻辑混在一起虽然方便阅读但也导致改文案时必须小心不要改坏代码。更规范的做法是把文案放进字典或 JSON 文件代码只负责读取和展示。第四仓库管理。即使只是单文件项目也建议把代码放到 Git 仓库里管理。每完成一个功能就提交一次既能保留版本历史也方便在开发出错时回滚。搭配requirements.txt虽然本项目为空可以更好地管理依赖。希望这篇教程能帮你在动手实践中巩固 Python 知识。建议先照着代码敲一遍再尝试修改数值、新增车厢、增加随机事件。代码里的TODO和可扩展点很多适合反复修改练习也可以根据自己的想法继续完善这列末日列车。