ai-code-battle/bots/kamikaze/strategy.js
jedarden f11d00a177 feat(bot): add Assassin bot (Rust) — decapitation archetype
All units rush the nearest enemy core via BFS, ignoring enemy units
and economy. No perimeter defense — full commitment to core destruction.
Targets persist across turns for consistent pathing.

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

154 lines
3.9 KiB
JavaScript

const { distance2, manhattan, moveDir, posKey } = require("./grid");
const DIRECTIONS = ["N", "E", "S", "W"];
function computeMoves(state) {
const { rows, cols, attack_radius2 } = state.config;
const myId = state.you.id;
const myBots = [];
const enemyBots = [];
for (const bot of state.bots) {
if (bot.owner === myId) myBots.push(bot);
else enemyBots.push(bot);
}
if (myBots.length === 0) return [];
const walls = new Set(state.walls.map((w) => posKey(w.row, w.col)));
const enemyCores = state.cores.filter(
(c) => c.owner !== myId && c.active
);
const energySet = new Set(
(state.energy || []).map((e) => posKey(e.row, e.col))
);
const committed = new Set();
const moves = [];
// Bots already adjacent to enemies get priority so they press the attack
myBots.sort((a, b) => {
const da = nearestEnemyDist2(a.position, enemyBots, rows, cols);
const db = nearestEnemyDist2(b.position, enemyBots, rows, cols);
return da - db;
});
for (const bot of myBots) {
const br = bot.position.row;
const bc = bot.position.col;
// Find the nearest enemy
const target = findNearestEnemy(br, bc, enemyBots, rows, cols);
let bestDir = null;
let bestScore = -Infinity;
for (const dir of DIRECTIONS) {
const [nr, nc] = moveDir(br, bc, dir, rows, cols);
const nk = posKey(nr, nc);
if (walls.has(nk)) continue;
if (committed.has(nk)) continue;
let score = 0;
if (target) {
// Primary: move toward nearest enemy — minimize squared distance
const distToTarget = distance2(
nr, nc, target.position.row, target.position.col, rows, cols
);
score -= distToTarget * 10;
// Bonus for being within attack range (press the engagement)
if (distToTarget <= attack_radius2) {
score += 50;
}
// Secondary: prefer directions that also close distance to other enemies
let totalEnemyDist = 0;
for (const e of enemyBots) {
totalEnemyDist += distance2(
nr, nc, e.position.row, e.position.col, rows, cols
);
}
score -= totalEnemyDist * 0.1;
// Small bonus for collecting energy if it's along the way
if (energySet.has(nk)) {
score += 5;
}
} else {
// No enemies visible — march toward enemy core
if (enemyCores.length > 0) {
const coreDist = nearestCoreDist(
nr, nc, enemyCores, rows, cols
);
score -= coreDist * 10;
}
// Collect energy opportunistically
if (energySet.has(nk)) {
score += 3;
}
}
if (score > bestScore) {
bestScore = score;
bestDir = dir;
}
}
// Commit the destination to prevent self-collision
const targetKey = bestDir
? posKey(...moveDir(br, bc, bestDir, rows, cols))
: posKey(br, bc);
if (!committed.has(targetKey)) {
committed.add(targetKey);
if (bestDir) {
moves.push({
position: { row: br, col: bc },
direction: bestDir,
});
}
}
}
return moves;
}
function findNearestEnemy(r, c, enemyBots, rows, cols) {
let best = null;
let bestDist = Infinity;
for (const e of enemyBots) {
const d = distance2(r, c, e.position.row, e.position.col, rows, cols);
if (d < bestDist) {
bestDist = d;
best = e;
}
}
return best;
}
function nearestEnemyDist2(pos, enemyBots, rows, cols) {
let minD = Infinity;
for (const e of enemyBots) {
const d = distance2(
pos.row, pos.col, e.position.row, e.position.col, rows, cols
);
if (d < minD) minD = d;
}
return minD;
}
function nearestCoreDist(r, c, cores, rows, cols) {
let minD = Infinity;
for (const core of cores) {
const d = manhattan(
r, c, core.position.row, core.position.col, rows, cols
);
if (d < minD) minD = d;
}
return minD;
}
module.exports = { computeMoves };