From 4dd91decad4538636d34df90d01199a74a7e1392 Mon Sep 17 00:00:00 2001 From: jedarden Date: Wed, 22 Apr 2026 17:11:28 -0400 Subject: [PATCH] =?UTF-8?q?feat(bot):=20implement=20Kamikaze=20bot=20(Java?= =?UTF-8?q?Script)=20=E2=80=94=20aggressive=20self-sacrifice=20archetype?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Max-aggression strategy with unconditional attack: every unit charges the nearest enemy, never retreats, and presses engagements within attack range. Fixed self-collision bug by reserving all starting positions and freeing them as bots move away. Falls back to enemy core rushing when no enemies are visible, with exploratory spreading when no targets exist. Co-Authored-By: Claude Opus 4.7 --- bots/kamikaze/strategy.js | 65 +++++++++++++++++++++++---------------- 1 file changed, 38 insertions(+), 27 deletions(-) diff --git a/bots/kamikaze/strategy.js b/bots/kamikaze/strategy.js index 1d55bbc..57264af 100644 --- a/bots/kamikaze/strategy.js +++ b/bots/kamikaze/strategy.js @@ -22,10 +22,14 @@ function computeMoves(state) { (state.energy || []).map((e) => posKey(e.row, e.col)) ); - const committed = new Set(); + // Reserve all starting positions so no two bots end up on the same tile. + // When a bot moves away, its old cell is freed for others. + const committed = new Set( + myBots.map((b) => posKey(b.position.row, b.position.col)) + ); const moves = []; - // Bots already adjacent to enemies get priority so they press the attack + // Bots closest to enemies decide first — they get priority on attack positions myBots.sort((a, b) => { const da = nearestEnemyDist2(a.position, enemyBots, rows, cols); const db = nearestEnemyDist2(b.position, enemyBots, rows, cols); @@ -36,7 +40,6 @@ function computeMoves(state) { 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; @@ -52,42 +55,54 @@ function computeMoves(state) { 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) + // Close distance to nearest enemy as fast as possible + score -= distToTarget * 100; + + // Heavy bonus for staying in attack range — press the engagement if (distToTarget <= attack_radius2) { - score += 50; + score += 200; } - // Secondary: prefer directions that also close distance to other enemies + // Also prefer closing distance to other enemies (don't tunnel-vision) let totalEnemyDist = 0; for (const e of enemyBots) { totalEnemyDist += distance2( nr, nc, e.position.row, e.position.col, rows, cols ); } - score -= totalEnemyDist * 0.1; + score -= totalEnemyDist; - // Small bonus for collecting energy if it's along the way + // Grab energy only when it's directly along the attack path if (energySet.has(nk)) { score += 5; } } else { - // No enemies visible — march toward enemy core + // No enemies visible — rush enemy core to raze it if (enemyCores.length > 0) { const coreDist = nearestCoreDist( nr, nc, enemyCores, rows, cols ); - score -= coreDist * 10; + score -= coreDist * 100; + } else { + // No targets at all — spread outward to explore + // Prefer moving away from other friendly bots + let friendProximity = 0; + for (const other of myBots) { + if (other === bot) continue; + friendProximity += distance2( + nr, nc, other.position.row, other.position.col, rows, cols + ); + } + score += friendProximity * 0.5; } - // Collect energy opportunistically + // Collect energy while roaming (we need it to keep spawning) if (energySet.has(nk)) { - score += 3; + score += 10; } } @@ -97,20 +112,16 @@ function computeMoves(state) { } } - // 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, - }); - } + if (bestDir) { + const [nr, nc] = moveDir(br, bc, bestDir, rows, cols); + committed.delete(posKey(br, bc)); + committed.add(posKey(nr, nc)); + moves.push({ + position: { row: br, col: bc }, + direction: bestDir, + }); } + // If no direction is viable the bot holds; its starting cell stays in committed } return moves;