任务:实现游戏主循环(main.py)

任务:实现游戏主循环(main.py)

一、任务背景

《极妙幻境》的所有核心模块已经完成,现在需要将它们整合起来,形成一个可玩的文字游戏。你需要实现游戏的主入口 main.py,负责初始化、运行游戏主循环,并在结束时显示结局。该模块将调用之前实现的所有模块:道德值、卡牌获取、场景生成、BOSS战、层间事件、系统指令等。

二、模块位置与依赖

  • 文件路径src/main.py
  • 依赖模块(均在 src/game_logic/ 下):
    • config:提供 config 对象,包含所有配置参数(如 TOTAL_LAYERS, ROUNDS_PER_LAYER, BOSS_LAYERS, MAX_CARDS_PER_LAYER 等)
    • morality:提供 update_morality, check_extreme_ending, get_morality_ratios
    • card_acquisition:提供 should_get_card
    • scene_generator:提供 generate_layer_scenes
    • layer_event_generator:提供 generate_layer_event
    • option_outcome_generator:提供 generate_option_outcome
    • boss_fight_loop:提供 run_boss_fight
    • commands:提供 handle_system_command
  • 标准库os, sys, logging, random, json
  • 静态文本目录texts/ 下的文件(welcome.txt, rules.txt, endings/ 下的结局文件)

三、函数设计

3.1 主函数 main()

def main():
    """
    游戏入口。初始化游戏状态,运行主循环,结束后显示结局。
    """
    # 实现见下文

3.2 辅助函数(建议,但可在主函数内实现)

  • load_text_file(file_path: str) -> str:读取文本文件,若失败返回默认提示或错误信息。
  • show_ending(ending_type: str, morality_ratios: dict):根据结局类型读取对应的结局文本文件并显示,若文件缺失则显示默认文本。
  • handle_player_choice(choice: str, game_state: dict, round_data: dict) -> dict:处理玩家选项逻辑,返回更新后的状态和是否触发结局(可内联)。

四、主循环详细流程

4.1 初始化

  1. 加载配置(从 config 模块导入 config 对象)。
  2. 初始化游戏状态字典 game_state
    game_state = {
        'layer': 1,
        'round': 1,
        'good': 0,
        'neutral': 0,
        'evil': 0,
        'consecutive': 0,
        'last_choice': None,
        'layer_cards': [],      # 当前层获得的普通牌列表
        'wild_cards': [],       # 跨层累积的万能牌列表
        'in_boss_fight': False,
        'game_over': False,
        'ending_type': None
    }
    
  3. 读取欢迎语:welcome_text = load_text_file('texts/welcome.txt'),并显示。
  4. 可选显示规则?可以提示玩家输入 HELP 查看规则,或者直接显示简短提示。

4.2 层循环

while game_state['layer'] <= config.TOTAL_LAYERS and not game_state['game_over']:
    layer = game_state['layer']
    
    # 获取本层场景数据
    layer_data = generate_layer_scenes(
        layer=layer,
        rounds_per_layer=config.ROUNDS_PER_LAYER,
        morality_ratios=get_morality_ratios(game_state['good'], game_state['neutral'], game_state['evil'])
    )
    if layer_data is None:
        # 如果生成失败,使用备用场景(可简单提示并退出,或使用硬编码场景)
        print("幻境生成出现异常,请稍后再试。")
        break
    
    # 显示层开场白
    print(layer_data['layer_opening'])
    
    # 重置层内计数器
    cards_obtained_this_layer = 0
    game_state['layer_cards'] = []
    
    # 轮循环
    for round_data in layer_data['rounds']:
        current_round = round_data['round']
        game_state['round'] = current_round
        
        # 显示场景描述
        print(f"\n【第{layer}层 第{current_round}轮】")
        print(round_data['description'])
        
        # 显示选项
        for opt in ['A','B','C','D']:
            print(f"  {opt}. {round_data['options'][opt]['text']}")
        
        # 处理玩家输入(可能多次尝试)
        while True:
            raw_cmd = input("> ").strip()
            if not raw_cmd:
                continue
            
            # 先尝试系统指令
            sys_result = handle_system_command(raw_cmd, game_state, None)  # 非BOSS战,boss_fight=None
            if sys_result['should_quit']:
                game_state['game_over'] = True
                game_state['ending_type'] = 'quit'
                break
            if sys_result['output']:
                print(sys_result['output'])
                if '未知指令' in sys_result['output']:
                    continue  # 未知指令,继续输入
                else:
                    # 系统指令已处理,不消耗回合,继续输入?
                    # 但系统指令通常不消耗回合,所以应该回到本轮,让玩家重新选择选项
                    # 因此此处应 continue,而不是 break
                    continue  # 回到 while 开头,继续等待输入
            
            # 如果不是系统指令,检查是否是选项 A/B/C/D
            choice = raw_cmd.upper()
            if choice in ['A', 'B', 'C', 'D']:
                # 处理选项
                result = process_player_choice(choice, game_state, round_data, cards_obtained_this_layer)
                # 更新游戏状态
                game_state.update(result['game_state'])
                cards_obtained_this_layer = result['cards_obtained']
                if result['game_over']:
                    game_state['game_over'] = True
                    game_state['ending_type'] = result['ending_type']
                break  # 退出输入循环,进入下一轮
            else:
                print("请输入 A/B/C/D 或系统指令。输入 HELP 查看帮助。")
        
        if game_state['game_over']:
            break
        
        # 更新轮次(已在 process_player_choice 中处理,此处无需额外操作)
    
    if game_state['game_over']:
        break
    
    # 层结束处理
    if layer in config.BOSS_LAYERS:
        # BOSS战
        # 合并手牌:当前层普通牌 + 累积万能牌
        boss_fight = BossFight(
            player_cards=game_state['layer_cards'] + game_state['wild_cards'],
            morality_ratios=get_morality_ratios(game_state['good'], game_state['neutral'], game_state['evil'])
        )
        # 设置游戏状态为BOSS战中,以便系统指令识别
        game_state['in_boss_fight'] = True
        result = run_boss_fight(boss_fight, game_state)
        game_state['in_boss_fight'] = False
        
        if result == 'player':
            # 玩家胜利,清空所有牌,进入下一层
            game_state['layer_cards'] = []
            game_state['wild_cards'] = []
            game_state['layer'] += 1
            game_state['round'] = 1
        elif result == 'boss':
            # BOSS胜利,游戏结束
            game_state['game_over'] = True
            game_state['ending_type'] = 'boss_loss'
        else:  # 'quit'
            game_state['game_over'] = True
            game_state['ending_type'] = 'quit'
    else:
        # 非BOSS层:层间事件
        event = generate_layer_event(
            layer=layer,
            morality_ratios=get_morality_ratios(game_state['good'], game_state['neutral'], game_state['evil'])
        )
        if event is None:
            event = DEFAULT_EVENT  # 使用硬编码默认事件
        
        print("\n【层间异动】")
        print(event['description'])
        for opt in ['A','B','C','D']:
            trap_mark = " [陷阱]" if event['options'][opt]['is_trap'] else ""
            print(f"  {opt}. {event['options'][opt]['text']}{trap_mark}")
        
        # 等待玩家选择
        while True:
            choice = input("> ").strip().upper()
            if choice in ['A','B','C','D']:
                selected = event['options'][choice]
                print(selected['outcome'])
                if not selected['is_trap'] and selected['card_name']:
                    # 获得万能牌
                    wild_card = {
                        'type': 'wild',
                        'name': selected['card_name'],
                        'description': selected['card_description'],
                        'layer': layer
                    }
                    game_state['wild_cards'].append(wild_card)
                    print(f"获得万能牌:{wild_card['name']} - {wild_card['description']}")
                else:
                    print("你没有获得任何牌。")
                break
            else:
                # 允许系统指令吗?层间事件也是普通场景,可以支持系统指令
                sys_result = handle_system_command(choice, game_state, None)
                if sys_result['should_quit']:
                    game_state['game_over'] = True
                    game_state['ending_type'] = 'quit'
                    break
                if sys_result['output']:
                    print(sys_result['output'])
                    # 继续等待选择
        
        if game_state['game_over']:
            break
        
        # 进入下一层
        game_state['layer'] += 1
        game_state['round'] = 1

4.3 选项处理函数 process_player_choice

由于选项处理逻辑较为独立,建议写成内部函数,清晰易读。

def process_player_choice(choice, game_state, round_data, cards_obtained):
    """
    处理玩家选项,更新道德值、手牌,返回新状态和是否结束。
    """
    # 1. 确定卡牌类型(如果获得牌)
    should_get = should_get_card(
        total_rounds=config.ROUNDS_PER_LAYER,
        current_round=game_state['round'],
        cards_obtained=cards_obtained,
        max_cards=config.MAX_CARDS_PER_LAYER
    )
    card_type = None
    if should_get:
        if choice == 'A':
            card_type = 'good'
        elif choice == 'B':
            card_type = 'neutral'
        elif choice == 'C':
            card_type = 'evil'
        elif choice == 'D':
            card_type = random.choice(['good', 'evil'])
    
    # 2. 生成选项后续和卡牌信息
    outcome_data = generate_option_outcome(
        scene_description=round_data['description'],
        option_text=round_data['options'][choice]['text'],
        morality_ratios=get_morality_ratios(game_state['good'], game_state['neutral'], game_state['evil']),
        card_type=card_type
    )
    print(outcome_data['outcome'])
    
    # 3. 如果获得牌,创建卡牌对象并加入 layer_cards
    if card_type and outcome_data['card_name']:
        card = {
            'type': card_type,
            'name': outcome_data['card_name'],
            'description': outcome_data['card_description'],
            'layer': game_state['layer'],
            'round': game_state['round']
        }
        game_state['layer_cards'].append(card)
        print(f"获得卡牌:{card['name']} - {card['description']}")
        cards_obtained += 1
    
    # 4. 更新道德值
    new_good, new_neutral, new_evil, new_consecutive, ending = update_morality(
        current_round=(game_state['layer']-1)*config.ROUNDS_PER_LAYER + game_state['round'],
        last_choice=game_state['last_choice'],
        current_choice=choice,
        good=game_state['good'],
        neutral=game_state['neutral'],
        evil=game_state['evil'],
        consecutive=game_state['consecutive']
    )
    game_state['good'] = new_good
    game_state['neutral'] = new_neutral
    game_state['evil'] = new_evil
    game_state['consecutive'] = new_consecutive
    game_state['last_choice'] = choice
    
    # 5. 检查极端结局
    if ending:
        show_ending(ending, get_morality_ratios(new_good, new_neutral, new_evil))
        return {
            'game_state': game_state,
            'cards_obtained': cards_obtained,
            'game_over': True,
            'ending_type': ending
        }
    
    return {
        'game_state': game_state,
        'cards_obtained': cards_obtained,
        'game_over': False,
        'ending_type': None
    }

4.4 结局显示函数

def show_ending(ending_type: str, ratios: dict = None):
    """
    根据结局类型显示对应文本。
    ending_type 可能取值:
        - 'good', 'evil', 'neutral' (极端结局)
        - 'trick' (灵机一动特殊结局)
        - 'boss_loss' (BOSS战失败)
        - 'quit' (玩家主动退出)
        - 普通结局:根据道德比例决定 'good_normal', 'evil_normal', 'neutral_normal'
    """
    # 构建文件路径
    endings_dir = os.path.join(os.path.dirname(__file__), '..', 'texts', 'endings')
    filename_map = {
        'good': 'ending_extreme_good.txt',
        'evil': 'ending_extreme_evil.txt',
        'neutral': 'ending_extreme_neutral.txt',
        'trick': 'ending_trick.txt',
        'good_normal': 'ending_good.txt',
        'evil_normal': 'ending_evil.txt',
        'neutral_normal': 'ending_neutral.txt',
        'boss_loss': 'ending_boss_loss.txt',  # 如果没有可复用其他
        'quit': 'ending_quit.txt'
    }
    # 如果某些文件不存在,可提供默认文本
    # ...

4.5 文件读取函数

def load_text_file(file_path: str) -> str:
    """读取文本文件,若失败返回空字符串并记录日志。"""
    try:
        with open(file_path, 'r', encoding='utf-8') as f:
            return f.read()
    except Exception as e:
        logging.error(f"读取文件失败 {file_path}: {e}")
        return ""

五、配置使用

确保从 config 导入 config 对象,并使用其中的参数:

  • config.TOTAL_LAYERS
  • config.ROUNDS_PER_LAYER
  • config.BOSS_LAYERS
  • config.MAX_CARDS_PER_LAYER
  • 其他道德值相关参数已在模块内部使用,无需在主循环中处理。

六、日志配置

在主循环开始前配置日志,便于调试。

import logging
logging.basicConfig(level=config.LOG_LEVEL, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')

七、注意事项

  • 模块导入:确保所有导入正确,使用相对导入(如 from .game_logic import ...)或绝对导入(如 from game_logic import ...),根据项目结构决定。提示 codebuddy 使用 from src.game_logic import ...from game_logic import ...,但最终路径取决于运行方式。建议在 main.py 中使用 from game_logic import ...,因为运行时会从项目根目录调用 python -m src.mainpython src/main.py,需要确保路径正确。可以在文件开头添加 sys.path 处理或使用相对导入,但简单起见,建议使用绝对导入(假设项目根目录在 PYTHONPATH 中)。可以在 main.py 开头添加:
import sys
import os
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from game_logic import config
# 然后其他 from game_logic.xxx import yyy
  • 随机种子:无需设置,但可保留。

  • 错误处理:调用 LLM 的模块可能返回 None,需要妥善处理(如使用默认场景或退出)。

  • BOSS战中的系统指令run_boss_fight 内部已经处理了系统指令,主循环只需调用并传入 game_state

八、测试要求

  • 手动运行整个游戏流程,确保无报错。
  • 测试各种指令和选项。
  • 测试极端结局触发。
  • 测试BOSS战胜负。

九、提交要求

  • 创建 src/main.py,包含上述所有逻辑。
  • 确保 texts/ 目录及文件已存在(之前已创建)。
  • 代码注释清晰,关键步骤有说明。
  • 确保运行 python src/main.py 能正常开始游戏。

开始实现吧!如有疑问,随时沟通。

posted @ 2026-03-18 22:00  神秘园欢迎您  阅读(11)  评论(0)    收藏  举报