From 5fb7e0710365cad21ca8285904658e3617d580b5 Mon Sep 17 00:00:00 2001 From: Isaac Johnson Date: Sat, 5 Sep 2026 20:04:12 -0500 Subject: [PATCH] Update external bot agent with obstacle-aware distance navigation and safe pass fallback --- botagent/bot_agent.py | 47 +++++++++++++++++++++++++++++++++---------- 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/botagent/bot_agent.py b/botagent/bot_agent.py index 5918504..60add15 100644 --- a/botagent/bot_agent.py +++ b/botagent/bot_agent.py @@ -1,5 +1,6 @@ """External Bot Agent with deliberate decision-making logic: - Evaluates targets via Radar sensor. +- Intelligently navigates around impassable obstacles (mountains and forests). - Decides whether to negotiate party formation or refuse & fight based on strength. - Explicitly engages in 3-bout D20 battles when adjacent to an opposing party/refusing bot. - Parses battle results, bout rolls, scores, and absorbed members. @@ -138,10 +139,11 @@ class SmartBotAgent: party = res.json() print(f"✅ [PARTY FORMED] Squad '{party['name']}' established! Leader: {party['leader_name']} | Str: {party['total_strength']}") else: - # Fallback: move into adjacent square to trigger engine merge - self._pass_or_step() + # Fallback: pass turn if party creation rejected + requests.post(f"{BASE_URL}/players/{self.bot_id}/pass") except Exception as e: print(f"Party formation error: {e}") + requests.post(f"{BASE_URL}/players/{self.bot_id}/pass") def _initiate_battle(self, opponent_id: str): """Explicitly call the 3-Bout D20 Battle endpoint.""" @@ -164,33 +166,56 @@ class SmartBotAgent: else: print(f"Battle failed ({res.status_code}): {res.text}") + def _get_best_move_towards(self, target_x: int, target_y: int, moves: Dict[str, Any]) -> Optional[str]: + """Pick the available direction that minimizes Chebyshev distance to (target_x, target_y), strictly avoiding obstacles.""" + valid_moves = {d: chk for d, chk in moves.items() if chk.get("available")} + if not valid_moves: + return None + + def dist(chk: Dict[str, Any]) -> int: + return max(abs(chk["target_x"] - target_x), abs(chk["target_y"] - target_y)) + + return min(valid_moves.keys(), key=lambda d: dist(valid_moves[d])) + def _step_or_attack(self, target: Dict[str, Any]): - """Move adjacent/towards the target.""" + """Move adjacent/towards the target while avoiding obstacles.""" moves_res = requests.get(f"{BASE_URL}/players/{self.bot_id}/available-moves").json() - available = [d for d, chk in moves_res["moves"].items() if chk["available"]] - if available: - # Move towards target - chosen = available[0] + moves = moves_res.get("moves", {}) + chosen = self._get_best_move_towards(target["x"], target["y"], moves) + + if chosen: res = requests.post(f"{BASE_URL}/players/{self.bot_id}/move", json={"direction": chosen}).json() if res.get("battle_triggered"): print(f"⚔️ Move triggered battle! Winner: {res['battle_result']['winner_party_name']}") elif res.get("party_formed_triggered"): print(f"🤝 Move resulted in party alliance!") else: + print("⚠️ No passable moves adjacent to target (terrain/border constraint). Passing turn.") requests.post(f"{BASE_URL}/players/{self.bot_id}/pass") def _navigate_towards_goal(self, radar_res: Dict[str, Any]): - """Move towards the nearest target or explore unvisited areas.""" + """Move towards the nearest target routing around obstacles.""" rec_dir = radar_res.get("recommended_direction") + nearest = radar_res.get("nearest_target") moves_res = requests.get(f"{BASE_URL}/players/{self.bot_id}/available-moves").json() - available = [d for d, chk in moves_res["moves"].items() if chk["available"]] + moves = moves_res.get("moves", {}) + available = [d for d, chk in moves.items() if chk.get("available")] if not available: - print("🚫 No available moves. Passing turn.") + print("🚫 All adjacent paths blocked by borders or obstacle terrain (mountains/forests). Passing turn.") requests.post(f"{BASE_URL}/players/{self.bot_id}/pass") return - chosen_dir = rec_dir if rec_dir in available else available[0] + # 1. Prefer radar's obstacle-aware BFS pathfinder direction + if rec_dir and rec_dir in available: + chosen_dir = rec_dir + # 2. Otherwise pick the available direction that minimizes distance to the nearest target + elif nearest: + chosen_dir = self._get_best_move_towards(nearest["x"], nearest["y"], moves) or available[0] + # 3. Fallback to any valid open terrain cell + else: + chosen_dir = available[0] + print(f"🧭 Moving {chosen_dir} (Goal: {radar_res.get('bot_goal')}, Action: {radar_res.get('recommended_action')})") res = requests.post(f"{BASE_URL}/players/{self.bot_id}/move", json={"direction": chosen_dir}).json()