mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-22 05:03:50 +00:00
af86a9222f
## Description: > [!IMPORTANT] > Try here: https://mirv-test.openfront.dev/ > [!NOTE] > Blocks PRs: > - #2244 > - #2263 ### Summary Implements intelligent MIRV usage for fakehuman players, enabling them to make strategic nuclear strikes based on game state analysis. ### Changes #### Core FakeHuman Strategy (`FakeHumanExecution.ts`) - **MIRV Decision System**: Added `considerMIRV()` method that evaluates game state and determines optimal MIRV usage - **Three Strategic Targeting Modes**: 1. **Counter-MIRV**: Retaliatory strikes against players actively launching MIRVs at the fakehuman 2. **Victory Denial**: Preemptive strikes against players approaching win conditions - Team threshold: n% of total land (configurable) - Individual threshold: n% of total land (configurable) 3. **Steamroll Prevention**: Strikes against players with dominant city counts (n% ahead of next competitor) #### FakeHuman Behavior Tuning - **Cooldown System**: n-minute cooldown between MIRV attempts (configurable) - **Failure Rate**: ~n% chance of cooldown trigger without launch (simulates human hesitation/resource management; configurable) - **Territory Targeting**: Centers MIRV strikes on enemy territory center-of-mass for maximum impact #### Technical Improvements - **Type Safety**: Updated `UnitParamsMap` to include `targetTile` parameter for MIRV units - **Execution Flow**: Integrated MIRV consideration into fakehuman tick cycle outside of standard attack logic, due to its holistic strategic nature ### Game Balance Impact - **FakeHuman Threat Level**: Increases late-game fakehuman competitiveness - **Endgame Dynamics**: Prevents runaway victories, extends game tension ### Breaking Changes None - purely additive feature ### Related GitHub Issues: - #2205 ------ ## Other - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced Discord Username: samsammiliah --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: Evan <evanpelle@gmail.com>
133 lines
3.5 KiB
TypeScript
133 lines
3.5 KiB
TypeScript
import { Game, Player } from "../game/Game";
|
|
import { euclDistFN, GameMap, TileRef } from "../game/GameMap";
|
|
|
|
export function getSpawnTiles(gm: GameMap, tile: TileRef): TileRef[] {
|
|
return Array.from(gm.bfs(tile, euclDistFN(tile, 4, true))).filter(
|
|
(t) => !gm.hasOwner(t) && gm.isLand(t),
|
|
);
|
|
}
|
|
|
|
export function closestTile(
|
|
gm: GameMap,
|
|
refs: Iterable<TileRef>,
|
|
tile: TileRef,
|
|
): [TileRef | null, number] {
|
|
let minDistance = Infinity;
|
|
let minRef: TileRef | null = null;
|
|
for (const ref of refs) {
|
|
const distance = gm.manhattanDist(ref, tile);
|
|
if (distance < minDistance) {
|
|
minDistance = distance;
|
|
minRef = ref;
|
|
}
|
|
}
|
|
return [minRef, minDistance];
|
|
}
|
|
|
|
export function closestTwoTiles(
|
|
gm: GameMap,
|
|
x: Iterable<TileRef>,
|
|
y: Iterable<TileRef>,
|
|
): { x: TileRef; y: TileRef } | null {
|
|
const xSorted = Array.from(x).sort((a, b) => gm.x(a) - gm.x(b));
|
|
const ySorted = Array.from(y).sort((a, b) => gm.x(a) - gm.x(b));
|
|
|
|
if (xSorted.length === 0 || ySorted.length === 0) {
|
|
return null;
|
|
}
|
|
|
|
let i = 0;
|
|
let j = 0;
|
|
let minDistance = Infinity;
|
|
let result = { x: xSorted[0], y: ySorted[0] };
|
|
|
|
while (i < xSorted.length && j < ySorted.length) {
|
|
const currentX = xSorted[i];
|
|
const currentY = ySorted[j];
|
|
|
|
const distance =
|
|
Math.abs(gm.x(currentX) - gm.x(currentY)) +
|
|
Math.abs(gm.y(currentX) - gm.y(currentY));
|
|
|
|
if (distance < minDistance) {
|
|
minDistance = distance;
|
|
result = { x: currentX, y: currentY };
|
|
}
|
|
|
|
// If we're at the end of X, must move Y forward
|
|
if (i === xSorted.length - 1) {
|
|
j++;
|
|
}
|
|
// If we're at the end of Y, must move X forward
|
|
else if (j === ySorted.length - 1) {
|
|
i++;
|
|
}
|
|
// Otherwise, move whichever pointer has smaller x value
|
|
else if (gm.x(currentX) < gm.x(currentY)) {
|
|
i++;
|
|
} else {
|
|
j++;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Calculates the center of a player's territory using geometric approach.
|
|
* Uses the bounding box center and verifies ownership, falling back to nearest border tile if necessary.
|
|
*
|
|
* @param game - The game instance
|
|
* @param target - The player whose territory center to calculate
|
|
* @returns The tile reference for the territory center, or null if no valid center found
|
|
*/
|
|
export function calculateTerritoryCenter(
|
|
game: Game,
|
|
target: Player,
|
|
): TileRef | null {
|
|
const borderTiles = target.borderTiles();
|
|
if (borderTiles.size === 0) return null;
|
|
|
|
// Calculate bounding box center in a single pass through border tiles
|
|
let minX = Infinity,
|
|
maxX = -Infinity;
|
|
let minY = Infinity,
|
|
maxY = -Infinity;
|
|
|
|
for (const tile of borderTiles) {
|
|
const x = game.x(tile);
|
|
const y = game.y(tile);
|
|
if (x < minX) minX = x;
|
|
if (x > maxX) maxX = x;
|
|
if (y < minY) minY = y;
|
|
if (y > maxY) maxY = y;
|
|
}
|
|
|
|
const centerX = Math.floor((minX + maxX) / 2);
|
|
const centerY = Math.floor((minY + maxY) / 2);
|
|
|
|
const centerTile = game.ref(centerX, centerY);
|
|
|
|
// Verify ownership of the center tile
|
|
if (game.owner(centerTile) === target) {
|
|
return centerTile;
|
|
}
|
|
|
|
// Fall back to nearest border tile if center is not owned
|
|
let closestTile: TileRef | null = null;
|
|
let closestDistanceSquared = Infinity;
|
|
|
|
for (const tile of borderTiles) {
|
|
const dx = game.x(tile) - centerX;
|
|
const dy = game.y(tile) - centerY;
|
|
const distSquared = dx * dx + dy * dy;
|
|
|
|
if (distSquared < closestDistanceSquared) {
|
|
closestDistanceSquared = distSquared;
|
|
closestTile = tile;
|
|
}
|
|
}
|
|
|
|
return closestTile;
|
|
}
|