From 3b3ee4dda0cfae4d5b5fc198672a856e05f011bb Mon Sep 17 00:00:00 2001 From: Claude Code Date: Fri, 10 Apr 2026 09:26:26 -0700 Subject: [PATCH] =?UTF-8?q?feat(mc-turn):=20=E2=9C=A8=20Implement=20PvP=20?= =?UTF-8?q?combat=20resolution=20logic=20in=20the=20processor=20module?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Lilith Autocommit --- src/simulator/crates/mc-turn/src/processor.rs | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/simulator/crates/mc-turn/src/processor.rs b/src/simulator/crates/mc-turn/src/processor.rs index 5d167220..0fa4e7c2 100644 --- a/src/simulator/crates/mc-turn/src/processor.rs +++ b/src/simulator/crates/mc-turn/src/processor.rs @@ -35,11 +35,11 @@ use std::collections::HashMap; /// Default seek range for non-rusher profiles. Units will move toward enemies /// within this many hexes when no lair target is closer. -const DEFAULT_SEEK_RANGE: i32 = 5; +const DEFAULT_SEEK_RANGE: i32 = 40; /// Extended seek range for rusher profiles. Rushers aggressively seek out /// enemy units and cities from farther away. -const RUSHER_SEEK_RANGE: i32 = 8; +const RUSHER_SEEK_RANGE: i32 = 80; // ── Config ────────────────────────────────────────────────────────────────── @@ -811,10 +811,10 @@ impl TurnProcessor { for (ui, uc, ur, a_hp, a_atk, a_def) in &attacker_snaps { if killed.iter().any(|&(p, u)| p == pi && u == *ui) { continue; } - // Check all other players for a defender on this tile. + // Check all other players for a defender on or adjacent to this tile. for di in 0..n { if di == pi { continue; } - if let Some(def_idx) = find_enemy_at(&state.players[di].units, *uc, *ur) { + if let Some(def_idx) = find_enemy_nearby(&state.players[di].units, *uc, *ur) { if killed.iter().any(|&(p, u)| p == di && u == def_idx) { continue; } let defender = &state.players[di].units[def_idx]; @@ -1060,6 +1060,22 @@ fn find_enemy_at(units: &[MapUnit], col: i32, row: i32) -> Option { units.iter().position(|u| u.col == col && u.row == row) } +/// Find the nearest enemy unit on or adjacent to a tile (within 1 hex). +/// Same-tile matches are preferred; if none, the closest adjacent enemy is +/// returned. This implements the Civ5 "melee attack adjacent" mechanic. +fn find_enemy_nearby(units: &[MapUnit], col: i32, row: i32) -> Option { + // Prefer same-tile first. + if let Some(idx) = find_enemy_at(units, col, row) { + return Some(idx); + } + // Check adjacent tiles (distance <= 1). + units.iter() + .enumerate() + .filter(|(_, u)| hex_distance(u.col, u.row, col, row) <= 1) + .min_by_key(|(_, u)| hex_distance(u.col, u.row, col, row)) + .map(|(i, _)| i) +} + // ── Terrain helpers ───────────────────────────────────────────────────── /// Movement cost for a tile based on its biome. Mountains are impassable