Update app.py
Browse files
app.py
CHANGED
|
@@ -1,326 +1,511 @@
|
|
| 1 |
import streamlit as st
|
| 2 |
import random
|
| 3 |
-
from openai import OpenAI
|
| 4 |
import time
|
| 5 |
|
| 6 |
-
# ---
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
|
| 12 |
-
# --- 국가 기관 초기화 함수 ---
|
| 13 |
-
# 이 함수를 세션 상태 초기화보다 먼저 정의합니다.
|
| 14 |
-
def initialize_branches():
|
| 15 |
-
"""국가 기관의 초기 상태와 정보를 설정합니다."""
|
| 16 |
return {
|
| 17 |
-
'
|
| 18 |
-
|
| 19 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
}
|
| 21 |
|
| 22 |
# --- 세션 상태 초기화 ---
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
st.session_state['
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
# ---
|
| 40 |
-
def
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
#
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 124 |
return False
|
| 125 |
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 149 |
|
| 150 |
|
| 151 |
# --- UI 표시 함수들 ---
|
| 152 |
-
def
|
| 153 |
-
|
| 154 |
-
st.metric("
|
| 155 |
-
st.metric("
|
| 156 |
-
st.metric("
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
for
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 167 |
glossary = {
|
| 168 |
-
"
|
| 169 |
-
"
|
| 170 |
-
"
|
| 171 |
-
"
|
| 172 |
-
"
|
| 173 |
-
"
|
| 174 |
-
"
|
| 175 |
-
"
|
| 176 |
-
"
|
| 177 |
-
|
| 178 |
}
|
| 179 |
-
with st.sidebar.expander("🏛️
|
| 180 |
for term, definition in glossary.items():
|
| 181 |
st.markdown(f"**{term}:** {definition}")
|
| 182 |
st.markdown("---")
|
| 183 |
|
| 184 |
# --- 메인 게임 루프 ---
|
| 185 |
def main():
|
| 186 |
-
st.title("
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
st.
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 200 |
else:
|
| 201 |
-
st.error("
|
| 202 |
|
| 203 |
-
if st.session_state['
|
| 204 |
-
st.
|
|
|
|
| 205 |
st.divider()
|
| 206 |
-
if st.session_state['
|
| 207 |
-
st.subheader("
|
| 208 |
-
st.markdown(st.session_state['
|
| 209 |
else:
|
| 210 |
-
st.warning("
|
| 211 |
else:
|
| 212 |
-
|
|
|
|
| 213 |
|
|
|
|
| 214 |
with col_main_ui:
|
| 215 |
-
tabs = st.tabs(["🏛️
|
| 216 |
|
| 217 |
with tabs[0]:
|
| 218 |
-
st.subheader("
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 225 |
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
# national_status가 초기화되었는지 확인
|
| 229 |
-
if 'national_status' in st.session_state:
|
| 230 |
-
st.markdown(f"현재 보유 영향력: **{st.session_state['national_status']['influence_points']} P**")
|
| 231 |
-
st.warning("행동을 선택하면 영향력 포인트가 소모됩니다.")
|
| 232 |
-
action_buttons = {
|
| 233 |
-
"정책 연구/제안": "행정부나 국회에 특정 정책 방향을 제안합니다.",
|
| 234 |
-
"정보 공개 요청": "기관 활동의 투명성을 높이도록 요구합니다.",
|
| 235 |
-
"여론 조사/캠페인": "특정 이슈에 대한 국민적 공감대를 형성하려 시도합니다.",
|
| 236 |
-
"기관 간 협의 촉구": "갈등 상황에서 기관 간 대화를 유도합니다."
|
| 237 |
-
}
|
| 238 |
-
cols = st.columns(2)
|
| 239 |
-
i = 0
|
| 240 |
-
for action, desc in action_buttons.items():
|
| 241 |
-
with cols[i % 2]:
|
| 242 |
-
# 현재 이슈가 있을 때만 행동 버튼 활성화 (선택적)
|
| 243 |
-
if st.button(action, help=desc, use_container_width=True, key=f"action_{action}", disabled=not st.session_state.get('current_issue')):
|
| 244 |
-
take_player_action(action)
|
| 245 |
-
i += 1
|
| 246 |
-
if not st.session_state.get('current_issue'):
|
| 247 |
-
st.info("새로운 이슈가 발생한 후 행동을 선택할 수 있습니다.")
|
| 248 |
else:
|
| 249 |
-
st.
|
| 250 |
|
|
|
|
|
|
|
|
|
|
| 251 |
|
| 252 |
with tabs[2]:
|
| 253 |
st.subheader("주요 사건 기록")
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
log_text = "\n".join(st.session_state['event_log'][::-1]) # 최신 로그가 위로
|
| 257 |
-
st.text_area("로그", log_text, height=300, disabled=True, key="event_log_area")
|
| 258 |
-
else:
|
| 259 |
-
st.warning("이벤트 로그를 불러오는 중입니다...")
|
| 260 |
|
| 261 |
|
| 262 |
# --- 사이드바 ---
|
| 263 |
with st.sidebar:
|
| 264 |
-
st.header("📊
|
| 265 |
-
|
| 266 |
-
|
| 267 |
-
st.markdown(f"### 현재 연도: {st.session_state['game_year']}")
|
| 268 |
-
else:
|
| 269 |
-
st.markdown("### 현재 연도: 불러오는 중...")
|
| 270 |
-
|
| 271 |
-
# national_status가 초기화되었는지 확인
|
| 272 |
-
if 'national_status' in st.session_state:
|
| 273 |
-
display_national_dashboard()
|
| 274 |
-
else:
|
| 275 |
-
st.warning("국가 현황 정보를 불러오는 중입니다...")
|
| 276 |
-
|
| 277 |
st.divider()
|
| 278 |
|
| 279 |
-
|
| 280 |
-
|
| 281 |
-
|
| 282 |
-
|
| 283 |
-
|
| 284 |
-
|
| 285 |
-
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
|
| 291 |
-
|
| 292 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 293 |
|
| 294 |
st.divider()
|
| 295 |
-
|
| 296 |
|
| 297 |
with st.expander("🎮 게임 가이드"):
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
**대한민국 균형 잡기: 삼권분립 시뮬레이션** 게임에 오신 것을 환영합니다!
|
| 301 |
|
| 302 |
**🎯 게임 목표:**
|
| 303 |
-
|
| 304 |
|
| 305 |
**🕹️ 게임 방법:**
|
| 306 |
-
1.
|
| 307 |
-
2.
|
| 308 |
-
3.
|
| 309 |
-
4.
|
| 310 |
-
5.
|
|
|
|
| 311 |
|
| 312 |
**💡 학습 포인트:**
|
| 313 |
-
*
|
| 314 |
-
*
|
| 315 |
-
*
|
|
|
|
| 316 |
|
| 317 |
-
|
| 318 |
""")
|
| 319 |
|
| 320 |
if __name__ == "__main__":
|
| 321 |
-
#
|
| 322 |
-
# 이 부분은 세션 상태 초기화 블록에서 이미 처리되므로 중복될 수 있으나,
|
| 323 |
-
# 안전을 위해 한 번 더 체크하거나, 위쪽 초기화 블록을 신뢰하고 이 부분을 제거해도 됩니다.
|
| 324 |
-
# if 'government_branches' not in st.session_state:
|
| 325 |
-
# st.session_state['government_branches'] = initialize_branches()
|
| 326 |
main()
|
|
|
|
| 1 |
import streamlit as st
|
| 2 |
import random
|
|
|
|
| 3 |
import time
|
| 4 |
|
| 5 |
+
# --- 게임 설정 ---
|
| 6 |
+
MAX_YEARS = 5 # 대통령 임기 (5년)
|
| 7 |
+
INITIAL_POLITICAL_CAPITAL = 10
|
| 8 |
+
YEARLY_CAPITAL_GAIN = 5
|
| 9 |
+
|
| 10 |
+
# --- 정부 및 다른 기관 초기화 함수 ---
|
| 11 |
+
def initialize_government():
|
| 12 |
+
"""대통령 임기 시작 시 상태 및 다른 기관 정보 초기화"""
|
| 13 |
+
# 초기 공약 설정 (예시)
|
| 14 |
+
policy_options = ["경제 성장률 5% 달성", "청년 실업률 5% 이하 달성", "부동산 시장 안정화", "외교 관계 개선", "국방력 강화", "복지 예산 10% 증액"]
|
| 15 |
+
selected_goals = random.sample(policy_options, 3) # 3개 공약 선택
|
| 16 |
+
initial_goals = {goal: 0 for goal in selected_goals} # 달성률 0%로 시작
|
| 17 |
+
|
| 18 |
+
# 국회 의석 랜덤 설정 (여소야대 가능성 포함)
|
| 19 |
+
total_seats = 300
|
| 20 |
+
gov_party_seats = random.randint(120, 170) # 여당 의석 (과반 미달 가능)
|
| 21 |
+
opp_party_seats = total_seats - gov_party_seats - random.randint(5, 15) # 야당 및 기타
|
| 22 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
return {
|
| 24 |
+
'presidency_status': {
|
| 25 |
+
'approval_rating': random.randint(40, 60), # 초기 지지율
|
| 26 |
+
'political_capital': INITIAL_POLITICAL_CAPITAL, # 행동력
|
| 27 |
+
'policy_goals': initial_goals, # 공약 및 달성률(%)
|
| 28 |
+
'stability': 100, # 국가 안정도
|
| 29 |
+
'congress_relations': random.randint(40, 60) # 국회 관계 (0~100)
|
| 30 |
+
},
|
| 31 |
+
'other_branches': {
|
| 32 |
+
'Congress': { # 국회
|
| 33 |
+
'name': '국회 (입법부)',
|
| 34 |
+
'seats': {'Government': gov_party_seats, 'Opposition': opp_party_seats, 'Others': total_seats - gov_party_seats - opp_party_seats},
|
| 35 |
+
'stance': '사안별 협조 및 견제', # 초기 입장
|
| 36 |
+
'key_focus': random.sample(['민생 안정', '정부 견제', '개혁 입법', '지역 예산 확보'], 2), # 주요 관심사
|
| 37 |
+
'pending_bills': [], # 대통령 제출 법안 등
|
| 38 |
+
'relations_score': 50 # 대통령 관계 점수 (presidency_status와 연동)
|
| 39 |
+
},
|
| 40 |
+
'Judiciary': { # 사법부
|
| 41 |
+
'name': '법원/헌법재판소 (사법부)',
|
| 42 |
+
'status': '주요 현안 관련 소송 없음',
|
| 43 |
+
'independence_level': random.randint(75, 95), # 사법부 독립성 지수
|
| 44 |
+
'pending_cases': [] # 심리 중인 주요 사건
|
| 45 |
+
}
|
| 46 |
+
},
|
| 47 |
+
'event_log': [f"제 {random.randint(19, 22)}대 대통령 임기 시작"] # 임기 시작 로그
|
| 48 |
}
|
| 49 |
|
| 50 |
# --- 세션 상태 초기화 ---
|
| 51 |
+
if 'presidency_year' not in st.session_state:
|
| 52 |
+
st.session_state['presidency_year'] = 1
|
| 53 |
+
if 'game_state' not in st.session_state:
|
| 54 |
+
# 게임 시작 시 한 번만 초기화
|
| 55 |
+
st.session_state['game_state'] = initialize_government()
|
| 56 |
+
st.session_state.game_state['event_log'].append(f"초기 국정 지지도: {st.session_state.game_state['presidency_status']['approval_rating']}%")
|
| 57 |
+
st.session_state.game_state['event_log'].append(f"선택된 주요 공약: {', '.join(st.session_state.game_state['presidency_status']['policy_goals'].keys())}")
|
| 58 |
+
if 'current_agenda' not in st.session_state:
|
| 59 |
+
st.session_state['current_agenda'] = None
|
| 60 |
+
if 'agenda_briefing' not in st.session_state:
|
| 61 |
+
st.session_state['agenda_briefing'] = None
|
| 62 |
+
if 'game_over' not in st.session_state:
|
| 63 |
+
st.session_state['game_over'] = False
|
| 64 |
+
if 'action_taken_this_year' not in st.session_state:
|
| 65 |
+
st.session_state['action_taken_this_year'] = False # 연도별 행동 1회 제한 등 가능
|
| 66 |
+
|
| 67 |
+
# --- 국정 현안 생성 함수 (Placeholder) ---
|
| 68 |
+
def generate_agenda(year, game_state):
|
| 69 |
+
"""현실적인 국정 현안 생성 (현재는 Placeholder)"""
|
| 70 |
+
agendas = [
|
| 71 |
+
{"title": "글로벌 공급망 위기 심화", "description": "주요 교역국의 경제 불안과 물류 차질로 인해 국내 수출입에 차질이 발생하고 있으며, 핵심 산업의 부품 수급난이 현실화되고 있습니다. 산업계 보호와 경제 충격 완화를 위한 정부의 선제적 대응이 요구됩니다.", "type": "economy"},
|
| 72 |
+
{"title": "대규모 감염병 재확산 조짐", "description": "해외 유입 변이 바이러스 확산으로 감염병 확진자 수가 다시 증가세를 보이고 있습니다. 의료 시스템 부담 가중과 사회적 거리두기 강화 여부를 두고 정부의 신속하고 정확한 판단이 중요해졌습니다.", "type": "social"},
|
| 73 |
+
{"title": "부동산 가격 급등 및 불안 심리 확산", "description": "수도권을 중심으로 주택 가격이 다시 급등하며 무주택자의 불안감이 커지고 있습니다. 강력한 추가 규제 또는 공급 확대 정책을 두고 여론이 갈리고 있으며, 국회에서도 관련 논의가 활발합니다.", "type": "social"},
|
| 74 |
+
{"title": "주변국과의 외교적 마찰 발생", "description": "역사 문제 또는 해양 경계선을 둘러싼 갈등으로 주변국과의 외교적 긴장감이 높아졌습니다. 양국 관계 악화가 경제 및 안보에 미칠 파장을 최소화하기 위한 섬세한 외교적 노력이 필요합니다.", "type": "diplomacy"},
|
| 75 |
+
{"title": "국회, 정부조직 개편 법안 발의", "description": "야당을 중심으로 정부 부처 통폐합 및 기능 조정을 골자로 하는 정부조직법 개정안이 발의되었습니다. 효율성 증대라는 명분과 정치적 공세라는 해석이 엇갈리며, 대통령실의 입장 표명이 요구되고 있습니다.", "type": "politics"},
|
| 76 |
+
{"title": "대형 산업 재해 발생 및 안전 규제 강화 요구", "description": "대규모 공사 현장에서 중대 산업 재해가 발생하여 다수의 인명 피해가 발생했습니다. 기업 처벌 강화와 작업장 안전 규제 전면 개정을 요구하는 목소리가 높아지고 있으며, 관련 입법 논의가 불가피해 보입니다.", "type": "social"}
|
| 77 |
+
]
|
| 78 |
+
chosen_agenda = random.choice(agendas)
|
| 79 |
+
log_message = f"{year}년차 주요 현안: {chosen_agenda['title']}"
|
| 80 |
+
if 'event_log' in game_state:
|
| 81 |
+
game_state['event_log'].append(log_message)
|
| 82 |
+
return chosen_agenda
|
| 83 |
+
|
| 84 |
+
# --- 현안 브리핑 제공 함수 (Placeholder) ---
|
| 85 |
+
def provide_agenda_briefing(agenda, game_state):
|
| 86 |
+
"""AI 대신 Placeholder 브리핑 제공"""
|
| 87 |
+
title = agenda['title']
|
| 88 |
+
briefing = f"**현안 브리핑: {title}**\n\n"
|
| 89 |
+
briefing += f"대통령님, 현재 '{title}' 현안에 대한 대응이 시급합니다.\n"
|
| 90 |
+
|
| 91 |
+
# 예시: 대통령이 취할 수 있는 조치 제안 (현안 타입별 분기 가능)
|
| 92 |
+
options = []
|
| 93 |
+
if agenda['type'] in ['economy', 'social']:
|
| 94 |
+
options.append({
|
| 95 |
+
"action": "긴급 경제/민생 안정 대책 발표 (행정명령/지시)",
|
| 96 |
+
"cost": 3,
|
| 97 |
+
"pros": "신속한 대응 가능, 단기적 여론 안정 효과.",
|
| 98 |
+
"cons": "근본 해결 어려움, 법적 근거 논란 시 사법부 제동 가능성, 국회 반발 시 정치적 부담.",
|
| 99 |
+
"congress_impact": -5, # 국회 관계 영향 (예시)
|
| 100 |
+
"judiciary_risk": 0.2 # 사법부 개입 확률 (예시)
|
| 101 |
+
})
|
| 102 |
+
options.append({
|
| 103 |
+
"action": "관련 법률안/추경 예산안 국회 제출",
|
| 104 |
+
"cost": 4,
|
| 105 |
+
"pros": "근본적 해결 기반 마련, 통과 시 정책 성공률 기여.",
|
| 106 |
+
"cons": f"국회 통과 불확실 (현재 관계: {game_state['other_branches']['Congress']['relations_score']}/100), 장시간 소요, 여야 협상 난항 예상.",
|
| 107 |
+
"congress_impact": 10, # 통과 시 관계 개선 기대 (예시)
|
| 108 |
+
"judiciary_risk": 0.05
|
| 109 |
+
})
|
| 110 |
+
if agenda['type'] == 'diplomacy':
|
| 111 |
+
options.append({
|
| 112 |
+
"action": "외교 특사 파견 및 고위급 회담 추진",
|
| 113 |
+
"cost": 3,
|
| 114 |
+
"pros": "직접적 대화를 통한 갈등 완화 시도.",
|
| 115 |
+
"cons": "상대국 거부 시 외교적 고립 심화 가능성, 국내 강경 여론 부담.",
|
| 116 |
+
"congress_impact": 0,
|
| 117 |
+
"judiciary_risk": 0
|
| 118 |
+
})
|
| 119 |
+
if agenda['type'] == 'politics':
|
| 120 |
+
options.append({
|
| 121 |
+
"action": "국회와 협상 시도 (여야 지도부 면담)",
|
| 122 |
+
"cost": 2,
|
| 123 |
+
"pros": "정치적 타협점 모색, 국회 관계 개선 기회.",
|
| 124 |
+
"cons": "협상 결렬 시 리더십 타격, 시간 소모.",
|
| 125 |
+
"congress_impact": 5,
|
| 126 |
+
"judiciary_risk": 0
|
| 127 |
+
})
|
| 128 |
+
options.append({
|
| 129 |
+
"action": "대국민 담화 발표 (여론전)",
|
| 130 |
+
"cost": 1,
|
| 131 |
+
"pros": "국정 운영 방향 설명, 지지층 결집 시도.",
|
| 132 |
+
"cons": "설득력 부족 시 역풍 가능성, 지지도 변동성 큼.",
|
| 133 |
+
"congress_impact": -2,
|
| 134 |
+
"judiciary_risk": 0
|
| 135 |
+
})
|
| 136 |
+
|
| 137 |
+
briefing += "\n**대응 방안 제안:**\n"
|
| 138 |
+
for i, opt in enumerate(options):
|
| 139 |
+
briefing += f"{i+1}. **{opt['action']}** (정치력 소모: {opt['cost']})\n"
|
| 140 |
+
briefing += f" - **긍정:** {opt['pros']}\n"
|
| 141 |
+
briefing += f" - **부정:** {opt['cons']}\n"
|
| 142 |
+
# 간단한 예상 결과 추가
|
| 143 |
+
briefing += f" - *예상 국회 반응:* 관계 점수 {opt['congress_impact']:+}점 변동 가능성\n"
|
| 144 |
+
briefing += f" - *예상 사법부 리스크:* {opt['judiciary_risk']*100:.0f}% 확률로 관련 소송/심판 가능성\n\n"
|
| 145 |
+
|
| 146 |
+
log_message = f" - 현안 '{title}'에 대한 브리핑 완료."
|
| 147 |
+
if 'event_log' in game_state:
|
| 148 |
+
game_state['event_log'].append(log_message)
|
| 149 |
+
|
| 150 |
+
# 브리핑 텍스트와 함께 선택 옵션 데이터 반환
|
| 151 |
+
return {"text": briefing, "options": options}
|
| 152 |
+
|
| 153 |
+
# --- 대통령 행동 실행 함수 ---
|
| 154 |
+
def execute_presidential_action(action_index, briefing_data, game_state):
|
| 155 |
+
"""선택된 대통령 행동을 실행하고 게임 상태 업데이트"""
|
| 156 |
+
options = briefing_data.get("options", [])
|
| 157 |
+
if not (0 <= action_index < len(options)):
|
| 158 |
+
st.error("잘못된 행동 선택입니다.")
|
| 159 |
return False
|
| 160 |
|
| 161 |
+
selected_action = options[action_index]
|
| 162 |
+
action_name = selected_action['action']
|
| 163 |
+
cost = selected_action['cost']
|
| 164 |
+
presidency_status = game_state['presidency_status']
|
| 165 |
+
other_branches = game_state['other_branches']
|
| 166 |
+
|
| 167 |
+
if presidency_status['political_capital'] < cost:
|
| 168 |
+
st.warning(f"정치력이 부족합니다! (필요: {cost}, 보유: {presidency_status['political_capital']})")
|
| 169 |
+
return False
|
| 170 |
+
|
| 171 |
+
# 정치력 소모
|
| 172 |
+
presidency_status['political_capital'] -= cost
|
| 173 |
+
log_message = f" - 대통령 지시: '{action_name}' 수행 (정치력 {cost} 소모)"
|
| 174 |
+
game_state['event_log'].append(log_message)
|
| 175 |
+
st.success(f"'{action_name}' 지시 완료.")
|
| 176 |
+
|
| 177 |
+
# 행동 유형별 게임 상태 업데이트 (간단한 예시)
|
| 178 |
+
if "법률안" in action_name or "예산안" in action_name:
|
| 179 |
+
bill_name = f"{st.session_state['presidency_year']}년차-{agenda['title'][:10]}-관련법안"
|
| 180 |
+
other_branches['Congress']['pending_bills'].append({"name": bill_name, "status": "제출됨", "origin": "President"})
|
| 181 |
+
game_state['event_log'].append(f" - '{bill_name}'이(가) 국회에 제출되었습니다.")
|
| 182 |
+
# 국회 관계에 즉각적인 영향은 주지 않고, 통과 여부에서 반영
|
| 183 |
+
elif "행정명령" in action_name or "대책 발표" in action_name:
|
| 184 |
+
# 즉각적인 안정도/지지도 영향 (소폭)
|
| 185 |
+
presidency_status['stability'] = min(100, presidency_status['stability'] + random.randint(1, 5))
|
| 186 |
+
presidency_status['approval_rating'] += random.randint(-2, 3)
|
| 187 |
+
# 사법부 리스크 반영 (다음 턴 시뮬레이션에서 처리)
|
| 188 |
+
if random.random() < selected_action.get('judiciary_risk', 0):
|
| 189 |
+
case_name = f"{st.session_state['presidency_year']}년차-{action_name[:10]}-위헌소송"
|
| 190 |
+
other_branches['Judiciary']['pending_cases'].append({"name": case_name, "type": "Constitutional Review"})
|
| 191 |
+
game_state['event_log'].append(f" - [경고] '{action_name}' 관련하여 위헌 소송이 제기될 가능성이 포착되었습니다!")
|
| 192 |
+
elif "협상" in action_name:
|
| 193 |
+
# 국회 관계 점수 소폭 상승 시도
|
| 194 |
+
change = random.randint(3, 8)
|
| 195 |
+
other_branches['Congress']['relations_score'] = min(100, other_branches['Congress']['relations_score'] + change)
|
| 196 |
+
game_state['event_log'].append(f" - 국회와의 관계가 소폭 개선되었습니다. (관계 점수 {change:+}점)")
|
| 197 |
+
elif "대국민 담화" in action_name:
|
| 198 |
+
# 지지도 변동폭 크게
|
| 199 |
+
change = random.randint(-5, 7)
|
| 200 |
+
presidency_status['approval_rating'] += change
|
| 201 |
+
game_state['event_log'].append(f" - 대국민 담화 결과, 국정 지지도가 {change:+}%p 변동했습니다.")
|
| 202 |
+
elif "거부권 행사" in action_name: # 거부권은 시뮬레이션 단계에서 국회가 통과시킨 법안에 대해 행사
|
| 203 |
+
pass # 실제 로직은 simulate 단계에서 처리
|
| 204 |
+
|
| 205 |
+
# 지지도/관계 점수 0~100 범위 유지
|
| 206 |
+
presidency_status['approval_rating'] = max(0, min(100, presidency_status['approval_rating']))
|
| 207 |
+
other_branches['Congress']['relations_score'] = max(0, min(100, other_branches['Congress']['relations_score']))
|
| 208 |
+
|
| 209 |
+
st.session_state['action_taken_this_year'] = True # 행동 완료 표시
|
| 210 |
+
return True
|
| 211 |
+
|
| 212 |
+
# --- 견제와 균형 시뮬레이션 함수 ---
|
| 213 |
+
def simulate_checks_and_balances(game_state):
|
| 214 |
+
"""국회와 사법부의 반응을 시뮬레이션하고 결과 반영"""
|
| 215 |
+
presidency_status = game_state['presidency_status']
|
| 216 |
+
congress = game_state['other_branches']['Congress']
|
| 217 |
+
judiciary = game_state['other_branches']['Judiciary']
|
| 218 |
+
log = game_state['event_log']
|
| 219 |
+
year = st.session_state['presidency_year']
|
| 220 |
+
|
| 221 |
+
log.append(f"--- {year}년차 연말 정국 결산 ---")
|
| 222 |
+
|
| 223 |
+
# 1. 국회 시뮬레이션 (법안 처리 등)
|
| 224 |
+
processed_bills = []
|
| 225 |
+
for i, bill in enumerate(congress['pending_bills']):
|
| 226 |
+
if bill['status'] == '제출됨':
|
| 227 |
+
# 법안 통과 확률 계산 (더 정교화 가능)
|
| 228 |
+
pass_chance = 0.3 # 기본 통과 확률
|
| 229 |
+
pass_chance += (congress['relations_score'] - 50) * 0.005 # 국회 관계 영향
|
| 230 |
+
pass_chance += (presidency_status['approval_rating'] - 50) * 0.003 # 대통령 지지율 영향
|
| 231 |
+
# 여소야대 페널티 (예시)
|
| 232 |
+
if congress['seats']['Government'] < (sum(congress['seats'].values()) / 2):
|
| 233 |
+
pass_chance -= 0.15
|
| 234 |
+
|
| 235 |
+
pass_chance = max(0.05, min(0.95, pass_chance)) # 5% ~ 95%
|
| 236 |
+
|
| 237 |
+
if random.random() < pass_chance:
|
| 238 |
+
bill['status'] = '통과'
|
| 239 |
+
log.append(f" - [국회] '{bill['name']}' 법안이 국회를 통과했습니다!")
|
| 240 |
+
# 정책 목표 달성률 증가 (연관성 판단 필요 - 여기서는 랜덤하게 1개 목표 증가)
|
| 241 |
+
if presidency_status['policy_goals']:
|
| 242 |
+
goal_to_update = random.choice(list(presidency_status['policy_goals'].keys()))
|
| 243 |
+
progress = random.randint(5, 15)
|
| 244 |
+
presidency_status['policy_goals'][goal_to_update] = min(100, presidency_status['policy_goals'][goal_to_update] + progress)
|
| 245 |
+
log.append(f" - 공약 '{goal_to_update}' 달성률이 {progress}%p 상승했습니다.")
|
| 246 |
+
presidency_status['approval_rating'] += random.randint(1, 4) # 지지도 소폭 상승
|
| 247 |
+
congress['relations_score'] = min(100, congress['relations_score'] + random.randint(2, 6)) # 관계 개선
|
| 248 |
+
else:
|
| 249 |
+
bill['status'] = '부결'
|
| 250 |
+
log.append(f" - [국회] '{bill['name']}' 법안이 국회에서 부결되었습니다.")
|
| 251 |
+
presidency_status['approval_rating'] -= random.randint(1, 3) # 지지도 소폭 하락
|
| 252 |
+
congress['relations_score'] = max(0, congress['relations_score'] - random.randint(1, 4)) # 관계 악화
|
| 253 |
+
|
| 254 |
+
processed_bills.append(i) # 처리된 법안 인덱스 저장
|
| 255 |
+
|
| 256 |
+
# 처리된 법안 목록에서 제거 (뒤에서부터 제거해야 인덱스 문제 없음)
|
| 257 |
+
for i in sorted(processed_bills, reverse=True):
|
| 258 |
+
del congress['pending_bills'][i]
|
| 259 |
+
|
| 260 |
+
# 국회 랜덤 이벤트 (예: 국정조사 발동)
|
| 261 |
+
if random.random() < 0.1: # 10% 확률
|
| 262 |
+
log.append(" - [국회] 특정 사안에 대한 국정조사가 발동되었습니다. 정부 부처 긴장 고조.")
|
| 263 |
+
presidency_status['stability'] -= random.randint(3, 7)
|
| 264 |
+
congress['relations_score'] = max(0, congress['relations_score'] - random.randint(5, 10))
|
| 265 |
+
|
| 266 |
+
# 2. 사법부 시뮬레이션 (위헌 심사 등)
|
| 267 |
+
ruled_cases = []
|
| 268 |
+
for i, case in enumerate(judiciary['pending_cases']):
|
| 269 |
+
# 판결 확률 계산 (독립성 지수 영향)
|
| 270 |
+
uphold_chance = 0.6 # 기본 합헌/적법 확률
|
| 271 |
+
uphold_chance += (50 - judiciary['independence_level']) * 0.005 # 독립성 낮으면 정부 유리? (논쟁적)
|
| 272 |
+
|
| 273 |
+
if random.random() < uphold_chance:
|
| 274 |
+
case['status'] = '합헌/적법'
|
| 275 |
+
log.append(f" - [사법부] '{case['name']}'에 대해 합헌/적법 결정이 내려졌습니다.")
|
| 276 |
+
else:
|
| 277 |
+
case['status'] = '위헌/위법'
|
| 278 |
+
log.append(f" - [사법부] '{case['name']}'에 대해 위헌/위법 결정이 내려졌습니다! 관련 정책 효력 상실.")
|
| 279 |
+
presidency_status['approval_rating'] -= random.randint(3, 8) # 지지도 하락
|
| 280 |
+
presidency_status['stability'] -= random.randint(2, 6) # 안정도 하락
|
| 281 |
+
# 관련 정책 목표 후퇴 (구현 필요)
|
| 282 |
+
|
| 283 |
+
ruled_cases.append(i)
|
| 284 |
+
|
| 285 |
+
for i in sorted(ruled_cases, reverse=True):
|
| 286 |
+
del judiciary['pending_cases'][i]
|
| 287 |
+
|
| 288 |
+
# 3. 연말 최종 상태 업데이트
|
| 289 |
+
presidency_status['approval_rating'] += random.randint(-2, 2) # 자연 변동
|
| 290 |
+
presidency_status['stability'] += random.randint(-3, 1) # 자연 변동
|
| 291 |
+
# 지지도/안정도/관계 점수 범위 유지
|
| 292 |
+
presidency_status['approval_rating'] = max(0, min(100, presidency_status['approval_rating']))
|
| 293 |
+
presidency_status['stability'] = max(0, min(100, presidency_status['stability']))
|
| 294 |
+
congress['relations_score'] = max(0, min(100, congress['relations_score']))
|
| 295 |
+
|
| 296 |
+
log.append(f"--- {year}년차 국정 결산 완료 ---")
|
| 297 |
|
| 298 |
|
| 299 |
# --- UI 표시 함수들 ---
|
| 300 |
+
def display_presidency_dashboard(game_state):
|
| 301 |
+
status = game_state['presidency_status']
|
| 302 |
+
st.metric("국정 지지도", f"{status['approval_rating']}%")
|
| 303 |
+
st.metric("대통령 정치력", f"{status['political_capital']} P")
|
| 304 |
+
st.metric("국가 안정도", f"{status['stability']} / 100")
|
| 305 |
+
st.metric("국회 관계", f"{game_state['other_branches']['Congress']['relations_score']} / 100")
|
| 306 |
+
|
| 307 |
+
st.subheader("주요 공약 달성률")
|
| 308 |
+
for goal, progress in status['policy_goals'].items():
|
| 309 |
+
st.progress(progress / 100, text=f"{goal} ({progress}%)")
|
| 310 |
+
|
| 311 |
+
def display_other_branches_status(game_state):
|
| 312 |
+
congress = game_state['other_branches']['Congress']
|
| 313 |
+
judiciary = game_state['other_branches']['Judiciary']
|
| 314 |
+
|
| 315 |
+
with st.expander(f"{congress['name']} (관계 점수: {congress['relations_score']})"):
|
| 316 |
+
st.markdown(f"**의석 분포:** 여당 {congress['seats']['Government']}석 / 야당 {congress['seats']['Opposition']}석 / 기타 {congress['seats']['Others']}석")
|
| 317 |
+
st.markdown(f"**현재 입장:** {congress['stance']}")
|
| 318 |
+
st.markdown(f"**주요 관심사:** {', '.join(congress['key_focus'])}")
|
| 319 |
+
if congress['pending_bills']:
|
| 320 |
+
st.markdown("**계류 중인 주요 법안:**")
|
| 321 |
+
for bill in congress['pending_bills']:
|
| 322 |
+
st.markdown(f"- {bill['name']} ({bill.get('status', '제출됨')})")
|
| 323 |
+
else:
|
| 324 |
+
st.markdown("**계류 중인 주요 법안 없음**")
|
| 325 |
+
|
| 326 |
+
with st.expander(f"{judiciary['name']} (독립성: {judiciary['independence_level']})"):
|
| 327 |
+
st.markdown(f"**현재 상태:** {judiciary['status']}")
|
| 328 |
+
if judiciary['pending_cases']:
|
| 329 |
+
st.markdown("**심리 중인 주요 사건:**")
|
| 330 |
+
for case in judiciary['pending_cases']:
|
| 331 |
+
st.markdown(f"- {case['name']} ({case.get('type', '일반')})")
|
| 332 |
+
else:
|
| 333 |
+
st.markdown("**심리 중인 주요 사건 없음**")
|
| 334 |
+
|
| 335 |
+
def display_gov_terms_glossary():
|
| 336 |
glossary = {
|
| 337 |
+
"법률안 거부권 (재의 요구권)": "국회가 통과시킨 법률안에 대해 대통령이 이의가 있을 때, 공포하지 않고 국회에 다시 심의해달라고 요구할 수 있는 권한. 국회는 재적의원 과반수 출석과 출석의원 3분의 2 이상의 찬성으로 재의결 가능.",
|
| 338 |
+
"행정명령": "대통령이 법률에서 위임받은 사항이나 법률을 집행하기 위해 필요한 사항에 대해 발하는 명령 (대통령령). 법률의 범위 내에서만 가능하며, 위헌/위법 시 사법부의 통제를 받음.",
|
| 339 |
+
"예산안 편성권": "정부(대통령)가 다음 해의 국가 살림 계획인 예산안을 짜서 국회에 제출할 수 있는 권한. 국회는 심의 과정에서 수정 가능.",
|
| 340 |
+
"국회 예산 심의/확정권": "정부가 제출한 예산안을 국회가 심사하고 최종적으로 확정하는 권한. 정부 예산안을 삭감하거나 증액(정부 동의 필요)할 수 있음.",
|
| 341 |
+
"위헌법률심판권": "국회가 만든 법률이 헌법에 위배되는지 여부를 헌법재판소가 심판하는 권한.",
|
| 342 |
+
"명령·규칙·처분 심사권": "대통령령 등 행정기관이 만든 명령이나 규칙, 또는 행정 처분이 헌법이나 법률에 위반되는지 여부를 대법원이 최종적으로 심사하는 권한.",
|
| 343 |
+
"국정감사/조사권": "국회가 정부의 국정 운영 전반이나 특정 사안에 대해 잘못된 점이 없는지 감사하거나 조사할 수 있는 권한. 행정부를 견제하는 주요 수단.",
|
| 344 |
+
"탄핵소추권": "대통령 등 고위 공직자가 직무상 중대한 헌법이나 법률 위반 시, 국회가 파면을 결정해달라고 헌법재판소에 청구할 수 있는 권한.",
|
| 345 |
+
"인사청문회": "대통령이 임명하는 고위 공직 후보자에 대해 국회가 직무수행 능력과 도덕성 등을 검증하는 절차. 일부 직위는 국회 동의가 필수적.",
|
| 346 |
+
"정치력 (Political Capital)": "대통령이 자신의 정책을 추진하거나 영향력을 행사하는 데 사용할 수 있는 비공식적인 자원. 지지도, 협상력, 리더십 등을 포함하는 개념으로 게임 내 행동력으로 사용됨.",
|
| 347 |
}
|
| 348 |
+
with st.sidebar.expander("🏛️ 정부 용어 사전", expanded=False):
|
| 349 |
for term, definition in glossary.items():
|
| 350 |
st.markdown(f"**{term}:** {definition}")
|
| 351 |
st.markdown("---")
|
| 352 |
|
| 353 |
# --- 메인 게임 루프 ---
|
| 354 |
def main():
|
| 355 |
+
st.title("👑 대통령의 균형추: 삼권분립 리더십 시뮬레이션")
|
| 356 |
+
|
| 357 |
+
if st.session_state['game_over']:
|
| 358 |
+
st.balloons()
|
| 359 |
+
st.header("임기 종료")
|
| 360 |
+
st.subheader("최종 국정 운영 결과")
|
| 361 |
+
final_status = st.session_state.game_state['presidency_status']
|
| 362 |
+
st.metric("최종 국정 지지도", f"{final_status['approval_rating']}%")
|
| 363 |
+
st.metric("최종 국가 안정도", f"{final_status['stability']}")
|
| 364 |
+
st.metric("최종 국회 관계", f"{st.session_state.game_state['other_branches']['Congress']['relations_score']}")
|
| 365 |
+
st.write("주요 공약 최종 달성률:")
|
| 366 |
+
for goal, progress in final_status['policy_goals'].items():
|
| 367 |
+
st.progress(progress / 100, text=f"{goal} ({progress}%)")
|
| 368 |
+
st.subheader("국정 운영 기록")
|
| 369 |
+
log_text = "\n".join(st.session_state.game_state['event_log'][::-1])
|
| 370 |
+
st.text_area("로그", log_text, height=400, disabled=True, key="final_log")
|
| 371 |
+
if st.button("다시 시작하기"):
|
| 372 |
+
# 세션 상태 초기화 및 새로고침
|
| 373 |
+
for key in list(st.session_state.keys()):
|
| 374 |
+
del st.session_state[key]
|
| 375 |
+
st.rerun()
|
| 376 |
+
return # 게임 오버 시 메인 루프 종료
|
| 377 |
+
|
| 378 |
+
col_agenda, col_main_ui = st.columns([2, 3])
|
| 379 |
+
|
| 380 |
+
# --- 왼쪽: 국정 현안 브리핑 ---
|
| 381 |
+
with col_agenda:
|
| 382 |
+
st.header(f"📰 {st.session_state['presidency_year']}년차 국정 현안")
|
| 383 |
+
|
| 384 |
+
# 새 현안 생성 버튼 (연도 시작 시 자동으로 생성되도록 변경)
|
| 385 |
+
if st.session_state['current_agenda'] is None:
|
| 386 |
+
with st.spinner("새로운 국정 현안 보고 준비 중..."):
|
| 387 |
+
time.sleep(1) # 로딩 효과
|
| 388 |
+
agenda = generate_agenda(st.session_state['presidency_year'], st.session_state.game_state)
|
| 389 |
+
if agenda:
|
| 390 |
+
st.session_state['current_agenda'] = agenda
|
| 391 |
+
st.session_state['agenda_briefing'] = provide_agenda_briefing(agenda, st.session_state.game_state)
|
| 392 |
+
st.session_state['action_taken_this_year'] = False # 새 현안 발생 시 행동 가능
|
| 393 |
+
st.rerun() # 즉시 반영
|
| 394 |
else:
|
| 395 |
+
st.error("현안 생성에 실패했습니다.")
|
| 396 |
|
| 397 |
+
if st.session_state['current_agenda']:
|
| 398 |
+
st.subheader(f"현안: {st.session_state['current_agenda']['title']}")
|
| 399 |
+
st.markdown(st.session_state['current_agenda']['description'])
|
| 400 |
st.divider()
|
| 401 |
+
if st.session_state['agenda_briefing']:
|
| 402 |
+
st.subheader("📝 보좌진 브리핑 및 대응 방안")
|
| 403 |
+
st.markdown(st.session_state['agenda_briefing']['text'])
|
| 404 |
else:
|
| 405 |
+
st.warning("브리핑 정보를 불러오는 중입니다.")
|
| 406 |
else:
|
| 407 |
+
# 이 부분은 위 로직에 의해 거의 표시되지 않음
|
| 408 |
+
st.info("다음 연도 국정 현안을 기다리고 있습니다.")
|
| 409 |
|
| 410 |
+
# --- 오른쪽: 메인 UI (탭) ---
|
| 411 |
with col_main_ui:
|
| 412 |
+
tabs = st.tabs(["🏛️ 대통령 집무실 (행동 선택)", "📊 국회 및 사법부 동향", "📜 국정 운영 로그"])
|
| 413 |
|
| 414 |
with tabs[0]:
|
| 415 |
+
st.subheader("대통령 지시 사항")
|
| 416 |
+
if st.session_state['current_agenda'] and st.session_state['agenda_briefing']:
|
| 417 |
+
if not st.session_state['action_taken_this_year']:
|
| 418 |
+
st.markdown(f"현재 정치력: **{st.session_state.game_state['presidency_status']['political_capital']} P**")
|
| 419 |
+
options = st.session_state['agenda_briefing'].get("options", [])
|
| 420 |
+
num_options = len(options)
|
| 421 |
+
cols = st.columns(2) # 2열 배치
|
| 422 |
+
|
| 423 |
+
for i, opt in enumerate(options):
|
| 424 |
+
col_index = i % 2
|
| 425 |
+
with cols[col_index]:
|
| 426 |
+
button_label = f"{opt['action']} ({opt['cost']}P)"
|
| 427 |
+
button_key = f"action_{st.session_state['presidency_year']}_{i}" # 연도별 고유 키
|
| 428 |
+
if st.button(button_label, key=button_key, use_container_width=True,
|
| 429 |
+
help=f"긍정: {opt['pros']} / 부정: {opt['cons']}",
|
| 430 |
+
disabled=(st.session_state.game_state['presidency_status']['political_capital'] < opt['cost'])):
|
| 431 |
+
execute_presidential_action(i, st.session_state['agenda_briefing'], st.session_state.game_state)
|
| 432 |
+
st.rerun() # 행동 후 즉시 상태 업데이트 반영
|
| 433 |
+
else:
|
| 434 |
+
st.info(f"{st.session_state['presidency_year']}년차 주요 지시는 이미 내려졌습니다. '다음 해 국정 운영' 버튼을 눌러주세요.")
|
| 435 |
|
| 436 |
+
elif st.session_state['action_taken_this_year']:
|
| 437 |
+
st.info(f"{st.session_state['presidency_year']}년차 주요 지시는 이미 내려졌습니다. '다음 해 국정 운영' 버튼을 눌러주세요.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 438 |
else:
|
| 439 |
+
st.info("국정 현안 브리핑을 기다리고 있습니다.")
|
| 440 |
|
| 441 |
+
with tabs[1]:
|
| 442 |
+
st.subheader("다른 국가기관 동향")
|
| 443 |
+
display_other_branches_status(st.session_state.game_state)
|
| 444 |
|
| 445 |
with tabs[2]:
|
| 446 |
st.subheader("주요 사건 기록")
|
| 447 |
+
log_text = "\n".join(st.session_state.game_state['event_log'][::-1]) # 최신 로그가 위로
|
| 448 |
+
st.text_area("로그", log_text, height=400, disabled=True, key="event_log_area")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 449 |
|
| 450 |
|
| 451 |
# --- 사이드바 ---
|
| 452 |
with st.sidebar:
|
| 453 |
+
st.header("📊 국정 현황판")
|
| 454 |
+
st.markdown(f"### 임기 {st.session_state['presidency_year']} / {MAX_YEARS} 년차")
|
| 455 |
+
display_presidency_dashboard(st.session_state.game_state)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 456 |
st.divider()
|
| 457 |
|
| 458 |
+
# 다음 해로 넘어가기 버튼
|
| 459 |
+
if st.button("➡️ 다음 해 국정 운영", use_container_width=True, key="next_year_button",
|
| 460 |
+
disabled=not st.session_state['action_taken_this_year']): # 행동을 해야 다음 해로
|
| 461 |
+
with st.spinner(f"{st.session_state['presidency_year']}년차 국정 결산 및 다음 해 준비 중..."):
|
| 462 |
+
simulate_checks_and_balances(st.session_state.game_state) # 시뮬레이션 실행
|
| 463 |
+
st.session_state['presidency_year'] += 1
|
| 464 |
+
# 연간 정치력 회복
|
| 465 |
+
st.session_state.game_state['presidency_status']['political_capital'] += YEARLY_CAPITAL_GAIN
|
| 466 |
+
# 다음 턴 위해 현안/브리핑 초기화
|
| 467 |
+
st.session_state['current_agenda'] = None
|
| 468 |
+
st.session_state['agenda_briefing'] = None
|
| 469 |
+
st.session_state['action_taken_this_year'] = False # 새해에는 행동 가능
|
| 470 |
+
|
| 471 |
+
# 게임 오버 조건 확인
|
| 472 |
+
if st.session_state['presidency_year'] > MAX_YEARS:
|
| 473 |
+
st.session_state['game_over'] = True
|
| 474 |
+
st.session_state.game_state['event_log'].append(f"--- 임기 종료 ---")
|
| 475 |
+
|
| 476 |
+
time.sleep(1.5) # 결과 처리 시간 보여주기
|
| 477 |
+
st.rerun() # 화면 새로고침하여 다음 턴 시작
|
| 478 |
+
|
| 479 |
+
if not st.session_state['action_taken_this_year'] and st.session_state['current_agenda']:
|
| 480 |
+
st.warning("올해 국정 현안에 대한 지시를 내려주세요.")
|
| 481 |
|
| 482 |
st.divider()
|
| 483 |
+
display_gov_terms_glossary() # 정부 용어 사전 표시
|
| 484 |
|
| 485 |
with st.expander("🎮 게임 가이드"):
|
| 486 |
+
st.markdown(f"""
|
| 487 |
+
**대통령의 균형추: 삼권분립 리더십 시뮬레이션** 게임에 오신 것을 환영합니다, 대통령님!
|
|
|
|
| 488 |
|
| 489 |
**🎯 게임 목표:**
|
| 490 |
+
{MAX_YEARS}년의 임기 동안 발생하는 국정 현안에 대응하고, 주요 공약을 이행하며 성공적인 대통령으로 임기를 마치는 것입니다. 이 과정에서 국회(입법부)의 협력과 견제, 사법부의 심사를 경험하며 삼권분립의 원리를 체감하게 됩니다.
|
| 491 |
|
| 492 |
**🕹️ 게임 방법:**
|
| 493 |
+
1. **현안 브리핑 확인 (매년 초):** 새로운 국정 현안과 보좌진의 대응 방안 브리핑을 확인합니다. (왼쪽 영역)
|
| 494 |
+
2. **대통령 지시 내리기:** '대통령 집무실' 탭에서 제시된 대응 방안 중 하나를 선택하여 지시를 내립니다. 행동에는 '정치력(P)'이 소모됩니다. (오른쪽 탭 1)
|
| 495 |
+
3. **다른 기관 동향 파악:** '국회 및 사법부 동향' 탭에서 현재 국회와의 관계, 계류 법안, 사법부의 주요 활동 등을 확인합니다. (오른쪽 탭 2)
|
| 496 |
+
4. **국정 운영 로그 확인:** 대통령의 지시, 국회/사법부의 반응, 지지도 변화 등 주요 사건 기록을 확인합니다. (오른쪽 탭 3)
|
| 497 |
+
5. **다음 해 국정 운영:** 사이드바의 '다음 해 국정 운영' 버튼을 눌러 연말 국정 결산(국회/사법부 반응 시뮬레이션)을 진행하고 다음 해로 넘어갑니다. (대통령 지시를 내려야 활성화됨)
|
| 498 |
+
6. **지표 관리:** 사이드바의 '국정 현황판'에서 지지도, 정치력, 공약 달성률 등을 꾸준히 확인하며 임기를 운영하세요.
|
| 499 |
|
| 500 |
**💡 학습 포인트:**
|
| 501 |
+
* 대통령의 **권한**(행정명령, 법안제출권 등)과 **한계**(정치력 소모, 국회/사법부 견제)를 느껴보세요.
|
| 502 |
+
* 국회와의 **관계 점수**가 법안 통과 등에 미치는 영향을 관찰하세요. (협상과 견제)
|
| 503 |
+
* 대통령의 결정이 때로는 **사법부의 심판** 대상이 될 수 있음을 확인하세요. (위헌/위법 결정)
|
| 504 |
+
* '정부 용어 사전'을 통해 관련 용어를 익혀보세요.
|
| 505 |
|
| 506 |
+
**성공적인 국정 운영을 기원합니다!**
|
| 507 |
""")
|
| 508 |
|
| 509 |
if __name__ == "__main__":
|
| 510 |
+
# 필요시 초기화 로직 추가 가능 (현재는 세션 상태 블록에서 처리)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 511 |
main()
|