当前位置:首页 > 技术 > 正文内容

基于Python构建交互式文本冒险游戏

访客 技术 2026年6月8日 1

文本冒险游戏作为经典的交互式叙事形式,能够通过纯文字描述营造出丰富的想象空间。本文将展示如何利用Python从零构建一个模块化的文字冒险游戏引擎,涵盖场景管理、状态追踪和分支叙事等核心机制。

核心架构设计

游戏引擎采用三层架构:场景层负责叙事内容,逻辑层处理状态转换,交互层解析用户输入。这种分离设计便于后续扩展新的游戏机制。

场景数据建模

首先定义场景的数据结构,每个场景包含描述文本、可选动作及目标场景映射:

from dataclasses import dataclass, field
from typing import Dict, List, Callable, Optional

@dataclass
class Action:
    """玩家可执行的动作"""
    description: str          # 动作描述(显示给玩家)
    target_scene: str         # 跳转目标场景ID
    condition: Optional[Callable] = None  # 执行条件
    effect: Optional[Callable] = None   # 副作用函数

@dataclass
class Scene:
    """游戏场景"""
    scene_id: str
    narrative: str            # 场景描述文本
    actions: List[Action] = field(default_factory=list)
    on_enter: Optional[Callable] = None  # 进入场景时触发

游戏状态管理器

使用专用类封装玩家状态,包括位置、背包和自定义属性:

class GameState:
    def __init__(self):
        self.current_location = "entrance"
        self.inventory: List[str] = []
        self.flags: Dict[str, any] = {}
        self.visited: set = set()
    
    def set_flag(self, key: str, value: any):
        self.flags[key] = value
    
    def check_flag(self, key: str, default=None) -> any:
        return self.flags.get(key, default)
    
    def add_item(self, item: str):
        if item not in self.inventory:
            self.inventory.append(item)
    
    def has_item(self, item: str) -> bool:
        return item in self.inventory

场景注册与游戏引擎

构建场景注册中心和主循环引擎:

class AdventureEngine:
    def __init__(self):
        self.scenes: Dict[str, Scene] = {}
        self.state = GameState()
        self.running = False
    
    def register(self, scene: Scene):
        """注册场景到游戏世界"""
        self.scenes[scene.scene_id] = scene
    
    def transition_to(self, scene_id: str):
        """执行场景切换"""
        if scene_id not in self.scenes:
            print(f"\n[错误] 场景 '{scene_id}' 不存在")
            return
        
        self.state.current_location = scene_id
        self.state.visited.add(scene_id)
        current = self.scenes[scene_id]
        
        # 触发进入回调
        if current.on_enter:
            current.on_enter(self.state)
        
        self.render(current)
    
    def render(self, scene: Scene):
        """渲染当前场景"""
        print(f"\n{'='*40}")
        print(scene.narrative)
        print(f"{'='*40}")
        
        # 过滤并显示可用动作
        valid_actions = []
        for idx, act in enumerate(scene.actions, 1):
            if act.condition is None or act.condition(self.state):
                valid_actions.append(act)
                print(f"  [{idx}] {act.description}")
        
        if not valid_actions:
            print("\n(此处无路可走...)")
            self.running = False
            return
        
        self.handle_input(valid_actions)
    
    def handle_input(self, actions: List[Action]):
        """解析玩家输入"""
        while True:
            try:
                choice = input("\n> 选择行动编号: ").strip()
                selected = actions[int(choice) - 1]
                
                # 执行副作用
                if selected.effect:
                    selected.effect(self.state)
                
                # 跳转目标场景
                self.transition_to(selected.target_scene)
                break
                
            except (ValueError, IndexError):
                print("无效输入,请重新选择")

构建完整游戏世界

以下演示如何组装一个包含谜题和分支的地下城探险:

def build_dungeon():
    engine = AdventureEngine()
    
    # 入口大厅
    entrance = Scene(
        scene_id="entrance",
        narrative="你站在一座古老城堡的入口。石门半掩,冷风从缝隙中涌出。",
        actions=[
            Action("推开石门进入大厅", "hallway"),
            Action("检查门边的石碑", "tablet")
        ]
    )
    
    # 神秘石碑
    tablet = Scene(
        scene_id="tablet",
        narrative="石碑上刻着:'唯有持火把者,方能穿越黑暗之廊。'",
        actions=[
            Action("返回入口", "entrance")
        ]
    )
    
    # 长廊(需要火把)
    def give_torch(state: GameState):
        state.add_item("火把")
        print("\n[获得物品:火把]")
    
    hallway = Scene(
        scene_id="hallway",
        narrative="长廊深邃而漆黑,尽头传来滴水的回声。",
        actions=[
            Action(
                "摸黑前行(危险)", 
                "pit_trap",
                condition=lambda s: not s.has_item("火把")
            ),
            Action(
                "点燃火把照亮前路", 
                "treasure_room",
                condition=lambda s: s.has_item("火把"),
                effect=give_torch
            )
        ]
    )
    
    # 陷阱场景
    pit_trap = Scene(
        scene_id="pit_trap",
        narrative="你踩空了!坠入深坑的途中,黑暗吞噬了一切...",
        actions=[]  # 无动作,游戏结束
    )
    
    # 宝藏室
    treasure = Scene(
        scene_id="treasure_room",
        narrative="火光映照下,密室中央的石台上放着一颗发光的宝石。",
        actions=[
            Action("取走宝石并离开", "victory")
        ]
    )
    
    # 胜利结局
    victory = Scene(
        scene_id="victory",
        narrative="你带着传说中的宝石走出城堡,阳光洒满全身。冒险成功!",
        actions=[]
    )
    
    # 注册所有场景
    for s in [entrance, tablet, hallway, pit_trap, treasure, victory]:
        engine.register(s)
    
    return engine

# 启动游戏
if __name__ == "__main__":
    game = build_dungeon()
    game.running = True
    game.transition_to("entrance")
    
    while game.running:
        pass  # 输入处理在transition_to中递归调用

扩展功能:条件叙事与动态描述

通过继承Scene实现更复杂的动态场景:

class DynamicScene(Scene):
    """根据状态改变描述的场景"""
    def __init__(self, scene_id: str, base_narrative: str):
        super().__init__(scene_id=scene_id, narrative=base_narrative)
        self.descriptions: Dict[str, str] = {}
    
    def add_variant(self, flag_key: str, narrative: str):
        self.descriptions[flag_key] = narrative
        return self
    
    def get_narrative(self, state: GameState) -> str:
        for flag, desc in self.descriptions.items():
            if state.check_flag(flag):
                return desc
        return self.narrative


# 使用示例:根据玩家选择改变场景描述
library = DynamicScene(
    "library",
    "图书馆积满灰尘,书架上的书似乎很久没人动过。"
)
library.add_variant(
    "read_spellbook",
    "你读懂了魔法书上的咒语,书架后隐藏的密室缓缓打开。"
)

存档系统实现

import json
import pickle

class SaveManager:
    @staticmethod
    def save(state: GameState, filepath: str):
        """序列化游戏状态"""
        data = {
            "location": state.current_location,
            "inventory": state.inventory,
            "flags": state.flags,
            "visited": list(state.visited)
        }
        with open(filepath, 'w', encoding='utf-8') as f:
            json.dump(data, f, ensure_ascii=False, indent=2)
    
    @staticmethod
    def load(filepath: str) -> GameState:
        """恢复游戏状态"""
        with open(filepath, 'r', encoding='utf-8') as f:
            data = json.load(f)
        
        state = GameState()
        state.current_location = data["location"]
        state.inventory = data["inventory"]
        state.flags = data["flags"]
        state.visited = set(data["visited"])
        return state

设计要点总结

  • 数据驱动:场景与逻辑分离,便于非程序员参与内容创作
  • 延迟求值:条件函数在运行时评估,支持复杂的状态判断
  • 无副作用默认:Action的condition和effect均为可选,保持简单场景的简洁性
  • 可扩展性:通过继承Scene或Action可添加动画、音效等新特性

该框架可作为基础进一步扩展战斗系统、NPC对话树或随机地图生成等高级功能。

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。