improved local agent, usage

This commit is contained in:
Isaac Johnson 2026-09-06 16:25:35 -05:00
parent fe50196dab
commit a65acbfa9e
2 changed files with 74 additions and 6 deletions

View File

@ -44,3 +44,9 @@ Run the script, then start the game from the frontend UI:
```bash ```bash
python bot.py python bot.py
``` ```
## Example with local LLM
```
$ OLLAMA_BASE_URL="http://192.168.1.220:11434" python3 ./bot.py -n myAIBot -s 2
```

View File

@ -340,6 +340,52 @@ Respond ONLY with JSON: {{"form_alliance": true|false, "reasoning": "short reaso
elif res.get("party_formed_triggered"): elif res.get("party_formed_triggered"):
print(f"🤝 Formed or joined squad: {res.get('formed_party', {}).get('name')}") print(f"🤝 Formed or joined squad: {res.get('formed_party', {}).get('name')}")
def _get_board_snapshot(self) -> Optional[Dict[str, Any]]:
"""Fetch the full board (players, parties, obstacles) so the LLM sees more than radar's nearest few."""
try:
res = requests.get(f"{self.base_url}/board", timeout=10)
res.raise_for_status()
return res.json()
except requests.exceptions.RequestException as e:
print(f"⚠️ [BOARD FETCH ERROR] {e}")
return None
OBSTACLE_SYMBOLS = {"mountain": "M", "forest": "F", "valley": "V"}
LOCAL_MAP_RADIUS = 8
def _build_local_map(self, board: Dict[str, Any], center_x: int, center_y: int) -> List[str]:
"""Render an ASCII minimap centered on the bot: @ = self, A = ally, E = enemy, M/F/V = obstacles, . = open."""
config = board.get("config", {})
max_x = config.get("max_x", 64)
max_y = config.get("max_y", 64)
radius = self.LOCAL_MAP_RADIUS
obstacle_at = {(o["x"], o["y"]): o.get("type", "mountain") for o in board.get("obstacles", [])}
player_at: Dict[tuple, List[Dict[str, Any]]] = {}
for p in board.get("players", []):
if p["id"] == self.bot_id:
continue
player_at.setdefault((p["x"], p["y"]), []).append(p)
rows = []
for y in range(center_y - radius, center_y + radius + 1):
row_chars = []
for x in range(center_x - radius, center_x + radius + 1):
if x == center_x and y == center_y:
row_chars.append("@")
elif x < 0 or y < 0 or x >= max_x or y >= max_y:
row_chars.append("#")
elif (x, y) in obstacle_at:
row_chars.append(self.OBSTACLE_SYMBOLS.get(obstacle_at[(x, y)], "M"))
elif (x, y) in player_at:
occupants = player_at[(x, y)]
is_ally = self.party_id and any(o.get("party_id") == self.party_id for o in occupants)
row_chars.append("A" if is_ally else "E")
else:
row_chars.append(".")
rows.append("".join(row_chars))
return rows
def _ask_llm_for_direction( def _ask_llm_for_direction(
self, self,
radar_res: Dict[str, Any], radar_res: Dict[str, Any],
@ -350,12 +396,14 @@ Respond ONLY with JSON: {{"form_alliance": true|false, "reasoning": "short reaso
targets_summary = [ targets_summary = [
{ {
"name": t["name"], "name": t["name"],
"x": t["x"],
"y": t["y"],
"distance": t["distance"], "distance": t["distance"],
"strength": t["strength"], "strength": t["strength"],
"is_party": bool(t.get("party_id")), "party": t.get("party_name") or ("solo" if not t.get("party_id") else t.get("party_id")),
"is_ally": t.get("is_ally", False), "is_ally": t.get("is_ally", False),
} }
for t in radar_res.get("targets", [])[:6] for t in radar_res.get("targets", [])
] ]
moves_summary = { moves_summary = {
d: { d: {
@ -367,17 +415,31 @@ Respond ONLY with JSON: {{"form_alliance": true|false, "reasoning": "short reaso
if d in available if d in available
} }
board = self._get_board_snapshot()
map_section = ""
if board:
local_map = self._build_local_map(board, my_info["x"], my_info["y"])
map_section = f"""
Local map (radius {self.LOCAL_MAP_RADIUS} around you, row = one Y line, top-to-bottom is
increasing Y, left-to-right is increasing X): @ = you, A = ally, E = enemy/neutral bot,
M = mountain, F = forest, V = valley, # = out of bounds, . = open ground.
{chr(10).join(local_map)}
"""
prompt = f"""{GAME_RULES_SUMMARY} prompt = f"""{GAME_RULES_SUMMARY}
You are bot "{self.name}" (strength {self.strength}, score {my_info['score']}, party: {self.party_id or 'Solo'}). You are bot "{self.name}" (strength {self.strength}, score {my_info['score']}, party: {self.party_id or 'Solo'})
at position ({my_info['x']}, {my_info['y']}).
Server's radar suggestion: recommended_direction={radar_res.get('recommended_direction')}, Server's radar suggestion: recommended_direction={radar_res.get('recommended_direction')},
recommended_action={radar_res.get('recommended_action')}, goal={radar_res.get('bot_goal')}. recommended_action={radar_res.get('recommended_action')}, goal={radar_res.get('bot_goal')}.
Nearby targets: {json.dumps(targets_summary)} {map_section}
All known bots/parties on the board (sorted nearest first): {json.dumps(targets_summary)}
Your ONLY legal moves this turn, with resulting coordinates and any strength penalty for Your ONLY legal moves this turn, with resulting coordinates and any strength penalty for
squeezing past obstacles: {json.dumps(moves_summary)} squeezing past obstacles: {json.dumps(moves_summary)}
Choose the direction that best serves your strategy (e.g. approach weaker solo bots to grow Choose the direction that best serves your strategy (e.g. approach weaker solo bots to grow
your party, avoid stronger hostile parties, minimize strength penalties, or explore if nothing your party, avoid stronger hostile parties, route around obstacles visible on the map, minimize
is nearby). You MUST pick a key from the legal moves object above. strength penalties, or explore if nothing is nearby). You MUST pick a key from the legal moves
object above.
Respond ONLY with JSON: {{"direction": "<one of {available}>", "reasoning": "short reason"}} Respond ONLY with JSON: {{"direction": "<one of {available}>", "reasoning": "short reason"}}
""" """