WIP
This commit is contained in:
parent
b4302da855
commit
bb67458180
1
TODO.md
1
TODO.md
|
@ -49,6 +49,7 @@ Battle
|
|||
* Toggle bar/text display in power section of action bar
|
||||
* Show a cooldown indicator on move action icon, if the simulation would cause the engine to overheat
|
||||
* [WIP] Add an hexagonal grid
|
||||
* Use the grid for "border" exclusion areas
|
||||
* Use distances in units of this grid (add a multiplier to be compatible with no grid mode)
|
||||
* Fix repel effect to snap to grid (beware of two ships being moved to the same location!)
|
||||
* Fix engine targetting producing out-of-grid coordinates because of exclusion areas
|
||||
|
|
|
@ -5,6 +5,22 @@ module TK.SpaceTac.Specs {
|
|||
check.nears(arenaAngle({ x: 0, y: 0 }, { x: 1, y: 1 }), Math.PI / 4);
|
||||
})
|
||||
|
||||
test.case("checks if a location is in range of another", check => {
|
||||
check.in("first order", check => {
|
||||
check.equals(arenaInRange({ x: 0, y: 0 }, { x: 4, y: 4 }, 5), false, "<5");
|
||||
check.equals(arenaInRange({ x: 0, y: 0 }, { x: 4, y: 4 }, 8), true, "<8");
|
||||
});
|
||||
|
||||
check.in("second order", check => {
|
||||
check.equals(arenaInRange({ x: 4, y: 4 }, { x: 0, y: 0 }, 5), false, "<5");
|
||||
check.equals(arenaInRange({ x: 4, y: 4 }, { x: 0, y: 0 }, 8), true, "<8");
|
||||
});
|
||||
|
||||
check.equals(arenaInRange({ x: 0, y: 0 }, { x: 0.99999999999999, y: 0 }, 1), true, "0.99999999999999");
|
||||
check.equals(arenaInRange({ x: 0, y: 0 }, { x: 1.00000000000001, y: 0 }, 1), true, "1.00000000000001");
|
||||
check.equals(arenaInRange({ x: 0, y: 0 }, { x: 1.000001, y: 0 }, 1), false, "1.000001");
|
||||
})
|
||||
|
||||
test.case("computes an angular difference", check => {
|
||||
check.equals(angularDifference(0.5, 1.5), 1.0);
|
||||
check.nears(angularDifference(0.5, 1.5 + Math.PI * 6), 1.0);
|
||||
|
|
|
@ -75,6 +75,13 @@ module TK.SpaceTac {
|
|||
return Math.sqrt(dx * dx + dy * dy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a location is in range of another (accounting for rounding errors)
|
||||
*/
|
||||
export function arenaInRange(loc1: IArenaLocation, loc2: IArenaLocation, range: number): boolean {
|
||||
return arenaDistance(loc1, loc2) - range < 1e-8;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a location is inside an area
|
||||
*/
|
||||
|
|
|
@ -63,14 +63,6 @@ module TK.SpaceTac {
|
|||
return first(actions, action => this.ship.actions.getCooldown(action).canUse());
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that a move action can reach a given destination
|
||||
*/
|
||||
canMoveTo(action: MoveAction, target: Target): boolean {
|
||||
let checked = action.checkLocationTarget(this.ship, target);
|
||||
return checked != null && checked.x == target.x && checked.y == target.y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an iterator for scanning a circle
|
||||
*/
|
||||
|
@ -104,7 +96,7 @@ module TK.SpaceTac {
|
|||
|
||||
let candidates: [number, Target][] = [];
|
||||
iforeach(this.scanCircle(target.x, target.y, radius), candidate => {
|
||||
if (this.canMoveTo(action, candidate)) {
|
||||
if (action.checkLocationTarget(this.ship, candidate)) {
|
||||
candidates.push([candidate.getDistanceTo(this.ship.location), candidate]);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -110,10 +110,7 @@ module TK.SpaceTac {
|
|||
|
||||
// Check if a target is in range from a specific point
|
||||
isInRange(x: number, y: number, radius: number): boolean {
|
||||
var dx = this.x - x;
|
||||
var dy = this.y - y;
|
||||
var length = Math.sqrt(dx * dx + dy * dy);
|
||||
return (length <= radius);
|
||||
return arenaInRange(this, new ArenaLocation(x, y), radius);
|
||||
}
|
||||
|
||||
// Constraint a target, to be in a given range from a specific point
|
||||
|
|
|
@ -17,6 +17,7 @@ module TK.SpaceTac {
|
|||
var battle = new Battle(fleet1, fleet2);
|
||||
if (!hexgrid) {
|
||||
battle.grid = new PixelGrid();
|
||||
battle.placeShips();
|
||||
}
|
||||
battle.ships.list().forEach(ship => TestTools.setShipModel(ship, 1, 0));
|
||||
battle.play_order = fleet1.ships.concat(fleet2.ships);
|
||||
|
|
|
@ -236,9 +236,9 @@ module TK.SpaceTac {
|
|||
*
|
||||
* Will call checkLocationTarget or checkShipTarget by default
|
||||
*/
|
||||
checkTarget(ship: Ship, target: Target): Target | null {
|
||||
checkTarget(ship: Ship, target: Target): boolean {
|
||||
if (this.checkCannotBeApplied(ship)) {
|
||||
return null;
|
||||
return false;
|
||||
} else {
|
||||
if (target.isShip()) {
|
||||
return this.checkShipTarget(ship, target);
|
||||
|
@ -248,16 +248,18 @@ module TK.SpaceTac {
|
|||
}
|
||||
}
|
||||
|
||||
// Method to reimplement to check if a space target is suitable
|
||||
// Must return null if the target can't be applied, an altered target, or the original target
|
||||
protected checkLocationTarget(ship: Ship, target: Target): Target | null {
|
||||
return null;
|
||||
/**
|
||||
* Method to reimplement to check if a space target is suitable
|
||||
*/
|
||||
protected checkLocationTarget(ship: Ship, target: Target): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Method to reimplement to check if a ship target is suitable
|
||||
// Must return null if the target can't be applied, an altered target, or the original target
|
||||
protected checkShipTarget(ship: Ship, target: Target): Target | null {
|
||||
return null;
|
||||
/**
|
||||
* Method to reimplement to check if a ship target is suitable
|
||||
*/
|
||||
protected checkShipTarget(ship: Ship, target: Target): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -302,19 +304,18 @@ module TK.SpaceTac {
|
|||
return false;
|
||||
}
|
||||
|
||||
let checked_target = this.checkTarget(ship, target);
|
||||
if (!checked_target) {
|
||||
if (!this.checkTarget(ship, target)) {
|
||||
console.warn("Action rejected - invalid target", ship, this, target);
|
||||
return false;
|
||||
}
|
||||
|
||||
let cost = this.getPowerUsage(ship, checked_target);
|
||||
let cost = this.getPowerUsage(ship, target);
|
||||
if (ship.getValue("power") < cost) {
|
||||
console.warn("Action rejected - not enough power", ship, this, checked_target);
|
||||
console.warn("Action rejected - not enough power", ship, this, target);
|
||||
return false;
|
||||
}
|
||||
|
||||
let diffs = this.getDiffs(ship, battle, checked_target);
|
||||
let diffs = this.getDiffs(ship, battle, target);
|
||||
if (diffs.length) {
|
||||
battle.applyDiffs(diffs);
|
||||
return true;
|
||||
|
|
|
@ -18,12 +18,13 @@ module TK.SpaceTac.Specs {
|
|||
let action = new DeployDroneAction("testdrone", { power: 0 }, { deploy_distance: 8 });
|
||||
ship.actions.addCustom(action);
|
||||
|
||||
check.equals(action.checkTarget(ship, new Target(8, 0, null)), new Target(8, 0, null));
|
||||
check.equals(action.checkTarget(ship, new Target(12, 0, null)), new Target(8, 0, null));
|
||||
check.equals(action.checkTarget(ship, new Target(8, 0, null)), true);
|
||||
check.equals(action.checkTarget(ship, new Target(12, 0, null)), false);
|
||||
check.equals(action.checkTarget(ship, Target.newFromShip(ship)), false);
|
||||
|
||||
let other = new Ship();
|
||||
other.setArenaPosition(8, 0);
|
||||
check.equals(action.checkTarget(ship, new Target(8, 0, other)), null);
|
||||
check.equals(action.checkTarget(ship, new Target(8, 0, other)), false);
|
||||
});
|
||||
|
||||
test.case("deploys a new drone", check => {
|
||||
|
|
|
@ -67,9 +67,12 @@ module TK.SpaceTac {
|
|||
return result;
|
||||
}
|
||||
|
||||
checkLocationTarget(ship: Ship, target: Target): Target {
|
||||
target = target.constraintInRange(ship.arena_x, ship.arena_y, this.deploy_distance);
|
||||
return target;
|
||||
checkShipTarget(ship: Ship, target: Target): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
checkLocationTarget(ship: Ship, target: Target): boolean {
|
||||
return arenaInRange(ship.location, target, this.deploy_distance);
|
||||
}
|
||||
|
||||
getSpecificDiffs(ship: Ship, battle: Battle, target: Target): BaseBattleDiff[] {
|
||||
|
|
|
@ -59,8 +59,8 @@ module TK.SpaceTac {
|
|||
}
|
||||
}
|
||||
|
||||
protected checkShipTarget(ship: Ship, target: Target): Target | null {
|
||||
return ship.is(target.ship_id) ? target : null;
|
||||
protected checkShipTarget(ship: Ship, target: Target): boolean {
|
||||
return ship.is(target.ship_id);
|
||||
}
|
||||
|
||||
getTargettingMode(ship: Ship): ActionTargettingMode {
|
||||
|
|
|
@ -3,6 +3,7 @@ module TK.SpaceTac.Specs {
|
|||
test.case("checks movement against remaining AP", check => {
|
||||
var ship = new Ship();
|
||||
var battle = new Battle(ship.fleet);
|
||||
battle.grid = new PixelGrid();
|
||||
TestTools.setShipPlaying(battle, ship);
|
||||
ship.setValue("power", 6);
|
||||
ship.arena_x = 0;
|
||||
|
@ -11,14 +12,13 @@ module TK.SpaceTac.Specs {
|
|||
ship.actions.addCustom(action);
|
||||
|
||||
var result = action.checkTarget(ship, Target.newFromLocation(0, 20));
|
||||
check.equals(result, Target.newFromLocation(0, 20));
|
||||
check.equals(result, true);
|
||||
|
||||
result = action.checkTarget(ship, Target.newFromLocation(0, 80));
|
||||
check.nears(nn(result).y, 59.9);
|
||||
check.equals(result, false);
|
||||
|
||||
ship.setValue("power", 0);
|
||||
result = action.checkTarget(ship, Target.newFromLocation(0, 80));
|
||||
check.equals(result, null);
|
||||
result = action.checkTarget(ship, Target.newFromLocation(0, 0));
|
||||
check.equals(result, false);
|
||||
});
|
||||
|
||||
test.case("forbids targetting a ship", check => {
|
||||
|
@ -37,11 +37,11 @@ module TK.SpaceTac.Specs {
|
|||
test.case("applies and reverts", check => {
|
||||
let battle = TestTools.createBattle();
|
||||
let ship = battle.play_order[0];
|
||||
ship.setArenaPosition(500, 600)
|
||||
ship.setArenaPosition(500, 600);
|
||||
TestTools.setShipModel(ship, 100, 0, 20);
|
||||
ship.setValue("power", 5);
|
||||
|
||||
let action = new MoveAction("Engine", { distance_per_power: 1 });
|
||||
let action = new MoveAction("Engine", { distance_per_power: 4 });
|
||||
ship.actions.addCustom(action);
|
||||
|
||||
TestTools.actionChain(check, battle, [
|
||||
|
@ -53,9 +53,9 @@ module TK.SpaceTac.Specs {
|
|||
check.equals(ship.getValue("power"), 5, "power");
|
||||
},
|
||||
check => {
|
||||
check.nears(ship.arena_x, 504.382693, 5, "ship X");
|
||||
check.nears(ship.arena_y, 602.191346, 5, "ship Y");
|
||||
check.equals(ship.getValue("power"), 0, "power");
|
||||
check.equals(ship.arena_x, 510, "ship X");
|
||||
check.equals(ship.arena_y, 605, "ship Y");
|
||||
check.equals(ship.getValue("power"), 2, "power");
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
@ -71,51 +71,19 @@ module TK.SpaceTac.Specs {
|
|||
var action = new MoveAction("Engine", { distance_per_power: 1000, safety_distance: 200 });
|
||||
|
||||
var result = action.checkLocationTarget(ship, Target.newFromLocation(700, 500));
|
||||
check.equals(result, Target.newFromLocation(700, 500));
|
||||
check.equals(result, true);
|
||||
|
||||
result = action.checkLocationTarget(ship, Target.newFromLocation(800, 500));
|
||||
check.equals(result, Target.newFromLocation(800, 500));
|
||||
check.equals(result, true);
|
||||
|
||||
result = action.checkLocationTarget(ship, Target.newFromLocation(900, 500));
|
||||
check.equals(result, Target.newFromLocation(800, 500));
|
||||
check.equals(result, false);
|
||||
|
||||
result = action.checkLocationTarget(ship, Target.newFromLocation(1000, 500));
|
||||
check.equals(result, Target.newFromLocation(800, 500));
|
||||
check.equals(result, false);
|
||||
|
||||
result = action.checkLocationTarget(ship, Target.newFromLocation(1200, 500));
|
||||
check.equals(result, Target.newFromLocation(1200, 500));
|
||||
});
|
||||
|
||||
test.case("exclusion radius is applied correctly over two ships", check => {
|
||||
var battle = TestTools.createBattle(1, 2);
|
||||
var ship = battle.fleets[0].ships[0];
|
||||
var enemy1 = battle.fleets[1].ships[0];
|
||||
var enemy2 = battle.fleets[1].ships[1];
|
||||
TestTools.setShipModel(ship, 100, 0, 100);
|
||||
enemy1.setArenaPosition(0, 800);
|
||||
enemy2.setArenaPosition(0, 1000);
|
||||
|
||||
var action = new MoveAction("Engine", { distance_per_power: 1000, safety_distance: 150 });
|
||||
|
||||
var result = action.checkLocationTarget(ship, Target.newFromLocation(0, 1100));
|
||||
check.equals(result, Target.newFromLocation(0, 650));
|
||||
});
|
||||
|
||||
test.case("exclusion radius does not make the ship go back", check => {
|
||||
var battle = TestTools.createBattle(1, 2);
|
||||
var ship = battle.fleets[0].ships[0];
|
||||
var enemy1 = battle.fleets[1].ships[0];
|
||||
var enemy2 = battle.fleets[1].ships[1];
|
||||
TestTools.setShipModel(ship, 100, 0, 100);
|
||||
enemy1.setArenaPosition(0, 500);
|
||||
enemy2.setArenaPosition(0, 800);
|
||||
|
||||
var action = new MoveAction("Engine", { distance_per_power: 1000, safety_distance: 600 });
|
||||
|
||||
let result = action.checkLocationTarget(ship, Target.newFromLocation(0, 1000));
|
||||
check.equals(result, null);
|
||||
result = action.checkLocationTarget(ship, Target.newFromLocation(0, 1400));
|
||||
check.equals(result, Target.newFromLocation(0, 1400));
|
||||
check.equals(result, true);
|
||||
});
|
||||
|
||||
test.case("builds a textual description", check => {
|
||||
|
|
|
@ -124,9 +124,9 @@ module TK.SpaceTac {
|
|||
return target.constraintInRange(ship.arena_x, ship.arena_y, max_distance);
|
||||
}
|
||||
|
||||
checkLocationTarget(ship: Ship, target: Target): Target | null {
|
||||
checkLocationTarget(ship: Ship, target: Target): boolean {
|
||||
let fixed_target = this.applyExclusion(ship, this.applyReachableRange(ship, target));
|
||||
return fixed_target.getDistanceTo(target) < 1e-8 ? target : null;
|
||||
return fixed_target.getDistanceTo(target) < 1e-8;
|
||||
}
|
||||
|
||||
protected getSpecificDiffs(ship: Ship, battle: Battle, target: Target): BaseBattleDiff[] {
|
||||
|
|
|
@ -67,8 +67,8 @@ module TK.SpaceTac {
|
|||
return result;
|
||||
}
|
||||
|
||||
checkShipTarget(ship: Ship, target: Target): Target | null {
|
||||
return ship.is(target.ship_id) ? target : null;
|
||||
checkShipTarget(ship: Ship, target: Target): boolean {
|
||||
return ship.is(target.ship_id);
|
||||
}
|
||||
|
||||
getSpecificDiffs(ship: Ship, battle: Battle, target: Target, apply_effects = true): BaseBattleDiff[] {
|
||||
|
|
|
@ -38,30 +38,6 @@ module TK.SpaceTac.Specs {
|
|||
]);
|
||||
})
|
||||
|
||||
test.case("transforms ship target in location target, when the weapon has blast radius", check => {
|
||||
let ship1 = new Ship();
|
||||
ship1.setArenaPosition(50, 10);
|
||||
let ship2 = new Ship();
|
||||
ship2.setArenaPosition(150, 10);
|
||||
let action = TestTools.addWeapon(ship1, 1, 0, 100, 30);
|
||||
|
||||
let target = action.checkTarget(ship1, new Target(150, 10));
|
||||
check.equals(target, new Target(150, 10));
|
||||
|
||||
target = action.checkTarget(ship1, Target.newFromShip(ship2));
|
||||
check.equals(target, new Target(150, 10));
|
||||
|
||||
ship1.setArenaPosition(30, 10);
|
||||
|
||||
target = action.checkTarget(ship1, Target.newFromShip(ship2));
|
||||
check.equals(target, new Target(130, 10));
|
||||
|
||||
ship1.setArenaPosition(0, 10);
|
||||
|
||||
target = action.checkTarget(ship1, Target.newFromShip(ship2));
|
||||
check.equals(target, new Target(100, 10));
|
||||
})
|
||||
|
||||
test.case("lists impacted ships", check => {
|
||||
let ship1 = new Ship(null, "S1");
|
||||
ship1.setArenaPosition(10, 50);
|
||||
|
@ -104,7 +80,7 @@ module TK.SpaceTac.Specs {
|
|||
let battle = TestTools.createBattle();
|
||||
let ship = battle.play_order[0];
|
||||
let action = TestTools.addWeapon(ship, 1, 0, 100, 30);
|
||||
check.patch(action, "checkTarget", (ship: Ship, target: Target) => target);
|
||||
check.patch(action, "checkTarget", (ship: Ship, target: Target) => true);
|
||||
check.equals(ship.arena_angle, 0);
|
||||
|
||||
let result = action.apply(battle, ship, Target.newFromLocation(10, 20));
|
||||
|
|
|
@ -124,27 +124,24 @@ module TK.SpaceTac {
|
|||
}
|
||||
}
|
||||
|
||||
checkLocationTarget(ship: Ship, target: Target): Target | null {
|
||||
checkLocationTarget(ship: Ship, target: Target): boolean {
|
||||
if (target && (this.blast > 0 || this.angle > 0)) {
|
||||
target = target.constraintInRange(ship.arena_x, ship.arena_y, this.range);
|
||||
return target;
|
||||
return arenaInRange(ship.location, target, this.range);
|
||||
} else {
|
||||
return null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
checkShipTarget(ship: Ship, target: Target): Target | null {
|
||||
checkShipTarget(ship: Ship, target: Target): boolean {
|
||||
if (this.range > 0 && ship.is(target.ship_id)) {
|
||||
// No self fire
|
||||
return null;
|
||||
return false;
|
||||
} else {
|
||||
// Check if target is in range
|
||||
if (this.blast > 0 || this.angle > 0) {
|
||||
return this.checkLocationTarget(ship, new Target(target.x, target.y));
|
||||
} else if (target.isInRange(ship.arena_x, ship.arena_y, this.range)) {
|
||||
return target;
|
||||
} else {
|
||||
return null;
|
||||
return arenaInRange(ship.location, target, this.range);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue