feat: 추가된 메모리 기능으로 광맥 및 마지막 행동 저장

- 에이전트가 발견한 광맥 좌표를 `ore_patch_memory.json`에 저장하여 재시작 시 활용 가능
- 마지막 실행한 행동과 결과를 `agent_last_action_memory.json`에 저장하여 다음 상태 요약에서 참고 가능
- `state_reader.py`에서 메모리 로드 및 상태 요약에 포함
- `ai_planner.py`에서 시스템 프롬프트에 기억된 광맥 및 마지막 행동 관련 가이드 추가
- `README.md`에 새로운 메모리 기능 설명 추가
This commit is contained in:
21in7
2026-03-25 23:34:25 +09:00
parent 8c90e80582
commit 25eaa7f6cd
16 changed files with 498 additions and 12 deletions

View File

@@ -11,6 +11,13 @@ RCON을 통해 팩토리오 게임 상태를 읽어오는 모듈
import json
import os
from factorio_rcon import FactorioRCON
from ore_patch_memory import (
load_ore_patch_memory,
save_ore_patch_memory,
update_ore_patch_memory,
compute_distance_sq,
)
from agent_last_action_memory import load_last_action_memory
P = 'local p = game.players[1] if not p then rcon.print("{}") return end '
@@ -144,6 +151,8 @@ class StateReader:
"resources": self.scan_resources(),
"buildings": self.get_buildings(),
"tech": self.get_research_status(),
"ore_patch_memory": load_ore_patch_memory(),
"last_action_memory": load_last_action_memory(),
}
def get_player_info(self) -> dict:
@@ -400,7 +409,51 @@ if not ok then rcon.print("{}") end
"""
try:
raw = self.rcon.lua(lua)
return json.loads(raw) if raw and raw.startswith("{") else {}
res = json.loads(raw) if raw and raw.startswith("{") else {}
# scan_resources() 결과로도 광맥 좌표를 기억한다.
ore_mem = load_ore_patch_memory()
changed = False
if isinstance(res, dict) and res:
for ore, info in res.items():
if not isinstance(info, dict):
continue
tx = info.get("anchor_tile_x")
ty = info.get("anchor_tile_y")
cnt = info.get("count")
if isinstance(tx, int) and isinstance(ty, int):
before_patches = ore_mem.get(ore)
before_set: set[tuple[int, int]] = set()
if isinstance(before_patches, list):
for pp in before_patches:
if not isinstance(pp, dict):
continue
bx = pp.get("tile_x")
by = pp.get("tile_y")
if isinstance(bx, int) and isinstance(by, int):
before_set.add((bx, by))
ore_mem = update_ore_patch_memory(
ore_mem,
ore,
tx,
ty,
count=cnt if isinstance(cnt, int) else None,
)
after_patches = ore_mem.get(ore)
after_set: set[tuple[int, int]] = set()
if isinstance(after_patches, list):
for ap in after_patches:
if not isinstance(ap, dict):
continue
ax = ap.get("tile_x")
ay = ap.get("tile_y")
if isinstance(ax, int) and isinstance(ay, int):
after_set.add((ax, ay))
if before_set != after_set:
changed = True
if changed:
save_ore_patch_memory(ore_mem)
return res
except Exception:
return {}
@@ -452,6 +505,8 @@ if not ok then rcon.print("{}") end
inv = state.get("inventory", {})
res = state.get("resources", {})
bld = state.get("buildings", {})
ore_mem = state.get("ore_patch_memory", {}) or {}
last_action = state.get("last_action_memory", {}) or {}
lines = [
"## 현재 게임 상태",
@@ -496,6 +551,47 @@ if not ok then rcon.print("{}") end
else:
lines.append("- 반경 500타일 내 자원 없음 — 더 멀리 탐색 필요")
lines += ["", "### 기억된 광맥 (좌표)"]
if ore_mem:
px = p.get('x', 0)
py = p.get('y', 0)
known = []
for ore, patches in ore_mem.items():
if not isinstance(patches, list):
continue
for patch in patches:
if not isinstance(patch, dict):
continue
tx = patch.get("tile_x")
ty = patch.get("tile_y")
if isinstance(tx, int) and isinstance(ty, int):
dist = int(compute_distance_sq(px, py, tx, ty) ** 0.5)
known.append((ore, patch, dist))
known.sort(key=lambda x: x[2])
for ore, info, dist in known[:10]:
lines.append(
f"- {ore}: ({info.get('tile_x')},{info.get('tile_y')}) "
f"[거리: ~{dist}타일] count={info.get('count','?')}"
)
else:
lines.append("- 없음")
lines += ["", "### 마지막 행동(기억)"]
if last_action and isinstance(last_action, dict) and last_action.get("action"):
# state_summary 크기 방지를 위해 필드만 요약
act = last_action.get("action", "")
params = last_action.get("params", {})
success = last_action.get("success", None)
msg = last_action.get("message", "")
lines.append(f"- action={act} success={success} message={msg}")
if params:
try:
lines.append(f"- params={json.dumps(params, ensure_ascii=False)}")
except Exception:
pass
else:
lines.append("- 없음")
lines += ["", "### 건설된 건물"]
if bld:
for name, count in sorted(bld.items(), key=lambda x: -x[1])[:10]: