refactor(ai): improve city placement with working radius awareness
- Add aiCitySiteScore to evaluate tiles only outside existing city radii - Pre-compute covered tiles to avoid overlapping working ranges - Increase search radius from 7 to 12 for finding distant good sites - Reject sites within 3 tiles of own cities; soft penalty at 4-5 tiles - Replace deprecated siteQuality import with new scoring logic
This commit is contained in:
parent
929d27e188
commit
65a6ee12ad
|
|
@ -21,7 +21,6 @@ import {
|
|||
considerRequest, considerGift, makeTreatyRequest, resolveRequest, aiWouldAccept,
|
||||
escalateFrustration, checkPledges, pickFairTrade, setCooldown,
|
||||
} from './CivilizationDiplomacy.js';
|
||||
import { siteQuality } from './CivilizationWorldGen.js';
|
||||
|
||||
const MAX_UNIT_STEPS = 40; // per unit per turn, guards against loops
|
||||
|
||||
|
|
@ -437,11 +436,50 @@ function stepSettler(rules, state, civIdx, unit, strategy) {
|
|||
return moveToward(rules, state, unit, best.x, best.y);
|
||||
}
|
||||
|
||||
function aiCitySiteScore(rules, world, x, y, covered) {
|
||||
// Score the full working radius (Chebyshev distance <= 2), but only count
|
||||
// tiles that aren't already within range of another city.
|
||||
const { cols, rows } = world;
|
||||
let q = 0;
|
||||
for (let dy = -2; dy <= 2; dy += 1) {
|
||||
for (let dx = -2; dx <= 2; dx += 1) {
|
||||
const nx = x + dx;
|
||||
const ny = y + dy;
|
||||
if (nx < 0 || ny < 0 || nx >= cols || ny >= rows) continue;
|
||||
if (covered.has(nx * 1000 + ny)) continue; // tile already covered by another city
|
||||
const terr = rules.terrainList[world.terrain[ny * cols + nx]];
|
||||
if (terr.water || terr.id === 'mountains' || terr.id === 'glacier') continue;
|
||||
const spec = world.special[ny * cols + nx] >= 0 ? rules.specialList[world.special[ny * cols + nx]] : null;
|
||||
const food = spec ? spec.food : terr.food;
|
||||
const shield = spec ? spec.shield : terr.shield;
|
||||
const trade = spec ? spec.trade : terr.trade;
|
||||
q += food * 3 + shield * 2 + trade;
|
||||
}
|
||||
}
|
||||
return q;
|
||||
}
|
||||
|
||||
export function bestCitySite(rules, state, civIdx, unit) {
|
||||
const { world } = state;
|
||||
const myCities = civCities(state, civIdx);
|
||||
let best = null;
|
||||
let bestScore = -Infinity;
|
||||
const R = 7;
|
||||
const R = 12; // search wider so distant good sites are found
|
||||
|
||||
// Pre-compute which tiles are already within working radius of own cities
|
||||
const covered = new Set();
|
||||
for (const city of myCities) {
|
||||
for (let dy = -2; dy <= 2; dy += 1) {
|
||||
for (let dx = -2; dx <= 2; dx += 1) {
|
||||
const cx = city.x + dx;
|
||||
const cy = city.y + dy;
|
||||
if (cx >= 0 && cy >= 0 && cx < world.cols && cy < world.rows) {
|
||||
covered.add(cx * 1000 + cy);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (let dy = -R; dy <= R; dy += 1) {
|
||||
for (let dx = -R; dx <= R; dx += 1) {
|
||||
const x = unit.x + dx;
|
||||
|
|
@ -450,10 +488,25 @@ export function bestCitySite(rules, state, civIdx, unit) {
|
|||
if (!canFoundCity(rules, state, x, y)) continue;
|
||||
const terr = terrainAt(rules, world, x, y);
|
||||
if (terr.water || terr.id === 'mountains') continue;
|
||||
|
||||
// Don't found right on top of a neighbour's doorstep
|
||||
const enemyClose = state.civs.some((c) => c.alive && c.id !== civIdx
|
||||
&& civCities(state, c.id).some((ct) => cheb(ct.x, ct.y, x, y) <= 3));
|
||||
if (enemyClose) continue;
|
||||
const score = siteQuality(rules, world, x, y) - cheb(unit.x, unit.y, x, y) * 2;
|
||||
|
||||
// Penalize sites too close to own cities (overlapping working radii)
|
||||
let tooClose = false;
|
||||
let minOwnDist = Infinity;
|
||||
for (const city of myCities) {
|
||||
const d = cheb(city.x, city.y, x, y);
|
||||
if (d < minOwnDist) minOwnDist = d;
|
||||
if (d <= 3) { tooClose = true; break; } // hard reject: radii overlap almost fully
|
||||
}
|
||||
if (tooClose) continue;
|
||||
|
||||
const score = aiCitySiteScore(rules, world, x, y, covered)
|
||||
- cheb(unit.x, unit.y, x, y) * 1.5
|
||||
- (minOwnDist <= 5 ? (5 - minOwnDist) * 6 : 0); // soft penalty at 4-5 tiles
|
||||
if (score > bestScore) { bestScore = score; best = { x, y }; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue