# character_creation.py 시작 부분 (임포트 부분) import streamlit as st import random import time from typing import Dict, List, Any, Tuple, Optional from ..config.constants import ABILITY_NAMES from ..utils.dice_roller import roll_dice, display_dice_animation from ..modules.ai_service import generate_gemini_text from ..modules.character_utils import extract_background_tags, get_stat_info def initialize_character_creation_state(): """캐릭터 생성 관련 상태 초기화""" if 'character_creation_step' not in st.session_state: st.session_state.character_creation_step = 'race' if 'background_options_generated' not in st.session_state: st.session_state.background_options_generated = False if 'dice_rolled' not in st.session_state: st.session_state.dice_rolled = False if 'reroll_used' not in st.session_state: st.session_state.reroll_used = False def display_character_creation_page(): """캐릭터 생성 페이지 전체 표시""" st.header("2️⃣ 캐릭터 생성") # 마스터 메시지 표시 st.markdown(f"
{st.session_state.master_message}
", unsafe_allow_html=True) # 상태 초기화 initialize_character_creation_state() # 현재 단계에 따라 다른 UI 표시 if st.session_state.character_creation_step == 'race': display_race_selection() elif st.session_state.character_creation_step == 'profession': display_profession_selection() elif st.session_state.character_creation_step == 'background': display_background_selection() elif st.session_state.character_creation_step == 'abilities': display_abilities_selection() elif st.session_state.character_creation_step == 'review': display_character_review() def display_background_selection(): """배경 선택 UI""" st.subheader("배경 이야기 선택") # 배경 선택 설명 추가 st.markdown("""

캐릭터의 배경 이야기는 당신이 어떻게 모험가가 되었는지, 어떤 경험을 했는지를 설명합니다.

배경은 캐릭터의 동기와 성격을 형성하며, 게임 내에서의 역할 플레이에 큰 도움이 됩니다.

""", unsafe_allow_html=True) # 선택된 종족 및 직업 정보 표시 (개선된 UI) race_icon = st.session_state.get('race_icon', '👤') profession_icon = st.session_state.get('profession_icon', '👤') race_ability = st.session_state.get('race_ability', "특수 능력 없음") profession_skill = st.session_state.get('profession_skill', "특수 기술 없음") st.markdown(f"""
{race_icon}

{st.session_state.selected_race} {st.session_state.selected_profession}

{st.session_state.selected_race}: {race_ability}
{st.session_state.selected_profession}: {profession_skill}
{profession_icon}
""", unsafe_allow_html=True) # 배경 옵션 생성 if not st.session_state.background_options_generated: with st.spinner("캐릭터 배경 옵션을 생성 중..."): from src.modules.character_utils import generate_character_options st.session_state.character_backgrounds = generate_character_options( st.session_state.selected_profession, st.session_state.theme ) st.session_state.background_options_generated = True # 생성된 배경 옵션 표시 if 'character_backgrounds' in st.session_state and st.session_state.character_backgrounds: # 옵션 숫자 및 탭 생성 tabs = st.tabs([f"옵션 {i+1}" for i in range(len(st.session_state.character_backgrounds))]) for i, (tab, background) in enumerate(zip(tabs, st.session_state.character_backgrounds)): with tab: # 각 배경 스토리 표시 st.markdown(f"""
{background}
""", unsafe_allow_html=True) # 배경 선택 버튼 if st.button(f"이 배경으로 선택", key=f"bg_select_{i}", use_container_width=True): # 선택한 배경 저장 st.session_state.selected_background = background # 배경에서 태그 추출 from src.modules.character_utils import extract_background_tags st.session_state.background_tags = extract_background_tags(background) # 다음 단계로 진행 st.session_state.character_creation_step = 'abilities' st.session_state.master_message = f"흥미로운 배경이네요! 이제 당신의 능력치를 결정해 볼까요?" st.rerun() else: st.error("배경 옵션을 생성하는데 문제가 발생했습니다. 다시 시도해주세요.") # 직접 작성 옵션 추가 st.markdown("
", unsafe_allow_html=True) st.write("### 직접 배경 작성하기") st.write("원하는 배경 스토리를 직접 작성할 수 있습니다.") custom_background = st.text_area("당신의 캐릭터 배경 이야기를 작성하세요:", height=200) if st.button("이 배경으로 선택", key="custom_bg_select", use_container_width=True): if custom_background: # 선택한 배경 저장 st.session_state.selected_background = custom_background # 배경에서 태그 추출 from src.modules.character_utils import extract_background_tags st.session_state.background_tags = extract_background_tags(custom_background) # 다음 단계로 진행 st.session_state.character_creation_step = 'abilities' st.session_state.master_message = f"자신만의 배경 이야기를 만드셨군요! 이제 능력치를 결정해 볼까요?" st.rerun() else: st.warning("배경 이야기를 입력해주세요.") st.markdown("
", unsafe_allow_html=True) def display_race_selection(): """종족 선택 UI""" st.subheader("종족 선택") # 종족 선택 설명 추가 st.markdown("""

캐릭터의 종족은 당신의 모험에 큰 영향을 미칩니다. 각 종족은 고유한 특성과 문화적 배경을 가지고 있습니다.

종족에 따라 특정 능력치에 보너스가 부여될 수 있으며, 스토리텔링에도 영향을 줍니다.

""", unsafe_allow_html=True) # 종족 목록 가져오기 from src.modules.character_utils import generate_races races = generate_races(st.session_state.theme) # 종족별 아이콘 매핑 from src.modules.character_utils import RACE_ICONS, RACE_BONUSES, RACE_ABILITIES, RACE_DESCRIPTIONS # 종족 선택 버튼 표시 (개선된 카드 형식) race_cols = st.columns(3) for i, race in enumerate(races): with race_cols[i % 3]: icon = RACE_ICONS.get(race, '👤') # 기본 아이콘 bonus = RACE_BONUSES.get(race, {'??': '+?'}) # 기본 보너스 ability = RACE_ABILITIES.get(race, '특수 능력 없음') # 기본 특수 능력 # 종족 카드 생성 (개선된 UI) st.markdown(f"""
{icon}

{race}

능력치 보너스:
{"
".join([f"{k}: {v}" for k, v in bonus.items()])}
특수 능력:
{ability}
""", unsafe_allow_html=True) # 종족별 간단한 설명 if race in RACE_DESCRIPTIONS: st.markdown(f"""
{RACE_DESCRIPTIONS[race]}
""", unsafe_allow_html=True) st.markdown("
", unsafe_allow_html=True) if st.button(f"선택", key=f"race_{race}"): st.session_state.selected_race = race st.session_state.race_bonus = bonus st.session_state.race_ability = ability st.session_state.race_icon = icon st.session_state.character_creation_step = 'profession' st.session_state.master_message = f"{race} 종족을 선택하셨군요! 이제 당신의 직업을 선택해보세요." st.rerun() # 직접 입력 옵션 st.markdown("
", unsafe_allow_html=True) st.write("### 다른 종족 직접 입력") st.write("원하는 종족이 목록에 없다면, 직접 입력할 수 있습니다.") custom_race = st.text_input("종족 이름:") custom_icon = st.selectbox("아이콘 선택:", ['👤', '🧙', '🧝', '🧟', '👻', '👽', '🤖', '🦊', '🐲', '🌟']) # 능력치 보너스 선택 (최대 2개) st.write("능력치 보너스 선택 (최대 2개):") bonus_cols = st.columns(3) all_stats = ['STR', 'DEX', 'CON', 'INT', 'WIS', 'CHA'] custom_bonuses = {} for i, stat in enumerate(all_stats): with bonus_cols[i % 3]: bonus_value = st.selectbox(f"{stat} 보너스:", ['+0', '+1', '+2'], key=f"custom_bonus_{stat}") if bonus_value != '+0': custom_bonuses[stat] = bonus_value # 특수 능력 입력 custom_ability = st.text_area("특수 능력 (선택사항):", placeholder="예: 어둠 속에서도 잘 볼 수 있는 능력") if custom_race and st.button("이 종족으로 선택"): st.session_state.selected_race = custom_race st.session_state.race_bonus = custom_bonuses if custom_bonuses else {'없음': '+0'} st.session_state.race_ability = custom_ability if custom_ability else "특수 능력 없음" st.session_state.race_icon = custom_icon st.session_state.character_creation_step = 'profession' st.session_state.master_message = f"{custom_race} 종족을 선택하셨군요! 이제 당신의 직업을 선택해보세요." st.rerun() st.markdown("
", unsafe_allow_html=True) def display_profession_selection(): """직업 선택 UI""" st.subheader("직업 선택") # 직업 선택 설명 추가 st.markdown("""

직업은 캐릭터가 세계에서 수행하는 역할과 전문 기술을 결정합니다.

각 직업마다 중요한 능력치가 다르며, 독특한 기술과 성장 경로를 가집니다.

""", unsafe_allow_html=True) # 선택된 종족 표시 (개선된 UI) race_icon = st.session_state.get('race_icon', '👤') race_bonuses = st.session_state.get('race_bonus', {}) race_ability = st.session_state.get('race_ability', "특수 능력 없음") st.markdown(f"""
{race_icon}

선택한 종족: {st.session_state.selected_race}

능력치 보너스: {', '.join([f"{k} {v}" for k, v in race_bonuses.items()])}
특수 능력: {race_ability}
""", unsafe_allow_html=True) # 직업 선택 방식 profession_method = st.radio( "직업 선택 방식:", ["기본 직업 선택", "직접 직업 만들기"], horizontal=True ) if profession_method == "기본 직업 선택": # 직업 목록 가져오기 from src.modules.character_utils import generate_professions professions = generate_professions(st.session_state.theme) # 직업별 아이콘 및 정보 가져오기 from src.modules.character_utils import ( PROFESSION_ICONS, PROFESSION_STATS, PROFESSION_EQUIPMENT, PROFESSION_SKILLS ) # 직업 선택 버튼 표시 (개선된 카드 형식) profession_cols = st.columns(3) for i, profession in enumerate(professions): with profession_cols[i % 3]: icon = PROFESSION_ICONS.get(profession, '👤') # 기본 아이콘 key_stats = PROFESSION_STATS.get(profession, ['??', '??']) # 주요 능력치 equipment = PROFESSION_EQUIPMENT.get(profession, ['기본 장비']) # 시작 장비 skill = PROFESSION_SKILLS.get(profession, '특수 기술 없음') # 특수 기술 # 직업 카드 생성 (개선된 UI) st.markdown(f"""
{icon}

{profession}

주요 능력치: {' & '.join(key_stats)}
시작 장비:
특수 기술:
{skill}
""", unsafe_allow_html=True) if st.button(f"선택", key=f"prof_{profession}"): st.session_state.selected_profession = profession st.session_state.profession_icon = icon st.session_state.profession_stats = key_stats st.session_state.profession_equipment = equipment st.session_state.profession_skill = skill # 배경 옵션 생성 상태 확인 if not st.session_state.background_options_generated: with st.spinner("캐릭터 배경 옵션을 생성 중..."): st.session_state.character_backgrounds = generate_character_options( profession, st.session_state.theme ) st.session_state.background_options_generated = True st.session_state.character_creation_step = 'background' st.session_state.master_message = f"{profession} 직업을 선택하셨군요! 이제 캐릭터의 배경 이야기를 선택해보세요." st.rerun() else: # 직접 직업 만들기 st.markdown("
", unsafe_allow_html=True) st.write("### 나만의 직업 만들기") st.write("세계관에 맞는 독특한 직업을 직접 만들어보세요") custom_profession = st.text_input("직업 이름:") custom_icon = st.selectbox("아이콘 선택:", ['🧙', '⚔️', '🗡️', '🧪', '📚', '🔮', '🎭', '⚗️', '🛡️', '🚀', '💻', '🧬', '👽', '⚙️', '📡', '📦', '💉', '🔭']) # 주요 능력치 선택 (최대 2개) st.write("주요 능력치 선택 (최대 2개):") stat_cols = st.columns(3) all_stats = ['STR', 'DEX', 'CON', 'INT', 'WIS', 'CHA'] selected_stats = [] for i, stat in enumerate(all_stats): with stat_cols[i % 3]: if st.checkbox(f"{stat}", key=f"custom_prof_stat_{stat}"): selected_stats.append(stat) # 3개 이상 선택 시 경고 if len(selected_stats) > 2: st.warning("주요 능력치는 최대 2개까지만 선택할 수 있습니다. 처음 2개만 적용됩니다.") selected_stats = selected_stats[:2] elif len(selected_stats) == 0: st.info("주요 능력치를 1~2개 선택하세요.") # 시작 장비 입력 st.write("시작 장비 (콤마로 구분):") equipment_input = st.text_area("예: 검, 방패, 물약 3개", height=100) # 특수 기술 입력 special_skill = st.text_input("특수 기술 (예: 숨기: 은신 판정에 +2 보너스):") # 직업 설명 profession_desc = st.text_area("직업 설명:", placeholder="이 직업의 역할, 행동 방식, 세계관에서의 위치 등을 설명해주세요.", height=100) if st.button("이 직업으로 선택", use_container_width=True): if custom_profession and len(selected_stats) > 0 and special_skill: # 사용자 정의 직업 정보 저장 st.session_state.selected_profession = custom_profession st.session_state.profession_icon = custom_icon st.session_state.profession_stats = selected_stats # 장비 파싱 equipment_list = [item.strip() for item in equipment_input.split(',') if item.strip()] if not equipment_list: equipment_list = ["기본 장비"] st.session_state.profession_equipment = equipment_list st.session_state.profession_skill = special_skill st.session_state.profession_description = profession_desc # 배경 옵션 생성 상태 확인 if not st.session_state.background_options_generated: with st.spinner("캐릭터 배경 옵션을 생성 중..."): from src.modules.character_utils import generate_character_options st.session_state.character_backgrounds = generate_character_options( custom_profession, st.session_state.theme ) st.session_state.background_options_generated = True st.session_state.character_creation_step = 'background' st.session_state.master_message = f"{custom_profession} 직업을 선택하셨군요! 이제 캐릭터의 배경 이야기를 선택해보세요." st.rerun() else: st.error("직업 이름, 최소 1개의 주요 능력치, 특수 기술은 필수 입력사항입니다.") st.markdown("
", unsafe_allow_html=True) def display_abilities_selection(): """능력치 설정 UI""" st.subheader("능력치 설정") # 능력치 설정 설명 추가 st.markdown("""

능력치는 캐릭터의 신체적, 정신적 역량을 수치화한 것입니다.

주사위를 굴려 결정하거나, 기본값을 사용할 수 있습니다.

""", unsafe_allow_html=True) # 선택된 종족, 직업, 배경 태그 표시 (개선된 UI) race_icon = st.session_state.get('race_icon', '👤') profession_icon = st.session_state.get('profession_icon', '👤') key_stats = st.session_state.get('profession_stats', ['??', '??']) race_bonuses = st.session_state.get('race_bonus', {}) bg_tags = st.session_state.get('background_tags', ["신비로운"]) # 태그 표시용 HTML 생성 tags_html = "" from modules.character_utils import BACKGROUND_TAGS_COLORS for tag in bg_tags: tag_color = BACKGROUND_TAGS_COLORS.get(tag, "#607D8B") # 기본값은 회색 tags_html += f""" {tag} """ # 캐릭터 요약 표시 st.markdown(f"""
{race_icon}

{st.session_state.selected_race} {st.session_state.selected_profession}

{tags_html}
{profession_icon}
핵심 능력치
{"・".join(key_stats)}
종족 보너스
{"・".join([f"{k} {v}" for k, v in race_bonuses.items()])}
""", unsafe_allow_html=True) ability_col1, ability_col2 = st.columns([3, 1]) with ability_col1: # 능력치 설정 방법 선택 ability_method = st.radio( "능력치 설정 방법:", ["3D6 주사위 굴리기", "기본 능력치 사용"], horizontal=True ) if ability_method == "3D6 주사위 굴리기": ability_roll_section() else: # 기본 능력치 사용 base_abilities_section() with ability_col2: # 능력치 설명 및 정보 표시 st.markdown("""

능력치 정보

STR근력, 물리적 공격력
DEX민첩성, 회피/정확도
CON체력, 생존력
INT지능, 마법/기술 이해력
WIS지혜, 직관/인식력
CHA매력, 설득력/교섭력
""", unsafe_allow_html=True) # 능력치 점수 해석 st.markdown("""

능력치 점수 해석

1-3심각한 약점
4-6약함
7-9평균 이하
10-12평균적
13-15평균 이상
16-17매우 뛰어남
18+전설적 수준
""", unsafe_allow_html=True) # 배경 요약 st.markdown("""

배경 요약

""", unsafe_allow_html=True) # 배경 텍스트에서 중요 부분만 추출 (첫 200자) bg_summary = st.session_state.selected_background[:200] if len(st.session_state.selected_background) > 200: bg_summary += "..." st.markdown(f"{bg_summary}", unsafe_allow_html=True) st.markdown("
", unsafe_allow_html=True) # 뒤로 가기 옵션 if st.button("← 배경 선택으로 돌아가기", use_container_width=True): st.session_state.character_creation_step = 'background' # 주사위 굴리기 관련 상태 초기화 if 'dice_rolled' in st.session_state: del st.session_state.dice_rolled if 'reroll_used' in st.session_state: del st.session_state.reroll_used if 'rolled_abilities' in st.session_state: del st.session_state.rolled_abilities st.session_state.master_message = "배경을 다시 선택해 보세요!" st.rerun() def ability_roll_section(): """주사위 굴리기로 능력치 결정하는 UI 섹션""" # 주사위 굴리기 관련 상태 초기화 if 'dice_rolled' not in st.session_state: st.session_state.dice_rolled = False if 'reroll_used' not in st.session_state: st.session_state.reroll_used = False # 주사위 굴리기 설명 추가 st.markdown("""

능력치는 각각 3D6(6면체 주사위 3개) 방식으로 결정됩니다.

각 능력치는 3~18 사이의 값을 가지며, 평균값은 10-11입니다.

14 이상은 뛰어난 능력, 16 이상은 탁월한 능력입니다.

다시 굴리기는 1번만 가능합니다.

""", unsafe_allow_html=True) # 주사위 굴리기 버튼 if not st.session_state.dice_rolled and st.button("주사위 굴리기", use_container_width=True, key="roll_ability_dice"): st.session_state.dice_rolled = True # 능력치 목록 ability_names = ['STR', 'INT', 'DEX', 'CON', 'WIS', 'CHA'] rolled_abilities = {} # 각 능력치별 주사위 굴리기 결과 애니메이션으로 표시 ability_placeholders = {} for ability in ability_names: ability_placeholders[ability] = st.empty() # 순차적으로 각 능력치 굴리기 for ability in ability_names: with st.spinner(f"{ability} 굴리는 중..."): # 주사위 애니메이션 표시 dice_result = display_dice_animation(ability_placeholders[ability], "3d6", 0.5) rolled_abilities[ability] = dice_result['total'] time.sleep(0.2) # 약간의 딜레이 # 세션에 저장 st.session_state.rolled_abilities = rolled_abilities st.rerun() # 굴린 결과 표시 if st.session_state.dice_rolled and 'rolled_abilities' in st.session_state: st.write("#### 주사위 결과:") cols = st.columns(3) i = 0 # 직업 정보를 미리 가져옴 prof = st.session_state.selected_profession if 'selected_profession' in st.session_state else "" # 직업별 중요 능력치 정보 profession_key_stats = st.session_state.get('profession_stats', []) # 능력치 총점 계산 (나중에 보여주기 위함) total_points = sum(st.session_state.rolled_abilities.values()) # 결과를 정렬하여 먼저 중요 능력치를 표시 sorted_abilities = sorted( st.session_state.rolled_abilities.items(), key=lambda x: (x[0] not in profession_key_stats, profession_key_stats.index(x[0]) if x[0] in profession_key_stats else 999) ) for ability, value in sorted_abilities: with cols[i % 3]: # 직업에 중요한 능력치인지 확인 is_key_stat = ability in profession_key_stats # 색상 및 설명 가져오기 color, description = get_stat_info(ability, value, prof) # 중요 능력치 강조 스타일 highlight = "border: 2px solid gold; background-color: rgba(255, 215, 0, 0.1);" if is_key_stat else "" key_badge = "핵심" if is_key_stat else "" # 능력치 값에 따른 바 그래프 너비 계산 (백분율, 최대 18 기준) bar_width = min(100, (value / 18) * 100) # 개선된 능력치 표시 st.markdown(f"""
{ability}{key_badge} {value}
{description}
""", unsafe_allow_html=True) i += 1 # 능력치 총점 표시 avg_total = 63 # 3D6 6개의 평균 # 총점 평가 (낮음, 평균, 높음) if total_points < avg_total - 5: total_rating = "낮음" total_color = "#F44336" # 빨간색 elif total_points > avg_total + 5: total_rating = "높음" total_color = "#4CAF50" # 녹색 else: total_rating = "평균" total_color = "#FFC107" # 노란색 st.markdown(f"""
능력치 총점:
{total_points} {total_rating}
(평균 63, 70+ 우수, 80+ 탁월)
""", unsafe_allow_html=True) # 버튼 열 생성 col1, col2 = st.columns(2) with col1: if st.button("이 능력치로 진행하기", use_container_width=True, key="use_these_stats"): st.session_state.character['stats'] = st.session_state.rolled_abilities st.session_state.character['profession'] = st.session_state.selected_profession st.session_state.character['race'] = st.session_state.selected_race st.session_state.character['backstory'] = st.session_state.selected_background st.session_state.character_creation_step = 'review' st.session_state.master_message = "좋습니다! 캐릭터가 거의 완성되었습니다. 최종 확인을 해 볼까요?" # 다시 굴리기 관련 상태 초기화 st.session_state.dice_rolled = False st.session_state.reroll_used = False st.rerun() with col2: # 다시 굴리기 버튼 - 한번만 사용 가능하도록 제한 if st.button("다시 굴리기", use_container_width=True, key="reroll_ability_dice", disabled=st.session_state.reroll_used): if not st.session_state.reroll_used: # 다시 굴리기 사용 표시 st.session_state.reroll_used = True # 능력치 목록 ability_names = ['STR', 'INT', 'DEX', 'CON', 'WIS', 'CHA'] rerolled_abilities = {} # 각 능력치별 재굴림 결과 표시 reroll_placeholders = {} for ability in ability_names: reroll_placeholders[ability] = st.empty() # 순차적으로 각 능력치 다시 굴리기 for ability in ability_names: with st.spinner(f"{ability} 다시 굴리는 중..."): # 다시 굴림 애니메이션 표시 dice_result = display_dice_animation(reroll_placeholders[ability], "3d6", 0.5) rerolled_abilities[ability] = dice_result['total'] time.sleep(0.1) # 약간의 딜레이 # 결과 저장 및 상태 업데이트 st.session_state.rolled_abilities = rerolled_abilities st.session_state.reroll_message = "다시 굴리기 기회를 사용했습니다." st.rerun() # 다시 굴리기 사용 여부 표시 if st.session_state.reroll_used: st.info("다시 굴리기 기회를 이미 사용했습니다.") def base_abilities_section(): """기본 능력치 설정 UI 섹션""" st.write("#### 기본 능력치:") base_abilities = {'STR': 10, 'INT': 10, 'DEX': 10, 'CON': 10, 'WIS': 10, 'CHA': 10} # 직업에 따른 추천 능력치 조정 if 'selected_profession' in st.session_state: profession = st.session_state.selected_profession profession_key_stats = st.session_state.get('profession_stats', []) # 주요 능력치에 보너스 부여 for stat in profession_key_stats: if stat in base_abilities: base_abilities[stat] = 14 # 주요 능력치는 14로 설정 # 종족에 따른 능력치 보너스 적용 if 'race_bonus' in st.session_state: for stat, bonus in st.session_state.race_bonus.items(): if stat in base_abilities: # 보너스값에서 '+'를 제거하고 정수로 변환 bonus_value = int(bonus.replace('+', '')) base_abilities[stat] += bonus_value elif stat == "모든 능력치": # 모든 능력치에 보너스 적용 bonus_value = int(bonus.replace('+', '')) for ability in base_abilities: base_abilities[ability] += bonus_value # 결과 표시 (향상된 시각적 표현) cols = st.columns(3) i = 0 # 직업 정보 가져오기 prof = st.session_state.selected_profession if 'selected_profession' in st.session_state else "" key_stats = st.session_state.get('profession_stats', []) # 정렬: 주요 능력치 먼저 sorted_abilities = sorted( base_abilities.items(), key=lambda x: (x[0] not in key_stats, key_stats.index(x[0]) if x[0] in key_stats else 999) ) for ability, value in sorted_abilities: with cols[i % 3]: color, description = get_stat_info(ability, value, prof) is_key_stat = ability in key_stats # 중요 능력치 강조 스타일 highlight = "border: 2px solid gold; background-color: rgba(255, 215, 0, 0.1);" if is_key_stat else "" key_badge = "핵심" if is_key_stat else "" # 종족 보너스 표시 race_bonus_badge = "" for stat, bonus in st.session_state.race_bonus.items(): if stat == ability or stat == "모든 능력치": race_bonus_badge = f"{bonus}" # 개선된 능력치 표시 st.markdown(f"""
{ability}{key_badge}{race_bonus_badge} {value}
{description}
""", unsafe_allow_html=True) i += 1 # 능력치 총점 표시 total_points = sum(base_abilities.values()) avg_total = 60 # 평균 총점 # 총점 평가 (낮음, 평균, 높음) if total_points < avg_total - 5: total_rating = "낮음" total_color = "#F44336" # 빨간색 elif total_points > avg_total + 5: total_rating = "높음" total_color = "#4CAF50" # 녹색 else: total_rating = "평균" total_color = "#FFC107" # 노란색 st.markdown(f"""
능력치 총점: {total_points} {total_rating}
(평균 60-65, 70+ 우수, 80+ 탁월)
""", unsafe_allow_html=True) if st.button("기본 능력치로 진행하기", use_container_width=True): st.session_state.character['stats'] = base_abilities st.session_state.character['profession'] = st.session_state.selected_profession st.session_state.character['race'] = st.session_state.selected_race st.session_state.character['backstory'] = st.session_state.selected_background st.session_state.character_creation_step = 'review' st.session_state.master_message = "좋습니다! 캐릭터가 거의 완성되었습니다. 최종 확인을 해 볼까요?" st.rerun() def display_character_review(): """캐릭터 최종 확인 UI""" st.subheader("캐릭터 최종 확인") # 마지막 설명 추가 st.markdown("""

당신의 캐릭터가 완성되었습니다! 최종 정보를 확인하고 모험을 시작하세요.

능력치, 장비, 특수 능력을 확인하고 필요하다면 수정할 수 있습니다.

""", unsafe_allow_html=True) review_col1, review_col2 = st.columns([2, 1]) with review_col1: # 종족 및 직업 아이콘 가져오기 race_icon = st.session_state.get('race_icon', '👤') profession_icon = st.session_state.get('profession_icon', '👤') bg_tags = st.session_state.get('background_tags', ["신비로운"]) # 태그 표시용 HTML 생성 tags_html = "" from modules.character_utils import BACKGROUND_TAGS_COLORS for tag in bg_tags: tag_color = BACKGROUND_TAGS_COLORS.get(tag, "#607D8B") # 기본값은 회색 tags_html += f""" {tag} """ # 캐릭터 카드 생성 (화려한 디자인) st.markdown(f"""
{race_icon}

{st.session_state.character['race']} {st.session_state.character['profession']}

{tags_html}
{profession_icon}
캐릭터 특성
{st.session_state.get('race_ability', '종족 특성 없음')}
{st.session_state.get('profession_skill', '직업 특성 없음')}
배경 스토리
{st.session_state.character['backstory']}
""", unsafe_allow_html=True) # 인벤토리 표시 (개선된 버전) st.markdown("""

인벤토리

""", unsafe_allow_html=True) # 인벤토리 아이템 정렬 from modules.item_manager import display_inventory_for_review display_inventory_for_review(st.session_state.character['inventory']) st.markdown("
", unsafe_allow_html=True) # 특별한 특성 추가 if 'special_trait' not in st.session_state: # 테마와 배경 태그에 따른 특성 선택 from modules.character_utils import generate_special_trait st.session_state.special_trait = generate_special_trait( st.session_state.theme, st.session_state.get('background_tags', ["신비로운"]) ) # 특수 특성 표시 st.markdown(f"""

특별한 특성

🌟 {st.session_state.special_trait.split(":")[0]}
{":".join(st.session_state.special_trait.split(":")[1:])}
""", unsafe_allow_html=True) with review_col2: # 능력치 표시 st.markdown("""

능력치

""", unsafe_allow_html=True) # 직업 정보 가져오기 prof = st.session_state.character['profession'] key_stats = st.session_state.get('profession_stats', []) # 능력치 값 총합 계산 total_points = sum(st.session_state.character['stats'].values()) # 능력치 설정 for stat, value in st.session_state.character['stats'].items(): # 색상 및 설명 가져오기 color, description = get_stat_info(stat, value, prof) is_key_stat = stat in key_stats # 키 스탯 표시 key_badge = "" if is_key_stat: key_badge = f"핵심" # 바 그래프 너비 계산 (백분율, 최대 18 기준) bar_width = min(100, (value / 18) * 100) # 능력치 바 생성 st.markdown(f"""
{stat} {key_badge}
{value}
{description}
""", unsafe_allow_html=True) # 능력치 총점 표시 avg_total = 60 # 평균 총점 # 총점 평가 (낮음, 평균, 높음) if total_points < avg_total - 5: total_rating = "낮음" total_color = "#F44336" # 빨간색 elif total_points > avg_total + 5: total_rating = "높음" total_color = "#4CAF50" # 녹색 else: total_rating = "평균" total_color = "#FFC107" # 노란색 st.markdown(f"""
능력치 총점: {total_points} {total_rating}
""", unsafe_allow_html=True) st.markdown("
", unsafe_allow_html=True) # 시작 위치 정보 st.markdown(f"""

시작 위치

{st.session_state.current_location}
""", unsafe_allow_html=True) # 캐릭터 플레이 팁 st.markdown(f"""

플레이 팁

""", unsafe_allow_html=True) # 최종 선택 버튼 col1, col2 = st.columns(2) with col1: if st.button("이 캐릭터로 게임 시작", use_container_width=True): # 특별한 특성 저장 if 'special_trait' in st.session_state: st.session_state.character['special_trait'] = st.session_state.special_trait # 게임 시작 준비 with st.spinner("게임을 준비하는 중..."): # 시작 메시지 생성 from modules.ai_service import generate_game_intro intro = generate_game_intro( st.session_state.world_description, st.session_state.character, st.session_state.current_location ) st.session_state.story_log.append(intro) # 행동 제안 생성 상태 설정 st.session_state.suggestions_generated = False # 게임 시작 st.session_state.stage = 'game_play' st.session_state.master_message = f"모험이 시작되었습니다! {st.session_state.character['race']} {st.session_state.character['profession']}으로서의 여정이 펼쳐집니다." # 행동 단계 초기화 st.session_state.action_phase = 'suggestions' st.rerun() with col2: if st.button("처음부터 다시 만들기", use_container_width=True): # 캐릭터 생성 단계 초기화 st.session_state.character_creation_step = 'race' st.session_state.background_options_generated = False # 임시 데이터 삭제 for key in ['selected_race', 'selected_profession', 'character_backgrounds', 'selected_background', 'rolled_abilities', 'special_trait', 'race_bonus', 'race_ability', 'race_icon', 'profession_icon', 'profession_stats', 'profession_equipment', 'profession_skill', 'background_tags', 'dice_rolled', 'reroll_used']: if key in st.session_state: del st.session_state[key] # 캐릭터 정보 초기화 from modules.item_manager import initialize_inventory st.session_state.character = { 'profession': '', 'stats': {'STR': 0, 'INT': 0, 'DEX': 0, 'CON': 0, 'WIS': 0, 'CHA': 0}, 'backstory': '', 'inventory': initialize_inventory(st.session_state.theme) } st.session_state.master_message = "다시 시작해봅시다! 어떤 종족을 선택하시겠어요?" st.rerun()