From 6ee4fef688ce177b67135cb2448a51d592dab988 Mon Sep 17 00:00:00 2001 From: gihyeon Date: Wed, 25 Mar 2026 20:09:02 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20Factorio=202.0=20=ED=98=B8=ED=99=98=20+?= =?UTF-8?q?=20=EC=9E=90=EC=9B=90=20=EC=8A=A4=EC=BA=94=20=EB=B0=98=EA=B2=BD?= =?UTF-8?q?=20500=20+=20type=20=EA=B8=B0=EB=B0=98=20=EA=B2=80=EC=83=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - scan_resources: radius 200→500, type="resource"로 검색 (이름 호환 문제 없음) - get_buildings: name 대신 type 기반 + pcall 안전 감싸기 - summarize_for_ai: 자원 거리 표시 추가 --- state_reader.py | 146 +++++++++++++++++++++++++++--------------------- 1 file changed, 81 insertions(+), 65 deletions(-) diff --git a/state_reader.py b/state_reader.py index fbd1f52..4baef6f 100644 --- a/state_reader.py +++ b/state_reader.py @@ -1,13 +1,16 @@ """ state_reader.py RCON을 통해 팩토리오 게임 상태를 읽어오는 모듈 -핵심 변경: game.player → game.players[1] (RCON 호환) + +핵심 변경: +- game.player → game.players[1] (RCON 호환) +- scan_resources 반경 200→500 (초반 탐색 개선) +- get_buildings: name 대신 type 기반 검색 (Factorio 2.0 호환) """ import json from factorio_rcon import FactorioRCON -# 플레이어 참조 헬퍼 (RCON에서 game.player는 nil) P = """local p = game.players[1] if not p then rcon.print("{}") return end """ @@ -18,7 +21,6 @@ class StateReader: self.rcon = rcon def get_full_state(self) -> dict: - """전체 게임 상태를 한 번에 수집""" return { "player": self.get_player_info(), "inventory": self.get_inventory(), @@ -27,7 +29,6 @@ class StateReader: "tech": self.get_research_status(), } - # ── 플레이어 ──────────────────────────────────────────────────── def get_player_info(self) -> dict: lua = P + """ rcon.print(game.table_to_json({ @@ -39,7 +40,6 @@ rcon.print(game.table_to_json({ raw = self.rcon.lua(lua) return json.loads(raw) if raw else {} - # ── 인벤토리 ──────────────────────────────────────────────────── def get_inventory(self) -> dict: lua = P + """ local inv = p.get_main_inventory() @@ -54,77 +54,85 @@ rcon.print(game.table_to_json(result)) raw = self.rcon.lua(lua) return json.loads(raw) if raw else {} - # ── 자원 패치 스캔 ────────────────────────────────────────────── - def scan_resources(self, radius: int = 200) -> dict: - """플레이어 주변 자원 패치 위치 탐색""" + def scan_resources(self, radius: int = 500) -> dict: + """플레이어 주변 자원 패치 위치 탐색 (반경 500타일)""" lua = P + f""" local surface = p.surface local center = p.position local resources = {{}} -local ore_types = {{"iron-ore", "copper-ore", "coal", "stone", "uranium-ore"}} -for _, ore in ipairs(ore_types) do - local entities = surface.find_entities_filtered{{ +-- type="resource"로 모든 자원을 한번에 검색 (이름 호환 문제 없음) +local all_res = surface.find_entities_filtered{{ area = {{ {{center.x - {radius}, center.y - {radius}}}, {{center.x + {radius}, center.y + {radius}}} }}, - name = ore - }} - if #entities > 0 then - local sx, sy = 0, 0 - for _, e in ipairs(entities) do - sx = sx + e.position.x - sy = sy + e.position.y - end - resources[ore] = {{ - count = #entities, - center_x = math.floor(sx / #entities), - center_y = math.floor(sy / #entities) - }} - end -end -rcon.print(game.table_to_json(resources)) -""" - raw = self.rcon.lua(lua) - return json.loads(raw) if raw else {} - - # ── 건물 목록 ─────────────────────────────────────────────────── - def get_buildings(self, radius: int = 300) -> dict: - lua = P + f""" -local surface = p.surface -local center = p.position - -local entity_names = {{ - "burner-mining-drill", "electric-mining-drill", - "stone-furnace", "steel-furnace", "electric-furnace", - "transport-belt", "fast-transport-belt", - "burner-inserter", "inserter", - "small-electric-pole", "medium-electric-pole", - "steam-engine", "boiler", "offshore-pump", - "assembling-machine-1", "assembling-machine-2", - "chest", "iron-chest", "steel-chest" + type = "resource" }} +-- 자원 이름별로 그룹핑 +for _, e in ipairs(all_res) do + local name = e.name + if not resources[name] then + resources[name] = {{count = 0, sx = 0, sy = 0}} + end + local r = resources[name] + r.count = r.count + 1 + r.sx = r.sx + e.position.x + r.sy = r.sy + e.position.y +end + +-- 중심점 계산 local result = {{}} -for _, name in ipairs(entity_names) do - local entities = surface.find_entities_filtered{{ - area = {{ - {{center.x - {radius}, center.y - {radius}}}, - {{center.x + {radius}, center.y + {radius}}} - }}, - name = name - }} - if #entities > 0 then - result[name] = #entities - end +for name, r in pairs(resources) do + result[name] = {{ + count = r.count, + center_x = math.floor(r.sx / r.count), + center_y = math.floor(r.sy / r.count) + }} +end +rcon.print(game.table_to_json(result)) +""" + raw = self.rcon.lua(lua) + return json.loads(raw) if raw else {} + + def get_buildings(self, radius: int = 300) -> dict: + """type 기반 검색으로 Factorio 2.0 호환""" + lua = P + f""" +local surface = p.surface +local center = p.position +local area = {{ + {{center.x - {radius}, center.y - {radius}}}, + {{center.x + {radius}, center.y + {radius}}} +}} + +-- type 기반으로 검색 (엔티티 이름 변경에 안전) +local types = {{ + "mining-drill", "furnace", "assembling-machine", + "transport-belt", "inserter", "electric-pole", + "generator", "boiler", "offshore-pump", + "lab", "radar", "container", + "ammo-turret", "electric-turret", "wall", "gate", + "oil-refinery", "chemical-plant", "mining-drill" +}} + +local result = {{}} +for _, t in ipairs(types) do + local ok, entities = pcall(function() + return surface.find_entities_filtered{{area = area, type = t}} + end) + if ok and entities and #entities > 0 then + -- 이름별로 세분화 + for _, e in ipairs(entities) do + result[e.name] = (result[e.name] or 0) + 1 + end + end end rcon.print(game.table_to_json(result)) """ raw = self.rcon.lua(lua) return json.loads(raw) if raw else {} - # ── 연구 현황 ─────────────────────────────────────────────────── def get_research_status(self) -> dict: lua = P + """ local force = p.force @@ -145,15 +153,14 @@ rcon.print(game.table_to_json({ return json.loads(raw) if raw else {} def summarize_for_ai(self, state: dict) -> str: - """AI 프롬프트용 상태 요약 텍스트 생성""" p = state.get("player", {}) inv = state.get("inventory", {}) res = state.get("resources", {}) bld = state.get("buildings", {}) lines = [ - f"## 현재 게임 상태", - f"### 플레이어", + "## 현재 게임 상태", + "### 플레이어", f"- 위치: ({p.get('x', '?')}, {p.get('y', '?')})", f"- 체력: {p.get('health', '?')}", "", @@ -175,15 +182,24 @@ rcon.print(game.table_to_json({ if not any(inv.get(item, 0) > 0 for item in key_items): lines.append("- 비어 있음") - lines += ["", "### 주변 자원 패치"] + lines += ["", "### 주변 자원 패치 (반경 500타일 스캔)"] if res: - for ore, info in res.items(): + # 거리순으로 정렬 + px = p.get('x', 0) + py = p.get('y', 0) + sorted_res = sorted( + res.items(), + key=lambda item: ((item[1]['center_x'] - px)**2 + (item[1]['center_y'] - py)**2) + ) + for ore, info in sorted_res: + dist = int(((info['center_x'] - px)**2 + (info['center_y'] - py)**2)**0.5) lines.append( f"- {ore}: {info['count']}타일 " - f"(중심: {info['center_x']}, {info['center_y']})" + f"(중심: {info['center_x']}, {info['center_y']}) " + f"[거리: ~{dist}타일]" ) else: - lines.append("- 탐색된 자원 없음") + lines.append("- 반경 500타일 내 자원 없음 — 더 멀리 탐색 필요") lines += ["", "### 건설된 건물"] if bld: