1
0
Fork 0
This commit is contained in:
Michaël Lemaire 2018-07-09 12:28:18 +02:00
parent b4302da855
commit bb67458180
15 changed files with 81 additions and 121 deletions

View File

@ -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

View File

@ -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);

View File

@ -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
*/

View File

@ -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]);
}
});

View File

@ -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

View File

@ -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);

View File

@ -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;

View File

@ -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 => {

View File

@ -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[] {

View File

@ -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 {

View File

@ -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 => {

View File

@ -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[] {

View File

@ -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[] {

View File

@ -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));

View File

@ -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);
}
}
}