mingming2323's picture
Rename to 러브로그(LoveLog) and clean up unused files
ff932fe
"""하트로그 시나리오 프레임 데이터 — 스테이지 기반 + 다중 캐릭터"""
import random
# ── 15축 정의 ──────────────────────────────────────────────
BEHAVIOR_AXES = [
"cooperation", "leadership", "emotional_depth", "pace", "humor",
"risk", "contact_frequency", "affection", "jealousy", "planning",
]
BIG5_AXES = [
"openness", "conscientiousness", "extraversion", "agreeableness", "neuroticism",
]
ALL_AXES = BEHAVIOR_AXES + BIG5_AXES
# ── 관계 스테이지 ─────────────────────────────────────────
STAGES = {
1: {"name": "첫만남", "desc": "설렘, 첫인상, 어색함", "axes": ["pace", "extraversion", "openness"]},
2: {"name": "데이트", "desc": "알아가기, 관심 표현, 거리 좁히기", "axes": ["humor", "cooperation", "affection"]},
3: {"name": "썸", "desc": "감정 확인, 밀당", "axes": ["emotional_depth", "affection", "contact_frequency"]},
4: {"name": "관계 정의", "desc": "고백/확인 또는 정리", "axes": ["risk", "pace", "planning"]},
5: {"name": "갈등", "desc": "현실적 충돌, 의견 차이", "axes": ["cooperation", "agreeableness", "neuroticism"]},
6: {"name": "결말", "desc": "사귐, 친구, 고백 거절, 또는 거절", "axes": ["planning", "conscientiousness", "emotional_depth"]},
}
# ── warmth 분기 임계값 ────────────────────────────────────
WARMTH_THRESHOLDS = {
"stage3_cold": 3, # 이 미만이면 멀어지는 방향
"confession_mutual": 7, # 이상이면 상대도 고백 → 사귄다
"confession_accepted": 4, # 이상이면 고백 수락 → 사귄다
"confession_soft_reject": 2, # 이상이면 거절 → 친구로 남는다
# > 0: 거절당함
# <= 0: 고백 불가 (선택지에 안 뜸)
"interruption": 2, # 이 미만이면 난입 확정
"early_ending": 0, # 모든 캐릭터가 이 이하이면 조기 결말
}
MAX_INTERRUPTIONS = 2 # 난입 최대 횟수
INTERRUPT_STAGES = {3, 4} # 난입 발생 가능 스테이지
# ── 첫만남 유형 ───────────────────────────────────────────
MEETING_TYPES = [
{"type": "소개팅", "desc": "친구가 소개해준 사람. 카페에서 처음 만남."},
{"type": "친구에서 발전", "desc": "같은 동호회/회사 사람. 이미 아는 사이에서 미묘해짐."},
{"type": "우연한 만남", "desc": "동네 편의점/카페에서 자주 마주치다 말 걸게 됨."},
]
# ── 캐릭터 템플릿 풀 ──────────────────────────────────────
CHAR_POOL = {
"M": [
{"name": "김서진", "age": 27, "job": "IT회사 백엔드 개발자", "personality": "조용하지만 할 말은 하는 편. 리액션이 늦지만 한번 웃으면 오래 웃음.", "speech_style": "...어 그건 좀, 아 근데 / 단답 많지만 가끔 길게"},
{"name": "이준호", "age": 29, "job": "광고대행사 기획자", "personality": "사교적이고 분위기 메이커. 약간 오버하는 경향.", "speech_style": "아 진짜? 대박 / 텐션 높고 리액션 큼"},
{"name": "박도현", "age": 26, "job": "스타트업 디자이너", "personality": "감성적이고 세심. 상대 기분에 민감.", "speech_style": "음... 괜찮아? / 조심스럽고 배려하는 톤"},
{"name": "최현우", "age": 28, "job": "대기업 영업팀", "personality": "자신감 있고 직설적. 결정이 빠름.", "speech_style": "내가 할게 / 짧고 확실한 톤"},
{"name": "정민재", "age": 25, "job": "대학원생 (경영학)", "personality": "분석적이고 신중. 계획을 좋아함.", "speech_style": "그거 한번 생각해보자 / 논리적이지만 딱딱하진 않음"},
],
"F": [
{"name": "이수빈", "age": 26, "job": "출판사 편집자", "personality": "차분하고 관찰력 좋음. 말보다 표정으로 드러남.", "speech_style": "아 그래? / 짧게 대답하지만 눈 맞춤 많음"},
{"name": "한소희", "age": 28, "job": "마케팅 대리", "personality": "활발하고 주도적. 약속 잡는 걸 좋아함.", "speech_style": "야 우리 이거 하자! / 에너지 넘치는 톤"},
{"name": "김하은", "age": 25, "job": "카페 바리스타 겸 일러스트레이터", "personality": "몽글몽글하고 감성적. 사소한 것에 감동.", "speech_style": "헐 이거 진짜 예쁘다... / 감탄사 많음"},
{"name": "정유진", "age": 27, "job": "금융권 애널리스트", "personality": "똑부러지고 현실적. 비효율 싫어함.", "speech_style": "그건 좀 아닌 거 같은데 / 명확하고 직접적"},
{"name": "박지우", "age": 24, "job": "대학원생 (심리학)", "personality": "공감 능력 높고 호기심 많음. 질문을 많이 함.", "speech_style": "그때 어떤 기분이었어? / 탐구하는 톤"},
],
}
def pick_characters(partner_gender: str, count: int = 3, seed: int | None = None) -> list[dict]:
"""상대 성별 풀에서 count명 랜덤 선택 + 첫만남 유형 배정."""
rng = random.Random(seed)
pool = CHAR_POOL.get(partner_gender, CHAR_POOL["F"])
chars = rng.sample(pool, min(count, len(pool)))
meetings = list(MEETING_TYPES)
rng.shuffle(meetings)
result = []
for i, char in enumerate(chars):
result.append({
**char,
"id": i,
"meeting_type": meetings[i]["type"],
"meeting_desc": meetings[i]["desc"],
"warmth": 0,
"active": True,
"event_count": 0,
})
return result
def get_warmth_variant(stage: int, warmth: float) -> str:
"""warmth 기반으로 스테이지 variant 결정."""
t = WARMTH_THRESHOLDS
if stage == 3:
return "warm" if warmth >= t["stage3_cold"] else "cold"
return "normal"
def get_confession_outcome(warmth: float) -> str:
"""고백 시 warmth 기반 결과 결정."""
t = WARMTH_THRESHOLDS
if warmth >= t["confession_mutual"]:
return "mutual"
if warmth >= t["confession_accepted"]:
return "accepted"
if warmth >= t["confession_soft_reject"]:
return "soft_reject"
return "rejected"
def should_interrupt(warmth: float) -> bool:
"""난입 이벤트 발생 조건. 임계값 미만이면 확정, 이상이면 50%."""
if warmth < WARMTH_THRESHOLDS["interruption"]:
return True
return random.random() < 0.5
# ── 감정 태그 ──────────────────────────────────────────────
EMOTION_TAGS = {
"calm": {"emoji": "😶", "label": "담담함"},
"anxious": {"emoji": "😰", "label": "불안"},
"upset": {"emoji": "😠", "label": "서운함"},
"excited": {"emoji": "😍", "label": "설렘"},
"confused": {"emoji": "🤔", "label": "혼란"},
}
# ── 연애 유형 카드 ─────────────────────────────────────────
DATING_TYPES = {
"flame": {
"emoji": "🔥",
"name": "불꽃형",
"title": "불꽃형 몰입 러버",
"desc": "감정은 빠르게, 선택은 직진형. 외향성과 모험성이 높고 감정 표현이 빠른 타입.",
"weights": {"risk": 2.0, "pace": 2.0, "extraversion": 1.5, "jealousy": 1.0, "affection": 1.0},
},
"stable": {
"emoji": "🌿",
"name": "안정형",
"title": "안정형 신뢰 파트너",
"desc": "계획적이고 협력적. 관계의 안정성을 가장 중시하는 타입.",
"weights": {"cooperation": 2.0, "planning": 2.0, "agreeableness": 1.5, "conscientiousness": 1.5},
},
"emotional": {
"emoji": "🌊",
"name": "감정형",
"title": "감정형 깊은 교감러",
"desc": "감정의 깊이가 남다른 타입. 공감 능력이 높고 관계에 진심으로 몰입한다.",
"weights": {"emotional_depth": 2.5, "neuroticism": 1.0, "openness": 1.0, "affection": 1.0},
},
"strategic": {
"emoji": "🧠",
"name": "전략형",
"title": "전략형 관계 설계자",
"desc": "관계도 전략적으로. 리더십이 있고 계획적으로 관계를 이끄는 타입.",
"weights": {"leadership": 2.0, "planning": 2.0, "conscientiousness": 1.5},
},
"free": {
"emoji": "🫧",
"name": "자유형",
"title": "자유형 독립 연애러",
"desc": "나만의 속도로. 독립적이고 개방적이며 유연한 연애를 추구하는 타입.",
"weights": {"risk": 1.5, "openness": 2.0, "jealousy": -2.0},
},
}