some backend rules for seeking wizard when low on health, tests, updated models in GEAR
This commit is contained in:
parent
5ed7bb927e
commit
0bc1c972b3
|
|
@ -607,10 +607,42 @@ class GameEngine:
|
|||
|
||||
nearest = primary_targets[0] if primary_targets else (targets[0] if targets else None)
|
||||
|
||||
wiz_dist = max(abs(player.x - self.wizard.x), abs(player.y - self.wizard.y))
|
||||
wiz_radar = WizardRadarTarget(
|
||||
id=self.wizard.id,
|
||||
name=self.wizard.name,
|
||||
x=self.wizard.x,
|
||||
y=self.wizard.y,
|
||||
distance=wiz_dist,
|
||||
strength=self.wizard.strength,
|
||||
can_challenge=(wiz_dist <= 1),
|
||||
)
|
||||
|
||||
rec_dir = None
|
||||
rec_act = "explore_unvisited"
|
||||
|
||||
if nearest:
|
||||
if player.health < 2 and self.wizard:
|
||||
rec_act = "seek_wizard"
|
||||
# Use BFS pathfinder to recommend direction navigating toward Gary the Wizard
|
||||
bfs_path = self._find_path_bfs((player.x, player.y), (self.wizard.x, self.wizard.y))
|
||||
if bfs_path and len(bfs_path) >= 2:
|
||||
step_x, step_y = bfs_path[1]
|
||||
dx = step_x - player.x
|
||||
dy = step_y - player.y
|
||||
else:
|
||||
dx = 1 if self.wizard.x > player.x else (-1 if self.wizard.x < player.x else 0)
|
||||
dy = 1 if self.wizard.y > player.y else (-1 if self.wizard.y < player.y else 0)
|
||||
|
||||
for name, (ox, oy) in DIRECTION_OFFSETS.items():
|
||||
if ox == dx and oy == dy and "_" in name:
|
||||
rec_dir = name
|
||||
break
|
||||
if not rec_dir:
|
||||
for name, (ox, oy) in DIRECTION_OFFSETS.items():
|
||||
if ox == dx and oy == dy:
|
||||
rec_dir = name
|
||||
break
|
||||
elif nearest:
|
||||
# Use BFS pathfinder to recommend direction navigating around obstacles!
|
||||
bfs_path = self._find_path_bfs((player.x, player.y), (nearest.x, nearest.y))
|
||||
if bfs_path and len(bfs_path) >= 2:
|
||||
|
|
@ -642,17 +674,6 @@ class GameEngine:
|
|||
else:
|
||||
rec_act = "hunt_party"
|
||||
|
||||
wiz_dist = max(abs(player.x - self.wizard.x), abs(player.y - self.wizard.y))
|
||||
wiz_radar = WizardRadarTarget(
|
||||
id=self.wizard.id,
|
||||
name=self.wizard.name,
|
||||
x=self.wizard.x,
|
||||
y=self.wizard.y,
|
||||
distance=wiz_dist,
|
||||
strength=self.wizard.strength,
|
||||
can_challenge=(wiz_dist <= 1),
|
||||
)
|
||||
|
||||
return BotRadarResponse(
|
||||
player_id=player.id,
|
||||
current_x=player.x,
|
||||
|
|
@ -1844,7 +1865,12 @@ class GameEngine:
|
|||
f"It is not your turn. Current turn belongs to '{curr_name}' ({curr_id})."
|
||||
)
|
||||
|
||||
bot_goal = "find_and_defeat_all_parties" if player.party_id else "form_party"
|
||||
seeking_wizard = bool(player.health < 2 and self.wizard)
|
||||
bot_goal = (
|
||||
"seek_wizard"
|
||||
if seeking_wizard
|
||||
else ("find_and_defeat_all_parties" if player.party_id else "form_party")
|
||||
)
|
||||
|
||||
# 1. Check if already adjacent to encounter before moving
|
||||
formed_party, battle_res = self._check_adjacent_encounter(player)
|
||||
|
|
@ -1887,7 +1913,7 @@ class GameEngine:
|
|||
effective_str = player.strength
|
||||
if player.party_id and player.party_id in self.parties:
|
||||
effective_str = self.parties[player.party_id].total_strength
|
||||
if effective_str >= self.wizard.strength or player.health >= 4:
|
||||
if effective_str >= self.wizard.strength or player.health >= 4 or player.health < 2:
|
||||
if player.health <= 5:
|
||||
bot_reward = "health"
|
||||
elif player.strength < 4.0:
|
||||
|
|
@ -1933,28 +1959,34 @@ class GameEngine:
|
|||
turn=self._get_turn_info(),
|
||||
)
|
||||
|
||||
# Find nearest target according to goal
|
||||
# Find nearest target according to goal or seek wizard if low health (< 2 HP)
|
||||
targets = []
|
||||
for other in self.players.values():
|
||||
if other.id == player.id or not other.is_alive or other.health <= 0:
|
||||
continue
|
||||
if player.party_id and player.party_id == other.party_id:
|
||||
continue
|
||||
dist = max(abs(player.x - other.x), abs(player.y - other.y))
|
||||
targets.append((dist, other))
|
||||
target_bot = None
|
||||
if not seeking_wizard:
|
||||
for other in self.players.values():
|
||||
if other.id == player.id or not other.is_alive or other.health <= 0:
|
||||
continue
|
||||
if player.party_id and player.party_id == other.party_id:
|
||||
continue
|
||||
dist = max(abs(player.x - other.x), abs(player.y - other.y))
|
||||
targets.append((dist, other))
|
||||
|
||||
targets.sort(key=lambda t: t[0])
|
||||
targets.sort(key=lambda t: t[0])
|
||||
|
||||
if bot_goal == "form_party":
|
||||
preferred = [t for t in targets if not t[1].party_id]
|
||||
target_bot = preferred[0][1] if preferred else (targets[0][1] if targets else None)
|
||||
else:
|
||||
preferred = [t for t in targets if t[1].party_id]
|
||||
target_bot = preferred[0][1] if preferred else (targets[0][1] if targets else None)
|
||||
if bot_goal == "form_party":
|
||||
preferred = [t for t in targets if not t[1].party_id]
|
||||
target_bot = preferred[0][1] if preferred else (targets[0][1] if targets else None)
|
||||
else:
|
||||
preferred = [t for t in targets if t[1].party_id]
|
||||
target_bot = preferred[0][1] if preferred else (targets[0][1] if targets else None)
|
||||
|
||||
# Check if BFS pathfinder finds optimal route around obstacles
|
||||
bfs_next_step: Optional[Tuple[int, int]] = None
|
||||
if target_bot:
|
||||
if seeking_wizard:
|
||||
path = self._find_path_bfs((player.x, player.y), (self.wizard.x, self.wizard.y))
|
||||
if path and len(path) >= 2:
|
||||
bfs_next_step = (path[1][0] - player.x, path[1][1] - player.y)
|
||||
elif target_bot:
|
||||
path = self._find_path_bfs((player.x, player.y), (target_bot.x, target_bot.y))
|
||||
if path and len(path) >= 2:
|
||||
bfs_next_step = (path[1][0] - player.x, path[1][1] - player.y)
|
||||
|
|
@ -1973,7 +2005,11 @@ class GameEngine:
|
|||
if bfs_next_step and (dx, dy) == bfs_next_step:
|
||||
score += 50.0
|
||||
|
||||
if target_bot:
|
||||
if seeking_wizard:
|
||||
old_dist = max(abs(player.x - self.wizard.x), abs(player.y - self.wizard.y))
|
||||
new_dist = max(abs(tx - self.wizard.x), abs(ty - self.wizard.y))
|
||||
score += (old_dist - new_dist) * 10.0
|
||||
elif target_bot:
|
||||
old_dist = max(abs(player.x - target_bot.x), abs(player.y - target_bot.y))
|
||||
new_dist = max(abs(tx - target_bot.x), abs(ty - target_bot.y))
|
||||
score += (old_dist - new_dist) * 10.0
|
||||
|
|
|
|||
|
|
@ -364,7 +364,7 @@ class BotRadarResponse(BaseModel):
|
|||
nearest_target: Optional[RadarTarget] = None
|
||||
wizard: Optional[WizardRadarTarget] = None
|
||||
recommended_direction: Optional[str] = None
|
||||
recommended_action: str # "seek_partner", "form_party", "hunt_party", "engage_battle", "explore_unvisited", "challenge_wizard"
|
||||
recommended_action: str # "seek_partner", "form_party", "hunt_party", "engage_battle", "explore_unvisited", "challenge_wizard", "seek_wizard"
|
||||
|
||||
|
||||
class BoardConfig(BaseModel):
|
||||
|
|
|
|||
|
|
@ -765,3 +765,92 @@ def test_player_death_rule_and_scoreboard_preservation():
|
|||
assert b3["id"] in ranking_ids
|
||||
|
||||
|
||||
|
||||
|
||||
def test_radar_seek_wizard_when_health_less_than_2():
|
||||
"""Test that when a player's health is less than 2, get_bot_radar recommends 'seek_wizard'
|
||||
and directs the player toward Gary the Wizard NPC."""
|
||||
client = TestClient(app)
|
||||
client.post("/api/reset")
|
||||
|
||||
# Register two players
|
||||
p1 = client.post("/api/players", json={"name": "HurtBot", "color": "#ef4444", "strength": 3}).json()
|
||||
p2 = client.post("/api/players", json={"name": "HealthyBot", "color": "#10b981", "strength": 3}).json()
|
||||
|
||||
# 1. Initially both players have 10 HP (>= 2), so recommended_action should NOT be seek_wizard
|
||||
radar1 = client.get(f"/api/players/{p1['id']}/radar").json()
|
||||
assert radar1["recommended_action"] != "seek_wizard"
|
||||
assert radar1["recommended_action"] in ("seek_partner", "form_party", "explore_unvisited")
|
||||
|
||||
# 2. Set p1 health to 1 (< 2)
|
||||
async def set_p1_low_health():
|
||||
b1 = await game_engine.get_player(p1["id"])
|
||||
b1.health = 1
|
||||
asyncio.run(set_p1_low_health())
|
||||
|
||||
# Verify radar recommended_action is now 'seek_wizard'
|
||||
radar_low = client.get(f"/api/players/{p1['id']}/radar").json()
|
||||
assert radar_low["recommended_action"] == "seek_wizard"
|
||||
assert radar_low["recommended_direction"] is not None
|
||||
assert "wizard" in radar_low
|
||||
assert radar_low["wizard"]["name"] == "Gary the Wizard"
|
||||
|
||||
# 3. Set p1 health to 2 (>= 2)
|
||||
async def set_p1_health_2():
|
||||
b1 = await game_engine.get_player(p1["id"])
|
||||
b1.health = 2
|
||||
asyncio.run(set_p1_health_2())
|
||||
|
||||
# Verify radar recommended_action reverts back from 'seek_wizard'
|
||||
radar_hp2 = client.get(f"/api/players/{p1['id']}/radar").json()
|
||||
assert radar_hp2["recommended_action"] != "seek_wizard"
|
||||
|
||||
|
||||
def test_step_bot_ai_seeks_and_challenges_wizard_when_low_health():
|
||||
"""Test that step_bot_ai navigates towards the wizard when HP < 2,
|
||||
and voluntarily challenges the wizard for healing upon arriving adjacent."""
|
||||
client = TestClient(app)
|
||||
client.post("/api/reset")
|
||||
|
||||
# Register a weak bot with 1 HP
|
||||
p = client.post("/api/players", json={"name": "SickBot", "color": "#10b981", "strength": 1}).json()
|
||||
wiz = client.get("/api/wizard").json()
|
||||
wx, wy = wiz["x"], wiz["y"]
|
||||
|
||||
# Place bot 2 steps away from Gary the Wizard (ensure within bounds)
|
||||
bot_x = wx + 2 if wx <= 62 else wx - 2
|
||||
bot_y = wy
|
||||
|
||||
async def setup_low_hp_bot():
|
||||
b = await game_engine.get_player(p["id"])
|
||||
b.x = bot_x
|
||||
b.y = bot_y
|
||||
b.health = 1
|
||||
b.strength = 1.0 # Weaker than Gary (3.0)
|
||||
asyncio.run(setup_low_hp_bot())
|
||||
|
||||
client.post("/api/game/start")
|
||||
game_engine.turn_order = [p["id"]]
|
||||
game_engine.current_turn_index = 0
|
||||
|
||||
# 1. First ai-step: Should navigate closer to the wizard with bot_goal='seek_wizard'
|
||||
step1_res = client.post(f"/api/players/{p['id']}/ai-step")
|
||||
assert step1_res.status_code == 200
|
||||
step1_data = step1_res.json()
|
||||
assert step1_data["bot_goal"] == "seek_wizard"
|
||||
assert step1_data["action_taken"] == "moved"
|
||||
|
||||
# Distance to wizard should have decreased
|
||||
dist_after_move = max(abs(step1_data["move_result"]["new_position"]["x"] - wx),
|
||||
abs(step1_data["move_result"]["new_position"]["y"] - wy))
|
||||
assert dist_after_move <= 1
|
||||
|
||||
# 2. Place bot adjacent to Gary and execute ai-step: should challenge Gary for health reward!
|
||||
game_engine.turn_order = [p["id"]]
|
||||
game_engine.current_turn_index = 0
|
||||
|
||||
step2_res = client.post(f"/api/players/{p['id']}/ai-step")
|
||||
assert step2_res.status_code == 200
|
||||
step2_data = step2_res.json()
|
||||
assert step2_data["action_taken"] == "challenged_wizard"
|
||||
assert step2_data["wizard_challenge_result"] is not None
|
||||
|
|
|
|||
|
|
@ -132,8 +132,8 @@ class SmartBotAgent:
|
|||
if wizard and wizard.get("can_challenge"):
|
||||
my_health = my_info.get("health", 10)
|
||||
wiz_str = wizard.get("strength", 3.0)
|
||||
# Challenge wizard if bot has >= wizard strength or healthy enough (HP >= 4)
|
||||
if self.strength >= wiz_str or my_health >= 4:
|
||||
# Challenge wizard if bot has >= wizard strength, healthy enough (HP >= 4), or low health seeking healing (HP < 2)
|
||||
if self.strength >= wiz_str or my_health >= 4 or my_health < 2:
|
||||
print(f"🧙 [WIZARD NEARBY] Adjacent to {wizard.get('name', 'Gary the Wizard')} (Str: {wiz_str})! HP: {my_health}, Bot Str: {self.strength}. Choosing to challenge!")
|
||||
self._challenge_wizard(my_health=my_health)
|
||||
return
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ Rules you must respect when choosing among the OPTIONS given to you:
|
|||
- Diagonal squeezes between obstacle corners cost strength (0.1 solo, 0.2 leader / 0.1 followers).
|
||||
- The Wandering Wizard NPC can be voluntarily challenged when adjacent (distance <= 1). The challenge
|
||||
is a 3-bout D20 duel (strength * D20). If victorious, the player decides whether to receive +2 score,
|
||||
+2 strength, or +2 health; losing costs 2 health (or score if no health).
|
||||
+2 strength, or +2 health; losing costs 2 health (or score if no health). Challenging the wizard is the only way to add health in the game.
|
||||
- The game ends when all surviving bots are united into a single remaining party.
|
||||
You will only ever be asked to choose between options that are legal - always answer with the
|
||||
requested JSON object and nothing else.
|
||||
|
|
@ -529,8 +529,8 @@ 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, 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.
|
||||
strength penalties, go to the Gary the Wizard NPC for points or health). As you are a risk taker, the Wizard is an ideal target
|
||||
You MUST pick a key from the legal moves object above.
|
||||
|
||||
Respond ONLY with JSON: {{"direction": "<one of {available}>", "reasoning": "short reason"}}
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -56,15 +56,15 @@ python3 bot.py \
|
|||
--name "GeminiTitan" \
|
||||
--color "#0ea5e9" \
|
||||
--strength 6 \
|
||||
--model "gemini-2.5-flash" \
|
||||
--model "gemini-3.8-flash" \
|
||||
--project "YOUR_GCP_PROJECT_ID"
|
||||
```
|
||||
|
||||
#### Using Environment Variables:
|
||||
```bash
|
||||
export VERTEX_PROJECT_ID="YOUR_GCP_PROJECT_ID"
|
||||
export VERTEX_LOCATION="us-central1"
|
||||
export VERTEX_MODEL="gemini-2.5-flash"
|
||||
export VERTEX_LOCATION="global"
|
||||
export VERTEX_MODEL="gemini-3.8-flash"
|
||||
export BOT_NAME="GearBot"
|
||||
export BOT_COLOR="#10b981"
|
||||
export BOT_STRENGTH="5"
|
||||
|
|
@ -89,8 +89,8 @@ python3 bot.py --url "http://192.168.1.100:8000/api" --name "RemoteGear"
|
|||
| `-s` | `--strength` | `BOT_STRENGTH` | `5` | Starting strength (1 to 10) |
|
||||
| `-H` | `--health` | `BOT_HEALTH` | `10` | Starting health points (default 10) |
|
||||
| `-p` | `--project` | `VERTEX_PROJECT_ID` | Auto-detected from `gcloud` | Google Cloud Project ID |
|
||||
| `-l` | `--location` | `VERTEX_LOCATION` | `us-central1` | Google Cloud region for Vertex AI |
|
||||
| `-m` | `--model` | `VERTEX_MODEL` | `gemini-2.5-flash` | Gemini model name |
|
||||
| `-l` | `--location` | `VERTEX_LOCATION` | `global` | Google Cloud region for Vertex AI (e.g. `global`, `us-central1`) |
|
||||
| `-m` | `--model` | `VERTEX_MODEL` | `gemini-3.8-flash` | Gemini model name |
|
||||
| `-k` | `--api-key` | `VERTEX_API_KEY` / `GEMINI_API_KEY` | *(None)* | Optional Gemini API key |
|
||||
|
||||
---
|
||||
|
|
|
|||
|
|
@ -70,8 +70,8 @@ Follow the browser prompt to grant access.
|
|||
Add to your `~/.bashrc` or run in your terminal:
|
||||
```bash
|
||||
export VERTEX_PROJECT_ID=$(gcloud config get-value project)
|
||||
export VERTEX_LOCATION="us-central1"
|
||||
export VERTEX_MODEL="gemini-2.5-flash"
|
||||
export VERTEX_LOCATION="global"
|
||||
export VERTEX_MODEL="gemini-3.8-flash"
|
||||
```
|
||||
|
||||
---
|
||||
|
|
@ -136,10 +136,11 @@ import subprocess, requests, json, os
|
|||
|
||||
token = os.getenv('VERTEX_ACCESS_TOKEN') or subprocess.check_output(['gcloud', 'auth', 'print-access-token'], text=True).strip()
|
||||
project = os.getenv('VERTEX_PROJECT_ID') or subprocess.check_output(['gcloud', 'config', 'get-value', 'project'], text=True).strip()
|
||||
location = os.getenv('VERTEX_LOCATION', 'us-central1')
|
||||
model = os.getenv('VERTEX_MODEL', 'gemini-2.5-flash')
|
||||
location = os.getenv('VERTEX_LOCATION', 'global')
|
||||
model = os.getenv('VERTEX_MODEL', 'gemini-3.8-flash')
|
||||
|
||||
url = f'https://{location}-aiplatform.googleapis.com/v1/projects/{project}/locations/{location}/publishers/google/models/{model}:generateContent'
|
||||
host = 'aiplatform.googleapis.com' if location == 'global' else f'{location}-aiplatform.googleapis.com'
|
||||
url = f'https://{host}/v1/projects/{project}/locations/{location}/publishers/google/models/{model}:generateContent'
|
||||
headers = {'Authorization': f'Bearer {token}', 'Content-Type': 'application/json'}
|
||||
payload = {'contents': [{'role': 'user', 'parts': [{'text': 'Hello Gemini'}]}]}
|
||||
|
||||
|
|
@ -158,7 +159,7 @@ else:
|
|||
|---|---|---|
|
||||
| `403 PermissionDenied: Vertex AI API has not been used...` | API is disabled | Run `gcloud services enable aiplatform.googleapis.com` |
|
||||
| `401 Unauthorized` / `Token expired` | Token expired or invalid | Re-run `gcloud auth application-default login` or refresh `gcloud auth login` |
|
||||
| `404 Publisher model ... not found` | Region does not have the model | Default to `us-central1`, `us-east4`, or check model name (`gemini-2.5-flash`, `gemini-1.5-flash`) |
|
||||
| `404 Publisher model ... not found` | Region does not have the model | Modern Gemini 3.x models (such as `gemini-3.8-flash`) are deployed in `global` (`aiplatform.googleapis.com`), while older models may be in `us-central1`. `bot.py` handles auto-fallback between `global` and `us-central1`. |
|
||||
| `No Google Cloud project ID detected` | Project is not set | Run `gcloud config set project <PROJECT_ID>` or export `VERTEX_PROJECT_ID` |
|
||||
|
||||
Once setup is complete, proceed to [INSTALL.md](INSTALL.md) and [README.md](README.md) to install dependencies and run your agent!
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ DEFAULT_BOT_COLOR = "#4285f4"
|
|||
DEFAULT_BOT_STRENGTH = 5
|
||||
DEFAULT_BOT_HEALTH = 10
|
||||
DEFAULT_MODEL = "gemini-3.8-flash"
|
||||
DEFAULT_LOCATION = "us-central1"
|
||||
DEFAULT_LOCATION = "global"
|
||||
|
||||
GAME_RULES_SUMMARY = """
|
||||
Rules you must respect when choosing among the OPTIONS given to you:
|
||||
|
|
@ -54,7 +54,7 @@ Rules you must respect when choosing among the OPTIONS given to you:
|
|||
- Diagonal squeezes between obstacle corners cost strength (0.1 solo, 0.2 leader / 0.1 followers).
|
||||
- The Wandering Wizard NPC can be voluntarily challenged when adjacent (distance <= 1). The challenge
|
||||
is a 3-bout D20 duel (strength * D20). If victorious, the player decides whether to receive +2 score,
|
||||
+2 strength, or +2 health; losing costs 2 health (or score if no health).
|
||||
+2 strength, or +2 health; losing costs 2 health (or score if no health). Challenging the wizard is the only way to add health in the game.
|
||||
- The game ends when all surviving bots are united into a single remaining party.
|
||||
You will only ever be asked to choose between options that are legal - always answer with the
|
||||
requested JSON object and nothing else.
|
||||
|
|
@ -204,6 +204,19 @@ class VertexGeminiClient:
|
|||
|
||||
return True, f"Authenticated to GCP Project '{self.project_id}' in region '{self.location}' (model: {self.model})"
|
||||
|
||||
def _build_vertex_url(self, location: Optional[str] = None) -> str:
|
||||
"""Construct the Vertex AI REST generateContent URL."""
|
||||
loc = location or self.location or "global"
|
||||
if loc == "global":
|
||||
host = "aiplatform.googleapis.com"
|
||||
else:
|
||||
host = f"{loc}-aiplatform.googleapis.com"
|
||||
return (
|
||||
f"https://{host}/v1/"
|
||||
f"projects/{self.project_id}/locations/{loc}/"
|
||||
f"publishers/google/models/{self.model}:generateContent"
|
||||
)
|
||||
|
||||
def ask_json(self, prompt: str) -> Optional[Dict[str, Any]]:
|
||||
"""Query Gemini requesting structured JSON output."""
|
||||
payload = {
|
||||
|
|
@ -232,11 +245,7 @@ class VertexGeminiClient:
|
|||
print("⚠️ [VERTEX AUTH ERROR] Not authenticated. See botagent_gear/SETUP.md.")
|
||||
return None
|
||||
|
||||
url = (
|
||||
f"https://{self.location}-aiplatform.googleapis.com/v1/"
|
||||
f"projects/{self.project_id}/locations/{self.location}/"
|
||||
f"publishers/google/models/{self.model}:generateContent"
|
||||
)
|
||||
url = self._build_vertex_url()
|
||||
headers = {"Content-Type": "application/json"}
|
||||
if token:
|
||||
headers["Authorization"] = f"Bearer {token}"
|
||||
|
|
@ -253,6 +262,18 @@ class VertexGeminiClient:
|
|||
headers["Authorization"] = f"Bearer {new_token}"
|
||||
res = requests.post(url, headers=headers, json=payload, timeout=45)
|
||||
|
||||
# Auto-fallback location on 404 (e.g. if model is hosted in 'global' vs regional 'us-central1')
|
||||
if res.status_code == 404 and self.project_id:
|
||||
fallback_loc = "global" if self.location != "global" else "us-central1"
|
||||
fallback_url = self._build_vertex_url(fallback_loc)
|
||||
if self.api_key:
|
||||
fallback_url = f"{fallback_url}?key={self.api_key}"
|
||||
res_fb = requests.post(fallback_url, headers=headers, json=payload, timeout=45)
|
||||
if res_fb.status_code == 200:
|
||||
print(f"ℹ️ [VERTEX] Model '{self.model}' located in '{fallback_loc}' (switched from '{self.location}').")
|
||||
self.location = fallback_loc
|
||||
res = res_fb
|
||||
|
||||
res.raise_for_status()
|
||||
data = res.json()
|
||||
|
||||
|
|
@ -715,8 +736,8 @@ 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, 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.
|
||||
strength penalties, go to the Gary the Wizard NPC for points or health). As you are a risk taker, the Wizard is an ideal target
|
||||
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