feat: 추가된 메모리 기능으로 광맥 및 마지막 행동 저장
- 에이전트가 발견한 광맥 좌표를 `ore_patch_memory.json`에 저장하여 재시작 시 활용 가능 - 마지막 실행한 행동과 결과를 `agent_last_action_memory.json`에 저장하여 다음 상태 요약에서 참고 가능 - `state_reader.py`에서 메모리 로드 및 상태 요약에 포함 - `ai_planner.py`에서 시스템 프롬프트에 기억된 광맥 및 마지막 행동 관련 가이드 추가 - `README.md`에 새로운 메모리 기능 설명 추가
This commit is contained in:
@@ -7,6 +7,7 @@ action_executor.py — 순수 AI 플레이 버전
|
||||
"""
|
||||
import time
|
||||
from factorio_rcon import FactorioRCON
|
||||
from ore_patch_memory import load_ore_patch_memory, save_ore_patch_memory, update_ore_patch_memory
|
||||
|
||||
P = """local p = game.players[1]
|
||||
if not p then rcon.print("NO_PLAYER") return end
|
||||
@@ -75,14 +76,37 @@ local ok, data = pcall(function()
|
||||
local res = p.surface.find_entities_filtered{position = pos, radius = 50, type = "resource"}
|
||||
if #res == 0 then return "NONE:" .. string.format("%.0f,%.0f", pos.x, pos.y) end
|
||||
local counts = {}
|
||||
for _, e in ipairs(res) do counts[e.name] = (counts[e.name] or 0) + 1 end
|
||||
local best_x, best_y = pos.x, pos.y
|
||||
local best_d2 = math.huge
|
||||
local wanted_active = wanted and next(wanted) ~= nil
|
||||
|
||||
for _, e in ipairs(res) do
|
||||
local n = e.name
|
||||
counts[n] = (counts[n] or 0) + 1
|
||||
|
||||
local relevant = true
|
||||
if wanted_active then
|
||||
relevant = wanted[n] and true or false
|
||||
end
|
||||
if relevant then
|
||||
local dx = e.position.x - pos.x
|
||||
local dy = e.position.y - pos.y
|
||||
local d2 = dx*dx + dy*dy
|
||||
if d2 < best_d2 then
|
||||
best_d2 = d2
|
||||
best_x = e.position.x
|
||||
best_y = e.position.y
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- wanted_ores가 지정된 경우: 그 중 하나라도 있으면 FOUND, 아니면 UNWANTED 반환
|
||||
if wanted and next(wanted) ~= nil then
|
||||
if wanted_active then
|
||||
for n, _ in pairs(wanted) do
|
||||
if counts[n] and counts[n] > 0 then
|
||||
local parts = {}
|
||||
for name, count in pairs(counts) do parts[#parts+1] = name .. "=" .. count end
|
||||
return "FOUND:" .. string.format("%.0f,%.0f", pos.x, pos.y) .. "|" .. table.concat(parts, ",")
|
||||
return "FOUND:" .. string.format("%.0f,%.0f", best_x, best_y) .. "|" .. table.concat(parts, ",")
|
||||
end
|
||||
end
|
||||
return "UNWANTED:" .. string.format("%.0f,%.0f", pos.x, pos.y)
|
||||
@@ -90,7 +114,7 @@ local ok, data = pcall(function()
|
||||
|
||||
local parts = {}
|
||||
for name, count in pairs(counts) do parts[#parts+1] = name .. "=" .. count end
|
||||
return "FOUND:" .. string.format("%.0f,%.0f", pos.x, pos.y) .. "|" .. table.concat(parts, ",")
|
||||
return "FOUND:" .. string.format("%.0f,%.0f", best_x, best_y) .. "|" .. table.concat(parts, ",")
|
||||
end)
|
||||
if ok then rcon.print(data) else rcon.print("ERROR") end
|
||||
""")
|
||||
@@ -98,6 +122,39 @@ if ok then rcon.print(data) else rcon.print("ERROR") end
|
||||
if result.startswith("FOUND:"):
|
||||
self.rcon.lua(P + "p.walking_state = {walking = false, direction = defines.direction.north}")
|
||||
parts = result.replace("FOUND:", "").split("|")
|
||||
# FOUND: "tx,ty|name=count,name2=count" 형태
|
||||
try:
|
||||
loc = parts[0]
|
||||
lx, ly = loc.split(",", 1)
|
||||
tile_x = int(float(lx))
|
||||
tile_y = int(float(ly))
|
||||
except Exception:
|
||||
tile_x, tile_y = None, None
|
||||
|
||||
counts: dict[str, int] = {}
|
||||
if len(parts) > 1 and parts[1]:
|
||||
for seg in parts[1].split(","):
|
||||
if "=" not in seg:
|
||||
continue
|
||||
k, v = seg.split("=", 1)
|
||||
try:
|
||||
counts[k] = int(v)
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
ores_to_store = wanted_ores if wanted_ores else list(counts.keys())
|
||||
if tile_x is not None and tile_y is not None and ores_to_store:
|
||||
mem = load_ore_patch_memory()
|
||||
updated = False
|
||||
for ore in ores_to_store:
|
||||
c = counts.get(ore)
|
||||
if c is None or c <= 0:
|
||||
continue
|
||||
mem = update_ore_patch_memory(mem, ore, tile_x, tile_y, count=c)
|
||||
updated = True
|
||||
if updated:
|
||||
save_ore_patch_memory(mem)
|
||||
|
||||
return True, f"자원 발견! 위치({parts[0]}), 자원: {parts[1] if len(parts)>1 else ''}"
|
||||
if result.startswith("UNWANTED:"):
|
||||
# 원하는 자원이 아니면 계속 걷기
|
||||
@@ -333,13 +390,37 @@ local have = inv.get_item_count("{name}")
|
||||
if have < 1 then rcon.print("NO_ITEM") return end
|
||||
local dist = math.sqrt(({x} - p.position.x)^2 + ({y} - p.position.y)^2)
|
||||
if dist > p.build_distance + 2 then rcon.print("TOO_FAR:" .. string.format("%.1f", dist)) return end
|
||||
p.cursor_stack.set_stack({{name="{name}", count=1}})
|
||||
local built = p.build_from_cursor{{position = {{{x}, {y}}}, direction = {lua_dir}}}
|
||||
p.cursor_stack.clear()
|
||||
if built then rcon.print("OK") else rcon.print("BLOCKED") end
|
||||
|
||||
-- (x,y)가 자원 패치 위/겹침 등으로 배치 불가인 경우가 있어,
|
||||
-- 인접 타일을 can_place_entity로 먼저 확인 후 성공 좌표를 사용한다.
|
||||
local ox, oy = {x}, {y}
|
||||
local candidates = {{
|
||||
{{ox, oy}},
|
||||
{{ox+1, oy}}, {{ox-1, oy}}, {{ox, oy+1}}, {{ox, oy-1}},
|
||||
{{ox+1, oy+1}}, {{ox-1, oy-1}}, {{ox+1, oy-1}}, {{ox-1, oy+1}}
|
||||
}}
|
||||
|
||||
for _, pos in ipairs(candidates) do
|
||||
local cx, cy = pos[1], pos[2]
|
||||
local can = p.surface.can_place_entity{{name="{name}", position={{cx, cy}}, direction={lua_dir}}}
|
||||
if can then
|
||||
p.cursor_stack.set_stack{{name="{name}", count=1}}
|
||||
local built = p.build_from_cursor{{position = {{cx, cy}}, direction = {lua_dir}}}
|
||||
p.cursor_stack.clear()
|
||||
if built then
|
||||
rcon.print(string.format("OK:%.0f,%.0f", cx, cy))
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
rcon.print("BLOCKED")
|
||||
""")
|
||||
if not result or result in ("NO_PLAYER", "NO_CHARACTER"): return False, result or "플레이어 없음"
|
||||
if result == "OK": return True, f"{name} 배치 ({x},{y})"
|
||||
elif result.startswith("OK:"):
|
||||
coords = result.split(":", 1)[1]
|
||||
return True, f"{name} 배치 ({coords})"
|
||||
elif result == "NO_ITEM": return False, f"인벤토리에 {name} 없음"
|
||||
elif result.startswith("TOO_FAR"): return False, f"너무 멀음 - move 먼저"
|
||||
elif result == "BLOCKED": return False, f"배치 불가"
|
||||
|
||||
Reference in New Issue
Block a user