Junhoee's picture
Upload 140 files
e68cec5 verified
Raw
History Blame Contribute Delete
5.72 kB
"""μΉ΄λ₯΄λ°€λΌ 챕터1 ν”Œλ ˆμ΄ CLI β€” 리딩 루프 + κ°œμž… 포켓 (M4 톡합).
μ‚¬μš©λ²•:
.venv/bin/python scripts/play.py # λŒ€ν™”ν˜• (포켓은 LLM ν‚€ 있으면 μ±„νŒ…)
.venv/bin/python scripts/play.py --policy trust --quiet # μžλ™ ν”Œλ ˆμ΄, μ—”λ”© 확인
.venv/bin/python scripts/play.py --chat # 포켓 μžμœ μ±„νŒ… κ°•μ œ (OPENAI_API_KEY ν•„μš”)
.venv/bin/python scripts/play.py --no-chat # 포켓 μ±„νŒ… 끄기 (μ„ νƒμ§€λ§Œ)
포켓 μ±„νŒ…: 선택지 μΉ΄λ“œμ—μ„œ 번호 λŒ€μ‹  자유 ν…μŠ€νŠΈλ₯Ό μž…λ ₯ν•˜λ©΄ μΉ΄λ₯΄λ°€λΌμ™€ λŒ€ν™”κ°€ 열리고,
director 수렴(λ˜λŠ” ν„΄ μƒν•œ) ν›„ μ„ νƒμ§€λ‘œ λ³΅κ·€ν•œλ‹€.
"""
from __future__ import annotations
import argparse
import asyncio
import os
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from engine.repositories.content import ContentRepository # noqa: E402
from engine.services.story import StepResult, StoryEngine # noqa: E402
DIM = "\033[2m"
BOLD = "\033[1m"
RESET = "\033[0m"
def render(step: StepResult, quiet: bool):
if quiet or step.card is None:
return
c = step.card
print(f"\n{BOLD}── {c.id} Β· {c.title} ──{RESET}")
for b in step.narration:
if b.type == "dialogue_center":
print(f"\n{BOLD} ❝ {b.text} ❞{RESET}\n")
elif b.type == "character_dialogue":
print(f' {b.speaker}: "{b.text}"')
elif b.type == "stage_direction":
print(f"{DIM} ({b.text}){RESET}")
else:
print("\n" + b.text)
async def pocket_chat(pocket_service, engine: StoryEngine, card_id: str, first_line: str):
"""κ°œμž… 포켓 μžμœ μ±„νŒ… β€” 수렴/ν„΄ μƒν•œκΉŒμ§€ 돌고 μˆ¨μ€ μƒνƒœλ₯Ό μ»€λ°‹ν•œλ‹€."""
sid = await pocket_service.open(card_id, engine.state)
line = first_line
while True:
turn = await pocket_service.run_turn(sid, line)
print(f"\n {BOLD}μΉ΄λ₯΄λ°€λΌ{RESET}: {turn.reply}")
if turn.converged:
print(f"{DIM} (μž₯면이 쑰용히 λ‹€μŒμœΌλ‘œ κΈ°μš΄λ‹€ β€” {turn.reason}){RESET}")
break
line = input("λ‚˜(둜라) > ").strip()
if not line:
break
log = await pocket_service.close(sid, engine.state)
return log
def pick_or_chat(step: StepResult, policy: str, chat_enabled: bool):
"""선택지 μž…λ ₯ λ˜λŠ” 자유 ν…μŠ€νŠΈ(포켓 μ±„νŒ… μ§„μž…). λ°˜ν™˜: ('choice', idx) | ('chat', text)."""
choices = step.choices
if policy != "ask":
for i, ch in enumerate(choices):
if ch.axis == policy:
return "choice", i
for i, ch in enumerate(choices):
if ch.axis in (None, "neutral"):
return "choice", i
return "choice", 0
for i, ch in enumerate(choices):
print(f" [{i + 1}] {ch.label}")
if chat_enabled:
print(f"{DIM} (번호 λŒ€μ‹  ν•˜κ³  싢은 말을 μ“°λ©΄ μΉ΄λ₯΄λ°€λΌμ™€ λŒ€ν™”ν•©λ‹ˆλ‹€){RESET}")
while True:
raw = input("선택 > ").strip()
if raw.isdigit() and 1 <= int(raw) <= len(choices):
return "choice", int(raw) - 1
if raw and chat_enabled:
return "chat", raw
if raw and not chat_enabled:
print(f"{DIM} (포켓 μ±„νŒ… 꺼짐 β€” 번호λ₯Ό μž…λ ₯ν•˜μ„Έμš”){RESET}")
async def main():
ap = argparse.ArgumentParser()
ap.add_argument("--policy", default="ask", choices=["ask", "trust", "doubt", "neutral"])
ap.add_argument("--quiet", action="store_true")
ap.add_argument("--content", default="content")
ap.add_argument("--chat", action="store_true", help="포켓 μžμœ μ±„νŒ… κ°•μ œ ν™œμ„±ν™”")
ap.add_argument("--no-chat", action="store_true", help="포켓 μžμœ μ±„νŒ… λΉ„ν™œμ„±ν™”")
args = ap.parse_args()
chat_enabled = not args.no_chat and (args.chat or bool(os.environ.get("OPENAI_API_KEY")))
repo = ContentRepository(args.content)
engine = StoryEngine(repo)
pocket_service = None
if chat_enabled:
from engine.plugins.logging import TurnLoggingPlugin
from engine.services.pocket import PocketService
pocket_service = PocketService(repo, plugins=[TurnLoggingPlugin()])
step = engine.start()
while step.ending is None:
render(step, args.quiet)
if step.choices:
while True:
kind, val = pick_or_chat(step, args.policy, chat_enabled)
if kind == "choice":
choice, step = engine.choose(val)
if not args.quiet:
print(f"\n{DIM}β–· {choice.label}{RESET}")
if choice.result:
print(choice.result)
break
card = repo.get_card(engine.current_id)
if not card.can_chat:
print(f"{DIM} (이 μž₯면에선 κ·Έλ…€κ°€ μžλ¦¬μ— μ—†λ‹€ β€” 번호λ₯Ό μž…λ ₯ν•˜μ„Έμš”){RESET}")
continue
await pocket_chat(pocket_service, engine, engine.current_id, val)
# μ±„νŒ… ν›„ 같은 μΉ΄λ“œμ˜ μ„ νƒμ§€λ‘œ 볡귀 (λ„λ‹¬μ Β·λΆ„κΈ°λŠ” μ½”λ“œκ°€ 보μž₯)
else:
if not args.quiet and args.policy == "ask":
input(f"{DIM}(λ‹€μŒ ⏎){RESET}")
step = engine.advance()
s = engine.state
print(f"\n{BOLD}═══ 챕터 μ’…λ£Œ: {step.ending.label} ═══{RESET}")
print(f"{DIM}[디버그 β€” μœ μ € λΉ„λ…ΈμΆœ] trust={s.trust_score} doubt={s.doubt_score} "
f"frailty={s.frailty} level={s.trust_level} μΉ΄λ“œ {len(engine.visited)}μž₯ λ°©λ¬Έ{RESET}")
if __name__ == "__main__":
asyncio.run(main())