fix: mine_resource 실패한 타일 좌표 기억 + Lua에서 제외

근본 원인: 매번 같은 타일(388,2)을 찾아서 무한 반복
수정:
1. Python에서 failed_positions set() 유지
2. Lua에 exclude 테이블 전달 → 실패한 좌표 건너뛰기
3. 접근 불가 타일은 즉시 제외 목록에 추가
4. 고갈된 타일도 제외
5. 최대 15개 다른 타일 순서대로 시도
6. ALL_EXCLUDED 시 명확한 메시지 반환
This commit is contained in:
2026-03-25 20:59:03 +09:00
parent 1c5c0e0a5a
commit bc7bb4d1e6

View File

@@ -2,9 +2,8 @@
action_executor.py — 순수 AI 플레이 버전 action_executor.py — 순수 AI 플레이 버전
핵심 수정: 핵심 수정:
- mine_resource: 광석 타일로 먼저 WALK → 도착 후 mining_state - mine_resource: 실패한 타일 좌표를 기억하고 Lua에 전달해서 제외
- 손 채굴 거리 2.7타일 이내에서만 채굴 가능 - 매 라운드마다 다른 타일을 찾도록 보장
- 가장 가까운 광석으로 걸어가서 채굴 → 고갈 시 다음 타일로 이동
""" """
import time import time
from factorio_rcon import FactorioRCON from factorio_rcon import FactorioRCON
@@ -80,7 +79,7 @@ if ok then rcon.print(data) else rcon.print("ERROR") end
last_pos = pos_str last_pos = pos_str
if stuck_count >= 3: if stuck_count >= 3:
self.rcon.lua(P + "p.walking_state = {walking = false, direction = defines.direction.north}") self.rcon.lua(P + "p.walking_state = {walking = false, direction = defines.direction.north}")
return False, f"장애물에 막힘 위치({pos_str}) — 다른 방향 시도" return False, f"장애물에 막힘 ({pos_str}) — 다른 방향 시도"
self.rcon.lua(P + "p.walking_state = {walking = false, direction = defines.direction.north}") self.rcon.lua(P + "p.walking_state = {walking = false, direction = defines.direction.north}")
return False, f"{direction} 방향 자원 미발견 — 다른 방향 시도" return False, f"{direction} 방향 자원 미발견 — 다른 방향 시도"
@@ -126,56 +125,80 @@ rcon.print("WALK:" .. string.format("%.1f", dist))
self.rcon.lua(P + "p.walking_state = {walking = false, direction = defines.direction.north}") self.rcon.lua(P + "p.walking_state = {walking = false, direction = defines.direction.north}")
return False, f"({x},{y}) 시간초과 (거리: {last_dist:.0f})" return False, f"({x},{y}) 시간초과 (거리: {last_dist:.0f})"
# ── 채굴 (광석으로 걸어간 뒤 채굴) ────────────────────────────── # ── 채굴 (실패 타일 제외 + 걸어간 뒤 채굴) ──────────────────────
def mine_resource(self, ore: str = "iron-ore", count: int = 10) -> tuple[bool, str]: def mine_resource(self, ore: str = "iron-ore", count: int = 10) -> tuple[bool, str]:
"""1. 가까운 광석 찾기 → 2. 그 위치로 걸어가기 → 3. 채굴. """1. 가까운 광석 찾기 (실패한 좌표 제외)
한 타일 고갈 시 다음 가까운 타일로 자동 이동.""" 2. 그 위치로 걸어가기
3. 채굴 시도
4. 실패 시 해당 좌표를 제외 목록에 추가 → 반복"""
before_count = self._get_item_count(ore) before_count = self._get_item_count(ore)
target_count = before_count + count target_count = before_count + count
total_mined = 0 total_mined = 0
failed_positions = set() # 실패한 좌표 기억
for round_num in range(15): # 최대 15번 시도
# 1. Lua에서 가장 가까운 광석 찾기 (실패한 좌표 제외)
# 제외 좌표를 Lua 테이블로 전달
exclude_lua = "local exclude = {}\n"
for i, (fx, fy) in enumerate(failed_positions):
exclude_lua += f'exclude["{fx:.0f},{fy:.0f}"] = true\n'
for round_num in range(10): # 최대 10번 이동+채굴 시도
# 1. 가장 가까운 광석 위치 찾기
find_result = self.rcon.lua(P + f""" find_result = self.rcon.lua(P + f"""
{exclude_lua}
local res = p.surface.find_entities_filtered{{position = p.position, radius = 80, name = "{ore}"}} local res = p.surface.find_entities_filtered{{position = p.position, radius = 80, name = "{ore}"}}
if #res == 0 then rcon.print("NOT_FOUND") return end if #res == 0 then rcon.print("NOT_FOUND") return end
-- 거리순 정렬해서 가장 가까운 1개 -- 거리순 정렬
local closest = res[1] local pos = p.position
local min_dist = 999999 table.sort(res, function(a, b)
local da = (a.position.x - pos.x)^2 + (a.position.y - pos.y)^2
local db = (b.position.x - pos.x)^2 + (b.position.y - pos.y)^2
return da < db
end)
-- 제외 목록에 없는 가장 가까운 광석 찾기
for _, e in ipairs(res) do for _, e in ipairs(res) do
local d = (e.position.x - p.position.x)^2 + (e.position.y - p.position.y)^2 local key = string.format("%.0f,%.0f", e.position.x, e.position.y)
if d < min_dist then min_dist = d closest = e end if not exclude[key] then
rcon.print(string.format("%.1f,%.1f", e.position.x, e.position.y))
return
end
end end
rcon.print(string.format("%.1f,%.1f", closest.position.x, closest.position.y)) rcon.print("ALL_EXCLUDED")
""") """)
if not find_result or find_result in ("NO_PLAYER", "NO_CHARACTER", "NOT_FOUND"): if not find_result or find_result in ("NO_PLAYER", "NO_CHARACTER"):
break
if find_result == "NOT_FOUND":
if total_mined > 0: if total_mined > 0:
return True, f"{ore} {total_mined}개 채굴 (주변 광석 소진)" return True, f"{ore} {total_mined}개 채굴 (주변 광석 소진)"
return False, f"반경 80 내 {ore} 없음 — explore로 다른 광맥 찾기" return False, f"반경 80 내 {ore} 없음 — explore로 다른 광맥 찾기"
if find_result == "ALL_EXCLUDED":
if total_mined > 0:
return True, f"{ore} {total_mined}개 채굴 (접근 가능 타일 모두 시도)"
return False, f"{ore} 근처 타일 {len(failed_positions)}개 모두 접근 불가 — 다른 위치로 이동 필요"
try: try:
parts = find_result.split(",") parts = find_result.split(",")
ox, oy = float(parts[0]), float(parts[1]) ox, oy = float(parts[0]), float(parts[1])
except: except:
return False, f"광석 좌표 파싱 실패: {find_result}" return False, f"좌표 파싱 실패: {find_result}"
# 2. 광석 위치로 걸어가기 # 2. 광석 위치로 걸어가기
print(f" [채굴] 광석({ox:.0f},{oy:.0f})으로 이동...") print(f" [채굴] 광석({ox:.0f},{oy:.0f})으로 이동... (시도 {round_num+1}, 제외: {len(failed_positions)}개)")
ok, msg = self.move(int(ox), int(oy)) ok, msg = self.move(int(ox), int(oy))
if not ok: if not ok:
print(f" [채굴] 이동 실패: {msg} — 다음 시도") print(f" [채굴] 이동 실패: {msg}")
# 이동 실패해도 현재 위치에서 채굴 시도 failed_positions.add((ox, oy))
pass continue
# 3. 현재 위치에서 반경 3 이내 광석 채굴 (손 채굴 거리) # 3. 현재 위치에서 채굴 시도
stall_count = 0 stall_count = 0
last_item = self._get_item_count(ore) last_item = self._get_item_count(ore)
mined_this_tile = False
for tick in range(300): # 최대 30초 for tick in range(300): # 최대 30초
self.rcon.lua(P + f"p.mining_state = {{mining = true, position = {{{ox}, {oy}}}}}") self.rcon.lua(P + f"p.mining_state = {{mining = true, position = {{{ox}, {oy}}}}}")
time.sleep(0.1) time.sleep(0.1)
if tick % 8 == 7: # 매 0.8초마다 체크 if tick % 8 == 7:
current = self._get_item_count(ore) current = self._get_item_count(ore)
if current >= target_count: if current >= target_count:
self.rcon.lua(P + "p.mining_state = {mining = false}") self.rcon.lua(P + "p.mining_state = {mining = false}")
@@ -185,24 +208,32 @@ rcon.print(string.format("%.1f,%.1f", closest.position.x, closest.position.y))
if current > last_item: if current > last_item:
stall_count = 0 stall_count = 0
last_item = current last_item = current
mined_this_tile = True
else: else:
stall_count += 1 stall_count += 1
# 2번 연속 진행 없음 → 이 타일 안 닿음, 다음으로
if stall_count >= 2: if stall_count >= 2:
print(f" [채굴] 타일({ox:.0f},{oy:.0f}) 채굴 불가/고갈 — 다음 타일 이동")
break break
self.rcon.lua(P + "p.mining_state = {mining = false}") self.rcon.lua(P + "p.mining_state = {mining = false}")
total_mined = self._get_item_count(ore) - before_count total_mined = self._get_item_count(ore) - before_count
if not mined_this_tile:
# 이 타일에서 한 개도 못 캤음 → 접근 불가
failed_positions.add((ox, oy))
print(f" [채굴] ({ox:.0f},{oy:.0f}) 접근 불가 → 제외 목록 추가 (총 {len(failed_positions)}개)")
else:
# 채굴 됐지만 타일 고갈
print(f" [채굴] ({ox:.0f},{oy:.0f}) 고갈/중단. 현재 {total_mined}개 채굴됨")
failed_positions.add((ox, oy)) # 고갈된 것도 제외
if total_mined >= count: if total_mined >= count:
return True, f"{ore} {total_mined}개 채굴 완료" return True, f"{ore} {total_mined}개 채굴 완료"
# 10라운드 후 # 모든 라운드 후
if total_mined > 0: if total_mined > 0:
return True, f"{ore} {total_mined}개 채굴 (목표 {count}개 중 일부)" return True, f"{ore} {total_mined}개 채굴 (목표 {count}개 중 일부)"
return False, f"{ore} 채굴 실패 — 접근 가능한 광석 없음" return False, f"{ore} 채굴 실패 — {len(failed_positions)}개 타일 접근 불가"
# ── 제작/배치/삽입/레시피/연구/대기 ────────────────────────────── # ── 제작/배치/삽입/레시피/연구/대기 ──────────────────────────────
def craft_item(self, item: str, count: int = 1) -> tuple[bool, str]: def craft_item(self, item: str, count: int = 1) -> tuple[bool, str]: