Improve bot (try)

This commit is contained in:
Mike Müller 2026-03-08 21:11:12 +01:00
parent 86ae86febc
commit cfeda33d33
2 changed files with 199 additions and 49 deletions

1
.gitignore vendored
View File

@ -130,3 +130,4 @@ dist
.yarn/install-state.gz .yarn/install-state.gz
.pnp.* .pnp.*
highscores.json

View File

@ -1452,23 +1452,123 @@ function adjacentCrate(x, y) {
return false; return false;
} }
function enemyInBlastPotential(player) { function countAdjacentCrates(x, y) {
let count = 0;
for (const dir of DIRECTIONS) {
const nx = x + dir.dx;
const ny = y + dir.dy;
if (inBounds(nx, ny) && state.map[ny][nx].type === "crate") {
count += 1;
}
}
return count;
}
function threatenedEnemiesFromCell(player, x, y) {
let count = 0;
for (const other of state.players) { for (const other of state.players) {
if (!other.alive || other.id === player.id) { if (!other.alive || other.id === player.id) {
continue; continue;
} }
if (player.x !== other.x && player.y !== other.y) { if (x !== other.x && y !== other.y) {
continue; continue;
} }
const distance = Math.abs(player.x - other.x) + Math.abs(player.y - other.y); const distance = Math.abs(x - other.x) + Math.abs(y - other.y);
if (distance > player.flameRange) { if (distance > player.flameRange) {
continue; continue;
} }
if (!hasHardBlockBetween(player.x, player.y, other.x, other.y)) { if (!hasHardBlockBetween(x, y, other.x, other.y)) {
return true; count += 1;
} }
} }
return false; return count;
}
function nearestEnemyDistance(x, y, playerId) {
let best = Infinity;
for (const other of state.players) {
if (!other.alive || other.id === playerId) {
continue;
}
const distance = Math.abs(x - other.x) + Math.abs(y - other.y);
if (distance < best) {
best = distance;
}
}
return best;
}
function botFindDirection(player, evaluator, maxDepth = 8) {
const visited = new Set([tileKey(player.x, player.y)]);
const queue = [];
for (const dir of DIRECTIONS) {
const nx = player.x + dir.dx;
const ny = player.y + dir.dy;
if (!canEnterTile(nx, ny, player)) {
continue;
}
visited.add(tileKey(nx, ny));
queue.push({ x: nx, y: ny, depth: 1, firstDir: dir.key });
}
let best = null;
while (queue.length > 0) {
const node = queue.shift();
const score = evaluator(node.x, node.y, node.depth);
if (Number.isFinite(score)) {
if (!best || score > best.score || (score === best.score && node.depth < best.depth)) {
best = { score, depth: node.depth, dir: node.firstDir };
}
}
if (node.depth >= maxDepth) {
continue;
}
for (const dir of DIRECTIONS) {
const nx = node.x + dir.dx;
const ny = node.y + dir.dy;
const key = tileKey(nx, ny);
if (visited.has(key)) {
continue;
}
if (!canEnterTile(nx, ny, player)) {
continue;
}
visited.add(key);
queue.push({ x: nx, y: ny, depth: node.depth + 1, firstDir: node.firstDir });
}
}
return best ? best.dir : null;
}
function pickEscapeDirection(player) {
return botFindDirection(
player,
(x, y, depth) => {
const danger = estimateDangerAt(x, y);
let score = -danger * 3 - depth * 0.55;
if (danger < 0.6) {
score += 3.5;
}
if (!state.fireLookup.has(tileKey(x, y))) {
score += 0.6;
}
const tile = state.map[y][x];
if (tile && tile.powerup) {
score += 0.8;
}
return score;
},
9,
);
}
function enemyInBlastPotential(player) {
return threatenedEnemiesFromCell(player, player.x, player.y) > 0;
} }
function updateBot(player, dt) { function updateBot(player, dt) {
@ -1481,63 +1581,112 @@ function updateBot(player, dt) {
if (player.ai.thinkTimer > 0) { if (player.ai.thinkTimer > 0) {
return; return;
} }
player.ai.thinkTimer = 0.13 + Math.random() * 0.2; player.ai.thinkTimer = 0.08 + Math.random() * 0.12;
const currentDanger = estimateDangerAt(player.x, player.y); const currentDanger = estimateDangerAt(player.x, player.y);
const liveEnemies = state.players.filter((p) => p.alive && p.id !== player.id); const currentKey = tileKey(player.x, player.y);
const targetEnemy = liveEnemies.sort( const targetEnemy = state.players
(a, b) => .filter((p) => p.alive && p.id !== player.id)
Math.abs(a.x - player.x) + Math.abs(a.y - player.y) - .sort(
(Math.abs(b.x - player.x) + Math.abs(b.y - player.y)), (a, b) =>
)[0]; Math.abs(a.x - player.x) + Math.abs(a.y - player.y) -
(Math.abs(b.x - player.x) + Math.abs(b.y - player.y)),
)[0];
if (state.fireLookup.has(currentKey) || currentDanger >= 1.2) {
player.ai.desiredDir = pickEscapeDirection(player);
return;
}
const cratePressure = countAdjacentCrates(player.x, player.y);
const enemyPressure = threatenedEnemiesFromCell(player, player.x, player.y);
if ( if (
player.bombsPlaced < player.bombCapacity && player.bombsPlaced < player.bombCapacity &&
player.ai.bombCooldown <= 0 && player.ai.bombCooldown <= 0 &&
currentDanger < 0.9 && currentDanger < 0.8 &&
(adjacentCrate(player.x, player.y) || enemyInBlastPotential(player)) && hasEscapeRouteAfterBomb(player) &&
hasEscapeRouteAfterBomb(player) (enemyPressure > 0 || cratePressure >= 2 || (cratePressure >= 1 && Math.random() < 0.45))
) { ) {
if (dropBomb(player)) { if (dropBomb(player)) {
player.ai.bombCooldown = 1.4 + Math.random() * 0.8; player.ai.bombCooldown = enemyPressure > 0 ? 0.8 + Math.random() * 0.45 : 1.1 + Math.random() * 0.55;
} }
} }
const options = []; let desiredDir = botFindDirection(
const dirs = shuffle([...DIRECTIONS]); player,
for (const dir of dirs) { (x, y, depth) => {
const nx = player.x + dir.dx; const tile = state.map[y][x];
const ny = player.y + dir.dy; if (!tile || !tile.powerup) {
if (!canEnterTile(nx, ny, player)) { return Number.NEGATIVE_INFINITY;
continue; }
} return 8 - depth - estimateDangerAt(x, y) * 1.8;
const danger = estimateDangerAt(nx, ny); },
let score = Math.random() * 0.35 - danger * 1.65; 7,
);
if (danger < currentDanger) { if (!desiredDir && targetEnemy) {
score += 2.5; desiredDir = botFindDirection(
} player,
(x, y, depth) => {
const tile = state.map[ny][nx]; const danger = estimateDangerAt(x, y);
if (tile.powerup) { if (danger > 4.2) {
score += 1.6; return Number.NEGATIVE_INFINITY;
} }
const dist = Math.abs(targetEnemy.x - x) + Math.abs(targetEnemy.y - y);
if (adjacentCrate(nx, ny)) { let score = 6 - dist * 1.1 - depth * 0.4 - danger * 1.3;
score += 0.3; if (countAdjacentCrates(x, y) > 0) {
} score += 0.35;
}
if (targetEnemy) { return score;
const nowDist = Math.abs(targetEnemy.x - player.x) + Math.abs(targetEnemy.y - player.y); },
const newDist = Math.abs(targetEnemy.x - nx) + Math.abs(targetEnemy.y - ny); 8,
score += (nowDist - newDist) * 0.45; );
}
options.push({ dir: dir.key, score });
} }
options.sort((a, b) => b.score - a.score); if (!desiredDir) {
player.ai.desiredDir = options.length > 0 ? options[0].dir : null; desiredDir = botFindDirection(
player,
(x, y, depth) => {
const crates = countAdjacentCrates(x, y);
if (crates <= 0) {
return Number.NEGATIVE_INFINITY;
}
return crates * 2.2 - depth * 0.55 - estimateDangerAt(x, y) * 1.4;
},
8,
);
}
if (!desiredDir) {
const options = [];
for (const dir of shuffle([...DIRECTIONS])) {
const nx = player.x + dir.dx;
const ny = player.y + dir.dy;
if (!canEnterTile(nx, ny, player)) {
continue;
}
const danger = estimateDangerAt(nx, ny);
const nearestEnemy = nearestEnemyDistance(nx, ny, player.id);
let score = Math.random() * 0.18 - danger * 1.8 - nearestEnemy * 0.16;
if (danger < currentDanger) {
score += 1.6;
}
if (adjacentCrate(nx, ny)) {
score += 0.25;
}
const tile = state.map[ny][nx];
if (tile.powerup) {
score += 1.4;
}
options.push({ dir: dir.key, score });
}
options.sort((a, b) => b.score - a.score);
desiredDir = options.length > 0 ? options[0].dir : null;
}
player.ai.desiredDir = desiredDir;
} }
function registerMultiplayerKill(killerPlayerId, victimPlayerId) { function registerMultiplayerKill(killerPlayerId, victimPlayerId) {