플레이어 세대 기록 초기화 및 세대 계산 및 사망 기록 등 기능 수정

This commit is contained in:
김민구
2026-07-06 19:29:35 +09:00
parent 5dc1a4a7b6
commit e67dbb8084
4 changed files with 107 additions and 27 deletions
+9
View File
@@ -15,3 +15,12 @@ class AncestorDeath(Base):
created_at: Mapped[datetime] = mapped_column(default=datetime.now, nullable=False)
# 문지기 NPC 대사 캐시 모델
class DialogueCache(Base):
__tablename__ = 'dialogue_caches'
generation: Mapped[int] = mapped_column(primary_key=True)
dialogue: Mapped[str] = mapped_column(String(500), nullable=False)
created_at: Mapped[datetime] = mapped_column(default=datetime.now, nullable=False)
+45 -3
View File
@@ -1,9 +1,10 @@
from fastapi import APIRouter, Depends, Query
from fastapi import APIRouter, Depends, Query, BackgroundTasks
from sqlalchemy.orm import Session
from backend.repository.db_init import get_db
from backend.schemas.game_schema import (
AncestorDeathCreate,
DeathRecordRequest,
NextGenerationResponse,
AncestorDeathResponse,
DialogueResponse,
SkillCreateRequest,
@@ -14,14 +15,55 @@ from backend.services import game_service
router = APIRouter(prefix="/api/game", tags=["Game"])
@router.post("/death", response_model=AncestorDeathResponse)
def record_death(req: DeathRecordRequest, db: Session = Depends(get_db)):
return game_service.create_death_history(db=db, cause_of_death=req.cause_of_death)
def record_death(req: AncestorDeathCreate, db: Session = Depends(get_db)):
death_history = game_service.create_death_history(db=db, death_data=req)
# 다음 세대 대사 즉시 생성 및 캐싱 보장 (동기식 호출)
next_gen = death_history.generation + 1
game_service.pregenerate_npc_dialogue(db=db, next_generation=next_gen)
return death_history
@router.get("/generation/next", response_model=NextGenerationResponse)
def get_next_generation(db: Session = Depends(get_db)):
next_gen = game_service.get_next_generation(db=db)
return NextGenerationResponse(next_generation=next_gen)
@router.get("/npc-dialogue", response_model=DialogueResponse)
def get_npc_dialogue(current_generation: int = Query(..., description="현재 플레이어 세대"), db: Session = Depends(get_db)):
# 1세대일 때는 기본 설정 대사 즉시 리턴 (선조 사망 기록이 존재하지 않으므로)
if current_generation == 1:
initial_dialogue = "이 던전에는 온갖 무시무시한 몬스터들이 가득하다. 풋내기 1세손아, 목숨이 아깝거든 바짝 긴장하고 들어가거라."
return DialogueResponse(dialogue=initial_dialogue)
# 1. 캐시 테이블에서 기생성된 대사 조회
from backend.repository.models import DialogueCache
cached = db.query(DialogueCache).filter(DialogueCache.generation == current_generation).first()
if cached:
print(f"[Cache Hit] {current_generation}대 조언 대사를 캐시에서 즉시 반환합니다.")
return DialogueResponse(dialogue=cached.dialogue)
# 2. 캐시 미스 시 Fallback 실시간 생성
print(f"[Cache Miss] {current_generation}대 조언 대사 실시간 생성을 수행합니다. (지연 발생)")
dialogue_text = game_service.generate_npc_dialogue(db=db, current_generation=current_generation)
return DialogueResponse(dialogue=dialogue_text)
# 데이터베이스 전체 초기화 API (데이터 비우기 + ID 1번 리셋)
from sqlalchemy import text
@router.post("/reset")
def reset_database(db: Session = Depends(get_db)):
try:
db.execute(text("SET FOREIGN_KEY_CHECKS = 0;"))
db.execute(text("TRUNCATE TABLE ancestor_deaths;"))
db.execute(text("TRUNCATE TABLE dialogue_caches;"))
db.execute(text("SET FOREIGN_KEY_CHECKS = 1;"))
db.commit()
return {"status": "success", "message": "데이터베이스 및 ID 자동 증가 값이 초기화되었습니다."}
except Exception as e:
db.rollback()
from fastapi import HTTPException
raise HTTPException(status_code=500, detail=f"데이터베이스 초기화 에러: {e}")
@router.post("/generate-skill", response_model=SkillCreateResponse)
def create_skill(req: SkillCreateRequest):
return game_service.generate_ai_skill(prompt=req.prompt, player_level=req.player_level)
+3
View File
@@ -11,6 +11,9 @@ class AncestorDeathCreate(BaseModel):
class DeathRecordRequest(BaseModel):
cause_of_death: str = Field(..., description="사망 원인 (누구에게 죽었는지)")
class NextGenerationResponse(BaseModel):
next_generation: int = Field(..., description="다음 플레이어 세대 번호")
class AncestorDeathResponse(BaseModel):
id: int
name: str
+50 -24
View File
@@ -6,7 +6,9 @@ from sqlalchemy import desc, func
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from backend.ai.llm import hugging_llm
from backend.repository.models import AncestorDeath
from datetime import datetime
from backend.repository.db_init import SessionLocal
from backend.repository.models import AncestorDeath, DialogueCache
from backend.schemas.game_schema import AncestorDeathCreate, SkillCreateResponse
# 랜덤 이름 리스트 (자르반, 가렌 등 판타지 풍 이름)
@@ -16,30 +18,50 @@ RANDOM_NAMES = [
"소나", "룰루", "유미", "레오나", "다이애나", "세주아니", "우디르", "신 짜오"
]
# 1. 선조 사망 기록 등록
def create_death_history(db: Session, cause_of_death: str):
# 세대 계산: DB에서 현재 등록된 최대 세대를 조회하여 다음 세대 번호를 계산합니다. (기록이 없다면 1세대)
# 1. 다음 도전 세대 번호 계산
def get_next_generation(db: Session) -> int:
max_gen = db.query(func.max(AncestorDeath.generation)).scalar()
generation = (max_gen + 1) if max_gen is not None else 1
# 랜덤 이름 선택 후 세대 접미사 추가 (예: 자르반 3세)
base_name = random.choice(RANDOM_NAMES)
name = f"{base_name} {generation}"
# 장소는 임시로 1층 설정
floor = 1
return (max_gen + 1) if max_gen is not None else 1
# 2. 선조 사망 기록 등록 (클라이언트에서 생성해 보낸 데이터 그대로 저장)
def create_death_history(db: Session, death_data: AncestorDeathCreate):
db_death = AncestorDeath(
name=name,
generation=generation,
cause_of_death=cause_of_death,
floor=floor
name=death_data.name,
generation=death_data.generation,
cause_of_death=death_data.cause_of_death,
floor=death_data.floor
)
db.add(db_death)
db.commit()
db.refresh(db_death)
return db_death
# 3. 문지기 NPC 조언 대사 사전 생성 (동기식 생성 및 캐싱 보장)
def pregenerate_npc_dialogue(db: Session, next_generation: int):
try:
print(f"[Pregen] {next_generation}대손용 문지기 대사 동기 생성 시작...")
# 1. AI 조언 대사 생성
dialogue_text = generate_npc_dialogue(db=db, current_generation=next_generation)
# 2. 캐시 DB에 저장 (이미 존재한다면 Overwrite 하여 덮어씁니다.)
cached = db.query(DialogueCache).filter(DialogueCache.generation == next_generation).first()
if cached:
cached.dialogue = dialogue_text
cached.created_at = datetime.now()
else:
cached = DialogueCache(
generation=next_generation,
dialogue=dialogue_text
)
db.add(cached)
db.commit()
print(f"[Pregen] {next_generation}대손용 대사 캐싱 완료: {dialogue_text}")
except Exception as e:
db.rollback()
print(f"[Pregen ERROR] 대사 생성 실패: {e}")
# 2. 문지기 NPC 조언 대사 생성
def generate_npc_dialogue(db: Session, current_generation: int) -> str:
# 현재 세대 이전(부모 세대들)의 사망 기록 중 최근 3개를 가져옴
@@ -73,19 +95,23 @@ def generate_npc_dialogue(db: Session, current_generation: int) -> str:
ancestor_history = "\n".join(history_lines)
# 늙은 문지기 NPC 프롬프트 정의
npc_prompt = """당신은 무한 반복 로그라이크 게임의 노련하고 퉁명스러운 던전 문지기 NPC '발두르'입니다.
새로 던전에 도전하려는 플레이어(자손)에게 그의 선조들의 죽음을 기반으로 뼈 때리거나 진지한 조언을 해줍니다.
npc_prompt = """당신은 무한 반복 로그라이크 던전의 노련하고 퉁명스러운 늙은 문지기 NPC '발두르'입니다.
새로 던전에 들어서려는 플레이어(자손)에게 그의 선조(부모, 조부모 등)가 어떻게 죽었는지를 언급하며, 한심해하면서도 속으로는 살아남기를 바라는 츤데레 말투의 한글 대사를 생성하세요.
[성격 및 말투 지침]
- 늙고 많은 도전자들의 죽음을 지켜본 노련한 문지기입니다.
- 반말(낮춤말)을 사용하며, 말투는 다소 퉁명스럽고 거칠지만 속으로는 플레이어가 살아남기를 바라는 따뜻한 츤데레 톤입니다.
- 너무 구구절절 말하지 말고, 1~2문장의 핵심 조언 대사로만 하세요. (대답 이외의 부가 설명은 절대 작성하지 마세요)
[성격 및 말투 가이드라인]
- 낮춤말(반말)을 사용하며, 다소 거칠고 퉁명스럽게 말하세요. (예: "뒈졌다", "한심하긴", "멍청한 놈", "눈 똑바로 떠라")
- 번역투(~를 주의해, 너의 조상들 등)를 절대로 쓰지 마세요. 자연스러운 한국 판타지 소설/게임 속 늙은 던전 경비병 말투를 구사해야 합니다.
- 1~2문장의 짧고 굵은 대사로만 출력하세요. 대사 이외의 서술이나 설명은 일절 배제하세요.
[자연스러운 말투 예시 (Few-Shot)]
- "네 아비 녀석도 1층에서 그 멍청한 가시 함정에 걸려 뒈졌지. 살아서 돌아오고 싶거든 바닥이나 똑바로 보고 걸어라, 쯧."
- "네 할아비 놈도 2층에서 붉은 슬라임 녀석의 불길에 녹아내렸어. 네 놈은 숯검둥이가 되기 싫으면 몸 사리라고."
[이전 선조들의 사망 기록]
{ancestor_history}
현재 이 게임에 도전하는 플레이어{current_generation}대 자손입니다.
위 선조들의 구체적인 사망 원인(예: 가시 함정, 특정 몬스터 등)을 직접적으로 언급하며, 이번 도전에서 같은 실수를 반복하지 않도록 주의를 주는 대사를 생성하세요.
현재 도전자{current_generation}대 자손입니다.
위 선조들의 구체적인 사망 원인을 직접 언급하며, 이번 도전에서 같은 멍청한 실수를 반복하지 않도록 호통치거나 조언하는 대사를 한글로 출력하세요.
"""
prompt_template = ChatPromptTemplate.from_template(npc_prompt)