ai-code-battle/bots/phalanx/src/game.rs
jedarden ed4be7a4d3 feat(bot): add Phalanx bot (Rust) — tight formation archetype
Formation-based combat bot that moves all units as a coordinated group:
- Circular mean centroid computation (toroidal-aware)
- Hexagonal packing formation slots with greedy slot assignment
- Rally mode when mean distance from centroid exceeds 3 cells
- Scored movement: formation cohesion + advance toward enemy concentration
- Attack range bonus when engaging enemies in formation
- Self-collision avoidance via claimed destination tracking
- 10 unit tests covering all core algorithms

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 16:43:39 -04:00

139 lines
3.5 KiB
Rust

//! Game state types for AI Code Battle protocol.
use serde::{Deserialize, Serialize};
/// Position on the grid
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Position {
pub row: i32,
pub col: i32,
}
/// Game configuration
#[derive(Debug, Clone, Deserialize)]
pub struct GameConfig {
pub rows: u32,
pub cols: u32,
pub max_turns: u32,
pub vision_radius2: u32,
pub attack_radius2: u32,
pub spawn_cost: u32,
pub energy_interval: u32,
}
/// Player info
#[derive(Debug, Clone, Deserialize)]
pub struct PlayerInfo {
pub id: u32,
pub energy: u32,
pub score: u32,
}
/// Visible bot
#[derive(Debug, Clone, Deserialize)]
pub struct VisibleBot {
pub position: Position,
pub owner: u32,
}
/// Visible core
#[derive(Debug, Clone, Deserialize)]
pub struct VisibleCore {
pub position: Position,
pub owner: u32,
pub active: bool,
}
/// Fog-filtered game state visible to this bot
#[derive(Debug, Clone, Deserialize)]
pub struct GameState {
pub match_id: String,
pub turn: u32,
pub config: GameConfig,
pub you: PlayerInfo,
#[serde(default)]
pub bots: Vec<VisibleBot>,
#[serde(default)]
pub energy: Vec<Position>,
#[serde(default)]
pub cores: Vec<VisibleCore>,
#[serde(default)]
pub walls: Vec<Position>,
#[serde(default)]
pub dead: Vec<VisibleBot>,
}
/// Movement direction
#[derive(Debug, Clone, Copy, Serialize)]
pub enum Direction {
#[serde(rename = "N")]
N,
#[serde(rename = "E")]
E,
#[serde(rename = "S")]
S,
#[serde(rename = "W")]
W,
}
/// A single move command
#[derive(Debug, Clone, Serialize)]
pub struct Move {
pub position: Position,
pub direction: Direction,
}
/// Response containing moves
#[derive(Debug, Clone, Serialize)]
pub struct MoveResponse {
pub moves: Vec<Move>,
}
impl Direction {
/// All directions in order: N, E, S, W
pub fn all() -> [Direction; 4] {
[Direction::N, Direction::E, Direction::S, Direction::W]
}
}
impl Position {
/// Move in a direction, wrapping around the toroidal grid
pub fn move_toward(&self, dir: Direction, rows: i32, cols: i32) -> Position {
match dir {
Direction::N => Position {
row: (self.row - 1 + rows) % rows,
col: self.col,
},
Direction::E => Position {
row: self.row,
col: (self.col + 1) % cols,
},
Direction::S => Position {
row: (self.row + 1) % rows,
col: self.col,
},
Direction::W => Position {
row: self.row,
col: (self.col - 1 + cols) % cols,
},
}
}
/// Calculate squared distance with toroidal wrapping
pub fn distance2(&self, other: &Position, rows: i32, cols: i32) -> u32 {
let dr = (self.row - other.row).abs();
let dc = (self.col - other.col).abs();
let dr = dr.min(rows - dr);
let dc = dc.min(cols - dc);
(dr * dr + dc * dc) as u32
}
/// Toroidal delta (signed shortest displacement from self to other)
pub fn delta_to(&self, other: &Position, rows: i32, cols: i32) -> (i32, i32) {
let dr = other.row - self.row;
let dc = other.col - self.col;
let dr = if dr.abs() <= rows / 2 { dr } else { dr - rows * dr.signum() };
let dc = if dc.abs() <= cols / 2 { dc } else { dc - cols * dc.signum() };
(dr, dc)
}
}