Files
factorio-ai-agent/main.py
21in7 25eaa7f6cd feat: 추가된 메모리 기능으로 광맥 및 마지막 행동 저장
- 에이전트가 발견한 광맥 좌표를 `ore_patch_memory.json`에 저장하여 재시작 시 활용 가능
- 마지막 실행한 행동과 결과를 `agent_last_action_memory.json`에 저장하여 다음 상태 요약에서 참고 가능
- `state_reader.py`에서 메모리 로드 및 상태 요약에 포함
- `ai_planner.py`에서 시스템 프롬프트에 기억된 광맥 및 마지막 행동 관련 가이드 추가
- `README.md`에 새로운 메모리 기능 설명 추가
2026-03-25 23:34:25 +09:00

170 lines
6.2 KiB
Python

"""
main.py — 순수 AI 플레이 버전
치트 없이 실제 게임 메커니즘으로 플레이.
걷기/채굴/제작에 실제 시간이 걸리므로 ACTION_DELAY를 설정.
핵심 변경:
- 시작 시 플레이어 접속 여부 확인
- game.player → game.players[1] (RCON 호환)
- JSON 파싱 실패 시 재시도 (ai_planner 내부)
"""
import time
import os
import json
from factorio_rcon import FactorioRCON
from state_reader import StateReader
from context_compressor import ContextCompressor
from ai_planner import AIPlanner
from action_executor import ActionExecutor
from agent_last_action_memory import save_last_action_memory
RCON_HOST = os.getenv("FACTORIO_HOST", "127.0.0.1")
RCON_PORT = int(os.getenv("FACTORIO_PORT", "25575"))
RCON_PASSWORD = os.getenv("FACTORIO_PASSWORD", "factorio_ai")
ACTION_DELAY = 0.2
LOG_FILE = "agent_log.jsonl"
# 건물 N개 이상이면 압축 모드로 전환
COMPRESS_THRESHOLD = 50
def check_player(rcon: FactorioRCON) -> bool:
"""서버에 접속한 플레이어가 있는지 확인하고 안내"""
print("\n[초기화] 플레이어 접속 확인 중...")
for attempt in range(30): # 최대 30초 대기
result = rcon.check_player()
if result:
info = rcon.lua("""
local p = game.players[1]
if p then
rcon.print(p.name .. " @ " .. string.format("%.0f, %.0f", p.position.x, p.position.y))
end
""")
print(f"[초기화] OK 플레이어 발견: {info}")
return True
if attempt == 0:
print("[초기화] WARN 접속한 플레이어가 없습니다!")
print("[초기화] 팩토리오 클라이언트로 이 서버에 접속하세요.")
print(f"[초기화] 서버 주소: {RCON_HOST}")
print(f"[초기화] 대기 중... (최대 30초)")
time.sleep(1)
print("[오류] 30초 내에 플레이어가 접속하지 않았습니다.")
print("[오류] 팩토리오 클라이언트로 서버에 접속한 후 다시 실행하세요.")
return False
def run():
print("=" * 60)
print(" 팩토리오 순수 AI 에이전트 (치트 없음)")
print(" - 실제 걷기 / 실제 채굴 / 실제 제작 / 건설 거리 제한")
print(" - RCON 호환: game.players[1] 사용")
print("=" * 60)
with FactorioRCON(RCON_HOST, RCON_PORT, RCON_PASSWORD) as rcon:
# ── 플레이어 접속 확인 ──
if not check_player(rcon):
return
reader = StateReader(rcon)
compressor = ContextCompressor(rcon)
planner = AIPlanner()
executor = ActionExecutor(rcon)
total_actions = 0
queue: list[dict] = []
entity_count = 0
while True:
try:
if not queue:
print(f"\n{'='*50}")
print(f"[재계획] 총 {total_actions}개 실행 완료")
# ── 압축 레벨 자동 선택 ──
if entity_count < COMPRESS_THRESHOLD:
print("[상태] 초반 모드 - 상세 상태 수집")
state = reader.get_full_state()
summary = reader.summarize_for_ai(state)
entity_count = sum(
v for v in state.get("buildings", {}).values()
if isinstance(v, int)
)
elif entity_count < 200:
print(f"[상태] 중반 모드 - 구역 압축 (건물 {entity_count}개)")
summary = compressor.get_compressed_state(detail_level=1)
else:
print(f"[상태] 후반 모드 - 글로벌 압축 (건물 {entity_count}개)")
summary = compressor.get_compressed_state(detail_level=0)
global_info = compressor._get_global_summary()
entity_count = global_info.get("total_entities", entity_count)
# ── AI 계획 ──
queue = planner.decide(summary)
if not queue:
print("[경고] AI가 행동 반환 안 함. 10초 후 재시도")
time.sleep(10)
continue
# ── 행동 실행 ──
action = queue.pop(0)
total_actions += 1
act = action.get("action", "")
reason = action.get("reason", "")
print(f"\n[{total_actions}] {act}")
print(f" 이유: {reason}")
print(f" params: {json.dumps(action.get('params',{}), ensure_ascii=False)}")
success, message = executor.execute(action)
status = "OK" if success else "FAIL"
print(f" 결과: {status} {message}")
# 재시작 시 직전 행동을 참고할 수 있도록 메모리에 저장
try:
save_last_action_memory({
"action": act,
"params": action.get("params", {}),
"success": success,
"message": message,
"timestamp": time.time(),
})
except Exception:
pass
planner.record_feedback(action, success, message)
if not success:
print(" -> 실패. 재계획 요청")
queue.clear()
_log(total_actions, action, success, message)
time.sleep(ACTION_DELAY)
except KeyboardInterrupt:
print(f"\n종료 (총 {total_actions}개 실행)")
break
except Exception as e:
print(f"[오류] {e}")
import traceback
traceback.print_exc()
time.sleep(5)
def _log(step, action, success, message):
with open(LOG_FILE, "a", encoding="utf-8") as f:
f.write(json.dumps({
"step": step, "action": action,
"success": success, "message": message,
"timestamp": time.time()
}, ensure_ascii=False) + "\n")
if __name__ == "__main__":
run()