Merge branch 'main' into mac

This commit is contained in:
Christopher Mesona
2025-06-09 22:27:46 +02:00
committed by GitHub
2 changed files with 96 additions and 66 deletions
+2 -5
View File
@@ -58,11 +58,8 @@ export class BotExecution implements Execution {
if (this.behavior === null) {
throw new Error("not initialized");
}
const traitors = this.bot
.neighbors()
.filter((n) => n.isPlayer() && n.isTraitor()) as Player[];
if (traitors.length > 0) {
const toAttack = this.random.randElement(traitors);
const toAttack = this.behavior.getNeighborTraitorToAttack();
if (toAttack !== null) {
const odds = this.bot.isFriendly(toAttack) ? 6 : 3;
if (this.random.chance(odds)) {
this.behavior.sendAttack(toAttack);
+94 -61
View File
@@ -43,22 +43,48 @@ export class BotBehavior {
this.game.addExecution(new EmojiExecution(this.player, player.id(), emoji));
}
private setNewEnemy(newEnemy: Player | null) {
this.enemy = newEnemy;
this.enemyUpdated = this.game.ticks();
}
private clearEnemy() {
this.enemy = null;
}
forgetOldEnemies() {
// Forget old enemies
if (this.game.ticks() - this.enemyUpdated > 100) {
this.enemy = null;
this.clearEnemy();
}
}
private hasSufficientTroops(): boolean {
const maxPop = this.game.config().maxPopulation(this.player);
const ratio = this.player.population() / maxPop;
return ratio >= this.triggerRatio;
}
private checkIncomingAttacks() {
// Switch enemies if we're under attack
const incomingAttacks = this.player.incomingAttacks();
if (incomingAttacks.length > 0) {
this.enemy = incomingAttacks
.sort((a, b) => b.troops() - a.troops())[0]
.attacker();
this.enemyUpdated = this.game.ticks();
let largestAttack = 0;
let largestAttacker: Player | undefined;
for (const attack of incomingAttacks) {
if (attack.troops() <= largestAttack) continue;
largestAttack = attack.troops();
largestAttacker = attack.attacker();
}
if (largestAttacker !== undefined) {
this.setNewEnemy(largestAttacker);
}
}
getNeighborTraitorToAttack(): Player | null {
const traitors = this.player
.neighbors()
.filter((n): n is Player => n.isPlayer() && n.isTraitor());
return traitors.length > 0 ? this.random.randElement(traitors) : null;
}
assistAllies() {
@@ -79,8 +105,7 @@ export class BotBehavior {
}
// All checks passed, assist them
this.player.updateRelation(ally, -20);
this.enemy = target;
this.enemyUpdated = this.game.ticks();
this.setNewEnemy(target);
this.emoji(ally, this.assistAcceptEmoji);
break outer;
}
@@ -90,50 +115,55 @@ export class BotBehavior {
selectEnemy(): Player | null {
if (this.enemy === null) {
// Save up troops until we reach the trigger ratio
const maxPop = this.game.config().maxPopulation(this.player);
const ratio = this.player.population() / maxPop;
if (ratio < this.triggerRatio) return null;
}
if (!this.hasSufficientTroops()) return null;
// Prefer neighboring bots
if (this.enemy === null) {
// Prefer neighboring bots
const bots = this.player
.neighbors()
.filter((n) => n.isPlayer() && n.type() === PlayerType.Bot) as Player[];
if (bots.length > 0) {
const density = (p: Player) => p.troops() / p.numTilesOwned();
this.enemy = bots.sort((a, b) => density(a) - density(b))[0];
this.enemyUpdated = this.game.ticks();
let lowestDensityBot: Player | undefined;
let lowestDensity = Infinity;
for (const bot of bots) {
const currentDensity = density(bot);
if (currentDensity < lowestDensity) {
lowestDensity = currentDensity;
lowestDensityBot = bot;
}
}
if (lowestDensityBot !== undefined) {
this.setNewEnemy(lowestDensityBot);
}
}
}
// Retaliate against incoming attacks
if (this.enemy === null) {
this.checkIncomingAttacks();
}
// Retaliate against incoming attacks
if (this.enemy === null) {
this.checkIncomingAttacks();
}
// Select the most hated player
if (this.enemy === null) {
const mostHated = this.player.allRelationsSorted()[0];
if (mostHated !== undefined && mostHated.relation === Relation.Hostile) {
this.enemy = mostHated.player;
this.enemyUpdated = this.game.ticks();
// Select the most hated player
if (this.enemy === null) {
const mostHated = this.player.allRelationsSorted()[0];
if (
mostHated !== undefined &&
mostHated.relation === Relation.Hostile
) {
this.setNewEnemy(mostHated.player);
}
}
}
// Sanity check, don't attack our allies or teammates
if (this.enemy && this.player.isFriendly(this.enemy)) {
this.enemy = null;
}
return this.enemy;
return this.enemySanityCheck();
}
selectRandomEnemy(): Player | TerraNullius | null {
if (this.enemy === null) {
// Save up troops until we reach the trigger ratio
const maxPop = this.game.config().maxPopulation(this.player);
const ratio = this.player.population() / maxPop;
if (ratio < this.triggerRatio) return null;
if (!this.hasSufficientTroops()) return null;
// Choose a new enemy randomly
const neighbors = this.player.neighbors();
@@ -145,34 +175,32 @@ export class BotBehavior {
continue;
}
}
this.enemy = neighbor;
this.enemyUpdated = this.game.ticks();
this.setNewEnemy(neighbor);
}
}
// Retaliate against incoming attacks
if (this.enemy === null) {
this.checkIncomingAttacks();
}
// Retaliate against incoming attacks
if (this.enemy === null) {
this.checkIncomingAttacks();
}
// Select a traitor as an enemy
if (this.enemy === null) {
const traitors = this.player
.neighbors()
.filter((n) => n.isPlayer() && n.isTraitor()) as Player[];
if (traitors.length > 0) {
const toAttack = this.random.randElement(traitors);
const odds = this.player.isFriendly(toAttack) ? 6 : 3;
if (this.random.chance(odds)) {
this.enemy = toAttack;
this.enemyUpdated = this.game.ticks();
// Select a traitor as an enemy
if (this.enemy === null) {
const toAttack = this.getNeighborTraitorToAttack();
if (toAttack !== null) {
if (!this.player.isFriendly(toAttack) && this.random.chance(3)) {
this.setNewEnemy(toAttack);
}
}
}
}
// Sanity check, don't attack our allies or teammates
return this.enemySanityCheck();
}
private enemySanityCheck(): Player | null {
if (this.enemy && this.player.isFriendly(this.enemy)) {
this.enemy = null;
this.clearEnemy();
}
return this.enemy;
}
@@ -200,12 +228,17 @@ export class BotBehavior {
}
function shouldAcceptAllianceRequest(player: Player, request: AllianceRequest) {
const isTraitor = request.requestor().isTraitor();
const hasMalice = player.relation(request.requestor()) < Relation.Neutral;
const requestorIsMuchLarger =
request.requestor().numTilesOwned() > player.numTilesOwned() * 3;
const tooManyAlliances = request.requestor().alliances().length >= 3;
return (
!isTraitor && !hasMalice && (requestorIsMuchLarger || !tooManyAlliances)
);
if (player.relation(request.requestor()) < Relation.Neutral) {
return false; // Reject if hasMalice
}
if (request.requestor().isTraitor()) {
return false; // Reject if isTraitor
}
if (request.requestor().numTilesOwned() > player.numTilesOwned() * 3) {
return true; // Accept if requestorIsMuchLarger
}
if (request.requestor().alliances().length >= 3) {
return false; // Reject if tooManyAlliances
}
return true; // Accept otherwise
}