NorwayForest/Ladder_Game
0
1import streamlit as st2import matplotlib3matplotlib.use('Agg')4import matplotlib.pyplot as plt5import random6import numpy as np7 8# 페이지 설정9st.set_page_config(10 page_title="🎮 Ladder Game", 11 page_icon="🎮",12 layout="wide"13)14 15def create_ladder_game(num_players, results_list):16 """사다리 게임 생성 및 실행"""17 try:18 # 결과 처리 - 수정된 부분19 if not results_list or len(results_list) == 0:20 results = [f"Result{i}" for i in range(1, num_players + 1)]21 else:22 results = results_list.copy()23 # 결과 개수 맞추기 - 부족한 경우 빈 문자열로 채우기24 while len(results) < num_players:25 results.append("") # 빈 문자열로 채우기26 results = results[:num_players]27 28 # 사다리 생성29 ladder_height = 830 horizontal_lines = []31 32 # 랜덤 시드 설정 (재실행시 다른 결과)33 random.seed()34 35 for level in range(1, ladder_height):36 for pos in range(num_players - 1):37 if random.random() < 0.35: # 35% 확률로 가로선 생성38 # 인접한 가로선이 있는지 체크39 adjacent = False40 for line_level, line_pos in horizontal_lines:41 if line_level == level and abs(line_pos - pos) <= 1:42 adjacent = True43 break44 if not adjacent:45 horizontal_lines.append((level, pos))46 47 # 각 참가자의 경로 추적 및 결과 계산48 final_results = {}49 all_paths = {}50 51 for start_pos in range(num_players):52 current_pos = start_pos53 path = [(0, current_pos)]54 55 # 레벨별로 경로 추적56 for level in range(1, ladder_height + 1):57 # 현재 레벨에서 가로선이 있는지 확인58 for line_level, line_pos in horizontal_lines:59 if line_level == level:60 if line_pos == current_pos and current_pos < num_players - 1:61 # 오른쪽으로 이동62 current_pos += 163 break64 elif line_pos == current_pos - 1 and current_pos > 0:65 # 왼쪽으로 이동66 current_pos -= 167 break68 69 # 안전 장치: 범위 체크70 current_pos = max(0, min(num_players - 1, current_pos))71 path.append((level, current_pos))72 73 # 최종 결과 저장74 final_results[f"user{start_pos + 1}"] = results[current_pos]75 all_paths[f"user{start_pos + 1}"] = path76 77 # 시각화 (크기 60%로 축소)78 fig, ax = plt.subplots(figsize=(max(7.2, num_players * 1.2), 4.8))79 80 # 세로선 그리기81 for i in range(num_players):82 ax.plot([i, i], [ladder_height, 0], 'k-', linewidth=3)83 84 # 가로선 그리기85 for level, pos in horizontal_lines:86 y = ladder_height - level87 ax.plot([pos, pos + 1], [y, y], 'k-', linewidth=4)88 89 # 참가자 이름 표시90 for i in range(num_players):91 ax.text(i, ladder_height + 0.4, f"user{i+1}", 92 ha='center', va='center', fontsize=12, fontweight='bold',93 bbox=dict(boxstyle="round,pad=0.3", facecolor="lightblue", alpha=0.8))94 95 # 결과 표시 - 수정된 부분96 for i, result in enumerate(results):97 display_result = result if result.strip() else "빈칸" # 빈 문자열이면 "빈칸" 표시98 result_color = "lightcoral" if result.strip() else "lightgray" # 빈칸이면 회색99 ax.text(i, -0.4, display_result, 100 ha='center', va='center', fontsize=11, fontweight='bold',101 bbox=dict(boxstyle="round,pad=0.3", facecolor=result_color, alpha=0.8))102 103 # 각 참가자의 경로를 다른 색으로 표시104 colors = ['red', 'blue', 'green', 'orange', 'purple', 'brown', 'pink', 'gray', 'cyan', 'magenta']105 106 for idx in range(num_players):107 player = f"user{idx + 1}"108 path = all_paths[player]109 color = colors[idx % len(colors)]110 111 # 경로 그리기112 x_coords = [point[1] for point in path]113 y_coords = [ladder_height - point[0] for point in path]114 115 ax.plot(x_coords, y_coords, color=color, linewidth=3, alpha=0.7, label=player)116 117 # 시작점과 끝점 마킹118 ax.scatter(x_coords[0], y_coords[0], color=color, s=120, marker='o', alpha=0.9)119 ax.scatter(x_coords[-1], y_coords[-1], color=color, s=120, marker='s', alpha=0.9)120 121 # 그래프 설정122 ax.set_xlim(-0.7, num_players - 0.3)123 ax.set_ylim(-0.8, ladder_height + 0.8)124 ax.set_title("🎮 Ladder Game Results", fontsize=16, fontweight='bold', pad=20)125 ax.set_xticks([])126 ax.set_yticks([])127 ax.grid(True, alpha=0.3)128 129 # 범례 추가130 ax.legend(bbox_to_anchor=(1.05, 1), loc='upper left', fontsize=10)131 132 plt.tight_layout()133 134 return fig, final_results, results135 136 except Exception as e:137 st.error(f"오류 발생: {str(e)}")138 return None, {}, []139 140# 메인 앱 UI141def main():142 st.title("🎮 Interactive Ladder Game")143 st.markdown("### 참가자를 선택하고 결과를 입력한 후 사다리 게임을 즐겨보세요! 🎯")144 145 # 세션 상태 초기화146 if 'results_list' not in st.session_state:147 st.session_state.results_list = ["Winner", "Loser", "Coffee", "Cleaning"]148 149 if 'input_counter' not in st.session_state:150 st.session_state.input_counter = 0151 152 # 메인 레이아웃153 col1, col2 = st.columns([1, 2])154 155 with col1:156 st.subheader("🎛️ 게임 설정")157 158 # 참가자 수 선택 (화살표로 조정 가능)159 num_players = st.number_input(160 "참가자 수", 161 min_value=2, 162 max_value=10, 163 value=4, 164 step=1,165 help="화살표를 사용해서 참가자 수를 조정하세요"166 )167 168 st.markdown("---")169 170 # 결과 입력 부분 개선171 st.subheader("🎯 결과 입력")172 173 # 현재 결과 개수와 참가자 수 비교 표시 - 추가된 부분174 current_count = len(st.session_state.results_list)175 if current_count < num_players:176 st.info(f"📊 현재 {current_count}개 입력됨 (필요: {num_players}개)")177 elif current_count == num_players:178 st.success(f"✅ 결과 입력 완료! ({current_count}/{num_players}개)")179 else:180 st.warning(f"⚠️ 결과가 너무 많습니다! ({current_count}개, 필요: {num_players}개)")181 182 # 결과 입력창 (엔터키 감지를 위한 form 사용) - 수정된 부분183 with st.form(key=f"add_result_form_{st.session_state.input_counter}", clear_on_submit=True):184 new_result = st.text_input(185 "새 결과 입력 후 Enter 또는 추가 버튼 클릭",186 value="",187 help="결과를 입력하고 Enter를 누르거나 추가 버튼을 클릭하세요",188 placeholder="예: Winner, 1등, 커피 등...",189 disabled=(len(st.session_state.results_list) >= num_players) # 최대 개수 도달시 비활성화190 )191 192 # Submit 버튼 - 수정된 부분193 submitted = st.form_submit_button(194 "➕ 추가", 195 use_container_width=True,196 disabled=(len(st.session_state.results_list) >= num_players) # 최대 개수 도달시 비활성화197 )198 199 if submitted and new_result.strip():200 if len(st.session_state.results_list) >= num_players:201 st.error(f"❌ 더 이상 추가할 수 없습니다! (최대 {num_players}개)")202 else:203 st.session_state.results_list.append(new_result.strip())204 st.session_state.input_counter += 1 # 폼 리셋을 위한 카운터205 st.success(f"'{new_result.strip()}' 추가됨!")206 st.rerun()207 208 # 최대 개수 도달시 안내 메시지 - 추가된 부분209 if len(st.session_state.results_list) >= num_players:210 st.info("💡 참가자 수만큼 결과가 입력되었습니다. 더 추가하려면 참가자 수를 늘리거나 기존 결과를 삭제하세요.")211 212 # 전체 삭제 버튼213 if st.button("🗑️ 전체 삭제", use_container_width=True):214 st.session_state.results_list = []215 st.success("모든 결과가 삭제되었습니다!")216 st.rerun()217 218 # 현재 결과 목록 표시219 st.markdown("**📋 현재 결과 목록:**")220 if st.session_state.results_list:221 for idx, result in enumerate(st.session_state.results_list):222 col_result, col_delete = st.columns([4, 1])223 with col_result:224 st.markdown(f"{idx + 1}. {result}")225 with col_delete:226 if st.button("❌", key=f"delete_{idx}", help=f"{result} 삭제"):227 st.session_state.results_list.pop(idx)228 st.success(f"'{result}' 삭제됨!")229 st.rerun()230 else:231 st.info("결과가 없습니다. 위에 입력해주세요.")232 233 # 부족한 결과 안내 - 추가된 부분234 missing_count = num_players - len(st.session_state.results_list)235 if missing_count > 0:236 st.warning(f"⚠️ {missing_count}개의 결과가 부족합니다. 게임 실행시 빈칸으로 처리됩니다.")237 238 st.markdown("---")239 240 # 게임 실행 및 리셋 버튼241 col_run, col_reset = st.columns(2)242 with col_run:243 run_game = st.button("🎲 사다리 게임 실행!", type="primary", use_container_width=True)244 with col_reset:245 reset_game = st.button("🔄 Mix Again!", use_container_width=True)246 247 st.markdown("---")248 249 # 빠른 예시 버튼들250 st.subheader("🚀 빠른 예시")251 252 col_a, col_b = st.columns(2)253 with col_a:254 if st.button("🏆 승부 게임"):255 st.session_state.results_list = ["Winner", "Loser", "Draw", "Retry"]256 st.rerun()257 258 with col_b:259 if st.button("🥇 순위 게임"):260 st.session_state.results_list = ["1st", "2nd", "3rd", "4th", "5th"]261 st.rerun()262 263 col_c, col_d = st.columns(2)264 with col_c:265 if st.button("☕ 역할 분담"):266 st.session_state.results_list = ["Coffee", "Cleaning", "Rest", "Shopping"]267 st.rerun()268 269 with col_d:270 if st.button("🎁 선물 뽑기"):271 st.session_state.results_list = ["Gift A", "Gift B", "Gift C"]272 st.rerun()273 274 with col2:275 st.subheader("🎨 게임 결과")276 277 # 리셋 처리278 if reset_game:279 st.session_state.show_initial = True280 st.session_state.game_results = None281 st.success("게임이 리셋되었습니다!")282 st.rerun()283 284 # 게임 실행 또는 초기 표시285 if run_game or st.session_state.get('show_initial', True):286 with st.spinner("🎮 사다리 게임 생성 중..."):287 fig, final_results, results_final = create_ladder_game(num_players, st.session_state.results_list)288 289 if fig is not None:290 # 결과를 세션에 저장291 st.session_state.game_results = {292 'fig': fig,293 'final_results': final_results,294 'results_final': results_final295 }296 st.session_state.show_initial = False297 298 # 저장된 게임 결과 표시299 if st.session_state.get('game_results') is not None:300 game_data = st.session_state.game_results301 302 # 그래프와 결과를 나란히 배치303 graph_col, result_col = st.columns([2, 1])304 305 with graph_col:306 # 그래프 표시 (60% 크기)307 st.pyplot(game_data['fig'], use_container_width=True)308 # matplotlib figure를 닫지 않고 재사용309 310 with result_col:311 # 최종 결과 표시 (세로 배치) - 수정된 부분312 st.markdown("### 🎊 최종 결과")313 314 # 결과를 세로로 표시315 for player, result in game_data['final_results'].items():316 display_result = result if result.strip() else "빈칸"317 st.metric(318 label=player,319 value=display_result,320 delta=None321 )322 323 # 상세 결과 (접기/펼치기) - 전체 너비로 - 수정된 부분324 with st.expander("📋 상세 결과 보기"):325 for player, result in game_data['final_results'].items():326 display_result = result if result.strip() else "빈칸"327 st.write(f"**{player}** ➡️ **{display_result}**")328 else:329 st.info("🎲 '사다리 게임 실행!' 버튼을 클릭해서 게임을 시작하세요!")330 331 # 사이드바 정보332 with st.sidebar:333 st.markdown("## 📖 사용법")334 st.markdown("""335 1. **참가자 수**: 화살표로 2-10명 조정336 2. **결과 입력**: 337 - 텍스트 입력 후 Enter 키 또는 "추가" 버튼338 - 참가자 수만큼만 입력 가능339 - 개별 삭제나 전체 삭제 가능340 3. **게임 실행**: 버튼 클릭으로 실행341 4. **Mix Again!**: 다시 한 번 실행합니다. 342 343 ### 💡 팁344 - Enter 키를 누르면 입력창이 자동으로 비워집니다345 - 결과가 참가자 수보다 적으면 빈칸으로 처리됩니다346 - 매번 실행할 때마다 다른 사다리가 생성됩니다347 - 빠른 예시 버튼으로 쉽게 시작하세요!348 """)349 350 st.markdown("---")351 st.markdown("### 🎯 사용 예시")352 st.markdown("""353 **결과 입력 방법:**354 1. "Winner" 입력 → Enter 키 또는 추가 버튼355 2. "Loser" 입력 → Enter 키 또는 추가 버튼356 3. "Coffee" 입력 → Enter 키 또는 추가 버튼357 4. "Cleaning" 입력 → Enter 키 또는 추가 버튼358 359 **참가자 수와 결과 개수가 다른 경우:**360 - 결과가 부족하면: 빈칸으로 처리361 - 결과가 많으면: 더 이상 입력 불가362 """)363 364 st.markdown("---")365 st.markdown("**🎮 Made with Streamlit**")366 367if __name__ == "__main__":368 # 세션 상태 초기화369 if 'show_initial' not in st.session_state:370 st.session_state.show_initial = True371 372 main()