improved local agent, usage
This commit is contained in:
parent
fe50196dab
commit
a65acbfa9e
|
|
@ -44,3 +44,9 @@ Run the script, then start the game from the frontend UI:
|
|||
```bash
|
||||
python bot.py
|
||||
```
|
||||
|
||||
## Example with local LLM
|
||||
|
||||
```
|
||||
$ OLLAMA_BASE_URL="http://192.168.1.220:11434" python3 ./bot.py -n myAIBot -s 2
|
||||
```
|
||||
|
|
@ -340,6 +340,52 @@ Respond ONLY with JSON: {{"form_alliance": true|false, "reasoning": "short reaso
|
|||
elif res.get("party_formed_triggered"):
|
||||
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(
|
||||
self,
|
||||
radar_res: Dict[str, Any],
|
||||
|
|
@ -350,12 +396,14 @@ Respond ONLY with JSON: {{"form_alliance": true|false, "reasoning": "short reaso
|
|||
targets_summary = [
|
||||
{
|
||||
"name": t["name"],
|
||||
"x": t["x"],
|
||||
"y": t["y"],
|
||||
"distance": t["distance"],
|
||||
"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),
|
||||
}
|
||||
for t in radar_res.get("targets", [])[:6]
|
||||
for t in radar_res.get("targets", [])
|
||||
]
|
||||
moves_summary = {
|
||||
d: {
|
||||
|
|
@ -367,17 +415,31 @@ Respond ONLY with JSON: {{"form_alliance": true|false, "reasoning": "short reaso
|
|||
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}
|
||||
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')},
|
||||
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
|
||||
squeezing past obstacles: {json.dumps(moves_summary)}
|
||||
|
||||
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
|
||||
is nearby). You MUST pick a key from the legal moves object above.
|
||||
your party, avoid stronger hostile parties, route around obstacles visible on the map, minimize
|
||||
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"}}
|
||||
"""
|
||||
|
|
|
|||
Loading…
Reference in New Issue