概要
「奇妙な幻想郷」の主要モジュールがすべて完成したため、これらを統合してプレイ可能なテキストベースのゲームとして動作させる必要があります。このタスクでは src/main.py を作成し、ゲーム全体の初期化・実行・終了処理を行うメインループを構築します。
ファイルと依存関係
- パス:
src/main.py - 必要なモジュール(すべて
src/game_logic/配下):config: ゲーム設定値(例:TOTAL_LAYERS,ROUNDS_PER_LAYERなど)morality: 善悪値管理および判定機能card_acquisition: カード取得条件のチェックscene_generator: 各層のシナリオ生成layer_event_generator: 層間イベント生成option_outcome_generator: 選択肢による結果生成boss_fight_loop: ボス戦の実施commands: システムコマンドのハンドリング
- 標準ライブラリ:
os,sys,logging,random,json - 静的テキスト:
texts/welcome.txtおよびtexts/endings/内の各種エンドファイル
関数設計
1. メイン関数 main()
def main():
"""
ゲームの開始点。状態の初期化、メインループの実行、終了時のエンド表示を行う。
"""
pass # 実装は以下参照
2. 補助関数
read_text(path): テキストファイルを読み込む。失敗時はデフォルトメッセージを使用。display_end(type_, stats): 指定されたタイプに対応するエンドテキストを表示。execute_choice(option, state, data): プレイヤーの選択肢に応じた処理を行い、更新された状態や終了フラグを返す。
メインループフロー
1. 初期設定
configから全設定情報を取得- ゲーム状態辞書を初期化:
status = { 'level': 1, 'turn': 1, 'virtue': 0, 'balance': 0, 'vice': 0, 'streak': 0, 'prev_action': None, 'normal_cards': [], 'universal_cards': [], 'in_battle': False, 'finished': False, 'result': None } - ウェルカムメッセージを表示:
welcome_msg = read_text('texts/welcome.txt') - 必要であればヘルプ表示を促す
2. レベルごとのループ
while status['level'] <= config.TOTAL_LAYERS and not status['finished']:
current_level = status['level']
# シナリオ生成
scenario = generate_layer_scenes(
layer=current_level,
rounds_per_layer=config.ROUNDS_PER_LAYER,
morality_ratios=get_morality_ratios(status['virtue'], status['balance'], status['vice'])
)
if not scenario:
print("異常発生によりゲームを中断します")
break
print(scenario['opening'])
collected_cards = 0
status['normal_cards'] = []
for turn_info in scenario['events']:
current_turn = turn_info['turn']
status['turn'] = current_turn
print(f"\n【第{current_level}層 第{current_turn}ラウンド】")
print(turn_info['description'])
for key in ['A','B','C','D']:
print(f" {key}. {turn_info['choices'][key]['label']}")
while True:
user_input = input("> ").strip()
if not user_input:
continue
command_response = handle_system_command(user_input, status, None)
if command_response['exit']:
status['finished'] = True
status['result'] = 'quit'
break
if command_response['message']:
print(command_response['message'])
if '不明なコマンド' in command_response['message']:
continue
else:
continue
choice_key = user_input.upper()
if choice_key in ['A', 'B', 'C', 'D']:
result = execute_choice(choice_key, status, turn_info, collected_cards)
status.update(result['updated_status'])
collected_cards = result['collected_count']
if result['ended']:
status['finished'] = True
status['result'] = result['outcome']
break
else:
print("A/B/C/Dまたはシステムコマンドを入力してください。HELPで詳細確認")
if status['finished']:
break
if status['finished']:
break
# ボス層かどうかの判定
if current_level in config.BOSS_LAYERS:
battle_instance = BossFight(
player_deck=status['normal_cards'] + status['universal_cards'],
morality_ratios=get_morality_ratios(status['virtue'], status['balance'], status['vice'])
)
status['in_battle'] = True
outcome = run_boss_fight(battle_instance, status)
status['in_battle'] = False
if outcome == 'player':
status['normal_cards'] = []
status['universal_cards'] = []
status['level'] += 1
status['turn'] = 1
elif outcome == 'boss':
status['finished'] = True
status['result'] = 'boss_defeat'
else:
status['finished'] = True
status['result'] = 'quit'
else:
intermission_event = generate_layer_event(
layer=current_level,
morality_ratios=get_morality_ratios(status['virtue'], status['balance'], status['vice'])
)
if not intermission_event:
intermission_event = DEFAULT_EVENT
print("\n【層間変動】")
print(intermission_event['summary'])
for k in ['A','B','C','D']:
trap_indicator = " [罠]" if intermission_event['actions'][k]['trap'] else ""
print(f" {k}. {intermission_event['actions'][k]['label']}{trap_indicator}")
while True:
action = input("> ").strip().upper()
if action in ['A','B','C','D']:
selected = intermission_event['actions'][action]
print(selected['effect'])
if not selected['trap'] and selected['reward']:
reward_card = {
'kind': 'universal',
'title': selected['reward'],
'detail': selected['desc'],
'origin': current_level
}
status['universal_cards'].append(reward_card)
print(f"ユニバーサルカード入手:{reward_card['title']} - {reward_card['detail']}")
else:
print("カードは獲得できませんでした。")
break
else:
cmd_res = handle_system_command(action, status, None)
if cmd_res['exit']:
status['finished'] = True
status['result'] = 'quit'
break
if cmd_res['message']:
print(cmd_res['message'])
if status['finished']:
break
status['level'] += 1
status['turn'] = 1
3. 選択肢処理関数 execute_choice
def execute_choice(choice, status, data, count):
should_receive = should_get_card(
total_rounds=config.ROUNDS_PER_LAYER,
current_round=status['turn'],
cards_obtained=count,
max_cards=config.MAX_CARDS_PER_LAYER
)
card_kind = None
if should_receive:
mapping = {'A': 'virtue', 'B': 'balance', 'C': 'vice', 'D': random.choice(['virtue', 'vice'])}
card_kind = mapping.get(choice)
effect = generate_option_outcome(
scene_description=data['description'],
option_text=data['choices'][choice]['label'],
morality_ratios=get_morality_ratios(status['virtue'], status['balance'], status['vice']),
card_type=card_kind
)
print(effect['response'])
if card_kind and effect['item']:
item = {
'category': card_kind,
'name': effect['item'],
'info': effect['details'],
'stage': status['level'],
'phase': status['turn']
}
status['normal_cards'].append(item)
print(f"カード取得:{item['name']} - {item['info']}")
count += 1
updated_virtue, updated_balance, updated_vice, updated_streak, final_result = update_morality(
current_round=(status['level']-1)*config.ROUNDS_PER_LAYER + status['turn'],
last_choice=status['prev_action'],
current_choice=choice,
good=status['virtue'],
neutral=status['balance'],
evil=status['vice'],
consecutive=status['streak']
)
status['virtue'] = updated_virtue
status['balance'] = updated_balance
status['vice'] = updated_vice
status['streak'] = updated_streak
status['prev_action'] = choice
if final_result:
display_end(final_result, get_morality_ratios(updated_virtue, updated_balance, updated_vice))
return {
'updated_status': status,
'collected_count': count,
'ended': True,
'outcome': final_result
}
return {
'updated_status': status,
'collected_count': count,
'ended': False,
'outcome': None
}
4. 結末表示関数
def display_end(end_type, ratios=None):
endings_folder = os.path.join(os.path.dirname(__file__), '..', 'texts', 'endings')
file_map = {
'virtue': 'ending_extreme_good.txt',
'vice': 'ending_extreme_evil.txt',
'balance': 'ending_extreme_neutral.txt',
'clever': 'ending_trick.txt',
'normal_virtue': 'ending_good.txt',
'normal_vice': 'ending_evil.txt',
'normal_balance': 'ending_neutral.txt',
'boss_defeat': 'ending_boss_loss.txt',
'quit': 'ending_quit.txt'
}
5. ファイル読み込み関数
def read_text(path):
try:
with open(path, 'r', encoding='utf-8') as file:
return file.read()
except Exception as error:
logging.error(f"ファイル読み込みエラー {path}: {error}")
return ""
ログ出力設定
import logging
logging.basicConfig(level=config.LOG_LEVEL, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
注意事項
- モジュールのインポートはプロジェクト構造に基づき適切に行うこと(相対パスまたは絶対パス)
- LLM呼び出しによる
None応答にはデフォルト値での代替処理が必要 - ボス戦中は既存のコマンドハンドラを再利用可能
テスト要件
- ゲーム全体の実行確認(エラーなし)
- 各コマンドと選択肢の動作検証
- 特殊エンド条件のトリガー確認
- ボス戦勝利・敗北ケースの検証
提出物
src/main.pyの完成版コードtexts/ディレクトリ以下の必要なファイル群- 明確なコメントと説明付きのコード
python src/main.pyで正常起動すること