Gary the Wizard
Build and Publish Docker Image / Build and Push Docker Image (push) Successful in 1m3s
Details
Build and Publish Docker Image / Build and Push Docker Image (push) Successful in 1m3s
Details
This commit is contained in:
parent
e7a1e75863
commit
5ed7bb927e
26
AGENTS.md
26
AGENTS.md
|
|
@ -110,12 +110,12 @@ When writing bots, backend engine logic, or game simulations, adhere strictly to
|
||||||
- **Party**: Leader loses -0.2 strength; followers each lose -0.1 strength.
|
- **Party**: Leader loses -0.2 strength; followers each lose -0.1 strength.
|
||||||
- Moving along outer obstacle edges has no penalty. Minimum bot strength floor is 0.1.
|
- Moving along outer obstacle edges has no penalty. Minimum bot strength floor is 0.1.
|
||||||
|
|
||||||
### 6. The Wizard (NPC Encounter & Challenge)
|
### 6. Gary the Wizard (NPC Encounter & Challenge)
|
||||||
- A wandering Wizard NPC roams the map at a random passable coordinate.
|
- Gary the Wizard is a wandering NPC who roams the map at a random passable coordinate.
|
||||||
- Players (or party leaders) who locate the wizard (adjacent or on same tile, distance <= 1) may voluntarily **choose to challenge** the wizard.
|
- Players (or party leaders) who locate Gary the Wizard (adjacent or on same tile, distance <= 1) may voluntarily **choose to challenge** him.
|
||||||
- **Challenge Resolution (3-Bout D20)**:
|
- **Challenge Resolution (3-Bout D20)**:
|
||||||
- Exactly 3 bouts are conducted.
|
- Exactly 3 bouts are conducted.
|
||||||
- In each bout, each side rolls a D20 die, multiplied by their strength (the wizard has 3.0 strength; parties use squad total strength).
|
- In each bout, each side rolls a D20 die, multiplied by their strength (Gary the Wizard has 3.0 strength; parties use squad total strength).
|
||||||
- Highest bout score wins the bout. Best 2-out-of-3 bouts wins the challenge.
|
- Highest bout score wins the bout. Best 2-out-of-3 bouts wins the challenge.
|
||||||
- **Victory Rewards & Defeat Penalties**:
|
- **Victory Rewards & Defeat Penalties**:
|
||||||
- **Victory**: The player (or party leader) decides and chooses which reward to claim:
|
- **Victory**: The player (or party leader) decides and chooses which reward to claim:
|
||||||
|
|
@ -124,11 +124,11 @@ When writing bots, backend engine logic, or game simulations, adhere strictly to
|
||||||
- **+2 Health**: Adds +2 health points (expanding max health if current health exceeds initial maximum).
|
- **+2 Health**: Adds +2 health points (expanding max health if current health exceeds initial maximum).
|
||||||
- **Defeat**: Player/party leader loses **2 health** (or **2 score points** if they do not have health to lose).
|
- **Defeat**: Player/party leader loses **2 health** (or **2 score points** if they do not have health to lose).
|
||||||
- **Post-Challenge**:
|
- **Post-Challenge**:
|
||||||
- Following the challenge, the wizard teleports to a new random open coordinate on the map.
|
- Following the challenge, Gary the Wizard teleports to a new random open coordinate on the map.
|
||||||
|
|
||||||
### 7. Player Health, Damage & Death
|
### 7. Player Health, Damage & Death
|
||||||
- All bots register with default **10 HP** (configurable).
|
- All bots register with default **10 HP** (configurable).
|
||||||
- Health damage is sustained by losing battles (-1 to -3 HP) or losing Wizard challenges (-2 HP).
|
- Health damage is sustained by losing battles (-1 to -3 HP) or losing Gary the Wizard challenges (-2 HP).
|
||||||
- When a bot's health drops to **0 HP**, they are **dead**.
|
- When a bot's health drops to **0 HP**, they are **dead**.
|
||||||
- **Death Consequences**:
|
- **Death Consequences**:
|
||||||
- **Party Disconnection**: The dead bot is immediately detached from any party. If the dead bot was the leader, squad leadership transfers to the strongest surviving squad member (or the party dissolves if empty). Defeated dead followers are not absorbed.
|
- **Party Disconnection**: The dead bot is immediately detached from any party. If the dead bot was the leader, squad leadership transfers to the strongest surviving squad member (or the party dissolves if empty). Defeated dead followers are not absorbed.
|
||||||
|
|
@ -154,9 +154,9 @@ The backend serves both REST endpoints under `/api` and a live WebSocket stream
|
||||||
| `GET` | `/api/players` | List all active players, scores, health, and positions |
|
| `GET` | `/api/players` | List all active players, scores, health, and positions |
|
||||||
| `GET` | `/api/board` | Full board state (grid, obstacles, players, parties, wizard, current turn) |
|
| `GET` | `/api/board` | Full board state (grid, obstacles, players, parties, wizard, current turn) |
|
||||||
| `POST` | `/api/board/reset` | Clear board, reset parties, reset players, and respawn wizard |
|
| `POST` | `/api/board/reset` | Clear board, reset parties, reset players, and respawn wizard |
|
||||||
| `GET` | `/api/wizard` | Get current Wizard NPC coordinates and attributes |
|
| `GET` | `/api/wizard` | Get current Gary the Wizard NPC coordinates and attributes |
|
||||||
| `POST` | `/api/wizard/challenge` | Challenge the Wizard NPC: `{"player_id": str, "reward_choice": "score"|"strength"|"health"}` (3-bout D20 duel) |
|
| `POST` | `/api/wizard/challenge` | Challenge Gary the Wizard NPC: `{"player_id": str, "reward_choice": "score"|"strength"|"health"}` (3-bout D20 duel) |
|
||||||
| `GET` | `/api/players/{id}/radar` | Scans surroundings, finds closest bots and Wizard NPC |
|
| `GET` | `/api/players/{id}/radar` | Scans surroundings, finds closest bots and Gary the Wizard NPC |
|
||||||
| `GET` | `/api/players/{id}/memory` | Coordinate history and visited locations |
|
| `GET` | `/api/players/{id}/memory` | Coordinate history and visited locations |
|
||||||
| `GET` | `/api/players/{id}/available-moves` | Valid movements in all 8 directions (evaluates terrain & obstacles) |
|
| `GET` | `/api/players/{id}/available-moves` | Valid movements in all 8 directions (evaluates terrain & obstacles) |
|
||||||
| `POST` | `/api/players/{id}/move` | Execute a move (`{"direction": "N"|"S"|"E"|"W"|"NE"|"NW"|"SE"|"SW"}`) |
|
| `POST` | `/api/players/{id}/move` | Execute a move (`{"direction": "N"|"S"|"E"|"W"|"NE"|"NW"|"SE"|"SW"}`) |
|
||||||
|
|
@ -211,11 +211,11 @@ The application is containerized into a single unified image via [Dockerfile](Do
|
||||||
- **Workflow**:
|
- **Workflow**:
|
||||||
1. Registers bot via `POST /api/players`.
|
1. Registers bot via `POST /api/players`.
|
||||||
2. Polls `GET /api/turn` to wait for its turn.
|
2. Polls `GET /api/turn` to wait for its turn.
|
||||||
3. Uses `GET /api/players/{id}/radar` to find nearest target and detect Wizard NPC proximity.
|
3. Uses `GET /api/players/{id}/radar` to find nearest target and detect Gary the Wizard NPC proximity.
|
||||||
4. Avoids looping using internal coordinate history.
|
4. Avoids looping using internal coordinate history.
|
||||||
5. Computes vector direction, evaluates diagonal obstacles, and moves.
|
5. Computes vector direction, evaluates diagonal obstacles, and moves.
|
||||||
6. Evaluates alliances vs. fights strictly according to strength hierarchy.
|
6. Evaluates alliances vs. fights strictly according to strength hierarchy.
|
||||||
7. Decides whether to challenge the Wizard NPC based on relative strength and health advantage.
|
7. Decides whether to challenge Gary the Wizard NPC based on relative strength and health advantage.
|
||||||
- **Run Command**:
|
- **Run Command**:
|
||||||
```bash
|
```bash
|
||||||
python3 botagent/bot_agent.py --name CyberBot --color "#10b981" -s 4 -H 10 --url http://localhost:8000/api
|
python3 botagent/bot_agent.py --name CyberBot --color "#10b981" -s 4 -H 10 --url http://localhost:8000/api
|
||||||
|
|
@ -228,7 +228,7 @@ The application is containerized into a single unified image via [Dockerfile](Do
|
||||||
- Communicates with an LLM backend (configured for local/remote Ollama HTTP API at `/api/generate` with model `gemma4:12b`, or adaptable to OpenAI-compatible endpoints).
|
- Communicates with an LLM backend (configured for local/remote Ollama HTTP API at `/api/generate` with model `gemma4:12b`, or adaptable to OpenAI-compatible endpoints).
|
||||||
- Delegates discretionary decisions to the model:
|
- Delegates discretionary decisions to the model:
|
||||||
- Voluntary alliances (whether to ally or keep hunting when solo meets solo).
|
- Voluntary alliances (whether to ally or keep hunting when solo meets solo).
|
||||||
- Voluntary Wizard challenges (whether to challenge the Wizard NPC when adjacent for +2 score vs. -2 health risk).
|
- Voluntary Wizard challenges (whether to challenge Gary the Wizard NPC when adjacent for +2 score vs. -2 health risk).
|
||||||
- Navigation direction toward radar targets while balancing obstacle squeeze trade-offs.
|
- Navigation direction toward radar targets while balancing obstacle squeeze trade-offs.
|
||||||
- Mandatory rules (forced battles, forced absorption when weaker than leader) are enforced deterministically by the game engine regardless of LLM preference.
|
- Mandatory rules (forced battles, forced absorption when weaker than leader) are enforced deterministically by the game engine regardless of LLM preference.
|
||||||
- **Configuration & Environment Variables**:
|
- **Configuration & Environment Variables**:
|
||||||
|
|
@ -252,7 +252,7 @@ The application is containerized into a single unified image via [Dockerfile](Do
|
||||||
- **LLM Integration**:
|
- **LLM Integration**:
|
||||||
- Communicates with Google Cloud Vertex AI generateContent REST endpoint or Google AI Studio Gemini API.
|
- Communicates with Google Cloud Vertex AI generateContent REST endpoint or Google AI Studio Gemini API.
|
||||||
- Features structured JSON generation (`responseMimeType: application/json`) and system instruction enforcement.
|
- Features structured JSON generation (`responseMimeType: application/json`) and system instruction enforcement.
|
||||||
- Delegates discretionary choices to Gemini: voluntary alliances, choosing whether to challenge the Wizard NPC, and spatial pathing.
|
- Delegates discretionary choices to Gemini: voluntary alliances, choosing whether to challenge Gary the Wizard NPC, and spatial pathing.
|
||||||
- Supports multiple authentication methods:
|
- Supports multiple authentication methods:
|
||||||
- Interactive developer login (`gcloud auth application-default login` or `gcloud auth login`).
|
- Interactive developer login (`gcloud auth application-default login` or `gcloud auth login`).
|
||||||
- Automated service account keys (`GOOGLE_APPLICATION_CREDENTIALS`).
|
- Automated service account keys (`GOOGLE_APPLICATION_CREDENTIALS`).
|
||||||
|
|
|
||||||
|
|
@ -28,11 +28,11 @@
|
||||||
- In a party, the leader loses **0.2 strength**, and follower members each lose **0.1 strength**.
|
- In a party, the leader loses **0.2 strength**, and follower members each lose **0.1 strength**.
|
||||||
4. Moving along the open outer edge of an obstacle incurs no penalty. Minimum bot strength cannot fall below 0.1.
|
4. Moving along the open outer edge of an obstacle incurs no penalty. Minimum bot strength cannot fall below 0.1.
|
||||||
|
|
||||||
6. **The Wizard (NPC Encounter & Challenge)**:
|
6. **Gary the Wizard (NPC Encounter & Challenge)**:
|
||||||
1. The Wizard is a special Non-Player Character (NPC) roaming the map at a random passable location.
|
1. Gary the Wizard is a special Non-Player Character (NPC) roaming the map at a random passable location.
|
||||||
2. Players (or party leaders) who find the wizard (adjacent or on the same coordinate, distance <= 1) may voluntarily **choose to challenge** the wizard.
|
2. Players (or party leaders) who find Gary the Wizard (adjacent or on the same coordinate, distance <= 1) may voluntarily **choose to challenge** him.
|
||||||
3. **Challenge Resolution (3-Bout D20)**:
|
3. **Challenge Resolution (3-Bout D20)**:
|
||||||
- Challenges consist of 3 bouts: each side rolls a D20 die, multiplied by their strength (the wizard has a strength of 3.0; a party uses its squad total strength).
|
- Challenges consist of 3 bouts: each side rolls a D20 die, multiplied by their strength (Gary the Wizard has a strength of 3.0; a party uses its squad total strength).
|
||||||
- Highest bout score wins the bout. Best 2 out of 3 bouts wins the challenge.
|
- Highest bout score wins the bout. Best 2 out of 3 bouts wins the challenge.
|
||||||
4. **Victory Rewards & Defeat Penalties**:
|
4. **Victory Rewards & Defeat Penalties**:
|
||||||
- **Victory**: The player (or party leader) must decide and choose which reward to receive:
|
- **Victory**: The player (or party leader) must decide and choose which reward to receive:
|
||||||
|
|
@ -41,11 +41,11 @@
|
||||||
- **+2 Health**: Adds +2 health points (HP) to the bot (expanding max health if current health exceeds initial maximum).
|
- **+2 Health**: Adds +2 health points (HP) to the bot (expanding max health if current health exceeds initial maximum).
|
||||||
- **Defeat**: The player (or party leader) loses **2 health** (or **2 score points** if they do not have health to lose).
|
- **Defeat**: The player (or party leader) loses **2 health** (or **2 score points** if they do not have health to lose).
|
||||||
5. **Wizard Relocation**:
|
5. **Wizard Relocation**:
|
||||||
- Following any challenge, the wizard teleports to a new random open coordinate on the map.
|
- Following any challenge, Gary the Wizard teleports to a new random open coordinate on the map.
|
||||||
|
|
||||||
7. **Player Health, Damage & Death**:
|
7. **Player Health, Damage & Death**:
|
||||||
1. All bots register with default **10 HP** (configurable via `--health` / `-H`).
|
1. All bots register with default **10 HP** (configurable via `--health` / `-H`).
|
||||||
2. Health damage is sustained by losing battles (-1 to -3 HP) or losing Wizard challenges (-2 HP).
|
2. Health damage is sustained by losing battles (-1 to -3 HP) or losing Gary the Wizard challenges (-2 HP).
|
||||||
3. When a player's health points reach **0 HP**, they are **dead**.
|
3. When a player's health points reach **0 HP**, they are **dead**.
|
||||||
4. **Death Consequences**:
|
4. **Death Consequences**:
|
||||||
- **Party Disconnection**: The dead player is immediately disconnected from any existing party. If the dead player was the party leader, squad leadership transfers to the strongest surviving member (or the party dissolves if no living members remain). Dead followers are not absorbed into opposing parties.
|
- **Party Disconnection**: The dead player is immediately disconnected from any existing party. If the dead player was the party leader, squad leadership transfers to the strongest surviving member (or the party dissolves if no living members remain). Dead followers are not absorbed into opposing parties.
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,7 @@ class GameEngine:
|
||||||
wx, wy = self._find_random_free_position()
|
wx, wy = self._find_random_free_position()
|
||||||
return WizardNPC(
|
return WizardNPC(
|
||||||
id="wizard_npc",
|
id="wizard_npc",
|
||||||
name="Grand Wizard",
|
name="Gary the Wizard",
|
||||||
x=wx,
|
x=wx,
|
||||||
y=wy,
|
y=wy,
|
||||||
strength=3.0,
|
strength=3.0,
|
||||||
|
|
@ -1486,7 +1486,7 @@ class GameEngine:
|
||||||
return WizardChallengeResult(
|
return WizardChallengeResult(
|
||||||
challenger_id=player.id,
|
challenger_id=player.id,
|
||||||
challenger_name=player.name,
|
challenger_name=player.name,
|
||||||
wizard_name="Grand Wizard",
|
wizard_name=self.wizard.name if self.wizard else "Gary the Wizard",
|
||||||
party_id=party_id,
|
party_id=party_id,
|
||||||
bouts=bouts,
|
bouts=bouts,
|
||||||
player_bouts_won=player_bouts_won,
|
player_bouts_won=player_bouts_won,
|
||||||
|
|
@ -1532,8 +1532,9 @@ class GameEngine:
|
||||||
|
|
||||||
dist = max(abs(player.x - self.wizard.x), abs(player.y - self.wizard.y))
|
dist = max(abs(player.x - self.wizard.x), abs(player.y - self.wizard.y))
|
||||||
if dist > 1:
|
if dist > 1:
|
||||||
|
wiz_name = self.wizard.name if self.wizard else "Gary the Wizard"
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Player '{player.name}' is not adjacent to the Grand Wizard (distance {dist}). Must be within 1 distance."
|
f"Player '{player.name}' is not adjacent to {wiz_name} (distance {dist}). Must be within 1 distance."
|
||||||
)
|
)
|
||||||
|
|
||||||
return self._resolve_wizard_challenge_internal(player, reward_choice=reward_choice)
|
return self._resolve_wizard_challenge_internal(player, reward_choice=reward_choice)
|
||||||
|
|
@ -1880,7 +1881,7 @@ class GameEngine:
|
||||||
turn=self._get_turn_info(),
|
turn=self._get_turn_info(),
|
||||||
)
|
)
|
||||||
|
|
||||||
# 1b. Check if adjacent to the Grand Wizard NPC and choose to challenge
|
# 1b. Check if adjacent to Gary the Wizard NPC and choose to challenge
|
||||||
wiz_dist = max(abs(player.x - self.wizard.x), abs(player.y - self.wizard.y))
|
wiz_dist = max(abs(player.x - self.wizard.x), abs(player.y - self.wizard.y))
|
||||||
if wiz_dist <= 1 and (not player.party_id or player.is_party_leader):
|
if wiz_dist <= 1 and (not player.party_id or player.is_party_leader):
|
||||||
effective_str = player.strength
|
effective_str = player.strength
|
||||||
|
|
|
||||||
|
|
@ -268,7 +268,7 @@ class BattleRequest(BaseModel):
|
||||||
|
|
||||||
class WizardNPC(BaseModel):
|
class WizardNPC(BaseModel):
|
||||||
id: str = "wizard_npc"
|
id: str = "wizard_npc"
|
||||||
name: str = "Grand Wizard"
|
name: str = "Gary the Wizard"
|
||||||
x: int
|
x: int
|
||||||
y: int
|
y: int
|
||||||
strength: float = 3.0
|
strength: float = 3.0
|
||||||
|
|
@ -290,7 +290,7 @@ class WizardChallengeBout(BaseModel):
|
||||||
class WizardChallengeResult(BaseModel):
|
class WizardChallengeResult(BaseModel):
|
||||||
challenger_id: str
|
challenger_id: str
|
||||||
challenger_name: str
|
challenger_name: str
|
||||||
wizard_name: str = "Grand Wizard"
|
wizard_name: str = "Gary the Wizard"
|
||||||
party_id: Optional[str] = None
|
party_id: Optional[str] = None
|
||||||
bouts: List[WizardChallengeBout]
|
bouts: List[WizardChallengeBout]
|
||||||
player_bouts_won: int
|
player_bouts_won: int
|
||||||
|
|
@ -317,7 +317,7 @@ class WizardChallengeRequest(BaseModel):
|
||||||
|
|
||||||
class WizardRadarTarget(BaseModel):
|
class WizardRadarTarget(BaseModel):
|
||||||
id: str = "wizard_npc"
|
id: str = "wizard_npc"
|
||||||
name: str = "Grand Wizard"
|
name: str = "Gary the Wizard"
|
||||||
x: int
|
x: int
|
||||||
y: int
|
y: int
|
||||||
distance: int
|
distance: int
|
||||||
|
|
|
||||||
|
|
@ -484,7 +484,7 @@ def test_wizard_npc_existence_and_radar_detection():
|
||||||
assert wiz_res.status_code == 200
|
assert wiz_res.status_code == 200
|
||||||
wiz_data = wiz_res.json()
|
wiz_data = wiz_res.json()
|
||||||
assert wiz_data["id"] == "wizard_npc"
|
assert wiz_data["id"] == "wizard_npc"
|
||||||
assert wiz_data["name"] == "Grand Wizard"
|
assert wiz_data["name"] == "Gary the Wizard"
|
||||||
assert wiz_data["strength"] == 3.0
|
assert wiz_data["strength"] == 3.0
|
||||||
assert 0 <= wiz_data["x"] <= 64
|
assert 0 <= wiz_data["x"] <= 64
|
||||||
assert 0 <= wiz_data["y"] <= 64
|
assert 0 <= wiz_data["y"] <= 64
|
||||||
|
|
|
||||||
|
|
@ -134,7 +134,7 @@ class SmartBotAgent:
|
||||||
wiz_str = wizard.get("strength", 3.0)
|
wiz_str = wizard.get("strength", 3.0)
|
||||||
# Challenge wizard if bot has >= wizard strength or healthy enough (HP >= 4)
|
# Challenge wizard if bot has >= wizard strength or healthy enough (HP >= 4)
|
||||||
if self.strength >= wiz_str or my_health >= 4:
|
if self.strength >= wiz_str or my_health >= 4:
|
||||||
print(f"🧙 [WIZARD NEARBY] Adjacent to {wizard.get('name', 'Grand Wizard')} (Str: {wiz_str})! HP: {my_health}, Bot Str: {self.strength}. Choosing to challenge!")
|
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)
|
self._challenge_wizard(my_health=my_health)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
@ -250,7 +250,7 @@ class SmartBotAgent:
|
||||||
else:
|
else:
|
||||||
reward_choice = "score"
|
reward_choice = "score"
|
||||||
|
|
||||||
print(f"🧙 [WIZARD CHALLENGE] Challenging Grand Wizard to a 3-bout D20 duel (reward if won: +2 {reward_choice})...")
|
print(f"🧙 [WIZARD CHALLENGE] Challenging Gary the Wizard to a 3-bout D20 duel (reward if won: +2 {reward_choice})...")
|
||||||
try:
|
try:
|
||||||
res = requests.post(
|
res = requests.post(
|
||||||
f"{self.base_url}/wizard/challenge",
|
f"{self.base_url}/wizard/challenge",
|
||||||
|
|
|
||||||
|
|
@ -212,7 +212,7 @@ class AIBotAgent:
|
||||||
prompt = f"""{GAME_RULES_SUMMARY}
|
prompt = f"""{GAME_RULES_SUMMARY}
|
||||||
You are bot "{self.name}" (strength {self.strength}, score {my_info['score']}, health {my_info.get('health', 10)})
|
You are bot "{self.name}" (strength {self.strength}, score {my_info['score']}, health {my_info.get('health', 10)})
|
||||||
at position ({my_info['x']}, {my_info['y']}).
|
at position ({my_info['x']}, {my_info['y']}).
|
||||||
You are adjacent to the NPC Grand Wizard (strength {wizard['strength']}).
|
You are adjacent to the NPC Gary the Wizard (strength {wizard['strength']}).
|
||||||
Challenging the wizard initiates a 3-bout D20 duel (strength * roll).
|
Challenging the wizard initiates a 3-bout D20 duel (strength * roll).
|
||||||
- If you win: You choose one reward: +2 score, +2 strength, or +2 health!
|
- If you win: You choose one reward: +2 score, +2 strength, or +2 health!
|
||||||
- If you lose: -2 health points (or -2 score if no health)!
|
- If you lose: -2 health points (or -2 score if no health)!
|
||||||
|
|
@ -231,7 +231,7 @@ Respond ONLY with JSON: {{"challenge_wizard": true|false, "reward_choice": "scor
|
||||||
|
|
||||||
def _challenge_wizard(self, wizard: Dict[str, Any], reward_choice: str = "score"):
|
def _challenge_wizard(self, wizard: Dict[str, Any], reward_choice: str = "score"):
|
||||||
"""Execute the challenge against the Wizard NPC."""
|
"""Execute the challenge against the Wizard NPC."""
|
||||||
print(f"🧙 [WIZARD CHALLENGE] Challenging {wizard.get('name', 'Grand Wizard')} to a 3-bout D20 duel (reward if won: +2 {reward_choice})...")
|
print(f"🧙 [WIZARD CHALLENGE] Challenging {wizard.get('name', 'Gary the Wizard')} to a 3-bout D20 duel (reward if won: +2 {reward_choice})...")
|
||||||
try:
|
try:
|
||||||
res = requests.post(
|
res = requests.post(
|
||||||
f"{self.base_url}/wizard/challenge",
|
f"{self.base_url}/wizard/challenge",
|
||||||
|
|
@ -513,7 +513,7 @@ Respond ONLY with JSON: {{"form_alliance": true|false, "reasoning": "short reaso
|
||||||
map_section = f"""
|
map_section = f"""
|
||||||
Local map (radius {self.LOCAL_MAP_RADIUS} around you, row = one Y line, top-to-bottom is
|
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,
|
increasing Y, left-to-right is increasing X): @ = you, A = ally, E = enemy/neutral bot,
|
||||||
W = Grand Wizard NPC, M = mountain, F = forest, V = valley, # = out of bounds, . = open ground.
|
W = Gary the Wizard NPC, M = mountain, F = forest, V = valley, # = out of bounds, . = open ground.
|
||||||
{chr(10).join(local_map)}
|
{chr(10).join(local_map)}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -398,7 +398,7 @@ class VertexAIBotAgent:
|
||||||
prompt = f"""{GAME_RULES_SUMMARY}
|
prompt = f"""{GAME_RULES_SUMMARY}
|
||||||
You are bot "{self.name}" (strength {self.strength}, score {my_info['score']}, health {my_info.get('health', 10)})
|
You are bot "{self.name}" (strength {self.strength}, score {my_info['score']}, health {my_info.get('health', 10)})
|
||||||
at position ({my_info['x']}, {my_info['y']}).
|
at position ({my_info['x']}, {my_info['y']}).
|
||||||
You are adjacent to the NPC Grand Wizard (strength {wizard['strength']}).
|
You are adjacent to the NPC Gary the Wizard (strength {wizard['strength']}).
|
||||||
Challenging the wizard initiates a 3-bout D20 duel (strength * roll).
|
Challenging the wizard initiates a 3-bout D20 duel (strength * roll).
|
||||||
- If you win: You choose one reward: +2 score, +2 strength, or +2 health!
|
- If you win: You choose one reward: +2 score, +2 strength, or +2 health!
|
||||||
- If you lose: -2 health points (or -2 score if no health)!
|
- If you lose: -2 health points (or -2 score if no health)!
|
||||||
|
|
@ -417,7 +417,7 @@ Respond ONLY with JSON: {{"challenge_wizard": true|false, "reward_choice": "scor
|
||||||
|
|
||||||
def _challenge_wizard(self, wizard: Dict[str, Any], reward_choice: str = "score"):
|
def _challenge_wizard(self, wizard: Dict[str, Any], reward_choice: str = "score"):
|
||||||
"""Execute the challenge against the Wizard NPC."""
|
"""Execute the challenge against the Wizard NPC."""
|
||||||
print(f"🧙 [WIZARD CHALLENGE] Challenging {wizard.get('name', 'Grand Wizard')} to a 3-bout D20 duel (reward if won: +2 {reward_choice})...")
|
print(f"🧙 [WIZARD CHALLENGE] Challenging {wizard.get('name', 'Gary the Wizard')} to a 3-bout D20 duel (reward if won: +2 {reward_choice})...")
|
||||||
try:
|
try:
|
||||||
res = requests.post(
|
res = requests.post(
|
||||||
f"{self.base_url}/wizard/challenge",
|
f"{self.base_url}/wizard/challenge",
|
||||||
|
|
@ -699,7 +699,7 @@ Respond ONLY with JSON: {{"form_alliance": true|false, "reasoning": "short reaso
|
||||||
map_section = f"""
|
map_section = f"""
|
||||||
Local map (radius {self.LOCAL_MAP_RADIUS} around you, row = one Y line, top-to-bottom is
|
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,
|
increasing Y, left-to-right is increasing X): @ = you, A = ally, E = enemy/neutral bot,
|
||||||
W = Grand Wizard NPC, M = mountain, F = forest, V = valley, # = out of bounds, . = open ground.
|
W = Gary the Wizard NPC, M = mountain, F = forest, V = valley, # = out of bounds, . = open ground.
|
||||||
{chr(10).join(local_map)}
|
{chr(10).join(local_map)}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -623,7 +623,7 @@ export const BoardCanvas: React.FC<BoardCanvasProps> = ({
|
||||||
<span className="text-slate-500">|</span>
|
<span className="text-slate-500">|</span>
|
||||||
<span className="text-purple-300 font-bold flex items-center gap-1">
|
<span className="text-purple-300 font-bold flex items-center gap-1">
|
||||||
<span>🧙♂️</span>
|
<span>🧙♂️</span>
|
||||||
<span>Grand Wizard NPC (Str: {boardState.wizard?.strength})</span>
|
<span>Gary the Wizard NPC (Str: {boardState.wizard?.strength})</span>
|
||||||
</span>
|
</span>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ export const WizardChallengeModal: React.FC<WizardChallengeModalProps> = ({
|
||||||
if (!challenge) return null;
|
if (!challenge) return null;
|
||||||
|
|
||||||
const playerName = challenge.challenger_name;
|
const playerName = challenge.challenger_name;
|
||||||
const wizardName = challenge.wizard_name || 'Grand Wizard';
|
const wizardName = challenge.wizard_name || 'Gary the Wizard';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-md p-4 animate-in fade-in duration-200">
|
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-md p-4 animate-in fade-in duration-200">
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ export const WizardPromptModal: React.FC<WizardPromptModalProps> = ({
|
||||||
|
|
||||||
if (!isOpen || !challenger) return null;
|
if (!isOpen || !challenger) return null;
|
||||||
|
|
||||||
const wizardName = wizard?.name || 'Grand Wizard';
|
const wizardName = wizard?.name || 'Gary the Wizard';
|
||||||
const wizardStr = wizard?.strength ?? 3.0;
|
const wizardStr = wizard?.strength ?? 3.0;
|
||||||
|
|
||||||
const options: Array<{
|
const options: Array<{
|
||||||
|
|
@ -73,7 +73,7 @@ export const WizardPromptModal: React.FC<WizardPromptModalProps> = ({
|
||||||
<span className="text-2xl">🧙♂️</span>
|
<span className="text-2xl">🧙♂️</span>
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-base font-bold text-slate-100 font-mono tracking-wide">
|
<h2 className="text-base font-bold text-slate-100 font-mono tracking-wide">
|
||||||
CHALLENGE THE GRAND WIZARD
|
CHALLENGE GARY THE WIZARD
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-xs text-slate-400 font-mono">
|
<p className="text-xs text-slate-400 font-mono">
|
||||||
Select your victory reward before entering the 3-bout D20 duel
|
Select your victory reward before entering the 3-bout D20 duel
|
||||||
|
|
|
||||||
|
|
@ -215,7 +215,7 @@ export function useGameSocket() {
|
||||||
}));
|
}));
|
||||||
const c: WizardChallengeResult = data.challenge_result || data.challenge;
|
const c: WizardChallengeResult = data.challenge_result || data.challenge;
|
||||||
setActiveWizardChallenge(c);
|
setActiveWizardChallenge(c);
|
||||||
const wizName = c.wizard_name || 'Grand Wizard';
|
const wizName = c.wizard_name || 'Gary the Wizard';
|
||||||
let rewardLabel = `+${c.score_change} score`;
|
let rewardLabel = `+${c.score_change} score`;
|
||||||
if (c.reward_chosen === 'strength') {
|
if (c.reward_chosen === 'strength') {
|
||||||
rewardLabel = `+${c.strength_change ?? 2} strength`;
|
rewardLabel = `+${c.strength_change ?? 2} strength`;
|
||||||
|
|
@ -495,7 +495,7 @@ export function useGameSocket() {
|
||||||
} else if (data.action_taken === 'challenged_wizard' && data.wizard_challenge_result) {
|
} else if (data.action_taken === 'challenged_wizard' && data.wizard_challenge_result) {
|
||||||
const wcr = data.wizard_challenge_result;
|
const wcr = data.wizard_challenge_result;
|
||||||
setActiveWizardChallenge(wcr);
|
setActiveWizardChallenge(wcr);
|
||||||
const wizName = wcr.wizard_name || 'Grand Wizard';
|
const wizName = wcr.wizard_name || 'Gary the Wizard';
|
||||||
setLastEventMessage(
|
setLastEventMessage(
|
||||||
wcr.player_won
|
wcr.player_won
|
||||||
? `🧙 ${wcr.challenger_name} defeated ${wizName}! (+${wcr.score_change} score)`
|
? `🧙 ${wcr.challenger_name} defeated ${wizName}! (+${wcr.score_change} score)`
|
||||||
|
|
|
||||||
|
|
@ -438,7 +438,7 @@ export function drawPlayerPiece(
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Canvas Drawing Helper for the Grand Wizard NPC
|
// Canvas Drawing Helper for the Gary the Wizard NPC
|
||||||
export function drawWizardPiece(
|
export function drawWizardPiece(
|
||||||
ctx: CanvasRenderingContext2D,
|
ctx: CanvasRenderingContext2D,
|
||||||
wizard: { x: number; y: number; name: string; strength: number; color?: string },
|
wizard: { x: number; y: number; name: string; strength: number; color?: string },
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue