diff --git "a/app.py" "b/app.py" --- "a/app.py" +++ "b/app.py" @@ -1,1224 +1,527 @@ +# --- 페이지 설정 (스크립트 최상단) --- +# 이 부분이 가장 먼저 실행되어야 합니다! +st.set_page_config(layout="wide") + # -*- coding: utf-8 -*- import streamlit as st import random import time import math -# --- 페이지 설정 (스크립트 최상단) --- -# 이 부분이 가장 먼저 실행되어야 합니다! -st.set_page_config(layout="wide") - # --- 게임 설정 --- -MAX_YEARS = 5 +# MAX_YEARS는 시나리오별로 다르게 설정될 수 있음 +# QUARTERLY_ACTIONS_LIMIT도 시나리오/상황에 따라 조절 가능 +MAX_TURNS_PER_SCENARIO = 10 # 예시: 한 시나리오당 10턴 QUARTERLY_ACTIONS_LIMIT = 1 -# --- 난이도 설정 --- -DIFFICULTY_LEVELS = { - "쉬움": { - "initial_capital": 20, - "yearly_gain": 10, - "initial_approval_range": (50, 70), - "initial_congress_relations_range": (55, 75), - "initial_stability": 100, - "event_impact_modifier": 0.8, - "checks_balances_leniency": 0.15, - "vocab_level": "쉬움" # 어휘 수준 키 - }, - "보통": { - "initial_capital": 15, - "yearly_gain": 8, - "initial_approval_range": (40, 60), - "initial_congress_relations_range": (40, 60), - "initial_stability": 100, - "event_impact_modifier": 1.0, - "checks_balances_leniency": 0.0, - "vocab_level": "보통" - }, - "어려움": { - "initial_capital": 10, - "yearly_gain": 6, - "initial_approval_range": (30, 50), - "initial_congress_relations_range": (25, 45), - "initial_stability": 90, - "event_impact_modifier": 1.2, - "checks_balances_leniency": -0.15, - "vocab_level": "어려움" +# --- 시나리오 설정 --- +SCENARIOS = { + "4.19_revolution": { + "display_name": "4.19 혁명 (1960)", + "start_year": 1960, + "initial_capital": 10, # 당시 이승만 정부의 정치적 영향력 + "initial_approval": 30, # 부정선거 직후 상황 가정 + "initial_stability": 60, # 불안정한 사회 분위기 + "initial_resistance": 20, # 국민 저항도 (새로운 지표) + "vocab_level": "보통", # 기본 어휘 수준 + "max_turns": 8, # 1960년 3월 ~ 4월 말까지의 주요 사건을 턴으로 표현 + "player_role": "이승만 정부" + }, + "5.18_gwangju": { + "display_name": "5.18 광주 민주화운동 (1980)", + "start_year": 1980, + "initial_capital": 15, # 신군부의 권력 장악력 + "initial_approval": 25, # 유신 종식 후 민주화 열망 속 신군부 등장 + "initial_stability": 40, # 매우 불안정 + "initial_resistance": 30, + "vocab_level": "보통", + "max_turns": 10, # 5월 중순 ~ 말까지의 사건 + "player_role": "신군부 계엄사령부" + }, + "june_struggle": { + "display_name": "6월 민주 항쟁 (1987)", + "start_year": 1987, + "initial_capital": 12, # 전두환 정부 말기 + "initial_approval": 35, + "initial_stability": 50, + "initial_resistance": 40, + "vocab_level": "보통", + "max_turns": 12, # 1987년 상반기 주요 사건 + "player_role": "전두환 정부" } } -# --- 난이도별 텍스트 저장소 --- -# (이하 ALL_TEXTS 사전 정의는 이전 답변과 동일하게 유지됩니다) +# --- 난이도별 텍스트 저장소 (역사 시나리오에 맞게 대폭 수정) --- ALL_TEXTS = { - # 대시보드 용어 - "term_approval": { - "쉬움": "국민들이 얼마나 좋아하나 (인기 점수)", - "보통": "국정 지지도", - "어려움": "국정 운영에 대한 국민적 지지율" - }, - "term_political_capital": { - "쉬움": "대통령 힘 점수 (정치력)", - "보통": "대통령 정치력", - "어려움": "대통령의 정책 추진 동력 (정치적 자본)" - }, - "term_stability": { - "쉬움": "나라가 얼마나 안정적인가", - "보통": "국가 안정도", - "어려움": "국가 시스템 안정성 지수" - }, - "term_congress_relations": { - "쉬움": "국회랑 얼마나 친한가", - "보통": "국회 관계 점수", - "어려움": "대정부 입법부 협력/견제 지수" - }, - "term_policy_goals": { - "쉬움": "대통령 약속 진행 상황", - "보통": "주요 공약 달성률", - "어려움": "핵심 국정 과제 이행률" - }, - # 기관 이름 - "term_congress_name": { - "쉬움": "국회 (법 만드는 곳)", - "보통": "국회 (입법부)", - "어려움": "입법부 (국회)" - }, - "term_judiciary_name": { - "쉬움": "법원 (법 지키는지 보는 곳)", - "보통": "법원/헌법재판소 (사법부)", - "어려움": "사법부 (법원 및 헌법재판소)" - }, - # UI 제목/라벨 - "dashboard_title": { - "쉬움": "📊 나라 상황판", - "보통": "📊 국정 현황판", - "어려움": "📊 국정 운영 대시보드" - }, - "dashboard_term": { - "쉬움": "대통령 {year}년차, {quarter}번째 계절", - "보통": "임기 {year}년차 {quarter}분기", - "어려움": "대통령 임기 {year}년차 제 {quarter}사분기" - }, - "institutions_title": { - "쉬움": "다른 중요 기관들 소식", - "보통": "기관 동향", - "어려움": "타 국가기관 현황 브리핑" - }, - "cabinet_title": { - "쉬움": "장관님들 소식", - "보통": "내각 현황", - "어려움": "행정 각 부처 장관 동향" - }, - "agenda_title": { - "쉬움": "📰 이번 계절 보고서", - "보통": "📰 분기별 보고", - "어려움": "📰 분기별 국정 현안 보고" - }, - "agenda_event_title": { - "쉬움": "이번 계절의 일: {title}", - "보통": "현안/기회: {title}", - "어려움": "분기 현안 브리핑: {title}" - }, - "briefing_title": { - "쉬움": "📝 비서실 도움말", - "보통": "📝 보좌진 브리핑 및 대응 방안", - "어려움": "📝 참모진 현안 분석 및 권고안" - }, - "office_title": { - "쉬움": "🏛️ 대통령 방", - "보통": "🏛️ 대통령 집무실", - "어려움": "🏛️ 대통령 집무실" # 동일하게 사용 - }, - "office_available_capital": { - "쉬움": "쓸 수 있는 힘 점수: **{capital} P**", - "보통": "가용 정치력: **{capital} P**", - "어려움": "가용 정치적 자본: **{capital} P**" - }, - "office_action_limit": { - "쉬움": "이번 계절에 할 수 있는 큰 결정: {remaining}번 남음", - "보통": "이번 분기 행동 가능 횟수: {remaining}회", - "어려움": "현 분기 주요 의사결정 잔여 횟수: {remaining}회" - }, - "office_issue_actions": { - "쉬움": "지금 생긴 일 해결하기", - "보통": "현안 관련 조치", - "어려움": "당면 현안 대응 옵션" - }, - "office_standard_actions": { - "쉬움": "평소에 할 수 있는 일들", - "보통": "상시 국정 활동", - "어려움": "상시 국정 운영 활동" - }, - "term_event_log": { - "쉬움": "그동안 있었던 일들 기록", - "보통": "국정 운영 로그", - "어려움": "국정 운영 타임라인 기록" - }, - "button_next_quarter": { - "쉬움": "➡️ 다음 계절로!", - "보통": "➡️ 다음 분기 진행", - "어려움": "➡️ 차기 분기 개시" - }, - "button_restart": { - "쉬움": "다시 해볼래!", - "보통": "다시 시작하기", - "어려움": "게임 재시작" - }, - # 이벤트 제목 예시 - "event_title_minor_disaster": { "쉬움": "작은 재난 발생", "보통": "소규모 재난 발생", "어려움": "국지적 자연재해 발생"}, - "event_desc_minor_disaster": { "쉬움": "비가 많이 오거나 불이 나서 동네 몇 군데가 피해를 입었어요. 빨리 도와줘야 해요.", "보통": "집중 호우 또는 산불 등으로 특정 지역에 피해가 발생했습니다. 신속한 지원이 필요합니다.", "어려움": "국지성 호우 또는 산불로 인해 일부 지역에서 물적 피해가 보고되었습니다. 긴급 구호 및 복구 계획 수립이 요구됩니다."}, - "action_visit_disaster_area": { "쉬움": "피해 지역 가서 위로하기", "보통": "피해 지역 현장 방문 및 지원 약속", "어려움": "피해 지역 순방 및 정부 차원 지원 공표"}, - - "event_title_small_protest": { "쉬움": "사람들이 모여서 외치고 있어요", "보통": "소규모 시위 발생", "어려움": "제한적 규모의 집회 발생"}, - "event_desc_small_protest": { "쉬움": "어떤 문제 때문에 몇몇 사람들이 모여 목소리를 내고 있어요. 시끄럽지만 큰 문제는 아니에요.", "보통": "특정 현안에 대한 불만으로 일부 시민들이 소규모 집회를 열고 있습니다. 상황 관리가 필요합니다.", "어려움": "특정 정책 또는 사회 현안에 대한 이견 표출로 소규모 집회가 개최되었습니다. 확산 가능성 주시 요망."}, - "action_meet_protesters": { "쉬움": "시위대 이야기 들어보기", "보통": "시위대 대표 면담 추진", "어려움": "집회 주동자 대화 채널 구축 시도"}, - - "event_title_price_rise": {"쉬움": "물건값이 조금 올랐어요", "보통": "물가 소폭 상승", "어려움": "소비자 물가 지수 소폭 상승"}, - "event_desc_price_rise": {"쉬움": "가게 물건값이 조금 올라서 사람들이 걱정해요. 안심시켜야 해요.", "보통": "일부 품목의 가격 상승으로 소비자 물가가 소폭 상승했습니다. 시장 불안 심리 차단이 필요합니다.", "어려움": "주요 소비재 가격 인상으로 인해 단기 인플레이�� 압력이 감지됩니다. 선제적 시장 안정화 조치가 요구됩니다."}, - "action_address_prices": {"쉬움": "물가 괜찮다고 말하기", "보통": "물가 안정 노력 관련 대국민 메시지 발표", "어려움": "물가 안정 기조 유지 관련 정부 입장 표명"}, - - "event_title_summit_offer": {"쉬움": "다른 나라 대통령 만나자고 해요", "보통": "정상회담 개최 제안", "어려움": "양자 정상회담 개최 제의 접수"}, - "event_desc_summit_offer": {"쉬움": "이웃 나라 대통령이 만나자고 연락 왔어요. 친하게 지내면 좋아요.", "보통": "주요 동맹국/주변국 정상으로부터 양자 회담 개최 제안이 왔습니다. 외교적 성과 기회입니다.", "어려움": "주요 외교 파트너 국가 수반의 공식적인 양자 정상회담 제의가 접수되었습니다. 의제 조율 및 전략 수립이 필요합니다."}, - "action_accept_summit": {"쉬움": "만나서 좋은 얘기하기", "보통": "정상회담 적극 수락 및 의제 준비 지시", "어려움": "정상회담 제의 수락 및 실무 준비 TF 구성 지시"}, - - "event_title_host_event": {"쉬움": "큰 국제 행사 우리나라에서 열까?", "보통": "대규모 국제 행사 유치 가능성", "어려움": "메가 이벤트 국내 유치 타당성 검토"}, - "event_desc_host_event": {"쉬움": "올림픽 같은 큰 행사를 우리나라에서 열 수 있을지 알아보고 있대요. 좋겠죠?", "보통": "대규모 국제 스포츠/문화 행사 유치 경쟁에서 유리한 고지를 점했다는 보고입니다. 국가 이미지 제고 효과가 기대됩니다.", "어려움": "주요 국제 스포츠/문화 행사의 차기 개최지 선정과 관련하여 유력 후보로 부상했다는 분석입니다. 국가 브랜드 가치 제고 및 경제적 파급 효과가 예상됩니다."} , - "action_support_hosting": {"쉬움": "행사 유치 돕기", "보통": "국제 행사 유치위원회에 예산/행정 지원 강화", "어려움": "범정부 차원의 국제 행사 유치 지원 체계 구축"}, - - "event_title_tech_investment": {"쉬움": "새로운 기술에 투자할까?", "보통": "신기술 분야 투자 적기 보고", "어려움": "미래 전략기술 분야 투자 최적 시점 분석"}, - "event_desc_tech_investment": {"쉬움": "미래에 중요해질 새 기술에 투자하면 좋겠다는 보고예요. 돈 벌 수 있을까요?", "보통": "미래 산업 관련 핵심 신기술 분야에 대한 국내외 투자 환경이 유리하다는 분석입니다. 정부 지원 시 관련 공약 달성에 도움이 될 수 있습니다.", "어려움": "차세대 핵심 기술 분야의 R&D 및 상용화 투자 환경이 우호적으로 조성되었다는 분석 보고입니다. 국가 경쟁력 강화 및 관련 국정 과제 이행의 기회입니다."}, - "action_review_tech_funding": {"쉬움": "새 기술 연구비 지원 알아보기", "보통": "신기술 분야 R&D 예산 증액 국회 요청 준비", "어려움": "전략기술 R&D 예산 확대 편성 및 입법부 설득 전략 수립"}, - - "event_title_opposition_criticism": {"쉬움": "야당이 {minister} 장관 싫어해요", "보통": "야당, {minister} 장관 책임론 제기", "어려움": "야당의 {minister} 장관에 대한 문책 요구"}, - "event_desc_opposition_criticism": {"쉬움": "국회 반대편 의원들이 {minister} 장관이 일 못한다고 계속 비판해요.", "보통": "야당에서 {minister} 장관의 정책 실패 또는 부적절한 처신을 이유로 책임론을 강하게 제기하고 있습니다.", "어려움": "주요 야당은 {minister} 장관의 직무 수행 능력 및 정책적 판단에 심각한 문제가 있다며 사퇴 또는 해임 건의를 검토 중입니다."}, - "action_defend_minister": {"쉬움": "{minister} 장관 편들어주기", "보통": "{minister} 장관 엄호 및 야당 설득 시도", "어려움": "{minister} 장관에 대한 신임 재확인 및 야당 공세 대응"}, - - "event_title_party_dissent": {"쉬움": "우리 편에서도 다른 소리가 나와요", "보통": "여당 내 특정 현안 관련 이견 노출", "어려움": "집권여당 내 정책 노선 관련 이견 표출"}, - "event_desc_party_dissent": {"쉬움": "대통령이랑 같은 편 의원들 중에서도 정부가 하는 일에 대해 다른 생각을 말하기 시작했어요.", "보통": "정부 주요 정책 방향에 대해 여당 내 일부 의원들이 공개적으로 다른 목소리를 내기 시작했습니다. 내부 조율이 필요해 보입니다.", "어려움": "주요 국정 현안에 대한 당정 간 이견이 일부 언론을 통해 노출되었습니다. 당내 결속력 약화 및 정책 추진 동력 저하가 우려됩니다."}, - "action_meet_party_members": {"쉬움": "우리 편 의원들 만나서 얘기하기", "보통": "여당 지도부/중진 의원들과 비공개 회동", "어려움": "여당 의원들과의 정책 간담회를 통한 내부 이견 조율"}, - - "event_title_media_criticism": {"쉬움": "신문/뉴스에서 정부 욕해요", "보통": "언론의 특정 정책 비판 보도", "어려움": "주요 언론의 정부 정책 비판 기조 강화"}, - "event_desc_media_criticism": {"쉬움": "신문이나 뉴스에서 정부가 하는 일 하나를 콕 집어서 나쁘다고 말해요. 사람들이 믿으면 어쩌죠?", "보통": "주요 언론에서 정부의 특정 정책에 대한 문제점을 지적하거나 부정적인 여론을 부각하는 보도가 이어지고 있습니다.", "어려움": "복수의 주요 언론 매체에서 특정 정부 정책의 문제점 및 부작용을 집중적으로 보도하며 비판적 여론 형성을 주도하고 있습니다."}, - "action_media_interview": {"쉬움": "기자들 만나서 설명하기", "보통": "언론 인터뷰를 통해 정책 설명 및 오해 해소", "어려움": "대통령 또는 주무 장관의 언론 브리핑을 통한 정책 정당성 설파"}, - - "event_title_congress_data_request": {"쉬움": "국회에서 자료 달래요", "보통": "국회, 정부 정책 자료 요구", "어려움": "국회의 행정부에 대한 자료 제출 요구"}, - "event_desc_congress_data_request": {"쉬움": "국회의원들이 정부가 하는 일에 대해 자세히 알려달라고 자료를 내래요. 귀찮네요.", "보통": "국회 상임위원회에서 정부가 추진 중인 정책과 관련된 상세 자료 제출을 공식적으로 요구했습니다.", "어려움": "국회 소관 상임위원회에서 특정 정책의 추진 경과 및 예산 집행 내역 등 민감한 자료의 제출을 요구하여 행정부 부담이 가중되고 있습니다."}, - "action_submit_data": {"쉬움": "자료 잘 만들어서 주기", "보통": "성실히 자료 제출 준비 지시", "어려움": "국회 요구 자료의 신속하고 정확한 제출 지시"}, - "action_minimize_data": {"쉬움": "자료 조금만 주기 (혼날지도?)", "보통": "자료 제출 범위 최소화 검토 (관계 악화 위험)", "어려움": "제출 자료 범위에 대한 법적 검토 및 최소 수준 제출 추진"}, - - "event_title_congress_hearing_review": {"쉬움": "국회가 청문회 할지 고민 중이래요", "보통": "국회, 특정 사안 청문회 개최 검토", "어려움": "국회의 특정 현안 관련 청문회 개최 검토"}, - "event_desc_congress_hearing_review": {"쉬움": "국회에서 시끄러운 문제에 대해 담당자 불러서 물어보는 청문회를 열지 말지 생각 중이래요. 열리면 골치 아파요.", "보통": "국회에서 논란이 되는 특정 사안에 대해 관계 부처 장관 등을 출석시켜 질의하는 청문회 개최 여부를 검토 중입니다. 개최 시 정부에 부담이 될 수 있습니다.", "어려움": "특정 국정 현안의 진상 규명 또는 책임 소재 파악을 위해 국회 차원의 청문회 개최 필요성이 제기되어 관련 상임위에서 논의가 진행 중입니다."}, - "action_prevent_hearing": {"쉬움": "청문회 못 열게 막아보기", "보통": "여당 통해 청문회 개최 저지 또는 연기 시도", "어려움": "여당과 협력하여 청문회 개최의 부당성 설파 및 무산 유도"}, - "action_prepare_hearing": {"쉬움": "청문회 잘 대답하게 준비하기", "보통": "청문회 대비 관계부처 합동 준비 지시", "어려움": "예상 쟁점 분석 및 대응 논리 개발 등 청문회 철저 대비 지시"}, - - "event_title_judiciary_lawsuit": {"쉬움": "누가 정부를 법원에 고소했어요", "보통": "법원, 정부 정책 관련 소송 접수", "어려움": "정부 정책 관련 행정소송 제기"}, - "event_desc_judiciary_lawsuit": {"쉬움": "정부가 한 일이 잘못됐다면서 누가 법원에 고소했어요. 재판에서 지면 큰일나요.", "보통": "정부가 시행한 특정 정책의 위법 또는 부당성을 주장하며 시민 또는 단체가 행정소송을 제기했습니다. 판결 결과에 따라 정책 추진에 차질이 발생할 수 있습니다.", "어려움": "정부의 특정 행정 처분 또는 부작위에 대해 그 효력을 다투는 행정소송이 관할 법원에 접수되었습니다. 사법부의 판단에 따라 해당 정책의 지속 가능성이 결정될 수 있습니다."}, - "action_prepare_legal_defense": {"쉬움": "변호사 시켜서 잘 싸우라고 하기", "보통": "법무부에 법적 대응 논리 개발 및 준비 지시", "어려움": "법무부 주관 하에 정부 소송대리인단 구성 및 적극적 변론 준비 지시"}, - "action_persuade_public": {"쉬움": "국민들에게 우리 편 들어달라고 설득하기", "보통": "정책의 정당성 관련 대국민 홍보 강화", "어려움": "해당 정책의 공익성 및 불가피성에 대한 적극적 여론 형성 노력"}, - - "event_title_routine_report": { "쉬움": "이번 계절은 조용했어요", "보통": "일상 국정 보고", "어려움": "정기 국정 운영 현황 보고"}, - "event_desc_routine_report": { "쉬움": "이번 계절은 별일 없었어요. 하던 일 계속하면 돼요.", "보통": "이번 분기는 특별한 현안 없이 국정이 비교적 안정적으로 운영되고 있다는 보고입니다. 기존 정책 추진에 집중할 시기입니다.", "어려움": "현 분기 특이사항 없이 안정적인 국정 운영 기조가 유지되고 있다는 정례 보고입니다. 기존 정책과제의 지속적인 추진이 권고됩니다."}, - "action_check_goal_progress": { "쉬움": "약속 얼마나 지켰나 보기", "보통": "주요 공약 진행 상황 점검", "어려움": "핵심 국정 과제 이행 현황 중간 점검"}, - "action_strengthen_pr": { "쉬움": "정부가 잘하고 있다고 자랑하기", "보통": "정책 홍보 활동 강화 지시", "어려움": "국정 운영 성과 홍보 전략 강화 지시"}, - - # 행동 관련 메시지 - "action_button_label": { # 버튼 레이블은 통일성 위해 보통 수준 유지 - "쉬움": "{action} ({cost} 힘 점수)", - "보통": "{action} ({cost}P)", - "어려움": "{action} (정치자본 {cost} 소모)" - }, - "action_cost_warning": { - "쉬움": "힘 점수가 모자라요!", - "보통": "정치력이 부족합니다!", - "어려움": "정치적 자본이 부족합니다!" - }, - "action_success_message": { - "쉬움": "'{action}' 하라고 시켰어요.", - "보통": "'{action}' 지시 완료.", - "어려움": "'{action}' 관련 대통령령 발령 완료." # 예시 - }, - "action_impact_approval": { - "쉬움": "국민 인기도 {change:+}%p 바뀔 듯", - "보통": "국정 지지도 {change:+}%p 변동 예상", - "어려움": "국민 지지율 {change:+}%p 변동 예측" - }, - "action_impact_stability":{ - "쉬움": "나라 안정 점수 {change:+}%p 바뀔 듯", - "보통": "국가 안정도 {change:+}%p 변동 예상", - "어려움": "국가 안정성 지수 {change:+}%p 변동 예측" - }, - "action_impact_congress":{ - "쉬움": "국회랑 관계 점수 {change:+}%p 바뀔 듯", - "보통": "국회 관계 점수 {change:+}%p 변동 예상", - "어려움": "대 국회 관계 지수 {change:+}%p 변동 예측" - }, - "action_impact_goal":{ - "쉬움": "약속 '{goal}' {prog:+}%p 만큼 진행될 듯", - "보통": "공약 '{goal}' {prog:+}%p 달성 예상", - "어려움": "국정 과제 '{goal}' {prog:+}%p 진척 예상" - }, - # 연말 결산 - "checks_balances_title": { - "쉬움": "{year}년차, 한 해 돌아보기", - "보통": "{year}년차 연말 국정 결산 (견제와 균형)", - "어려움": "{year}년차 연말 국정 평가 (기관 간 상호작용 분석)" - }, - "checks_balances_bill_submitted": { - "쉬움": "[국회] '{bill_name}' 법안 내봤어요.", - "보통": "[국회] '{bill_name}' 법안 제출됨.", - "어려움": "[입법부] '{bill_name}' 의안 상정됨." - }, - "checks_balances_bill_passed": { - "쉬움": "[국회] '{bill_name}' 법안 통과! 👍", - "보통": "[국회] '{bill_name}' 법안 통과! 🎉", - "어려움": "[입법부] '{bill_name}' 의안 가결됨." - }, - "checks_balances_bill_failed": { - "쉬움": "[국회] '{bill_name}' 법안 통과 못했어요 😥", - "보통": "[국회] '{bill_name}' 법안 부결.", - "어려움": "[입법부] '{bill_name}' 의안 부결됨." - }, - "checks_balances_investigation": { - "쉬움": "[국회] 국회가 정부 하는 일 자세히 살펴보겠대요. 귀찮아질かも...", - "보통": "[국회] 특정 사안에 대한 국정조사/청문회가 발동되었습니다. 정부 부담 증가.", - "어려움": "[입법부] 국회의 국정조사권 또는 청문회 발동. 행정부 운영 부담 가중." - }, - "checks_balances_judiciary_ruling":{ - "쉬움": "[법원] '{case_name}' 사건 판결: {result}", - "보통": "[사법부] '{case_name}' 사건 판결: {result}", - "어려움": "[사법부] '{case_name}' 사안에 대한 사법적 판단: {result}" - }, - "checks_balances_judiciary_uphold":{ - "쉬움": "문제 없대요!", - "보통": "문제 없음 (합헌/적법)", - "어려움": "합헌 또는 적법 결정" - }, - "checks_balances_judiciary_strike_down":{ - "쉬움": "문제 있대요! 계획 바꾸거나 조심해야 함.", - "보통": "문제 있음! (위헌/위법) 정책 차질 발생.", - "어려움": "위헌 또는 위법 결정. 관련 정책 추진 제동." - }, - "checks_balances_summary":{ - "쉬움": "{year}년차 끝! (법안 통과: {passed}개, 실패: {failed}개)", - "보통": "{year}년차 결산 완료 (법안 통과: {passed}건, 부결: {failed}건)", - "어려움": "{year}년차 국정 평가 완료 (법안 가결: {passed}건, 부결: {failed}건)" - }, - # 게임 오버 - "game_over_title": { - "쉬움": "대통령 임기 끝! (총 {years}년)", - "보통": "임기 종료 (총 {years}년)", - "어려움": "대통령 임기 만료 (재임 기간: {years}년)" - }, - "game_over_subtitle": { - "쉬움": "내가 대통령으로 한 일 결과", - "보통": "최종 국정 운영 결과", - "어려움": "임기 만료 시점 국정 운영 평가" - }, - # 용어 사전 (쉬움/보통/어려움 순서) - "glossary_veto": { - "쉬움": "**법률안 거부권:** 국회가 만든 법이 마음에 안 들 때, 대통령이 '다시 생각해 보세요!'라고 돌려보낼 수 있는 힘. 하지만 국회 의원들이 아주 많이(2/3 이상) 찬성하면 거부 못함.", - "보통": "**법률안 거부권 (재의 요구권):** 국회가 통과시킨 법률안에 대해 대통령이 이의가 있을 때, 공포하지 않고 국회에 다시 심의해달라고 요구할 수 있는 권한. 국회는 재적의원 과반수 출석과 출석의원 3분의 2 이상의 찬성으로 재의결 가능.", - "어려움": "**법률안 재의 요구권 (거부권):** 입법부가 의결한 법률안에 대해 행정부 수반인 대통령이 이의를 제기하며 공포를 거부하고 입법부에 재심의를 요구하는 헌법상 권한. 국회 재의결 요건(재적 과반 출석, 출석 2/3 찬성) 충족 시 법률로 확정." - }, - "glossary_exec_order": { - "쉬움": "**대통령 명령:** 법을 잘 지키기 위해 대통령이 내리는 자세한 규칙. 법보다 높을 순 없음.", - "보통": "**행정명령 (대통령령):** 대통령이 법률에서 위임받은 사항이나 법률을 집행하기 위해 필요한 사항에 대해 발하는 명령. 법률의 범위 내에서만 유효.", - "어려움": "**대통령령 (집행명령/위임명령):** 헌법과 법률에 근거하여 대통령이 발하는 행정입법의 한 형태. 법률 집행을 위한 집행명령과 법률의 위임에 따른 위임명령으로 구분되며, 사법심사의 대상." - }, - "glossary_budget_proposal": { - "쉬움": "**예산안 만들기:** 내년에 나라 살림을 어떻게 쓸지 정부(대통령)가 계획 짜서 국회에 내는 것.", - "보통": "**예산안 편성권:** 정부(대통령)가 다음 해의 국가 살림 계획인 예산안을 짜서 국회에 제출할 수 있는 권한.", - "어려움": "**정부의 예산안 편성권:** 행정부가 차기 회계연도의 국가 재정 운용 계획인 예산안을 수립하여 입법부에 제출하는 헌법상 권한." - }, - "glossary_budget_approval": { - "쉬움": "**예산안 심사/확정:** 국회가 정부가 낸 예산안을 보고 '이렇게 써도 좋다!' 결정하는 것. 돈을 깎거나 (정부 동의 얻어) 늘릴 수 있음.", - "보통": "**국회 예산 심의/확정권:** 정부가 제출한 예산안을 국회가 심사하고 최종적으로 확정하는 권한. 정부 예산안을 삭감하거나 증액(정부 동의 필요)할 수 있음.", - "어려움": "**국회의 예산안 심의·확정권:** 정부가 제출한 예산안에 대해 국회가 심의를 거쳐 수정 또는 확정하는 권한. 재정 민주주의의 핵심 요소로, 감액은 자유로우나 증액 및 새 비목 설치는 정부 동의 필요." - }, - "glossary_judicial_review_law": { - "쉬움": "**법률 심사권 (헌법재판소):** 국회가 만든 법이 가장 큰 법인 '헌법'에 어긋나는지 아닌지 헌법재판소가 판단하는 힘.", - "보통": "**위헌법률심판권:** 국회가 만든 법률이 헌법에 위배되는지 여부를 헌법재판소가 심판하는 권한.", - "어려움": "**위헌법률심판 제청권/심판권:** 법률의 위헌 여부가 재판의 전제가 된 경우 법원의 제청 또는 당사자의 신청에 의해 헌법재판소가 해당 법률의 헌법 합치성을 심판하는 권한." - }, - "glossary_judicial_review_order":{ - "쉬움": "**명령/규칙 심사권 (대법원):** 대통령 명령 같은 규칙이 헌법이나 법률에 맞는지 대법원이 최종 판단하는 힘.", - "보통": "**명령·규칙·처분 심사권:** 대통령령 등 행정기관이 만든 명령이나 규칙, 또는 행정 처분이 헌법이나 법률에 위반되는지 여부를 대법원이 최종적으로 심사하는 권한.", - "어려움": "**명령·규칙·처분에 대한 최종 심사권:** 행정입법(명령, 규칙)이나 행정처분의 위헌·위법성이 재판의 전제가 된 경우, 대법원이 이를 최종적으로 심사하여 판단하는 권한." - }, - "glossary_parliamentary_audit":{ - "쉬움": "**국정감사/조사:** 국회가 정부가 일 잘 하고 있는지, 잘못은 없는지 감시하고 조사하는 활동.", - "보통": "**국정감사/조사권:** 국회가 정부의 국정 운영 전반이나 특정 사안에 대해 잘��된 점이 없는지 감사하거나 조사할 수 있는 권한. 행정부를 견제하는 주요 수단.", - "어려움": "**국정감사 및 조사권:** 국회가 정부의 국정 수행 실태를 파악하고 감독하기 위해 매년 정기적으로(감사) 또는 특정 현안에 대해(조사) 행사하는 헌법상 권한." - }, - "glossary_impeachment":{ - "쉬움": "**탄핵:** 대통령 같은 높은 공무원이 아주 큰 잘못(헌법/법률 위반)을 했을 때, 국회가 그만두게 해달라고 헌법재판소에 요청하는 것.", - "보통": "**탄핵소추권:** 대통령 등 고위 공직자가 직무상 중대한 헌법이나 법률 위반 시, 국회가 파면을 결정해달라고 헌법재판소에 청구할 수 있는 권한.", - "어려움": "**탄핵소추 의결권 및 심판 청구권:** 국회가 헌법 또는 법률을 중대하게 위반한 특정 고위 공직자에 대해 파면을 구하는 소추안을 의결하고 헌법재판소에 심판을 청구하는 권한." - }, - "glossary_confirmation_hearing":{ - "쉬움": "**인사청문회:** 대통령이 뽑으려는 장관 같은 높은 공무원이 그 일 할 능력 있는지, 문제는 없는지 국회가 미리 살펴보는 자리.", - "보통": "**인사청문회:** 대통령이 임명하는 고위 공직 후보자에 대해 국회가 직무수행 능력과 도덕성 등을 검증하는 절차. 일부 직위는 국회 동의가 필수적.", - "어려움": "**인사청문회 실시권:** 대통령이 임명하는 주요 공직 후보자의 자질, 능력, 도덕성 등을 국회 상임위원회 또는 특별위원회 차원에서 검증하는 제도. 임명동의안 처리 또는 임명 전 검증 절차로 기능." - }, - "glossary_political_capital":{ - "쉬움": "**정치력 (게임 점수):** 대통령이 자기 뜻대로 일을 할 때 쓰는 힘 점수. 중요한 결정 할 때 필요.", - "보통": "**정치력 (Political Capital):** 대통령이 자신의 정책을 추진하거나 영향력을 행사하는 데 사용할 수 있는 비공식적인 자원. 지지도, 협상력, 리더십 등을 포함하는 개념으로 게임 내 행동력으로 사용됨.", - "어려움": "**정치적 자본 (Political Capital):** 대통령이 국정 운영 목표 달성을 위해 동원할 수 있는 무형의 자산. 국민적 지지, 의회 내 영향력, 정책 정당성, 리더십 등을 포괄하며, 정치적 행위의 비용으로 소모됨." - }, - # 상시 행동 키 - "action_replace_minister": {"쉬움": "{minister} 장관 바꾸기", "보통": "{minister} 장관 교체", "어려움": "{minister} 장관 경질"}, - "action_prepare_bill": {"쉬움": "새로운 법 만들기 준비", "보통": "법률안 국회 제출 준비", "어려움": "입법안 발의 준비 착수"}, - "action_meet_ruling_party": {"쉬움": "우리 편 의원들이랑 밥 먹기", "보통": "여당 지도부와 만찬 회동", "어려움": "집권여당 지도부와 정례 회동"}, - "action_meet_opposition": {"쉬움": "반대편 의원들 만나보기", "보통": "야당 지도부 만나기 (관계 개선 시도)", "어려움": "주요 야당 지도부와 협의 채널 구축"}, - "action_meet_biz_leaders": {"쉬움": "회사 사장님들 만나기", "보통": "주요 경제 단체 간담회", "어려움": "주요 경제계 인사 초청 간담회"}, - "action_respect_judiciary": {"쉬움": "법원 존중한다고 말하기", "보통": "사법부 존중 메시지 발표", "어려움": "사법부 독립성 존중 관련 대국민 메시지 발표"}, - "action_no_special_action": {"쉬움": "이번엔 그냥 넘어가기", "보통": "이번 분기 특별 조치 없음", "어려움": "현 분기 특이 조치 보류"}, - # 상태 메시지 키 - "status_loading_report": {"쉬움": "{year}년 {quarter}번째 계절 보고서 만드는 중...", "보통": "{year}년 {quarter}분기 보고 준비 중...", "어려움": "{year}년 제 {quarter}사분기 국정 현안 브리핑 준비 중..."} , - "status_waiting_report": {"쉬움": "다음 계절 보고서 기다리는 중...", "보통": "다음 분기 보고를 기다리고 있습니다.", "어려움": "차기 분기 보고 대기 중..."} , - "status_action_limit_reached": {"쉬움": "이번 계절({quarter})에 할 큰 결정 다 했어요. 다음 계절로 넘어가요.", "보통": "이번 분기({quarter}Q) 주요 활동을 완료했습니다. 다음 분기로 넘어가세요.", "어려움": "현 분기({quarter}Q) 주요 의사결정 한도를 소진했습니다. 차기 분기로 진행하십시오."}, - # 사이드바 키 - "sidebar_title": {"쉬움": "메뉴", "보통": "사이드 메뉴", "어려움": "부가 기능 메뉴"}, - "sidebar_glossary_title": {"쉬움": "🏛️ 어려운 말 사전", "보통": "🏛️ 정부 용어 사전", "어려움": "🏛️ 주요 정부 용어 해설"}, - # 기타 UI 텍스트 - "error_invalid_action": {"쉬움": "잘못 골랐어요!", "보통": "잘못된 행동 선택입니다.", "어려움": "유효하지 않은 의사결정입니다."}, - "error_event_generation_failed": {"쉬움": "보고서 만드는데 실패했어요 ㅠㅠ", "보통": "분기 보고 생성에 실패했습니다.", "어려움": "분기 현안 브리핑 생성 중 오류가 발생했습니다."}, - "warning_briefing_loading": {"쉬움": "도움말 가져오는 중...", "보통": "브리핑 정보를 불러오는 중입니다.", "어려움": "참모진 권고안 로딩 중..."}, - "caption_no_goals": {"쉬움": "정한 약속이 없어요.", "보통": "설정된 공약이 없습니다.", "어려움": "설정된 국정 과제가 없습니다."}, - "caption_no_cabinet": {"쉬움": "장관님들 정보가 없어요.", "보통": "내각 정보가 없습니다.", "어려움": "행정 각료 정보가 없습니다."}, - "caption_no_pending_bills": {"쉬움": "국회에서 지금 얘기 중인 법안이 없어요.", "보통": "현재 특별히 논의 중인 법안 없음", "어려움": "현재 계류 중인 주요 의안 없음"}, - "caption_no_pending_cases": {"쉬움": "법원에서 지금 다루는 특별한 사건이 없어요.", "보통": "현재 특별한 사건 없음", "어려움": "현재 계류 중인 주요 사법 사건 없음"}, - "label_pending_bills": {"쉬움": "국회에서 얘기 중인 법안들", "보통": "논의 중인 법안", "어려움": "계류 의안 목록"}, - "label_pending_cases": {"쉬움": "법원에서 다루는 사건들", "보통": "진행 중인 주요 사건", "어려움": "계류 사법 사건 목록"}, - "log_initial_approval": {"쉬움": "처음 인기도", "보통": "초기 국정 지지도", "어려움": "임기 개시 시점 지지율"}, - "log_initial_goals": {"쉬움": "내가 하기로 한 약속들", "보통": "선택된 주요 공약", "어려움": "선정된 핵심 국정 과제"}, - "log_action_result": {"쉬움": "결과:", "보통": "결과:", "어려움": "파급 효과:"}, - "log_minister_change": {"쉬움": "{minister} 장관 바꿈.", "보통": "{minister} 장관 교체.", "어려움": "{minister} 장관 경질."}, - "log_minister_old_approval": {"쉬움": "전 장관 인기도", "보통": "구 장관 지지도", "어려움": "전임 장관 지지율"}, - "log_minister_new_approval": {"쉬움": "새 장관 인기도", "보통": "신 장관 지지도", "어려움": "신임 장관 지지율"}, - "log_bill_prep_start": {"쉬움": "'{bill_name}' 법 만들기 시작.", "보통": "'{bill_name}' 제출 준비 착수.", "어려움": "'{bill_name}' 입법 준비 개시."}, - "log_yearly_capital_gain": {"쉬움": "연말 보너스 힘 점수", "보통": "연말 보너스 정치력", "어려움": "연말 정치적 자본 회복"}, - "log_end_of_term": {"쉬움": "--- 대통령 임기 끝! ---", "보통": "--- 임기 종료 ---", "어려움": "--- 대통령 임기 만료 ---"}, - "log_start_year": {"쉬움": "--- {year}년차 시작! ---", "보통": "--- {year}년차 시작 ---", "어려움": "--- 임기 {year}년차 개시 ---"}, - "log_start_quarter": {"쉬움": "--- {year}년차 {quarter}번째 계절 시작! ---", "보통": "--- {year}년차 {quarter}분기 시작 ---", "어려움": "--- {year}년차 제 {quarter}사분기 개시 ---"}, - "log_error_minister_not_found": {"쉬움": "오류: {minister} 부서 못 찾음.", "보통": "오류: {minister} 부처를 찾을 수 없습니다.", "어려움": "오류: {minister} 부처 정보 부재."}, - "log_bill_pass_chance": {"쉬움": "'{bill_name}' 통과 가능성", "보통": "'{bill_name}' 통과 확률", "어려움": "'{bill_name}' 가결 확률"}, - "log_judiciary_case_received": {"쉬움": "[{judiciary_name}] '{case_name}' 사건 들어옴.", "보통": "[{judiciary_name}] '{case_name}' 사건 접수됨.", "어려움": "[{judiciary_name}] '{case_name}' 사안 계류됨."}, - "log_judiciary_strike_down_chance": {"쉬움": "'{case_name}' 문제 있다고 판결할 가능성", "보통": "'{case_name}' 위헌/위법 판결 확률", "어려움": "'{case_name}' 위헌/위법 결정 확률"}, - "log_natural_fluctuation": {"쉬움": "연말 점수 변화", "보통": "연말 자연 변동", "어려움": "연말 지표 자연 변동성"}, + # --- 공통 UI --- + "game_title": {"보통": "📜 격동의 현대사: 선택의 순간들"}, + "scenario_select_title": {"보통": "📜 시나리오 선택"}, + "scenario_select_button": {"보통": "이 시대로 가기"}, + "dashboard_title": {"보통": "📊 정부 상황판"}, + "dashboard_term": {"보통": "{year}년 {turn}번째 국면"}, # 분기 -> 국면/턴 + "term_approval": {"보통": "정권 지지도"}, + "term_political_capital": {"보통": "정치적 자산"}, + "term_stability": {"보통": "국가 안정도"}, + "term_resistance": {"보통": "국민 저항도"}, # 새로운 지표 + "historical_briefing_title": {"보통": "📜 역사 기록 브리핑"}, + "player_options_title": {"보통": "📝 대응 방안 선��"}, + "action_button_label": {"보통": "{action} ({cost} 정치자산 소모)"}, + "action_cost_warning": {"보통": "정치적 자산이 부족합니다!"}, + "action_success_message": {"보통": "'{action}' 지시 완료."}, + "button_next_turn": {"보통": "➡️ 다음 국면으로"}, + "button_restart_scenario": {"보통": "시나리오 다시 시작"}, + "button_back_to_scenario_select": {"보통": "다른 시나리오 선택"}, + "game_over_title": {"보통": "역사의 한 페이지가 넘어갑니다"}, # 게임 오버 -> 시나리오 종료 + "game_over_subtitle": {"보통": "주요 결정과 그 결과"}, + "term_event_log": {"보통": "주요 사건 기록"}, + "historical_source_title": {"보통": "사료 보기"}, + "status_loading_report": {"보통": "{year}년 {turn}번째 국면 준비 중..."}, + "status_action_limit_reached": {"보통": "이번 국면에 주요 결정을 모두 내렸습니다. 다음 국면으로 진행하세요."}, + + # --- 4.19 혁명 시나리오 관련 텍스트 --- + "scenario_419_name": {"보통": "4.19 혁명 (1960)"}, + "event_419_turn1_title": {"보통": "3.15 부정선거와 민심 이반"}, + "event_419_turn1_desc": {"보통": "1960년 3월 15일, 정부통령 선거에서 자유당 정권은 전례 없는 부정선거를 자행했습니다. 투표함 바꿔치기, 사전투표, 공개투표 등 불법 행위가 만연하여 국민들의 분노가 커지고 있습니다."}, + "event_419_turn1_source": {"보통": "[사료] 당시 신문 기사: '입후보자 명단에도 없는 사람이 당선되는 희대의 코미디...' (가상)"}, + "action_419_turn1_option1": {"보통": "부정선거 의혹 강력 부인 및 반정부 세력 엄단 경고"}, + "action_419_turn1_option2": {"보통": "일부 지역 재선거 검토 발표로 민심 수습 시도"}, + "action_419_turn1_option3": {"보통": "내무부 장관 경질 및 대국민 사과 발표"}, + + "event_419_turn2_title": {"보통": "마산 시위와 김주열 열사 발견"}, + "event_419_turn2_desc": {"보통": "3.15 부정선거에 항의하는 마산 지역 시위가 격화되던 중, 4월 11일 실종되었던 마산상고 학생 김주열 군이 눈에 최루탄이 박힌 처참한 모습으로 마산 앞바다에서 발견되었습니다. 이 사건은 전국적인 분노를 촉발시켰습니다."}, + "event_419_turn2_source": {"보통": "[사료] 김주열 열사 발견 당시 사진 (묘사): '교복 차림의 학생, 얼굴 형체를 알아보기 힘들 정도로 부어있고 오른쪽 눈에는 검은 물체가 박혀 있었다.'"}, + "action_419_turn2_option1": {"보통": "김주열 군 사망 유감 표명, 철저한 진상 조사 약속"}, + "action_419_turn2_option2": {"보통": "마산 지역 계엄령 선포 검토 및 시위 강경 진압 지시"}, + "action_419_turn2_option3": {"보통": "사건 축소 보도 및 불순분자 개입설 유포"}, + + "event_419_final_rhee_resignation_title": {"보통": "이승만 대통령 하야"}, + "event_419_final_rhee_resignation_desc": {"보통": "걷잡을 수 없이 확산되는 시위와 국민적 저항, 그리고 미국의 압력 끝에 1960년 4월 26일, 이승만 대통령은 결국 하야를 발표했습니다. 이로써 12년간 이어진 자유당 정권은 막을 내렸습니다."}, + "event_419_final_rhee_resignation_source": {"보통": "[사료] 이승만 대통령 하야 성명: '나는 국민이 원한다면 대통령직을 사임할 것이며... 또한 3.15 정부통령 선거에 많은 부정이 있었다 하니... 선거를 다시 하도록 지시하였고... 국민이 원한다면 내각책임제 개헌도 할 것이다.'"}, + + # (5.18, 6월 항쟁 관련 텍스트도 유사하게 추가) } - -# --- 어휘 조정 함수 (난이도별 텍스트 반환) --- +# --- 어휘 조정 함수 (기존과 동일) --- def get_text(key, level="보통"): - """난이도에 따라 다른 텍스트 반환, 없으면 '보통' -> key 순으로 fallback""" level_texts = ALL_TEXTS.get(key) if level_texts: if level in level_texts: return level_texts[level] elif "보통" in level_texts: - # st.warning(f"텍스트 키 '{key}'에 대한 '{level}' 난이도 텍스트 없음. '보통' 사용.") # 디버깅용 return level_texts["보통"] - # st.error(f"텍스트 키 '{key}' 찾을 수 없음!") # 디버깅용 - return key # 키 자체를 반환 (최후 fallback) - -# --- 정부 및 다른 기관 초기화 함수 --- -def initialize_government(difficulty): - """대통령 임기 시작 시 상태 및 다른 기관 정보 초기화 (난이도 적용)""" - settings = DIFFICULTY_LEVELS[difficulty] - vocab_level = settings["vocab_level"] - get_text_func = lambda k: get_text(k, vocab_level) # 초기화 시점 어휘 수준 적용 - - policy_options = ["경제 성장률 5% 달성", "청년 실업률 5% 이하 달성", "부동산 시장 안정화", "미래 산업 육성", "외교 관계 개선", "��방력 강화", "복지 예산 10% 증액", "탄소 배출 감축"] - selected_goals = random.sample(policy_options, 3) - initial_goals = {goal: 0 for goal in selected_goals} + return key - total_seats = 300 - gov_party_seats_base = random.randint(120, 170) - if difficulty == "어려움": gov_party_seats = min(gov_party_seats_base, random.randint(100, 155)) - elif difficulty == "쉬움": gov_party_seats = max(gov_party_seats_base, random.randint(145, 180)) - else: gov_party_seats = gov_party_seats_base - opp_party_seats = total_seats - gov_party_seats - random.randint(5, 15) - other_seats = total_seats - gov_party_seats - opp_party_seats +# --- 게임 상태 초기화 (시나리오 기반으로 변경) --- +def initialize_scenario_state(scenario_key): + scenario_settings = SCENARIOS[scenario_key] + vocab_level = scenario_settings["vocab_level"] + get_text_func = lambda k: get_text(k, vocab_level) return { - 'presidency_status': { - 'approval_rating': random.randint(*settings['initial_approval_range']), - 'political_capital': settings['initial_capital'], - 'policy_goals': initial_goals, - 'stability': settings['initial_stability'], - 'congress_relations': random.randint(*settings['initial_congress_relations_range']) + 'scenario_key': scenario_key, + 'player_role': scenario_settings['player_role'], + 'current_turn': 1, + 'max_turns': scenario_settings['max_turns'], + 'game_year': scenario_settings['start_year'], # 연도는 참고용 + 'status': { + 'approval_rating': scenario_settings['initial_approval'], + 'political_capital': scenario_settings['initial_capital'], + 'stability': scenario_settings['initial_stability'], + 'resistance': scenario_settings['initial_resistance'], # 새 지표 }, - 'other_branches': { - 'Congress': { - 'name': get_text_func("term_congress_name"), - 'seats': {'Government': gov_party_seats, 'Opposition': opp_party_seats, 'Others': other_seats}, - 'stance': '사안별 협조 및 견제' if difficulty == "보통" else ("대체로 협조적" if difficulty == "쉬움" else "대체로 비판적/견제"), - 'key_focus': random.sample(['민생 안정', '정부 견제', '개혁 입법', '지역 예산 확보'], 2), - 'pending_bills': [], - 'relations_score': random.randint(*settings['initial_congress_relations_range']) - }, - 'Judiciary': { - 'name': get_text_func("term_judiciary_name"), - 'status': '특별한 사건 없음', - 'independence_level': random.randint(70, 95) if difficulty != "어려움" else random.randint(80, 100), - 'pending_cases': [], - 'stance_modifier': 0 - } - }, - 'cabinet': { - '경제부': {'minister_approval': random.randint(30,70), 'performance': 50}, - '사회부': {'minister_approval': random.randint(30,70), 'performance': 50}, - '외교부': {'minister_approval': random.randint(30,70), 'performance': 50}, - '국방부': {'minister_approval': random.randint(30,70), 'performance': 50}, - }, - 'event_log': [f"제 {random.randint(19, 22)}대 대통령 임기 시작 ({difficulty})"] + 'event_log': [f"{get_text_func(f'scenario_{scenario_key}_name')} ({scenario_settings['player_role']}) 시작"], + 'historical_events_completed': [] # 진행된 주요 역사 이벤트 기록 } -# --- 세션 상태 초기화 --- -if 'game_started' not in st.session_state: - st.session_state['game_started'] = False -if 'difficulty' not in st.session_state: - st.session_state['difficulty'] = "보통" # 기본값 - -# --- 난이도 선택 화면 --- -if not st.session_state.game_started: - st.title("👑 대통령의 균형추: 삼권분립 리더십 시뮬레이션") - st.write("게임을 시작하기 전에 난이도를 선택해주세요. 난이도에 따라 게임 설명과 용어가 달라집니다.") - selected_difficulty = st.radio( - "게임 난이도를 선택하세요:", - list(DIFFICULTY_LEVELS.keys()), - index=1, - horizontal=True, - captions=["쉬운 단어와 설명으로 게임해요.", "표준적인 설명으로 게임해요.", "전문적인 용어와 설명으로 게임해요."] - ) - if st.button("게임 시작!"): - st.session_state['difficulty'] = selected_difficulty - st.session_state['game_year'] = 1 - st.session_state['game_quarter'] = 1 - st.session_state['game_state'] = initialize_government(selected_difficulty) - # 초기 로그 메시지 추가 (난이도별 텍스트 사용) - vocab_level_init = DIFFICULTY_LEVELS[selected_difficulty]["vocab_level"] # 여기에서 vocab_level 정의 - get_text_func_init = lambda k: get_text(k, vocab_level_init) # 초기화용 get_text 함�� - initial_approval = st.session_state.game_state['presidency_status']['approval_rating'] - st.session_state.game_state['event_log'].append(f"{get_text_func_init('log_initial_approval')}: {initial_approval}%") - goals = st.session_state.game_state['presidency_status']['policy_goals'].keys() - st.session_state.game_state['event_log'].append(f"{get_text_func_init('log_initial_goals')}: {', '.join(goals)}") - - st.session_state['current_quarterly_event'] = None - st.session_state['event_briefing'] = None - st.session_state['game_over'] = False - st.session_state['actions_taken_this_quarter'] = 0 - st.session_state['game_started'] = True - st.rerun() - st.stop() - -# --- 게임 진행 중 --- -# 현재 게임 난이도 및 어휘 수준 가져오기 -difficulty = st.session_state.difficulty -settings = DIFFICULTY_LEVELS[difficulty] -vocab_level = settings["vocab_level"] -get_text_func = lambda key: get_text(key, vocab_level) # 게임 전반에 사용할 get_text 함수 - -# --- 분기별 현안/기회 생성 함수 --- -def generate_quarterly_event(year, quarter, game_state, difficulty): - """매 분기 발생하는 현안/기회 생성 (난이도 및 견제 요소 반영)""" - settings = DIFFICULTY_LEVELS[difficulty] - vocab_level = settings["vocab_level"] - get_text_for_event = lambda k: get_text(k, vocab_level) # 이벤트 생성 시 사용할 get_text - presidency_status = game_state['presidency_status'] - congress = game_state['other_branches']['Congress'] - judiciary = game_state['other_branches']['Judiciary'] - - event_types = ["minor_issue", "opportunity", "political_move", "checks_balances_event", "random"] - weights = [30, 25, 20, 15, 10] - if difficulty == "어려움": weights = [35, 15, 25, 20, 5] - elif difficulty == "쉬움": weights = [20, 35, 15, 10, 20] - if congress['relations_score'] < 40: - weights[3] += 10 - weights[2] += 5 - - chosen_type = random.choices(event_types, weights=weights, k=1)[0] - event = {"title_key": "", "description_key": "", "type": chosen_type, "options": [], "context": {}} # 키로 저장, context 추가 - - # 이벤트 내용 정의 (키 사용) - if chosen_type == "minor_issue": - issues = [ - {"title_key": "event_title_minor_disaster", "desc_key": "event_desc_minor_disaster"}, - {"title_key": "event_title_small_protest", "desc_key": "event_desc_small_protest"}, - {"title_key": "event_title_price_rise", "desc_key": "event_desc_price_rise"}, - ] - selected = random.choice(issues) - event["title_key"] = selected["title_key"] - event["description_key"] = selected["desc_key"] - event["options"] = [ - {"action_key": "action_visit_disaster_area", "cost": math.ceil(2 * (1/settings['event_impact_modifier'])), "impact": {"approval": random.randint(1,3), "stability": random.randint(0,2)}}, - {"action_key": "action_meet_protesters", "cost": math.ceil(1 * (1/settings['event_impact_modifier'])), "impact": {"stability": random.randint(0,1)}}, - {"action_key": "action_address_prices", "cost": math.ceil(1 * (1/settings['event_impact_modifier'])), "impact": {"approval": random.randint(-1,2)}}, - ] - elif chosen_type == "opportunity": - opps = [ - {"title_key": "event_title_summit_offer", "desc_key": "event_desc_summit_offer"}, - {"title_key": "event_title_host_event", "desc_key": "event_desc_host_event"}, - {"title_key": "event_title_tech_investment", "desc_key": "event_desc_tech_investment"}, - ] - selected = random.choice(opps) - event["title_key"] = selected["title_key"] - event["description_key"] = selected["desc_key"] - event["options"] = [ - {"action_key": "action_accept_summit", "cost": math.ceil(2 * (1/settings['event_impact_modifier'])), "impact": {"approval": random.randint(0,2), "policy_goals": {"외교 관계 개선": random.randint(1,3)}}}, # 공약 이름은 일단 하드코딩 유지 - {"action_key": "action_support_hosting", "cost": math.ceil(3 * (1/settings['event_impact_modifier'])), "impact": {"stability": random.randint(1,2), "approval": random.randint(1,3)}}, - {"action_key": "action_review_tech_funding", "cost": math.ceil(2 * (1/settings['event_impact_modifier'])), "impact": {"policy_goals": {"미래 산업 육성": random.randint(2,5)}}}, - ] - elif chosen_type == "political_move": - minister_name = random.choice(list(game_state['cabinet'].keys())) if game_state['cabinet'] else "특정" - event["context"]["minister"] = minister_name # context에 장관 이름 저장 - moves = [ - {"title_key": "event_title_opposition_criticism", "desc_key": "event_desc_opposition_criticism"}, - {"title_key": "event_title_party_dissent", "desc_key": "event_desc_party_dissent"}, - {"title_key": "event_title_media_criticism", "desc_key": "event_desc_media_criticism"}, - ] - selected = random.choice(moves) - event["title_key"] = selected["title_key"] - event["description_key"] = selected["desc_key"] - # 옵션에도 context 전달 필요 시 추가 - event["options"] = [ - {"action_key": "action_defend_minister", "cost": math.ceil(2 * (1/settings['event_impact_modifier'])), "impact": {"congress_relations": random.randint(-4,1)}, "context": {"minister": minister_name}}, - {"action_key": "action_meet_party_members", "cost": math.ceil(1 * (1/settings['event_impact_modifier'])), "impact": {"political_capital": 1}}, - {"action_key": "action_media_interview", "cost": math.ceil(1 * (1/settings['event_impact_modifier'])), "impact": {"approval": random.randint(-2,2)}}, - ] - elif chosen_type == "checks_balances_event": - cb_events = [] - if congress['relations_score'] < 50 or difficulty == "어려움": - cb_events.append({"title_key": "event_title_congress_data_request", "desc_key": "event_desc_congress_data_request", "options": [{"action_key": "action_submit_data", "cost": 1, "impact": {"congress_relations": random.randint(0,2)}}, {"action_key": "action_minimize_data", "cost": 0, "impact": {"congress_relations": random.randint(-3,0)}}]}) - cb_events.append({"title_key": "event_title_congress_hearing_review", "desc_key": "event_desc_congress_hearing_review", "options": [{"action_key": "action_prevent_hearing", "cost": 2, "impact": {"congress_relations": random.randint(-2,1)}}, {"action_key": "action_prepare_hearing", "cost": 1, "impact": {"stability": random.randint(-2,0)}}]}) - if judiciary['independence_level'] > 80 or random.random() < 0.1: - cb_events.append({"title_key": "event_title_judiciary_lawsuit", "desc_key": "event_desc_judiciary_lawsuit", "options": [{"action_key": "action_prepare_legal_defense", "cost": 1, "impact": {}}, {"action_key": "action_persuade_public", "cost": 1, "impact": {"approval": random.randint(0,1)}}]}) - - if not cb_events: - chosen_type = "random" # 대체 - else: - selected = random.choice(cb_events) - event["title_key"] = selected["title_key"] - event["description_key"] = selected["desc_key"] - event["options"] = selected["options"] - event["type"] = "checks_balances_event" # 타입 명시 - - # Random 타입 처리 - if chosen_type == "random": - event["title_key"] = "event_title_routine_report" - event["description_key"] = "event_desc_routine_report" - event["type"] = "random" - event["options"] = [ - {"action_key": "action_check_goal_progress", "cost": math.ceil(1 * (1/settings['event_impact_modifier'])), "impact": {}}, - {"action_key": "action_strengthen_pr", "cost": math.ceil(1 * (1/settings['event_impact_modifier'])), "impact": {"approval": random.randint(0,1)}}, - ] - - # 공통 옵션 추가 (키 사용) - event["options"].append({"action_key": "action_no_special_action", "cost": 0, "impact": {}}) - - # 최종 이벤트 객체 생성 (텍스트 변환) - final_event = { - "title": get_text_for_event(event["title_key"]).format(**event.get("context", {})), - "description": get_text_for_event(event["description_key"]).format(**event.get("context", {})), - "type": event["type"], - "options": [] - } - for opt in event.get("options", []): # options가 없을 수도 있으므로 get 사용 - final_event["options"].append({ - "action": get_text_for_event(opt["action_key"]).format(**opt.get("context", {})), - "cost": opt["cost"], - "impact": opt.get("impact", {}) # impact가 없을 수도 있음 - }) - - log_message = f"{year}년차 {quarter}분기 현안: {final_event['title']}" - if 'event_log' in game_state: - game_state['event_log'].append(log_message) - return final_event - -# --- 이벤트 브리핑 제공 함수 --- -def provide_event_briefing(event, game_state, difficulty): - """분기별 현안/기회에 대한 브리핑 생성 (난이도별 어휘 적용)""" - settings = DIFFICULTY_LEVELS[difficulty] - vocab_level = settings["vocab_level"] - get_text_for_briefing = lambda key: get_text(key, vocab_level) # 브리핑용 get_text - - title = event['title'] # 이미 변환된 텍스트 사용 - briefing = f"**{get_text_for_briefing('dashboard_term').format(year=st.session_state['game_year'], quarter=st.session_state['game_quarter'])} 보고: {title}**\n\n" - briefing += f"{event['description']}\n\n" # 이미 변환된 텍스트 사용 - briefing += f"**{get_text_for_briefing('briefing_title')}**\n" # 브리핑 제목도 난이도별로 +# --- 역사 이벤트 데이터 (예시: 4.19 혁명) --- +# 각 턴별로 발생할 이벤트와 선택지를 정의 +# 실제로는 더 많은 턴과 이벤트, 선택지, 결과가 필요 +HISTORICAL_EVENTS = { + "4.19_revolution": [ + { # Turn 1 + "turn": 1, + "title_key": "event_419_turn1_title", + "description_key": "event_419_turn1_desc", + "source_key": "event_419_turn1_source", + "options": [ + {"action_key": "action_419_turn1_option1", "cost": 1, "impact": {"approval": -10, "stability": -5, "resistance": +15}}, + {"action_key": "action_419_turn1_option2", "cost": 2, "impact": {"approval": +5, "stability": +0, "resistance": -5, "political_capital": -1}}, + {"action_key": "action_419_turn1_option3", "cost": 3, "impact": {"approval": +10, "stability": +5, "resistance": -10, "political_capital": -2}}, + ] + }, + { # Turn 2 + "turn": 2, + "title_key": "event_419_turn2_title", + "description_key": "event_419_turn2_desc", + "source_key": "event_419_turn2_source", + "options": [ + {"action_key": "action_419_turn2_option1", "cost": 2, "impact": {"approval": +5, "stability": -5, "resistance": +5}}, + {"action_key": "action_419_turn2_option2", "cost": 1, "impact": {"approval": -15, "stability": -20, "resistance": +25, "political_capital": +1}}, + {"action_key": "action_419_turn2_option3", "cost": 0, "impact": {"approval": -10, "stability": -5, "resistance": +10}}, + ] + }, + # ... (고려대생 피습, 경무대 앞 발포, 교수단 시위 등 추가) ... + { # 마지막 턴 (예시) + "turn": 8, # 시나리오의 max_turns와 일치 + "is_final_event": True, # 이 이벤트 후 시나리오 종료 + "title_key": "event_419_final_rhee_resignation_title", + "description_key": "event_419_final_rhee_resignation_desc", + "source_key": "event_419_final_rhee_resignation_source", + "options": [] # 선택지 없이 결과만 보여줄 수도 있음 + } + ], + "5.18_gwangju": [ + # 5.18 관련 이벤트 정의 + ], + "june_struggle": [ + # 6월 항쟁 관련 이벤트 정의 + ] +} +# --- 다음 역사 이벤트 가져오기 --- +def get_next_historical_event(scenario_key, current_turn, game_state): + scenario_events = HISTORICAL_EVENTS.get(scenario_key, []) + for event_data in scenario_events: + if event_data["turn"] == current_turn: + # 어휘 수준에 맞게 텍스트 변환 + vocab_level = SCENARIOS[scenario_key]["vocab_level"] + get_text_for_event = lambda k: get_text(k, vocab_level) + + final_event = { + "title": get_text_for_event(event_data["title_key"]), + "description": get_text_for_event(event_data["description_key"]), + "source_text": get_text_for_event(event_data.get("source_key", "")), # 사료가 없을 수도 있음 + "is_final_event": event_data.get("is_final_event", False), + "options": [] + } + for opt_data in event_data.get("options", []): + final_event["options"].append({ + "action": get_text_for_event(opt_data["action_key"]), + "cost": opt_data["cost"], + "impact": opt_data.get("impact", {}) + }) + + log_message = f"{game_state['game_year']}년 {current_turn}번째 국면: {final_event['title']}" + game_state['event_log'].append(log_message) + return final_event + return None # 해당 턴에 이벤트가 없거나, 모든 이벤트가 끝난 경우 + +# --- 이벤트 브리핑 제공 함수 (사료 포함하도록 수정) --- +def provide_historical_event_briefing(event, game_state): + scenario_key = game_state['scenario_key'] + vocab_level = SCENARIOS[scenario_key]["vocab_level"] + get_text_for_briefing = lambda k: get_text(k, vocab_level) + + briefing = f"**{get_text_for_briefing('dashboard_term').format(year=game_state['game_year'], turn=game_state['current_turn'])} 보고: {event['title']}**\n\n" + briefing += f"{event['description']}\n\n" + if event.get('source_text'): + briefing += f"**{get_text_for_briefing('historical_source_title')}**\n" + briefing += f"> {event['source_text']}\n\n" # 인용구 스타일 + + briefing += f"**{get_text_for_briefing('player_options_title')}**\n" options_data = [] for i, opt in enumerate(event.get("options", [])): cost = opt['cost'] - action_text = opt['action'] # 이미 변환된 텍스트 사용 - briefing += f"{i+1}. **{action_text}** ({get_text_for_briefing('action_button_label').format(action='', cost=cost)})\n" + action_text = opt['action'] + # 정치자산 소모 표기 수정 + cost_text = get_text_for_briefing('action_button_label').split('(')[-1].replace(')', '').replace('{cost}', str(cost)).replace('P', get_text_for_briefing('term_political_capital').split(' ')[-1]) + + briefing += f"{i+1}. **{action_text}** ({cost_text})\n" effects = [] - impact_modifier = settings['event_impact_modifier'] - impact = opt.get("impact", {}) # impact가 없을 수도 있음 + impact = opt.get("impact", {}) + + # 예시: 지지도, 안정도, 저항도, 정치자산 변화 표기 + if 'approval' in impact: effects.append(f"{get_text_for_briefing('term_approval')} {impact['approval']:+}%p") + if 'stability' in impact: effects.append(f"{get_text_for_briefing('term_stability')} {impact['stability']:+}%p") + if 'resistance' in impact: effects.append(f"{get_text_for_briefing('term_resistance')} {impact['resistance']:+}%p") + if 'political_capital' in impact and impact['political_capital'] != 0 : effects.append(f"{get_text_for_briefing('term_political_capital')} {impact['political_capital']:+}") - if 'approval' in impact: - val = impact['approval'] - adjusted_val = math.ceil(val / impact_modifier) if val < 0 else math.floor(val * impact_modifier) - effects.append(get_text_for_briefing('action_impact_approval').format(change=adjusted_val)) - if 'stability' in impact: - val = impact['stability'] - adjusted_val = math.ceil(val / impact_modifier) if val < 0 else math.floor(val * impact_modifier) - effects.append(get_text_for_briefing('action_impact_stability').format(change=adjusted_val)) - if 'congress_relations' in impact: - val = impact['congress_relations'] - adjusted_val = math.ceil(val / impact_modifier) if val < 0 else math.floor(val * impact_modifier) - effects.append(get_text_for_briefing('action_impact_congress').format(change=adjusted_val)) - if 'policy_goals' in impact: - for goal, prog in impact['policy_goals'].items(): - if goal in game_state['presidency_status']['policy_goals']: - val = prog - adjusted_val = math.ceil(val / impact_modifier) if val < 0 else math.floor(val * impact_modifier) - effects.append(get_text_for_briefing('action_impact_goal').format(goal=goal, prog=adjusted_val)) if effects: briefing += f" - *예상 결과:* {', '.join(effects)}\n" options_data.append(opt) - return {"text": briefing, "options": options_data} -# --- 대통령 행동 실행 함수 --- -def execute_presidential_action(action_index, briefing_data, game_state, difficulty): - """선택된 대통령 행동 실행 (난이도 영향 적용)""" - settings = DIFFICULTY_LEVELS[difficulty] - impact_modifier = settings['event_impact_modifier'] - vocab_level = settings["vocab_level"] - get_text_for_exec = lambda key: get_text(key, vocab_level) # 실행 시 사용할 get_text + +# --- 플레이어 행동 실행 함수 (지표 업데이트 등) --- +def execute_player_action(action_index, briefing_data, game_state): + scenario_key = game_state['scenario_key'] + vocab_level = SCENARIOS[scenario_key]["vocab_level"] + get_text_for_exec = lambda k: get_text(k, vocab_level) options = briefing_data.get("options", []) if not (0 <= action_index < len(options)): - st.error(get_text_for_exec("error_invalid_action")) + st.error("잘못된 행동 선택입니다.") # 이 부분도 get_text 사용 가능 return False selected_action = options[action_index] - action_name = selected_action['action'] # 이미 변환된 텍스트 + action_name = selected_action['action'] cost = selected_action['cost'] impact = selected_action.get('impact', {}) - presidency_status = game_state['presidency_status'] - other_branches = game_state['other_branches'] - cabinet = game_state.get('cabinet', {}) # cabinet이 없을 경우 대비 + current_status = game_state['status'] - if presidency_status['political_capital'] < cost: - st.warning(get_text_for_exec('action_cost_warning') + f" (필요: {cost}, 보유: {presidency_status['political_capital']})") + if current_status['political_capital'] < cost: + st.warning(get_text_for_exec('action_cost_warning') + f" (필요: {cost}, 보유: {current_status['political_capital']})") return False - presidency_status['political_capital'] -= cost - log_message = f" - 대통령 지시({st.session_state.game_quarter}Q): '{action_name}' 수행 ({get_text_for_exec('term_political_capital')} {cost} 소모)" + current_status['political_capital'] -= cost + log_message = f" - 정부 결정({game_state['current_turn']}국면): '{action_name}' ({get_text_for_exec('term_political_capital')} {cost} 소모)" game_state['event_log'].append(log_message) st.success(get_text_for_exec('action_success_message').format(action=action_name)) - approval_change_log = 0 - stability_change_log = 0 - congress_change_log = 0 - + # 지표 업데이트 + log_parts = [] if 'approval' in impact: - change = impact['approval'] - adjusted_change = math.ceil(change / impact_modifier) if change < 0 else math.floor(change * impact_modifier) - presidency_status['approval_rating'] += adjusted_change - approval_change_log = adjusted_change + current_status['approval_rating'] += impact['approval'] + log_parts.append(f"{get_text_for_exec('term_approval')} {impact['approval']:+}%p") if 'stability' in impact: - change = impact['stability'] - adjusted_change = math.ceil(change / impact_modifier) if change < 0 else math.floor(change * impact_modifier) - presidency_status['stability'] += adjusted_change - stability_change_log = adjusted_change - if 'congress_relations' in impact: - change = impact['congress_relations'] - adjusted_change = math.ceil(change / impact_modifier) if change < 0 else math.floor(change * impact_modifier) - other_branches['Congress']['relations_score'] += adjusted_change - congress_change_log = adjusted_change - - log_parts = [] - if approval_change_log != 0: log_parts.append(f"{get_text_for_exec('term_approval')} {approval_change_log:+}%p") - if stability_change_log != 0: log_parts.append(f"{get_text_for_exec('term_stability')} {stability_change_log:+}%p") - if congress_change_log != 0: log_parts.append(f"{get_text_for_exec('term_congress_relations')} {congress_change_log:+}%p") - if log_parts: game_state['event_log'].append(f" - {get_text_for_exec('log_action_result')} {', '.join(log_parts)} 변동.") - - if 'policy_goals' in impact: - for goal, progress in impact['policy_goals'].items(): - if goal in presidency_status['policy_goals']: - change = progress - adjusted_change = math.ceil(change / impact_modifier) if change < 0 else math.floor(change * impact_modifier) - current_progress = presidency_status['policy_goals'][goal] - new_progress = min(100, current_progress + adjusted_change) - presidency_status['policy_goals'][goal] = new_progress - if new_progress > current_progress: - game_state['event_log'].append(f" - {get_text_for_exec('term_policy_goals')} '{goal}' {adjusted_change}%p 상승 (현재 {new_progress}%)") + current_status['stability'] += impact['stability'] + log_parts.append(f"{get_text_for_exec('term_stability')} {impact['stability']:+}%p") + if 'resistance' in impact: + current_status['resistance'] += impact['resistance'] + log_parts.append(f"{get_text_for_exec('term_resistance')} {impact['resistance']:+}%p") + if 'political_capital' in impact and impact['political_capital'] !=0: # 비용 외 추가 변동 + current_status['political_capital'] += impact['political_capital'] + log_parts.append(f"{get_text_for_exec('term_political_capital')} {impact['political_capital']:+}") - # 장관 교체 로직 수정: 키를 기반으로 비교 - minister_replace_action_key = "action_replace_minister" - minister_replace_action_text_template = get_text(minister_replace_action_key, vocab_level) - # action_name이 "어떤장관 장관 교체" 형태인지 확인 - if minister_replace_action_text_template.startswith('{minister}') and \ - minister_replace_action_text_template.endswith(action_name.split(' ')[-1]): # "장관 바꾸기", "장관 교체", "장관 경질" 등 끝부분 일치 확인 - minister_to_replace = action_name.replace(minister_replace_action_text_template.split(' ')[-1], '').strip() # 끝부분 제거하고 이름 추출 - if minister_to_replace in cabinet: - old_approval = cabinet[minister_to_replace]['minister_approval'] - cabinet[minister_to_replace]['minister_approval'] = random.randint(40, 70) - cabinet[minister_to_replace]['performance'] = 50 - approval_change = random.randint(-4, 4) if difficulty == "어려움" else (random.randint(-2, 5) if difficulty == "쉬움" else random.randint(-3, 3)) - congress_change = random.randint(-6, 1) if difficulty == "어려움" else (random.randint(-3, 4) if difficulty == "쉬움" else random.randint(-5, 2)) - presidency_status['approval_rating'] += approval_change - other_branches['Congress']['relations_score'] += congress_change - game_state['event_log'].append(f" - {get_text_for_exec('log_minister_change').format(minister=minister_to_replace)} ({get_text_for_exec('log_minister_old_approval')}: {old_approval}%, {get_text_for_exec('log_minister_new_approval')}: {cabinet[minister_to_replace]['minister_approval']}%)") - if approval_change != 0: game_state['event_log'].append(f" - {get_text_for_exec('term_approval')} {approval_change:+} 변동.") - if congress_change != 0: game_state['event_log'].append(f" - {get_text_for_exec('term_congress_relations')} {congress_change:+} 변동.") - elif minister_to_replace: # 이름이 추출되었는데 cabinet에 없는 경우 - game_state['event_log'].append(get_text_for_exec('log_error_minister_not_found').format(minister=minister_to_replace)) - # 이름 추출 실패 시 (예: action_name 형식이 예상과 다를 때) 별도 처리 없음 + if log_parts: game_state['event_log'].append(f" - 결과: {', '.join(log_parts)} 변동.") - # 법안 제출 준비 로직 수정: 키를 기반으로 비교 - bill_prep_action_key = "action_prepare_bill" - if action_name == get_text(bill_prep_action_key, vocab_level): - bill_name = f"{st.session_state['game_year']}년-{random.choice(['경제','사회','환경','외교'])}-법안" # 예시 이름 - game_state['pending_legislation'] = game_state.get('pending_legislation', []) - game_state['pending_legislation'].append({"name": bill_name, "status": "준비중", "goal_related": random.choice(list(presidency_status['policy_goals'].keys())) if presidency_status['policy_goals'] else None}) - game_state['event_log'].append(f" - {get_text_for_exec('log_bill_prep_start').format(bill_name=bill_name)}") + # 지표 범위 유지 (0-100) + current_status['approval_rating'] = max(0, min(100, current_status['approval_rating'])) + current_status['stability'] = max(0, min(100, current_status['stability'])) + current_status['resistance'] = max(0, min(100, current_status['resistance'])) + current_status['political_capital'] = max(0, current_status['political_capital']) # 정치자산은 상한선 없음 - # 지표 범위 유지 - presidency_status['approval_rating'] = max(0, min(100, presidency_status['approval_rating'])) - presidency_status['stability'] = max(0, min(100, presidency_status['stability'])) - other_branches['Congress']['relations_score'] = max(0, min(100, other_branches['Congress']['relations_score'])) - if presidency_status.get('policy_goals'): # policy_goals가 없을 경우 대비 - for goal in presidency_status['policy_goals']: - presidency_status['policy_goals'][goal] = max(0, min(100, presidency_status['policy_goals'][goal])) - - st.session_state['actions_taken_this_quarter'] += 1 + st.session_state['actions_taken_this_turn'] += 1 return True -# --- 견제와 균형 시뮬레이션 함수 --- -def simulate_checks_and_balances(game_state, difficulty): - """(연말 실행) 국회와 사법부 반응 시뮬레이션 (난이도 적용 및 텍스트 사용)""" - settings = DIFFICULTY_LEVELS[difficulty] - vocab_level = settings["vocab_level"] - get_text_for_sim = lambda key: get_text(key, vocab_level) # 시뮬레이션용 get_text - presidency_status = game_state['presidency_status'] - congress = game_state['other_branches']['Congress'] - judiciary = game_state['other_branches']['Judiciary'] - log = game_state['event_log'] - year = st.session_state['game_year'] - - log.append(f"--- {get_text_for_sim('checks_balances_title').format(year=year)} ---") - - # 1. 국회 시뮬레이션 - if 'pending_legislation' in game_state: - for bill_to_submit in game_state['pending_legislation']: - if bill_to_submit['status'] == '준비중': - bill_to_submit['status'] = '제출됨' - congress['pending_bills'].append(bill_to_submit) - log.append(f" - {get_text_for_sim('checks_balances_bill_submitted').format(bill_name=bill_to_submit['name'])}") - game_state['pending_legislation'] = [] - - processed_bills_indices = [] - bills_passed_this_year = 0 - bills_rejected_this_year = 0 - - for i, bill in enumerate(congress.get('pending_bills', [])): # pending_bills 없을 경우 대비 - if bill.get('status') == '제출됨': - base_chance = 0.4 - relation_bonus = (congress['relations_score'] - 50) * 0.007 - approval_bonus = (presidency_status['approval_rating'] - 50) * 0.003 - is_minority_gov = congress['seats']['Government'] < (sum(congress['seats'].values()) / 2) - minority_penalty = -0.25 if is_minority_gov else 0 - difficulty_modifier = settings['checks_balances_leniency'] - pass_chance = base_chance + relation_bonus + approval_bonus + minority_penalty + difficulty_modifier - pass_chance = max(0.05, min(0.95, pass_chance)) - - log.append(f" - {get_text_for_sim('log_bill_pass_chance').format(bill_name=bill['name'])}: {pass_chance:.1%} (관계:{relation_bonus:.1%}, 지지도:{approval_bonus:.1%}, 여소야대:{minority_penalty:.1%}, 난이도:{difficulty_modifier:.1%})") - - if random.random() < pass_chance: - bill['status'] = '통과' - log.append(f" - {get_text_for_sim('checks_balances_bill_passed').format(bill_name=bill['name'])}") - bills_passed_this_year += 1 - approval_gain = math.floor(random.randint(2, 5) * (1 + settings['checks_balances_leniency'])) - relation_gain = math.floor(random.randint(3, 7) * (1 + settings['checks_balances_leniency'])) - presidency_status['approval_rating'] += approval_gain - congress['relations_score'] = min(100, congress['relations_score'] + relation_gain) - if approval_gain > 0: log.append(f" - {get_text_for_sim('term_approval')} {approval_gain:+}%p 상승.") - if relation_gain > 0: log.append(f" - {get_text_for_sim('term_congress_relations')} {relation_gain:+}%p 상승.") - if bill.get('goal_related') and bill['goal_related'] in presidency_status.get('policy_goals', {}): # policy_goals 없을 경우 대비 - goal_to_update = bill['goal_related'] - progress = random.randint(10, 25) - adjusted_progress = math.floor(progress * (1 + settings['checks_balances_leniency'] * 0.5)) - current_prog = presidency_status['policy_goals'][goal_to_update] - new_prog = min(100, current_prog + adjusted_progress) - presidency_status['policy_goals'][goal_to_update] = new_prog - if new_prog > current_prog: log.append(f" - {get_text_for_sim('term_policy_goals')} '{goal_to_update}' {adjusted_progress}%p 상승 (현재 {new_prog}%)") - else: - bill['status'] = '부결' - log.append(f" - {get_text_for_sim('checks_balances_bill_failed').format(bill_name=bill['name'])}") - bills_rejected_this_year += 1 - approval_loss = math.ceil(random.randint(2, 4) * (1 - settings['checks_balances_leniency'])) - relation_loss = math.ceil(random.randint(2, 5) * (1 - settings['checks_balances_leniency'])) - presidency_status['approval_rating'] -= approval_loss - congress['relations_score'] = max(0, congress['relations_score'] - relation_loss) - if approval_loss > 0: log.append(f" - {get_text_for_sim('term_approval')} {-approval_loss}%p 하락.") - if relation_loss > 0: log.append(f" - {get_text_for_sim('term_congress_relations')} {-relation_loss}%p 하락.") - if bill.get('goal_related') and bill['goal_related'] in presidency_status.get('policy_goals', {}) and random.random() < 0.2: - goal_to_update = bill['goal_related'] - setback = random.randint(1, 5) - current_prog = presidency_status['policy_goals'][goal_to_update] - new_prog = max(0, current_prog - setback) - presidency_status['policy_goals'][goal_to_update] = new_prog - if new_prog < current_prog: log.append(f" - {get_text_for_sim('term_policy_goals')} '{goal_to_update}' {-setback}%p 후퇴 (현재 {new_prog}%)") - - processed_bills_indices.append(i) - - investigation_chance = 0.10 - if congress['relations_score'] < 35: investigation_chance += 0.15 - if difficulty == "어려움": investigation_chance += 0.10 - if random.random() < investigation_chance: - log.append(f" - {get_text_for_sim('checks_balances_investigation')}") - stability_loss = math.ceil(random.randint(5, 10) * (1 - settings['checks_balances_leniency'])) - relation_loss = math.ceil(random.randint(7, 14) * (1 - settings['checks_balances_leniency'])) - presidency_status['stability'] -= stability_loss - congress['relations_score'] = max(0, congress['relations_score'] - relation_loss) - log.append(f" - {get_text_for_sim('term_stability')} {-stability_loss}%p 하락, {get_text_for_sim('term_congress_relations')} {-relation_loss}%p 하락.") - - # 2. 사법부 시뮬레이션 - ruled_cases_indices = [] - if random.random() < (0.1 + (0.1 if difficulty == '어려움' else 0)): - new_case_name = f"{year}년-{random.choice(['정책','인사','법률'])}-관련소송" - judiciary['pending_cases'] = judiciary.get('pending_cases', []) # 없을 경우 초기화 - judiciary['pending_cases'].append({"name": new_case_name, "status": "접수됨"}) - log.append(f" - {get_text_for_sim('log_judiciary_case_received').format(judiciary_name=judiciary['name'], case_name=new_case_name)}") - judiciary['status'] = "주요 사건 심리 중" - - for i, case in enumerate(judiciary.get('pending_cases', [])): # pending_cases 없을 경우 대비 - if case.get('status') == '접수됨' or case.get('status') == '심리중': - independence_factor = (judiciary.get('independence_level', 80) - 75) * 0.008 # 없을 경우 기본값 - difficulty_factor = settings['checks_balances_leniency'] * -1 - strike_down_chance = 0.3 + independence_factor + difficulty_factor - strike_down_chance = max(0.05, min(0.95, strike_down_chance)) - - log.append(f" - {get_text_for_sim('log_judiciary_strike_down_chance').format(case_name=case['name'])}: {strike_down_chance:.1%} (독립성:{independence_factor:.1%}, 난이도:{difficulty_factor:.1%})") - - if random.random() < strike_down_chance: - result_text = get_text_for_sim('checks_balances_judiciary_strike_down') - case['status'] = f'판결완료: {result_text}' - log.append(f" - {get_text_for_sim('checks_balances_judiciary_ruling').format(case_name=case['name'], result=result_text)}") - approval_loss = math.ceil(random.randint(5, 10) * (1 - settings['checks_balances_leniency'])) - stability_loss = math.ceil(random.randint(4, 8) * (1 - settings['checks_balances_leniency'])) - presidency_status['approval_rating'] -= approval_loss - presidency_status['stability'] -= stability_loss - log.append(f" - {get_text_for_sim('term_approval')} {-approval_loss}%p 하락, {get_text_for_sim('term_stability')} {-stability_loss}%p 하락.") - else: - result_text = get_text_for_sim('checks_balances_judiciary_uphold') - case['status'] = f'판결완료: {result_text}' - log.append(f" - {get_text_for_sim('checks_balances_judiciary_ruling').format(case_name=case['name'], result=result_text)}") - approval_gain = math.floor(random.randint(0, 2) * (1 + settings['checks_balances_leniency'])) - stability_gain = math.floor(random.randint(0, 3) * (1 + settings['checks_balances_leniency'])) - presidency_status['approval_rating'] += approval_gain - presidency_status['stability'] += stability_gain - if approval_gain > 0: log.append(f" - {get_text_for_sim('term_approval')} {approval_gain:+}%p 상승.") - if stability_gain > 0: log.append(f" - {get_text_for_sim('term_stability')} {stability_gain:+}%p 상승.") - - ruled_cases_indices.append(i) - - has_pending = any(c.get('status') == '접수됨' or c.get('status') == '심리중' for c in judiciary.get('pending_cases', [])) - if not has_pending: judiciary['status'] = "특별한 사건 없음" - # 3. 연말 최종 상태 업데이트 - approval_fluctuation = random.randint(-4, 4) if difficulty == "어려움" else (random.randint(-2, 5) if difficulty == "쉬움" else random.randint(-3, 3)) - stability_fluctuation = random.randint(-5, 2) if difficulty == "어려움" else (random.randint(-2, 4) if difficulty == "쉬움" else random.randint(-4, 2)) - presidency_status['approval_rating'] += approval_fluctuation - presidency_status['stability'] += stability_fluctuation - log.append(f" - {get_text_for_sim('log_natural_fluctuation')}: {get_text_for_sim('term_approval')} {approval_fluctuation:+}%p, {get_text_for_sim('term_stability')} {stability_fluctuation:+}%p") +# --- UI 표시 함수들 (새로운 지표 '저항도' 추가 등) --- +def display_dashboard(game_state): + scenario_key = game_state['scenario_key'] + vocab_level = SCENARIOS[scenario_key]["vocab_level"] + get_text_for_ui = lambda k: get_text(k, vocab_level) + status = game_state['status'] - presidency_status['approval_rating'] = max(0, min(100, presidency_status['approval_rating'])) - presidency_status['stability'] = max(0, min(100, presidency_status['stability'])) - congress['relations_score'] = max(0, min(100, congress['relations_score'])) - - log.append(f"--- {get_text_for_sim('checks_balances_summary').format(year=year, passed=bills_passed_this_year, failed=bills_rejected_this_year)} ---") - - -# --- UI 표시 함수들 --- -def display_presidency_dashboard(game_state, difficulty): - vocab_level = DIFFICULTY_LEVELS[difficulty]["vocab_level"] - get_text_for_ui = lambda key: get_text(key, vocab_level) # UI용 get_text - status = game_state['presidency_status'] st.metric(get_text_for_ui("term_approval"), f"{status['approval_rating']}%") - st.metric(get_text_for_ui("term_political_capital"), f"{status['political_capital']} P") + st.metric(get_text_for_ui("term_political_capital"), f"{status['political_capital']}") st.metric(get_text_for_ui("term_stability"), f"{status['stability']} / 100") - st.metric(get_text_for_ui("term_congress_relations"), f"{game_state['other_branches']['Congress']['relations_score']} / 100") - - st.subheader(get_text_for_ui("term_policy_goals")) - goals = status.get('policy_goals', {}) # 없을 경우 대비 - if not goals: - st.caption(get_text_for_ui("caption_no_goals")) - return - cols = st.columns(len(goals)) - i = 0 - for goal, progress in goals.items(): - with cols[i]: - st.progress(progress / 100) - st.caption(f"{goal} ({progress}%)") - i += 1 - -def display_other_branches_status(game_state, difficulty): - vocab_level = DIFFICULTY_LEVELS[difficulty]["vocab_level"] - get_text_for_ui = lambda key: get_text(key, vocab_level) - congress = game_state['other_branches']['Congress'] - judiciary = game_state['other_branches']['Judiciary'] - - with st.expander(f"{congress['name']} ({get_text_for_ui('term_congress_relations')}: {congress.get('relations_score', 50)})", expanded=True): # 기본값 추가 - seats = congress.get('seats', {'Government': 150, 'Opposition': 130, 'Others': 20}) # 기본값 추가 - st.markdown(f"**의석:** 여당 {seats['Government']} / 야당 {seats['Opposition']} / 기타 {seats['Others']} (총 {sum(seats.values())})") - if seats['Government'] < sum(seats.values()) / 2: - st.markdown("[주의] 여소야대 상황입니다. 법안 통과가 어려울 수 있습니다.", unsafe_allow_html=True) - st.markdown(f"**주요 관심사:** {', '.join(congress.get('key_focus', ['민생 안정']))}") # 기본값 추가 - st.markdown(f"**현재 태도:** {congress.get('stance', '중립')}") # 기본값 추가 - pending_bills_str = "\n".join([f"- {b['name']} ({b.get('status', '알수없음')})" for b in congress.get('pending_bills', []) if b.get('status') != '통과' and b.get('status') != '부결']) - if not pending_bills_str: pending_bills_str = get_text_for_ui("caption_no_pending_bills") - st.text_area(get_text_for_ui("label_pending_bills"), pending_bills_str, height=100, disabled=True, key="pending_bills_display") - - with st.expander(f"{judiciary['name']} (독립성: {judiciary.get('independence_level', 80)})", expanded=True): # 기본값 추가 - st.markdown(f"**현재 상태:** {judiciary.get('status', '사건 없음')}") # 기본값 추가 - pending_cases_str = "\n".join([f"- {c['name']} ({c.get('status', '심리중')})" for c in judiciary.get('pending_cases', []) if '판결완료' not in c.get('status', '')]) - if not pending_cases_str: pending_cases_str = get_text_for_ui("caption_no_pending_cases") - st.text_area(get_text_for_ui("label_pending_cases"), pending_cases_str, height=100, disabled=True, key="pending_cases_display") - -def display_cabinet_status(game_state, difficulty): - vocab_level = DIFFICULTY_LEVELS[difficulty]["vocab_level"] - get_text_for_ui = lambda key: get_text(key, vocab_level) - cabinet = game_state.get('cabinet', {}) - with st.expander(get_text_for_ui("cabinet_title")): - if not cabinet: - st.caption(get_text_for_ui("caption_no_cabinet")) - return - cols = st.columns(len(cabinet)) - i = 0 - for minister, data in cabinet.items(): - with cols[i]: - st.metric(f"{minister} 장관 {get_text_for_ui('term_approval')}", f"{data.get('minister_approval', 50)}%") # 기본값 추가 - i += 1 - -def display_gov_terms_glossary(difficulty): - vocab_level = DIFFICULTY_LEVELS[difficulty]["vocab_level"] - get_text_for_ui = lambda key: get_text(key, vocab_level) - - glossary_keys = [k for k in ALL_TEXTS if k.startswith('glossary_')] - - with st.sidebar.expander(get_text_for_ui("sidebar_glossary_title"), expanded=False): - for key in glossary_keys: - term_definition = get_text_for_ui(key) - parts = term_definition.split(':', 1) - term = parts[0].strip() - definition = parts[1].strip() if len(parts) > 1 else "" - st.markdown(f"**{term}:** {definition}") - st.markdown("---") - -# --- 메인 게임 루프 --- -def main(): - # 페이지 설정은 스크립트 최상단으로 이동했으므로 여기서는 호출하지 않음 - - if not st.session_state.game_started: - # 시작 화면 처리 루틴이 이 함수 위에 있으므로, - # game_started가 False이면 이 함수는 호출되지 않거나, - # 호출되더라도 바로 종료되어야 함. - # 만약을 위해 return 추가 - return - - # 게임 진행 중 사용할 난이도 및 어휘 수준 로드 - # st.session_state에 값이 없을 경우를 대비해 기본값 설정 - difficulty = st.session_state.get('difficulty', '보통') - settings = DIFFICULTY_LEVELS[difficulty] - vocab_level = settings["vocab_level"] - get_text_main = lambda key: get_text(key, vocab_level) # main 함수 내에서 사용할 get_text - - st.title("👑 대통령의 균형추: 삼권분립 리더십 시뮬레이션") - st.caption(f"난이도: {difficulty}") - - # game_state가 없을 경우 초기화 (오류 방지) - if 'game_state' not in st.session_state: - st.session_state.game_state = initialize_government(difficulty) - st.warning("게임 상태가 초기화되었습니다.") # 사용자에게 알림 - st.rerun() # 초기화 후 다시 실행 - - game_state = st.session_state.game_state - - # --- 게임 오버 처리 --- - if st.session_state.get('game_over', False): - st.balloons() - st.header(get_text_main("game_over_title").format(years=MAX_YEARS)) - st.subheader(get_text_main("game_over_subtitle")) - final_status = game_state.get('presidency_status', {}) # 없을 경우 대비 - other_branches = game_state.get('other_branches', {}) - congress_state = other_branches.get('Congress', {}) - col1, col2, col3 = st.columns(3) - with col1: st.metric(get_text_main("term_approval"), f"{final_status.get('approval_rating', 'N/A')}%") - with col2: st.metric(get_text_main("term_stability"), f"{final_status.get('stability', 'N/A')}") - with col3: st.metric(get_text_main("term_congress_relations"), f"{congress_state.get('relations_score', 'N/A')}") - - st.write(get_text_main("term_policy_goals")) - goals = final_status.get('policy_goals', {}) - if goals: - for goal, progress in goals.items(): - st.progress(progress / 100, text=f"{goal} ({progress}%)") + st.metric(get_text_for_ui("term_resistance"), f"{status['resistance']} / 100") # 새 지표 + +# (기존 display_other_branches_status, display_cabinet_status 등은 시나리오에 따라 불필요하거나 대폭 수정 필요) +# (display_gov_terms_glossary는 각 시나리오별 주요 용어 해설로 변경) + +def display_historical_glossary(scenario_key): + vocab_level = SCENARIOS[scenario_key]["vocab_level"] + get_text_for_ui = lambda k: get_text(k, vocab_level) + # 예시: ALL_TEXTS에 "glossary_419_315election": {"보통": "**3.15 부정선거:** 자유당 정권이 ..."} 와 같이 정의 + # glossary_keys = [k for k in ALL_TEXTS if k.startswith(f'glossary_{scenario_key}')] + # ... (기존 로직 활용) ... + st.sidebar.markdown("---") + st.sidebar.subheader(get_text_for_ui("historical_source_title")) # 임시로 여기에 사료 공간 마련 + if 'current_historical_event' in st.session_state and st.session_state.current_historical_event: + source = st.session_state.current_historical_event.get('source_text') + if source: + st.sidebar.info(source) else: - st.caption(get_text_main("caption_no_goals")) - - st.subheader(get_text_main("term_event_log")) - log_text = "\n".join(game_state.get('event_log', [])[::-1]) # 없을 경우 대비 - st.text_area(get_text_main("term_event_log"), log_text, height=400, disabled=True, key="final_log") - if st.button(get_text_main("button_restart")): - keys_to_delete = [k for k in st.session_state.keys() if k != 'game_started'] - for key in keys_to_delete: del st.session_state[key] - st.session_state.game_started = False - st.rerun() - return + st.sidebar.caption("현재 국면에 제공된 사료가 없습니다.") - # --- 메인 게임 화면 --- - col_dashboard, col_agenda, col_actions = st.columns([1.2, 1.8, 1.5]) - with col_dashboard: - st.header(get_text_main("dashboard_title")) - st.markdown(f"**{get_text_main('dashboard_term').format(year=st.session_state.get('game_year', 1), quarter=st.session_state.get('game_quarter', 1))}**") # 기본값 추가 - display_presidency_dashboard(game_state, difficulty) - st.divider() - st.subheader(get_text_main("institutions_title")) - display_other_branches_status(game_state, difficulty) - st.divider() - display_cabinet_status(game_state, difficulty) - - with col_agenda: - st.header(get_text_main("agenda_title")) - - if 'current_quarterly_event' not in st.session_state or st.session_state['current_quarterly_event'] is None: - spinner_text = get_text_main('status_loading_report').format(year=st.session_state.get('game_year', 1), quarter=st.session_state.get('game_quarter', 1)) - with st.spinner(spinner_text): - time.sleep(0.5) - event = generate_quarterly_event(st.session_state.get('game_year', 1), st.session_state.get('game_quarter', 1), game_state, difficulty) - if event: - st.session_state['current_quarterly_event'] = event - st.session_state['event_briefing'] = provide_event_briefing(event, game_state, difficulty) - st.session_state['actions_taken_this_quarter'] = 0 +# --- 세션 상태 초기화 --- +if 'game_mode' not in st.session_state: # 'scenario_selected', 'game_running', 'game_over' + st.session_state.game_mode = 'scenario_select' + +# --- 메인 게임 로직 --- +def historical_simulation_main(): + vocab_level = "보통" # 기본값, 시나리오 선택 후 변경됨 + if 'current_scenario_key' in st.session_state: + vocab_level = SCENARIOS[st.session_state.current_scenario_key]["vocab_level"] + get_text_main = lambda key: get_text(key, vocab_level) + + st.title(get_text_main("game_title")) + + # --- 시나리오 선택 화면 --- + if st.session_state.game_mode == 'scenario_select': + st.header(get_text_main("scenario_select_title")) + + # 각 시나리오에 대한 설명 추가 + scenario_descriptions = { + "4.19_revolution": "1960년, 자유당 정권의 3.15 부정선거에 맞서 일어난 학생과 시민들의 혁명. 당신은 이승만 정부의 입장에서 격동의 시기를 헤쳐나가야 합니다.", + "5.18_gwangju": "1980년 5월, 신군부의 등장과 비상계엄 확대에 저항하여 광주에서 일어난 민주화운동. 당신은 계엄군의 입장에서 역사의 소용돌이와 마주합니다. (주의: 민감한 내용을 다룹니다)", + "june_struggle": "1987년, 대통령 직선제 개헌을 요구하며 전국적으로 확산된 민주 항쟁. 당신은 전두환 정부의 일원으로서 중대한 결정을 내려야 합니다." + } + + for key, scenario_info in SCENARIOS.items(): + with st.container(border=True): + st.subheader(scenario_info["display_name"]) + st.caption(f"시대: {scenario_info['start_year']}년, 플레이어 역할: {scenario_info['player_role']}") + st.markdown(scenario_descriptions.get(key, "시나리오 설명이 준비되지 않았습니다.")) + if st.button(get_text_main("scenario_select_button"), key=f"select_{key}"): + st.session_state.current_scenario_key = key + st.session_state.game_state = initialize_scenario_state(key) + st.session_state.current_historical_event = None + st.session_state.event_briefing_data = None + st.session_state.actions_taken_this_turn = 0 + st.session_state.game_mode = 'game_running' + st.rerun() + st.stop() + + # --- 게임 진행 중 또는 시나리오 종료 --- + if st.session_state.game_mode in ['game_running', 'scenario_over']: + game_state = st.session_state.game_state + scenario_key = st.session_state.current_scenario_key + vocab_level = SCENARIOS[scenario_key]["vocab_level"] # 여기서 다시 설정 + get_text_main = lambda key: get_text(key, vocab_level) # get_text_main도 업데이트 + + st.caption(f"시나리오: {SCENARIOS[scenario_key]['display_name']} | 역할: {game_state['player_role']}") + + # --- 시나리오 종료 처리 --- + if st.session_state.game_mode == 'scenario_over': + st.balloons() + st.header(get_text_main("game_over_title")) + st.subheader(get_text_main("game_over_subtitle")) + final_status = game_state['status'] + col1, col2, col3, col4 = st.columns(4) + with col1: st.metric(get_text_main("term_approval"), f"{final_status.get('approval_rating', 'N/A')}%") + with col2: st.metric(get_text_main("term_political_capital"), f"{final_status.get('political_capital', 'N/A')}") + with col3: st.metric(get_text_main("term_stability"), f"{final_status.get('stability', 'N/A')}") + with col4: st.metric(get_text_main("term_resistance"), f"{final_status.get('resistance', 'N/A')}") + + st.subheader(get_text_main("term_event_log")) + log_text = "\n".join(game_state.get('event_log', [])[::-1]) + st.text_area(get_text_main("term_event_log"), log_text, height=300, disabled=True, key="final_log") + + col_btn1, col_btn2 = st.columns(2) + with col_btn1: + if st.button(get_text_main("button_restart_scenario"), use_container_width=True): + # 현재 시나리오 재시작 + st.session_state.game_state = initialize_scenario_state(scenario_key) + st.session_state.current_historical_event = None + st.session_state.event_briefing_data = None + st.session_state.actions_taken_this_turn = 0 + st.session_state.game_mode = 'game_running' + st.rerun() + with col_btn2: + if st.button(get_text_main("button_back_to_scenario_select"), use_container_width=True): + st.session_state.game_mode = 'scenario_select' + # 필요시 다른 세션 상태 정리 + keys_to_clear = ['current_scenario_key', 'game_state', 'current_historical_event', 'event_briefing_data', 'actions_taken_this_turn'] + for k in keys_to_clear: + if k in st.session_state: + del st.session_state[k] st.rerun() + st.stop() + + # --- 메인 게임 화면 (기존 레이아웃 활용) --- + col_dashboard, col_agenda, col_actions = st.columns([1.2, 1.8, 1.5]) + + with col_dashboard: + st.header(get_text_main("dashboard_title")) + st.markdown(f"**{get_text_main('dashboard_term').format(year=game_state['game_year'], turn=game_state['current_turn'])}**") + display_dashboard(game_state) + # (기관 현황, 내각 현황 등은 이 게임에서는 제거하거나 간소화) + + with col_agenda: + st.header(get_text_main("historical_briefing_title")) + + if st.session_state.current_historical_event is None: + spinner_text = get_text_main('status_loading_report').format(year=game_state['game_year'], turn=game_state['current_turn']) + with st.spinner(spinner_text): + time.sleep(0.5) # 로딩 느낌 + event = get_next_historical_event(scenario_key, game_state['current_turn'], game_state) + if event: + st.session_state.current_historical_event = event + st.session_state.event_briefing_data = provide_historical_event_briefing(event, game_state) + st.session_state.actions_taken_this_turn = 0 + st.rerun() + else: # 더 이상 이벤트가 없거나, 시나리오 최대 턴 도달 시 + st.session_state.game_mode = 'scenario_over' + game_state['event_log'].append(f"--- {SCENARIOS[scenario_key]['display_name']} 종료 ---") + st.rerun() + + if st.session_state.current_historical_event: + event_data = st.session_state.current_historical_event + st.subheader(f"{event_data.get('title', '알 수 없는 현안')}") + st.markdown(event_data.get('description', '')) + if event_data.get('source_text'): + st.info(f"**{get_text_main('historical_source_title')}:** {event_data.get('source_text')}") + st.divider() + if st.session_state.event_briefing_data: + # st.subheader("보좌진 브리핑 및 대응 방안") # 이 부분은 이미 event_briefing_data에 포함됨 + st.markdown(st.session_state.event_briefing_data.get('text', ''), unsafe_allow_html=True) else: - st.error(get_text_main("error_event_generation_failed")) - - if st.session_state.get('current_quarterly_event'): # get 사용 - event_data = st.session_state['current_quarterly_event'] - title_prefix = get_text_main('agenda_event_title').split(':')[0] - st.subheader(f"{title_prefix}: {event_data.get('title', '알 수 없는 현안')}") # get 사용 - st.markdown(event_data.get('description', '')) # get 사용 - st.divider() - if st.session_state.get('event_briefing'): # get 사용 - st.subheader(get_text_main("briefing_title")) - st.markdown(st.session_state['event_briefing'].get('text', ''), unsafe_allow_html=True) # get 사용 - else: - st.warning(get_text_main("warning_briefing_loading")) - elif 'game_started' in st.session_state and st.session_state.game_started: # 게임 시작 후에만 표시 - st.info(get_text_main("status_waiting_report")) - - - with col_actions: - st.header(get_text_main("office_title")) - st.markdown(get_text_main("office_available_capital").format(capital=game_state.get('presidency_status', {}).get('political_capital', 0))) # 기본값 추가 - remaining_actions = QUARTERLY_ACTIONS_LIMIT - st.session_state.get('actions_taken_this_quarter', 0) # 기본값 추가 - st.warning(get_text_main("office_action_limit").format(remaining=remaining_actions)) - - action_possible = (st.session_state.get('current_quarterly_event') and - st.session_state.get('event_briefing') and - remaining_actions > 0) + st.warning("브리핑 정보를 불러오는 중입니다.") # get_text 사용 가능 + + with col_actions: + st.header(SCENARIOS[scenario_key]['player_role'] + " 집무실") # 역할에 따라 변경 + st.markdown(f"가용 {get_text_main('term_political_capital')}: **{game_state['status']['political_capital']}**") + remaining_actions = QUARTERLY_ACTIONS_LIMIT - st.session_state.actions_taken_this_turn + st.warning(f"이번 국면 주요 결정 잔여 횟수: {remaining_actions}회") # get_text 사용 가능 + + action_possible = (st.session_state.current_historical_event and + st.session_state.event_briefing_data and + remaining_actions > 0 and + not st.session_state.current_historical_event.get("is_final_event", False) and # 최종 이벤트면 선택지 없음 + len(st.session_state.current_historical_event.get("options", [])) > 0) + + + if action_possible: + st.subheader(get_text_main("player_options_title")) + options = st.session_state.event_briefing_data.get("options", []) + for i, opt in enumerate(options): + button_label = get_text_main('action_button_label').format(action=opt.get('action', '알 수 없는 행동'), cost=opt.get('cost', 0)) + button_key = f"action_{game_state['current_turn']}_{i}" + is_disabled = (game_state['status']['political_capital'] < opt.get('cost', 0)) + if st.button(button_label, key=button_key, use_container_width=True, disabled=is_disabled): + if execute_player_action(i, st.session_state.event_briefing_data, game_state): + # 특정 행동이 즉시 다음 턴으로 넘어가거나 시나리오를 종료시킬 수도 있음 + # (예: 하야 결정) + # 여기서는 일단 상태만 업데이트하고 rerun + st.rerun() + elif st.session_state.actions_taken_this_turn >= QUARTERLY_ACTIONS_LIMIT and st.session_state.current_historical_event: + st.info(get_text_main("status_action_limit_reached")) + elif st.session_state.current_historical_event and st.session_state.current_historical_event.get("is_final_event", False): + st.info("이 국면은 역사적 결과 보고로 마무리됩니다. 다음 국면으로 진행하세요.") - if action_possible: - st.subheader(get_text_main("office_issue_actions")) - options = st.session_state.get('event_briefing', {}).get("options", []) # 기본값 추가 - for i, opt in enumerate(options): - button_label = get_text_main('action_button_label').format(action=opt.get('action', '알 수 없는 행동'), cost=opt.get('cost', 0)) # 기본값 추가 - button_key = f"action_{st.session_state.get('game_year', 1)}_{st.session_state.get('game_quarter', 1)}_{i}" - is_disabled = (game_state.get('presidency_status', {}).get('political_capital', 0) < opt.get('cost', 0)) - if st.button(button_label, key=button_key, use_container_width=True, disabled=is_disabled): - if execute_presidential_action(i, st.session_state['event_briefing'], game_state, difficulty): - st.rerun() - if remaining_actions > 0: st.divider() - st.subheader(get_text_main("office_standard_actions")) - # 상시 행동 정의 시 cabinet 존재 여부 확인 - cabinet_ministers = list(game_state.get('cabinet', {}).keys()) - default_minister = "특정" if not cabinet_ministers else random.choice(cabinet_ministers) - - standard_actions_defs = [ - {"action_key": "action_replace_minister", "cost_base": 4, "context": {"minister": default_minister}}, - {"action_key": "action_prepare_bill", "cost_base": 3}, - {"action_key": "action_meet_ruling_party", "cost_base": 2, "impact": {"congress_relations": random.randint(1,4)}}, - {"action_key": "action_meet_opposition", "cost_base": 3}, - {"action_key": "action_meet_biz_leaders", "cost_base": 1, "impact": {"approval": random.randint(0,2)}}, - {"action_key": "action_respect_judiciary", "cost_base": 1}, - ] - for j, std_def in enumerate(standard_actions_defs): - cost = math.ceil(std_def['cost_base'] * (1/settings['event_impact_modifier'])) - action_text = get_text_main(std_def['action_key']).format(**std_def.get("context", {})) - button_label = get_text_main('action_button_label').format(action=action_text, cost=cost) - button_key = f"std_action_{st.session_state.get('game_year', 1)}_{st.session_state.get('game_quarter', 1)}_{j}" - is_disabled = (game_state.get('presidency_status', {}).get('political_capital', 0) < cost) - temp_briefing_data = {"options": [{ - "action": action_text, - "cost": cost, - "impact": std_def.get("impact", {}) - }]} - if st.button(button_label, key=button_key, use_container_width=True, disabled=is_disabled): - if execute_presidential_action(0, temp_briefing_data, game_state, difficulty): - st.rerun() - elif st.session_state.get('actions_taken_this_quarter', 0) >= QUARTERLY_ACTIONS_LIMIT and st.session_state.get('current_quarterly_event'): - st.info(get_text_main("status_action_limit_reached").format(quarter=st.session_state.get('game_quarter', 1))) - elif 'game_started' in st.session_state and st.session_state.game_started and not st.session_state.get('current_quarterly_event'): # 게임 시작 후 & 이벤트 없을 때 - st.info(get_text_main("status_waiting_report")) - - - st.divider() - if st.button(get_text_main("button_next_quarter"), use_container_width=True, key="next_quarter_button"): - current_quarter = st.session_state.get('game_quarter', 1) - current_year = st.session_state.get('game_year', 1) - - if current_quarter == 4: - spinner_text = f"{current_year}년차 국정 결산 중..." # 키로 관리 가능 - with st.spinner(spinner_text): - simulate_checks_and_balances(game_state, difficulty) - capital_gain = settings['yearly_gain'] - game_state['presidency_status']['political_capital'] = game_state.get('presidency_status', {}).get('political_capital', 0) + capital_gain # 안전하게 접근 - game_state['event_log'] = game_state.get('event_log', []) # 로그 없을 경우 초기화 - game_state['event_log'].append(f" - {get_text_main('log_yearly_capital_gain')} {capital_gain}P 획득.") - time.sleep(1.5) - - st.session_state['game_year'] = current_year + 1 - st.session_state['game_quarter'] = 1 - if st.session_state['game_year'] > MAX_YEARS: - st.session_state['game_over'] = True - game_state['event_log'].append(get_text_main('log_end_of_term')) - else: - game_state['event_log'].append(get_text_main('log_start_year').format(year=st.session_state['game_year'])) - else: - st.session_state['game_quarter'] = current_quarter + 1 - game_state['event_log'] = game_state.get('event_log', []) - game_state['event_log'].append(get_text_main('log_start_quarter').format(year=current_year, quarter=st.session_state['game_quarter'])) - - st.session_state['current_quarterly_event'] = None - st.session_state['event_briefing'] = None - st.session_state['actions_taken_this_quarter'] = 0 - st.rerun() + # "다음 국면으로" 버튼은 항상 표시 (선택지를 다 소모했거나, 최종 이벤트인 경우 등) + if st.button(get_text_main("button_next_turn"), use_container_width=True, key="next_turn_button"): + current_turn = game_state['current_turn'] + max_turns_for_scenario = SCENARIOS[scenario_key]['max_turns'] + + if current_turn >= max_turns_for_scenario or \ + (st.session_state.current_historical_event and st.session_state.current_historical_event.get("is_final_event", False)): + st.session_state.game_mode = 'scenario_over' + game_state['event_log'].append(f"--- {SCENARIOS[scenario_key]['display_name']} 종료 ---") + else: + game_state['current_turn'] += 1 + # 연도 변경 로직 (필요하다면, 예: 1960년 3월 -> 4월) + # game_state['game_year'] = ... + game_state['event_log'].append(f"--- {game_state['game_year']}년 {game_state['current_turn']}번째 국면 시작 ---") + + st.session_state.current_historical_event = None + st.session_state.event_briefing_data = None + st.session_state.actions_taken_this_turn = 0 + st.rerun() - st.divider() - st.subheader(get_text_main("term_event_log")) - log_text = "\n".join(game_state.get('event_log', [])[::-1]) - st.text_area(get_text_main("term_event_log"), log_text, height=300, disabled=True, key="event_log_area_main") + st.divider() + st.subheader(get_text_main("term_event_log")) + log_text = "\n".join(game_state.get('event_log', [])[::-1]) + st.text_area(get_text_main("term_event_log"), log_text, height=200, disabled=True, key="event_log_area_main") - with st.sidebar: - st.header(get_text_main("sidebar_title")) - display_gov_terms_glossary(difficulty) - st.divider() - st.markdown("---") - st.caption(f"현재 난이도: {difficulty}") - if st.button(get_text_main("button_restart") + " (난이도 변경)"): - keys_to_delete = [k for k in st.session_state.keys() if k != 'game_started'] - for key in keys_to_delete: del st.session_state[key] - st.session_state.game_started = False - st.rerun() + with st.sidebar: + st.header("메뉴") # get_text 사용 가능 + display_historical_glossary(scenario_key) # 시나리오별 용어사전 + st.divider() + if st.button(get_text_main("button_back_to_scenario_select") + " (다른 시대)", use_container_width=True): + st.session_state.game_mode = 'scenario_select' + keys_to_clear = ['current_scenario_key', 'game_state', 'current_historical_event', 'event_briefing_data', 'actions_taken_this_turn'] + for k in keys_to_clear: + if k in st.session_state: + del st.session_state[k] + st.rerun() if __name__ == "__main__": - main() \ No newline at end of file + historical_simulation_main() \ No newline at end of file