Spaces:
Sleeping
Sleeping
humanda5
commited on
Commit
·
cf1ebab
1
Parent(s):
a88e1d8
몸(행동)과 관련된 내용 제거한 리팩토링 시작
Browse files- npc_social_network/README.txt +0 -10
- npc_social_network/data/vectorstores/npc.faiss +0 -0
- npc_social_network/{maps/manager → manager}/simulation_manager.py +1 -1
- npc_social_network/maps/engine/game_engine.py +0 -206
- npc_social_network/maps/manager/building.py +0 -11
- npc_social_network/maps/manager/image_loader.py +0 -27
- npc_social_network/maps/manager/image_registry.py +0 -25
- npc_social_network/maps/manager/tile.py +0 -11
- npc_social_network/maps/manager/village_map.py +0 -59
- npc_social_network/maps/villages/town_hall.py +0 -84
- npc_social_network/npc/npc_base.py +41 -175
- npc_social_network/npc/npc_manager.py +7 -38
- npc_social_network/scenarios/scenario_setup.py +5 -5
- test.ipynb +0 -374
npc_social_network/README.txt
CHANGED
@@ -19,21 +19,11 @@ LLM과 embedding 기반 기억 시스템을 통해 정교한 NPC 반응을 구
|
|
19 |
portfolio/
|
20 |
├── app.py # Flask 진입점
|
21 |
├── requirements.txt
|
22 |
-
├── run_npc_interactions.py
|
23 |
├── test.ipynb
|
24 |
└── npc_social_network/
|
25 |
├── README.txt
|
26 |
├── routes/
|
27 |
│ └── npc_route.py # 웹 라우트 (API)
|
28 |
-
├── maps/
|
29 |
-
│ ├── villages/town_hall.py # Pygame 메인 실행
|
30 |
-
│ ├── engine/game_engine.py # 게임 루프, 감정 HUD
|
31 |
-
│ └── manager/
|
32 |
-
│ ├── tile.py # Tile 클래스
|
33 |
-
│ ├── building.py # 건물 클래스
|
34 |
-
│ ├── image_loader.py # 이미지 로딩
|
35 |
-
│ ├── image_registry.py # 이미지 경로 사전
|
36 |
-
│ └── village_map.py # Village 전체 구성
|
37 |
├── npc/
|
38 |
│ ├── npc_base.py # NPC 본체 (감정/기억/관계 포함)
|
39 |
│ ├── npc_manager.py # NPC 리스트 관리
|
|
|
19 |
portfolio/
|
20 |
├── app.py # Flask 진입점
|
21 |
├── requirements.txt
|
|
|
22 |
├── test.ipynb
|
23 |
└── npc_social_network/
|
24 |
├── README.txt
|
25 |
├── routes/
|
26 |
│ └── npc_route.py # 웹 라우트 (API)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
27 |
├── npc/
|
28 |
│ ├── npc_base.py # NPC 본체 (감정/기억/관계 포함)
|
29 |
│ ├── npc_manager.py # NPC 리스트 관리
|
npc_social_network/data/vectorstores/npc.faiss
DELETED
Binary file (4.65 kB)
|
|
npc_social_network/{maps/manager → manager}/simulation_manager.py
RENAMED
@@ -1,4 +1,4 @@
|
|
1 |
-
# portfolio/npc_social_network/
|
2 |
import pickle
|
3 |
import os
|
4 |
from npc_social_network.npc.npc_manager import NPCManager
|
|
|
1 |
+
# portfolio/npc_social_network/manager/simulation_manager.py
|
2 |
import pickle
|
3 |
import os
|
4 |
from npc_social_network.npc.npc_manager import NPCManager
|
npc_social_network/maps/engine/game_engine.py
DELETED
@@ -1,206 +0,0 @@
|
|
1 |
-
# portfolio/npc_social_network/maps/engine/game_engine.py
|
2 |
-
# 게임 루프 및 이벤트 처리
|
3 |
-
import pygame
|
4 |
-
from pygame import Rect
|
5 |
-
import sys
|
6 |
-
import random
|
7 |
-
from datetime import datetime, timedelta
|
8 |
-
from npc_social_network.maps.manager.image_loader import ImageLoader
|
9 |
-
from npc_social_network.maps.manager.village_map import VillageMap
|
10 |
-
from npc_social_network.npc.npc_manager import NPCManager
|
11 |
-
|
12 |
-
|
13 |
-
class GameEngine:
|
14 |
-
"""
|
15 |
-
게임 루프, 이벤트 처리, 시각화 담당
|
16 |
-
"""
|
17 |
-
def __init__(self, screen, font, village: VillageMap, time_states: list, npc_manager: NPCManager):
|
18 |
-
self.screen = screen
|
19 |
-
self.font = font
|
20 |
-
# emotion HUD용 작은 글자 font 추가
|
21 |
-
self.emotion_font = pygame.font.SysFont("malgungothic", 14)
|
22 |
-
self.village = village
|
23 |
-
self.time_states = time_states
|
24 |
-
self.npc_manager = npc_manager
|
25 |
-
self.image_loader = village.image_loader
|
26 |
-
|
27 |
-
self.current_time_index = 0
|
28 |
-
self.frame_count = 0
|
29 |
-
self.frame_per_time_state = 200 # 기존: 시간 상태 전환 프레임
|
30 |
-
self.selected_npc = None # 선택된 NPC 상태 기본 필드
|
31 |
-
|
32 |
-
# 감정 바 색상 테이블 (수정 필요)
|
33 |
-
self.emotion_colors = {
|
34 |
-
"joy": (255, 215, 0), "gratitude": (173, 216, 230),
|
35 |
-
"anger": (255, 69, 0), "fear": (138, 43, 226),
|
36 |
-
"sadness": (70, 130, 180), "love": (255, 105, 180),
|
37 |
-
"interest": (0, 255, 127), "relief": (60, 179, 113),
|
38 |
-
}
|
39 |
-
# 관계 바 색상 테이블
|
40 |
-
self.relationship_colors = {
|
41 |
-
"best friend": (255, 182, 193), "friend": (144, 238, 144),
|
42 |
-
"acquaintance": (240, 230, 140), "stranger": (211, 211, 211),
|
43 |
-
"nuisance": (255, 160, 122), "rival": (255, 99, 71),
|
44 |
-
"enemy": (220, 20, 60),
|
45 |
-
}
|
46 |
-
|
47 |
-
|
48 |
-
# 실제 시간 흐름 시뮬레이션용
|
49 |
-
self.current_time = datetime.now() # 현재 시뮬레이션 시간
|
50 |
-
self.time_step = timedelta(minutes=1) # 1틱 = 1분
|
51 |
-
|
52 |
-
def draw(self):
|
53 |
-
self.village.draw(self.screen) # 마을 전체를 그림
|
54 |
-
|
55 |
-
# 상단 시간 텍스트 출력
|
56 |
-
time_state_text = self.time_states[self.current_time_index]
|
57 |
-
time_text = self.font.render(
|
58 |
-
f"현재 시간: {time_state_text} | {self.current_time.strftime('%H:%M')}",
|
59 |
-
True, (0, 0, 0)
|
60 |
-
)
|
61 |
-
self.screen.blit(time_text, (10, 10))
|
62 |
-
|
63 |
-
# 감정 상태 HUD 렌더링
|
64 |
-
if self.selected_npc:
|
65 |
-
self.draw_emotion_hud()
|
66 |
-
|
67 |
-
def draw_emotion_hud(self):
|
68 |
-
"""
|
69 |
-
감정 상태와 함께 관꼐 상태(유형, 점수)를 HUD에 표시
|
70 |
-
"""
|
71 |
-
npc = self.selected_npc
|
72 |
-
top_emotions = npc.get_top_emotions(top_n=5)
|
73 |
-
|
74 |
-
# 플레이어와의 관계 정보 가져오기
|
75 |
-
player_profile = npc.relationships._get_or_create_profile("플레이어")
|
76 |
-
relationship_type = player_profile.type
|
77 |
-
relationship_score = player_profile.score
|
78 |
-
|
79 |
-
# HUD 레이아웃 설정
|
80 |
-
hud_height = 180 # 원하는 높이
|
81 |
-
margin_right = 20 # 오른쪽 여백
|
82 |
-
margin_bottom = 20 # 아래쪽 여백 (화면 아래 띄우고 싶으면 사용)
|
83 |
-
|
84 |
-
hud_width = self.screen.get_width() * 0.45 # 화면 절반 너비
|
85 |
-
hud_rect = Rect(
|
86 |
-
self.screen.get_width() - hud_width - margin_right,
|
87 |
-
self.screen.get_height() - hud_height - margin_bottom,
|
88 |
-
hud_width,
|
89 |
-
hud_height
|
90 |
-
)
|
91 |
-
# 반투명 배경
|
92 |
-
s = pygame.Surface((hud_width, hud_height), pygame.SRCALPHA)
|
93 |
-
s.fill((30, 30, 30, 200))
|
94 |
-
self.screen.blit(s, (hud_rect.x, hud_rect.y))
|
95 |
-
|
96 |
-
padding_x = 20 # 오른쪽에서 얼마나 띄울지
|
97 |
-
padding_y = 10 # 위에서 얼마나 띄울지
|
98 |
-
spacing_y = 20 # 감정 간 간격
|
99 |
-
label_bar_gap = 100 # label과 bar 사이 간격
|
100 |
-
title_gap = 30 # 제목과 감정 표시 사이 간격
|
101 |
-
bar_width_max = hud_width - 2 * padding_x - label_bar_gap
|
102 |
-
bar_height = 18
|
103 |
-
|
104 |
-
# NPC 이름
|
105 |
-
self.screen.blit(
|
106 |
-
self.font.render(f"{npc.name} 내면", True, (255, 255, 255)),
|
107 |
-
(hud_rect.x + padding_x, hud_rect.y + padding_y)
|
108 |
-
)
|
109 |
-
|
110 |
-
# 감정 표시
|
111 |
-
for i, (emotion_name, value) in enumerate(top_emotions):
|
112 |
-
y = hud_rect.y + padding_y + title_gap + i * spacing_y
|
113 |
-
|
114 |
-
text_surface = self.emotion_font.render(f"{emotion_name} ({value:.1f})", True, (255, 255, 255))
|
115 |
-
self.screen.blit(text_surface, (hud_rect.x + padding_x, y))
|
116 |
-
|
117 |
-
# bar background
|
118 |
-
bar_bg_rect = Rect(hud_rect.x + padding_x + label_bar_gap, y + 3, bar_width_max, bar_height)
|
119 |
-
pygame.draw.rect(self.screen, (50, 50, 50), bar_bg_rect)
|
120 |
-
|
121 |
-
# bar fill
|
122 |
-
color = self.emotion_colors.get(emotion_name, (100, 200, 255))
|
123 |
-
# color 값이 유효한 튜플인지 확인
|
124 |
-
if not (isinstance(color, tuple) and len(color) == 3 and all(isinstance(c, int) for c in color)):
|
125 |
-
print(f"[경고] 잘못된 color 값 감지: {emotion_name} → {color}, 디폴트 색상 사용")
|
126 |
-
color = (100, 200, 255)
|
127 |
-
|
128 |
-
bar_fill_width = int((value / 10) * bar_width_max)
|
129 |
-
bar_fill_rect = Rect(hud_rect.x + padding_x + label_bar_gap, y + 3, bar_fill_width, bar_height)
|
130 |
-
pygame.draw.rect(self.screen, color, bar_fill_rect)
|
131 |
-
|
132 |
-
# 관계 상태 표시
|
133 |
-
relation_y = hud_rect.y + padding_y + title_gap + len(top_emotions) * spacing_y
|
134 |
-
relation_color = self.relationship_colors.get(relationship_type, (200, 200, 200))
|
135 |
-
relation_text = f"플레이어와의 관계: {relationship_type} ({relationship_score:.1f})"
|
136 |
-
relation_surface = self.emotion_font.render(relation_text, True, relation_color)
|
137 |
-
self.screen.blit(relation_surface, (hud_rect.x + padding_x, relation_y))
|
138 |
-
|
139 |
-
def update(self):
|
140 |
-
"""
|
141 |
-
게임의 모든 상태를 업데이트 (시간, NPC 행동, 상호작용 등)
|
142 |
-
"""
|
143 |
-
self.frame_count += 1
|
144 |
-
time_state_text = self.time_states[self.current_time_index]
|
145 |
-
|
146 |
-
# 1) 시간 상태(아침/점심 등) 순환 유지
|
147 |
-
if self.frame_count % self.frame_per_time_state == 0:
|
148 |
-
self.current_time_index = (self.current_time_index + 1) % len(self.time_states)
|
149 |
-
|
150 |
-
# 2) 실제 시간 경과
|
151 |
-
self.current_time += self.time_step
|
152 |
-
|
153 |
-
# NPC 행동/기억 업데이트
|
154 |
-
if self.frame_count % 20 == 0:
|
155 |
-
self.village.update_npcs(self.time_states[self.current_time_index])
|
156 |
-
|
157 |
-
# NPC 기억 승격 판단
|
158 |
-
if self.frame_count % 60 == 0: # 60 프레임마다 약 3초 기준
|
159 |
-
for npc in self.npc_manager.all():
|
160 |
-
npc.memory_store.promote_memories(time_threshold_minutes=60)
|
161 |
-
|
162 |
-
# 일정 프레임마다 모든 NPC의 자율 행동 업데이트
|
163 |
-
if self.frame_count % 120 == 0: # 약 12초에 한 번
|
164 |
-
# NPC 간 상호작용 시작
|
165 |
-
if random.random() < 0.3: # 30% 확률
|
166 |
-
self.npc_manager.initiate_npc_to_npc_interaction(time_context=time_state_text)
|
167 |
-
|
168 |
-
for npc in self.npc_manager.all():
|
169 |
-
# 주기적으로 관계 요약 업데이트 시도
|
170 |
-
if random.random() < 0.1:
|
171 |
-
target_to_summarize = self.npc_manager.get_random_npc(exclude=npc)
|
172 |
-
if target_to_summarize:
|
173 |
-
npc.relationships.summarize_relationship(target_to_summarize.name)
|
174 |
-
# 자율 행동 실행
|
175 |
-
npc.update_autonomous_behavior(time_context=time_state_text)
|
176 |
-
|
177 |
-
# 매우 낮은 확률로 NPC가 상징 기억을 생성하도록 함
|
178 |
-
if self.frame_count % 500 == 0: # 약 50초에 한 번, 낮은 확률로
|
179 |
-
if random.random() < 0.05: # 5% 확률
|
180 |
-
random_npc = self.npc_manager.get_random_npc()
|
181 |
-
if random_npc:
|
182 |
-
random_npc.create_symbolic_memory()
|
183 |
-
|
184 |
-
def handle_click(self, pos):
|
185 |
-
mx, my = pos
|
186 |
-
tile_x = mx // self.village.tile_size
|
187 |
-
tile_y = my // self.village.tile_size
|
188 |
-
npc = self.village.npc_manager.get_npc_at(tile_x, tile_y)
|
189 |
-
if npc:
|
190 |
-
time_state_text = self.time_states[self.current_time_index]
|
191 |
-
print(f"\n👤 {npc.name} ({npc.job})")
|
192 |
-
print(f" - 위치: ({tile_x}, {tile_y})")
|
193 |
-
print(f" - 대화: {npc.generate_dialogue(user_input='', use_llm=False, time_context=time_state_text)}")
|
194 |
-
print(f" - 기억: {npc.recall()}")
|
195 |
-
self.set_selected_npc(npc)
|
196 |
-
|
197 |
-
def set_selected_npc(self, npc):
|
198 |
-
self.selected_npc = npc
|
199 |
-
print(f"[HUD] {npc.name} 선택됨 → 감정 상태 표시 시작")
|
200 |
-
|
201 |
-
def clear_selected_npc(self):
|
202 |
-
"""
|
203 |
-
ESC 키로 Selected_npc 해제 메서드
|
204 |
-
"""
|
205 |
-
print("[HUD] 감정 상태 표시 해제됨")
|
206 |
-
self.selected_npc = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
npc_social_network/maps/manager/building.py
DELETED
@@ -1,11 +0,0 @@
|
|
1 |
-
# portfolio/npc_social_network/maps/manager/building.py
|
2 |
-
# 건물 클래스 정의
|
3 |
-
class Building:
|
4 |
-
def __init__(self, x, y, b_type, image):
|
5 |
-
self.x = x
|
6 |
-
self.y = y
|
7 |
-
self.type = b_type
|
8 |
-
self.image = image
|
9 |
-
|
10 |
-
def draw(self, screen, tile_size):
|
11 |
-
screen.blit(self.image, (self.x * tile_size, self.y * tile_size))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
npc_social_network/maps/manager/image_loader.py
DELETED
@@ -1,27 +0,0 @@
|
|
1 |
-
# portfolio/npc_social_network/maps/manager/image_loader.py
|
2 |
-
# 이미지 로더 클래스
|
3 |
-
import os
|
4 |
-
import pygame
|
5 |
-
from npc_social_network.maps.manager.image_registry import IMAGE_PATHS
|
6 |
-
|
7 |
-
class ImageLoader:
|
8 |
-
def __init__(self, tile_size):
|
9 |
-
self.tile_size = tile_size
|
10 |
-
self.cache = {}
|
11 |
-
|
12 |
-
# 현재 파일 기준 static/images/tiles까지의 절대 경로 설정
|
13 |
-
current_dir = os.path.dirname(os.path.abspath(__file__)) # .../maps/manager
|
14 |
-
self.base_path = os.path.abspath(
|
15 |
-
os.path.join(current_dir, '..', '..', 'static', 'images', 'tiles')
|
16 |
-
)
|
17 |
-
|
18 |
-
def load(self, category, key):
|
19 |
-
path = IMAGE_PATHS[category][key]
|
20 |
-
if path in self.cache:
|
21 |
-
return self.cache[path]
|
22 |
-
|
23 |
-
full_path = os.path.join(self.base_path, path) # 절대 경로로 이미지 위치 설정
|
24 |
-
image = pygame.image.load(full_path)
|
25 |
-
image = pygame.transform.scale(image, (self.tile_size, self.tile_size))
|
26 |
-
self.cache[path] = image
|
27 |
-
return image
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
npc_social_network/maps/manager/image_registry.py
DELETED
@@ -1,25 +0,0 @@
|
|
1 |
-
# portfolio/npc_social_network/maps/manager/image_registry.py
|
2 |
-
# 모든 이미지 경로 중앙관리
|
3 |
-
|
4 |
-
IMAGE_PATHS = {
|
5 |
-
"terrain": {
|
6 |
-
"grass": "terrain/tile_grass.png",
|
7 |
-
"dirt": "terrain/tile_dirt.png",
|
8 |
-
"stone": "terrain/tile_stone.png",
|
9 |
-
"water": "terrain/tile_water.png",
|
10 |
-
"pool" : "terrain/tile_pool.png",
|
11 |
-
},
|
12 |
-
"building": {
|
13 |
-
"house": "building/build_house.png",
|
14 |
-
"temple": "building/build_temple.png",
|
15 |
-
"market": "building/build_market.png",
|
16 |
-
},
|
17 |
-
"plant": {
|
18 |
-
"tree": "plant/plant_tree.png",
|
19 |
-
"wheat": "plant/plant_wheat.png",
|
20 |
-
"raspberry": "plant/plant_raspberry.png",
|
21 |
-
},
|
22 |
-
"npc": {
|
23 |
-
"default": "npc/npc.png",
|
24 |
-
}
|
25 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
npc_social_network/maps/manager/tile.py
DELETED
@@ -1,11 +0,0 @@
|
|
1 |
-
# portfolio/npc_social_network/maps/manager/tile.py
|
2 |
-
# 타일 클래스 정의
|
3 |
-
class Tile:
|
4 |
-
def __init__(self, x, y, tile_type, image):
|
5 |
-
self.x = x
|
6 |
-
self.y = y
|
7 |
-
self.type = tile_type
|
8 |
-
self.image = image
|
9 |
-
|
10 |
-
def draw(self, screen, tile_size):
|
11 |
-
screen.blit(self.image, (self.x * tile_size, self.y * tile_size))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
npc_social_network/maps/manager/village_map.py
DELETED
@@ -1,59 +0,0 @@
|
|
1 |
-
# portfolio/npc_social_network/maps/manager/village_map.py
|
2 |
-
# 마을 전체 관리 클래스
|
3 |
-
|
4 |
-
from npc_social_network.npc.npc_base import NPC
|
5 |
-
from npc_social_network.npc.npc_manager import NPCManager
|
6 |
-
|
7 |
-
class Building:
|
8 |
-
def __init__(self, x, y, b_type, image):
|
9 |
-
self.x = x
|
10 |
-
self.y = y
|
11 |
-
self.type = b_type
|
12 |
-
self.image = image
|
13 |
-
|
14 |
-
def draw(self, screen, tile_size):
|
15 |
-
screen.blit(self.image, (self.x * tile_size, self.y * tile_size))
|
16 |
-
|
17 |
-
class VillageMap:
|
18 |
-
def __init__(self, image_loader, tile_size, grid_width, grid_height, npc_manager: NPCManager):
|
19 |
-
self.image_loader = image_loader
|
20 |
-
self.tile_size = tile_size
|
21 |
-
self.grid_width = grid_width
|
22 |
-
self.grid_height = grid_height
|
23 |
-
self.tiles = []
|
24 |
-
self.buildings = []
|
25 |
-
self.npc_manager = npc_manager
|
26 |
-
|
27 |
-
def generate(self):
|
28 |
-
import random
|
29 |
-
|
30 |
-
terrain_keys = list(self.image_loader.cache.keys()) or ["grass", "dirt", "stone"]
|
31 |
-
|
32 |
-
for y in range(self.grid_height):
|
33 |
-
for x in range(self.grid_width):
|
34 |
-
terrain = random.choice(["grass", "dirt", "stone"])
|
35 |
-
image = self.image_loader.load("terrain", terrain)
|
36 |
-
self.tiles.append((x, y, image))
|
37 |
-
|
38 |
-
# 건물 초기 세팅값
|
39 |
-
self.buildings = [
|
40 |
-
Building(2, 3, "house", self.image_loader.load("building","house")),
|
41 |
-
Building(5, 5, "market", self.image_loader.load("building","market")),
|
42 |
-
Building(10, 3, "wheat", self.image_loader.load("plant", "wheat")),
|
43 |
-
Building(8, 8, "temple", self.image_loader.load("building", "temple", )),
|
44 |
-
]
|
45 |
-
|
46 |
-
def draw(self, screen):
|
47 |
-
for x, y, image in self.tiles:
|
48 |
-
screen.blit(image, (x * self.tile_size, y * self.tile_size))
|
49 |
-
for b in self.buildings:
|
50 |
-
b.draw(screen, self.tile_size)
|
51 |
-
self.npc_manager.draw_all(screen, self.tile_size, self.image_loader)
|
52 |
-
|
53 |
-
def update_npcs(self, time_state):
|
54 |
-
self.npc_manager.move_all()
|
55 |
-
for npc in self.npc_manager.all():
|
56 |
-
x, y = npc.get_position()
|
57 |
-
for b in self. buildings:
|
58 |
-
if (b.x, b.y) == (x, y):
|
59 |
-
print(f"{time_state} - {npc.name}이(가) {b.type}에 도착했습니다.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
npc_social_network/maps/villages/town_hall.py
DELETED
@@ -1,84 +0,0 @@
|
|
1 |
-
# portfolio/npc_social_network/maps/villages/town_hall.py
|
2 |
-
# 메인 실행 파일
|
3 |
-
import pygame
|
4 |
-
import sys
|
5 |
-
from npc_social_network.scenarios.scenario_setup import setup_initial_scenario
|
6 |
-
from ..manager.simulation_manager import save_simulation, load_simulation
|
7 |
-
from npc_social_network.maps.manager.image_loader import ImageLoader
|
8 |
-
from npc_social_network.maps.manager.village_map import VillageMap
|
9 |
-
from npc_social_network.maps.engine.game_engine import GameEngine
|
10 |
-
from npc_social_network.npc.npc_manager import NPCManager
|
11 |
-
from npc_social_network.npc.npc_base import NPC
|
12 |
-
|
13 |
-
def run_town_hall_simulation():
|
14 |
-
|
15 |
-
# 초기 설정
|
16 |
-
pygame.init()
|
17 |
-
|
18 |
-
tile_size = 32
|
19 |
-
width, height = 20, 20
|
20 |
-
screen = pygame.display.set_mode((width * tile_size, height * tile_size))
|
21 |
-
pygame.display.set_caption("town_hall")
|
22 |
-
|
23 |
-
# 캐릭터가 움직이는 일정 시간
|
24 |
-
clock = pygame.time.Clock()
|
25 |
-
font = pygame.font.SysFont("malgungothic", 20) # 한글 출력용
|
26 |
-
|
27 |
-
# 저장/불러오기
|
28 |
-
npc_manager = load_simulation() # 저장된 상태를 불러오기 시도
|
29 |
-
|
30 |
-
image_loader = ImageLoader(tile_size)
|
31 |
-
|
32 |
-
if npc_manager is None:
|
33 |
-
# 저장된 파일이 없으면 새로운 시나리오 시작
|
34 |
-
npc_manager = setup_initial_scenario(image_loader)
|
35 |
-
|
36 |
-
# 로딩
|
37 |
-
village = VillageMap(image_loader, tile_size, width, height, npc_manager)
|
38 |
-
village.generate()
|
39 |
-
|
40 |
-
if len(npc_manager.all()) == 0:
|
41 |
-
# 불러온 데이터가 비어있거나, 새로 시작하는 경우
|
42 |
-
village.generate()
|
43 |
-
print("새로운 NPC들을 생성했습니다.")
|
44 |
-
|
45 |
-
# 게임 엔진 초기 세팅
|
46 |
-
engine = GameEngine(screen, font, village, ["아침", "점심", "저녁", "밤"], npc_manager)
|
47 |
-
|
48 |
-
# pygame 루프
|
49 |
-
runnig = True
|
50 |
-
while runnig:
|
51 |
-
for event in pygame.event.get():
|
52 |
-
if event.type == pygame.QUIT:
|
53 |
-
running = False
|
54 |
-
elif event.type == pygame.KEYDOWN:
|
55 |
-
if event.key == pygame.K_s: # 'S' 키를 누르면 저장
|
56 |
-
save_simulation(npc_manager)
|
57 |
-
elif event.key == pygame.K_l: # 'L' 키를 누르면 불러오기
|
58 |
-
loaded_manager = load_simulation()
|
59 |
-
if loaded_manager:
|
60 |
-
npc_manager = loaded_manager
|
61 |
-
# 불러온 데이터로 엔진 재설정
|
62 |
-
village.npc_manager = npc_manager
|
63 |
-
engine.npc_manager = npc_manager
|
64 |
-
engine.selected_npc = None # 선택 초기화
|
65 |
-
print("시뮬레이션을 새로고침했습니다.")
|
66 |
-
|
67 |
-
elif event.key == pygame.K_ESCAPE:
|
68 |
-
engine.clear_selected_npc() # ESC로 선택 해제
|
69 |
-
elif event.type == pygame.MOUSEBUTTONDOWN:
|
70 |
-
engine.handle_click(pygame.mouse.get_pos()) # 마우스 클릭으로 NPC 선택
|
71 |
-
|
72 |
-
screen.fill((255, 255, 255))
|
73 |
-
engine.update()
|
74 |
-
engine.draw()
|
75 |
-
pygame.display.update()
|
76 |
-
clock.tick(10) # 초당 10프레임
|
77 |
-
|
78 |
-
# 종료 전에 최종 상태 저장
|
79 |
-
save_simulation(npc_manager)
|
80 |
-
pygame.quit()
|
81 |
-
sys.exit()
|
82 |
-
|
83 |
-
if __name__ == "__main__":
|
84 |
-
run_town_hall_simulation()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
npc_social_network/npc/npc_base.py
CHANGED
@@ -73,12 +73,9 @@ class NPC:
|
|
73 |
NPC 클래스
|
74 |
NPC에 대한 기본 정보를 객체화
|
75 |
"""
|
76 |
-
def __init__(self, name, job
|
77 |
self.name = name
|
78 |
self.job = job
|
79 |
-
self.path = path
|
80 |
-
self.image_identifier = image_identifier
|
81 |
-
self.current_index = 0
|
82 |
|
83 |
# npc 기억
|
84 |
self.memory_store = MemoryStore()
|
@@ -87,45 +84,37 @@ class NPC:
|
|
87 |
self.personality = PersonalityManager(personality)
|
88 |
self.relationships = RelationshipManager(self)
|
89 |
self.emotion = EmotionManager(EMOTION_DECAY_RATE, self.personality)
|
90 |
-
|
91 |
# 행동 관리자
|
92 |
self.behavior = BehaviorManager()
|
93 |
self.planner = PlannerManager(self)
|
94 |
-
|
95 |
# 내부: float 감정 수치 관리용 버퍼
|
96 |
self._emotion_buffer = self.emotion.get_buffer()
|
|
|
97 |
# baseline 감정 값 부여
|
98 |
baseline_buffer = {}
|
99 |
for emo in self._emotion_buffer:
|
100 |
base_min = 0.05
|
101 |
base_max = 0.3
|
102 |
category = EMOTION_CATEGORY_MAP.get(emo, None)
|
103 |
-
|
104 |
-
|
105 |
-
|
106 |
-
|
107 |
-
|
108 |
-
bias = self.personality.get("social_bias", 1.0)
|
109 |
-
elif category == "cognitive":
|
110 |
-
bias = self.personality.get("cognitive_bias", 1.0)
|
111 |
-
elif category == "complex":
|
112 |
-
bias = self.personality.get("complex_bias", 1.0)
|
113 |
-
else:
|
114 |
-
bias = self.personality.get("sensitive", 1.0)
|
115 |
|
116 |
baseline_value = base_min + (base_max - base_min) * bias
|
117 |
baseline_value *= self.personality.get("sensitive", 1.0) # 전체 민감도 적용
|
118 |
|
119 |
baseline_buffer[emo] = round(baseline_value, 3)
|
120 |
-
self._emotion_buffer[emo] = baseline_buffer[emo] # emotion_buffer 초기 적용
|
121 |
|
122 |
# baseline buffer를 EmotionManager에 등록
|
123 |
self.emotion.set_baseline(baseline_buffer)
|
|
|
|
|
124 |
|
125 |
# 프로필 지정 → 기본은 "stable", 나중에 NPC마다 다르게 부여
|
126 |
profile_type = "stable"
|
127 |
profile = PERSONALITY_PROFILE[profile_type]
|
128 |
-
|
129 |
# Personality 변화 속도 개별 설정
|
130 |
self.personality_change_rate = {
|
131 |
"sensitive": random.uniform(*profile["sensitive"]),
|
@@ -134,7 +123,7 @@ class NPC:
|
|
134 |
}
|
135 |
|
136 |
# Personality baseline 저장 (초기값 복사)
|
137 |
-
self.personality_baseline = copy.deepcopy(self.personality)
|
138 |
|
139 |
# 나이 추가 (0 ~ 80세 기본 랜덤 초기화 → 나중에 생성 시 age 지정 가능)
|
140 |
self.age = random.randint(0, 80)
|
@@ -143,27 +132,6 @@ class NPC:
|
|
143 |
self.personality_stage = None
|
144 |
self.update_personality_stage()
|
145 |
|
146 |
-
|
147 |
-
def move(self):
|
148 |
-
"""
|
149 |
-
이동
|
150 |
-
"""
|
151 |
-
self.current_index = (self.current_index + 1) % len(self.path)
|
152 |
-
|
153 |
-
def get_position(self):
|
154 |
-
"""
|
155 |
-
현재 위치 좌표 반환
|
156 |
-
"""
|
157 |
-
return self.path[self.current_index]
|
158 |
-
|
159 |
-
def draw(self, screen, tile_size, image_loader):
|
160 |
-
"""
|
161 |
-
NPC 이미지 화면에 렌더링
|
162 |
-
"""
|
163 |
-
image = image_loader.load(*self.image_identifier)
|
164 |
-
x, y = self.get_position()
|
165 |
-
screen.blit(image, (x * tile_size, y * tile_size))
|
166 |
-
|
167 |
def generate_dialogue(self, user_input: str, time_context: str, target_npc: Optional["NPC"]=None, use_llm: bool=True) -> str:
|
168 |
"""
|
169 |
플레이어 또는 NPC와 상호작용 시 사용되는 통합 대사 생성 함수
|
@@ -178,49 +146,31 @@ class NPC:
|
|
178 |
if len(matched_memories) >= 3:
|
179 |
self.summarize_and_store_memories(matched_memories)
|
180 |
|
181 |
-
# 3. LLM 프롬프트 생성
|
182 |
-
prompt = build_npc_prompt(self, user_input, matched_memories, target_npc)
|
183 |
-
|
184 |
-
# 4. LLM 호출
|
185 |
npc_reply = query_llm_with_prompt(prompt)
|
186 |
|
187 |
-
#
|
188 |
emotion = query_llm_for_emotion(npc_reply)
|
189 |
if emotion and emotion in EMOTION_LIST:
|
190 |
self.update_emotion(emotion, strength=2.0)
|
191 |
|
192 |
-
#
|
193 |
interlocutor_name = target_npc.name if target_npc else "플레이어"
|
|
|
|
|
194 |
|
195 |
-
# 6.
|
196 |
-
memory_content=""
|
197 |
-
if target_npc:
|
198 |
-
# NPC간 대화 기억
|
199 |
-
memory_content = f"[{interlocutor_name}의 말] '{user_input}' → [나의 응답] '{npc_reply}'"
|
200 |
-
else:
|
201 |
-
# 플레이어와의 기억
|
202 |
-
memory_content = f"[플레이어의 말] '{user_input}' → [나의 응답] '{npc_reply}'"
|
203 |
-
|
204 |
-
memory = self.remember(
|
205 |
-
content=memory_content,
|
206 |
-
importance=7,
|
207 |
-
emotion=emotion,
|
208 |
-
context_tags=[time_context]
|
209 |
-
)
|
210 |
-
|
211 |
-
# 7. 관계 반영 (플레이어나 다른 NPC)
|
212 |
if emotion and emotion in EMOTION_RELATION_IMPACT:
|
213 |
self.relationships.update_relationship(interlocutor_name, emotion, strength=2.0, memory=memory)
|
214 |
-
|
215 |
# NPC간 상호작용일 경우, 상대방의 프로필도 업데이트 (상대방의 평가)
|
216 |
if target_npc:
|
217 |
target_emotion = query_llm_for_emotion(user_input) # 내가 한 말에 대한 상대의 감정 추론
|
218 |
if target_emotion and target_emotion in EMOTION_RELATION_IMPACT:
|
219 |
target_npc.relationships.update_relationship(self.name, target_emotion, strength=2.0, memory=memory)
|
220 |
|
221 |
-
#
|
222 |
self.update_personality()
|
223 |
-
|
224 |
return npc_reply
|
225 |
|
226 |
else:
|
@@ -288,80 +238,37 @@ class NPC:
|
|
288 |
self.emotion.decay_emotion()
|
289 |
self._emotion_buffer = self.emotion.get_buffer()
|
290 |
|
291 |
-
def
|
292 |
"""
|
293 |
저장된 모든 기억(단기 + 장기)을 리스트로 반환
|
294 |
"""
|
295 |
return [m.content for m in self.memory_store.get_all_memories()]
|
296 |
|
297 |
-
def get_composite_emotion_state(self, top_n: int = 3):
|
298 |
"""
|
299 |
-
|
300 |
"""
|
301 |
buffer = self.emotion.get_buffer() # EmotionManager에서 최신 감정 상태 복사본 가져오기
|
302 |
sorted_emotions = sorted(buffer.items(), key=lambda x: x[1], reverse=True)
|
303 |
-
composite = [(emo,round(score, 2)) for emo, score in sorted_emotions[:top_n] if score > 0]
|
304 |
return composite # 예 [('fear', 2.0), ('anger'), 1.5]
|
305 |
|
306 |
-
def summarize_emotional_state(self):
|
307 |
"""
|
308 |
감정 상태 요약 텍스트 생성
|
309 |
"""
|
310 |
composite = self.get_composite_emotion_state()
|
311 |
dominant = self.emotion.get_dominant_emotion()
|
312 |
-
|
313 |
-
|
314 |
-
|
315 |
-
"""
|
316 |
-
감정과 상호작용의 긍/부정 여부에 따른 관계 변화량 결정
|
317 |
-
"""
|
318 |
-
base = 5.0 # 기본 영향력
|
319 |
-
|
320 |
-
# 감정 종류별 base 수정
|
321 |
-
if emotion in POSITIVE_RELATION_EMOTIONS:
|
322 |
-
base += 2.0
|
323 |
-
# 긍정 감정은 positive = True일 때만 +로 반영, positive = False일 경우 최소한으로 영향
|
324 |
-
return base if positive else 0.0
|
325 |
-
elif emotion in NEGATIVE_RELATION_EMOTIONS:
|
326 |
-
base += 3.0
|
327 |
-
# 부정 감정은 positive = False일 때만 -로 반영, positive = True일 경우 최소한으로 영향
|
328 |
-
return -base if not positive else 0.0
|
329 |
-
else:
|
330 |
-
# 정의되지 않은 감정 처리
|
331 |
-
return 0.0
|
332 |
|
333 |
def get_relationship_description(self, target_name: str) -> str:
|
334 |
"""
|
335 |
-
|
336 |
"""
|
337 |
return self.relationships.get_relationship_summary(target_name)
|
338 |
|
339 |
-
def reflect_memory_on_relationship(self, other_npc_name:str):
|
340 |
-
"""
|
341 |
-
기억이 관계에 미치는 영향 반영
|
342 |
-
"""
|
343 |
-
# 최근 기억 + 최근 감정을 기반으로 관계 변화량 결정
|
344 |
-
memories = self.memory_store.get_all_memories()
|
345 |
-
dominant_emotion = self.emotion.get_dominant_emotion()
|
346 |
-
|
347 |
-
# 기준 설정
|
348 |
-
base_influence = 0.0
|
349 |
-
for mem in memories[-5:]: # 최근 5개 기억 기준
|
350 |
-
if mem.importance >= 7: # 중요 기억만 반영
|
351 |
-
# 감정 성향에 따라 positive 결정 및 영향력 계산
|
352 |
-
# 중요 기억만 반영
|
353 |
-
is_positive = mem.emotion in POSITIVE_RELATION_EMOTIONS
|
354 |
-
delta = self._get_emotion_influence(mem.emotion, positive=is_positive)
|
355 |
-
base_influence += delta
|
356 |
-
|
357 |
-
# 현재 감정 상태 영향 추가 반영 (보조적 반영)
|
358 |
-
if dominant_emotion:
|
359 |
-
is_positive = dominant_emotion in POSITIVE_RELATION_EMOTIONS
|
360 |
-
emo_influence = self._get_emotion_influence(dominant_emotion, positive=is_positive)
|
361 |
-
base_influence += emo_influence * 0.5 # 현재 감정은 보조적 영향
|
362 |
-
|
363 |
-
# 관계 갱신
|
364 |
-
self.relationships.update_relationship(other_npc_name, base_influence)
|
365 |
|
366 |
def get_top_emotions(self, top_n=6):
|
367 |
"""
|
@@ -371,11 +278,10 @@ class NPC:
|
|
371 |
|
372 |
def update_personality(self):
|
373 |
"""
|
374 |
-
|
375 |
- MemoryType 별 가중치 + EmotionInfluence 별 가중치 모두 반영
|
376 |
"""
|
377 |
recent_memories = [m for m in self.memory_store.get_recent_memories(limit=10) if m.importance >= 5]
|
378 |
-
|
379 |
if not recent_memories:
|
380 |
return
|
381 |
|
@@ -461,7 +367,7 @@ class NPC:
|
|
461 |
- 노년 NPC → 오래된 기억 더 강조
|
462 |
"""
|
463 |
if self.personality_stage in [
|
464 |
-
"infancy_and_toddlerhood", "
|
465 |
"adolescence", "young_adulthood"
|
466 |
]:
|
467 |
# 최근 기억 강조 → 오래된 기억 영향 약화
|
@@ -478,51 +384,15 @@ class NPC:
|
|
478 |
"""
|
479 |
기억 리스트를 요약하고, NPC 장기 기억으로 저장
|
480 |
"""
|
481 |
-
if not memories:
|
482 |
-
return
|
483 |
-
|
484 |
summary = summarize_memories(memories)
|
485 |
-
|
486 |
-
|
487 |
-
|
488 |
-
|
489 |
-
|
490 |
-
|
491 |
-
Memory(content=summary, importance=9, emotion=self.emotion.get_dominant_emotion(), memory_type="Summary")
|
492 |
)
|
493 |
-
print(f"[요약 기억 저장됨] {summary}")
|
494 |
-
|
495 |
-
def reflect_memory_emotions_on_relationship(self, target_name: str, memory_limit: int=10):
|
496 |
-
"""
|
497 |
-
대상과의 모든 상호작용 기억을 바탕으로 관꼐를 재평가하도록 개선
|
498 |
-
"""
|
499 |
-
recent_memories = self.memory_store.get_recent_memories(limit=memory_limit)
|
500 |
-
|
501 |
-
for mem in recent_memories:
|
502 |
-
# 플레이어와의 관계를 평가할 때는, 다른 NPC 이름이 명시되지 않은 모든 대화 기억을 고려함
|
503 |
-
is_interaction_with_target = False
|
504 |
-
if target_name == "플레이어":
|
505 |
-
# 해당 기억이 플레이어와의 대화에서 비롯되었는지 확인 (단순 체크)
|
506 |
-
if "[입력]" in mem.content and "[응답]" in mem.content:
|
507 |
-
is_interaction_with_target = True
|
508 |
-
elif target_name in mem.content:
|
509 |
-
is_interaction_with_target = True
|
510 |
-
|
511 |
-
if not is_interaction_with_target:
|
512 |
-
continue # 대상이 언급된 기억만 추출
|
513 |
-
|
514 |
-
# 기억에 감정이 없거나, 관계에 영향을 주지 않는 감정이면 건너뜀
|
515 |
-
if not mem.emotion or mem.emotion not in EMOTION_RELATION_IMPACT:
|
516 |
-
continue
|
517 |
-
|
518 |
-
# 기억의 중요도를 기반으로 관계 변화 강도 결정 (1~10 -> 0.2~2.0)
|
519 |
-
strength = mem.importance / 5.0
|
520 |
-
print(f" - 기억 '{mem.content[:20]}...' (감정: {mem.emotion}, 중요도: {mem.importance}) -> 관계에 {strength:.1f}만큼의 강도로 영향을 줍니다.")
|
521 |
-
# 관계 업데이트 호출
|
522 |
-
self.relationships.update_relationship(target_name, mem.emotion, strength)
|
523 |
-
|
524 |
-
current_score = self.relationships.get_relationship(target_name)
|
525 |
-
print(f"[관계 업데이트 완료] {target_name}와의 최종 관계 점수: {current_score:.1f}")
|
526 |
|
527 |
def update_autonomous_behavior(self, time_context: str):
|
528 |
"""
|
@@ -546,22 +416,18 @@ class NPC:
|
|
546 |
else:
|
547 |
# 일정 확률로 새로운 목표를 생성 (매번 생성하지 않도록)
|
548 |
if random.random() < 0.1: # 10% 확률
|
549 |
-
print(f"[{self.name}] 새로운 목표를 고민하기 ���작합니다...")
|
550 |
self.planner.generate_goal()
|
551 |
|
552 |
def create_symbolic_memory(self):
|
553 |
"""
|
554 |
-
자신의
|
555 |
"""
|
556 |
-
from ..models.llm_helper import query_llm_with_prompt
|
557 |
-
|
558 |
long_term_memories = self.memory_store.long_term
|
559 |
if len(long_term_memories) < 10: # 충분한 기억이 쌓엿을 때만 생성
|
560 |
return
|
561 |
|
562 |
-
print(f"[{self.name}]이(가) 자신의 과거를 깊이 되돌아봅니다...")
|
563 |
-
|
564 |
# 상징 기억의 주제를 랜덤으로 선택
|
|
|
565 |
theme = random.choice([
|
566 |
"가장 행복했던 순간", "가장 후회되는 일", "나를 가장 화나게 했던 사건",
|
567 |
"나의 인생에서 가장 중요한 가르침", "최근 나를 가장 성장시킨 경험"
|
@@ -571,10 +437,10 @@ class NPC:
|
|
571 |
|
572 |
prompt = f"""
|
573 |
# 지시사항
|
574 |
-
|
575 |
이 기억들을 바탕으로, "{theme}"이라는 주제에 대해 당신의 삶을 대표하는 상징적인 의미나 깨달음을 한 문장의 독백으로 생성해주세요.
|
576 |
|
577 |
-
#
|
578 |
{memory_details}
|
579 |
|
580 |
→ "{theme}"에 대한 나의 생각
|
@@ -590,4 +456,4 @@ class NPC:
|
|
590 |
emotion = "rumination", # 반추, 성찰과 관련된 감정
|
591 |
memory_type="Symbolic",
|
592 |
)
|
593 |
-
print(f"[{self.name}]의 새로운
|
|
|
73 |
NPC 클래스
|
74 |
NPC에 대한 기본 정보를 객체화
|
75 |
"""
|
76 |
+
def __init__(self, name: str, job: str, personality: Optional[dict]=None):
|
77 |
self.name = name
|
78 |
self.job = job
|
|
|
|
|
|
|
79 |
|
80 |
# npc 기억
|
81 |
self.memory_store = MemoryStore()
|
|
|
84 |
self.personality = PersonalityManager(personality)
|
85 |
self.relationships = RelationshipManager(self)
|
86 |
self.emotion = EmotionManager(EMOTION_DECAY_RATE, self.personality)
|
|
|
87 |
# 행동 관리자
|
88 |
self.behavior = BehaviorManager()
|
89 |
self.planner = PlannerManager(self)
|
|
|
90 |
# 내부: float 감정 수치 관리용 버퍼
|
91 |
self._emotion_buffer = self.emotion.get_buffer()
|
92 |
+
|
93 |
# baseline 감정 값 부여
|
94 |
baseline_buffer = {}
|
95 |
for emo in self._emotion_buffer:
|
96 |
base_min = 0.05
|
97 |
base_max = 0.3
|
98 |
category = EMOTION_CATEGORY_MAP.get(emo, None)
|
99 |
+
bias_map = {
|
100 |
+
"core": "affect_bias", "social": "social_bias",
|
101 |
+
"cognitive": "cognitive_bias", "complex": "complex_bias"
|
102 |
+
}
|
103 |
+
bias = self.personality.get(bias_map.get(category, "sensitive"), 1.0)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
104 |
|
105 |
baseline_value = base_min + (base_max - base_min) * bias
|
106 |
baseline_value *= self.personality.get("sensitive", 1.0) # 전체 민감도 적용
|
107 |
|
108 |
baseline_buffer[emo] = round(baseline_value, 3)
|
|
|
109 |
|
110 |
# baseline buffer를 EmotionManager에 등록
|
111 |
self.emotion.set_baseline(baseline_buffer)
|
112 |
+
# emotion_buffer 초기화
|
113 |
+
self._emotion_buffer = self.emotion.get_buffer()
|
114 |
|
115 |
# 프로필 지정 → 기본은 "stable", 나중에 NPC마다 다르게 부여
|
116 |
profile_type = "stable"
|
117 |
profile = PERSONALITY_PROFILE[profile_type]
|
|
|
118 |
# Personality 변화 속도 개별 설정
|
119 |
self.personality_change_rate = {
|
120 |
"sensitive": random.uniform(*profile["sensitive"]),
|
|
|
123 |
}
|
124 |
|
125 |
# Personality baseline 저장 (초기값 복사)
|
126 |
+
self.personality_baseline = copy.deepcopy(self.personality.traits)
|
127 |
|
128 |
# 나이 추가 (0 ~ 80세 기본 랜덤 초기화 → 나중에 생성 시 age 지정 가능)
|
129 |
self.age = random.randint(0, 80)
|
|
|
132 |
self.personality_stage = None
|
133 |
self.update_personality_stage()
|
134 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
135 |
def generate_dialogue(self, user_input: str, time_context: str, target_npc: Optional["NPC"]=None, use_llm: bool=True) -> str:
|
136 |
"""
|
137 |
플레이어 또는 NPC와 상호작용 시 사용되는 통합 대사 생성 함수
|
|
|
146 |
if len(matched_memories) >= 3:
|
147 |
self.summarize_and_store_memories(matched_memories)
|
148 |
|
149 |
+
# 3. LLM 프롬프트 생성 및 호출
|
150 |
+
prompt = build_npc_prompt(self, user_input, matched_memories, time_context, target_npc)
|
|
|
|
|
151 |
npc_reply = query_llm_with_prompt(prompt)
|
152 |
|
153 |
+
# 4. 자신의 응답에서 감정 추론 및 상태 업데이트
|
154 |
emotion = query_llm_for_emotion(npc_reply)
|
155 |
if emotion and emotion in EMOTION_LIST:
|
156 |
self.update_emotion(emotion, strength=2.0)
|
157 |
|
158 |
+
# 5. 상호작용 기억 저장
|
159 |
interlocutor_name = target_npc.name if target_npc else "플레이어"
|
160 |
+
memory_content = f"[{interlocutor_name}의 말] '{user_input}' → [나의 응답] '{npc_reply}'"
|
161 |
+
memory = self.remember(content=memory_content, importance=7, emotion=emotion, context_tags=[time_context])
|
162 |
|
163 |
+
# 6. 관계 상태 업데이트
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
164 |
if emotion and emotion in EMOTION_RELATION_IMPACT:
|
165 |
self.relationships.update_relationship(interlocutor_name, emotion, strength=2.0, memory=memory)
|
|
|
166 |
# NPC간 상호작용일 경우, 상대방의 프로필도 업데이트 (상대방의 평가)
|
167 |
if target_npc:
|
168 |
target_emotion = query_llm_for_emotion(user_input) # 내가 한 말에 대한 상대의 감정 추론
|
169 |
if target_emotion and target_emotion in EMOTION_RELATION_IMPACT:
|
170 |
target_npc.relationships.update_relationship(self.name, target_emotion, strength=2.0, memory=memory)
|
171 |
|
172 |
+
# 7. 성격 변화 반영
|
173 |
self.update_personality()
|
|
|
174 |
return npc_reply
|
175 |
|
176 |
else:
|
|
|
238 |
self.emotion.decay_emotion()
|
239 |
self._emotion_buffer = self.emotion.get_buffer()
|
240 |
|
241 |
+
def recall_all_memories(self) -> List[str]:
|
242 |
"""
|
243 |
저장된 모든 기억(단기 + 장기)을 리스트로 반환
|
244 |
"""
|
245 |
return [m.content for m in self.memory_store.get_all_memories()]
|
246 |
|
247 |
+
def get_composite_emotion_state(self, top_n: int = 3) -> List[tuple]:
|
248 |
"""
|
249 |
+
현재 감정 상태 중 상위 N개를 반환
|
250 |
"""
|
251 |
buffer = self.emotion.get_buffer() # EmotionManager에서 최신 감정 상태 복사본 가져오기
|
252 |
sorted_emotions = sorted(buffer.items(), key=lambda x: x[1], reverse=True)
|
253 |
+
composite = [(emo,round(score, 2)) for emo, score in sorted_emotions[:top_n] if score > 0.1]
|
254 |
return composite # 예 [('fear', 2.0), ('anger'), 1.5]
|
255 |
|
256 |
+
def summarize_emotional_state(self) -> str:
|
257 |
"""
|
258 |
감정 상태 요약 텍스트 생성
|
259 |
"""
|
260 |
composite = self.get_composite_emotion_state()
|
261 |
dominant = self.emotion.get_dominant_emotion()
|
262 |
+
if not dominant:
|
263 |
+
return "현재 특별한 감정 없이 평온한 상태입니다."
|
264 |
+
return f"현재 {dominant} 등의 감정을 느끼고 있습니다.\n(주요 감정: {composite if composite else '없음'})"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
265 |
|
266 |
def get_relationship_description(self, target_name: str) -> str:
|
267 |
"""
|
268 |
+
특정 대상과의 관계를 자연어로 반환
|
269 |
"""
|
270 |
return self.relationships.get_relationship_summary(target_name)
|
271 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
272 |
|
273 |
def get_top_emotions(self, top_n=6):
|
274 |
"""
|
|
|
278 |
|
279 |
def update_personality(self):
|
280 |
"""
|
281 |
+
경험에 따라 성격을 점진적으로 변화시킴
|
282 |
- MemoryType 별 가중치 + EmotionInfluence 별 가중치 모두 반영
|
283 |
"""
|
284 |
recent_memories = [m for m in self.memory_store.get_recent_memories(limit=10) if m.importance >= 5]
|
|
|
285 |
if not recent_memories:
|
286 |
return
|
287 |
|
|
|
367 |
- 노년 NPC → 오래된 기억 더 강조
|
368 |
"""
|
369 |
if self.personality_stage in [
|
370 |
+
"infancy_and_toddlerhood", "early_childhood", "middle_childhood",
|
371 |
"adolescence", "young_adulthood"
|
372 |
]:
|
373 |
# 최근 기억 강조 → 오래된 기억 영향 약화
|
|
|
384 |
"""
|
385 |
기억 리스트를 요약하고, NPC 장기 기억으로 저장
|
386 |
"""
|
387 |
+
if not memories: return
|
|
|
|
|
388 |
summary = summarize_memories(memories)
|
389 |
+
if "[LLM Error]" in summary: return
|
390 |
+
self.remember(
|
391 |
+
Memory(content=f"[요약된 기억] summary",
|
392 |
+
importance=9,
|
393 |
+
emotion=self.emotion.get_dominant_emotion(),
|
394 |
+
memory_type="Summary")
|
|
|
395 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
396 |
|
397 |
def update_autonomous_behavior(self, time_context: str):
|
398 |
"""
|
|
|
416 |
else:
|
417 |
# 일정 확률로 새로운 목표를 생성 (매번 생성하지 않도록)
|
418 |
if random.random() < 0.1: # 10% 확률
|
|
|
419 |
self.planner.generate_goal()
|
420 |
|
421 |
def create_symbolic_memory(self):
|
422 |
"""
|
423 |
+
자신의 기억들을 바탕으로 상징적인 깨달음을 얻음
|
424 |
"""
|
|
|
|
|
425 |
long_term_memories = self.memory_store.long_term
|
426 |
if len(long_term_memories) < 10: # 충분한 기억이 쌓엿을 때만 생성
|
427 |
return
|
428 |
|
|
|
|
|
429 |
# 상징 기억의 주제를 랜덤으로 선택
|
430 |
+
# 수정 필요: 기억에 대한 주제가 랜덤으로 선택되는게 사람 답지 못함
|
431 |
theme = random.choice([
|
432 |
"가장 행복했던 순간", "가장 후회되는 일", "나를 가장 화나게 했던 사건",
|
433 |
"나의 인생에서 가장 중요한 가르침", "최근 나를 가장 성장시킨 경험"
|
|
|
437 |
|
438 |
prompt = f"""
|
439 |
# 지시사항
|
440 |
+
나는 '{self.name}'입니다. 다음은 나의 인생 기억 중 일부입니다.
|
441 |
이 기억들을 바탕으로, "{theme}"이라는 주제에 대해 당신의 삶을 대표하는 상징적인 의미나 깨달음을 한 문장의 독백으로 생성해주세요.
|
442 |
|
443 |
+
# 나의 기억들
|
444 |
{memory_details}
|
445 |
|
446 |
→ "{theme}"에 대한 나의 생각
|
|
|
456 |
emotion = "rumination", # 반추, 성찰과 관련된 감정
|
457 |
memory_type="Symbolic",
|
458 |
)
|
459 |
+
print(f"[{self.name}]의 새로운 깨달음: {symbolic_thought}")
|
npc_social_network/npc/npc_manager.py
CHANGED
@@ -23,17 +23,21 @@ class NPCManager:
|
|
23 |
"""
|
24 |
return self.npc_dict.get(name)
|
25 |
|
26 |
-
def get_random_npc(self, exclude: [
|
27 |
"""
|
28 |
특정 NPC를 제외하고 랜덤한 NPC를 선택
|
29 |
-
|
30 |
-
예) 근처에 산다, 특별한 이벤트가 있었다 등 과 같은 이유
|
31 |
"""
|
32 |
possible_targets = [n for n in self.npcs if n != exclude]
|
33 |
if not possible_targets:
|
34 |
return None
|
35 |
return random.choice(possible_targets)
|
36 |
|
|
|
|
|
|
|
|
|
37 |
def initiate_npc_to_npc_interaction(self, time_context: str):
|
38 |
"""
|
39 |
NPC 간의 상호작용을 시작시키는 함수
|
@@ -83,38 +87,3 @@ class NPCManager:
|
|
83 |
print(f"[{target.name}]: {response_utterance}")
|
84 |
print("-------------------------\n")
|
85 |
|
86 |
-
|
87 |
-
def move_all(self):
|
88 |
-
for npc in self.npcs:
|
89 |
-
npc.move()
|
90 |
-
|
91 |
-
def draw_all(self, screen, tile_size, image_loader):
|
92 |
-
for npc in self.npcs:
|
93 |
-
npc.draw(screen, tile_size, image_loader)
|
94 |
-
|
95 |
-
def get_npc_at(self, x, y):
|
96 |
-
for npc in self.npcs:
|
97 |
-
nx, ny = npc.get_position()
|
98 |
-
if nx == x and ny == y:
|
99 |
-
return npc
|
100 |
-
return None
|
101 |
-
|
102 |
-
def all(self):
|
103 |
-
return self.npcs
|
104 |
-
|
105 |
-
def npc_interactions(self):
|
106 |
-
"""
|
107 |
-
모든 NPC가 랜덤으로 다른 NPC 1명에게 상호작용 시도 (임시 함수)
|
108 |
-
"""
|
109 |
-
if len(self.npcs) < 2:
|
110 |
-
return # NPC가 2명 이상일 때만 진행
|
111 |
-
|
112 |
-
# npc_interactions 진입 확인용
|
113 |
-
print("[NPCManager] npc_interactions() 실행됨.", flush=True)
|
114 |
-
|
115 |
-
for npc in self.npcs:
|
116 |
-
possible_targets = [n for n in self.npcs if n != npc]
|
117 |
-
target = random.choice(possible_targets)
|
118 |
-
interaction_message = npc.interact_with_npc(target)
|
119 |
-
print(interaction_message)
|
120 |
-
|
|
|
23 |
"""
|
24 |
return self.npc_dict.get(name)
|
25 |
|
26 |
+
def get_random_npc(self, exclude: Optional['NPC']=None) -> Optional['NPC']:
|
27 |
"""
|
28 |
특정 NPC를 제외하고 랜덤한 NPC를 선택
|
29 |
+
# 수정 필요: 상대 NPC가 랜덤이 아니라, 상호작용할 만한 근거가 있어야한다.
|
30 |
+
# 예) 근처에 산다, 특별한 이벤트가 있었다 등 과 같은 이유
|
31 |
"""
|
32 |
possible_targets = [n for n in self.npcs if n != exclude]
|
33 |
if not possible_targets:
|
34 |
return None
|
35 |
return random.choice(possible_targets)
|
36 |
|
37 |
+
def all(self) -> List['NPC']:
|
38 |
+
"""관리 중인 모든 NPC의 리스트를 반환합니다."""
|
39 |
+
return self.npcs
|
40 |
+
|
41 |
def initiate_npc_to_npc_interaction(self, time_context: str):
|
42 |
"""
|
43 |
NPC 간의 상호작용을 시작시키는 함수
|
|
|
87 |
print(f"[{target.name}]: {response_utterance}")
|
88 |
print("-------------------------\n")
|
89 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
npc_social_network/scenarios/scenario_setup.py
CHANGED
@@ -15,23 +15,23 @@ def setup_initial_scenario(image_loader) -> NPCManager:
|
|
15 |
# --- 1. NPC 객체 생성 ---
|
16 |
# 엘린: 호기심 많고 감정적인 마법사
|
17 |
personality_elin = {"sensitive": 0.8, "stoic": 0.3, "cognitive_bias": 0.7}
|
18 |
-
elin = NPC("엘린", "마법사",
|
19 |
|
20 |
# 밥: 무뚝뚝하지만 정직한 대장장이
|
21 |
personality_bob = {"sensitive": 0.2, "stoic": 0.8, "cognitive_bias": 0.4}
|
22 |
-
bob = NPC("밥", "대장장이",
|
23 |
|
24 |
# 앨리스: 사교적이고 계산적인 상인
|
25 |
personality_alice = {"sensitive": 0.5, "stoic": 0.5, "cognitive_bias": 0.8}
|
26 |
-
alice = NPC("앨리스", "대장장이",
|
27 |
|
28 |
# 찰리: 성실하고 평화로운 농부
|
29 |
personality_charlie = {"sensitive": 0.6, "stoic": 0.6, "cognitive_bias": 0.3}
|
30 |
-
charlie = NPC("찰리", "농부",
|
31 |
|
32 |
# 다이애나: 조용하고 관찰력 있는 사서
|
33 |
personality_diana = {"sensitive": 0.7, "stoic": 0.7, "cognitive_bias": 0.9}
|
34 |
-
diana = NPC("다이애나", "사서",
|
35 |
|
36 |
# --- 2. 초기 기억 주입 ---
|
37 |
elin.remember(content="어젯밤 앨리스와 시장 가격 때문에 크게 다퉜다.", importance=8, emotion="anger")
|
|
|
15 |
# --- 1. NPC 객체 생성 ---
|
16 |
# 엘린: 호기심 많고 감정적인 마법사
|
17 |
personality_elin = {"sensitive": 0.8, "stoic": 0.3, "cognitive_bias": 0.7}
|
18 |
+
elin = NPC("엘린", "마법사", personality=personality_elin)
|
19 |
|
20 |
# 밥: 무뚝뚝하지만 정직한 대장장이
|
21 |
personality_bob = {"sensitive": 0.2, "stoic": 0.8, "cognitive_bias": 0.4}
|
22 |
+
bob = NPC("밥", "대장장이", personality=personality_bob)
|
23 |
|
24 |
# 앨리스: 사교적이고 계산적인 상인
|
25 |
personality_alice = {"sensitive": 0.5, "stoic": 0.5, "cognitive_bias": 0.8}
|
26 |
+
alice = NPC("앨리스", "대장장이", personality=personality_alice)
|
27 |
|
28 |
# 찰리: 성실하고 평화로운 농부
|
29 |
personality_charlie = {"sensitive": 0.6, "stoic": 0.6, "cognitive_bias": 0.3}
|
30 |
+
charlie = NPC("찰리", "농부", personality=personality_charlie)
|
31 |
|
32 |
# 다이애나: 조용하고 관찰력 있는 사서
|
33 |
personality_diana = {"sensitive": 0.7, "stoic": 0.7, "cognitive_bias": 0.9}
|
34 |
+
diana = NPC("다이애나", "사서", personality=personality_diana)
|
35 |
|
36 |
# --- 2. 초기 기억 주입 ---
|
37 |
elin.remember(content="어젯밤 앨리스와 시장 가격 때문에 크게 다퉜다.", importance=8, emotion="anger")
|
test.ipynb
CHANGED
@@ -2882,380 +2882,6 @@
|
|
2882 |
"# GPU 지원 버전으로 다시 설치\n",
|
2883 |
"!pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118"
|
2884 |
]
|
2885 |
-
},
|
2886 |
-
{
|
2887 |
-
"cell_type": "code",
|
2888 |
-
"execution_count": 1,
|
2889 |
-
"id": "8140bcea",
|
2890 |
-
"metadata": {},
|
2891 |
-
"outputs": [
|
2892 |
-
{
|
2893 |
-
"name": "stdout",
|
2894 |
-
"output_type": "stream",
|
2895 |
-
"text": [
|
2896 |
-
"pygame 2.6.1 (SDL 2.28.4, Python 3.11.11)\n",
|
2897 |
-
"Hello from the pygame community. https://www.pygame.org/contribute.html\n",
|
2898 |
-
"\n",
|
2899 |
-
"👤 Elin (farmer)\n",
|
2900 |
-
" - 위치: (5, 4)\n",
|
2901 |
-
" - 대화: 오늘은 밭에 나갈 날이군요!\n",
|
2902 |
-
" - 기억: []\n",
|
2903 |
-
"아침 - Elin이(가) market에 도착했습니다.\n"
|
2904 |
-
]
|
2905 |
-
},
|
2906 |
-
{
|
2907 |
-
"ename": "SystemExit",
|
2908 |
-
"evalue": "",
|
2909 |
-
"output_type": "error",
|
2910 |
-
"traceback": [
|
2911 |
-
"An exception has occurred, use %tb to see the full traceback.\n",
|
2912 |
-
"\u001b[31mSystemExit\u001b[39m\n"
|
2913 |
-
]
|
2914 |
-
},
|
2915 |
-
{
|
2916 |
-
"name": "stderr",
|
2917 |
-
"output_type": "stream",
|
2918 |
-
"text": [
|
2919 |
-
"C:\\Users\\human\\AppData\\Roaming\\Python\\Python311\\site-packages\\IPython\\core\\interactiveshell.py:3675: UserWarning: To exit: use 'exit', 'quit', or Ctrl-D.\n",
|
2920 |
-
" warn(\"To exit: use 'exit', 'quit', or Ctrl-D.\", stacklevel=1)\n"
|
2921 |
-
]
|
2922 |
-
}
|
2923 |
-
],
|
2924 |
-
"source": [
|
2925 |
-
"# pygame simulator 실행기 (임시)\n",
|
2926 |
-
"\n",
|
2927 |
-
"# 1. 환경 변수 설정 (모듈 인식 위해)\n",
|
2928 |
-
"import sys\n",
|
2929 |
-
"import os\n",
|
2930 |
-
"sys.path.append(os.path.abspath(\".\")) # 최상위 경로를 sys.path에 추가\n",
|
2931 |
-
"\n",
|
2932 |
-
"# 2. town_hall 직접 import 후 실행\n",
|
2933 |
-
"from npc_social_network.maps.villages import town_hall\n",
|
2934 |
-
"\n",
|
2935 |
-
"# town_hall.py는 __main__이 아니라 직접 실행되므로"
|
2936 |
-
]
|
2937 |
-
},
|
2938 |
-
{
|
2939 |
-
"cell_type": "code",
|
2940 |
-
"execution_count": 2,
|
2941 |
-
"id": "ac67ce84",
|
2942 |
-
"metadata": {},
|
2943 |
-
"outputs": [
|
2944 |
-
{
|
2945 |
-
"name": "stdout",
|
2946 |
-
"output_type": "stream",
|
2947 |
-
"text": [
|
2948 |
-
"초기 감정 상태:\n",
|
2949 |
-
"[('fear', 2.2), ('anger', 1.5), ('pride', 1.2)]\n",
|
2950 |
-
"감정 평균 강도: 1.63 / 대표 감정: fear(2.2), anger(1.5), pride(1.2)\n",
|
2951 |
-
"\n",
|
2952 |
-
"[1회 decay 후]\n",
|
2953 |
-
"{'core_positive': {'joy': 0.0, 'satisfaction': 0.0, 'gratitude': 0.0, 'calm': 0.0, 'anticipation': 0.0, 'pride': 0.19999999999999996, 'connectedness': 0.0}, 'core_negative': {'sadness': 0.0, 'anger': 1.0, 'anxiety': 0.0, 'disgust': 0.0, 'fear': 1.6, 'regret': 0.0, 'frustration': 0.0}, 'social_emotion': {'jealousy': 0.0, 'shame': 0.0, 'guilt': 0.0, 'compassion': 0.0, 'awe': 0.0, 'empathy': 0.0}, 'cognitive_state': {'confusion': 0.0, 'nervousness': 0.0, 'apathy': 0.0, 'excitement': 0.0, 'immersion': 0.0, 'skepticism': 0.0}}\n",
|
2954 |
-
"[('fear', 1.6), ('anger', 1.0), ('pride', 0.2)]\n",
|
2955 |
-
"감정 평균 강도: 0.93 / 대표 감정: fear(1.6), anger(1.0), pride(0.2)\n",
|
2956 |
-
"\n",
|
2957 |
-
"[2회 decay 후]\n",
|
2958 |
-
"{'core_positive': {'joy': 0.0, 'satisfaction': 0.0, 'gratitude': 0.0, 'calm': 0.0, 'anticipation': 0.0, 'pride': 0.0, 'connectedness': 0.0}, 'core_negative': {'sadness': 0.0, 'anger': 0.5, 'anxiety': 0.0, 'disgust': 0.0, 'fear': 1.0, 'regret': 0.0, 'frustration': 0.0}, 'social_emotion': {'jealousy': 0.0, 'shame': 0.0, 'guilt': 0.0, 'compassion': 0.0, 'awe': 0.0, 'empathy': 0.0}, 'cognitive_state': {'confusion': 0.0, 'nervousness': 0.0, 'apathy': 0.0, 'excitement': 0.0, 'immersion': 0.0, 'skepticism': 0.0}}\n",
|
2959 |
-
"[('fear', 1.0), ('anger', 0.5)]\n",
|
2960 |
-
"감정 평균 강도: 0.75 / 대표 감정: fear(1.0), anger(0.5)\n",
|
2961 |
-
"\n",
|
2962 |
-
"[3회 decay 후]\n",
|
2963 |
-
"{'core_positive': {'joy': 0.0, 'satisfaction': 0.0, 'gratitude': 0.0, 'calm': 0.0, 'anticipation': 0.0, 'pride': 0.0, 'connectedness': 0.0}, 'core_negative': {'sadness': 0.0, 'anger': 0.0, 'anxiety': 0.0, 'disgust': 0.0, 'fear': 0.4, 'regret': 0.0, 'frustration': 0.0}, 'social_emotion': {'jealousy': 0.0, 'shame': 0.0, 'guilt': 0.0, 'compassion': 0.0, 'awe': 0.0, 'empathy': 0.0}, 'cognitive_state': {'confusion': 0.0, 'nervousness': 0.0, 'apathy': 0.0, 'excitement': 0.0, 'immersion': 0.0, 'skepticism': 0.0}}\n",
|
2964 |
-
"[('fear', 0.4)]\n",
|
2965 |
-
"감정 평균 강도: 0.4 / 대표 감정: fear(0.4)\n",
|
2966 |
-
"\n",
|
2967 |
-
"[4회 decay 후]\n",
|
2968 |
-
"{'core_positive': {'joy': 0.0, 'satisfaction': 0.0, 'gratitude': 0.0, 'calm': 0.0, 'anticipation': 0.0, 'pride': 0.0, 'connectedness': 0.0}, 'core_negative': {'sadness': 0.0, 'anger': 0.0, 'anxiety': 0.0, 'disgust': 0.0, 'fear': 0.0, 'regret': 0.0, 'frustration': 0.0}, 'social_emotion': {'jealousy': 0.0, 'shame': 0.0, 'guilt': 0.0, 'compassion': 0.0, 'awe': 0.0, 'empathy': 0.0}, 'cognitive_state': {'confusion': 0.0, 'nervousness': 0.0, 'apathy': 0.0, 'excitement': 0.0, 'immersion': 0.0, 'skepticism': 0.0}}\n",
|
2969 |
-
"[]\n",
|
2970 |
-
"감정 평균 강도: 0.0 / 대표 감정: 없음\n",
|
2971 |
-
"\n",
|
2972 |
-
"[5회 decay 후]\n",
|
2973 |
-
"{'core_positive': {'joy': 0.0, 'satisfaction': 0.0, 'gratitude': 0.0, 'calm': 0.0, 'anticipation': 0.0, 'pride': 0.0, 'connectedness': 0.0}, 'core_negative': {'sadness': 0.0, 'anger': 0.0, 'anxiety': 0.0, 'disgust': 0.0, 'fear': 0.0, 'regret': 0.0, 'frustration': 0.0}, 'social_emotion': {'jealousy': 0.0, 'shame': 0.0, 'guilt': 0.0, 'compassion': 0.0, 'awe': 0.0, 'empathy': 0.0}, 'cognitive_state': {'confusion': 0.0, 'nervousness': 0.0, 'apathy': 0.0, 'excitement': 0.0, 'immersion': 0.0, 'skepticism': 0.0}}\n",
|
2974 |
-
"[]\n",
|
2975 |
-
"감정 평균 강도: 0.0 / 대표 감정: 없음\n"
|
2976 |
-
]
|
2977 |
-
}
|
2978 |
-
],
|
2979 |
-
"source": [
|
2980 |
-
"# 1. 경로 추가\n",
|
2981 |
-
"import sys, os\n",
|
2982 |
-
"sys.path.append(os.path.abspath(\".\")) # 프로젝트 루트 경로\n",
|
2983 |
-
"\n",
|
2984 |
-
"# 2. 클래스 import\n",
|
2985 |
-
"from npc_social_network.npc.npc_base import NPC\n",
|
2986 |
-
"\n",
|
2987 |
-
"# 3. NPC 생성 및 감정 입력\n",
|
2988 |
-
"npc = NPC(name=\"아린\", job=\"farmer\", path=[(0,0)], image=None)\n",
|
2989 |
-
"npc.remember(\"플레이어가 모욕함\", importance=6, emotion=\"anger\", strength=1.5)\n",
|
2990 |
-
"npc.remember(\"무서운 소리를 들음\", importance=5, emotion=\"fear\", strength=2.2)\n",
|
2991 |
-
"npc.remember(\"친구가 칭찬함\", importance=5, emotion=\"pride\", strength=1.2)\n",
|
2992 |
-
"\n",
|
2993 |
-
"print(\"초기 감정 상태:\")\n",
|
2994 |
-
"print(npc.get_composite_emotion_state()) # [('fear', 2.0), ('anger', 1.5), ('pride', 1.2)]\n",
|
2995 |
-
"print(npc.summarize_emotional_state()) # 감정 평균 강도: 1.57 / 대표 감정: fear(2.0), anger(1.5), pride(1.2)\n",
|
2996 |
-
"\n",
|
2997 |
-
"# 4. decay 반복 적용\n",
|
2998 |
-
"for step in range(1, 6):\n",
|
2999 |
-
" npc.decay_emotion()\n",
|
3000 |
-
" print(f\"\\n[{step}회 decay 후]\")\n",
|
3001 |
-
" print(npc.get_current_emotion_state())\n",
|
3002 |
-
" print(npc.get_composite_emotion_state())\n",
|
3003 |
-
" print(npc.summarize_emotional_state())"
|
3004 |
-
]
|
3005 |
-
},
|
3006 |
-
{
|
3007 |
-
"cell_type": "code",
|
3008 |
-
"execution_count": 1,
|
3009 |
-
"id": "cc6f2948",
|
3010 |
-
"metadata": {},
|
3011 |
-
"outputs": [
|
3012 |
-
{
|
3013 |
-
"name": "stdout",
|
3014 |
-
"output_type": "stream",
|
3015 |
-
"text": [
|
3016 |
-
"감정 평균 강도: 0.96 / 대표 감정: fear(0.96)\n"
|
3017 |
-
]
|
3018 |
-
}
|
3019 |
-
],
|
3020 |
-
"source": [
|
3021 |
-
"from npc_social_network.npc.npc_base import NPC\n",
|
3022 |
-
"\n",
|
3023 |
-
"# 민감하고 부정에 취약한 캐릭터\n",
|
3024 |
-
"custom_personality = {\n",
|
3025 |
-
" \"sensitive\": 1.0,\n",
|
3026 |
-
" \"negative_bias\": 1.5,\n",
|
3027 |
-
" \"stoic\": 0.1\n",
|
3028 |
-
"}\n",
|
3029 |
-
"\n",
|
3030 |
-
"npc = NPC(\"아린\", \"farmer\", [(0, 0)], None, personality=custom_personality)\n",
|
3031 |
-
"npc.remember(\"무서운 소리를 들음\", emotion=\"fear\", importance=5)\n",
|
3032 |
-
"npc.decay_emotion()\n",
|
3033 |
-
"print(npc.summarize_emotional_state())"
|
3034 |
-
]
|
3035 |
-
},
|
3036 |
-
{
|
3037 |
-
"cell_type": "code",
|
3038 |
-
"execution_count": 2,
|
3039 |
-
"id": "9638a156",
|
3040 |
-
"metadata": {},
|
3041 |
-
"outputs": [
|
3042 |
-
{
|
3043 |
-
"name": "stdout",
|
3044 |
-
"output_type": "stream",
|
3045 |
-
"text": [
|
3046 |
-
"로다와의 관계 점수: 7.0\n",
|
3047 |
-
"로다와의 관계 설명: 호감 있음\n"
|
3048 |
-
]
|
3049 |
-
}
|
3050 |
-
],
|
3051 |
-
"source": [
|
3052 |
-
"# 7단계 NPC 관계 시스템 테스트\n",
|
3053 |
-
"from npc_social_network.npc.npc_base import NPC\n",
|
3054 |
-
"\n",
|
3055 |
-
"npc1 = NPC(name=\"아린\", job=\"farmer\", path=[(0,0)], image=None)\n",
|
3056 |
-
"npc2 = NPC(name=\"로다\", job=\"blacksmith\", path=[(0,1)], image=None)\n",
|
3057 |
-
"\n",
|
3058 |
-
"npc1.interact_with(\"로다\", emotion=\"joy\", positive=True)\n",
|
3059 |
-
"npc1.interact_with(\"로다\", emotion=\"anger\", positive=True)\n",
|
3060 |
-
"\n",
|
3061 |
-
"print(\"로다와의 관계 점수:\", npc1.relationships.get_relationship(\"로다\"))\n",
|
3062 |
-
"print(\"로다와의 관계 설명:\", npc1.get_relationship_description(\"로다\"))"
|
3063 |
-
]
|
3064 |
-
},
|
3065 |
-
{
|
3066 |
-
"cell_type": "code",
|
3067 |
-
"execution_count": 3,
|
3068 |
-
"id": "53c994dd",
|
3069 |
-
"metadata": {},
|
3070 |
-
"outputs": [
|
3071 |
-
{
|
3072 |
-
"name": "stdout",
|
3073 |
-
"output_type": "stream",
|
3074 |
-
"text": [
|
3075 |
-
"로다와의 관계 점수: 9.5\n",
|
3076 |
-
"로다와의 관계 설명: 호감 있음\n"
|
3077 |
-
]
|
3078 |
-
}
|
3079 |
-
],
|
3080 |
-
"source": [
|
3081 |
-
"# 8단계 관계 + 감정 + 기억 연동\n",
|
3082 |
-
"from npc_social_network.npc.npc_base import NPC\n",
|
3083 |
-
"\n",
|
3084 |
-
"npc1 = NPC(name=\"아린\", job=\"farmer\", path=[(0,0)], image=None)\n",
|
3085 |
-
"\n",
|
3086 |
-
"# 중요 기억 등록\n",
|
3087 |
-
"npc1.remember(\"로다와 좋은 시간을 보냄\", importance=8, emotion=\"joy\")\n",
|
3088 |
-
"npc1.remember(\"로다에게 도움을 받음\", importance=9, emotion=\"gratitude\")\n",
|
3089 |
-
"npc1.remember(\"로다에게 실망함\", importance=7, emotion=\"anger\")\n",
|
3090 |
-
"\n",
|
3091 |
-
"# 현재 감정 입력\n",
|
3092 |
-
"npc1.remember(\"최근 로다와 유대감 형성\", importance=6, emotion=\"connectedness\")\n",
|
3093 |
-
"\n",
|
3094 |
-
"# 관계 반영\n",
|
3095 |
-
"npc1.reflect_memory_on_relationship(\"로다\")\n",
|
3096 |
-
"\n",
|
3097 |
-
"# 결과 출력\n",
|
3098 |
-
"print(\"로다와의 관계 점수:\", npc1.relationships.get_relationship(\"로다\"))\n",
|
3099 |
-
"print(\"로다와의 관계 설명:\", npc1.get_relationship_description(\"로다\"))"
|
3100 |
-
]
|
3101 |
-
},
|
3102 |
-
{
|
3103 |
-
"cell_type": "code",
|
3104 |
-
"execution_count": 1,
|
3105 |
-
"id": "69387020",
|
3106 |
-
"metadata": {},
|
3107 |
-
"outputs": [
|
3108 |
-
{
|
3109 |
-
"name": "stdout",
|
3110 |
-
"output_type": "stream",
|
3111 |
-
"text": [
|
3112 |
-
"[('sadness', 1.44), ('joy', 0.96), ('gratitude', 0.72)]\n",
|
3113 |
-
"아린 행동 시퀀스: \n",
|
3114 |
-
"(1.4) 슬퍼서 주저앉습니다....\n",
|
3115 |
-
"(1.0) 기뻐서 깡충 뜁니다.\n",
|
3116 |
-
"(0.7) 감사한 마음을 표현합니다.\n",
|
3117 |
-
"밭일도 해야 하겠군요.\n"
|
3118 |
-
]
|
3119 |
-
}
|
3120 |
-
],
|
3121 |
-
"source": [
|
3122 |
-
"# 9단계 복합 감정 - 행동 시퀀스 구성\n",
|
3123 |
-
"from npc_social_network.npc.npc_base import NPC\n",
|
3124 |
-
"\n",
|
3125 |
-
"npc = NPC(name=\"아린\", job=\"farmer\", path=[(0,0)], image=None)\n",
|
3126 |
-
"\n",
|
3127 |
-
"# 여러 감정 기억 등록\n",
|
3128 |
-
"npc.remember(\"칭찬받아 기쁨\", importance=7, emotion=\"joy\", strength=2.0)\n",
|
3129 |
-
"npc.remember(\"동료가 위로함\", importance=8, emotion=\"gratitude\", strength=1.5)\n",
|
3130 |
-
"npc.remember(\"실망스러운 일 겪음\", importance=8, emotion=\"sadness\", strength=1.8)\n",
|
3131 |
-
"print(npc.get_composite_emotion_state())\n",
|
3132 |
-
"\n",
|
3133 |
-
"# 복합 감정 기반 행동 시퀀스 출력\n",
|
3134 |
-
"print(npc.generate_dialogue())"
|
3135 |
-
]
|
3136 |
-
},
|
3137 |
-
{
|
3138 |
-
"cell_type": "code",
|
3139 |
-
"execution_count": 2,
|
3140 |
-
"id": "246a140b",
|
3141 |
-
"metadata": {},
|
3142 |
-
"outputs": [
|
3143 |
-
{
|
3144 |
-
"name": "stdout",
|
3145 |
-
"output_type": "stream",
|
3146 |
-
"text": [
|
3147 |
-
"\n",
|
3148 |
-
"[플레이어 입력] 오늘 하루는 최악이었어. 왜 다들 날 무시하는 거지?\n",
|
3149 |
-
"[요약 실패] LLM 호출 중 에러가 발생하여 요약 기억을 저장하지 않습니다. 에러: [LLM Error] 500 An internal error has occurred. Please retry or report in https://developers.generativeai.google/guide/troubleshooting\n",
|
3150 |
-
"[Personality Update] 엘라 → sensitive: 1.00, stoic: 0.00, cognitive_bias: 1.00\n",
|
3151 |
-
"[NPC 응답] ...어... 그랬어? 하루가 최악이었다니... 정말 힘들었겠구나.\n",
|
3152 |
-
"\n",
|
3153 |
-
"(잠깐 침묵하며 네 얼굴을 가만히 바라봐.)\n",
|
3154 |
-
"\n",
|
3155 |
-
"무시당하는 기분이라는 게... 얼마나 사람을 서글프게 만드는지, 나도... 조금은 알 것 같아. 마음이 많이 아팠겠네.\n",
|
3156 |
-
"\n",
|
3157 |
-
"무슨 일이 있었던 거야? 괜찮다면... 나에게 이야기해 줄 수 있을까? 네 이야기를 들으면... 조금이라도 마음이 편해질지도 모르잖아.\n",
|
3158 |
-
"\n",
|
3159 |
-
"[플레이어 입력] 고마워. 나 도와줘서 정말 감사했어.\n",
|
3160 |
-
"[요약 기억 저장됨] 이 사람은 과거에 무시당하는 느낌을 경험했던 것으로 보이며, 최근에는 찰리와 농사를 짓고 밥에게 선물을 받는 등 사람들과 교류하며 다양한 경험을 했고 특히 어제 앨리스와 심하게 다투기도 했습니다.\n",
|
3161 |
-
"[Personality Update] 엘라 → sensitive: 1.00, stoic: 0.00, cognitive_bias: 1.00\n",
|
3162 |
-
"[NPC 응답] \"...아, 고마워. (조금 생각하는 듯) 음... 아니야. 나야말로... 음, 그냥 도울 수 있어서 좋았어. 플레이어 네가 그렇게 고마워해주니... 나도 기쁘네. (아주 살짝 미소) 별거 아니었는데, 그렇게 말해주니 참... 좋네.\"\n",
|
3163 |
-
"\n",
|
3164 |
-
"[플레이어 입력] 그때 화낸 건 미안해. 내 잘못이었어.\n",
|
3165 |
-
"[요약 기억 저장됨] 제시된 정보를 종합해 보면, 이 사람은 최근 찰리와 함께 농사를 짓거나 밥에게 검을 선물받는 등 다양한 사람들과 교류하며 여러 경험을 했습니다. 특히 어제는 앨리스와 심하게 다투기도 했습니다.\n",
|
3166 |
-
"[Personality Update] 엘라 → sensitive: 1.00, stoic: 0.00, cognitive_bias: 1.00\n",
|
3167 |
-
"[NPC 응답] 아, 아니에요. 괜찮아요.\n",
|
3168 |
-
"\n",
|
3169 |
-
"그렇게 말해주니 오히려 마음이 좀 편해지네요. 저도 그때 조금... 신경 쓰였거든요.\n",
|
3170 |
-
"\n",
|
3171 |
-
"그래도 이렇게 먼저 말해줘서 고마워요. 이제 정말 괜찮아요.\n",
|
3172 |
-
"\n",
|
3173 |
-
"[플레이어 입력] 내가 선물한 책 어땠어?\n",
|
3174 |
-
"[요약 기억 저장됨] 이 사람은 최근 찰리와 함께 농사를 짓고 밥에게 선물을 받는 등 다양한 사람들과 교류했으며, 어제는 앨리스와 심하게 다투는 큰 갈등을 겪었습니다.\n",
|
3175 |
-
"[Personality Update] 엘라 → sensitive: 1.00, stoic: 0.00, cognitive_bias: 1.00\n",
|
3176 |
-
"[NPC 응답] 아, 그 책! 정말... 정말 좋았어! 네가 나에게 딱 맞는 책을 선물해줬구나 싶어서 읽는 내내 기뻤어.\n",
|
3177 |
-
"\n",
|
3178 |
-
"특히 거기에 담긴 마법 이론에 대한 새로운 접근 방식이... 내 생각을 정말 많이 자극했어. 평소에 내가 고민하던 부분에 대한 실마리를 찾은 것 같기도 하고 말이야.\n",
|
3179 |
-
"\n",
|
3180 |
-
"네 덕분에 정말 즐거운 시간을 보낼 수 있었어. 고마워! 요즘 이것저것 신경 쓸 일이 좀 있었는데, 책 읽는 동안은 다 잊고 몰입할 수 있었어. 네 덕분이야.\n",
|
3181 |
-
"\n",
|
3182 |
-
"혹시 너도 그 책 읽어봤어? 어떤 부분이 가장 흥미로웠는지 궁금하네.\n",
|
3183 |
-
"\n",
|
3184 |
-
"[플레이어 ��력] 우리 예전에 함께 일했던 거 기억나?\n",
|
3185 |
-
"[요약 실패] LLM 호출 중 에러가 발생하여 요약 기억을 저장하지 않습니다. 에러: [LLM Error] 500 An internal error has occurred. Please retry or report in https://developers.generativeai.google/guide/troubleshooting\n",
|
3186 |
-
"[Personality Update] 엘라 → sensitive: 1.00, stoic: 0.00, cognitive_bias: 1.00\n",
|
3187 |
-
"[NPC 응답] 우리 예전에 함께 일했던 거? 어머, 당연히 기억하지! 😊\n",
|
3188 |
-
"\n",
|
3189 |
-
"그때 생각하면... 왠지 모르게 기분이 좋아져. 함께 했던 시간들은... 나에게는 좋은 기억으로 남아있거든. 왜 갑자기 그 생각이 났어?\n",
|
3190 |
-
" - 기억 '[입력] '고마워. 나 도와줘서 정말...' (감정: joy, 중요도: 7) -> 관계에 1.4만큼의 강도로 영향을 줍니다.\n",
|
3191 |
-
"[관계 업데이트 완료] 플레이어와의 최종 관계 점수: 12.1\n",
|
3192 |
-
"\n",
|
3193 |
-
"🧠 현재 감정 상태 요약:\n",
|
3194 |
-
"감정 평균 강도: joy / 대표 감정: [('joy', 3.0), ('interest', 3.0), ('relief', 3.0)]\n",
|
3195 |
-
"\n",
|
3196 |
-
"📚 최근 기억:\n",
|
3197 |
-
"- [입력] '우리 예전에 함께 일했던 거 기억나?' → [응답] '우리 예전에 함께 일했던 거? 어머, 당연히 기억하지! 😊\n",
|
3198 |
-
"\n",
|
3199 |
-
"그때 생각하면... 왠지 모르게 기분이 좋아져. 함께 했던 시간들은... 나에게는 좋은 기억으로 남아있거든. 왜 갑자기 그 생각이 났어?' (nostalgia)\n",
|
3200 |
-
"- [입력] '내가 선물한 책 어땠어?' → [응답] '아, 그 책! 정말... 정말 좋았어! 네가 나에게 딱 맞는 책을 선물해줬구나 싶어서 읽는 내내 기뻤어.\n",
|
3201 |
-
"\n",
|
3202 |
-
"특히 거기에 담긴 마법 이론에 대한 새로운 접근 방식이... 내 생각을 정말 많이 자극했어. 평소에 내가 고민하던 부분에 대한 실마리를 찾은 것 같기도 하고 말이야.\n",
|
3203 |
-
"\n",
|
3204 |
-
"네 덕분에 정말 즐거운 시간을 보낼 수 있었어. 고마워! 요즘 이것저것 신경 쓸 일이 좀 있었는데, 책 읽는 동안은 다 잊고 몰입할 수 있었어. 네 덕분이야.\n",
|
3205 |
-
"\n",
|
3206 |
-
"혹시 너도 그 책 읽어봤어? 어떤 부분이 가장 흥미로웠는지 궁금하네.' (gratitude)\n",
|
3207 |
-
"- 이 사람은 최근 찰리와 함께 농사를 짓고 밥에게 선물을 받는 등 다양한 사람들과 교류했으며, 어제는 앨리스와 심하게 다투는 큰 갈등을 겪었습니다. (joy)\n",
|
3208 |
-
"- [입력] '그때 화낸 건 미안해. 내 잘못이었어.' → [응답] '아, 아니에요. 괜찮아요.\n",
|
3209 |
-
"\n",
|
3210 |
-
"그렇게 말해주니 오히려 마음이 좀 편해지네요. 저도 그때 조금... 신경 쓰였거든요.\n",
|
3211 |
-
"\n",
|
3212 |
-
"그래도 이렇게 먼저 말해줘서 고마워요. 이제 정말 괜찮아요.' (relief)\n",
|
3213 |
-
"- 제시된 정보를 종합해 보면, 이 사람은 최근 찰리와 함께 농사를 짓거나 밥에게 검을 선물받는 등 다양한 사람들과 교류하며 여러 경험을 했습니다. 특히 어제는 앨리스와 심하게 다투기도 했습니다. (joy)\n",
|
3214 |
-
"\n",
|
3215 |
-
"🔗 플레이어와의 관계:\n",
|
3216 |
-
"호감 있음\n"
|
3217 |
-
]
|
3218 |
-
}
|
3219 |
-
],
|
3220 |
-
"source": [
|
3221 |
-
"# portfolio/test.ipynb\n",
|
3222 |
-
"\n",
|
3223 |
-
"import time\n",
|
3224 |
-
"from npc_social_network.routes.npc_route import npc_manager\n",
|
3225 |
-
"\n",
|
3226 |
-
"def run_npc_test_scenario(npc_name=\"엘라\"):\n",
|
3227 |
-
" npc = npc_manager.get_npc_by_name(npc_name)\n",
|
3228 |
-
"\n",
|
3229 |
-
" test_inputs = [\n",
|
3230 |
-
" \"오늘 하루는 최악이었어. 왜 다들 날 무시하는 거지?\",\n",
|
3231 |
-
" \"고마워. 나 도와줘서 정말 감사했어.\",\n",
|
3232 |
-
" \"그때 화낸 건 미안해. 내 잘못이었어.\",\n",
|
3233 |
-
" \"내가 선물한 책 어땠어?\",\n",
|
3234 |
-
" \"우리 예전에 함께 일했던 거 기억나?\",\n",
|
3235 |
-
" ]\n",
|
3236 |
-
"\n",
|
3237 |
-
" for input_text in test_inputs:\n",
|
3238 |
-
" print(f\"\\n[플레이어 입력] {input_text}\")\n",
|
3239 |
-
" response = npc.generate_dialogue(input_text)\n",
|
3240 |
-
" print(f\"[NPC 응답] {response}\")\n",
|
3241 |
-
"\n",
|
3242 |
-
" # 기억 기반 관계 반영까지 테스트\n",
|
3243 |
-
" npc.reflect_memory_emotions_on_relationship(\"플레이어\")\n",
|
3244 |
-
"\n",
|
3245 |
-
" # 상태 요약 출력\n",
|
3246 |
-
" print(\"\\n🧠 현재 감정 상태 요약:\")\n",
|
3247 |
-
" print(npc.summarize_emotional_state())\n",
|
3248 |
-
"\n",
|
3249 |
-
" print(\"\\n📚 최근 기억:\")\n",
|
3250 |
-
" for m in npc.memory_store.get_recent_memories(limit=5):\n",
|
3251 |
-
" print(f\"- {m.content} ({m.emotion})\")\n",
|
3252 |
-
"\n",
|
3253 |
-
" print(\"\\n🔗 플레이어와의 관계:\")\n",
|
3254 |
-
" print(npc.get_relationship_description(\"플레이어\"))\n",
|
3255 |
-
"\n",
|
3256 |
-
"\n",
|
3257 |
-
"run_npc_test_scenario()"
|
3258 |
-
]
|
3259 |
}
|
3260 |
],
|
3261 |
"metadata": {
|
|
|
2882 |
"# GPU 지원 버전으로 다시 설치\n",
|
2883 |
"!pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118"
|
2884 |
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2885 |
}
|
2886 |
],
|
2887 |
"metadata": {
|