diff --git a/bots/rusher/src/strategy.rs b/bots/rusher/src/strategy.rs index a745290..eb11980 100644 --- a/bots/rusher/src/strategy.rs +++ b/bots/rusher/src/strategy.rs @@ -43,14 +43,31 @@ impl RusherStrategy { // Build wall lookup let walls: HashSet = state.walls.iter().copied().collect(); - // Find target cores to rush - let targets = self.get_rush_targets(state, my_id); - - // Assign each bot to the nearest target + // Assign each bot to a move let mut moves = Vec::with_capacity(my_bots.len()); let mut assigned_targets: HashSet = HashSet::new(); for bot in &my_bots { + // Zone awareness: if zone is active and bot is outside, move toward center immediately + if let Some(ref zone) = state.zone { + if zone.active { + let dist2 = bot.position.distance2(&zone.center, config.rows as i32, config.cols as i32); + if dist2 > zone.radius * zone.radius { + // Bot is outside the zone - survival priority: move toward zone center + if let Some(dir) = self.move_toward_position(bot.position, zone.center, &enemy_positions, &walls, config) { + moves.push(Move { + position: bot.position, + direction: dir, + }); + continue; + } + } + } + } + + // Find target cores to rush + let targets = self.get_rush_targets(state, my_id); + if let Some((dir, _)) = self.find_best_move( bot.position, &targets, @@ -148,6 +165,9 @@ impl RusherStrategy { } // Don't walk into enemy bots (but allow pathing near them) + // Note: We avoid enemy tiles to prevent self-collision, but + // don't detour around them - RusherBot prefers direct paths + // and will engage enemies at range (AttackRadius2) if enemy_positions.contains(&next) { continue; } @@ -157,7 +177,30 @@ impl RusherStrategy { } } - // No path found - pick a random direction + // No path found - prefer moving toward nearest enemy for engagement + if let Some(&nearest_enemy) = enemy_positions.iter().min_by_key(|e| { + start.distance2(e, rows, cols) + }) { + let mut best_dir = None; + let mut best_dist2 = u32::MAX; + + for dir in Direction::all() { + let next = start.move_toward(dir, rows, cols); + if !walls.contains(&next) && !enemy_positions.contains(&next) { + let dist2 = next.distance2(&nearest_enemy, rows, cols); + if dist2 < best_dist2 { + best_dist2 = dist2; + best_dir = Some(dir); + } + } + } + + if let Some(dir) = best_dir { + return Some((dir, start.move_toward(dir, rows, cols))); + } + } + + // Fallback: pick a random direction for dir in Direction::all() { let next = start.move_toward(dir, rows, cols); if !walls.contains(&next) && !enemy_positions.contains(&next) { @@ -190,3 +233,38 @@ impl Default for RusherStrategy { Self::new() } } + +impl RusherStrategy { + /// Move toward a target position, avoiding walls and enemies + fn move_toward_position( + &self, + bot_pos: Position, + target: Position, + enemy_positions: &HashSet, + walls: &HashSet, + config: &GameConfig, + ) -> Option { + let rows = config.rows as i32; + let cols = config.cols as i32; + + let mut best_dir = None; + let mut best_dist2 = u32::MAX; + + for dir in Direction::all() { + let next = bot_pos.move_toward(dir, rows, cols); + + // Skip walls and enemy positions + if walls.contains(&next) || enemy_positions.contains(&next) { + continue; + } + + let dist2 = next.distance2(&target, rows, cols); + if dist2 < best_dist2 { + best_dist2 = dist2; + best_dir = Some(dir); + } + } + + best_dir + } +} diff --git a/bots/swarm/src/strategy.ts b/bots/swarm/src/strategy.ts index 930e28d..12e9b44 100644 --- a/bots/swarm/src/strategy.ts +++ b/bots/swarm/src/strategy.ts @@ -82,7 +82,8 @@ export class SwarmStrategy { swarmCenter, enemyCenter, walls, - config + config, + state ); if (move) { moves.push(move); @@ -133,11 +134,21 @@ export class SwarmStrategy { swarmCenter: Position, enemyCenter: Position | null, walls: Set, - config: GameConfig + config: GameConfig, + state: GameState ): Move | null { const rows = config.rows; const cols = config.cols; + // Zone awareness: if zone is active and bot is outside, move toward center immediately + if (state.zone && state.zone.active) { + const distToZoneCenter2 = distance2(bot.position, state.zone.center, rows, cols); + if (distToZoneCenter2 > state.zone.radius * state.zone.radius) { + // Bot is outside the zone - survival priority: move toward zone center + return this.moveTowardPosition(bot, state.zone.center, walls, rows, cols); + } + } + // Find direction that maintains cohesion while advancing toward enemy let bestDir: Direction | null = null; let bestScore = -Infinity; @@ -173,11 +184,16 @@ export class SwarmStrategy { // Bonus for moving toward nearby enemies (engagement) let nearestEnemyDist = Infinity; + let currentNearestEnemyDist = Infinity; for (const enemy of enemyPositions.values()) { const dist = distance2(newPos, enemy.position, rows, cols); nearestEnemyDist = Math.min(nearestEnemyDist, dist); + const currentDist = distance2(bot.position, enemy.position, rows, cols); + currentNearestEnemyDist = Math.min(currentNearestEnemyDist, currentDist); } if (nearestEnemyDist < Infinity) { + // Bonus for getting closer to enemies (encourages active engagement) + score += (currentNearestEnemyDist - nearestEnemyDist) * 5; // Bonus for being in attack range if (nearestEnemyDist <= config.attack_radius2) { score += 50; @@ -198,6 +214,42 @@ export class SwarmStrategy { return null; } + /** + * Move toward a target position, avoiding walls + */ + private moveTowardPosition( + bot: VisibleBot, + target: Position, + walls: Set, + rows: number, + cols: number + ): Move | null { + let bestDir: Direction | null = null; + let bestDist2 = Infinity; + + for (const dir of ALL_DIRECTIONS) { + const newPos = moveToward(bot.position, dir, rows, cols); + const newPosKey = posKey(newPos); + + // Can't move into walls + if (walls.has(newPosKey)) { + continue; + } + + const dist2 = distance2(newPos, target, rows, cols); + if (dist2 < bestDist2) { + bestDist2 = dist2; + bestDir = dir; + } + } + + if (bestDir) { + return { position: bot.position, direction: bestDir }; + } + + return null; + } + /** * Check if moving to newPos maintains cohesion with friendly bots */