任务:实现游戏主循环(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_ratioscard_acquisition:提供should_get_cardscene_generator:提供generate_layer_sceneslayer_event_generator:提供generate_layer_eventoption_outcome_generator:提供generate_option_outcomeboss_fight_loop:提供run_boss_fightcommands:提供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 初始化
- 加载配置(从
config模块导入config对象)。 - 初始化游戏状态字典
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 } - 读取欢迎语:
welcome_text = load_text_file('texts/welcome.txt'),并显示。 - 可选显示规则?可以提示玩家输入
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_LAYERSconfig.ROUNDS_PER_LAYERconfig.BOSS_LAYERSconfig.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.main或python 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能正常开始游戏。
开始实现吧!如有疑问,随时沟通。

浙公网安备 33010602011771号