1
0
Fork 0
spacetac/src/core/TestTools.ts

189 lines
7.3 KiB
TypeScript

module TK.SpaceTac {
// unit testing utilities
export class TestTools {
// Create a battle between two fleets, with a fixed play order (owned ships, then enemy ships)
static createBattle(own_ships = 1, enemy_ships = 1): Battle {
var fleet1 = new Fleet(new Player("Attacker"));
var fleet2 = new Fleet(new Player("Defender"));
while (own_ships--) {
fleet1.addShip();
}
while (enemy_ships--) {
fleet2.addShip();
}
var battle = new Battle(fleet1, fleet2);
battle.ships.list().forEach(ship => TestTools.setShipModel(ship, 1, 0));
battle.play_order = fleet1.ships.concat(fleet2.ships);
battle.setPlayingShip(battle.play_order[0]);
return battle;
}
/**
* Add an engine, allowing a ship to move *distance*, for each action points
*/
static addEngine(ship: Ship, distance: number): MoveAction {
let action = new MoveAction("Engine", { distance_per_power: distance });
ship.actions.addCustom(action);
return action;
}
/**
* Add a weapon to a ship
*/
static addWeapon(ship: Ship, damage = 100, power_usage = 1, max_distance = 100, blast = 0, angle = 0): TriggerAction {
let action = new TriggerAction("Weapon", {
effects: [new DamageEffect(damage, DamageEffectMode.SHIELD_THEN_HULL)],
power: power_usage,
range: max_distance,
blast: blast,
angle: angle,
});
ship.actions.addCustom(action);
return action;
}
/**
* Force the current playing ship
*/
static setShipPlaying(battle: Battle, ship: Ship): void {
add(battle.play_order, ship);
battle.play_index = battle.play_order.indexOf(ship);
ship.playing = true;
}
/**
* Set a ship attributes (by changing its model)
*/
static setShipModel(ship: Ship, hull: number, shield = 0, power = 0, level = 1, actions: BaseAction[] = [], effects: BaseEffect[] = []) {
let model = new ShipModel();
ship.level.forceLevel(level);
ship.model = model;
// TODO Use a BaseModel subclass would be prettier
model.getActions = () => actions;
model.getEffects = () => effects.concat([
new AttributeEffect("hull_capacity", hull),
new AttributeEffect("shield_capacity", shield),
new AttributeEffect("power_capacity", power),
]);
ship.actions.updateFromShip(ship);
ship.updateAttributes();
ship.restoreHealth();
ship.setValue("power", power);
}
/**
* Force a ship attribute to a given value
*
* Beware that a call to ship.updateAttributes() may cancel this
*/
static setAttribute(ship: Ship, name: keyof ShipAttributes, value: number): void {
let attr = ship.attributes[name];
attr.reset();
attr.addModifier(value);
}
/**
* Check a diff chain on a given battle
*
* This will apply all diffs, then reverts them, checking at each step the battle state
*/
static diffChain(check: TestContext, battle: Battle, diffs: BaseBattleDiff[], checks: ((check: TestContext) => void)[]): void {
checks[0](check.sub("initial state"));
for (let i = 0; i < diffs.length; i++) {
diffs[i].apply(battle);
checks[i + 1](check.sub(`after diff ${i + 1} applied`));
}
for (let i = diffs.length - 1; i >= 0; i--) {
diffs[i].revert(battle);
checks[i](check.sub(`after diff ${i + 1} reverted`));
}
}
/**
* Check an action chain on a given battle
*
* This will apply all actions, then reverts them, checking at each step the battle state
*/
static actionChain(check: TestContext, battle: Battle, actions: [Ship, BaseAction, Target | undefined][], checks: ((check: TestContext) => void)[]): void {
checks[0](check.sub("initial state"));
for (let i = 0; i < actions.length; i++) {
let [ship, action, target] = actions[i];
battle.setPlayingShip(ship);
let result = battle.applyOneAction(action.id, target);
check.equals(result, true, `action ${i + 1} successfully applied`);
checks[i + 1](check.sub(`after action ${i + 1} applied`));
}
for (let i = actions.length - 1; i >= 0; i--) {
battle.revertOneAction();
checks[i](check.sub(`after action ${i + 1} reverted`));
}
}
}
function strip<T>(obj: T, attr: keyof T): any {
let result: any = {};
copyfields(obj, result);
delete result[attr];
return result;
}
function strip_id(effect: RObject): any {
if (effect instanceof StickyEffect) {
let result = strip(effect, "id");
result.base = strip_id(result.base);
return result;
} else {
return strip(effect, "id");
}
}
export function compare_effects(check: TestContext, effects1: BaseEffect[], effects2: BaseEffect[]): void {
check.equals(effects1.map(strip_id), effects2.map(strip_id), "effects");
}
export function compare_action(check: TestContext, action1: BaseAction | null, action2: BaseAction | null): void {
if (action1 === null || action2 === null) {
check.equals(action1, action2, "action");
} else {
check.equals(strip_id(action1), strip_id(action2), "action");
}
}
export function compare_trigger_action(check: TestContext, action1: BaseAction | null, action2: TriggerAction | null): void {
if (action1 === null || action2 === null || !(action1 instanceof TriggerAction)) {
check.equals(action1, action2, "action");
} else {
check.equals(strip_id(strip(action1, "effects")), strip_id(strip(action2, "effects")), "action");
compare_effects(check, action1.effects, action2.effects);
}
}
export function compare_toggle_action(check: TestContext, action1: BaseAction | null, action2: ToggleAction | null): void {
if (action1 === null || action2 === null || !(action1 instanceof ToggleAction)) {
check.equals(action1, action2, "action");
} else {
check.equals(strip_id(strip(action1, "effects")), strip_id(strip(action2, "effects")), "action");
compare_effects(check, action1.effects, action2.effects);
}
}
export function compare_drone_action(check: TestContext, action1: BaseAction | null, action2: DeployDroneAction | null): void {
if (action1 === null || action2 === null || !(action1 instanceof DeployDroneAction)) {
check.equals(action1, action2, "action");
} else {
check.equals(strip_id(strip(action1, "drone_effects")), strip_id(strip(action2, "drone_effects")), "action");
compare_effects(check, action1.drone_effects, action2.drone_effects);
}
}
}