rorshi commited on
Commit
6fe0d0c
·
1 Parent(s): dde6bd9

에러 발생으로 실행 불가

Browse files
Files changed (32) hide show
  1. app.py +43 -9
  2. data/saves/simulation_state.pkl +0 -0
  3. npc_social_network/README.txt +5 -3
  4. npc_social_network/data/saves/simulation_state.pkl +0 -0
  5. npc_social_network/manager/simulation_manager.py +1 -1
  6. npc_social_network/npc/npc_emotion.py +1 -1
  7. npc_social_network/routes/npc_route.py +90 -68
  8. npc_social_network/scenarios/scenario_setup.py +4 -4
  9. npc_social_network/simulation_core.py +14 -12
  10. npc_social_network/static/images/{tiles/animal/animal_horse.png → npc/npc_다이애나.png} +2 -2
  11. npc_social_network/static/images/{tiles/animal/animal_caw.png → npc/npc_밥.png} +2 -2
  12. npc_social_network/static/images/{tiles/animal/animal_chicken.png → npc/npc_앨리스.png} +2 -2
  13. npc_social_network/static/images/{tiles/animal/animal_dog.png → npc/npc_엘린.png} +2 -2
  14. npc_social_network/static/images/npc/npc_찰리.png +3 -0
  15. npc_social_network/static/images/tiles/animal/animal_pig.png +0 -3
  16. npc_social_network/static/images/tiles/animal/animal_sheep.png +0 -3
  17. npc_social_network/static/images/tiles/building/build_house.png +0 -3
  18. npc_social_network/static/images/tiles/building/build_market.png +0 -3
  19. npc_social_network/static/images/tiles/building/build_temple.png +0 -3
  20. npc_social_network/static/images/tiles/npc/npc.png +0 -3
  21. npc_social_network/static/images/tiles/plant/plant_raspberry.png +0 -3
  22. npc_social_network/static/images/tiles/plant/plant_tree.png +0 -3
  23. npc_social_network/static/images/tiles/plant/plant_wheat.png +0 -3
  24. npc_social_network/static/images/tiles/terrain/tile_dirt.png +0 -3
  25. npc_social_network/static/images/tiles/terrain/tile_grass.png +0 -3
  26. npc_social_network/static/images/tiles/terrain/tile_pool.png +0 -3
  27. npc_social_network/static/images/tiles/terrain/tile_stone.png +0 -3
  28. npc_social_network/static/images/tiles/terrain/tile_water.png +0 -3
  29. npc_social_network/templates/base.html +0 -15
  30. npc_social_network/templates/chat.html +0 -57
  31. npc_social_network/templates/dashboard.html +220 -0
  32. templates/main.html +8 -6
app.py CHANGED
@@ -1,18 +1,41 @@
1
  # portfolio/app.py
2
  from flask import Flask, render_template, url_for, redirect
3
- from npc_social_network.routes.npc_route import npc_bp
4
- from npc_social_network import simulation_core
5
  # from stock.routes.stock_route import stock_bp
6
- import os
7
- import subprocess
8
 
 
 
 
 
 
 
 
 
 
9
 
10
  def create_app():
11
- app = Flask(__name__) # static/template 경로를 기본값
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
 
13
- # 각 프로젝트는 Blueprint에서 자기 static/template 관리
14
- app.register_blueprint(npc_bp)
15
- # app.register_blueprint(stock_bp)
16
 
17
  # 포트폴리오의 메인 랜딩 페이지
18
  @app.route("/")
@@ -25,7 +48,18 @@ if __name__ == '__main__':
25
  # 1. flask 앱 실행
26
  app = create_app()
27
  # 2. NPC 시뮬레이션 초기화 및 백그라운드 스레드 시작
28
- simulation_core.initialize_simulation()
 
 
 
 
 
 
 
 
 
 
 
29
 
30
  # 3. Flask 웹 서버 실행
31
  # use_reloader=False는 디버그 모드에서 앱이 두 번 실행되는 것을 방지하여,
 
1
  # portfolio/app.py
2
  from flask import Flask, render_template, url_for, redirect
3
+ # from npc_social_network.routes.npc_route import npc_bp
4
+ # from npc_social_network import simulation_core
5
  # from stock.routes.stock_route import stock_bp
6
+ import threading
 
7
 
8
+ # -------------------------------------------------------------------
9
+ # 파일 1: portfolio/app.py (수정)
10
+ # 역할: 전체 포트폴리오의 진입점.
11
+ # NPC 시뮬레이션 스레드를 시작하고, 관련 URL 그룹(Blueprint)을 등록합니다.
12
+ # -------------------------------------------------------------------
13
+
14
+ # 시뮬레이션이 여러 번 초기화되는 것을 방지하기 위한 잠금 장치
15
+ init_lock = threading.Lock()
16
+ simulation_initialized = False
17
 
18
  def create_app():
19
+ """Flask 앱을 생성하고, 필요한 Blueprint를 등록하는 팩토리 함수."""
20
+ app = Flask(__name__,
21
+ template_folder='templates') # static/template 경로를 기본값
22
+
23
+ try:
24
+ from npc_social_network.routes.npc_route import npc_bp
25
+ app.register_blueprint(npc_bp)
26
+ print("✅ 'npc_social' Blueprint가 성공적으로 등록되었습니다.")
27
+ # app.register_blueprint(stock_bp)
28
+ except Exception as e:
29
+ import traceback
30
+ print("="*60)
31
+ print("❌ CRITICAL ERROR: Blueprint 등록 중 에러가 발생했습니다!")
32
+ print(f" 에러 메시지: {e}")
33
+ print("="*60)
34
+ traceback.print_exc() # 에러의 전체 경로를 출력합니다.
35
+ print("="*60)
36
+ # 이 에러가 해결될 때까지 서버는 정상 작동하지 않을 수 있습니다.
37
+ # --- FINAL DEBUGGING TOOL END ---
38
 
 
 
 
39
 
40
  # 포트폴리오의 메인 랜딩 페이지
41
  @app.route("/")
 
48
  # 1. flask 앱 실행
49
  app = create_app()
50
  # 2. NPC 시뮬레이션 초기화 및 백그라운드 스레드 시작
51
+ try:
52
+ from npc_social_network import simulation_core
53
+ simulation_core.initialize_simulation()
54
+ print("✅ 시뮬레이션이 성공적으로 초기화되었습니다.")
55
+ except Exception as e:
56
+ import traceback
57
+ print("="*60)
58
+ print("❌ CRITICAL ERROR: 시뮬레이션 초기화 중 에러가 발생했습니다!")
59
+ print(f" 에러 메시지: {e}")
60
+ print("="*60)
61
+ traceback.print_exc()
62
+ print("="*60)
63
 
64
  # 3. Flask 웹 서버 실행
65
  # use_reloader=False는 디버그 모드에서 앱이 두 번 실행되는 것을 방지하여,
data/saves/simulation_state.pkl DELETED
Binary file (19.7 kB)
 
npc_social_network/README.txt CHANGED
@@ -24,6 +24,10 @@ portfolio/
24
  ├── README.txt
25
  ├── routes/
26
  │ └── npc_route.py # 웹 라우트 (API)
 
 
 
 
27
  ├── npc/
28
  │ ├── npc_base.py # NPC 본체 (감정/기억/관계 포함)
29
  │ ├── npc_manager.py # NPC 리스트 관리
@@ -38,9 +42,7 @@ portfolio/
38
  │ ├── llm_helper.py # 감정 추론, 응답 생성
39
  │ └── llm_prompt_builder.py # 프롬프트 생성
40
  ├── templates/
41
- ├── chat.html # 채팅 UI
42
- │ ├── base.html # 공통 템플릿
43
- │ └── test_memory_tools.html # 임베딩 테스트용 페이지
44
  └── static/
45
  ├── css/style.css
46
  └── js/npc_chat.js
 
24
  ├── README.txt
25
  ├── routes/
26
  │ └── npc_route.py # 웹 라우트 (API)
27
+ ├── manager/
28
+ │ └── simulation_manager.py
29
+ ├── scenarios/
30
+ │ └── scenario_setup.py
31
  ├── npc/
32
  │ ├── npc_base.py # NPC 본체 (감정/기억/관계 포함)
33
  │ ├── npc_manager.py # NPC 리스트 관리
 
42
  │ ├── llm_helper.py # 감정 추론, 응답 생성
43
  │ └── llm_prompt_builder.py # 프롬프트 생성
44
  ├── templates/
45
+ └── dashboard.html # 임베딩 테스트용 페이지
 
 
46
  └── static/
47
  ├── css/style.css
48
  └── js/npc_chat.js
npc_social_network/data/saves/simulation_state.pkl ADDED
Binary file (12.8 kB). View file
 
npc_social_network/manager/simulation_manager.py CHANGED
@@ -3,7 +3,7 @@ import pickle
3
  import os
4
  from npc_social_network.npc.npc_manager import NPCManager
5
 
6
- SAVE_DIR = "data/saves"
7
  if not os.path.exists(SAVE_DIR):
8
  os.makedirs(SAVE_DIR)
9
 
 
3
  import os
4
  from npc_social_network.npc.npc_manager import NPCManager
5
 
6
+ SAVE_DIR = "npc_social_network/data/saves"
7
  if not os.path.exists(SAVE_DIR):
8
  os.makedirs(SAVE_DIR)
9
 
npc_social_network/npc/npc_emotion.py CHANGED
@@ -42,7 +42,7 @@ class EmotionManager:
42
  self._emotion_buffer[emotion] = min(max(0.0, self._emotion_buffer[emotion]), 100.0)
43
 
44
 
45
- def decay_emotion(self):
46
  """
47
  시간이 지남에 따라 모든 감정이 서서히 감소
48
  """
 
42
  self._emotion_buffer[emotion] = min(max(0.0, self._emotion_buffer[emotion]), 100.0)
43
 
44
 
45
+ def decay_emotions(self):
46
  """
47
  시간이 지남에 따라 모든 감정이 서서히 감소
48
  """
npc_social_network/routes/npc_route.py CHANGED
@@ -1,8 +1,7 @@
1
  # portfolio/npc_social_network/routes/npc_route.py
2
  from flask import Blueprint, render_template, request, jsonify, url_for
 
3
  from .. import simulation_core
4
- from npc_social_network.maps.manager.simulation_manager import load_simulation, save_simulation
5
- from ..npc.npc_manager import NPCManager
6
 
7
 
8
  npc_bp = Blueprint(
@@ -10,81 +9,85 @@ npc_bp = Blueprint(
10
  __name__,
11
  url_prefix="/npc_social_network",
12
  template_folder="../templates",
13
- static_folder="../static")
 
14
 
15
- # --------------------------------------------------
16
- # API 엔드포인트 정의
17
- # --------------------------------------------------
18
  @npc_bp.route("/")
19
  def dashboard():
20
- """월드 관찰자 대시보드 페이지를 렌더링합니다."""
21
  return render_template("dashboard.html")
22
 
23
- @npc_bp.route("api/world_state", method=['GET'])
24
  def get_world_state():
25
- """프론트엔드에 전달할 월드 전체 데이터를 JSON으로 반환합니다."""
26
- if not simulation_core.npc_manager:
27
- return jsonify({"error":"Simulation not started"}), 500
28
-
29
- nodes = []
30
- for npc in simulation_core.npc_manager.all():
31
- nodes.append({
32
- "id": npc.name,
33
- "label": npc.name,
34
- "shape": "image",
35
- "image": url_for('npc_social.static', filename=f'image/npc/npc{npc.name.lower()}.png'),
36
- "job": npc.job,
37
- "age": npc.age
38
- })
39
-
40
- edges = []
41
- drawn_relations = set()
42
- for npc in simulation_core.npc_manager.all():
43
- for target_name, profile in npc.relationships.relationships.items():
44
- relation_pair = tuple(sorted(npc.name, target_name))
45
- if relation_pair in drawn_relations: continue
46
-
47
- color = {"best friend": "#0400FF", "friend": "#00FF00", "acquaintance": "#68D4FF",
48
- "nuisance": "#FCF814", "rival": "#FF9E0D", "enemy": "#FF0000",
49
- "stranger":"#5E5C5C" }.get(profile.type, "gray")
50
 
51
- edges.append({
52
- "from": npc.name,
53
- "to": target_name,
54
- "label": f"{profile.type} ({profile.score: .1f})",
55
- "color": color,
56
- "width": 2
 
 
 
 
 
 
 
 
57
  })
58
- drawn_relations.add(relation_pair)
59
 
60
- return jsonify({
61
- "nodes": nodes,
62
- "edges": edges,
63
- "log": simulation_core.event_log,
64
- "paused": simulation_core.simulation_paused
65
- })
66
 
67
- @npc_bp.route("/api/npc_details/<npc_name>", methods=['GET'])
68
- def get_npc_details(npc_name):
69
- npc = simulation_core.npc_manager.get_npc_by_name(npc_name)
70
- if not npc:
71
- return jsonify({"error": "NPC not found"}), 404
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
 
73
- details = {
74
- "name": npc.name, "age": npc.age, "job": npc.job,
75
- "personality_summary": npc.personality.get_personality_summary(),
76
- "emotions": npc.get_composite_emotion_state(top_n=5),
77
- "goals": npc.planner.current_goal.description if npc.planner.has_active_plan() else "특별한 목표 없음",
78
- "memories": [mem.content for mem in npc.memory_store.get_recent_memories(limit=10)]
79
- }
80
- return jsonify(details)
 
81
 
82
  @npc_bp.route("/api/toggle_simulation", methods=['POST'])
83
  def toggle_simulation():
84
- simulation_core.simulation_paused = not simulation_core.simulation_paused
85
- status = "정지됨" if simulation_core.simulation_paused else "실행 중"
86
- simulation_core.add_log(f"시뮬레이션이 {status} 상태로 변경되었습니다.")
87
- return jsonify({"paused": simulation_core.simulation_paused})
 
 
88
 
89
  @npc_bp.route("/api/manual_tick", methods=['POST'])
90
  def manual_tick():
@@ -103,8 +106,27 @@ def inject_event():
103
  npc_name, event_text = data.get("npc_name"), data.get("event_text")
104
  npc = simulation_core.npc_manager.get_npc_by_name(npc_name)
105
 
106
- if npc and event_text:
107
- npc.remember(content=f"[요약된 기억] {event_text}", importance=10, emotion="surprise", memory_type="Summary") # 수정 필요: importance, emotion 값을 요약된 기억에 맞게 넣어줄 함수
108
- simulation_core.add_log(f"{npc_name}: '{event_text}'")
109
- return jsonify({"success": True})
110
- return jsonify({"success": False, "error": "Invalid data"}), 400
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  # portfolio/npc_social_network/routes/npc_route.py
2
  from flask import Blueprint, render_template, request, jsonify, url_for
3
+ import traceback
4
  from .. import simulation_core
 
 
5
 
6
 
7
  npc_bp = Blueprint(
 
9
  __name__,
10
  url_prefix="/npc_social_network",
11
  template_folder="../templates",
12
+ static_folder="../static"
13
+ )
14
 
 
 
 
15
  @npc_bp.route("/")
16
  def dashboard():
 
17
  return render_template("dashboard.html")
18
 
19
+ @npc_bp.route("/api/world_state", methods=['GET'])
20
  def get_world_state():
21
+ # --- DEBUGGING TOOL START ---
22
+ # try...except 블록이 에러의 상세 내용을 터미널에 출력해줍니다.
23
+ try:
24
+ with simulation_core.simulation_lock:
25
+ if not simulation_core.npc_manager:
26
+ return jsonify({"error": "Simulation not started"}), 500
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
 
28
+ # 데이터 복사 (잠금 시간을 최소화하기 위해)
29
+ all_npcs = list(simulation_core.npc_manager.all())
30
+ current_log = list(simulation_core.event_log)
31
+ is_paused = simulation_core.simulation_paused
32
+
33
+ nodes = []
34
+ for npc in all_npcs:
35
+ nodes.append({
36
+ "id": npc.name,
37
+ "label": npc.name,
38
+ "shape": "image",
39
+ "image": url_for('npc_social.static', filename=f'images/npc/npc_{npc.name.lower()}.png'),
40
+ "job": npc.job,
41
+ "age": npc.age
42
  })
 
43
 
44
+ edges = []
45
+ drawn_relations = set()
46
+ for npc in all_npcs:
47
+ for target_name, profile in npc.relationships.relationships.items():
48
+ relation_pair = tuple(sorted((npc.name, target_name)))
49
+ if relation_pair in drawn_relations: continue
50
 
51
+ color = {
52
+ "best friend": "#0400FF", "friend": "#00FF00", "acquaintance": "#68D4FF",
53
+ "nuisance": "#FCF814", "rival": "#FF9E0D", "enemy": "#FF0000",
54
+ "stranger":"#5E5C5C"
55
+ }.get(profile.type, "gray")
56
+
57
+ edges.append({
58
+ "from": npc.name,
59
+ "to": target_name,
60
+ "label": f"{profile.type} ({profile.score:.1f})",
61
+ "color": color,
62
+ "width": 2
63
+ })
64
+ drawn_relations.add(relation_pair)
65
+
66
+ return jsonify({
67
+ "nodes": nodes,
68
+ "edges": edges,
69
+ "log": current_log,
70
+ "paused": is_paused
71
+ })
72
 
73
+ except Exception as e:
74
+ # 에러 발생 시, 터미널에 상세한 로그를 출력합니다.
75
+ print("="*40)
76
+ print(f"!!! ERROR in get_world_state: {e}")
77
+ traceback.print_exc() # 에러가 발생한 위치를 정확히 알려줍니다.
78
+ print("="*40)
79
+ # 프론트엔드에도 에러가 발생했음을 알립니다.
80
+ return jsonify({"error": "An internal error occurred. Check the server terminal for details."}), 500
81
+ # --- DEBUGGING TOOL END ---
82
 
83
  @npc_bp.route("/api/toggle_simulation", methods=['POST'])
84
  def toggle_simulation():
85
+ with simulation_core.simulation_lock:
86
+ simulation_core.simulation_paused = not simulation_core.simulation_paused
87
+ status = "정지됨" if simulation_core.simulation_paused else "실행 "
88
+ simulation_core.add_log(f"시뮬레이션이 {status} 상태로 변경되었습니다.")
89
+ is_paused = simulation_core.simulation_paused
90
+ return jsonify({"paused": is_paused})
91
 
92
  @npc_bp.route("/api/manual_tick", methods=['POST'])
93
  def manual_tick():
 
106
  npc_name, event_text = data.get("npc_name"), data.get("event_text")
107
  npc = simulation_core.npc_manager.get_npc_by_name(npc_name)
108
 
109
+ with simulation_core.simulation_lock:
110
+ npc = simulation_core.npc_manager.get_npc_by_name(npc_name)
111
+ if npc and event_text:
112
+ npc.remember(content=f"[요약된 기억] {event_text}", importance=10, emotion="surprise", memory_type="Summary") # 수정 필요: 신이 돼서 넣는게 아니라, NPC가 마치 과거에 겪은 일처럼 자연스럽게 느끼도록
113
+ simulation_core.add_log(f"이벤트 주입(기억 요약) -> {npc_name}: '{event_text}'")
114
+ return jsonify({"success": True})
115
+ return jsonify({"success": False, "error": "Invalid data"}), 400
116
+
117
+ # get_npc_details 와 같은 나머지 읽기 전용 API도 Lock을 추가하면 더 안전합니다.
118
+ @npc_bp.route("/api/npc_details/<npc_name>", methods=['GET'])
119
+ def get_npc_details(npc_name):
120
+ with simulation_core.simulation_lock:
121
+ npc = simulation_core.npc_manager.get_npc_by_name(npc_name)
122
+ if not npc:
123
+ return jsonify({"error": "NPC not found"}), 404
124
+
125
+ details = {
126
+ "name": npc.name, "age": npc.age, "job": npc.job,
127
+ "personality_summary": npc.personality.get_personality_summary(),
128
+ "emotions": npc.get_composite_emotion_state(top_n=5),
129
+ "goals": npc.planner.current_goal.description if npc.planner.has_active_plan() else "특별한 목표 없음",
130
+ "memories": [mem.content for mem in npc.memory_store.get_recent_memories(limit=10)]
131
+ }
132
+ return jsonify(details)
npc_social_network/scenarios/scenario_setup.py CHANGED
@@ -2,7 +2,7 @@ from ..npc.npc_manager import NPCManager
2
  from ..npc.npc_base import NPC
3
  from ..npc.npc_memory import Memory
4
 
5
- def setup_initial_scenario(image_loader) -> NPCManager:
6
  """
7
  테스트를 위한 초기 NPC 월드를 설정하고 NPCManager를 반환합니다.
8
  - 5명의 NPC 생성
@@ -23,7 +23,7 @@ def setup_initial_scenario(image_loader) -> NPCManager:
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}
@@ -35,7 +35,7 @@ def setup_initial_scenario(image_loader) -> NPCManager:
35
 
36
  # --- 2. 초기 기억 주입 ---
37
  elin.remember(content="어젯밤 앨리스와 시장 가격 때문에 크게 다퉜다.", importance=8, emotion="anger")
38
- alice.remember(content="어젯밤 엘라가 내게 무례하게 소리쳤다.", importance=8, emotion="resentment")
39
  bob.remember(content="찰리가 어제 우리 집 지붕을 고쳐주어서 고맙다.", importance=7, emotion="gratitude")
40
  charlie.remember(content="밥의 대장간 일을 도와주고 빵을 얻었다. 그는 좋은 친구다.", importance=6, emotion="joy")
41
  diana.remember(content="도서관에서 밥이 책을 빌려가며 거칠게 다루어 조금 기분이 상했다.", importance=5, emotion="disgust")
@@ -50,7 +50,7 @@ def setup_initial_scenario(image_loader) -> NPCManager:
50
  # --- 4. 초기 관계 설정 ---
51
  # 엘라 <-> 앨리스 (나쁜 관계)
52
  elin.relationships.update_relationship("앨리스", "anger", strength=5.0)
53
- alice.relationships.update_relationship("엘라", "resentment", strength=4.0)
54
 
55
  # 밥 <-> 찰리 (좋은 관계)
56
  bob.relationships.update_relationship("찰리", "gratitude", strength=6.0)
 
2
  from ..npc.npc_base import NPC
3
  from ..npc.npc_memory import Memory
4
 
5
+ def setup_initial_scenario() -> NPCManager:
6
  """
7
  테스트를 위한 초기 NPC 월드를 설정하고 NPCManager를 반환합니다.
8
  - 5명의 NPC 생성
 
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}
 
35
 
36
  # --- 2. 초기 기억 주입 ---
37
  elin.remember(content="어젯밤 앨리스와 시장 가격 때문에 크게 다퉜다.", importance=8, emotion="anger")
38
+ alice.remember(content="어젯밤 엘린이 내게 무례하게 소리쳤다.", importance=8, emotion="resentment")
39
  bob.remember(content="찰리가 어제 우리 집 지붕을 고쳐주어서 고맙다.", importance=7, emotion="gratitude")
40
  charlie.remember(content="밥의 대장간 일을 도와주고 빵을 얻었다. 그는 좋은 친구다.", importance=6, emotion="joy")
41
  diana.remember(content="도서관에서 밥이 책을 빌려가며 거칠게 다루어 조금 기분이 상했다.", importance=5, emotion="disgust")
 
50
  # --- 4. 초기 관계 설정 ---
51
  # 엘라 <-> 앨리스 (나쁜 관계)
52
  elin.relationships.update_relationship("앨리스", "anger", strength=5.0)
53
+ alice.relationships.update_relationship("엘린", "resentment", strength=4.0)
54
 
55
  # 밥 <-> 찰리 (좋은 관계)
56
  bob.relationships.update_relationship("찰리", "gratitude", strength=6.0)
npc_social_network/simulation_core.py CHANGED
@@ -14,9 +14,10 @@ from .manager.simulation_manager import save_simulation, load_simulation
14
  npc_manager: NPCManager = None
15
  simulation_paused = True
16
  event_log = []
 
17
 
18
  #------------------------------------------
19
- # 2. 시뮬레이션 로진 함수
20
  #------------------------------------------
21
  def add_log(message):
22
  """이벤트 로그에 메시지를 추가합니다."""
@@ -31,19 +32,20 @@ def tick_simulation():
31
  if not npc_manager or simulation_paused:
32
  return
33
 
34
- add_log("시뮬레이션 틱 시작")
 
35
 
36
- for npc in npc_manager.all():
37
- npc.decay_emotions()
38
- npc.decay_memories()
39
- npc.update_autonomous_behavior("자율 행동 시간")
40
 
41
- if len(npc_manager.all()) >= 2:
42
- try:
43
- add_log("NPC 간 자율 상호작용을 시도합니다.")
44
- npc_manager.initiate_npc_to_npc_interaction("자율 행동 시간")
45
- except Exception as e:
46
- add_log(f"상호작용 중 오류 발생: {e}")
47
 
48
  def simulation_loop():
49
  """백그라운드에서 주기적으로 시뮬레이션을 실행하는 루프"""
 
14
  npc_manager: NPCManager = None
15
  simulation_paused = True
16
  event_log = []
17
+ simulation_lock = threading.Lock() # 데이터 동시 접근을 막는 잠금장치
18
 
19
  #------------------------------------------
20
+ # 2. 시뮬레이션 로직 함수
21
  #------------------------------------------
22
  def add_log(message):
23
  """이벤트 로그에 메시지를 추가합니다."""
 
32
  if not npc_manager or simulation_paused:
33
  return
34
 
35
+ with simulation_lock:
36
+ add_log("시뮬레이션 틱 시작")
37
 
38
+ for npc in npc_manager.all():
39
+ npc.decay_emotions()
40
+ npc.decay_memories()
41
+ npc.update_autonomous_behavior("자율 행동 시간")
42
 
43
+ if len(npc_manager.all()) >= 2:
44
+ try:
45
+ add_log("NPC 간 자율 상호작용을 시도합니다.")
46
+ npc_manager.initiate_npc_to_npc_interaction("자율 행동 시간")
47
+ except Exception as e:
48
+ add_log(f"상호작용 중 오류 발생: {e}")
49
 
50
  def simulation_loop():
51
  """백그라운드에서 주기적으로 시뮬레이션을 실행하는 루프"""
npc_social_network/static/images/{tiles/animal/animal_horse.png → npc/npc_다이애나.png} RENAMED
File without changes
npc_social_network/static/images/{tiles/animal/animal_caw.png → npc/npc_밥.png} RENAMED
File without changes
npc_social_network/static/images/{tiles/animal/animal_chicken.png → npc/npc_앨리스.png} RENAMED
File without changes
npc_social_network/static/images/{tiles/animal/animal_dog.png → npc/npc_엘린.png} RENAMED
File without changes
npc_social_network/static/images/npc/npc_찰리.png ADDED

Git LFS Details

  • SHA256: 89e8d5cb78f00e011d9563b5ccc4ec8172add6c5e0f2a11b7e8fbd2b3ae4f354
  • Pointer size: 132 Bytes
  • Size of remote file: 5.74 MB
npc_social_network/static/images/tiles/animal/animal_pig.png DELETED

Git LFS Details

  • SHA256: 9de3acb3b1de16b2c500d28276299d80854e3b1dd661bb3767fdac7fdcd365ea
  • Pointer size: 129 Bytes
  • Size of remote file: 8.28 kB
npc_social_network/static/images/tiles/animal/animal_sheep.png DELETED

Git LFS Details

  • SHA256: 47f571b1f2023bc3872a58736bfb75a7113e43ece61c8112242cebeff7483921
  • Pointer size: 129 Bytes
  • Size of remote file: 8.31 kB
npc_social_network/static/images/tiles/building/build_house.png DELETED

Git LFS Details

  • SHA256: 3e92fb2320d9cc4dc51b687cdd301892ac5a1c78e93c0319e9a0171dfe794bc8
  • Pointer size: 129 Bytes
  • Size of remote file: 8.36 kB
npc_social_network/static/images/tiles/building/build_market.png DELETED

Git LFS Details

  • SHA256: 84666cd7a7580f20086a40f9f478a77ca4384d7d28f3ac387ba11988689f6081
  • Pointer size: 129 Bytes
  • Size of remote file: 7.62 kB
npc_social_network/static/images/tiles/building/build_temple.png DELETED

Git LFS Details

  • SHA256: ac387dca36c020d9344b7d630411c4a0b2a2f5d0dfecd0e205a216b41fa34716
  • Pointer size: 129 Bytes
  • Size of remote file: 7.73 kB
npc_social_network/static/images/tiles/npc/npc.png DELETED

Git LFS Details

  • SHA256: 5fdc84aabac2136f0a364a89f575adf90ffdd6444298fcb4c41fb9c98f528711
  • Pointer size: 129 Bytes
  • Size of remote file: 3.53 kB
npc_social_network/static/images/tiles/plant/plant_raspberry.png DELETED

Git LFS Details

  • SHA256: 7882e06e7ee41929bd8125f1ac179066925adf0b844d76eba8c45f2ac1e36fcf
  • Pointer size: 129 Bytes
  • Size of remote file: 8.88 kB
npc_social_network/static/images/tiles/plant/plant_tree.png DELETED

Git LFS Details

  • SHA256: 5bbfd2e7f2120f0908bcad633db1446d3da133ffeb9b349df028be77f8b86d80
  • Pointer size: 129 Bytes
  • Size of remote file: 7.8 kB
npc_social_network/static/images/tiles/plant/plant_wheat.png DELETED

Git LFS Details

  • SHA256: 7763a3d6bd838caebebb34adcde78a8d29d4090dc2a7d042f5c3b5fed65f01af
  • Pointer size: 129 Bytes
  • Size of remote file: 7.99 kB
npc_social_network/static/images/tiles/terrain/tile_dirt.png DELETED

Git LFS Details

  • SHA256: 1062470b299eaa522a23fec076d0759eff6b5de78014c8b983d4135cf57b155a
  • Pointer size: 129 Bytes
  • Size of remote file: 5.2 kB
npc_social_network/static/images/tiles/terrain/tile_grass.png DELETED

Git LFS Details

  • SHA256: 7d3e01f1eabd5256b6bb413a8c39c421b80646d6a276cc3e93c66d86f002f664
  • Pointer size: 129 Bytes
  • Size of remote file: 7.92 kB
npc_social_network/static/images/tiles/terrain/tile_pool.png DELETED

Git LFS Details

  • SHA256: 956621e82b8d4cccda2a7592ba2e6115fded8f35e8fbc69b9541ecd40e4bf946
  • Pointer size: 129 Bytes
  • Size of remote file: 7.83 kB
npc_social_network/static/images/tiles/terrain/tile_stone.png DELETED

Git LFS Details

  • SHA256: 7042bc0f255b2ceb5ef953f1f8523c95d36ab7ca447df8979aa104f36d80a79b
  • Pointer size: 129 Bytes
  • Size of remote file: 7.91 kB
npc_social_network/static/images/tiles/terrain/tile_water.png DELETED

Git LFS Details

  • SHA256: 90d4c84e3242d4feb93001525e6a836fe72926ecaabbb1a8a12883ed3657e065
  • Pointer size: 129 Bytes
  • Size of remote file: 8.34 kB
npc_social_network/templates/base.html DELETED
@@ -1,15 +0,0 @@
1
- <!-- portfolio/npc_social_network/templates/base.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>
8
- Document
9
- </title>
10
- </head>
11
- <body>
12
- <!-- 자바스크립트는 로딩 -->
13
- <script src="{{ url_for('npc_social.static', filename='js/npc_chat.js') }}"></script>
14
- </body>
15
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
npc_social_network/templates/chat.html DELETED
@@ -1,57 +0,0 @@
1
- <!-- portfolio/npc_social_network/templates/chat.html -->
2
- <!DOCTYPE html>
3
- <html lang="ko">
4
- <head>
5
- <meta charset="UTF-8">
6
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
- <title>NPC Chat</title>
8
-
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>
31
- </div>
32
-
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">
39
- <h4>Memory:</h4>
40
- <ul id="memoryList">
41
- <li>로딩 중...</li>
42
- </ul>
43
- </div>
44
-
45
- <!-- Personality 상태 UI 표시 -->
46
- <div class="meta-info">
47
- <h4>Personality 상태:</h4>
48
- <ul id="personalityStatus">
49
- <li>로딩 중...</li>
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/dashboard.html ADDED
@@ -0,0 +1,220 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="ko">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>NPC 소셜 네트워크 대시보드</title>
7
+ <!-- vis-network 라이브러리 (CDN) -->
8
+ <script type="text/javascript" src="https://unpkg.com/vis-network/standalone/umd/vis-network.min.js"></script>
9
+ <style>
10
+ :root {
11
+ --bg-color: #f0f2f5;
12
+ --panel-bg-color: #ffffff;
13
+ --border-color: #dee2e6;
14
+ --shadow: 0 4px 6px rgba(0,0,0,0.05);
15
+ --primary-color: #007bff;
16
+ --success-color: #28a745;
17
+ }
18
+ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; display: flex; height: 100vh; margin: 0; background-color: var(--bg-color); }
19
+ #main-container { display: flex; flex: 1; padding: 15px; gap: 15px; }
20
+ #network-panel { flex: 3; display: flex; flex-direction: column; background: var(--panel-bg-color); border-radius: 12px; box-shadow: var(--shadow); }
21
+ #network { width: 100%; flex: 1; border-bottom: 1px solid var(--border-color); }
22
+ #controls { padding: 12px 15px; display: flex; flex-wrap: wrap; gap: 10px; align-items: center; background-color: #f8f9fa; border-radius: 0 0 12px 12px;}
23
+ #controls button { padding: 8px 15px; border-radius: 6px; border: none; cursor: pointer; color: white; font-size: 14px; font-weight: 500; transition: background-color 0.2s; }
24
+ #play-pause-btn { background-color: var(--success-color); }
25
+ #play-pause-btn.paused { background-color: var(--primary-color); }
26
+ #tick-btn { background-color: #6c757d; }
27
+ #inject-event-btn { background-color: #17a2b8; }
28
+ #controls select, #controls input { padding: 8px; border-radius: 6px; border: 1px solid var(--border-color); flex-grow: 1; }
29
+
30
+ #side-panel { flex: 1; display: flex; flex-direction: column; gap: 15px; min-width: 300px; }
31
+ .info-panel { background: var(--panel-bg-color); padding: 20px; border-radius: 12px; box-shadow: var(--shadow); overflow-y: auto; }
32
+ #npc-details { flex-basis: 45%; }
33
+ #log-panel { flex-basis: 55%; display: flex; flex-direction: column; }
34
+ #log-container { flex: 1; overflow-y: auto; font-size: 13px; line-height: 1.6; color: #495057; white-space: pre-wrap; word-wrap: break-word; }
35
+ h2 { margin-top: 0; margin-bottom: 15px; font-size: 18px; color: #343a40; border-bottom: 1px solid var(--border-color); padding-bottom: 10px; }
36
+ ul { padding-left: 20px; margin: 0; }
37
+ li { margin-bottom: 8px; }
38
+ </style>
39
+ </head>
40
+ <body>
41
+ <div id="main-container">
42
+ <!-- 관계 네트워크 패널 -->
43
+ <div id="network-panel">
44
+ <div id="network"></div>
45
+ <div id="controls">
46
+ <button id="play-pause-btn">Play</button>
47
+ <button id="tick-btn">Next Tick</button>
48
+ <select id="event-npc-select"></select>
49
+ <input type="text" id="event-text-input" placeholder="주입할 이벤트 입력 (예: 길에서 금화를 주웠다)">
50
+ <button id="inject-event-btn">이벤트 주입</button>
51
+ </div>
52
+ </div>
53
+
54
+ <!-- 사이드 정보 패널 -->
55
+ <div id="side-panel">
56
+ <div id="npc-details" class="info-panel">
57
+ <h2>NPC 상세 정보</h2>
58
+ <div id="npc-details-content">관계도에서 NPC를 선택하세요.</div>
59
+ </div>
60
+ <div id="log-panel" class="info-panel">
61
+ <h2>실시간 이벤트 로그</h2>
62
+ <div id="log-container">(로그 로딩 중...)</div>
63
+ </div>
64
+ </div>
65
+ </div>
66
+
67
+ <script>
68
+ document.addEventListener('DOMContentLoaded', function () {
69
+ const networkContainer = document.getElementById('network');
70
+ const logContainer = document.getElementById('log-container');
71
+ const detailsContainer = document.getElementById('npc-details-content');
72
+ const playPauseBtn = document.getElementById('play-pause-btn');
73
+ const tickBtn = document.getElementById('tick-btn');
74
+ const injectEventBtn = document.getElementById('inject-event-btn');
75
+ const eventNpcSelect = document.getElementById('event-npc-select');
76
+ const eventTextInput = document.getElementById('event-text-input');
77
+
78
+ let network = null;
79
+ let nodes = new vis.DataSet([]);
80
+ let edges = new vis.DataSet([]);
81
+ let autoUpdateInterval = null;
82
+
83
+ const options = {
84
+ nodes: {
85
+ borderWidth: 3,
86
+ size: 30,
87
+ color: { border: '#222222', background: '#666666' },
88
+ font: { color: '#000000' }
89
+ },
90
+ edges: {
91
+ font: { align: 'top' },
92
+ smooth: { type: 'cubicBezier' }
93
+ },
94
+ physics: {
95
+ stabilization: false,
96
+ solver: 'forceAtlas2Based',
97
+ forceAtlas2Based: { gravitationalConstant: -50, springLength: 100, springConstant: 0.08 }
98
+ },
99
+ interaction: { hover: true, tooltipDelay: 200 }
100
+ };
101
+
102
+ async function updateWorld() {
103
+ try {
104
+ const response = await fetch('/npc_social_network/api/world_state');
105
+ const data = await response.json();
106
+
107
+ if (data.error) throw new Error(data.error);
108
+
109
+ // vis.js 데이터셋 업데이트
110
+ nodes.update(data.nodes);
111
+ edges.update(data.edges);
112
+
113
+ // 네트워크가 없으면 새로 생성
114
+ if (!network) {
115
+ network = new vis.Network(networkContainer, { nodes, edges }, options);
116
+ network.on('click', onNodeClick);
117
+ }
118
+
119
+ updateNpcSelect(data.nodes);
120
+ updateLog(data.log);
121
+ updatePlayPauseButton(data.paused);
122
+
123
+ } catch (error) {
124
+ console.error('Error fetching world state:', error);
125
+ logContainer.textContent = "서버 연결 오류. app.py를 실행했는지 확인해주세요.";
126
+ }
127
+ }
128
+
129
+ async function onNodeClick(params) {
130
+ if (params.nodes.length > 0) {
131
+ const npcName = params.nodes[0];
132
+ try {
133
+ const response = await fetch(`/npc_social_network/api/npc_details/${npcName}`);
134
+ const details = await response.json();
135
+
136
+ let detailsHtml = `<h3>${details.name} (${details.job}, ${details.age}세)</h3>`;
137
+ detailsHtml += `<p><strong>성격:</strong> ${details.personality_summary}</p>`;
138
+ detailsHtml += `<p><strong>목표:</strong> ${details.goals}</p>`;
139
+ detailsHtml += `<strong>감정:</strong><ul>${details.emotions.length > 0 ? details.emotions.map(e => `<li>${e[0]}: ${e[1].toFixed(1)}</li>`).join('') : '<li>평온함</li>'}</ul>`;
140
+ detailsHtml += `<strong>최근 기억 (10개):</strong><ul>${details.memories.length > 0 ? details.memories.map(m => `<li>${m.substring(0, 80)}...</li>`).join('') : '<li>기억 없음</li>'}</ul>`;
141
+
142
+ detailsContainer.innerHTML = detailsHtml;
143
+
144
+ } catch (error) {
145
+ detailsContainer.innerHTML = `${npcName}의 정보를 불러오는 데 실패했습니다.`;
146
+ }
147
+ }
148
+ }
149
+
150
+ function updateNpcSelect(npcNodes) {
151
+ const currentSelection = eventNpcSelect.value;
152
+ eventNpcSelect.innerHTML = '';
153
+ npcNodes.forEach(node => {
154
+ const option = document.createElement('option');
155
+ option.value = node.id;
156
+ option.textContent = node.id;
157
+ eventNpcSelect.appendChild(option);
158
+ });
159
+ if (currentSelection) eventNpcSelect.value = currentSelection;
160
+ }
161
+
162
+ function updateLog(logMessages) {
163
+ logContainer.textContent = logMessages.join('\n');
164
+ }
165
+
166
+ function updatePlayPauseButton(isPaused) {
167
+ playPauseBtn.textContent = isPaused ? '▶ Play' : '❚❚ Pause';
168
+ if (isPaused) {
169
+ playPauseBtn.textContent = '▶ Play';
170
+ playPauseBtn.classList.remove('paused');
171
+ if (autoUpdateInterval) {
172
+ clearInterval(autoUpdateInterval);
173
+ autoUpdateInterval = null;
174
+ }
175
+ } else {
176
+ playPauseBtn.textContent = '❚❚ Pause';
177
+ playPauseBtn.classList.add('paused');
178
+ if (!autoUpdateInterval) {
179
+ // 시뮬레이션이 실행 중일 때만 자동 업데이트
180
+ autoUpdateInterval = setInterval(updateWorld, 5000);
181
+ }
182
+ }
183
+ }
184
+
185
+ // --- 이벤트 리스너 ---
186
+ playPauseBtn.addEventListener('click', async () => {
187
+ const response = await fetch('/npc_social_network/api/toggle_simulation', { method: 'POST' });
188
+ const data = await response.json();
189
+ updatePlayPauseButton(data.paused);
190
+ // 상태 변경 후 즉시 UI 업데이트
191
+ updateWorld();
192
+ });
193
+
194
+ tickBtn.addEventListener('click', async () => {
195
+ logContainer.textContent = "수동 틱 실행 중...";
196
+ await fetch('/npc_social_network/api/manual_tick', { method: 'POST' });
197
+ await updateWorld();
198
+ });
199
+
200
+ injectEventBtn.addEventListener('click', async () => {
201
+ const npc_name = eventNpcSelect.value;
202
+ const event_text = eventTextInput.value;
203
+ if (!npc_name || !event_text) return alert('NPC와 이벤트 내용을 모두 입력해주세요.');
204
+
205
+ await fetch('/npc_social_network/api/inject_event', {
206
+ method: 'POST',
207
+ headers: { 'Content-Type': 'application/json' },
208
+ body: JSON.stringify({ npc_name, event_text })
209
+ });
210
+
211
+ eventTextInput.value = '';
212
+ await updateWorld();
213
+ });
214
+
215
+ // --- 초기화 ---
216
+ updateWorld();
217
+ });
218
+ </script>
219
+ </body>
220
+ </html>
templates/main.html CHANGED
@@ -1,15 +1,17 @@
1
  <!--portfolio/templates/main.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>Portfolio Main</title>
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>
 
 
14
  </body>
15
  </html>
 
1
  <!--portfolio/templates/main.html-->
2
  <!DOCTYPE html>
3
+ <html lang="ko">
4
  <head>
5
  <meta charset="UTF-8">
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>포트폴리오</title>
8
  </head>
9
  <body>
10
+ <h1>포트폴리오 메인</h1>
11
+ <p>프로젝트 목록:</p>
12
+ <ul>
13
+ <!-- BUG FIX: form 대신 간단한 링크로 수정 -->
14
+ <li><a href="{{ url_for('npc_social.dashboard') }}">NPC 소셜 네트워크 관찰자 대시보드</a></li>
15
+ </ul>
16
  </body>
17
  </html>