플레이어 세대 기록 초기화 및 세대 계산 및 사망 기록 등 기능 수정
This commit is contained in:
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user