feat(game): damage bots for squeezing diagonally between impassable terrain pieces
This commit is contained in:
parent
cb732ad626
commit
f04b6c78aa
|
|
@ -858,6 +858,11 @@ class GameEngine:
|
|||
reason=f"Target square ({target_x}, {target_y}) is occupied by player '{occupant.name}'.",
|
||||
)
|
||||
|
||||
strength_penalty = 0.0
|
||||
if dx != 0 and dy != 0:
|
||||
if (player.x + dx, player.y) in self.obstacles and (player.x, player.y + dy) in self.obstacles:
|
||||
strength_penalty = 0.1
|
||||
|
||||
return MoveCheckResult(
|
||||
direction=direction_name,
|
||||
dx=dx,
|
||||
|
|
@ -866,6 +871,7 @@ class GameEngine:
|
|||
target_y=target_y,
|
||||
available=True,
|
||||
reason=None,
|
||||
strength_penalty=strength_penalty,
|
||||
)
|
||||
|
||||
async def check_single_move(self, player_id: str, direction_name: str) -> MoveCheckResult:
|
||||
|
|
@ -925,12 +931,12 @@ class GameEngine:
|
|||
)
|
||||
|
||||
def _update_party_strength(self, party: Party):
|
||||
total = 0
|
||||
total = 0.0
|
||||
for mid in party.member_ids:
|
||||
p = self.players.get(mid)
|
||||
if p:
|
||||
total += p.strength
|
||||
party.total_strength = max(1, total)
|
||||
party.total_strength = max(0.1, round(total, 1))
|
||||
|
||||
def _resolve_3bout_battle_internal(
|
||||
self, party1: Party, party2: Party
|
||||
|
|
@ -938,8 +944,8 @@ class GameEngine:
|
|||
bouts: List[BattleBout] = []
|
||||
p1_bouts_won = 0
|
||||
p2_bouts_won = 0
|
||||
p1_total_score = 0
|
||||
p2_total_score = 0
|
||||
p1_total_score = 0.0
|
||||
p2_total_score = 0.0
|
||||
|
||||
self._update_party_strength(party1)
|
||||
self._update_party_strength(party2)
|
||||
|
|
@ -947,8 +953,8 @@ class GameEngine:
|
|||
for bout_idx in range(1, 4):
|
||||
r1 = random.randint(1, 20)
|
||||
r2 = random.randint(1, 20)
|
||||
score1 = party1.total_strength * r1
|
||||
score2 = party2.total_strength * r2
|
||||
score1 = round(party1.total_strength * r1, 1)
|
||||
score2 = round(party2.total_strength * r2, 1)
|
||||
|
||||
p1_total_score += score1
|
||||
p2_total_score += score2
|
||||
|
|
@ -1292,11 +1298,18 @@ class GameEngine:
|
|||
m.x += dx
|
||||
m.y += dy
|
||||
m.visited_locations.append({"x": m.x, "y": m.y})
|
||||
if check.strength_penalty > 0:
|
||||
penalty = 0.2 if m.is_party_leader else 0.1
|
||||
m.strength = round(max(0.1, m.strength - penalty), 1)
|
||||
affected_players.append(m)
|
||||
if check.strength_penalty > 0:
|
||||
self._update_party_strength(party)
|
||||
else:
|
||||
player.x = check.target_x
|
||||
player.y = check.target_y
|
||||
player.visited_locations.append({"x": player.x, "y": player.y})
|
||||
if check.strength_penalty > 0:
|
||||
player.strength = round(max(0.1, player.strength - 0.1), 1)
|
||||
affected_players.append(player)
|
||||
|
||||
new_pos = {"x": player.x, "y": player.y}
|
||||
|
|
|
|||
|
|
@ -83,6 +83,7 @@ class MoveCheckResult(BaseModel):
|
|||
target_y: int
|
||||
available: bool
|
||||
reason: Optional[str] = None
|
||||
strength_penalty: float = 0.0
|
||||
|
||||
|
||||
class AvailableMovesResponse(BaseModel):
|
||||
|
|
@ -117,7 +118,7 @@ class Obstacle(BaseModel):
|
|||
class PlayerCreate(BaseModel):
|
||||
name: str = Field(..., min_length=1, max_length=32, description="Display name of the player")
|
||||
color: str = Field(..., description="Hex color code (e.g. #FF5733) or valid CSS color name")
|
||||
strength: int = Field(default=1, ge=1, description="Bot strength (default is 1)")
|
||||
strength: float = Field(default=1.0, ge=1, description="Bot strength (default is 1)")
|
||||
|
||||
@field_validator("name")
|
||||
@classmethod
|
||||
|
|
@ -144,7 +145,7 @@ class Player(BaseModel):
|
|||
color: str
|
||||
x: int
|
||||
y: int
|
||||
strength: int = 1
|
||||
strength: float = 1.0
|
||||
score: int = 0
|
||||
party_id: Optional[str] = None
|
||||
is_party_leader: bool = False
|
||||
|
|
@ -158,7 +159,7 @@ class Party(BaseModel):
|
|||
leader_id: str
|
||||
leader_name: str
|
||||
member_ids: List[str]
|
||||
total_strength: int = 1
|
||||
total_strength: float = 1.0
|
||||
created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
||||
|
||||
|
||||
|
|
@ -208,11 +209,11 @@ class PartyDefeatResult(BaseModel):
|
|||
class BattleBout(BaseModel):
|
||||
bout_number: int
|
||||
party1_roll: int = Field(..., ge=1, le=20, description="D20 roll (1 to 20) for party 1")
|
||||
party1_strength: int
|
||||
party1_score: int = Field(..., description="party1_strength * party1_roll")
|
||||
party1_strength: float
|
||||
party1_score: float = Field(..., description="party1_strength * party1_roll")
|
||||
party2_roll: int = Field(..., ge=1, le=20, description="D20 roll (1 to 20) for party 2")
|
||||
party2_strength: int
|
||||
party2_score: int = Field(..., description="party2_strength * party2_roll")
|
||||
party2_strength: float
|
||||
party2_score: float = Field(..., description="party2_strength * party2_roll")
|
||||
winner_name: Optional[str] = None
|
||||
|
||||
|
||||
|
|
@ -222,8 +223,8 @@ class BattleResult(BaseModel):
|
|||
party2_name: str
|
||||
party1_bouts_won: int
|
||||
party2_bouts_won: int
|
||||
party1_total_score: int
|
||||
party2_total_score: int
|
||||
party1_total_score: float
|
||||
party2_total_score: float
|
||||
winner_party_id: str
|
||||
winner_party_name: str
|
||||
winner_leader_id: str
|
||||
|
|
@ -239,7 +240,7 @@ class BattleResult(BaseModel):
|
|||
description="Remaining bots from the defeated party that merged into the winning party",
|
||||
)
|
||||
new_party_size: int
|
||||
new_party_strength: int
|
||||
new_party_strength: float
|
||||
|
||||
|
||||
class BattleRequest(BaseModel):
|
||||
|
|
@ -267,7 +268,7 @@ class RadarTarget(BaseModel):
|
|||
color: str
|
||||
x: int
|
||||
y: int
|
||||
strength: int = 1
|
||||
strength: float = 1.0
|
||||
distance: int
|
||||
party_id: Optional[str] = None
|
||||
party_name: Optional[str] = None
|
||||
|
|
|
|||
|
|
@ -215,7 +215,9 @@ class SmartBotAgent:
|
|||
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]))
|
||||
# Bot chooses whether to consider strength penalty: AI prioritizes keeping strength intact
|
||||
# (orders by has_penalty first, then distance)
|
||||
return min(valid_moves.keys(), key=lambda d: (valid_moves[d].get("strength_penalty", 0.0) > 0, dist(valid_moves[d])))
|
||||
|
||||
def _step_or_attack(self, target: Dict[str, Any]):
|
||||
"""Move adjacent/towards the target while avoiding obstacles."""
|
||||
|
|
|
|||
|
|
@ -485,7 +485,7 @@ export const BoardCanvas: React.FC<BoardCanvasProps> = ({
|
|||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'middle';
|
||||
|
||||
const text = `${player.name} [⚡${player.strength}]`;
|
||||
const text = `${player.name} [⚡${player.strength.toFixed(1)}]`;
|
||||
const textMetrics = ctx.measureText(text);
|
||||
const bgWidth = textMetrics.width + 12;
|
||||
const bgHeight = 16;
|
||||
|
|
|
|||
Loading…
Reference in New Issue