humanda5 commited on
Commit
cf1ebab
·
1 Parent(s): a88e1d8

몸(행동)과 관련된 내용 제거한 리팩토링 시작

Browse files
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/map/manager/simulation_manager.py
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, path, image_identifier, personality=None):
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
- bias = 1.0
104
-
105
- if category == "core":
106
- bias = self.personality.get("affect_bias", 1.0)
107
- elif category == "social":
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
- # 5. 감정 추론
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
- # 상호작용의 화자(interlocutor)를 명확히
193
  interlocutor_name = target_npc.name if target_npc else "플레이어"
 
 
194
 
195
- # 6. Memory 저장
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
- # 8. 성격 변화 반영
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 recall(self):
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
- 복합 감정 계산 (상위 N 감정 반환)
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
- return f"감정 평균 강도: {dominant} / 대표 감정: {composite if composite else '없음' }"
313
-
314
- def _get_emotion_influence(self, emotion: str, positive: bool) -> float:
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
- 관계 점수 대신 SocialProfile의 요약을 반환하도록 수정
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
- Memory / Emotion 기반으로 Personality 변화 적용
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", "early_choldhood", "middle_childhood",
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
- if "[LLM Error]" in summary:
487
- print(f"[요약 실패] LLM 호출 중 에러가 발생하여 요약 기억을 저장하지 않습니다. 에러: {summary}")
488
- return
489
-
490
- self.memory_store.add_memory(
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
- 당신은 '{self.name}'입니다. 다음은 당신의 인생 기억 중 일부입니다.
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}]의 새로운 깨달음 {symbolic_thought}")
 
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: ["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 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("엘린", "마법사", [[2,3], [3,3], [4,3], [5,3], [5,4], [5,5]], ("npc", "default"), personality=personality_elin)
19
 
20
  # 밥: 무뚝뚝하지만 정직한 대장장이
21
  personality_bob = {"sensitive": 0.2, "stoic": 0.8, "cognitive_bias": 0.4}
22
- bob = NPC("밥", "대장장이", [[1,1], [1,2], [2,2], [3,2], [3,1]], ("npc", "default"), personality=personality_bob)
23
 
24
  # 앨리스: 사교적이고 계산적인 상인
25
  personality_alice = {"sensitive": 0.5, "stoic": 0.5, "cognitive_bias": 0.8}
26
- alice = NPC("앨리스", "대장장이", [[8,8], [9,8], [9,9], [8,9]], ("npc", "default"), personality=personality_alice)
27
 
28
  # 찰리: 성실하고 평화로운 농부
29
  personality_charlie = {"sensitive": 0.6, "stoic": 0.6, "cognitive_bias": 0.3}
30
- charlie = NPC("찰리", "농부", [[10,3], [11,3], [12,3], [12,4]], ("npc", "default"), personality=personality_charlie)
31
 
32
  # 다이애나: 조용하고 관찰력 있는 사서
33
  personality_diana = {"sensitive": 0.7, "stoic": 0.7, "cognitive_bias": 0.9}
34
- diana = NPC("다이애나", "사서", [[7,7], [7,8], [8,8]], ("npc", "default"), personality=personality_diana)
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": {