Spaces:
Sleeping
Sleeping
humanda5
commited on
Commit
·
a88e1d8
1
Parent(s):
97627cd
Complete step 20: 모든 에러 수정 완료
Browse files- app.py +13 -3
- data/saves/simulation_state.pkl +0 -0
- npc_social_network/README.txt +1 -1
- npc_social_network/maps/engine/game_engine.py +5 -4
- npc_social_network/maps/manager/village_map.py +1 -13
- npc_social_network/maps/villages/town_hall.py +2 -1
- npc_social_network/npc/npc_base.py +8 -7
- npc_social_network/npc/npc_manager.py +5 -4
- npc_social_network/npc/npc_planner.py +2 -2
- npc_social_network/routes/npc_route.py +6 -3
- npc_social_network/scenarios/scenario_setup.py +5 -5
- npc_social_network/templates/chat.html +11 -3
- npc_social_network/templates/test_memory_tools.html +0 -55
- run_npc_interactions.py +0 -39
- templates/main.html +0 -2
- templates/npc_simulation_started.html +0 -14
app.py
CHANGED
@@ -5,6 +5,9 @@ from npc_social_network.routes.npc_route import npc_bp
|
|
5 |
import os
|
6 |
import subprocess
|
7 |
|
|
|
|
|
|
|
8 |
def create_app():
|
9 |
app = Flask(__name__) # static/template 경로를 기본값
|
10 |
|
@@ -18,12 +21,19 @@ def create_app():
|
|
18 |
|
19 |
@app.route("/run_npc_simulation")
|
20 |
def run_npc_simulation():
|
21 |
-
|
22 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
23 |
subprocess.Popen([python_exec, "-m", "npc_social_network.maps.villages.town_hall"]) # 비동기로 실행 (창 띄움)
|
24 |
|
25 |
# 실행 후 사용자 안내 페이지 표시
|
26 |
-
return
|
27 |
|
28 |
return app
|
29 |
|
|
|
5 |
import os
|
6 |
import subprocess
|
7 |
|
8 |
+
# 시뮬레이션 프로세스를 관리할 전역 변수
|
9 |
+
simulation_process = None
|
10 |
+
|
11 |
def create_app():
|
12 |
app = Flask(__name__) # static/template 경로를 기본값
|
13 |
|
|
|
21 |
|
22 |
@app.route("/run_npc_simulation")
|
23 |
def run_npc_simulation():
|
24 |
+
global simulation_process
|
25 |
+
# 이미 실행 중인 프로세스가 있다면 종료
|
26 |
+
if simulation_process and simulation_process.poll() is None:
|
27 |
+
simulation_process.terminate()
|
28 |
+
simulation_process.wait()
|
29 |
+
|
30 |
+
# 파이썬 가상환경의 실행 파일 경로를 명시적으로 지정할 수 있음
|
31 |
+
python_exec = "python"
|
32 |
+
# 비동기로 Pygame 시뮬레이션 실행
|
33 |
subprocess.Popen([python_exec, "-m", "npc_social_network.maps.villages.town_hall"]) # 비동기로 실행 (창 띄움)
|
34 |
|
35 |
# 실행 후 사용자 안내 페이지 표시
|
36 |
+
return redirect(url_for('npc_social.home'))
|
37 |
|
38 |
return app
|
39 |
|
data/saves/simulation_state.pkl
ADDED
Binary file (19.7 kB). View file
|
|
npc_social_network/README.txt
CHANGED
@@ -170,7 +170,7 @@ GET /run_npc_simulation
|
|
170 |
| 17단계 | 상징 기억 / 대표 기억 생성 | ✅ 완료 | 상징 기억 분류/요약 아직 미실행
|
171 |
| 18단계 | 기억 체계 확장: 시간 맥락 부여 | ✅ 완료 | 기억에 시간 맥락/계절/시기 부여 미완료
|
172 |
| 19단계 | 성격 변화 시스템 완성 | ✅ 완료 | 중요한 기억(Memory)과 강렬한 감정(Emotion)이 누적됨에 따라 NPC의 성격(sensitive, stoic 등)이 점진적으로 변화하는 로직을 구현
|
173 |
-
| 20단계 | 중간 테스트 및 안정화 |
|
174 |
|
175 |
# 사회적 상호작용 심화 (Social Interaction Deepening)
|
176 |
| 21단계 | 소문 및 평판 시스템 도입 | ⏳ 미진행 | NPC들 사이에 '소문(Gossip)'이 퍼지는 시스템을 구현
|
|
|
170 |
| 17단계 | 상징 기억 / 대표 기억 생성 | ✅ 완료 | 상징 기억 분류/요약 아직 미실행
|
171 |
| 18단계 | 기억 체계 확장: 시간 맥락 부여 | ✅ 완료 | 기억에 시간 맥락/계절/시기 부여 미완료
|
172 |
| 19단계 | 성격 변화 시스템 완성 | ✅ 완료 | 중요한 기억(Memory)과 강렬한 감정(Emotion)이 누적됨에 따라 NPC의 성격(sensitive, stoic 등)이 점진적으로 변화하는 로직을 구현
|
173 |
+
| 20단계 | 중간 테스트 및 안정화 | ✅ 완료 |
|
174 |
|
175 |
# 사회적 상호작용 심화 (Social Interaction Deepening)
|
176 |
| 21단계 | 소문 및 평판 시스템 도입 | ⏳ 미진행 | NPC들 사이에 '소문(Gossip)'이 퍼지는 시스템을 구현
|
npc_social_network/maps/engine/game_engine.py
CHANGED
@@ -22,6 +22,7 @@ class GameEngine:
|
|
22 |
self.village = village
|
23 |
self.time_states = time_states
|
24 |
self.npc_manager = npc_manager
|
|
|
25 |
|
26 |
self.current_time_index = 0
|
27 |
self.frame_count = 0
|
@@ -45,8 +46,8 @@ class GameEngine:
|
|
45 |
|
46 |
|
47 |
# 실제 시간 흐름 시뮬레이션용
|
48 |
-
|
49 |
-
|
50 |
|
51 |
def draw(self):
|
52 |
self.village.draw(self.screen) # 마을 전체를 그림
|
@@ -88,7 +89,7 @@ class GameEngine:
|
|
88 |
hud_height
|
89 |
)
|
90 |
# 반투명 배경
|
91 |
-
s = pygame.Surface((hud_width, hud_height), pygame.
|
92 |
s.fill((30, 30, 30, 200))
|
93 |
self.screen.blit(s, (hud_rect.x, hud_rect.y))
|
94 |
|
@@ -155,7 +156,7 @@ class GameEngine:
|
|
155 |
|
156 |
# NPC 기억 승격 판단
|
157 |
if self.frame_count % 60 == 0: # 60 프레임마다 약 3초 기준
|
158 |
-
for npc in self.
|
159 |
npc.memory_store.promote_memories(time_threshold_minutes=60)
|
160 |
|
161 |
# 일정 프레임마다 모든 NPC의 자율 행동 업데이트
|
|
|
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
|
|
|
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) # 마을 전체를 그림
|
|
|
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 |
|
|
|
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의 자율 행동 업데이트
|
npc_social_network/maps/manager/village_map.py
CHANGED
@@ -43,24 +43,12 @@ class VillageMap:
|
|
43 |
Building(8, 8, "temple", self.image_loader.load("building", "temple", )),
|
44 |
]
|
45 |
|
46 |
-
# NPC 초기 세팅값
|
47 |
-
elin_path = [[2,3], [3,3], [4,3], [5,3], [5,4], [5,5], [4,5], [3, 5], [2,5], [2,4]]
|
48 |
-
elin = NPC("Elin", "farmer", elin_path, self.image_loader.load("npc", "default"))
|
49 |
-
self.npc_manager.add_npc(elin)
|
50 |
-
# 테스트를 위해 다른 NPC들도 추가
|
51 |
-
bob = NPC("Bob", "blacksmith", [[1,1], [1,2], [2,2]], self.image_loader.load("npc", "default"))
|
52 |
-
self.npc_manager.add_npc(bob)
|
53 |
-
|
54 |
-
alice = NPC("Alice", "merchant", [[8,8], [9,8], [9,9]], self.image_loader.load("npc", "default"))
|
55 |
-
self.npc_manager.add_npc(alice)
|
56 |
-
|
57 |
-
|
58 |
def draw(self, screen):
|
59 |
for x, y, image in self.tiles:
|
60 |
screen.blit(image, (x * self.tile_size, y * self.tile_size))
|
61 |
for b in self.buildings:
|
62 |
b.draw(screen, self.tile_size)
|
63 |
-
self.npc_manager.draw_all(screen, self.tile_size)
|
64 |
|
65 |
def update_npcs(self, time_state):
|
66 |
self.npc_manager.move_all()
|
|
|
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()
|
npc_social_network/maps/villages/town_hall.py
CHANGED
@@ -27,12 +27,13 @@ def run_town_hall_simulation():
|
|
27 |
# 저장/불러오기
|
28 |
npc_manager = load_simulation() # 저장된 상태를 불러오기 시도
|
29 |
|
|
|
|
|
30 |
if npc_manager is None:
|
31 |
# 저장된 파일이 없으면 새로운 시나리오 시작
|
32 |
npc_manager = setup_initial_scenario(image_loader)
|
33 |
|
34 |
# 로딩
|
35 |
-
image_loader = ImageLoader(tile_size)
|
36 |
village = VillageMap(image_loader, tile_size, width, height, npc_manager)
|
37 |
village.generate()
|
38 |
|
|
|
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 |
|
npc_social_network/npc/npc_base.py
CHANGED
@@ -73,11 +73,11 @@ class NPC:
|
|
73 |
NPC 클래스
|
74 |
NPC에 대한 기본 정보를 객체화
|
75 |
"""
|
76 |
-
def __init__(self, name, job, path,
|
77 |
self.name = name
|
78 |
self.job = job
|
79 |
self.path = path
|
80 |
-
self.
|
81 |
self.current_index = 0
|
82 |
|
83 |
# npc 기억
|
@@ -156,12 +156,13 @@ class NPC:
|
|
156 |
"""
|
157 |
return self.path[self.current_index]
|
158 |
|
159 |
-
def draw(self, screen, tile_size):
|
160 |
"""
|
161 |
NPC 이미지 화면에 렌더링
|
162 |
"""
|
|
|
163 |
x, y = self.get_position()
|
164 |
-
screen.blit(
|
165 |
|
166 |
def generate_dialogue(self, user_input: str, time_context: str, target_npc: Optional["NPC"]=None, use_llm: bool=True) -> str:
|
167 |
"""
|
@@ -257,7 +258,7 @@ class NPC:
|
|
257 |
|
258 |
def remember(self, content: str, importance: int = 5, emotion: str = None,
|
259 |
strength: float = 1.0, memory_type:str = "Event",
|
260 |
-
|
261 |
"""
|
262 |
NPC가 새로운 기억을 저장하고 감정 상태에 반영
|
263 |
"""
|
@@ -266,7 +267,7 @@ class NPC:
|
|
266 |
importance = importance,
|
267 |
emotion=emotion or "neutral",
|
268 |
memory_type=memory_type,
|
269 |
-
context_tags=
|
270 |
)
|
271 |
self.memory_store.add_memory(memory)
|
272 |
if emotion and emotion in EMOTION_LIST:
|
@@ -538,7 +539,7 @@ class NPC:
|
|
538 |
importance = 6,
|
539 |
emotion="engagement",
|
540 |
memory_type="Behavior",
|
541 |
-
|
542 |
)
|
543 |
|
544 |
# 2. 계획이 없으면 새로운 목표를 생성하도록 시도
|
|
|
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 기억
|
|
|
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 |
"""
|
|
|
258 |
|
259 |
def remember(self, content: str, importance: int = 5, emotion: str = None,
|
260 |
strength: float = 1.0, memory_type:str = "Event",
|
261 |
+
context_tags: Optional[List[str]]=None) -> Memory:
|
262 |
"""
|
263 |
NPC가 새로운 기억을 저장하고 감정 상태에 반영
|
264 |
"""
|
|
|
267 |
importance = importance,
|
268 |
emotion=emotion or "neutral",
|
269 |
memory_type=memory_type,
|
270 |
+
context_tags=context_tags
|
271 |
)
|
272 |
self.memory_store.add_memory(memory)
|
273 |
if emotion and emotion in EMOTION_LIST:
|
|
|
539 |
importance = 6,
|
540 |
emotion="engagement",
|
541 |
memory_type="Behavior",
|
542 |
+
context_tags=[time_context]
|
543 |
)
|
544 |
|
545 |
# 2. 계획이 없으면 새로운 목표를 생성하도록 시도
|
npc_social_network/npc/npc_manager.py
CHANGED
@@ -34,7 +34,7 @@ class NPCManager:
|
|
34 |
return None
|
35 |
return random.choice(possible_targets)
|
36 |
|
37 |
-
def initiate_npc_to_npc_interaction(self):
|
38 |
"""
|
39 |
NPC 간의 상호작용을 시작시키는 함수
|
40 |
"""
|
@@ -75,7 +75,8 @@ class NPCManager:
|
|
75 |
# 2. Target이 Initiator의 말을 듣고 응답 생성
|
76 |
# generate_dialogue 함수를 재사용하되, target_npc 인자를 전달
|
77 |
response_utterance = target.generate_dialogue(
|
78 |
-
user_input=initial_utterance,
|
|
|
79 |
target_npc=initiator
|
80 |
)
|
81 |
|
@@ -87,9 +88,9 @@ class NPCManager:
|
|
87 |
for npc in self.npcs:
|
88 |
npc.move()
|
89 |
|
90 |
-
def draw_all(self, screen, tile_size):
|
91 |
for npc in self.npcs:
|
92 |
-
npc.draw(screen, tile_size)
|
93 |
|
94 |
def get_npc_at(self, x, y):
|
95 |
for npc in self.npcs:
|
|
|
34 |
return None
|
35 |
return random.choice(possible_targets)
|
36 |
|
37 |
+
def initiate_npc_to_npc_interaction(self, time_context: str):
|
38 |
"""
|
39 |
NPC 간의 상호작용을 시작시키는 함수
|
40 |
"""
|
|
|
75 |
# 2. Target이 Initiator의 말을 듣고 응답 생성
|
76 |
# generate_dialogue 함수를 재사용하되, target_npc 인자를 전달
|
77 |
response_utterance = target.generate_dialogue(
|
78 |
+
user_input=initial_utterance,
|
79 |
+
time_context = time_context,
|
80 |
target_npc=initiator
|
81 |
)
|
82 |
|
|
|
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:
|
npc_social_network/npc/npc_planner.py
CHANGED
@@ -50,7 +50,7 @@ class PlannerManager:
|
|
50 |
# 당신의 현재 상태 요약
|
51 |
- 주요 감정: {self.npc.summarize_emotional_state()}
|
52 |
- 최근 기억 3개:
|
53 |
-
{chr(10).join([f' - {mem.content}' for mem in self.npc.
|
54 |
- 플레이어와의 관계: {self.npc.get_relationship_description("플레이어")}
|
55 |
"""
|
56 |
|
@@ -119,7 +119,7 @@ class PlannerManager:
|
|
119 |
return f"({plan.goal.description} 목표를 달성하여 만족스럽습니다.)"
|
120 |
|
121 |
current_step = plan.steps[step_index]
|
122 |
-
print(f"[{self.npc.name}의 행동 계획
|
123 |
|
124 |
# 다음 단계로
|
125 |
plan.current_step_index += 1
|
|
|
50 |
# 당신의 현재 상태 요약
|
51 |
- 주요 감정: {self.npc.summarize_emotional_state()}
|
52 |
- 최근 기억 3개:
|
53 |
+
{chr(10).join([f' - {mem.content}' for mem in self.npc.memory_store.get_recent_memories(limit=3)])}
|
54 |
- 플레이어와의 관계: {self.npc.get_relationship_description("플레이어")}
|
55 |
"""
|
56 |
|
|
|
119 |
return f"({plan.goal.description} 목표를 달성하여 만족스럽습니다.)"
|
120 |
|
121 |
current_step = plan.steps[step_index]
|
122 |
+
print(f"[{self.npc.name}의 행동 계획 실행] {step_index + 1}/{len(plan.steps)}: {current_step}")
|
123 |
|
124 |
# 다음 단계로
|
125 |
plan.current_step_index += 1
|
npc_social_network/routes/npc_route.py
CHANGED
@@ -37,20 +37,23 @@ def home():
|
|
37 |
# 웹 페이지가 처음 로드될 때, 저장된 NPC 목록을 전달
|
38 |
npc_manager = get_npc_manager()
|
39 |
npc_names = list(npc_manager.npc_dict.keys())
|
40 |
-
|
|
|
41 |
|
42 |
# 채팅 처리 API
|
43 |
@npc_bp.route("/chat", methods=['POST'])
|
44 |
def chat():
|
45 |
"""웹 채팅 상호작용 후, 변경된 상태 파일에 저장"""
|
46 |
npc_manager = get_npc_manager()
|
|
|
|
|
|
|
47 |
user_input = request.json.get("message")
|
48 |
npc_name = request.json.get("npc")
|
49 |
-
|
50 |
npc = npc_manager.get_npc_by_name(npc_name)
|
51 |
|
52 |
if npc is None:
|
53 |
-
return jsonify({"error": "NPC
|
54 |
|
55 |
# generate_dialogue가 time_context를 요구하므로 임시 값을 전달
|
56 |
# generate_dialogue를 통해 응답 생성 및 내부 상태 업데이트
|
|
|
37 |
# 웹 페이지가 처음 로드될 때, 저장된 NPC 목록을 전달
|
38 |
npc_manager = get_npc_manager()
|
39 |
npc_names = list(npc_manager.npc_dict.keys())
|
40 |
+
# chat.html에 시뮬레이션 실행 상태와 NPC 목록을 전달
|
41 |
+
return render_template("chat.html", npc_names=npc_names or ["(시뮬레이션 미실행)"])
|
42 |
|
43 |
# 채팅 처리 API
|
44 |
@npc_bp.route("/chat", methods=['POST'])
|
45 |
def chat():
|
46 |
"""웹 채팅 상호작용 후, 변경된 상태 파일에 저장"""
|
47 |
npc_manager = get_npc_manager()
|
48 |
+
if not npc_manager.all():
|
49 |
+
return jsonify({"error": "시뮬레이션이 실행되지 않았습니다. 먼저 시뮬레이션을 시작해주세요."}), 400
|
50 |
+
|
51 |
user_input = request.json.get("message")
|
52 |
npc_name = request.json.get("npc")
|
|
|
53 |
npc = npc_manager.get_npc_by_name(npc_name)
|
54 |
|
55 |
if npc is None:
|
56 |
+
return jsonify({"error": "NPC를 찾을 수 없습니다."}), 404
|
57 |
|
58 |
# generate_dialogue가 time_context를 요구하므로 임시 값을 전달
|
59 |
# generate_dialogue를 통해 응답 생성 및 내부 상태 업데이트
|
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]],
|
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]],
|
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]],
|
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]],
|
31 |
|
32 |
# 다이애나: 조용하고 관찰력 있는 사서
|
33 |
personality_diana = {"sensitive": 0.7, "stoic": 0.7, "cognitive_bias": 0.9}
|
34 |
-
diana = NPC("다이애나", "사서", [[7,7], [7,8], [8,8]],
|
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("엘린", "마법사", [[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")
|
npc_social_network/templates/chat.html
CHANGED
@@ -9,15 +9,22 @@
|
|
9 |
<link rel="stylesheet" herf="{{ url_for('npc_social.static', filename='css/style.css') }}">
|
10 |
</head>
|
11 |
<body>
|
12 |
-
<
|
13 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
14 |
<div class="meta-info">
|
15 |
<select id="npc" onchange="loadNPCInfo()">
|
16 |
<!-- [MOD] 서버에서 전달받은 NPC 이름으로 동적 생성 -->
|
17 |
{% for name in npc_names %}
|
18 |
<option value="{{ name }}">{{ name }}</option>
|
19 |
{% else %}
|
20 |
-
<option>NPC 없음</option>
|
21 |
{% endfor %}
|
22 |
</select>
|
23 |
<span id="relationStatus" style="margin-left:20px";>관계 점수: 로딩 중...</span>
|
@@ -26,7 +33,6 @@
|
|
26 |
<div id="chatBox"></div>
|
27 |
<input type="text" id="message" placeholder="메세지를 입력하세요" style="width:80%">
|
28 |
<button onclick="sendMessage()">보내기</button>
|
29 |
-
<button onclick="triggerNPCInteractions()">NPC 상호작용 실행</button>
|
30 |
|
31 |
<!-- Memory 영역 추가 -->
|
32 |
<div class="meta-info">
|
@@ -44,6 +50,8 @@
|
|
44 |
</ul>
|
45 |
</div>
|
46 |
|
|
|
|
|
47 |
<script src="{{ url_for('npc_social.static', filename='js/npc_chat.js') }}"></script>
|
48 |
</body>
|
49 |
</html>
|
|
|
9 |
<link rel="stylesheet" herf="{{ url_for('npc_social.static', filename='css/style.css') }}">
|
10 |
</head>
|
11 |
<body>
|
12 |
+
<h1>NPC 소셜 네트워크 - 통합 대시보드</h1>
|
13 |
|
14 |
+
<!-- 시뮬레이션 제어 버튼 -->
|
15 |
+
<div class="controls">
|
16 |
+
<a href="{{ url_for('run_npc_simulation') }}"><button>시뮬레이션 시작 / 재시작</button></a>
|
17 |
+
<p>시뮬레이션을 시작하면 별도의 Pygame 창이 열립니다.</p>
|
18 |
+
</div>
|
19 |
+
|
20 |
+
<h2>NPC와 대화하기</h2>
|
21 |
<div class="meta-info">
|
22 |
<select id="npc" onchange="loadNPCInfo()">
|
23 |
<!-- [MOD] 서버에서 전달받은 NPC 이름으로 동적 생성 -->
|
24 |
{% for name in npc_names %}
|
25 |
<option value="{{ name }}">{{ name }}</option>
|
26 |
{% else %}
|
27 |
+
<option disabled>NPC 없음</option>
|
28 |
{% endfor %}
|
29 |
</select>
|
30 |
<span id="relationStatus" style="margin-left:20px";>관계 점수: 로딩 중...</span>
|
|
|
33 |
<div id="chatBox"></div>
|
34 |
<input type="text" id="message" placeholder="메세지를 입력하세요" style="width:80%">
|
35 |
<button onclick="sendMessage()">보내기</button>
|
|
|
36 |
|
37 |
<!-- Memory 영역 추가 -->
|
38 |
<div class="meta-info">
|
|
|
50 |
</ul>
|
51 |
</div>
|
52 |
|
53 |
+
<a href="{{ url_for('index') }}">Back to Main Page</a>
|
54 |
+
|
55 |
<script src="{{ url_for('npc_social.static', filename='js/npc_chat.js') }}"></script>
|
56 |
</body>
|
57 |
</html>
|
npc_social_network/templates/test_memory_tools.html
DELETED
@@ -1,55 +0,0 @@
|
|
1 |
-
<!-- portfolio/npc_social_network/templates/test_memory_tools.html -->
|
2 |
-
<!DOCTYPE html>
|
3 |
-
<html lang="ko">
|
4 |
-
<head>
|
5 |
-
<meta charset="UTF-8">
|
6 |
-
<title>NPC Memory Test Tools</title>
|
7 |
-
</head>
|
8 |
-
<body>
|
9 |
-
<h2>🧠 NPC 기억 임베딩 요청</h2>
|
10 |
-
<form id="embedForm">
|
11 |
-
NPC 이름: <input type="text" id="embedNpcName" value="엘라">
|
12 |
-
<button type="submit">임베딩 요청</button>
|
13 |
-
</form>
|
14 |
-
<pre id="embedResult"></pre>
|
15 |
-
|
16 |
-
<hr>
|
17 |
-
|
18 |
-
<h2>🔍 NPC 기억 검색 요청</h2>
|
19 |
-
<form id="searchForm">
|
20 |
-
NPC 이름: <input type="text" id="searchNpcName" value="엘라"><br>
|
21 |
-
질의 내용: <input type="text" id="searchQuery" value="누군가와 다퉜던 기억이 있어?">
|
22 |
-
<button type="submit">검색 요청</button>
|
23 |
-
</form>
|
24 |
-
<pre id="searchResult"></pre>
|
25 |
-
|
26 |
-
<script>
|
27 |
-
// 임베딩 요청
|
28 |
-
document.getElementById('embedForm').addEventListener('submit', async (e) => {
|
29 |
-
e.preventDefault();
|
30 |
-
const npc = document.getElementById('embedNpcName').value;
|
31 |
-
const res = await fetch("/npc_social_network/test_embed_memory", {
|
32 |
-
method: "POST",
|
33 |
-
headers: { "Content-Type": "application/json" },
|
34 |
-
body: JSON.stringify({ npc })
|
35 |
-
});
|
36 |
-
const json = await res.json();
|
37 |
-
document.getElementById('embedResult').innerText = JSON.stringify(json, null, 2);
|
38 |
-
});
|
39 |
-
|
40 |
-
// 검색 요청
|
41 |
-
document.getElementById('searchForm').addEventListener('submit', async (e) => {
|
42 |
-
e.preventDefault();
|
43 |
-
const npc = document.getElementById('searchNpcName').value;
|
44 |
-
const query = document.getElementById('searchQuery').value;
|
45 |
-
const res = await fetch("/npc_social_network/test_search_memory", {
|
46 |
-
method: "POST",
|
47 |
-
headers: { "Content-Type": "application/json" },
|
48 |
-
body: JSON.stringify({ npc, query })
|
49 |
-
});
|
50 |
-
const json = await res.json();
|
51 |
-
document.getElementById('searchResult').innerText = JSON.stringify(json, null, 2);
|
52 |
-
});
|
53 |
-
</script>
|
54 |
-
</body>
|
55 |
-
</html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
run_npc_interactions.py
DELETED
@@ -1,39 +0,0 @@
|
|
1 |
-
# portfolio/run_npc_interactions.py
|
2 |
-
|
3 |
-
import time
|
4 |
-
from npc_social_network.npc.npc_manager import npc_manager
|
5 |
-
from npc_social_network.npc.npc_base import NPC
|
6 |
-
|
7 |
-
def run_npc_test_scenario(npc_name="엘라"):
|
8 |
-
npc = npc_manager.get_npc_by_name[npc_name]
|
9 |
-
|
10 |
-
test_inputs = [
|
11 |
-
"오늘 하루는 최악이었어. 왜 다들 날 무시하는 거지?",
|
12 |
-
"고마워. 나 도와줘서 정말 감사했어.",
|
13 |
-
"그때 화낸 건 미안해. 내 잘못이었어.",
|
14 |
-
"내가 선물한 책 어땠어?",
|
15 |
-
"우리 예전에 함께 일했던 거 기억나?",
|
16 |
-
]
|
17 |
-
|
18 |
-
for input_text in test_inputs:
|
19 |
-
print(f"\n[플레이어 입력] {input_text}")
|
20 |
-
response = npc.interact_with_player(input_text)
|
21 |
-
print(f"[NPC 응답] {response}")
|
22 |
-
|
23 |
-
# 기억 기반 관계 반영까지 테스트
|
24 |
-
npc.reflect_memory_emotions_on_relationship("플레이어")
|
25 |
-
|
26 |
-
# 상태 요약 출력
|
27 |
-
print("\n🧠 현재 감정 상태 요약:")
|
28 |
-
print(npc.summarize_emotional_state())
|
29 |
-
|
30 |
-
print("\n📚 최근 기억:")
|
31 |
-
for m in npc.memory_store.get_recent_memories(limit=5):
|
32 |
-
print(f"- {m.content} ({m.emotion})")
|
33 |
-
|
34 |
-
print("\n🔗 플레이어와의 관계:")
|
35 |
-
print(npc.get_relationship_description("플레이어"))
|
36 |
-
|
37 |
-
|
38 |
-
if __name__ == "__main__":
|
39 |
-
run_npc_test_scenario()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
templates/main.html
CHANGED
@@ -8,8 +8,6 @@
|
|
8 |
</head>
|
9 |
<body>
|
10 |
<h1>Welcome to the Portfolio</h1>
|
11 |
-
<a href="{{ url_for('npc_social.home') }}">Go to Project 1</a>
|
12 |
-
<br><br>
|
13 |
<form action="{{ url_for('run_npc_simulation') }}" method="get">
|
14 |
<button type = "submit"> Run NPC Simulation (pygame) </button>
|
15 |
</form>
|
|
|
8 |
</head>
|
9 |
<body>
|
10 |
<h1>Welcome to the Portfolio</h1>
|
|
|
|
|
11 |
<form action="{{ url_for('run_npc_simulation') }}" method="get">
|
12 |
<button type = "submit"> Run NPC Simulation (pygame) </button>
|
13 |
</form>
|
templates/npc_simulation_started.html
DELETED
@@ -1,14 +0,0 @@
|
|
1 |
-
<!--portfolio/templates/npc_simulation_started.html-->
|
2 |
-
<!DOCTYPE html>
|
3 |
-
<html lang="en">
|
4 |
-
<head>
|
5 |
-
<meta charset="UTF-8">
|
6 |
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
7 |
-
<title>NPC Simulation Running</title>
|
8 |
-
</head>
|
9 |
-
<body>
|
10 |
-
<h1>NPC Simulation is now running!</h1>
|
11 |
-
<p>You can interact with the Pygame window that has opened.</p>
|
12 |
-
<a href="{{ url_for('index') }}">Back to Main Page</a>
|
13 |
-
</body>
|
14 |
-
</html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|