1
0
Fork 0
spacetac/src/core/ai/AIScoringHelpers.ts

152 lines
5.6 KiB
TypeScript

module TK.SpaceTac {
/**
* Get the proportional effect done to a ship's health (in -1,1 range)
*/
function getHealthDiffShip(plan: AIPlan, ship: Ship): number {
let chull = ship.getAttribute("hull_capacity");
let cshield = ship.getAttribute("shield_capacity");
let hull = ship.getValue("hull")
let shield = ship.getValue("shield");
let dhull = 0;
let dshield = 0;
plan.effects.forEach(diff => {
if (diff instanceof ShipValueDiff) {
if (ship.is(diff.ship_id)) {
if (diff.code == "hull") {
dhull += clamp(hull + diff.diff, 0, chull) - hull;
} else if (diff.code == "shield") {
dshield += clamp(shield + diff.diff, 0, cshield) - shield;
}
}
}
});
if (hull + dhull <= 0) {
return -1;
} else {
let diff = dhull + dshield;
return clamp(diff / (hull + shield), -1, 1);
}
}
/**
* Evaluate the effect on health for a group of ships
*/
function getHealthDiffShips(plan: AIPlan, ships: Ship[]): number {
if (ships.length) {
let diffs = ships.map(ship => getHealthDiffShip(plan, ship));
let deaths = sum(diffs.map(i => i == -1 ? 1 : 0));
return ((sum(diffs) * 0.5) - (deaths * 0.5)) / ships.length;
} else {
return 0;
}
}
/**
* Functions to help scoring a turn plan produced by an AI
*
* Helpers should try to remain in the -1..1 range
*/
export class AIScoringHelpers {
/**
* Sum of other scoring methods
*/
static sum(...scorings: AIPlanScoring[]): AIPlanScoring {
return plan => sum(scorings.map(scoring => scoring(plan)));
}
/**
* Scaled version of another scoring method
*/
static scaled(evaluator: AIPlanScoring, factor: number): AIPlanScoring {
return (plan: AIPlan) => factor * evaluator(plan);
};
/**
* Negatively score the fleet's remaining power, between -1 and 0
*/
static remainingPower(plan: AIPlan): number {
const toggled = isum(imap(plan.battle.iallies(plan.player), ship => sum(ship.getToggleActions(true).map(action => action.getPowerUsage(ship)))));
const available = isum(imap(plan.battle.iallies(plan.player), ship => ship.getAttribute("power_capacity")));
const used = sum(cfilter(plan.effects, ShipActionUsedDiff).map(diff => {
const ship = plan.battle.getShip(diff.ship_id);
if (ship && ship.isPlayedBy(plan.player)) {
const action = ship.actions.getById(diff.action);
if (action) {
return action.getPowerUsage(ship);
} else {
return 0;
}
} else {
return 0;
}
}));
return available ? (used - toggled - available) / available : 0;
}
/**
* Evaluate the effect on health to the enemy, between -1 and 1
*/
static healthEnemies(plan: AIPlan): number {
let enemies = imaterialize(plan.battle.ienemies(plan.player, true));
return -getHealthDiffShips(plan, enemies);
}
/**
* Evaluate the effect on health to allied ships, between -1 and 1
*/
static healthAllies(plan: AIPlan): number {
let allies = imaterialize(plan.battle.iallies(plan.player, true));
return getHealthDiffShips(plan, allies);
}
/**
* Evaluate the cost of overheating equipments
*/
static overheat(plan: AIPlan): number {
return sum(cfilter(plan.effects, ShipActionUsedDiff).map(diff => {
const ship = plan.battle.getShip(diff.ship_id);
if (ship && ship.isPlayedBy(plan.player)) {
const action = ship.actions.getById(diff.action);
if (action) {
const cooldown = ship.actions.getCooldown(action);
if (cooldown.willOverheat()) {
return -Math.min(1, 0.4 * cooldown.cooling);
} else {
return 0;
}
} else {
return 0;
}
} else {
return 0;
}
}));
}
/**
* Evaluate the gain or loss of active effects
*/
static activeEffects(plan: AIPlan): number {
let result = 0;
plan.effects.forEach(effect => {
if (effect instanceof ShipEffectAddedDiff || effect instanceof ShipEffectRemovedDiff) {
let target = plan.battle.getShip(effect.ship_id);
let enemy = target && !target.isPlayedBy(plan.player);
let beneficial = effect.effect.isBeneficial();
if (effect instanceof ShipEffectRemovedDiff) {
beneficial = !beneficial;
}
// TODO Evaluate the "power" of the effect
if ((beneficial && !enemy) || (!beneficial && enemy)) {
result += 1;
} else {
result -= 1;
}
}
});
return clamp(result / plan.battle.ships.count(), -1, 1);
}
}
}