152 lines
5.6 KiB
TypeScript
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);
|
|
}
|
|
}
|
|
} |