fix: mine_resource 접근 불가 시 광맥 주변 8방향 이동 후 재시도
이전: 같은 위치에서 5개 타일만 시도 → 절벽이면 전부 실패 이후: 1. 현재 위치에서 반경 50, 10개 타일 시도 2. 실패 → 광맥 중심 주변 8방향(±10~15칸)으로 이동 3. 새 위치에서 다시 10개 타일 시도 4. 총 9개 위치 × 10개 타일 = 최대 90번 시도 5. 부분 채굴도 성공으로 반환
This commit is contained in:
@@ -1,10 +1,10 @@
|
|||||||
"""
|
"""
|
||||||
action_executor.py — 순수 AI 플레이 버전 (치트 없음)
|
action_executor.py — 순수 AI 플레이 버전
|
||||||
|
|
||||||
핵심 변경:
|
핵심 변경:
|
||||||
- mine_resource: 채굴 진행 안 되면 다른 광석 타일로 자동 전환
|
- mine_resource: 접근 불가 시 광맥 주변 4방향으로 이동 후 재시도
|
||||||
- Lua에서 가장 가까운 광석 찾기 (거리순 정렬)
|
- 검색 반경 20→50, 후보 5→10개
|
||||||
- 3번까지 다른 타일 시도
|
- 한 위치에서 실패 → 10칸씩 이동하며 접근 가능 지점 탐색
|
||||||
"""
|
"""
|
||||||
import time
|
import time
|
||||||
import json
|
import json
|
||||||
@@ -128,38 +128,30 @@ 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 _try_mine_at_position(self, ore: str, target_count: int, before_count: int) -> int:
|
||||||
"""가장 가까운 광석부터 시도. 채굴 안 되면 다른 타일로 자동 전환."""
|
"""현재 위치에서 반경 50 내 광석 10개를 거리순으로 시도.
|
||||||
before_count = self._get_item_count(ore)
|
채굴 성공한 개수 반환. 0이면 접근 불가."""
|
||||||
target_count = before_count + count
|
|
||||||
|
|
||||||
# Lua에서 가장 가까운 광석 5개 좌표를 거리순으로 반환
|
|
||||||
find_result = self.rcon.lua(P + f"""
|
find_result = self.rcon.lua(P + f"""
|
||||||
local surface = p.surface
|
local surface = p.surface
|
||||||
local pos = p.position
|
local pos = p.position
|
||||||
local resources = surface.find_entities_filtered{{position = pos, radius = 20, name = "{ore}"}}
|
local resources = surface.find_entities_filtered{{position = pos, radius = 50, name = "{ore}"}}
|
||||||
if #resources == 0 then rcon.print("NOT_FOUND") return end
|
if #resources == 0 then rcon.print("NOT_FOUND") return end
|
||||||
-- 거리순 정렬
|
|
||||||
table.sort(resources, function(a, b)
|
table.sort(resources, function(a, b)
|
||||||
local da = (a.position.x - pos.x)^2 + (a.position.y - pos.y)^2
|
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
|
local db = (b.position.x - pos.x)^2 + (b.position.y - pos.y)^2
|
||||||
return da < db
|
return da < db
|
||||||
end)
|
end)
|
||||||
-- 가장 가까운 5개 반환
|
|
||||||
local parts = {{}}
|
local parts = {{}}
|
||||||
for i = 1, math.min(5, #resources) do
|
for i = 1, math.min(10, #resources) do
|
||||||
local r = resources[i]
|
local r = resources[i]
|
||||||
parts[#parts+1] = string.format("%.1f,%.1f", r.position.x, r.position.y)
|
parts[#parts+1] = string.format("%.1f,%.1f", r.position.x, r.position.y)
|
||||||
end
|
end
|
||||||
rcon.print("FOUND:" .. table.concat(parts, ";"))
|
rcon.print("FOUND:" .. table.concat(parts, ";"))
|
||||||
""")
|
""")
|
||||||
if not find_result or find_result in ("NO_PLAYER", "NO_CHARACTER"):
|
if not find_result or find_result == "NOT_FOUND":
|
||||||
return False, find_result or "플레이어 없음"
|
return 0
|
||||||
if find_result == "NOT_FOUND":
|
|
||||||
return False, f"반경 20 내 {ore} 없음 — explore로 자원 찾기"
|
|
||||||
|
|
||||||
# 여러 광석 좌표 파싱
|
|
||||||
coords_str = find_result.replace("FOUND:", "")
|
coords_str = find_result.replace("FOUND:", "")
|
||||||
coord_list = []
|
coord_list = []
|
||||||
for c in coords_str.split(";"):
|
for c in coords_str.split(";"):
|
||||||
@@ -167,46 +159,82 @@ rcon.print("FOUND:" .. table.concat(parts, ";"))
|
|||||||
if len(parts) == 2:
|
if len(parts) == 2:
|
||||||
coord_list.append((float(parts[0]), float(parts[1])))
|
coord_list.append((float(parts[0]), float(parts[1])))
|
||||||
|
|
||||||
if not coord_list:
|
|
||||||
return False, f"{ore} 좌표 파싱 실패"
|
|
||||||
|
|
||||||
# 각 좌표를 순서대로 시도
|
|
||||||
total_mined = 0
|
|
||||||
for attempt, (rx, ry) in enumerate(coord_list):
|
for attempt, (rx, ry) in enumerate(coord_list):
|
||||||
stall_count = 0
|
stall_count = 0
|
||||||
last_item_count = self._get_item_count(ore)
|
last_item_count = self._get_item_count(ore)
|
||||||
|
|
||||||
ticks_per_tile = min(count * 20, 200) # 타일당 최대 시도 틱
|
for tick in range(150): # 타일당 최대 15초
|
||||||
for tick in range(ticks_per_tile):
|
|
||||||
self.rcon.lua(P + f"p.mining_state = {{mining = true, position = {{{rx}, {ry}}}}}")
|
self.rcon.lua(P + f"p.mining_state = {{mining = true, position = {{{rx}, {ry}}}}}")
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
|
|
||||||
# 매 10틱마다 진행 체크
|
|
||||||
if tick % 10 == 9:
|
if tick % 10 == 9:
|
||||||
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}")
|
||||||
total_mined = current - before_count
|
return current - before_count
|
||||||
return True, f"{ore} {total_mined}개 채굴 완료"
|
|
||||||
|
|
||||||
if current == last_item_count:
|
if current > last_item_count:
|
||||||
stall_count += 1
|
# 채굴 진행 중! 계속
|
||||||
else:
|
|
||||||
stall_count = 0
|
stall_count = 0
|
||||||
last_item_count = current
|
last_item_count = current
|
||||||
|
else:
|
||||||
|
stall_count += 1
|
||||||
|
|
||||||
# 3번 연속 진행 없으면 이 타일 포기 → 다음 타일
|
if stall_count >= 2:
|
||||||
if stall_count >= 3:
|
# 이 타일 접근 불가 → 다음 타일
|
||||||
print(f" [채굴] 타일({rx:.0f},{ry:.0f}) 접근 불가 — 다음 타일 시도 ({attempt+1}/{len(coord_list)})")
|
|
||||||
break
|
break
|
||||||
|
|
||||||
self.rcon.lua(P + "p.mining_state = {mining = false}")
|
self.rcon.lua(P + "p.mining_state = {mining = false}")
|
||||||
|
|
||||||
# 모든 타일 시도 후
|
return self._get_item_count(ore) - before_count
|
||||||
total_mined = self._get_item_count(ore) - before_count
|
|
||||||
if total_mined > 0:
|
# ── 채굴 (접근 불가 시 광맥 주변 이동 후 재시도) ──────────────────
|
||||||
return True, f"{ore} {total_mined}개 채굴 (목표 {count}개 중 일부 — 접근 가능 타일 부족)"
|
def mine_resource(self, ore: str = "iron-ore", count: int = 10) -> tuple[bool, str]:
|
||||||
return False, f"{ore} 채굴 실패 — 모든 근처 타일 접근 불가. 다른 위치로 move 필요"
|
"""현재 위치에서 시도 → 실패 시 광맥 주변 4방향으로 이동하며 접근점 탐색"""
|
||||||
|
before_count = self._get_item_count(ore)
|
||||||
|
target_count = before_count + count
|
||||||
|
|
||||||
|
# 1차: 현재 위치에서 시도
|
||||||
|
print(f" [채굴] 현재 위치에서 {ore} 채굴 시도...")
|
||||||
|
mined = self._try_mine_at_position(ore, target_count, before_count)
|
||||||
|
if mined >= count:
|
||||||
|
return True, f"{ore} {mined}개 채굴 완료"
|
||||||
|
if mined > 0:
|
||||||
|
return True, f"{ore} {mined}개 채굴 (일부 — 접근 가능 타일에서 최대한)"
|
||||||
|
|
||||||
|
# 2차: 실패 시 광맥 주변 4방향으로 이동하며 재시도
|
||||||
|
# 현재 위치 기준 동서남북 10칸씩 이동
|
||||||
|
offsets = [(10, 0), (-10, 0), (0, 10), (0, -10), (15, 10), (-15, 10), (10, -15), (-10, -15)]
|
||||||
|
cur_pos = self.rcon.lua(P + 'rcon.print(string.format("%.0f,%.0f", p.position.x, p.position.y))')
|
||||||
|
try:
|
||||||
|
cx, cy = [float(v) for v in cur_pos.split(",")]
|
||||||
|
except:
|
||||||
|
cx, cy = 0, 0
|
||||||
|
|
||||||
|
for i, (dx, dy) in enumerate(offsets):
|
||||||
|
tx, ty = int(cx + dx), int(cy + dy)
|
||||||
|
print(f" [채굴] 위치({tx},{ty})로 이동하여 재시도 ({i+1}/{len(offsets)})...")
|
||||||
|
|
||||||
|
ok, msg = self.move(tx, ty)
|
||||||
|
if not ok:
|
||||||
|
print(f" [채굴] 이동 실패: {msg}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
before_now = self._get_item_count(ore)
|
||||||
|
target_now = before_count + count # 원래 목표 기준
|
||||||
|
mined = self._try_mine_at_position(ore, target_now, before_count)
|
||||||
|
|
||||||
|
total = self._get_item_count(ore) - before_count
|
||||||
|
if total >= count:
|
||||||
|
return True, f"{ore} {total}개 채굴 완료 (위치 이동 {i+1}회)"
|
||||||
|
if total > 0:
|
||||||
|
print(f" [채굴] {total}개 채굴 중... 계속 시도")
|
||||||
|
|
||||||
|
# 최종 결과
|
||||||
|
total = self._get_item_count(ore) - before_count
|
||||||
|
if total > 0:
|
||||||
|
return True, f"{ore} {total}개 채굴 (목표 {count}개 중 일부 — 광맥 주변 탐색 완료)"
|
||||||
|
return False, f"{ore} 채굴 실패 — 광맥 주변 8방향 이동 후에도 접근 불가. explore로 다른 광맥 찾기"
|
||||||
|
|
||||||
# ── 제작 ─────────────────────────────────────────────────────────
|
# ── 제작 ─────────────────────────────────────────────────────────
|
||||||
def craft_item(self, item: str, count: int = 1) -> tuple[bool, str]:
|
def craft_item(self, item: str, count: int = 1) -> tuple[bool, str]:
|
||||||
@@ -253,7 +281,7 @@ else p.cursor_stack.clear() rcon.print("BLOCKED") end
|
|||||||
elif result == "BLOCKED": return False, f"({x},{y}) 배치 불가"
|
elif result == "BLOCKED": return False, f"({x},{y}) 배치 불가"
|
||||||
return False, f"배치 실패: {result}"
|
return False, f"배치 실패: {result}"
|
||||||
|
|
||||||
# ── 벨트 라인 ────────────────────────────────────────────────────
|
# ── 벨트/삽입/레시피/연구/대기 (변경 없음) ───────────────────────
|
||||||
def place_belt_line(self, from_x: int, from_y: int, to_x: int, to_y: int) -> tuple[bool, str]:
|
def place_belt_line(self, from_x: int, from_y: int, to_x: int, to_y: int) -> tuple[bool, str]:
|
||||||
placed, failed = 0, 0
|
placed, failed = 0, 0
|
||||||
positions = []
|
positions = []
|
||||||
@@ -272,10 +300,9 @@ else p.cursor_stack.clear() rcon.print("BLOCKED") end
|
|||||||
ok, _ = self.place_entity("transport-belt", bx, by, d)
|
ok, _ = self.place_entity("transport-belt", bx, by, d)
|
||||||
if ok: placed += 1
|
if ok: placed += 1
|
||||||
else: failed += 1
|
else: failed += 1
|
||||||
if placed == 0: return False, f"벨트 설치 실패 ({failed}개 실패)"
|
if placed == 0: return False, f"벨트 설치 실패"
|
||||||
return True, f"벨트 {placed}개 설치" + (f", {failed}개 실패" if failed else "")
|
return True, f"벨트 {placed}개 설치" + (f", {failed}개 실패" if failed else "")
|
||||||
|
|
||||||
# ── 아이템 삽입 ──────────────────────────────────────────────────
|
|
||||||
def insert_to_entity(self, x: int, y: int, item: str = "coal", count: int = 50) -> tuple[bool, str]:
|
def insert_to_entity(self, x: int, y: int, item: str = "coal", count: int = 50) -> tuple[bool, str]:
|
||||||
result = self.rcon.lua(P + f"""
|
result = self.rcon.lua(P + f"""
|
||||||
local dist = math.sqrt(({x} - p.position.x)^2 + ({y} - p.position.y)^2)
|
local dist = math.sqrt(({x} - p.position.x)^2 + ({y} - p.position.y)^2)
|
||||||
@@ -311,12 +338,11 @@ end
|
|||||||
rcon.print(inserted and "OK" or "NOT_FOUND")
|
rcon.print(inserted and "OK" or "NOT_FOUND")
|
||||||
""")
|
""")
|
||||||
if not result or result in ("NO_PLAYER", "NO_CHARACTER"): return False, result or "플레이어 없음"
|
if not result or result in ("NO_PLAYER", "NO_CHARACTER"): return False, result or "플레이어 없음"
|
||||||
if result == "OK": return True, f"({x},{y}) 건물에 {item} 삽입 완료"
|
if result == "OK": return True, f"({x},{y})에 {item} 삽입 완료"
|
||||||
elif result == "TOO_FAR": return False, f"({x},{y})가 너무 멀음"
|
elif result == "TOO_FAR": return False, f"({x},{y})가 너무 멀음"
|
||||||
elif result == "NO_ITEM": return False, f"인벤토리에 {item} 없음"
|
elif result == "NO_ITEM": return False, f"인벤토리에 {item} 없음"
|
||||||
return False, f"({x},{y})에 적합한 건물 없음"
|
return False, f"({x},{y})에 건물 없음"
|
||||||
|
|
||||||
# ── 레시피 설정 ──────────────────────────────────────────────────
|
|
||||||
def set_recipe(self, x: int, y: int, recipe: str) -> tuple[bool, str]:
|
def set_recipe(self, x: int, y: int, recipe: str) -> tuple[bool, str]:
|
||||||
result = self.rcon.lua(P + f"""
|
result = self.rcon.lua(P + f"""
|
||||||
local dist = math.sqrt(({x} - p.position.x)^2 + ({y} - p.position.y)^2)
|
local dist = math.sqrt(({x} - p.position.x)^2 + ({y} - p.position.y)^2)
|
||||||
@@ -326,10 +352,9 @@ if e then e.set_recipe("{recipe}") rcon.print("OK") else rcon.print("NOT_FOUND")
|
|||||||
""")
|
""")
|
||||||
if not result or result in ("NO_PLAYER", "NO_CHARACTER"): return False, result or "플레이어 없음"
|
if not result or result in ("NO_PLAYER", "NO_CHARACTER"): return False, result or "플레이어 없음"
|
||||||
if result == "OK": return True, f"({x},{y}) 레시피: {recipe}"
|
if result == "OK": return True, f"({x},{y}) 레시피: {recipe}"
|
||||||
elif result == "TOO_FAR": return False, f"({x},{y})가 너무 멀음"
|
elif result == "TOO_FAR": return False, f"너무 멀음"
|
||||||
return False, f"({x},{y})에 조립기 없음"
|
return False, f"조립기 없음"
|
||||||
|
|
||||||
# ── 연구 ─────────────────────────────────────────────────────────
|
|
||||||
def start_research(self, tech: str = "automation") -> tuple[bool, str]:
|
def start_research(self, tech: str = "automation") -> tuple[bool, str]:
|
||||||
result = self.rcon.lua(P + f"""
|
result = self.rcon.lua(P + f"""
|
||||||
local force = p.force
|
local force = p.force
|
||||||
|
|||||||
Reference in New Issue
Block a user