Spaces:
Running
Running
File size: 28,378 Bytes
b7d75f3 c62158d b7d75f3 66414a3 b7d75f3 66414a3 b7d75f3 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 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 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 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 |
"""
캐릭터 생성 및 관리를 위한 유틸리티 모듈
"""
import random
import streamlit as st
import re
from modules.ai_service import generate_gemini_text
from modules.item_manager import initialize_inventory
from config.constants import PROFESSION_KEY_STATS, BACKGROUND_TAGS, ABILITY_NAMES
# 직업별 아이콘 맵핑
PROFESSION_ICONS = {
'마법사': '🧙',
'전사': '⚔️',
'도적': '🗡️',
'성직자': '📿',
'음유시인': '🎭',
'연금술사': '⚗️',
'우주 파일럿': '🚀',
'사이버 해커': '💻',
'생체공학자': '🧬',
'보안 요원': '🛡️',
'외계종족 전문가': '👽',
'기계공학자': '⚙️',
'정보 브로커': '📡',
'밀수업자': '📦',
'저항군 요원': '🕵️',
'엘리트 경비원': '💂',
'스카운터': '🔭',
'의료 기술자': '💉'
}
# 종족별 아이콘 맵핑
RACE_ICONS = {
'인간': '👤',
'엘프': '🧝',
'드워프': '🧔',
'하플링': '🧒',
'오크': '👹',
'고블린': '👺',
'드라코니안': '🐲',
'안드로이드': '🤖',
'외계인 하이브리드': '👽',
'변형 인류': '🧟',
'네뷸런': '👾',
'크로노스피어': '⏱️',
'우주 유목민': '🌠',
'변이체': '🧬',
'강화인류': '🦾',
'생체기계': '🦿',
'숙주': '🕸️',
'정신감응자': '🔮',
'저항자': '✊'
}
# 종족별 능력치 보너스
RACE_BONUSES = {
'인간': {'모든 능력치': '+1'},
'엘프': {'DEX': '+2', 'INT': '+1'},
'드워프': {'CON': '+2', 'STR': '+1'},
'하플링': {'DEX': '+2', 'CHA': '+1'},
'오크': {'STR': '+2', 'CON': '+1'},
'고블린': {'DEX': '+2', 'INT': '+1'},
'드라코니안': {'STR': '+2', 'CHA': '+1'},
'안드로이드': {'INT': '+2', 'STR': '+1'},
'외계인 하이브리드': {'WIS': '+2', 'CHA': '+1'},
'변형 인류': {'DEX': '+2', 'CON': '+1'},
'네뷸런': {'INT': '+2', 'WIS': '+1'},
'크로노스피어': {'INT': '+2', 'DEX': '+1'},
'우주 유목민': {'CON': '+2', 'WIS': '+1'},
'변이체': {'CON': '+2', 'STR': '+1'},
'강화인류': {'STR': '+2', 'DEX': '+1'},
'생체기계': {'STR': '+1', 'INT': '+2'},
'숙주': {'CON': '+2', 'WIS': '+1'},
'정신감응자': {'WIS': '+2', 'CHA': '+1'},
'저항자': {'DEX': '+1', 'CON': '+2'}
}
# 종족별 특수 능력
RACE_ABILITIES = {
'인간': '적응력: 하루에 한 번 주사위를 다시 굴릴 수 있습니다.',
'엘프': '암시야: 어두운 곳에서도 잘 볼 수 있습니다.',
'드워프': '내구력: 독성에 대한 저항력이 있습니다.',
'하플링': '행운: 주사위 결과가 1이 나오면 다시 굴릴 수 있습니다.',
'오크': '끈질김: 체력이 0이 되어도 1턴 더 활동할 수 있습니다.',
'고블린': '약삭빠름: 은신 판정에 +2 보너스를 받습니다.',
'드라코니안': '용의 숨결: 불을 내뿜을 수 있습니다.',
'안드로이드': '계산 능력: 수학적 판정에 +3 보너스를 받습니다.',
'외계인 하이브리드': '텔레파시: 타인의 마음을 읽을 수 있습니다.',
'변형 인류': '적응력: 유해한 환경에 저항할 수 있습니다.',
'네뷸런': '에너지 흡수: 에너지 공격을 흡수할 수 있습니다.',
'크로노스피어': '시간 감각: 시간 흐름을 느리게 할 수 있습니다.',
'우주 유목민': '우주 생존: 진공에서도 짧은 시간 살 수 있습니다.',
'변이체': '재생 능력: 턴마다 체력 1점을 회복합니다.',
'강화인류': '싸이버 강화: 특정 행동에 보너스를 받습니다.',
'생체기계': '에너지 저장: 에너지를 저장하고 방출할 수 있습니다.',
'숙주': '공생: 기생체와 함께 강화된 능력을 사용할 수 있습니다.',
'정신감응자': '원격 감지: 근처의 생명체를 감지할 수 있습니다.',
'저항자': '반역: 정신 지배에 대한 저항력이 있습니다.'
}
# 종족별 설명
RACE_DESCRIPTIONS = {
'인간': '적응력과 다재다능함으로 모든 환경에서 성공할 수 있습니다.',
'엘프': '우아하고 장수하는 종족으로 예술과 마법에 뛰어납니다.',
'드워프': '강인하고 끈질긴 종족으로 대장장이 기술과 광산 작업에 능숙합니다.',
'하플링': '작지만 용감한 종족으로 행운과 민첩성이 뛰어납니다.',
'오크': '강력하고 야만적인 종족으로 전투에 뛰어납니다.',
'고블린': '교활하고 약삭빠른 종족으로 생존 능력이 뛰어납니다.',
'드라코니안': '드래곤의 피를 물려받은 종족으로 원소 저항력이 있습니다.',
'안드로이드': '인공 지능을 가진 기계 생명체로 논리적 사고에 뛰어납니다.',
'외계인 하이브리드': '인간과 외계종의 혼혈로 독특한 능력을 가집니다.',
'변형 인류': '유전자 조작을 통해 진화한 인류로 특수 능력을 가집니다.',
'네뷸런': '에너지 기반 생명체로 물리적 형태를 변형할 수 있습니다.',
'크로노스피어': '시간의 흐름을 조작할 수 있는 능력을 가진 존재입니다.',
'우주 유목민': '우주 공간에서 세대를 거쳐 살아온 인류의 변종입니다.',
'변이체': '방사능이나 화학물질에 의해 변이된 인간입니다.',
'강화인류': '기계 장치를 통해 강화된 인간입니다.',
'생체기계': '생물학적 요소와 기계가 융합된 존재입니다.',
'숙주': '외계 생물체와 공생 관계를 맺은 인간입니다.',
'정신감응자': '정신적 능력이 발달한 인간의 진화 형태입니다.',
'저항자': '억압에 저항하며 생존한 강인한 인류입니다.'
}
# 직업별 주요 능력치
PROFESSION_STATS = {
'마법사': ['INT', 'WIS'],
'전사': ['STR', 'CON'],
'도적': ['DEX', 'CHA'],
'성직자': ['WIS', 'CHA'],
'음유시인': ['CHA', 'DEX'],
'연금술사': ['INT', 'DEX'],
'우주 파일럿': ['DEX', 'INT'],
'사이버 해커': ['INT', 'DEX'],
'생체공학자': ['INT', 'WIS'],
'보안 요원': ['STR', 'CON'],
'외계종족 전문가': ['WIS', 'CHA'],
'기계공학자': ['INT', 'DEX'],
'정보 브로커': ['INT', 'CHA'],
'밀수업자': ['DEX', 'CHA'],
'저항군 요원': ['DEX', 'CON'],
'엘리트 경비원': ['STR', 'DEX'],
'스카운터': ['DEX', 'WIS'],
'의료 기술자': ['INT', 'WIS']
}
# 직업별 시작 장비
PROFESSION_EQUIPMENT = {
'마법사': ['마법서', '지팡이', '로브', '마법 재료 파우치', '양초 3개'],
'전사': ['장검', '방패', '체인 메일', '배낭', '모험가 키트'],
'도적': ['단검 2개', '가죽 갑옷', '도둑 도구 세트', '후드 망토', '물약 2개'],
'성직자': ['메이스', '신성한 상징', '갑옷', '치유 키트', '기도문'],
'음유시인': ['류트', '가죽 갑옷', '단검', '여행 의상 세트', '매력 도구'],
'연금술사': ['연금술 키트', '로브', '약초 주머니', '실험 노트', '물약 3개'],
'우주 파일럿': ['레이저 건', '우주복', '통신 장치', '내비게이션 도구', '응급 키트'],
'사이버 해커': ['휴대용 컴퓨터', '임플란트 도구', '전자 장비 세트', '스텔스 장비', '데이터 칩'],
'생체공학자': ['의료 키트', '연구 도구', '생체 샘플 세트', '데이터 패드', '실험 장비'],
'보안 요원': ['에너지 소총', '방탄 조끼', '보안 키트', '통신 장치', '감시 장비'],
'외계종족 전문가': ['번역기', '외계 유물', '연구 노트', '통신 장치', '생명 지원 시스템'],
'기계공학자': ['공구 세트', '청사진', '부품 키트', '용접 장비', '분석 장치'],
'정보 브로커': ['암호화된 데이터 패드', '은밀한 통신 장치', '위장 도구', '정보 칩', '비상금'],
'밀수업자': ['블래스터 권총', '숨겨진 주머니 의류', '잠금해제 도구', '위조 신분증', '비상 탈출 키트'],
'저항군 요원': ['숨겨진 무기', '암호화 통신 장치', '위장 키트', '임시 폭발물', '생존 장비'],
'엘리트 경비원': ['충격봉', '방탄 유니폼', '감시 장치', '통신 이어피스', '신원 확인 장치'],
'스카운터': ['망원경', '생존 키트', '지도 제작 도구', '위치 추적기', '휴대용 쉘터'],
'의료 기술자': ['고급 의료 키트', '진단 스캐너', '응급 치료 약품', '수술 도구', '생체 모니터']
}
# 직업별 특수 기술
PROFESSION_SKILLS = {
'마법사': '마법 시전: 다양한 마법 주문을 시전할 수 있으며, 스크롤에서 주문을 배우는 능력이 있습니다.',
'전사': '전투 전문가: 모든 무기와 갑옷을 능숙하게 다루며, 전투 중 특별한 기동을 사용할 수 있습니다.',
'도적': '교묘한 행동: 민첩성을 활용한 은밀한 공격과 함정 해제, 자물쇠 따기에 능숙합니다.',
'성직자': '신성한 힘: 신성한 마법을 사용하여 치유하고 보호하며, 언데드를 물리칠 수 있습니다.',
'음유시인': '바드의 영감: 음악을 통해 동료를 격려하고 적을 혼란시키는 마법적 효과를 만들어냅니다.',
'연금술사': '물약 제조: 다양한 효과의 물약과 폭탄을 제조할 수 있는 지식이 있습니다.',
'우주 파일럿': '우주선 조종: 어떤 종류의 우주선이든 능숙하게 조종하고 위험한 상황에서 탈출할 수 있습니다.',
'사이버 해커': '시스템 침입: 컴퓨터 시스템과 보안 네트워크를 침투하고 조작할 수 있습니다.',
'생체공학자': '생체 분석: 생명체의 생물학적 특성을 분석하고 수정할 수 있는 지식이 있습니다.',
'보안 요원': '경계 태세: 항상 위험에 대비하여 경계 판정에 +2 보너스를 받습니다.',
'외계종족 전문가': '외계어 이해: 다양한 외계 언어를 해석하고 의사소통할 수 있습니다.',
'기계공학자': '장치 수리: 복잡한 기계와 장치를 수리하고 개선할 수 있는 지식이 있습니다.',
'정보 브로커': '정보망: 유용한 정보를 찾거나 거래하는데 탁월한 능력이 있습니다.',
'밀수업자': '은밀한 운송: 물건을 숨기고 감시를 피해 운반하는 기술이 있습니다.',
'저항군 요원': '게릴라 전술: 열세한 상황에서도 효과적으로 전투하고 은신할 수 있습니다.',
'엘리트 경비원': '경계 태세: 보안 시스템을 이해하고 침입자를 감지하는 능력이 뛰어납니다.',
'스카운터': '지형 탐색: 위험한 지형을 안전하게 탐색하고 자원을 찾아낼 수 있습니다.',
'의료 기술자': '응급 처치: 위급한 상황에서 부상을 치료하고 생명을 구할 수 있습니다.'
}
# 배경 태그 색상
BACKGROUND_TAGS_COLORS = {
"영웅적": "#4CAF50", # 녹색
"비극적": "#F44336", # 빨간색
"신비로운": "#9C27B0", # 보라색
"학자": "#2196F3", # 파란색
"범죄자": "#FF9800", # 주황색
"전사": "#795548", # 갈색
"귀족": "#FFC107", # 노란색
"서민": "#607D8B", # 회색
"이방인": "#009688", # 청록색
"운명적": "#E91E63" # 분홍색
}
def generate_professions(theme):
"""
테마에 따른 직업 목록 반환
Args:
theme (str): 세계관 테마
Returns:
list: 직업 목록
"""
professions = {
'fantasy': ['마법사', '전사', '도적', '성직자', '음유시인', '연금술사'],
'sci-fi': ['우주 파일럿', '사이버 해커', '생체공학자', '보안 요원', '외계종족 전문가', '기계공학자'],
'dystopia': ['정보 브로커', '밀수업자', '저항군 요원', '엘리트 경비원', '스카운터', '의료 기술자']
}
return professions.get(theme, ['모험가', '전문가', '기술자'])
def generate_races(theme):
"""
테마에 따른 종족 목록 반환
Args:
theme (str): 세계관 테마
Returns:
list: 종족 목록
"""
races = {
'fantasy': ['인간', '엘프', '드워프', '하플링', '오크', '고블린', '드라코니안'],
'sci-fi': ['인간', '안드로이드', '외계인 하이브리드', '변형 인류', '네뷸런', '크로노스피어', '우주 유목민'],
'dystopia': ['인간', '변이체', '강화인류', '생체기계', '숙주', '정신감응자', '저항자']
}
return races.get(theme, ['인간', '비인간', '신비종족'])
def generate_character_options(profession, theme):
"""
직업과 테마에 기반한 캐릭터 배경 옵션 생성
Args:
profession (str): 선택한 직업
theme (str): 세계관 테마
Returns:
list: 배경 스토리 옵션 목록
"""
prompt = f"""
당신은 TRPG 게임 마스터입니다. '{theme}' 테마의 세계에서 '{profession}' 직업을 가진
캐릭터의 3가지 다른 배경 스토리 옵션을 한국어로 제안해주세요.
각 옵션은 다음 요소를 포함해야 합니다:
## 삼위일체 구조
1. **배경 서사**: 캐릭터가 겪은 결정적 사건 3개
2. **도덕적 축**: 선택을 규정하는 2가지 원칙
3. **정체성 기반**: 타인에게 설명하는 5초 자기소개
## 개성화를 위한 요소
- 캐릭터만의 독특한 특성이나 버릇
- 관계망 (가족, 멘토, 적대자 등)
- 물리적 특징이나 외형적 특성
## 직업 연계성
- 이 캐릭터가 해당 직업을 가지게 된 이유
- 직업 관련 전문 기술이나 지식
각 옵션을 120단어 내외로 작성해주세요.
모든 문장은 완결된 형태로 작성하세요.
다음 형식으로 반환해주세요:
#옵션 1:
(첫 번째 배경 스토리)
#옵션 2:
(두 번째 배경 스토리)
#옵션 3:
(세 번째 배경 스토리)
"""
from src.modules.ai_service import generate_gemini_text
response = generate_gemini_text(prompt, 800)
# 옵션 분리
options = []
current_option = ""
for line in response.split('\n'):
if line.startswith('#옵션') or line.startswith('# 옵션') or line.startswith('옵션'):
if current_option:
options.append(current_option.strip())
current_option = ""
else:
current_option += line + "\n"
if current_option:
options.append(current_option.strip())
# 옵션이 3개 미만이면 백업 옵션 추가
while len(options) < 3:
options.append(f"당신은 {profession}으로, 험난한 세계에서 살아남기 위해 기술을 연마했습니다. 특별한 재능을 가지고 있으며, 자신의 운명을 개척하고자 합니다.")
return options[:3] # 최대 3개까지만 반환
def extract_background_tags(background_text):
"""
배경 텍스트에서 태그를 추출하는 함수
Args:
background_text (str): 배경 스토리 텍스트
Returns:
list: 추출된 태그 목록
"""
tags = []
keyword_map = {
"영웅": "영웅적", "구원": "영웅적", "정의": "영웅적",
"비극": "비극적", "상실": "비극적", "슬픔": "비극적", "고통": "비극적",
"신비": "신비로운", "마법": "신비로운", "초자연": "신비로운",
"학자": "학자", "연구": "학자", "지식": "학자", "서적": "학자",
"범죄": "범죄자", "도둑": "범죄자", "불법": "범죄자", "암흑가": "범죄자",
"전사": "전사", "전투": "전사", "군인": "전사", "검술": "전사",
"귀족": "귀족", "왕족": "귀족", "부유": "귀족", "상류층": "귀족",
"서민": "서민", "평민": "서민", "일반인": "서민", "농부": "서민",
"이방인": "이방인", "외지인": "이방인", "여행자": "이방인", "이주민": "이방인",
"운명": "운명적", "예언": "운명적", "선택받은": "운명적"
}
for keyword, tag in keyword_map.items():
if keyword.lower() in background_text.lower() and tag not in tags:
tags.append(tag)
# 최대 3개 태그 제한
return tags[:3] if tags else ["신비로운"] # 기본 태그 추가
def extract_background_tags(background_text):
"""
배경 텍스트에서 태그를 추출하는 함수
Args:
background_text (str): 배경 스토리 텍스트
Returns:
list: 추출된 태그 목록
"""
tags = []
keyword_map = {
"영웅": "영웅적", "구원": "영웅적", "정의": "영웅적",
"비극": "비극적", "상실": "비극적", "슬픔": "비극적", "고통": "비극적",
"신비": "신비로운", "마법": "신비로운", "초자연": "신비로운",
"학자": "학자", "연구": "학자", "지식": "학자", "서적": "학자",
"범죄": "범죄자", "도둑": "범죄자", "불법": "범죄자", "암흑가": "범죄자",
"전사": "전사", "전투": "전사", "군인": "전사", "검술": "전사",
"귀족": "귀족", "왕족": "귀족", "부유": "귀족", "상류층": "귀족",
"서민": "서민", "평민": "서민", "일반인": "서민", "농부": "서민",
"이방인": "이방인", "외지인": "이방인", "여행자": "이방인", "이주민": "이방인",
"운명": "운명적", "예언": "운명적", "선택받은": "운명적"
}
for keyword, tag in keyword_map.items():
if keyword.lower() in background_text.lower() and tag not in tags:
tags.append(tag)
# 최대 3개 태그 제한
return tags[:3] if tags else ["신비로운"] # 기본 태그 추가
def get_stat_info(stat, value, profession):
"""
스탯별 색상 및 설명 제공
Args:
stat (str): 능력치 코드
value (int): 능력치 값
profession (str): 직업
Returns:
tuple: (색상 코드, 설명 텍스트)
"""
# 스탯별 색상 설정 (낮음 - 중간 - 높음)
if value < 8:
color = "#F44336" # 빨강 (낮음)
level = "낮음"
elif value < 12:
color = "#FFC107" # 노랑 (보통)
level = "보통"
elif value < 16:
color = "#4CAF50" # 초록 (높음)
level = "높음"
else:
color = "#3F51B5" # 파랑 (매우 높음)
level = "매우 높음"
# 직업별 스탯 적합성 설명
if profession in PROFESSION_KEY_STATS and stat in PROFESSION_KEY_STATS[profession]:
match = "핵심" if PROFESSION_KEY_STATS[profession][0] == stat else "중요"
description = f"{level} - {match} 스탯"
else:
description = f"{level}"
return color, description
def display_character_panel(character, location):
"""
캐릭터 정보를 왼쪽 패널에 표시
Args:
character (dict): 캐릭터 정보
location (str): 현재 위치
"""
from modules.item_manager import display_inventory
st.markdown("<div class='character-panel'>", unsafe_allow_html=True)
st.write(f"## {character['profession']}")
# 능력치 표시
st.write("### 능력치")
for stat, value in character['stats'].items():
# 직업 정보 가져오기
prof = character['profession']
color, description = get_stat_info(stat, value, prof)
st.markdown(f"""
<div class='stat-box' style="border-left: 4px solid {color};">
<span class='stat-name'>{stat}</span>
<span class='stat-value'>{value}</span>
<div style="font-size: 0.8rem; color: #aaaaaa; margin-top: 2px;">{description}</div>
</div>
""", unsafe_allow_html=True)
# 인벤토리 표시
st.write("### 인벤토리")
display_inventory(character['inventory'])
st.markdown("</div>", unsafe_allow_html=True)
# 위치 정보
st.markdown(f"""
<div class='location-box' style='margin-bottom: 15px; padding: 12px; background-color: #2d3748; border-radius: 5px; text-align: center;'>
<h3 style='margin: 0; color: #e0e0ff;'>현재 위치</h3>
<div style='font-size: 1.2rem; font-weight: bold; margin-top: 8px;'>{location}</div>
</div>
""", unsafe_allow_html=True)
def initialize_character(profession, backstory, stats, theme):
"""
캐릭터 초기화 및 인벤토리 설정
Args:
profession (str): 직업
backstory (str): 배경 스토리
stats (dict): 능력치
theme (str): 게임 테마
Returns:
dict: 초기화된 캐릭터 정보
"""
# 아이템 객체 리스트로 인벤토리 초기화
inventory = initialize_inventory(theme)
character = {
'profession': profession,
'backstory': backstory,
'stats': stats,
'inventory': inventory,
'special_trait': None
}
return character
def ability_roll_section(placeholder):
"""
능력치 주사위 굴리기 기능
Args:
placeholder (st.empty): 결과를 표시할 플레이스홀더
"""
# 주사위 굴리기 관련 상태 초기화
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
# 주사위 굴리기 설명 추가
placeholder.markdown("""
<div style='background-color: #2a3549; padding: 10px; border-radius: 5px; margin-bottom: 15px;'>
<p>능력치는 각각 3D6(6면체 주사위 3개) 방식으로 결정됩니다.</p>
<p>각 능력치는 3~18 사이의 값을 가지며, 평균값은 10-11입니다.</p>
<p>14 이상은 뛰어난 능력, 16 이상은 탁월한 능력입니다.</p>
<p><strong>다시 굴리기는 1번만 가능합니다.</strong></p>
</div>
""", unsafe_allow_html=True)
# 주사위 굴리기 버튼
if not st.session_state.dice_rolled and placeholder.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] = placeholder.empty()
# 순차적으로 각 능력치 굴리기
for ability in ability_names:
# 3D6 주사위 결과 계산
dice_rolls = [random.randint(1, 6) for _ in range(3)]
total = sum(dice_rolls)
# 결과 표시
ability_placeholders[ability].markdown(f"""
<div style='background-color: #1e2636; padding: 10px; border-radius: 5px; margin-bottom: 5px;'>
<div style='display: flex; justify-content: space-between;'>
<span><strong>{ability}</strong></span>
<span>🎲 {dice_rolls[0]} + {dice_rolls[1]} + {dice_rolls[2]} = <strong>{total}</strong></span>
</div>
</div>
""", unsafe_allow_html=True)
rolled_abilities[ability] = total
# 세션에 저장
st.session_state.rolled_abilities = rolled_abilities
st.rerun()
def generate_special_trait(theme, background_tags):
"""
캐릭터의 특별한 특성 생성
Args:
theme (str): 게임 테마
background_tags (list): 배경 태그 목록
Returns:
str: 생성된 특별한 특성
"""
# 테마별 특성 목록
fantasy_traits = [
"마법에 대한 직관: 마법 관련 판정에 +1 보너스",
"언어 재능: 하나의 추가 언어를 이해할 수 있음",
"생존 본능: 위험 감지 판정에 +2 보너스",
"전투 감각: 선제력 판정에 +1 보너스",
"비밀 감지: 숨겨진 문이나 함정 찾기에 +2 보너스"
]
scifi_traits = [
"기계 친화력: 장치 조작 판정에 +1 보너스",
"우주 적응: 저중력 환경 적응에 +2 보너스",
"전술적 사고: 전투 전략 판정에 +1 보너스",
"네트워크 감각: 정보 검색에 +2 보너스",
"생체 회복: 휴식 시 추가 체력 회복"
]
dystopia_traits = [
"생존자 본능: 위험한 상황 탈출에 +1 보너스",
"자원 절약: 소비품 사용 효율 +25%",
"야간 시력: 어두운 곳에서 시각 판정에 불이익 없음",
"불굴의 의지: 정신적 충격 저항에 +2 보너스",
"전술적 직감: 교전 시 선제 행동 확률 +15%"
]
# 태그에 따른 특성 선택 확률 조정
has_hero = "영웅적" in background_tags
has_scholarly = "학자" in background_tags
has_tragic = "비극적" in background_tags
has_criminal = "범죄자" in background_tags
has_mysterious = "신비로운" in background_tags
if theme == "fantasy":
traits = fantasy_traits
if has_hero:
traits.append("운명의 보호: 하루에 한 번 치명적 공격을 일반 공격으로 낮출 수 있음")
if has_scholarly:
traits.append("비전학자: 마법 관련 지식 판정에 +2 보너스")
if has_tragic:
traits.append("고통의 힘: 체력이 절반 이하일 때 공격력 +1")
if has_criminal:
traits.append("그림자 걷기: 은신 판정에 +2 보너스")
if has_mysterious:
traits.append("신비한 직감: 하루에 한 번 주사위를 다시 굴릴 수 있음")
elif theme == "sci-fi":
traits = scifi_traits
if has_hero:
traits.append("영웅적 리더십: 아군 NPC 의사 결정에 영향력 +25%")
if has_scholarly:
traits.append("데이터 분석: 기술 장치 판독에 +2 보너스")
if has_tragic:
traits.append("역경의 경험: 위기 상황에서 판단력 +1")
if has_criminal:
traits.append("시스템 침투: 보안 해제 시도에 +2 보너스")
if has_mysterious:
traits.append("양자 직감: 확률적 사건 예측에 +15% 정확도")
else: # dystopia
traits = dystopia_traits
if has_hero:
traits.append("불굴의 영웅: 동료를 보호하는 행동에 +2 보너스")
if has_scholarly:
traits.append("생존 지식: 자원 활용 효율 +20%")
if has_tragic:
traits.append("상실의 분노: 개인적 원한에 관련된 행동에 +2 보너스")
if has_criminal:
traits.append("암시장 연결망: 희귀 물품 거래 시 15% 할인")
if has_mysterious:
traits.append("통제 면역: 정신 조작 시도에 대한 저항 +25%")
# 무작위 특성 선택
return random.choice(traits) |