- `_append_memory_sections` 함수를 추가하여 압축 모드에서 기억된 광맥과 마지막 행동 정보를 상태 요약에 포함하도록 개선 - `main.py`에서 상태 요약 생성 시 메모리 섹션을 자동으로 추가하여 AI의 의사결정에 필요한 정보를 제공 - `README.md`에 새로운 메모리 기능 및 사용 방법에 대한 설명 추가 - `state_reader.py`에서 인벤토리 판독 로직을 개선하여 캐시 사용 조건을 명확히 하여 안정성 향상
10 KiB
10 KiB
팩토리오 AI 에이전트 (순수 플레이)
AI가 팩토리오를 치트 없이 자율적으로 플레이하는 에이전트입니다. 실제 걷기, 실제 채굴, 실제 제작, 건설 거리 제한 등 모든 게임 메커닉을 준수합니다.
순수 플레이 모드란?
| 항목 | 치트 모드 | 순수 모드 (현재) |
|---|---|---|
| 이동 | 텔레포트 | 실제 걷기 (walking_state) |
| 채굴 | 인벤토리 직접 삽입 | 자원 패치에서 실제 채굴 (mining_state) |
| 제작 | 무조건 지급 | 재료 소모 실제 제작 (begin_crafting) |
| 건설 | 어디서든 create_entity | 건설 거리 내에서 build_from_cursor |
| 삽입 | 무한 아이템 | 플레이어 인벤토리에서 차감 |
파일 구조
factorio_ai/
├── main.py ← 메인 루프 (여기서 실행)
├── factorio_rcon.py ← RCON 연결 (게임과 통신)
├── state_reader.py ← 게임 상태 읽기 (자원, 인벤토리, 건물)
├── context_compressor.py ← 중반 이후 상태 압축
├── ai_planner.py ← AI 행동 계획 (순수 플레이 제약 반영)
├── action_executor.py ← 실제 게임 조작 (순수 메커닉)
└── agent_log.jsonl ← 행동 로그 (자동 생성)
설치
# 외부 라이브러리 불필요! Python 표준 라이브러리만 사용
팩토리오 RCON 설정
방법 1: 싱글플레이
- 팩토리오 실행
- 게임 시작 후 콘솔 열기 (` 키 또는 ~)
- 아래 입력:
/rcon-port 25575 /rcon-password factorio_ai
방법 2: 서버 모드 (권장)
# Windows
factorio.exe --start-server saves/mysave.zip --rcon-port 25575 --rcon-password factorio_ai
# Linux/Mac
./factorio --start-server saves/mysave.zip --rcon-port 25575 --rcon-password factorio_ai
실행
# Z.ai API 키 설정
export ZAI_API_KEY="your-key-here"
# 기본 실행
python main.py
# 커스텀 서버
FACTORIO_HOST=192.168.1.10 FACTORIO_PORT=25575 FACTORIO_PASSWORD=mypass python main.py
# Z.ai API 키 설정
$env:ZAI_API_KEY="your-key-here"
# 기본 실행
python .\main.py
# 커스텀 서버 (한 줄 버전)
$env:FACTORIO_HOST="192.168.1.10"; $env:FACTORIO_PORT="25575"; $env:FACTORIO_PASSWORD="mypass"; python .\main.py
# (권장) 스크립트 실행
.\run_factorio_ai.ps1 "your-key-here" "mypass"
동작 원리
1. RCON → 팩토리오에서 현재 상태 읽기
(플레이어 위치, 인벤토리, 자원 패치, 건물 목록)
2. 상태 → AI API로 전송
"다음에 뭘 해야 하나요?" (순수 플레이 제약 포함)
3. AI → JSON 행동 시퀀스 반환
[move → mine_resource → craft_item → place_entity ...]
4. RCON → 팩토리오에서 실제 게임 메커닉으로 실행
- 캐릭터가 실제로 걸어감
- 실제로 곡괭이질해서 채굴
- 재료를 소모해서 제작
- 건설 거리 내에서만 배치
AI 목표 변경
planner.set_goal(
"전력 인프라 구축: offshore-pump → boiler → steam-engine → 전선 연결"
)
GLM API 연결 문제 디버깅
타임아웃·연결 오류가 나면 원인을 로그로 먼저 구분하는 것이 좋습니다.
-
전용 검사 스크립트 (에이전트와 동일한
urllib경로):ZAI_API_KEY="your-key" python scripts/glm_connection_check.py -
실행 시 상세 로그:
GLM_DEBUG=1을 켜면 재시도마다[GLM 원인] …한 줄에URLError.reason, SSL/소켓errno, DNS 힌트 등이 붙고, 스택 트레이스도 출력됩니다. -
자주 있는 원인: Docker/서버에서 외부 HTTPS(443) 차단, 프록시 필요(
HTTPS_PROXY), DNS 실패, API 키 만료·오타, 응답이 느려 타임아웃(GLM_HTTP_TIMEOUT_SECONDS증가 ).
주의사항
- 순수 플레이이므로 걷기, 채굴, 제작에 실제 시간이 소요됩니다
- AI가 "move 먼저 → 작업" 패턴을 학습하도록 프롬프트가 설계되어 있습니다
agent_log.jsonl에 모든 행동과 타임스탬프가 기록됩니다ai_planner.py는 GLM 응답이 잘리거나(중괄호/대괄호 불일치) 마크다운이 섞여도 JSON 파싱을 복구하도록{} / []균형 추적과 보정 로직을 사용합니다.ai_planner.py의AIPlanner.decide()는TimeoutError/ConnectionError/urllib.error.URLError같은 GLM HTTP 지연/연결 오류도 3회 재시도한 뒤, 상태 요약에 나온 광맥(앵커) 좌표가 있으면mine_resource(먼 경우move후 채굴)로 폴백하고, 광맥 정보가 없을 때만explore방향을 순환하며 탐색합니다(동일 방향 탐색 루프 완화).- GLM HTTP 읽기 제한 시간은 기본 120초이며,
GLM_HTTP_TIMEOUT_SECONDS로 조정할 수 있습니다. 광맥은 플레이어와 200타일 이상 떨어진 경우에만 폴백에서move를 끼우며, 임계값은GLM_FALLBACK_MOVE_THRESHOLD(기본 200)로 바꿀 수 있습니다. ai_planner.py의_call_glm()에서 GLM 지연 원인 분석을 위한 타이밍 로그가 출력됩니다.total: 요청 시작~콘텐츠 반환까지 전체 소요http_read: HTTP 응답 본문 수신까지 소요json_parse: 응답 JSON 파싱 시간
- 파이썬 에이전트 재실행 시 인벤토리 “기억”이 필요하면
inventory_memory.json캐시를 사용합니다.RCON에서 인벤토리를 읽지 못하거나(비정상 출력/파싱 실패 등) 직전에 성공적으로 읽은 인벤토리를 대신 사용합니다.- 성공적으로 인벤토리가 읽히면 해당 캐시를 갱신합니다(코드는
inv.get_contents()가nil/비테이블을 반환해도 안전하게 처리). - 또한
inv.get_contents()결과가 비면 Lua에서 인덱스 기반으로 한 번 더 읽어서{}가 과도하게 나오는 걸 줄입니다. - 캐시 대체는
get_inventory()의 파싱이 실패한 경우에만 수행합니다. - 또한 일부 환경에서
p.get_main_inventory()가 없으면defines.inventory.character_main으로 한 번 더 읽습니다. - 캐시 파일이 아직 없으면(초기 실행) fallback도
{}가 됩니다.
- (중요) 일부 Factorio 버전에서는
game.table_to_json이 없을 수 있어서, Lua 내부에 간단 JSON 인코더를 넣어 인벤토리/상태를 안정적으로 가져옵니다. explore및scan_resources()로 발견한 광맥(자원 엔티티) 좌표는ore_patch_memory.json에 ore 종류별로 여러 패치 좌표 목록으로 저장되고, 다음 상태 요약에서 AI가 좌표를 재사용(우선 이동)할 수 있게 합니다.- 디버깅용으로
INV_DEBUG=1을 켜면,main inventory외에cursor_stack/armor/trash총합도 함께 출력합니다(어디에 아이템이 있는지 확인용). mine_resource에서 실패한 채굴 타일 제외(exclude)는 Lua와 Python 양쪽에서 정수 타일 좌표(tx, ty) 키로 통일해, 제외한 좌표가 반복 선택되지 않도록 합니다.- 또한 채굴 시작(
mining_state) 좌표는 정수 타일이 아니라, Lua가 찾은 실제 자원 엔티티의e.position(정확 실수 좌표)을 사용해 “플레이어가 타일 위에 있는데도 즉시 채굴 감지 실패”를 줄입니다. mine_resource는LuaControl.mining_state를 해당 타깃에 대해 한 번만 설정합니다. 이전에는 대기 루프마다 같은 값을반복 설정해 우클릭을 연타하는 것과 비슷한 동작이었으며, 이제는 인간이 우클릭을 누른 채로 두는 것에 가깝게 유지합니다.mine_resource는 move 후p.position근처(반경 1.2)에서 실제로 해당 광물 엔티티가 있는지 재확인하고, 없으면 채굴을 시도하지 않고 다음 후보로 넘어갑니다.scan_resources()는 패치 평균 중심(center_x/y)만이 아니라 플레이어 기준 가장 가까운 실제 엔티티 좌표(anchor_x/y)도 함께 반환하며,summarize_for_ai()에서는 앵커를 기준으로 거리/추천을 계산합니다.mine_resource의 초기 “후보 엔티티 탐색” 반경은 패치 중심 오차를 흡수하기 위해 250으로 확장해, 이동 직후 바로 실패하는 케이스를 줄입니다.- 에이전트를 재시작하더라도 직전에 실행했던 action/result를
agent_last_action_memory.json에 저장해, 다음 상태 요약에서 AI가 참고할 수 있게 합니다. - 저장 기준: 성공/실패 상관없이 가장 마지막으로 실행을 시도한 action 1개만 저장합니다.
explore는wanted_ores가 있으면 해당 자원이 발견될 때까지 멈추지 않고 계속 이동해,iron-ore처럼 주변에 흔한 자원만 계속 발견되어 진행이 막히는 문제를 줄입니다.place_entity는 지정 좌표가BLOCKED이거나 인벤토리에 해당 아이템이 없어도(예: 이미 설치됨) 주변±1 타일및 요청 좌표 중심 반경3타일 내의 기존 엔티티를 우선 찾아REUSED로 재사용합니다.stone-furnace는place_entity가 성공하면(신규 배치든REUSED든) executor가 즉시coal(연료)와iron-ore/copper-ore(가능한 것 우선)를 투입해 제련이 시작되도록 보정합니다.- 또한
insert_to_entity는 burner 인벤토리 투입 전에can_insert를 확인해, 미삽입/아이템 증발 위험을 줄입니다.
- 또한
- (Cursor) Windows에서
sessionStart훅 실행 중 앱 선택창이 뜨는 경우:- 프로젝트 훅은
E:/develop/factorio-ai-agent/.cursor/hooks.json및E:/develop/factorio-ai-agent/.cursor/session-start-hook.ps1를 PowerShell로 실행하도록 구성되어 있습니다. - Superpowers 플러그인의
./hooks/session-start가 bash로 실행되도록hooks-cursor.json을 수정했습니다(필요 시 Cursor 재시작 후View -> Output -> Hooks에서 확인).
- 프로젝트 훅은
- 인벤토리 판독은
inv.get_contents()를 우선 사용해, 일부 환경에서#inv/ 인덱스 접근이 비어있음으로 오인되는 문제를 줄입니다.